/* Дизайн-система АНО ДПО «Эксперт-Образование».
   Требования и обоснование — docs/18_design_system.md.
   Цвета взяты из логотипа, контрасты проверены по WCAG 2.1.
   Значения цветов и размеров задаются только здесь и используются через var(). */

:root {
  /* Фирменный фиолетовый */
  --brand-900: #4c0262;
  --brand-800: #640380;
  --brand-700: #7a049d;
  --brand-600: #8b04b2;
  --brand-100: #fae9fe;
  --brand-50:  #fdf9ff;

  /* Фирменный оранжевый. --accent-500 — только заливка, текстом не используется:
     контраст на белом 2.17, порог не проходит. Для текста есть --accent-800. */
  --accent-800: #8c5300;
  --accent-600: #e08500;
  --accent-500: #ff9700;
  --accent-100: #fff5e5;

  /* Нейтральные */
  --ink:        #1a1226;
  --ink-muted:  #5b5169;
  --ink-invert: #ffffff;
  --surface:    #ffffff;
  --bg:         #faf7fc;
  --line:       #e6dced;

  /* Состояния */
  --success: #1c7a4a;
  --warning: #8c5300;
  --danger:  #b3261e;
  --info:    #1f5f9e;

  /* Шрифты подключаются локально, без обращения к сторонним доменам.
     Засечки в заголовках дают академический характер (docs/18 §3.1). */
  --font-heading: Georgia, 'PT Serif', 'Times New Roman', serif;
  --font-body: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;

  /* Шаг отступов кратен 4 */
  --space-1: 4px;   --space-2: 8px;   --space-3: 12px;  --space-4: 16px;
  --space-6: 24px;  --space-8: 32px;  --space-12: 48px; --space-16: 64px;
  --space-24: 96px;

  --radius-sm: 8px;
  --radius-md: 16px;
  --radius-pill: 999px;

  /* Высота липкой шапки. Используется в отступе прокрутки к якорям
     и в расчёте высоты первого экрана — три места должны совпадать. */
  --header-h: 64px;
  /* Высота строки новостей. Стоит над первым экраном и вычитается
     из его высоты, иначе экран уезжает вниз ровно на эту полосу. */
  --ticker-h: 52px;

  --shadow: 0 14px 40px rgba(26, 18, 38, 0.08);
  --shadow-lifted: 0 20px 48px rgba(26, 18, 38, 0.14);

  /* Версия для слабовидящих: два цвета схемы и межстрочный интервал.
     Значения по умолчанию объявлены здесь, а не только на `body.vision`,
     чтобы `var(--v-ink)` в правилах панели всегда во что-то разрешался:
     нераспознанный токен выбрасывает правило целиком. Схемы перекрывают
     их на `body`. */
  --v-bg: #ffffff;
  --v-ink: #000000;
  --v-line: 1.5;
}

*, *::before, *::after { box-sizing: border-box; }

/* Переход по якорю из меню не должен прятать заголовок блока под липкой
   шапкой: отсюда scroll-padding-top, равный её высоте. */
/* Атрибут hidden прячет элемент правилом браузера, а любое собственное
   display его перебивает: правила автора сильнее правил браузера независимо
   от специфичности. В студии это уже стоило белой панели поверх страницы. */
[hidden] { display: none !important; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + var(--space-4));
  /* Место под полосу прокрутки резервируется всегда. Без этого страница
     дёргается вбок в момент, когда открытое окно прячет прокрутку. */
  scrollbar-gutter: stable;
}

/* Пока открыто окно, список под ним не прокручивается: иначе посетитель
   закрывает карточку и оказывается не там, где её открыл. Браузеры сами
   этого не делают — <dialog> блокирует нажатия, но не колесо мыши. */
html.is-modal-open { overflow: hidden; }

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.65;
  background: var(--bg);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  color: var(--brand-900);
  text-wrap: balance;
}
h1 { font-size: clamp(2rem, 4.5vw, 2.75rem); line-height: 1.15; font-weight: 700; }
h2 { font-size: clamp(1.6rem, 3vw, 2rem); line-height: 1.2; font-weight: 700; }
h3 { font-family: var(--font-body); font-size: 1.3rem; line-height: 1.3; font-weight: 600; }
h4 { font-family: var(--font-body); font-size: 1.05rem; font-weight: 600; }

p { margin: 0 0 var(--space-3); max-width: 68ch; }
a { color: var(--brand-700); text-decoration: none; }
a:hover { color: var(--brand-900); text-decoration: underline; }
img { max-width: 100%; }
.muted { color: var(--ink-muted); font-size: 0.9rem; }

/* Вспомогательный класс: скрыто от глаз, но доступно скринридерам
   и поисковым системам. Для подписей полей и заголовков, которые нужны
   разметке, но не нужны в макете. display: none убрал бы элемент
   и из программ чтения тоже. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Кольцо фокуса обязательно и убирать его нельзя (docs/18 §6). */
:focus-visible {
  outline: 2px solid var(--brand-600);
  outline-offset: 2px;
  border-radius: var(--space-1);
}

/* Ссылка «к основному содержанию» — первая в порядке обхода клавиатурой. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: var(--space-2);
  z-index: 100;
  padding: var(--space-3) var(--space-4);
  background: var(--brand-600);
  color: var(--ink-invert);
  border-radius: var(--radius-sm);
}
.skip-link:focus { left: var(--space-4); }

.container { width: min(1120px, calc(100% - 32px)); margin: 0 auto; }

/* --- Шапка ------------------------------------------------------------- */


.site-header {
  position: sticky;
  top: 0;
  z-index: 30;
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}
/* Три группы: слева опознавание организации, по центру разделы сайта,
   справа служебное и вход.

   Сетка «1fr auto 1fr» ставит середину ровно по центру окна независимо
   от ширины боковых групп. При выравнивании через flex середина съезжала
   вслед за длиной левой группы, и на разных страницах меню стояло
   по-разному — левая группа то с якорями главной, то без них.

   Шапка идёт во всю ширину окна, а не по контейнеру: логотип уезжал
   к середине, а по краям оставались пустые поля. */
.nav-wrap {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-2) var(--space-6);
}
.nav-side { display: flex; align-items: center; gap: var(--space-4); min-width: 0; }
.nav-side-right { justify-content: flex-end; gap: var(--space-3); }
.brand {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--brand-900);
  /* Название в две строки с межстрочным интервалом абзаца (1.65) давало
     разрыв шире самих букв. Заголовкам — 1.1–1.2. */
  line-height: 1.15;
}
.brand:hover { text-decoration: none; }
.brand-logo {
  width: 40px;
  height: 40px;
  /* Логотип отделён от названия чертой: без неё знак и текст читаются
     как одно склеенное пятно. */
}
/* Название рядом с логотипом. В одну строку: перенос на две наезжал
   на первый пункт меню — высота строки шапки фиксирована. */
.brand-name { white-space: nowrap; font-size: 0.98rem; }

/* Якоря на блоки главной стоят сразу за логотипом и отделены от разделов
   сайта чертой: это навигация другого рода. Видны только на главной. */
.landing-nav {
  display: flex;
  gap: var(--space-4);
  padding-left: var(--space-4);
  border-left: 1px solid var(--line);
}
/* Якоря главной набраны скромнее разделов: они ведут к абзацу,
   а не в раздел, и не должны спорить с ним за внимание. */
.landing-nav a { color: var(--ink-muted); font-size: 0.92rem; white-space: nowrap; }
.landing-nav a:hover { color: var(--brand-700); }

/* Якорям нужно место, и это посчитано, а не прикинуто. Левой группе
   требуется 388 px: логотип с названием 238, промежуток 16 и сами якоря 134.
   Ширина колонки сетки — это (окно − разделы − два промежутка) / 2.

   Порог поднят с 1440 до 1600 вместе с пятым разделом «Интенсивы»
   (29.08.2026). Разделы занимали 533 px и стали занимать 657 — колонка
   на 1440 сжалась с 453 до 360, и «Контакты» снова налезали на
   «Репетиторский центр», на 28 px. Замер, а не расчёт по разметке:
   прокрутки при этом не возникает, и обычная проверка «есть ли прокрутка»
   такого не видит (скилл ui-layout, ошибки 20 и 21).

   Порог считается так: (окно − 657 − 32) / 2 ≥ 388, то есть от 1465 px.
   Взято 1600 — с запасом на следующий пункт и на другую длину названия
   организации: 1465 прошло бы впритык и сломалось бы от одной буквы.

   Ниже порога якоря из строки убираются: на 1280 и уже они есть
   в выпадающем меню, а между ним и порогом остаются подвал и сама
   страница — блоки, к которым они ведут, находятся на ней же. Понижать
   порог выпадающего меню ради двух якорей нельзя: 1366 — самая частая
   ширина ноутбука, и разделы сайта должны быть на виду именно там. */
@media (max-width: 1599px) {
  .landing-nav { display: none; }
}

/* Переключатель версии для слабовидящих — значок с подписью в правом углу.
   `flex: none` и запрет переноса обязательны: без них правая группа сжимает
   ссылку до ширины значка, и подпись ломается на две строки, поднимая
   всю шапку (скилл ui-layout, ошибка 5). */
.vision-toggle {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-width: 44px;
  min-height: 44px;
  color: var(--ink-muted);
  font-size: 1.1rem;
}
.vision-toggle-text { white-space: nowrap; font-size: 0.92rem; }
.vision-toggle:hover { color: var(--brand-700); }
.main-nav {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-wrap: nowrap;
}
/* Пункт меню — кнопка: заметная область нажатия, подсветка при наведении
   и заливка в разделе, где посетитель сейчас. Без последнего меню
   превращается в список ссылок, по которому непонятно, где ты. */
.nav-link {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 var(--space-3);
  border-radius: var(--radius-pill);
  color: var(--ink);
  font-size: 1rem;
  font-weight: 600;
  white-space: nowrap;
  transition: background-color .16s ease, color .16s ease;
}
.nav-link:hover { background: var(--brand-100); color: var(--brand-900); text-decoration: none; }
/* Пункт меню бывает кнопкой — когда он открывает окно, а не ведёт по адресу.
   Без сброса браузер рисует ему серую заливку и рамку, и в выпадающем меню
   такой пункт выглядит чужим среди ссылок. */
button.nav-link {
  border: 0;
  background: none;
  font: inherit;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.nav-link.is-active { background: var(--brand-600); color: var(--ink-invert); }
.nav-link.is-active:hover { background: var(--brand-800); color: var(--ink-invert); }

.header-actions {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: var(--space-6);
}
.nav-toggle { display: none; }
.nav-utility-item { display: none; }
/* Пункт меню, открывающий окно. Выглядит как остальные: посетителю
   не должно быть видно, что это кнопка, а не ссылка. */
.nav-link-button {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-pill);
  background: none;
  font: inherit;
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .16s ease, color .16s ease;
}
.nav-link-button:hover { background: var(--brand-100); color: var(--brand-900); }

/* --- Кнопки ------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 46px;
  padding: 0 var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font: inherit;
  font-size: 1rem;
  font-weight: 600;
  cursor: pointer;
  transition: background-color .18s ease, color .18s ease, transform .18s ease;
}
.btn { white-space: nowrap; }
/* 44, а не 40: это порог области нажатия (критерий 2.5.5). Меньшая кнопка
   мельче подушечки пальца, и «Записаться» на витрине — не то место, где
   стоит экономить четыре пикселя. Уменьшенной она остаётся за счёт полей
   и кегля, а не высоты. */
.btn-sm { min-height: 44px; padding: 0 var(--space-4); font-size: 0.92rem; }
.btn:hover { text-decoration: none; transform: translateY(-1px); }
/* Отправленная форма гасит свою кнопку до ответа сервера: второе нажатие
   слало второй такой же запрос. Погасшая кнопка — это и есть ответ
   «принято, ждите», поэтому подпись не меняется. */
.btn:disabled { opacity: 0.65; cursor: progress; transform: none; }
.btn-primary { background: var(--brand-600); color: var(--ink-invert); }
.btn-primary:hover { background: var(--brand-800); color: var(--ink-invert); }
.btn-secondary { background: transparent; border-color: var(--brand-600); color: var(--brand-700); }
.btn-secondary:hover { background: var(--brand-50); color: var(--brand-900); }
/* Целевая кнопка. Не более одной на странице. */
.btn-accent { background: var(--accent-500); color: var(--ink); }
.btn-accent:hover { background: var(--accent-600); color: var(--ink); }

/* --- Секции ------------------------------------------------------------ */
/* Блоки идут друг за другом с небольшим отступом. Растягивания на высоту
   окна больше нет: посетитель видел по одному блоку за прокрутку. */
.section { padding: var(--space-12) 0; }
/* Первая секция страницы начинается сразу под шапкой. Полный отступ здесь
   складывался с высотой шапки и служебной полосы и давал пустой экран
   над заголовком. */
main > .section:first-child { padding-top: var(--space-8); }

.section-soft { background: var(--brand-50); }
.section-heading { margin-bottom: var(--space-6); }
/* Заголовок страницы внутри той же секции, что и содержимое: два отступа
   подряд давали пустой экран между текстом и первым рядом карточек.

   Ограничения ширины у блока нет намеренно. С max-width: 70ch заголовок
   в 43 знака при кегле 44 пикселя не помещался и переносился, а лид
   разбивался на три строки — вместе выходило четыре строки вместо трёх.
   Длину строки держит сам лид, у заголовка её ограничивать нечем. */
.page-intro { margin-bottom: var(--space-8); }
.page-intro h1 {
  margin: 0 0 var(--space-3);
  /* Кегль считается от ширины окна, чтобы заголовок держался в одной строке
     и на 1024, а не только на широком мониторе. 43 знака при средней ширине
     буквы 0.5em занимают около 22em, делитель 24 добавляет запас.

     Нижняя граница обязательна. Формула без неё на 390 пикселях давала
     (390 − 64) / 24 = 13.6 px: заголовок страницы оказывался **мельче
     основного текста** на всех двадцати страницах с этим блоком. Найдено
     замером 20.08.2026 (скилл ui-layout, ошибка 24).

     На узком экране заголовок переносится, и это верно: одна строка там
     означала бы нечитаемый кегль. */
  font-size: clamp(1.6rem, calc((100vw - 4rem) / 24), 2.4rem);
}
/* Ширина пояснения подобрана так, чтобы оно занимало ровно две строки:
   вместе с заголовком выходит три, как и требовалось. */
.page-intro .lead { margin: 0; max-width: 60ch; }

/* Пояснение в одну строку — для витрин центра и школы профессий.
   Замер шрифтом system-ui (Segoe UI): самая длинная из двух строк, 113 знаков,
   занимает 1014 px при кегле 18 px, а контейнер шире — 1120 px. Мешал только
   предел в 60 знаков, он здесь снимается.

   Кегль считается от ширины окна, чтобы строка держалась и на узком мониторе:
   делитель 62 даёт 16 px при 1024 px — строка 878 px в контейнере 992 px,
   запас 114 px.
   Ниже 1024 px правило не применяется: одна строка потребовала бы кегля,
   который на телефоне не прочитать, поэтому текст переносится как обычно. */
.page-intro .lead-oneline { max-width: none; }

@media (min-width: 1024px) {
  .page-intro .lead-oneline {
    white-space: nowrap;
    font-size: min(1.15rem, calc((100vw - 32px) / 62));
  }
}
.eyebrow {
  display: inline-block;
  margin-bottom: var(--space-3);
  color: var(--accent-800);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  font-size: 0.8rem;
}
.lead { font-size: 1.15rem; color: var(--ink-muted); max-width: 62ch; }

.about-grid, .contacts-grid {
  display: grid;
  grid-template-columns: 1.15fr 0.85fr;
  gap: var(--space-8);
  align-items: center;
}
/* Первый экран должен помещаться в окно целиком: посетитель обязан увидеть
   и заголовок, и обе кнопки без прокрутки. 100svh — «малая» высота окна,
   она учитывает адресную строку мобильного браузера, в отличие от 100vh. */
/* Два направления рядом. Оба равнозначны: одинаковая ширина, одинаковая
   высота карточки, разное только оформление кнопки. */
/* Призыв над плитками: строка заголовка и строка пояснения.
   Больше — и плитки уезжают за нижний край окна. */
.hero-pitch {
  margin: 0 auto var(--space-8);
  text-align: center;
}
.hero-pitch h1 {
  margin: 0 0 var(--space-3);
  /* Заголовок держится в одну строку, и числа здесь — из замера браузером,
     а не из прикидки «0.5em на букву». Прежние 28em и делитель 29 были
     взяты по памяти и оказались малы: «Профессия за два месяца или
     уверенность перед экзаменом» занимает 1193 px при кегле 36.8 px,
     то есть 32.4em, а не 28.

     Отсюда обе величины:
     — делитель 33: при 1024 px кегль 29 px, строка 943 px в контейнере
       977 px. С делителем 29 строка была 1056 px и давала горизонтальную
       прокрутку на самой частой ширине ноутбука;
     — потолок 2.1rem: 32.4em × 33.6 px = 1089 px при контейнере 1120 px.
       С прежними 2.3rem строка вылезала из контейнера на широком мониторе
       и держалась в окне только за счёт полей страницы. */
  font-size: min(2.1rem, calc((100vw - 4rem) / 33));
  white-space: nowrap;
}
.hero-pitch p {
  /* Общее ограничение абзаца в 68 знаков здесь мешает: пояснение должно
     уместиться во вторую строку целиком. */
  max-width: none;
  margin: 0;
  color: var(--ink-muted);
  font-size: 1.1rem;
}

.direction-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
}
.direction-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
  overflow: hidden;
  transition: box-shadow .18s ease, transform .18s ease;
}
.direction-card:hover { box-shadow: var(--shadow-lifted); transform: translateY(-2px); }
/* Иллюстрация в карточке — не главное содержимое: она не должна быть выше
   текста, который сопровождает. Отсюда фиксированная высота, а не пропорции
   исходного файла. */
.direction-image {
  width: 100%;
  height: 150px;
  object-fit: cover;
  object-position: center 35%;
  display: block;
}
.direction-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6) var(--space-6);
}
.direction-body h2 { margin: 0; font-size: 1.4rem; }
.direction-body p { margin: 0; }
.direction-body .btn { align-self: flex-start; margin-top: auto; }
.stretched-link { color: inherit; }
.stretched-link:hover { text-decoration: none; }
.stretched-link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
}
.direction-facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  font-size: 0.85rem;
}
.direction-facts span {
  padding: 2px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--brand-100);
  color: var(--brand-900);
}

/* Колонка для одиночного содержимого: формы и текста. Растягивать форму
   на всю ширину рядом с тремя строками текста — значит оставить полэкрана
   пустым. */
.narrow { width: min(760px, 100%); margin-inline: auto; }



/* --- Отзывы -------------------------------------------------------------- */
.review-list { list-style: none; margin: 0 0 var(--space-8); padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr)); gap: var(--space-3); }
.review-card { min-width: 0; padding: var(--space-4) var(--space-6); border: 1px solid var(--line); border-radius: var(--radius-md); background: var(--surface); }
.review-card > p { margin: 0 0 var(--space-2); }
/* Оценка крупно и первой: по ней отзывы просматривают, а текст читают
   выборочно. */
.review-rating { font-family: var(--font-heading); font-size: 1.3rem; color: var(--brand-700); }
.tile-rating { margin: 0 0 var(--space-2); color: var(--ink-muted); }
.tile-rating b { color: var(--brand-700); font-size: 1.05rem; }

/* --- Расписание и занятия ----------------------------------------------- */
/* Дни в колонки по ширине окна, а не сеткой 7×12: недельная сетка
   на 360 пикселях нечитаема, и на телефоне колонка остаётся одна. */
.schedule-days {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-6);
}
.schedule-day { min-width: 0; }
.schedule-day-title { margin: 0 0 var(--space-2); font-size: 1rem; }
.schedule-slots { display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* Час — цель нажатия, а не строчка текста: 44 пикселя по высоте. */
.schedule-slot {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--brand-600);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--brand-700);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.schedule-slot:hover { background: var(--brand-100); }
.schedule-slot-static { border-style: dashed; cursor: default; }
.schedule-slot-len { font-size: 0.75rem; font-weight: 400; color: var(--ink-muted); }

.lesson-list { list-style: none; margin: 0 0 var(--space-8); padding: 0; display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--space-3); }
.lesson-card {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-4);
  padding: var(--space-4) var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}
/* Ждущая решения запись выделена рамкой: это единственное здесь,
   что требует действия сегодня. */
.lesson-card-waiting { border-color: var(--accent-600); }
.lesson-card-past { opacity: 0.75; }
.lesson-card-body { min-width: 0; flex: 1 1 260px; }
.lesson-card-body > p { margin: 0 0 var(--space-1); }
.lesson-card-title { margin: 0 0 var(--space-1); font-size: 1.05rem; }
.lesson-card-when { font-weight: 600; }
.lesson-badge { color: var(--ink-muted); font-size: 0.9rem; }
.lesson-card-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* --- Свободные окна в карточке преподавателя ---------------------------- */
/* Время — то, ради чего человек и открыл подбор, поэтому оно набрано
   не так, как перечень предметов: значения выделены подложкой, а подпись
   пояса стоит отдельно и приглушённо. */
.tile-slots { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--space-2); margin: 0 0 var(--space-3); }
.tile-slots-label { color: var(--ink-muted); font-size: 0.9rem; }
.tile-slot {
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--brand-100);
  color: var(--brand-800);
  font-size: 0.9rem;
  font-weight: 600;
  white-space: nowrap;
}
.tile-slots-zone { color: var(--ink-muted); font-size: 0.82rem; }

/* --- Рейтинг обучающихся ------------------------------------------------ */
/* Свой результат — крупной цифрой и первым: за ней человек сюда и приходит,
   а таблицу он смотрит вторым делом. */
.score-own {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4) var(--space-6);
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  margin-bottom: var(--space-8);
}
.score-own-figure { display: flex; align-items: baseline; gap: var(--space-2); }
.score-own-value { font-family: var(--font-heading); font-size: 2.4rem; line-height: 1.1; color: var(--brand-700); }
.score-own-label { color: var(--ink-muted); }
.score-own-place, .score-own-consent { margin: 0; }
.score-own-consent { color: var(--ink-muted); font-size: 0.95rem; }

/* Бейджи — плитками, а не строкой: строка из пяти достижений на 360 пикселях
   переносится по одному слову и читается как список опечаток. */
.badge-list {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-8);
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: var(--space-3);
}
.badge-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.badge-icon {
  flex: none;
  width: 40px;
  height: 40px;
  display: grid;
  place-items: center;
  border-radius: var(--radius-pill);
  background: var(--brand-100);
  color: var(--brand-700);
  font-size: 1.1rem;
}
.badge-body { display: grid; gap: 2px; min-width: 0; }
.badge-hint { color: var(--ink-muted); font-size: 0.85rem; }

.score-table { width: 100%; border-collapse: collapse; }
.score-table th, .score-table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--line);
  text-align: left;
}
.score-table th { color: var(--ink-muted); font-weight: 600; font-size: 0.9rem; }
/* Своя строка выделяется заливкой, а не только жирным: в списке из двадцати
   фамилий взгляд ищет себя, и жирный шрифт для этого слишком тихий. */
.score-row-own { background: var(--brand-50); font-weight: 600; }

/* --- Проверка документа ------------------------------------------------ */
/* Ответ страницы — цветом рамки и словом, а не только словом: «выдан»
   и «аннулирован» различаются с расстояния, с которого текст ещё не читают. */
.check-result {
  margin-top: var(--space-6);
  padding: var(--space-6);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.check-result-valid { border-color: var(--success); }
.check-result-void { border-color: var(--danger); }
.check-verdict { margin: 0 0 var(--space-4); font-weight: 600; font-size: 1.05rem; }
.check-result-valid .check-verdict { color: var(--success); }
.check-result-void .check-verdict { color: var(--danger); }
/* Две колонки, пока подписи и значения сопоставимы по длине; ниже 560
   пикселей значение уезжало бы в четыре строки рядом с коротким «Номер». */
.check-facts { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: var(--space-2) var(--space-4); margin: 0; }
.check-facts dt { color: var(--ink-muted); }
.check-facts dd { margin: 0; }
@media (max-width: 560px) {
  .check-facts { grid-template-columns: 1fr; gap: 0; }
  .check-facts dd { margin-bottom: var(--space-3); }
}

/* --- Карточки ---------------------------------------------------------- */
.service-card, .news-card, .contact-card, .request-form {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
}
.service-card, .news-card, .contact-card { padding: var(--space-6); }
.service-card, .news-card { transition: box-shadow .18s ease, transform .18s ease; }
.service-card:hover, .news-card:hover { box-shadow: var(--shadow-lifted); transform: translateY(-2px); }
.service-card h3, .news-card h3 { margin-top: 0; }
.service-card ul { padding-left: var(--space-4); color: var(--ink-muted); }
.card-image {
  width: 100%;
  border-radius: var(--space-3);
  margin-bottom: var(--space-4);
  aspect-ratio: 16 / 10;
  max-height: 200px;
  object-fit: cover;
  /* Обрезается низ, а не верх. По умолчанию `cover` держит центр, и у
     вертикального снимка первым уходит то, ради чего его выбирали:
     лица, заголовки, верхняя часть предмета. Внизу почти всегда фон. */
  object-position: top;
  background: var(--brand-50);
}
.contact-card { display: flex; flex-direction: column; gap: var(--space-2); }
.contact-card h3 { margin: 0; }
.contact-card p { margin: 0; }

/* Сообщение вместо пустого раздела: карточка во всю ширину, и текст в ней
   тоже. Общее правило `p { max-width: 68ch }` бережёт длину строки в статьях,
   но здесь давало обратное: карточка на всю ширину, текст — чуть больше
   половины, и справа полкарточки пустого места, которое читается как
   недоделанная страница. Строки тут по одной-две, и длина им не вредит.
   Отступ у строк заметнее обычного: абзацы объясняют положение дел,
   и слипшимися они читаются как один абзац. */
.notice-card { gap: var(--space-3); }
.notice-card p { max-width: none; }
.notice-card .notice-action { margin-top: var(--space-2); }

.service-grid, .news-grid, .contact-list-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-6);
}
.news-meta {
  display: inline-block;
  margin-bottom: var(--space-2);
  color: var(--accent-800);
  font-size: 0.85rem;
  font-weight: 600;
}
/* Перечисление особенностей. Пункт бывает и `div`, и `li`: там, где это
   действительно список — «что нужно для публикации», — разметка списочная,
   и маркеры браузера в таком оформлении лишние. */
.feature-list { display: grid; gap: var(--space-3); list-style: none; padding-left: 0; }
.feature-list div,
.feature-list li {
  display: flex;
  flex-direction: column;
  padding: var(--space-4) var(--space-6);
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent-500);
  border-radius: var(--radius-sm);
}
.feature-list strong { color: var(--brand-900); }
.feature-list span { color: var(--ink-muted); font-size: 0.95rem; }

/* --- Формы ------------------------------------------------------------- */
.request-form { display: grid; gap: var(--space-4); padding: var(--space-6); }
/* Подпись всегда над полем, плейсхолдер её не заменяет (docs/18 §5). */
.request-form label { display: grid; gap: var(--space-2); font-size: 0.95rem; font-weight: 500; }
.request-form input,
.request-form select,
.request-form textarea {
  width: 100%;
  min-height: 46px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  font: inherit;
  color: var(--ink);
  background: var(--surface);
}
.request-form input:focus, .request-form select:focus, .request-form textarea:focus {
  border-color: var(--brand-600);
}
.request-form textarea { min-height: 110px; resize: vertical; }
.field-error { color: var(--danger); font-size: 0.9rem; font-weight: 500; }
.field-error::before { content: "⚠ "; }
/* У флажка подпись и ошибка лежат в одном контейнере, и без переноса
   ошибка приклеивалась к концу строки «Я даю согласие…», разрывая её
   пополам. Своя строка под подписью — там её и ищут. */
.checkbox-row .field-error { display: block; margin-top: 4px; }

/* Поле с ошибкой видно, а не только описано текстом под ним.
   Селектор с `.request-form` нужен по специфичности: общее правило
   `.request-form input` (0,2,0) сильнее одиночного `.field-invalid input`
   (0,1,1), и рамка осталась бы серой (скилл ui-layout, ошибка 15). */
.field-invalid > span:first-child { color: var(--danger); }
.field-invalid input,
.field-invalid select,
.field-invalid textarea,
.request-form .field-invalid input,
.request-form .field-invalid select,
.request-form .field-invalid textarea {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px rgba(179, 38, 30, 0.12);
}

/* Фотография педагога: окно показа, рядом с ним — поля «О себе».
   Окно квадратное: портрет и в карточке, и в окне просмотра квадрат,
   и человек должен видеть ровно то, что получится. 180 пикселей: больше
   отодвигает поля за нижний край окна ноутбука, меньше — не даёт
   разглядеть лицо.

   Пояснения рядом с окном нет намеренно: что делает окно, видно по нему
   самому, а строка экрана рядом с фотографией стоит дороже подсказки. */
.photo-row {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: var(--space-4);
  align-items: start;
  margin-bottom: var(--space-3);
}
.photo-fields { display: grid; gap: var(--space-3); }
/* Ряды внутри колонки уже разделены собственным gap: нижний отступ
   ряда дал бы двойной промежуток перед подписью под блоком. */
.photo-fields .form-row { margin-bottom: 0; }
@media (max-width: 700px) {
  /* Окно занимает ширину и встаёт над полями: колонка в 180 пикселей
     оставляет соседней 150 — в них не помещается ни одна подпись. */
  .photo-row { grid-template-columns: 1fr; }
}
.photo-picker { display: grid; gap: var(--space-2); }
/* Кнопка, а не div: окно нажимают, и клавиатура должна на него попадать. */
.photo-frame,
.request-form .photo-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  padding: 0;
  border: 1px dashed var(--line);
  border-radius: var(--radius-md);
  background: var(--brand-50);
  cursor: pointer;
}
.photo-frame:hover { border-color: var(--brand-600); }
/* Снимок позиционируется рамкой: ширина и сдвиги приходят в атрибуте style
   и считаются от стороны окна — тот же расчёт, что делает сервер. */
.photo-frame img { position: absolute; max-width: none; }
.photo-empty {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: center;
  justify-content: center;
  padding: var(--space-3);
  color: var(--ink-muted);
  font-size: 0.85rem;
  text-align: center;
}
.photo-empty-sign { font-size: 1.8rem; line-height: 1; color: var(--brand-600); }
.photo-actions { display: grid; gap: var(--space-2); justify-items: start; }
.photo-actions .btn { width: 100%; }
.photo-hint p:last-child { margin-bottom: 0; }

/* Окно обрезки. Площадка квадратная и не больше высоты окна ноутбука:
   окно вместе с ползунком и кнопками обязано помещаться в 700 пикселей. */
.crop-stage {
  position: relative;
  width: min(360px, 60vh, 100%);
  aspect-ratio: 1 / 1;
  margin: 0 auto var(--space-3);
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--ink);
  touch-action: none;
  cursor: grab;
}
.crop-stage:active { cursor: grabbing; }
.crop-stage img { position: absolute; max-width: none; user-select: none; }
.crop-zoom { display: grid; gap: var(--space-2); margin-bottom: var(--space-3); font-size: 0.9rem; }
.crop-zoom input { width: 100%; accent-color: var(--brand-600); }

/* Список пройденных программ. Строки разделены линией: без неё три ряда
   полей подряд читаются как одна анкета на двадцать граф. */
.training-list { display: grid; gap: var(--space-4); }
.training-row {
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--brand-50);
}
.training-row > label { margin-bottom: var(--space-2); }

/* Стоимость занятий в кабинете (docs/16 Р-42). Строка предмета устроена
   как строка пройденной программы — те же рамка, фон и отступы: это
   один и тот же вид «повторяющейся записи», и второй вид для неё
   означал бы, что кабинет собран из разных кусков. */
.price-row-title {
  margin: 0 0 var(--space-3);
  font-size: 1.05rem;
  /* Заголовку — свой интервал: 1.65 от абзаца дал бы между строками
     названия предмета больше, чем высота самих букв (ошибка 7). */
  line-height: 1.2;
}
.price-modes { display: grid; gap: var(--space-2); margin-bottom: var(--space-4); }
/* Селектор из двух классов, а не один: `.request-form label` (0,1,1)
   сильнее одиночного `.price-mode` (0,1,0) и поставил бы переключатель
   над подписью (скилл ui-layout, ошибка 15). */
.price-mode,
.request-form .price-mode {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  min-height: 44px;
  font-weight: 400;
}
/* Стандартный кружок 13×13 — точка, а не элемент управления: по нему
   промахиваются и мышью, и пальцем. */
.price-mode input[type="radio"],
.request-form .price-mode input[type="radio"] {
  flex: none;
  width: 24px;
  height: 24px;
  min-height: 24px;
  padding: 0;
  accent-color: var(--brand-600);
  cursor: pointer;
}
/* Цена организации рядом с вариантом: переносом не разрывается —
   «1 200 ₽/» с «час» на второй строке читается как обрывок. */
.price-row .price-tag { white-space: nowrap; color: var(--ink-muted); }

/* Показ пароля. Обёртку и кнопку ставит скрипт (static/js/password.js):
   без него поле остаётся обычным, и форма работает.

   Отступ поля справа перебивает общее `.request-form input` (0,2,0) —
   селектор написан такой же длины с запасом, иначе текст уходил бы
   под кнопку (скилл ui-layout, ошибка 15). 52 пикселя — это 44 кнопки
   и 8 воздуха. */
.password-field { position: relative; display: block; }
.password-field input,
.request-form .password-field input { padding-right: 52px; }
.password-toggle,
.request-form .password-toggle {
  position: absolute;
  top: 50%;
  right: var(--space-1);
  transform: translateY(-50%);
  /* 44 — предел, ниже которого по кнопке промахиваются пальцем. */
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--ink-muted);
  cursor: pointer;
}
.password-toggle:hover { color: var(--brand-700); }

/* Страницы входа и регистрации: форма вместе с кнопкой должна быть видна
   целиком в окне 1366×700. Заголовок здесь не витрина, а подпись к форме,
   поэтому и отступы секции, и кегль заголовка меньше обычных — без этого
   кнопка «Зарегистрироваться» уходила на 39 пикселей за нижний край. */
.auth-page { padding-top: var(--space-8); }
.auth-page .page-intro { margin-bottom: var(--space-4); }
.auth-page .page-intro h1 { font-size: clamp(1.5rem, 3vw, 2rem); }
/* Промежутки между полями плотнее общих: семь полей и кнопка при шаге 16
   давали 716 пикселей — на шестнадцать больше окна ноутбука. В анкете
   кабинета полей шестнадцать, и там же плотность решает, помещается ли
   раздел на экран целиком. */
.auth-page .request-form { gap: var(--space-3); padding: var(--space-4) var(--space-6); }
.auth-page /* `min-width: 0` — не украшение. Браузер даёт `fieldset` собственное правило
   `min-inline-size: min-content`, и элемент отказывается сжиматься ниже своего
   содержимого. Замер на 360: анкета педагога 315 пикселей внутри поля в 280,
   то есть блок вылезал из формы на 35. В разметке и в стилях всё правильно —
   мешает правило браузера, которого в файлах проекта нет.
   Скилл ui-layout, ошибка 26. */
.form-block {
  min-width: 0; padding: var(--space-4) var(--space-6); margin-bottom: var(--space-4); }
.auth-page .form-block > label { margin-bottom: var(--space-3); }
.auth-page .form-row { gap: var(--space-3); margin-bottom: var(--space-3); }
.auth-page .request-form textarea { min-height: 76px; }
/* Пояснения в форме идут во всю её ширину: общий предел абзаца в 68 знаков
   рвал строку посреди фразы, хотя место справа оставалось пустым. */
.request-form .form-note, .request-form p { max-width: none; }
.auth-page .form-note { margin-bottom: var(--space-2); }
.cabinet-form-title { margin: var(--space-8) 0 var(--space-2); font-size: 1.4rem; }

/* Ряд полей. Одна колонка из семи полей не помещается в окно ноутбука,
   а прокручивать регистрацию из трёх строк неправильно: и форма, и кнопка
   должны быть видны сразу. На узком экране ряд разворачивается в столбец —
   поля в две колонки на 360 нечитаемы. */
/* `align-items: start` обязателен: иначе поле в ячейке растягивается
   на высоту соседней, у которой под ним подсказка, и одно поле в ряду
   оказывается вдвое выше другого. */
.form-row { display: grid; gap: var(--space-4); align-items: start; }
@media (min-width: 760px) {
  .form-row { grid-template-columns: 1fr 1fr; }
  .form-row-3 { grid-template-columns: 1fr 1fr 1fr; }
  /* Неравные колонки: список с длинными подписями и год из четырёх цифр
     в равных долях смотрятся как ошибка — половина строки под «2024». */
  .form-row-wide { grid-template-columns: 2fr 1fr; }
}
/* Кнопка и подпись рядом с ней: подпись под кнопкой отнимала бы ещё строку. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

/* Требования к паролю: загораются по мере ввода, а не после отправки.
   Строкой, а не столбцом: четыре строки подряд отодвигали кнопку
   за нижний край окна ноутбука. */
.password-rules {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  margin: calc(-1 * var(--space-2)) 0 0;
  padding: 0;
  list-style: none;
  color: var(--ink-muted);
  font-size: 0.9rem;
}
.password-rules li::before { content: "• "; }
.password-rules li.is-ok { color: var(--success); }
.password-rules li.is-ok::before { content: "✓ "; }
.password-rules li.is-bad { color: var(--danger); }
.password-rules li.is-bad::before { content: "× "; }
.form-note { color: var(--ink-muted); font-size: 0.85rem; }
/* Флажок слева, подпись справа.
   Селектор с двумя классами нужен по специфичности: общее правило
   .request-form label (0,1,1) сильнее одиночного .checkbox-row (0,1,0),
   и его display: grid ставил флажок над текстом, а не рядом. */
.checkbox-row,
.request-form .checkbox-row {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  font-weight: 400;
}
/* Стандартный флажок — 13 пикселей: мимо него промахиваются и мышью,
   и пальцем. Нажимается вся подпись, но видимый квадрат должен
   читаться как элемент управления. */
.checkbox-row input[type="checkbox"],
.request-form .checkbox-row input[type="checkbox"] {
  flex: none;
  width: 24px;
  height: 24px;
  min-height: 24px;
  margin: 1px 0 0;
  padding: 0;
  accent-color: var(--brand-600);
  cursor: pointer;
}
.checkbox-row span { flex: 1; }
.form-message {
  padding: var(--space-4) var(--space-6);
  border-radius: var(--radius-sm);
  border-left: 4px solid;
}
.form-message-success { background: #eaf6f0; border-color: var(--success); color: #14512f; }
.form-message-error { background: #fdeceb; border-color: var(--danger); color: #7d1a15; }
/* Ловушка для роботов: скрыта визуально, но остаётся в разметке. */
.honeypot { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* --- Подвал ------------------------------------------------------------ */
.site-footer {
  background: var(--brand-900);
  color: var(--ink-invert);
  padding: var(--space-12) 0 var(--space-6);
  margin-top: var(--space-16);
}
.site-footer h4 { color: var(--ink-invert); margin-top: 0; }
.site-footer a { color: var(--ink-invert); }
.site-footer .muted { color: rgba(255, 255, 255, 0.72); }
.footer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-8); }
.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  justify-content: space-between;
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px solid rgba(255, 255, 255, 0.18);
  font-size: 0.9rem;
}

/* --- Адаптив ----------------------------------------------------------- */
@media (max-width: 1280px) {
  /* Меню в одну строку перестаёт помещаться раньше, чем сетки становятся
     узкими, поэтому оно уходит под кнопку именно здесь. */
  .main-nav {
    display: none;
    position: absolute;
    top: var(--header-h);
    right: var(--space-4);
    left: var(--space-4);
    z-index: 40;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--space-3);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-lifted);
  }
  .main-nav.open { display: flex; }
  .main-nav .nav-link { border-radius: var(--radius-sm); min-height: 44px; padding: 0 var(--space-3); }
  /* На узком экране служебная полоса прячется, а её ссылки появляются
     внутри выпадающего меню: две строки шапки на телефоне — расточительство. */
  .nav-utility-item { display: flex; align-items: center; border-top: 1px solid var(--line); }
  /* Те же кнопки в правом углу — только для широкого экрана. На 360
     пикселях они вместе с логотипом давали наложение: сумма ширин
     превышала окно, а перенос запрещён. */
  .nav-wide-only { display: none; }
  /* Разделы уехали под кнопку, и средняя колонка сетки опустела.
     «1fr auto 1fr» при этом делит окно ровно пополам, а логотип
     с названием в половину 360-пиксельного экрана не влезает — он
     выходил за свою колонку и налезал на кнопку меню. Две колонки:
     логотип берёт нужное, правый угол прижат к краю. */
  .nav-wrap { grid-template-columns: 1fr auto; }
  /* Кнопка меню включается там же, где разделы уезжают под неё.
     Она стояла в блоке «не шире 400», и на всём промежутке от 401
     до 1280 пикселей — планшеты, окно в половину экрана, ноутбук 1366
     при системном масштабе 125 % — навигации не было вообще: ни разделов,
     ни кнопки, ни входа в кабинет. Ровно ошибка 16 из скилла ui-layout,
     только наоборот: порог оказался не слишком высоким, а слишком низким. */
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--ink);
    font-size: 1.3rem;
    cursor: pointer;
  }
}

@media (max-width: 400px) {
  /* Самые узкие телефоны: 40 пикселей логотипа, название, значок версии
     для слабовидящих и кнопка меню в сумме подходили к 360 вплотную.
     Уменьшается кегль названия и поля строки, а не выкидывается
     название: по нему опознают организацию. */
  .nav-wrap { padding-inline: var(--space-3); gap: var(--space-2); }
  .brand-name { font-size: 0.86rem; }
  .brand { gap: var(--space-2); }
  .utility-nav { display: none; }
  .utility-row { justify-content: flex-end; }
}

@media (max-width: 900px) {
  .hero-pitch { margin-bottom: var(--space-6); text-align: left; }
  /* На узком экране одна строка означала бы нечитаемый размер шрифта:
     заголовок переносится, как обычный текст. */
  .hero-pitch h1 { font-size: clamp(1.5rem, 5.2vw, 2rem); white-space: normal; }
  .hero-pitch p { font-size: 1rem; }

  .about-grid, .contacts-grid, .direction-grid {
    grid-template-columns: 1fr;
    gap: var(--space-6);
  }
  .direction-image { height: 130px; }
  /* На узком экране первый экран перестаёт быть двухколоночным, и требование
     «поместиться в окно» превращается в пустоту под контентом. */
}

@media (max-width: 760px) {
  body { font-size: 16px; }
  .section { padding: var(--space-8) 0; }
  .hero { padding: var(--space-8) 0 var(--space-6); }
  /* Подпись у переключателя версии для слабовидящих прячется: в шапке
     телефона остаётся значок, область нажатия 44 пикселя сохраняется. */
  .vision-toggle-text { display: none; }
  .footer-grid { gap: var(--space-6); }
}

/* Анимации отключаются, если пользователь их не хочет. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .btn:hover, .service-card:hover, .news-card:hover { transform: none; }
}

@media print {
  .site-header, .site-footer, .btn, .nav-toggle { display: none; }
  body { background: #fff; color: #000; }
}

/* --- Новости ----------------------------------------------------------- */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-4);
  margin-bottom: var(--space-8);
}
.filter-bar label { display: grid; gap: var(--space-2); font-size: 0.9rem; font-weight: 500; }
.filter-bar select {
  min-height: 44px;
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  font: inherit;
  background: var(--surface);
  color: var(--ink);
}
.news-card h3 { margin: var(--space-2) 0; }
.news-card h3 a { color: var(--brand-900); }
.news-card h3 a:hover { color: var(--brand-700); }
.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  margin-top: var(--space-12);
}
.footer-links { list-style: none; margin: 0; padding: 0; }
.footer-links li { margin-bottom: var(--space-2); }

.breadcrumbs { margin-bottom: var(--space-4); font-size: 0.9rem; color: var(--ink-muted); }
.article { max-width: 760px; }
.article-cover {
  width: 100%;
  border-radius: var(--radius-md);
  margin: var(--space-6) 0;
}
.article-body { font-size: 1.05rem; }
.article-body p { margin-bottom: var(--space-4); }
/* Плашка на карточке новости, созданной агентом, — только в панели управления. */
.badge-agent {
  display: inline-block;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--accent-100);
  color: var(--accent-800);
  font-size: 0.8rem;
  font-weight: 600;
}

/* --- Раздел «Сведения об образовательной организации» -------------------- */


.sveden-layout {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: var(--space-8);
  align-items: start;
}
/* Элемент грида по умолчанию не сжимается ниже `min-content` своего
   содержимого. У таблицы педагогического состава это шесть колонок текста,
   то есть больше тысячи пикселей, — и колонка раздвигала страницу,
   а `overflow-x: auto` на обёртке таблицы при этом бессилен: переполнялась
   не обёртка, а сама колонка. Замер 24.08.2026: прокрутка 609 px на 1024
   и 337 px на 1440. Та же причина, что у ошибки 26 с `fieldset`. */
.sveden-layout > * { min-width: 0; }
.sveden-nav {
  position: sticky;
  top: var(--space-4);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.sveden-nav-title { font-size: 0.95rem; margin-bottom: var(--space-3); }
.sveden-nav ul { list-style: none; margin: 0; padding: 0; }
.sveden-nav li { margin-bottom: var(--space-1); }
.sveden-nav a,
.sveden-nav .is-current {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  font-size: 0.92rem;
  line-height: 1.35;
}
.sveden-nav a { color: var(--ink); }
.sveden-nav a:hover { background: var(--brand-100); color: var(--brand-900); }
.sveden-nav .is-current { background: var(--brand-600); color: var(--ink-invert); font-weight: 600; }

.sveden-content h2 { margin: var(--space-8) 0 var(--space-3); font-size: 1.35rem; }
.sveden-content h2:first-child { margin-top: 0; }
.sveden-content h3 { margin: var(--space-4) 0 var(--space-2); font-size: 1.1rem; }

.sveden-index { list-style: none; margin: 0; padding: 0; }
.sveden-index li { border-bottom: 1px solid var(--line); }
.sveden-index a { display: block; padding: var(--space-3) 0; }

.sveden-card {
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-4);
}
.sveden-dl { display: grid; grid-template-columns: minmax(140px, 240px) 1fr; gap: var(--space-2) var(--space-4); }
.sveden-dl dt { color: var(--ink-muted); }
.sveden-dl dd { margin: 0; }
/* Документ, которого ещё нет. Заметно, но не тревожно: это штатное
   состояние комплекта, а не ошибка страницы. */
.doc-pending {
  display: inline-block;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--accent-100);
  color: var(--accent-800);
  font-size: 0.88rem;
}
/* Документа нет и не будет: коллективный договор не заключался,
   предписаний не выносилось. Тон спокойнее, чем у «в стадии формирования»:
   там работа впереди, здесь вопрос закрыт. */
.doc-absent {
  display: inline-block;
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background: var(--bg);
  border: 1px solid var(--line);
  color: var(--ink-muted);
  font-size: 0.88rem;
}
.sveden-files { list-style: none; margin: 0; padding: 0; }
.sveden-files li { padding: var(--space-2) 0; border-bottom: 1px solid var(--line); }

/* Широкая таблица прокручивается внутри своего контейнера и не тянет за собой
   страницу: горизонтальная прокрутка всего документа запрещена (docs/18 §4.1). */
.table-scroll { overflow-x: auto; margin-bottom: var(--space-6); }
.sveden-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.95rem;
}
.sveden-table th,
.sveden-table td {
  padding: var(--space-3);
  border: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}
.sveden-table thead th { background: var(--brand-100); color: var(--brand-900); }
.sveden-table tbody th[scope="row"] {
  background: var(--bg);
  font-weight: 600;
  width: 40%;
}

@media (max-width: 900px) {
  .sveden-layout { grid-template-columns: 1fr; }
  .sveden-nav { position: static; }
}

/* На узком экране таблица перестраивается в карточки: подпись столбца берётся
   из data-label. Прокручивать таблицу из десяти столбцов на телефоне нельзя —
   подраздел, который невозможно прочитать, проверяющий засчитает
   как неразмещённые сведения. */
@media (max-width: 640px) {
  .sveden-table thead { display: none; }
  .sveden-table,
  .sveden-table tbody,
  .sveden-table tr,
  .sveden-table td,
  .sveden-table th { display: block; width: auto; }
  .sveden-table tr {
    margin-bottom: var(--space-4);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    overflow: hidden;
  }
  .sveden-table td,
  .sveden-table th { border: none; border-bottom: 1px solid var(--line); }
  .sveden-table tr > *:last-child { border-bottom: none; }
  .sveden-table tbody th[scope="row"] { width: auto; }
  .sveden-table td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: var(--space-1);
    color: var(--ink-muted);
    font-size: 0.82rem;
  }
  .sveden-dl { grid-template-columns: 1fr; }
  .sveden-dl dt { font-size: 0.85rem; }
}

/* --- Версия для слабовидящих --------------------------------------------- */

/* Отдельных шаблонов у версии нет: правки страницы иначе пришлось бы делать
   дважды, и версии разъехались бы. Меняется только оформление.

   Настройки приходят отдельными классами на body (`vision-color-wb`,
   `vision-font-large` …). Схема задаёт два цвета, кегль — один размер,
   интервал — одно число; остальное собирается из них. Так добавление
   пятой схемы — это две строки, а не правка всего блока. */

body.vision {
  /* Схема по умолчанию — чёрным по белому — объявлена в `:root`.
     Здесь она раскладывается на токены сайта. */

  /* Токены сайта перекрываются схемой. Оттенков в режиме не бывает:
     приглушённый текст, светлые подложки и фирменный фиолетовый — это
     контрасты ниже 7:1, ради которых режим и включают. */
  --bg: var(--v-bg);
  --surface: var(--v-bg);
  --ink: var(--v-ink);
  --ink-muted: var(--v-ink);
  --ink-invert: var(--v-bg);
  --line: var(--v-ink);
  --brand-50: var(--v-bg);
  --brand-100: var(--v-bg);
  --brand-600: var(--v-ink);
  --brand-700: var(--v-ink);
  --brand-800: var(--v-ink);
  --brand-900: var(--v-ink);
  --accent-100: var(--v-bg);
  --accent-800: var(--v-ink);
  --success: var(--v-ink);
  --warning: var(--v-ink);
  --danger: var(--v-ink);
  --info: var(--v-ink);

  font-size: 1.25rem;
  line-height: var(--v-line);
  background: var(--v-bg);
  color: var(--v-ink);
}

/* Цветовые схемы. Контрасты посчитаны по формуле относительной яркости
   из ГОСТ Р 52872—2019: 21:1, 21:1, 7.8:1 и 15:1 — все выше порога ААА. */
body.vision-color-wb { --v-bg: #000000; --v-ink: #ffffff; }
body.vision-color-blue { --v-bg: #9dd1ff; --v-ink: #063462; }
body.vision-color-yellow { --v-bg: #000000; --v-ink: #ffd700; }

/* Кегль. Обычный в режиме — уже крупнее обычного сайта; браузерное
   увеличение работает поверх и даёт требуемые стандартом 200 %. */
body.vision-font-large { font-size: 1.5rem; }
body.vision-font-huge { font-size: 1.75rem; }

/* Межстрочный интервал. Критерий 1.4.8 требует не меньше 1.5 внутри абзаца
   и промежутка между абзацами в полтора раза больше межстрочного —
   отсюда множитель в отступе, а не постоянное число. */
body.vision-space-wide { --v-line: 1.75; }
body.vision-space-widest { --v-line: 2; }
body.vision p,
body.vision li,
body.vision dd,
body.vision dt { line-height: var(--v-line); }
body.vision p { margin: 0 0 calc(var(--v-line) * 1.5em); }
body.vision li { margin-bottom: calc(var(--v-line) * 0.5em); }

/* Полуторный промежуток между абзацами стандарт требует для текстовых
   блоков, а «текстовый блок» он же определяет как текст более чем из одного
   предложения (п. 3.1.80). Строки карточки — предметы, стаж, срок, цена —
   не текстовые блоки: там промежуток в две с половиной строки разрывает
   карточку на несвязанные куски. */
body.vision .tile-body p,
body.vision .direction-body p,
body.vision .person-card p,
body.vision .contact-card p,
body.vision .modal-info p,
body.vision .form-block p,
body.vision form p { margin-bottom: var(--space-3); }

/* Мельче основного текста в режиме не бывает. Подписи, пометки, сноски,
   хлебные крошки и мелкие кнопки набраны 0.8–0.95rem — то есть ровно тем,
   из-за чего режим и включают: крупный кегль в теле страницы ничего не даёт,
   если цена и срок под карточкой остались прежними.

   Правило общее, а не список из двух десятков классов: перечень пришлось бы
   дополнять при каждом новом мелком элементе, и его бы забывали. Заголовки
   и сама панель исключены — им размер задан осмысленно. */
body.vision [class]:not(h1):not(h2):not(h3):not(h4):not([class*="vision-"]) {
  font-size: 1em;
}
/* Второе правило — для элементов без класса: подписи полей и сами поля
   класса не имеют, а размер им задан через родителя (`.form-row label`).
   Без этого в анкете крупнели рамки, но не крупнели надписи над ними. */
body.vision label,
body.vision small,
body.vision th,
body.vision td,
body.vision dt,
body.vision dd,
body.vision figcaption,
body.vision input,
body.vision select,
body.vision textarea,
body.vision option,
body.vision button { font-size: 1em; }

/* Ширина строки — не больше 68 знаков при любом кегле (порог по стандарту 80).
   У абзацев предел стоит и в обычной версии, у списков и определений — нет. */
body.vision li,
body.vision dd { max-width: 68ch; }

/* Ссылка отличается подчёркиванием, а не цветом: пятый цвет пришлось бы
   подбирать под каждую схему и заново считать контраст, а подчёркивание
   работает во всех четырёх и само по себе отвечает критерию 1.4.1. */
body.vision a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.2em;
}
body.vision .btn,
body.vision .vision-option,
body.vision .sveden-nav .is-current { text-decoration: none; }

body.vision .btn,
body.vision .sveden-nav .is-current {
  background: var(--v-ink);
  color: var(--v-bg);
  border: 2px solid var(--v-ink);
}
body.vision .btn-secondary {
  background: var(--v-bg);
  color: var(--v-ink);
}
body.vision .sveden-table th,
body.vision .sveden-table td { border: 2px solid var(--v-ink); }
body.vision .sveden-table thead th { background: var(--v-bg); color: var(--v-ink); }

/* Поля ввода в режиме: рамка толще, фон — фон схемы, а не белый. */
body.vision input,
body.vision select,
body.vision textarea {
  background: var(--v-bg);
  color: var(--v-ink);
  border: 2px solid var(--v-ink);
}

/* Кольцо фокуса цветом текста: на чёрном фоне фирменный фиолетовый
   не виден, а currentColor подходит к любой схеме. */
body.vision :focus-visible { outline: 3px solid currentColor; outline-offset: 3px; }

/* Изображения убирает переключатель, и никакие другие правила их не прячут.
   Раньше в режиме безусловно скрывалось всё с пустым `alt` — то есть
   обложки программ и предметов: их подпись пуста, потому что название
   написано рядом. Посетитель включал режим и терял картинки без спроса. */
body.vision-images-off .card-image,
body.vision-images-off .modal-media,
body.vision-images-off img[alt=""] { display: none; }

/* Буквенная подложка нарисована прямо в разметке, и её цвета заданы
   атрибутами — мимо токенов и мимо схемы. Фиолетовая буква на сиреневом
   даёт около 4:1, а режим включают ради семи. Свойства CSS перебивают
   атрибуты представления, поэтому хватает правил без правки шаблона. */
body.vision .card-image-letter rect { fill: var(--v-bg); }
body.vision .card-image-letter circle { fill: var(--v-ink); fill-opacity: 0.12; }
body.vision .card-image-letter text { fill: var(--v-ink); opacity: 1; }

body.vision * { box-shadow: none !important; }

/* Липкая шапка отключается: при кегле 28 пикселей она занимает у экрана
   ноутбука долю, которой уже жалко, и накрывает то, к чему перешли. */
/* Фон шапки задан не токеном, а полупрозрачным белым с размытием — схема
   его не перекрывает, и на чёрно-жёлтой схеме получалась белая полоса
   с жёлтым текстом: контраст 1.4:1, читать нечем. */
body.vision .site-header {
  position: static;
  background: var(--v-bg);
  backdrop-filter: none;
  border-bottom: 3px solid var(--v-ink);
}

/* Цветные плашки сообщений собраны из своих пастельных цветов, мимо токенов.
   В режиме они становятся цветами схемы, а различаются толщиной рамки
   и текстом — цвет и так не может быть единственным признаком (1.4.1). */
body.vision .form-message-success,
body.vision .form-message-error,
body.vision .form-message-info {
  background: var(--v-bg);
  color: var(--v-ink);
  border: 3px solid var(--v-ink);
}
body.vision .site-footer .muted { color: var(--v-bg); }

/* Шапка складывается в две строки. Сетка «1fr auto 1fr» рассчитана на кегль
   обычной версии: при 24 пикселях название организации и четыре раздела
   в одну строку не помещаются и налезают друг на друга — ошибка 19
   из скилла ui-layout, только вызванная не шириной окна, а кеглем.

   Одна колонка на всё давала три строки в столбик, и шапка читалась как
   рассыпавшийся список. Двух строк достаточно: сверху опознавание
   организации и служебные кнопки по краям, снизу разделы сайта.
   Раз шапка уже не липкая, вторую строку она может себе позволить. */
body.vision .nav-wrap {
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "brand utility"
    "nav nav";
  row-gap: var(--space-2);
}
body.vision .nav-side-left { grid-area: brand; }
body.vision .nav-side-right { grid-area: utility; justify-content: flex-end; }
/* Разделы занимают вторую строку целиком и стоят по центру: прижатые
   к левому краю, они читались как продолжение названия организации,
   а не как отдельный ряд. */
body.vision .main-nav { grid-area: nav; justify-content: center; }
body.vision .nav-side,
body.vision .main-nav { flex-wrap: wrap; }

/* Запрет переноса снимается. Критерий 1.4.12 требует, чтобы ничего
   не терялось при увеличенных межбуквенных и межсловных интервалах,
   а строка с `nowrap` при таких интервалах уезжает за край экрана. */
body.vision .lead-oneline,
body.vision .tab,
body.vision .nav-link,
body.vision .brand-name,
body.vision .btn { white-space: normal; }

/* Одного разрешения переносить мало: у `.brand` стоит `flex: none`, и блок
   сохранял ширину по самой длинной строке, вылезая из своей колонки прямо
   под значок режима. Замер на 360: название требовало 237 пикселей при
   колонке в 228 и накрывало глаз на 49. Здесь блоку разрешено сжиматься,
   и название переносится по дефису.

   Причина, по которой перенос запрещён в обычной версии, — «наезжал
   на первый пункт меню» — в режиме не действует: разделы стоят отдельной
   второй строкой. */
body.vision .brand { flex: 1 1 auto; min-width: 0; }
body.vision .lead-oneline,
body.vision .tab { font-size: inherit; }

/* --- Панель настроек режима ---------------------------------------------- */

.vision-panel {
  background: var(--v-bg);
  color: var(--v-ink);
  border-bottom: 3px solid var(--v-ink);
  padding: var(--space-3) 0;
}
.vision-panel-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-6);
}
.vision-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.vision-group-exit { margin-left: auto; }
.vision-group-title { font-weight: 700; }

/* Целевая зона не меньше 44×44 (критерий 2.5.5): промахнуться по кнопке,
   которая делает текст крупнее, — обиднее всего. */
.vision-option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0 var(--space-3);
  border: 2px solid var(--v-ink);
  background: var(--v-bg);
  color: var(--v-ink);
  font-weight: 600;
  line-height: 1.2;
}
/* Выбранный вариант различается заливкой и рамкой, но в разметке
   у него стоит `aria-current` — цвет не единственный признак. */
.vision-option[aria-current] { outline: 3px solid var(--v-ink); outline-offset: 3px; }
/* У образцов схемы заливка занята самой схемой — им остаётся только рамка. */
.vision-option[aria-current]:not(.vision-swatch) {
  background: var(--v-ink);
  color: var(--v-bg);
}
/* Кнопки размера показывают размер собой. */
.vision-size-normal { font-size: 1rem; }
.vision-size-large { font-size: 1.4rem; }
.vision-size-huge { font-size: 1.8rem; }
/* Кнопки схем окрашены в свою схему — так видно, что получится.
   Селектор начинается с `body.vision`, иначе его перебивает правило
   `body.vision a { color: inherit }`, и буква на образце пропадает:
   чёрным по чёрному. */
body.vision .vision-swatch-bw { background: #ffffff; color: #000000; border-color: #000000; }
body.vision .vision-swatch-wb { background: #000000; color: #ffffff; border-color: #ffffff; }
body.vision .vision-swatch-blue { background: #9dd1ff; color: #063462; border-color: #063462; }
body.vision .vision-swatch-yellow { background: #000000; color: #ffd700; border-color: #ffd700; }
.vision-help { color: var(--v-ink); }


/* --- Плитки: программы, предметы, педагоги ------------------------------- */

/* Одна сетка на все витрины. Раньше их было три с разными правилами,
   и карточки на трёх страницах выглядели по-разному. */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--space-6);
}
/* Витрина педагогов. Портрет квадратный, и в колонке 357 пикселей плитка
   вырастает до 540 — ряд перестаёт помещаться в окно ноутбука вместе
   с вкладками и отбором. Колонка уже: педагогов в ряду четверо,
   плитка возвращается к 430. */
.card-grid-people { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.tile {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow);
  overflow: hidden;
  transition: box-shadow .18s ease, transform .18s ease;
}
.tile:hover { box-shadow: var(--shadow-lifted); transform: translateY(-2px); }
/* Вся плитка открывает окно: выцеливать кнопку «Подробнее» посетитель
   не обязан. Курсор говорит об этом заранее. */
.tile[data-modal-open] { cursor: pointer; }
/* Плитка-заготовка: место будущей записи. Приподнимать её при наведении
   незачем — нажимать не на что. */
.tile-empty { border-style: dashed; box-shadow: none; }
.tile-empty:hover { box-shadow: none; transform: none; }
.tile-empty h2 { color: var(--ink-muted); }
/* Обложка на плитке показывается целиком. Полоса в 150 пикселей резала
   квадратные снимки программ вчетверо: у изображения 900×900 в окне
   350×150 оставалась середина без голов и без нижней трети.

   Окно квадратное — под формат самих файлов, а `contain` страхует
   от снимков другой формы: они впишутся с мягкими полями, но целиком.
   Плитка от этого выше, и это осознанная плата: обложка здесь —
   то, по чему выбирают. */
.tile .card-image {
  margin: 0;
  border-radius: 0;
  height: auto;
  max-height: none;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  object-position: center;
}
/* Буквенная подложка — не фотография: обрезать в ней нечего, а квадрат
   в 357 пикселей под одну букву читается как пустое место. Остаётся
   полосой, как было. У педагогов её перебивает правило портрета ниже:
   там подложка стоит в ряду с квадратными снимками. */
.tile .card-image-letter { aspect-ratio: 16 / 10; max-height: 200px; }
/* Межстрочный интервал плотнее общего: в карточке не абзац для чтения,
   а три-четыре строки описания, и воздух в 1.65 растягивает плитку
   на пустом месте. */
.tile-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  gap: var(--space-2);
  padding: var(--space-4) var(--space-6) var(--space-6);
  line-height: 1.4;
}
.tile-body h2 { margin: 0; font-size: 1.2rem; }
.tile-body h2 a { color: var(--brand-900); }
.tile-cover { display: block; }
.section-footer { margin-top: var(--space-8); }
.tile-body p { margin: 0; }
.tile-facts { display: flex; flex-wrap: wrap; gap: var(--space-2); font-size: 0.85rem; }
.tile-facts span {
  padding: 2px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--brand-100);
  color: var(--brand-900);
}
/* Кнопки прижаты к низу карточки: в сетке карточки одной высоты,
   поэтому ряды кнопок выстраиваются на одной линии. */
.tile-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: auto;
  padding-top: var(--space-3);
}

/* Буквенная подложка вместо фотографии, когда изображения нет. */
.card-image-letter { display: block; width: 100%; }

/* --- Вкладки ------------------------------------------------------------- */

/* Вкладок на странице две, и они — главный выбор посетителя: искать
   педагога или начать с предмета. Поэтому по центру и крупнее обычного
   пункта меню. Прижатые к левому краю мелкие вкладки читались как
   служебный переключатель, мимо которого проходят. */
.tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-6);
  margin-bottom: var(--space-8);
  border-bottom: 1px solid var(--line);
}
.tab {
  display: inline-flex;
  align-items: center;
  min-height: 68px;
  padding: 0 var(--space-8);
  /* Подчёркивание толще обычного: при таком кегле трёхпиксельная линия
     теряется и текущая вкладка перестаёт читаться как выбранная. */
  border-bottom: 4px solid transparent;
  color: var(--ink);
  font-size: 1.45rem;
  font-weight: 600;
}
.tab:hover { color: var(--brand-700); text-decoration: none; }
.tab.is-current { border-bottom-color: var(--brand-600); color: var(--brand-900); }
@media (max-width: 560px) {
  /* На узком экране крупный кегль ломает строку надвое: «Преподаватели»
     при 1.45rem занимает больше ширины экрана. Считано на 360: два слова
     по 1.05rem с промежутком 8 укладываются в 300 пикселей. */
  .tabs { gap: var(--space-2); }
  .tab { min-height: 52px; padding: 0 var(--space-3); font-size: 1.05rem; }
}

/* --- Страница программы, предмета, педагога ------------------------------ */

.detail-grid {
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: var(--space-8);
  align-items: start;
}
.detail-aside {
  position: sticky;
  top: calc(var(--header-h) + var(--space-4));
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
}
.detail-aside .card-image { margin: 0; }
.detail-aside p { margin: 0; }
.detail-price { font-family: var(--font-heading); font-size: 1.6rem; color: var(--brand-700); }
/* Шаги по порядку: номера здесь несут смысл, в отличие от перечня равных
   пунктов, где маркеры только мешают. */
.steps-list { margin: 0 0 var(--space-4); padding-left: var(--space-6); }
.steps-list li { margin-bottom: var(--space-2); }
.plain-list { list-style: none; margin: 0 0 var(--space-4); padding: 0; }
.plain-list li { padding: var(--space-2) 0; border-bottom: 1px solid var(--line); }

/* Программа обучения на странице курса: разделы и уроки внутри них.
   Нумерация у разделов осмысленная — они идут по порядку прохождения;
   уроки внутри раздела маркерами не нумеруются второй раз, иначе получается
   «3.1.2» без надобности. */
.course-plan { margin: 0 0 var(--space-6); padding-left: var(--space-6); }
.course-plan-module + .course-plan-module { margin-top: var(--space-4); }
.course-plan-title { margin: 0; font-weight: 600; }
.course-plan-summary { margin: var(--space-1) 0 0; color: var(--ink-muted); }
.course-plan-submodule { margin-top: var(--space-3); }
.course-plan-subtitle { margin: 0 0 var(--space-1); font-weight: 600; font-size: 0.95rem; }
.course-plan-lessons { list-style: none; margin: var(--space-2) 0 0; padding: 0; }
.course-plan-lessons li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-1) 0;
  border-bottom: 1px solid var(--line);
}
.course-plan-minutes { color: var(--ink-muted); font-size: 0.9rem; white-space: nowrap; }

@media (max-width: 900px) {
  .detail-grid { grid-template-columns: 1fr; }
  .detail-aside { position: static; }
  /* Высоту обложке здесь больше не задаём: она квадратная по пропорции,
     а фиксированные 130 пикселей вернули бы обрезку (скилл ui-layout,
     ошибка 1 — высота изображения задаётся пропорцией, а не числом). */
}


/* --- Всплывающие окна ---------------------------------------------------- */

/* Закрытие по Esc, блокировку страницы под окном и возврат фокуса делает
   сам <dialog>. Здесь только оформление. */
/* Окно висит над страницей и не участвует в её потоке.

   Здесь стояло position: relative — ради того, чтобы крестик в углу
   позиционировался относительно окна. Это перебивало правило браузера
   dialog:modal { position: fixed }: правила автора сильнее правил
   браузера независимо от специфичности. Окно оставалось в потоке
   документа, то есть на своём месте в сетке карточек, и при открытии
   карточки из нижнего ряда браузер прокручивал страницу к ней —
   вверх списка.

   position: fixed делает то же, что и relative, для крестика: любой
   позиционированный элемент задаёт систему координат для absolute
   внутри себя. */
.modal {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(980px, calc(100% - 32px));
  max-height: min(88vh, 820px);
  padding: 0;
  border: 0;
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-lifted);
  overflow: auto;
}
.modal::backdrop { background: rgba(26, 18, 38, 0.55); }

/* Крестик поверх содержимого, а не в отдельной шапке: шапка занимала
   строку и отодвигала заголовок карточки вниз. */
.modal-close {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  z-index: 2;
  width: 44px;
  height: 44px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}
.modal-close:hover { background: var(--brand-100); color: var(--brand-900); }

/* Слева изображение, справа всё остальное. Кнопка записи стоит выше
   текста: тот, кто уже решил, не должен пролистывать описание. */
.modal-card {
  display: grid;
  grid-template-columns: minmax(220px, 340px) 1fr;
  gap: var(--space-8);
  padding: var(--space-6);
  align-items: start;
}
/* В окне обложка показывается так же, как на плитке: квадратом и целиком.
   `max-height: 200px` из общего правила `.card-image` резал её вдвое —
   окно было шире двухсот пикселей, и `cover` срезал верх и низ. */
.modal-media .card-image {
  margin: 0;
  width: 100%;
  height: auto;
  max-height: none;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  object-position: center;
  border-radius: var(--radius-md);
}
/* Визитка под портретом: цена, стаж, предметы и запись. Место под фото
   пустовало — справа сведений о квалификации всегда больше, чем высота
   снимка, а цена и кнопка нужнее рядом с лицом, чем в конце списка. */
.person-card { display: flex; flex-direction: column; gap: var(--space-2); margin-top: var(--space-4); }
.person-card .tile-facts { margin: 0; }
.person-card .btn { margin-top: var(--space-2); }
/* Предмет и цена часа строкой: цена без предмета рядом ничего не значит,
   а у каждого предмета она своя. */
.rate-facts { display: grid; grid-template-columns: 1fr auto; gap: var(--space-1) var(--space-3); margin: 0; }
.rate-facts dt { font-weight: 500; }
.rate-facts dd { margin: 0; text-align: right; color: var(--accent-800); font-weight: 600; }
.rate-facts dd .muted { font-weight: 400; }

.modal-info { display: flex; flex-direction: column; gap: var(--space-3); }
.modal-info h2 {
  margin: 0;
  padding-right: 56px;
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  color: var(--brand-700);
}
.modal-duration { margin: 0; color: var(--brand-700); font-style: italic; }
/* Оранжевый для текста берётся только тёмный: --accent-500 на белом
   даёт контраст 2.17 и не проходит порог (docs/18 §2.3). */
.modal-price {
  margin: 0;
  font-family: var(--font-heading);
  font-size: 1.8rem;
  font-weight: 700;
  color: var(--accent-800);
}
.btn-block { width: 100%; }
/* Строка о наборе стоит под кнопкой записи, а не над ней: «мест: 8» —
   довод для того, кто ещё думает, а решившему нужна кнопка. */
.modal-start { margin: 0; color: var(--ink-muted); font-size: 0.95rem; }
.modal-text { display: flex; flex-direction: column; gap: var(--space-3); }
.modal-text p { margin: 0; max-width: none; }
/* Перечень занятий: нумерация снаружи, отступ из шкалы. Без min-width: 0
   длинная строка занятия распирала бы колонку окна на телефоне
   (скилл ui-layout, ошибка 26). */
.modal-outline, .outline-list {
  margin: 0;
  padding-left: var(--space-6);
  min-width: 0;
  display: grid;
  gap: var(--space-2);
}
.outline-list { margin-bottom: var(--space-8); }
.outline-list li, .modal-outline li { max-width: 68ch; }
.modal-programme { margin: 0; font-weight: 600; }

@media (max-width: 760px) {
  /* Колонки рядом не помещаются: изображение сверху, текст под ним.
     Пропорция остаётся квадратной — иначе обложка на телефоне снова
     показывалась бы не целиком. */
  .modal-card { grid-template-columns: 1fr; gap: var(--space-4); padding: var(--space-4); }
  .modal-info h2 { padding-right: 48px; font-size: 1.5rem; }
}

/* Портрет педагога показывается целиком.
   Снимок уже обрезан квадратом тем, кто на нём изображён, и второй раз
   его режет витрина: `.card-image` ограничивает высоту двумястами
   пикселями и растягивает по ширине с `object-fit: cover`, а плитка
   задаёт полосу в 150. И там, и там у человека отрезало макушку.

   Правило стоит после всех, кто его перебивал бы: `.tile .card-image`,
   `.modal-media .card-image` и оба медиазапроса выше по файлу имеют ту же
   специфичность, и при равной выигрывает последний (скилл ui-layout,
   ошибка 15). `contain` вместо `cover` — ради карточек, заведённых до
   появления рамки: их снимок любой формы, и обрезать его нечем. */
.card-image-person,
.tile .card-image-person,
.modal-media .card-image-person {
  height: auto;
  max-height: none;
  aspect-ratio: 1 / 1;
  object-fit: contain;
  /* Портрет остаётся по центру: у `contain` ничего не обрезается,
     и прижимать его к верху незачем — общее правило для обложек
     карточек здесь не к месту. */
  object-position: center;
  background: var(--brand-50);
}

/* Обложка предмета — широкая, а не квадратная.
   Файлы предметов нарисованы в 16:9 (1664×928), и общее правило плитки
   вписывало их в квадрат 357×357: сверху и снизу оставалось по 78 пикселей
   пустоты, а плитка вырастала до 536. Кроме того, предметы и педагоги
   выглядели одинаково — обе витрины квадратные, хотя показывают разное.

   Селекторы длиной с те, что перебиваются (`.tile .card-image`,
   `.modal-media .card-image`), и правило стоит после них: при равной
   специфичности выигрывает последнее (скилл ui-layout, ошибка 15). */
.card-image-subject,
.tile .card-image-subject,
.modal-media .card-image-subject {
  height: auto;
  max-height: none;
  aspect-ratio: 16 / 9;
  object-position: center;
}
/* В витрине кадр заполняется целиком: файлы уже нужной формы, обрезка
   съедает меньше процента по краям. В окне — `contain`: там на картинку
   смотрят, а не пробегают глазами, и загруженный когда-нибудь снимок
   другой формы должен быть виден весь. */
.card-image-subject,
.tile .card-image-subject { object-fit: cover; }
.modal-media .card-image-subject { object-fit: contain; background: var(--brand-50); }

@media (max-width: 640px) {
  .modal { width: 100%; max-width: 100%; max-height: 100%; height: 100%; border-radius: 0; }
  /* Окно входа — исключение: в нём две плитки или форма из двух полей,
     и на весь экран оно разворачивается пустотой. Карточке программы
     полный экран нужен, короткому окну — нет.

     Высота именно `fit-content`, а не `auto`: у элемента `dialog` браузер
     сам ставит `fit-content`, а `auto` его перебивает — и окно, у которого
     заданы и верх, и низ (`inset: 0`), растягивается между ними.
     Это тот же случай, что ошибки 15 и 18 в скилле: правило автора
     перебило правило браузера, и результат выглядит как «стили не
     применились». */
  .modal-narrow {
    width: calc(100% - 24px);
    height: fit-content;
    max-height: 92vh;
    border-radius: var(--radius-md);
  }
}


/* --- Строка новостей ----------------------------------------------------- */

/* Заголовки последних новостей бегущей строкой. Нажатие ведёт на страницу
   новостей с карточками.

   Движение останавливается при наведении и фокусе, полностью отключается
   при системной настройке «уменьшить движение» и в версии для слабовидящих.
   Требование 2.2.2 WCAG о возможности остановить движущийся текст
   выполняется наведением и фокусом. */
.news-ticker {
  background: var(--brand-900);
  color: var(--ink-invert);
  overflow: hidden;
}
.news-ticker-link {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: min(1120px, calc(100% - 32px));
  margin: 0 auto;
  min-height: var(--ticker-h);
  color: var(--ink-invert);
}
.news-ticker-link:hover { color: var(--ink-invert); text-decoration: none; }
.news-ticker-label {
  flex: none;
  padding: 2px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--accent-500);
  color: var(--ink);
  font-size: 0.8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
/* Окно, в котором едет лента. Без overflow строка растянула бы страницу
   и дала горизонтальную прокрутку. */
.news-ticker-viewport { flex: 1; overflow: hidden; }
.news-ticker-track {
  display: inline-flex;
  gap: var(--space-8);
  white-space: nowrap;
  will-change: transform;
  animation: news-ticker 32s linear infinite;
}
.news-ticker-link:hover .news-ticker-track,
.news-ticker-link:focus-visible .news-ticker-track { animation-play-state: paused; }
.news-ticker-item { font-size: 0.95rem; }
.news-ticker-item + .news-ticker-item::before { content: "— "; opacity: 0.6; }
.news-ticker-more { flex: none; font-size: 0.9rem; text-decoration: underline; white-space: nowrap; }

@keyframes news-ticker {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@media (max-width: 760px) {
  /* На узком экране на строку остаётся мало места: подпись «Все новости»
     прячется, нажатие по самой строке никуда не девается. */
  .news-ticker-more { display: none; }
}

/* Движение отключается совсем: заголовки просто стоят на месте. */
@media (prefers-reduced-motion: reduce) {
  .news-ticker-track { animation: none; }
}
body.vision .news-ticker-track { animation: none; }
body.vision .news-ticker { background: var(--v-ink); color: var(--v-bg); }
body.vision .news-ticker a { color: var(--v-bg); }


/* --- Преподаватели предмета ---------------------------------------------- */

/* Две строки по три фотографии: карточка предмета показывает, что вести
   занятие есть кому, а выбирают преподавателя в своём разделе. */
.subject-teachers { display: flex; flex-direction: column; gap: var(--space-3); }
.subject-teachers-title { margin: 0; font-size: 1rem; }
.teacher-thumbs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
  list-style: none;
  margin: 0;
  padding: 0;
}
.teacher-thumbs a {
  display: block;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--brand-100);
}
.teacher-thumbs img { width: 100%; height: 100%; object-fit: cover; display: block; }
.teacher-thumbs a:hover { outline: 2px solid var(--brand-600); }
.teacher-thumb-letter {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  font-family: var(--font-heading);
  font-size: 1.4rem;
  font-weight: 700;
  color: var(--brand-700);
}

/* --- Отбор преподавателей ------------------------------------------------ */

.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.filter-bar label { display: flex; flex-direction: column; gap: var(--space-1); font-size: 0.9rem; }
.filter-bar select,
.filter-bar input[type="search"] {
  min-height: 44px;
  padding: 0 var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font: inherit;
}
.filter-reset { align-self: center; font-size: 0.9rem;
  /* Область нажатия 44: ссылка в панели отбора работает как кнопка,
     и промах по ней сбрасывает не то, что человек хотел. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.filter-count { margin: 0 0 var(--space-6); color: var(--ink-muted); font-size: 0.9rem; }
.filter-empty { margin-top: var(--space-6); color: var(--ink-muted); }

/* Карточка преподавателя, к которой перешли по якорю с карточки предмета,
   не должна прятаться под липкой шапкой. */
.card-grid > .tile[id] { scroll-margin-top: calc(var(--header-h) + var(--space-6)); }

/* --- Анкета: поля сгруппированы блоками ----------------------------------
   Одна колонка подряд из шестнадцати полей — анкета, которую не дозаполняют.
   Группа с заголовком показывает, сколько осталось. */
.container-narrow { max-width: 760px; }

.form-block {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--space-6);
  margin-bottom: var(--space-6);
}
.form-block legend {
  padding: 0 var(--space-3);
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--ink);
}
/* Отметка у заголовка блока: что появится сразу, а что после проверки
   (docs/16 Р-25). Без неё человек не понимает, почему одна правка видна
   на сайте немедленно, а другая ждёт решения. */
.badge-instant,
.badge-review,
.badge-mixed {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 2px var(--space-3);
  border-radius: var(--radius-pill);
  font-size: 0.75rem;
  font-weight: 500;
  vertical-align: middle;
}
.badge-instant { background: var(--brand-100); color: var(--brand-900); }
.badge-review { background: var(--accent-100); color: var(--accent-800); }
.badge-mixed { background: var(--brand-50); color: var(--ink-muted); }

/* Уход с сайта. Отделено от анкеты рамкой и цветом: это не ещё одно поле,
   а действие, которого не ждут. Кнопка удаления — ссылкой, а не заливкой:
   к красной кнопке рука тянется, а это последнее, что здесь нужно. */
.danger-zone { margin-top: var(--space-8); border-color: var(--danger); }
.danger-zone h3 { margin: 0 0 var(--space-2); font-size: 1.1rem; color: var(--danger); }
.danger-zone form { margin-bottom: var(--space-4); }
.danger-zone form:last-child { margin-bottom: 0; }
.link-danger { color: var(--danger); }
.link-danger:hover { color: var(--danger); text-decoration: underline; }
.form-block > label { margin-bottom: var(--space-4); }
.form-block .form-note { margin-top: 0; }

.field-help {
  display: block;
  margin-top: 4px;
  font-size: 0.9rem;
  font-weight: 400;
  /* Токен называется --ink-muted; несуществующий var() просто наследовал
     цвет подписи, и подсказка не отличалась от неё. */
  color: var(--ink-muted);
}

/* Набор флажков — предметы в анкете. Правило писалось под разметку `ul`
   и не применялось ни разу: Django рисует набор вложенными `div`, и флажки
   растягивались на всю ширину поля высотой 46 пикселей. Поэтому зацепка
   теперь за свой класс на виджете, а не за структуру чужой разметки. */
.checkbox-group {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 0 var(--space-4);
}
.checkbox-group > div { display: flex; }
/* Селектор длиннее общего `.request-form label`: при равной специфичности
   выигрывает последнее правило, а от порядка в файле зависеть не должно
   (скилл ui-layout, ошибка 15). */
.request-form .checkbox-group label,
.checkbox-group label {
  display: flex;
  /* Перенос: «Обществознание» и «1100 ₽/час» в колонке 224 пикселя не встают
     в строку, и цена вылезала за край подписи на 25 пикселей. Цена уходит
     во вторую строку, а не сжимается: у неё `nowrap`, и сжиматься ей нечем. */
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: center;
  width: 100%;
  font-weight: 400;
  /* Палец попадает по строке целиком, а не только по квадрату 13×13. */
  min-height: 44px;
}
/* Предметы в анкете: в подписи стоит ещё и цена, и «Обществознание —
   1100 ₽/час» в колонке 190 пикселей ложилось в три строки. Селектор
   из двух классов, а не один: при равной специфичности решал бы порядок
   правил в файле (скилл ui-layout, ошибка 15). */
.checkbox-group.checkbox-group-wide {
  /* `min(280px, 100%)`, а не 280px: на 360 пикселях колонка шире окна
     распирала страницу на 16 пикселей и давала горизонтальную прокрутку
     (скилл ui-layout, ошибка 14). */
  grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
}
/* Цена переносом не разрывается: подпись ломалась и по пробелу, и по косой
   черте, и «час» уезжало на вторую строку без числа. Переносится подпись
   после тире — там разрыв читается. */
.checkbox-group .price-tag { white-space: nowrap; }
.request-form .checkbox-group input[type="checkbox"],
.checkbox-group input[type="checkbox"] {
  flex: none;
  width: 24px;
  height: 24px;
  min-height: 24px;
  padding: 0;
  accent-color: var(--brand-600);
  cursor: pointer;
}


/* --- Личный кабинет ------------------------------------------------------
   Кабинет верстается как страница сайта, а не как отдельный интерфейс:
   анкету заполняют с телефона, и своя раскладка здесь означала бы вторую
   мобильную вёрстку без единой причины. */

/* Третий тон плашки: «идёт проверка» — не успех и не ошибка. */
.form-message-info { background: #eef4fb; border-color: var(--info); color: #143b63; }
.form-message h2 { margin: 0 0 var(--space-2); font-size: 1.15rem; }
.form-message p:last-child { margin-bottom: 0; }
.form-message .btn { margin-top: var(--space-3); }

/* Кнопки в ряд, на узком экране — в столбец. Кнопка выхода живёт
   в форме: выход делается POST-запросом, иначе его выполнит
   любой предзагрузчик ссылок. */
.cabinet-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: center;
  margin: var(--space-6) 0;
}
.cabinet-actions form { margin: 0; }

.cabinet-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  padding: var(--space-6);
  margin-bottom: var(--space-6);
}
.cabinet-card h2 { margin-top: 0; font-size: 1.2rem; }
.cabinet-card .feature-list { margin: 0; }
/* В этом списке у пунктов нет пояснений — только названия. Поля карточки,
   рассчитанные на заголовок с текстом, делали из трёх строк три пустые
   коробки. */
.cabinet-card .feature-list li { padding: var(--space-3) var(--space-4); }

/* Порядок шагов регистрации: что произойдёт дальше — до формы, а не после. */
.steps {
  margin: 0 0 var(--space-6);
  padding-left: var(--space-6);
  color: var(--ink-muted);
}
.steps li { margin-bottom: var(--space-2); }

@media (max-width: 480px) {
  /* На узком экране кнопки во всю ширину: рядом они дают по 140 пикселей
     на кнопку, и подписи переносятся посреди слова. */
  .cabinet-actions { flex-direction: column; align-items: stretch; }
  .cabinet-actions .btn, .cabinet-actions form, .cabinet-actions button { width: 100%; }
}

/* --- Окно с заголовком и текстом -----------------------------------------
   Второй вид окна: у карточек программ содержимое лежит в `.modal-card`
   со своими полями, а у служебных окон — в `.modal-head` и `.modal-body`,
   и полей у них не было вовсе. Текст упирался в край окна, заголовок
   налезал на крестик. Оформление окна должно быть у обоих видов,
   а не у того, с которого начинали. */
.modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-6) var(--space-6) var(--space-4);
  border-bottom: 1px solid var(--line);
}
.modal-head h2 {
  margin: 0;
  font-size: 1.5rem;
  color: var(--brand-700);
}
/* Крестик в шапке стоит в потоке, а не поверх содержимого: заголовку
   не нужен отступ под него, а нажать по нему на телефоне проще. */
.modal-head .modal-close { position: static; flex: none; }
.modal-body { padding: var(--space-6); }
.modal-body > :first-child { margin-top: 0; }
.modal-body > :last-child { margin-bottom: 0; }
.modal-body p { max-width: none; }

/* Окно входа уже карточки программы: форма из двух полей на 980 пикселях
   выглядит потерянной (скилл ui-layout, ошибка 3). */
.modal-narrow { width: min(520px, calc(100% - 32px)); }

/* Заголовок окна входа стоит по центру, а крестик — поверх шапки в углу.
   Внутренние поля равны его ширине, иначе длинный заголовок на узком
   экране заезжает под крестик. */
.modal-narrow .modal-head {
  position: relative;
  justify-content: center;
  padding-inline: 56px;
  text-align: center;
}
.modal-narrow .modal-head .modal-close {
  position: absolute;
  top: 50%;
  right: var(--space-3);
  transform: translateY(-50%);
}

/* Две дороги в одном окне входа: педагог и слушатель. Кроме них в шаге
   ничего нет, поэтому кнопки занимают всё место: попасть по такой можно
   не целясь, и выбор читается с одного взгляда. */
.modal-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-4);
}
.modal-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 140px;
  padding: var(--space-4) var(--space-6);
  /* Рамка в два пикселя: у кнопки во весь блок тонкая линия теряется
     и читается как разделитель, а не как край нажимаемой области. */
  border: 2px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--brand-800);
  font: inherit;
  font-size: 1.25rem;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
  transition: border-color .16s ease, background-color .16s ease;
}
.modal-tile:hover, .modal-tile:focus-visible {
  border-color: var(--brand-600);
  background: var(--brand-50);
  text-decoration: none;
}
/* Плитка бывает и ссылкой, и кнопкой в форме — выглядеть должны одинаково.
   Форма растягивается на всю ячейку сетки, иначе кнопка внутри неё окажется
   уже соседней плитки. */
.modal-tiles form { display: flex; margin: 0; }
.modal-tiles form .modal-tile { width: 100%; }
/* Выход — действие обратное входу, и выделять его цветом незачем. */
.modal-tile-quiet { color: var(--ink-muted); }

/* Шаги внутри окна: выбор, вход, восстановление пароля. Скрытый шаг
   прячется атрибутом `hidden` — общее правило `[hidden]` сильнее любого
   display, поэтому собственного display у шага нет (ошибка 18). */
.modal-pane { display: flex; flex-direction: column; gap: var(--space-4); }
.modal-pane .request-form { padding: 0; }
.modal-back {
  align-self: flex-start;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink-muted);
  font: inherit;
  font-size: 0.92rem;
  cursor: pointer;
}
.modal-back:hover { color: var(--brand-700); }
/* Ссылка, которая на самом деле кнопка: переключает шаг окна, а не ведёт
   по адресу. Выглядеть должна ссылкой — посетителю разница не видна. */
/* Выглядит ссылкой, но это кнопка — «Удалить фотографию», «Убрать программу»,
   «Удалить анкету». Замер на 360 дал высоту 26 пикселей при пороге 44
   (критерий 2.5.5): по такой цели промахиваются пальцем, а промах здесь
   стоит стёртой анкеты. Высоту даёт область нажатия, а не кегль —
   подчёркнутая строка остаётся такой же. */
.link-button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--brand-700);
  font: inherit;
  text-decoration: underline;
  cursor: pointer;
}
.link-button:hover { color: var(--brand-900); }
.modal-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  color: var(--ink-muted);
  font-size: 0.92rem;
}
/* Ссылка и кнопка-ссылка в одной строке должны выглядеть одинаково:
   посетителю незачем знать, что одна ведёт на страницу, а вторая
   переключает шаг окна. */
.modal-links a { color: var(--brand-700); text-decoration: underline; }

@media (max-width: 560px) {
  /* Две плитки рядом дают по 130 пикселей: подпись переносится посреди
     слова. На узком экране они идут друг под другом. */
  .modal-tiles { grid-template-columns: 1fr; }
  .modal-head, .modal-body { padding: var(--space-4); }
}

/* Приписка в ячейке таблицы: должность руководителя под названием органа,
   прочерки адреса и телефона под фамилией учредителя. Отдельные колонки
   под такие значения разгоняют таблицу на семь столбцов, а спрятать их
   стилями нельзя — размеченные данные обязаны быть видимы (п. 27). */
.table-note {
  margin: 4px 0 0;
  font-size: 0.9rem;
  color: var(--ink-muted);
}
