/* Заплатка под смену тегов ради клавиатуры: корзины «Категорий» стали
   <button>, а их содержимое — <span>. Возвращаем блочное поведение,
   иначе строки схлопываются в одну. */
.cat-box { font-family: inherit; text-align: inherit; width: 100%;
           background: var(--surface); cursor: pointer; }
.cat-box-title, .cat-box-items { display: block; }
.flashcard { cursor: pointer; }

/* ===== Горизонтальная прокрутка на 320–375px =====
   Замер аудита: словарь 437px при окне 320 (+117), чат 394 (+74),
   «Пары» 382 (+62). На телефоне кнопка удаления слова уезжала за край —
   удалить слово было нельзя вообще. */

/* Четыре фильтра словаря в одну строку не помещаются */
.dict-filters { display: flex; gap: 6px; flex-wrap: wrap; }

/* min-width:auto у flex-элемента держит ширину по содержимому и распирает
   строку изнутри — отсюда и вылет за экран */
.dict-row .d-en { min-width: 0; flex: 1 1 auto; }
.chat-input-row { display: flex; gap: 10px; flex-wrap: wrap; }
.chat-input-row input { min-width: 0; flex: 1 1 140px; }
.pair-item, .scr-tile { min-width: 0; }

@media (max-width: 720px) {
  /* Перевод уходит на свою строку: в одну строку с оригиналом и кнопками
     он не помещается ни при каком раскладе */
  .dict-row { flex-wrap: wrap; }
  .dict-row .d-ru { flex: 1 0 100%; }
  /* Цель нажатия: было 20px по ширине при 44px по высоте */
  .d-del { min-width: 44px; }
}

/* ===== Ответ различается знаком, а не только цветом =====
   Зелёный «верно» и шалфейный акцент по яркости почти совпадают (1,07:1),
   а при дальтонизме верный и неверный ответ становятся одним серо-бурым.
   Галочка и крестик работают всегда — это требование WCAG 1.4.1. */
.ans-mark { font-weight: 800; margin-left: 4px; }
.mcq-option.right { background: var(--accent); color: var(--on-accent);
                    border-color: var(--accent-deep); }
.mcq-option.wrong { border-width: 2px; border-color: var(--err); }

/* Живые области: результат ответа менялся молча — скринридер о нём
   не сообщал ничего. */
.type-feedback { min-height: 1.4em; }

/* Логотип рисуется тем же движком, что и персонаж: системный эмодзи выглядел
   по-разному на каждой ОС и в шалфейной гамме бил ярче всего на экране. */
.brand-cat { width: 28px; height: 28px; display: inline-flex; flex: none; }
.brand-cat svg { width: 100%; height: auto; animation: none; }

/* Заголовок лендинга: без <br> и с балансировкой строк — браузер сам
   раскладывает их ровно, вместо четырёх рваных получается две-три. */
.hero { max-width: min(900px, 92vw); padding-top: var(--space-48); }
.hero h1 {
  font-size: clamp(42px, 6.4vw, var(--text-display));
  letter-spacing: -0.03em;
  text-wrap: balance;
  max-width: 18ch;
}
/* Кот на входных экранах рисует уши выше своего бокса — без отступа сверху
   они срезались краем окна на всех трёх страницах. */
@media (max-width: 480px) { .hero { padding-top: var(--space-32); } }

/* Второй кот в приветствии главной — лишний: персонаж уже стоит в карточке
   действия двадцатью пикселями ниже и там он что-то говорит. */
.dash-head .cat-avatar { display: none; }

/* ===== Панель репетитора и экран ученика — одна система =====
   Арт-директор осознанно сделал панель плотнее: это рабочий инструмент,
   а не игра. Спорить с этим не буду — но он ушёл слишком далеко.
   Замер критика: у панели текст 13px против 18 у ученика, кнопка 8px радиуса
   против 999, и цена «799 ₽» на экране подписки оказалась МЕЛЬЧЕ основного
   текста ученика. Экран, где берут деньги, не может быть самым мелким.

   Плотность оставляем, но выражаем её ШАГОМ ШКАЛЫ, а не отдельной системой:
   нижняя граница кегля 14px, форма кнопок общая с ученической. */
:root:has(#screen-verify) {
  --text-caption:    14px;   /* было 13 — ниже этого читать тяжело */
  --text-body-sm:    16px;
  --text-body:       17px;
  --text-subheading: 20px;
  --text-heading-sm: 22px;
  --text-heading:    28px;

  --radius-card: 14px;       /* было 10, у ученика 18 — плотнее, но родное */
  --radius-btn:  999px;      /* форма общая: кнопка везде кнопка */
  --radius-tag:  999px;
}
:root:has(#screen-verify) .card,
:root:has(#screen-verify) .plan-card,
:root:has(#screen-verify) .admin-stat,
:root:has(#screen-verify) .pack-card {
  /* Без тени карточки читались как строки таблицы — отсюда и ощущение,
     что панель собрана другим человеком. */
  box-shadow: var(--shadow-soft);
}
/* Цена — это главное число экрана подписки, а не подпись к нему. */
:root:has(#screen-verify) .plan-price,
:root:has(#screen-verify) .pack-price { font-size: var(--text-heading); font-weight: 800; }

/* Единственная разница, которую стоит УСИЛИТЬ: у панели нет персонажа.
   Кот — язык ученика, а не рабочего инструмента взрослого. */
:root:has(#screen-verify) .topbar .brand-cat { display: none; }

/* ===== Мобильная шапка: 143px из 812 — это 17,6% экрана, и она липкая =====
   Три ряда: марка + чипы, потом навигация в две строки вразнобой.
   На телефоне меню уезжает вниз — туда, где его достаёт большой палец,
   а сверху остаётся только марка и переключатель темы. */
@media (max-width: 600px) {
  /* backdrop-filter на шапке создаёт containing block, и position: fixed
     внутри неё отсчитывается от самой шапки, а не от окна — меню вставало
     сверху вместо низа. На 56-пиксельной полосе размытие всё равно не видно. */
  .topbar { backdrop-filter: none; }
  .topbar-inner {
    flex-wrap: nowrap;
    gap: var(--space-8);
    padding: var(--space-8) var(--space-16);
    min-height: 56px;
  }

  /* Меню — нижняя панель. position: fixed, а не sticky: панель обязана
     стоять на месте и при прокрутке длинного словаря. */
  .topbar .nav {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 40;
    display: flex;
    justify-content: space-around;
    gap: 0;
    padding: var(--space-8) var(--space-8) max(var(--space-8), env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--paper) 94%, transparent);
    backdrop-filter: blur(12px);
    border-top: var(--border-w) solid var(--line-soft);
  }
  .topbar .nav-btn {
    flex: 1 1 0;
    min-width: 0;
    padding: 10px 4px;
    text-align: center;
    border-radius: var(--radius-card);
    /* overflow: hidden + text-overflow: ellipsis отсюда убраны. Пока пункт
       был голым текстом, обрезка спасала от распухания панели. С иконкой
       она стала вредить: «Тренировки» резалось на «Тренировк» — подпись под
       иконкой и так однострочная, а её кегль подобран под самое длинное
       слово (см. блок нижней панели в конце файла). */
  }
  /* Отступ снизу считается от --tabbar-h — константы из того же блока */
  main { padding-bottom: calc(var(--tabbar-h) + var(--space-8) + env(safe-area-inset-bottom)); }
  .site-footer { margin-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }

  /* Очки и уровень дублируются карточками прогресса двумя экранами ниже —
     в шапке они только отнимают место у имени и кнопки выхода. */
  .user-chip #xp-chip,
  .user-chip #streak-chip,
  .user-chip #user-level-chip { display: none; }
  .user-chip { gap: var(--space-8); flex: 0 1 auto; min-width: 0; }
  .user-chip #user-name-chip {
    max-width: 9ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* Цель нажатия: ссылки «выйти» и «пароль» были 12px без запаса */
  .user-chip .link-btn { min-height: 44px; display: inline-flex; align-items: center; }
}

/* Длинные подписи меню не влезают в нижнюю панель на 375px: «Чат с Савелием»
   обрезался на половине слова. На узком экране остаётся короткая форма. */
@media (max-width: 820px) {
  .nav-long { display: none; }
  .topbar .nav-btn .badge { font-size: 10px; padding: 1px 5px; }
}

/* ===== Две гарнитуры, а не одна =====
   --font-ui: "Inter" был объявлен в токенах, но шрифт не загружался: поля
   ввода рендерились системным (SF Pro на Маке, Segoe UI на Windows), всё
   остальное — Nunito. Получалась случайная двухшрифтовость, худший из
   вариантов. Теперь Inter загружен и роли разведены честно:
     Nunito — заголовки и слово-герой: округлый, тёплый, с характером;
     Inter  — весь рабочий текст: цифры, метки, кнопки, таблицы.
   Nunito на длинном тексте читается «детским», Inter на заголовках —
   безликим; каждый на своём месте работает. */
body, input, textarea, select, button,
.btn, .btn-primary, .btn-ghost, .chip, .tab, .nav-btn,
.stat-label, .stat-note, .muted, .muted-small, .section-note,
.hero-sub, .d-ru, .ex-desc, .plan-row, .at-table, .lb-row {
  font-family: var(--font-ui);
}

h1, h2, h3, .section-title, .dash-head h2,
.today-title, .stat-value, .quiz-word, .w-en, .d-en,
.result-value, .plan-price, .pack-price, .brand, .handnote {
  font-family: var(--font-display);
}

/* Inter плотнее Nunito на том же кегле — вторичный текст компенсируем весом,
   иначе он выглядит бледнее, чем задумано. */
.muted, .muted-small, .stat-note, .section-note { font-weight: 450; }

/* ===== Чат на узком экране =====
   Замер критика на 375: ряд ввода низом 753 против верха нижней панели 751 —
   перекрытие в 2px, поле ввода ужато до 168px, плейсхолдер обрезан,
   шапка чата «кот-репетитор · онлайн» ломалась на три строки. */
@media (max-width: 600px) {
  .chat-head, .chat-head-info {
    flex-wrap: wrap;
    row-gap: var(--space-4);
  }
  .chat-head-info { flex: 1 1 100%; min-width: 0; }
  /* Подпись «онлайн» и название роли не должны рвать слово пополам */
  .chat-head-info .chat-sub { white-space: nowrap; }

  /* Поле не может быть уже 180px: иначе в нём не видно, что пишешь */
  .chat-input-row input { min-width: 180px; flex: 1 1 180px; }
  /* Зазор до нижней панели: без него ряд ввода лез под неё */
  .chat-input-row { margin-bottom: var(--space-16); }
  /* Коробка чата занимала фиксированную высоту и оставляла 190px пустоты
     под единственной репликой */
  .chat-box { min-height: 40vh; max-height: 52vh; }

  /* Кнопка голосового чата — иконкой, подпись съедала строку */
  #voice-mode-btn { min-width: 44px; }
  #voice-mode-btn .vm-label { display: none; }
}

/* Нижняя панель включалась только ниже 768 — ровно на 768 шапка снова
   становилась двухэтажной и занимала 104px. Планшет тоже телефон, если
   ширины не хватает на семь пунктов в строку. */
@media (max-width: 820px) {
  .topbar { backdrop-filter: none; }
  .topbar .nav {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
    display: flex; justify-content: space-around; gap: 0;
    padding: var(--space-8) var(--space-8) max(var(--space-8), env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--paper) 94%, transparent);
    backdrop-filter: blur(12px);
    border-top: var(--border-w) solid var(--line-soft);
  }
  .topbar .nav-btn { flex: 1 1 0; min-width: 0; text-align: center; }
  main { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }
  .site-footer { margin-bottom: calc(64px + env(safe-area-inset-bottom)); }
  .topbar-inner { flex-wrap: nowrap; min-height: 56px; }
}

/* Высота чата считалась как 100vh минус 140 — число взято под старую шапку
   в 143px и не учитывает нижнюю панель. Плюс форма ввода вылезала за
   пределы обёртки: флекс-ребёнок не сжимается ниже своего содержимого,
   пока ему не поставить min-height: 0. Без него любая max-height
   на родителе игнорируется содержимым.
   dvh вместо vh: на мобильном Safari 100vh считается по развёрнутому
   адресному полю, и низ экрана всегда оказывается за кадром. */
@media (max-width: 820px) {
  .chat-wrap {
    display: flex;
    flex-direction: column;
    /* шапка 57 + нижняя панель 61 + воздух */
    height: calc(100dvh - 130px);
    max-height: none;
    min-height: 0;
    overflow: hidden;
  }
  .chat-box {
    flex: 1 1 auto;
    min-height: 0;          /* вот без этого всё и разъезжалось */
    max-height: none;
    overflow-y: auto;
  }
  .chat-form, .chat-input-row { flex: 0 0 auto; }
}

/* Ряд ввода в чате: микрофон, язык, поле и отправка должны стоять в одну
   строку. Из-за flex-wrap кнопка отправки уезжала на вторую и съедала
   ещё 60px высоты — на телефоне это заметно. */
@media (max-width: 820px) {
  .chat-input-row {
    flex-wrap: nowrap;
    gap: var(--space-8);
    align-items: center;
  }
  .chat-input-row input { flex: 1 1 auto; min-width: 0; }
  /* Иконочные кнопки не должны растягиваться и не должны сжиматься ниже пальца */
  .chat-input-row .mic-btn,
  .chat-input-row .stt-lang-btn,
  .chat-input-row button[type="submit"] {
    flex: 0 0 auto;
    width: 44px; min-width: 44px; height: 44px;
    padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
  }
  /* Шапка чата: кот и подпись рядом, а не столбиком — экономит 90px */
  .chat-head { flex-direction: row; align-items: center; gap: var(--space-12); }
  .chat-head .cat-avatar { width: 48px; flex: none; }
  .chat-head-info { flex: 1 1 auto; }
}

/* Пузырь реплики кота ночью сливался с лентой — 1,07:1. Отдельная,
   самая светлая поверхность: только у неё в тёмной теме есть право
   выделяться, потому что это единственное, что нужно отличать глазом. */
[data-theme="night"] .speech-bubble,
[data-theme="night"] .msg-cat,
[data-theme="night"] .ach-toast { background: var(--surface-hi); color: var(--ink); }
/* --paper ночью — почти чёрный: текст тоста гас до 1,65:1 */
[data-theme="night"] .ach-toast i { color: var(--ink); }
[data-theme="night"] .ach-card,
[data-theme="night"] .stat-card { border: var(--border-w) solid var(--line-soft); }

/* Наведение ночью не давало никакого отклика: тень чёрным по чёрному
   не видна. Отзываемся границей — она в тёмной теме единственное,
   что читается. */
[data-theme="night"] .card:hover,
[data-theme="night"] .stat-card:hover,
[data-theme="night"] .ach-card:hover,
[data-theme="night"] .pack-card:hover {
  border-color: var(--accent-line);
  box-shadow: none;
}

/* Формы на десктопе растягивались во всю ширину карточки: поле «Имя»
   на лендинге было 850px. Ввести туда можно два слова — ширина обещает
   абзац и сбивает с толку. */
@media (min-width: 821px) {
  .auth-card form, #reset-form, #verify-form { max-width: 420px; margin-inline: auto; }
}

/* Заголовок, видимый только скринридеру: экран должен иметь имя в структуре
   документа, даже если визуально оно избыточно. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* Имя собеседника было <b> — жирность без семантики. Стал заголовком,
   размер оставляем прежним. */
.chat-title { font-size: var(--text-body-sm); font-weight: 800; margin: 0; }

/* Ссылка «к содержимому»: на телефоне меню — это 5 остановок Tab перед
   каждым экраном. Появляется только при фокусе с клавиатуры. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
}
.skip-link:focus {
  left: var(--space-12); top: var(--space-12);
  background: var(--surface); color: var(--ink);
  padding: 10px 18px; border-radius: var(--radius-btn);
  box-shadow: var(--focus-ring);
}

/* ===== «Слово дня»: три уровня вместо одной строки =====
   Слово было 20px в общем потоке с кнопкой и переводом — строка рвалась
   посреди фразы, а само слово, ради которого карточка и существует,
   ничем не выделялось. */
.wod-main {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  margin: var(--space-8) 0 var(--space-4);
}
.wod-word {
  font-family: var(--font-display);
  font-size: var(--text-heading);
  font-weight: 800;
  line-height: var(--leading-tight);
  letter-spacing: -0.02em;
}
/* Кнопка озвучки — отдельный круглый контрол, а не знак внутри строки */
.wod-say-btn {
  flex: none;
  width: 40px; height: 40px; min-width: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border: var(--border-w) solid var(--line-control);
  border-radius: 999px;
  background: var(--surface);
}
.wod-tr {
  display: block;
  font-size: var(--text-body-sm);
  color: var(--ink-soft);
}
.wod-card .w-ex {
  display: block;
  margin-top: var(--space-12);
  font-size: var(--text-caption);
  color: var(--ink-soft);
}

/* ===== Иконки =====
   Контур наследует цвет текста и меняется вместе с темой — в отличие от
   эмодзи, которые рисуются шрифтом ОС, всегда полноцветные и всегда ярче
   окружения. Двадцать карточек тренировок были радугой поверх шалфея. */
.ic { display: block; flex: none; }
[data-icon] { display: inline-flex; align-items: center; justify-content: center; }

/* Иконка в карточке упражнения.
   Была --ink-soft — ровно тот же тон, что у описания под ней. В сетке из
   двадцати карточек она не отличалась от текста и выбирать не помогала:
   глазу не за что зацепиться, все двадцать читаются одинаково серыми.
   Отдаём ей чернила заголовка — иконка и имя встают в одну группу, описание
   остаётся вторым уровнем. Как графика ей нужно 3:1, а --ink даёт 11,5:1
   днём и 11,6:1 ночью, так что запас двойной. */
.ex-icon {
  display: flex;                 /* был инлайн во всю ширину карточки */
  width: 28px; height: 28px;     /* общий бокс: иконки встают в ровную колонку */
  color: var(--ink);
  margin-bottom: var(--space-8);
}
/* Размер приходит атрибутами svg из icons.js; правило перебивает их, чтобы
   кегль иконки жил в стилях, а не в трёх местах кода. */
.ex-icon > .ic { width: 100%; height: 100%; }
/* Наведение, фокус и нажатие: чернила уходят в шалфей. Контраст падает
   с 11,5 до 5,9:1 — обе величины выше порога, а видно именно смену цвета:
   на уже тёмной иконке она заметнее, чем смена светлоты. */
.ex-card:hover .ex-icon,
.ex-card:focus-visible .ex-icon,
.ex-card:active .ex-icon { color: var(--accent); }
.ex-title-icon { display: inline-flex; vertical-align: -3px; color: var(--accent); }

.ach-toast-icon { color: var(--accent); }

/* Кнопки с иконкой и подписью: зазор и выравнивание по центру строки */
.btn [data-icon], .btn .ic { vertical-align: -4px; margin-right: 6px; }
.say-btn [data-icon], .mic-btn [data-icon] { margin: 0; }

/* ===== Карточка слова =====
   Содержимое стояло впритык к краям: картинка касалась верхней границы,
   подсказка — нижней. Формально ничего не вылезало, но выглядело так,
   будто вылезает. Плюс пропорция 668×260 — это не карточка, а полоса. */
.flashcard {
  max-width: 420px;
  margin-inline: auto;
  min-height: 320px;
  display: flex;              /* иначе внутренняя часть не растянется */
}
/* Высота задавалась внешнему элементу, а рисуется внутренний: лишние 60px
   висели пустотой под видимой карточкой — оттуда и оторванные кнопки,
   и ощущение, что содержимое жмётся кверху. */
.flashcard-inner {
  flex: 1 1 auto;
  min-height: 320px;
}
.flashcard-face {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-16);
  padding: var(--space-32) var(--space-24);
  text-align: center;
}
/* Картинка к слову — крупная, но не в полкарточки */
.flashcard .word-art { font-size: 44px; line-height: 1; margin: 0; }
/* Слово — главное на карточке, ему и вес */
.flashcard .quiz-word {
  font-size: var(--text-heading);
  line-height: var(--leading-tight);
  margin: 0;
  word-break: break-word;
}
/* Подсказка отбивается от кнопки и не липнет к нижнему краю */
.flip-hint {
  font-size: var(--text-caption);
  color: var(--ink-soft);
  margin: 0;
}
/* Кнопки ответа — на своей строке, с воздухом от карточки */
.quiz-buttons { margin-top: var(--space-24); gap: var(--space-12); }

@media (max-width: 600px) {
  .flashcard, .flashcard-inner { min-height: 280px; }
  .flashcard { max-width: none; }
  .flashcard-face { padding: var(--space-24) var(--space-16); }
  .flashcard .word-art { font-size: 38px; }
}

/* Короткие экраны (тренажёр, упражнение) заканчивались на середине окна,
   и футер подползал сразу за ними — кот из подвала выглядывал из-под
   нижнего меню и читался как артефакт. Экран занимает высоту окна за
   вычетом шапки и панели, футер уходит за фолд. */
.screen:not(.hidden) {
  min-height: calc(100dvh - 130px);
}
@media (min-width: 821px) {
  .screen:not(.hidden) { min-height: calc(100dvh - 80px); }
}

/* ==========================================================================
   ЗНАЧОК НАГРАДЫ
   --------------------------------------------------------------------------
   Три замечания критика об одном объекте: чип заполнен иконкой на 39% и пуст
   внутри; ночью его граница не видна вовсе, иконка висит в воздухе; внутри
   карточки нет иерархии — имя и описание почти одного кегля.

   Плюс собственная ошибка прошлого захода, из-за которой две трети правки
   не работали: селектор был написан как `:not(.got)`, а класс в разметке —
   `ach-done`. То есть «закрытой» считалась КАЖДАЯ награда, включая
   полученные: им тоже ставилось opacity .38 и вторичный тон, и разница
   между «есть» и «нет» пропадала ровно там, где она и есть весь смысл экрана.
   ========================================================================== */

.ach-icon {
  position: relative;
  /* было 56 при иконке 28 — 50% по стороне, 25% по площади */
  width: 52px; height: 52px;
  /* граница объявлена всегда и прозрачной: иначе бокс дёргается на 2px
     между полученной и закрытой наградой */
  border: var(--border-w) solid transparent;
}
/* 32 в 52 — 62% по стороне вместо 50%. Иконка перестаёт плавать в пустом
   квадрате. Только прямой потомок: у замка внутри свой размер, 12px. */
.ach-icon > .ic { width: 32px; height: 32px; opacity: 1; }

/* Полученная: шалфейная иконка в кольце того же цвета на шалфейной подложке.
   Кольцо — единственное, что ночью отличает чип от карточки: подложка даёт
   против неё 1,10:1, то есть почти ничего, а --accent на подложке — 5,2:1
   днём и 5,8:1 ночью при требовании к графике 3:1.

   Ступень награды — тремя ступенями светлоты ОДНОГО шалфея, а не пастелью:
   у глины и лаванды нет «больше» и «меньше», у светлоты есть. Бронза —
   мягкая подложка акцента, серебро — та же гуще, золото — заливка акцентом
   в полную силу (та самая, что у главной кнопки; тёмные чернила по ней
   дают 7,7:1 днём и 9,0:1 ночью). Тот же приём, что у наклеек на главной. */
.ach-card.ach-done .ach-icon {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}
.ach-card.ach-done.ach-tier-silver .ach-icon {
  background: color-mix(in oklab, var(--accent) 26%, var(--surface));
}
.ach-card.ach-done.ach-tier-gold .ach-icon {
  background: var(--accent-fill);
  color: var(--on-accent-fill);
}

/* Закрытая: пустая рамка — «место под награду».
   Здесь стояла --line-soft: 1,20:1 днём и 1,39:1 ночью, то есть рамки не
   было ни в одной теме, и иконка действительно висела в воздухе.
   --line-control заведён в токенах как раз под границы контролов —
   3,00:1 днём и 4,35:1 ночью на вторичной поверхности. */
.ach-card:not(.ach-done) .ach-icon {
  background: none;
  border-style: dashed;
  border-color: var(--line-control);
  color: var(--ink-soft);
  /* Гашение стояло дважды: .5 на чипе и ещё .38 на иконке. В сумме 19% —
     это 1,69:1, вдвое ниже порога 3:1, значок фактически стирался.
     «Закрыто» и без того сказано рамкой, замком, полосой прогресса и
     приглушённым именем; гасить до невидимости незачем. --ink-soft даёт
     5,1:1 днём и 7,0:1 ночью. */
  opacity: 1;
}
/* Замок — маленьким бейджем в углу, поверх собственной иконки награды.
   Раньше все закрытые были одинаковыми замками, и различить «выучить 10
   слов» и «набрать 5000 очков» было нечем. */
.ach-lock {
  position: absolute; right: -2px; bottom: -2px;
  display: inline-flex; padding: 2px;
  background: var(--surface); border-radius: 999px;
  color: var(--ink-soft);
}

/* Иерархии внутри карточки не было: имя 18/600, описание 16/400 — шаг в один
   кегль, глаз его не берёт. Три сигнала вместо одного: кегль 20 против 16,
   вес 700 против 400, чернила против вторичного тона.
   Специфичность на класс выше нужной — чтобы правило не проигрывало
   .word-card .w-en, .ex-name, .ach-name из savely.css при любой перестановке
   файлов. */
.ach-card .ach-name {
  font-size: var(--text-body);
  font-weight: 700;
  line-height: var(--leading-tight);
  color: var(--ink);
}
.ach-card .ach-desc {
  font-size: var(--text-caption);
  font-weight: 400;
  line-height: var(--leading-snug);
  color: var(--ink-soft);
}
/* На телефоне сетка наград — две колонки по ~163px, и 20px полужирным
   рвёт «Неделя без пропусков» на три строки. Ступень остаётся: 18 к 16
   плюс вес и тон. */
@media (max-width: 600px) {
  .ach-card .ach-name { font-size: var(--text-body-sm); }
  .ach-icon { width: 48px; height: 48px; }
  .ach-icon > .ic { width: 28px; height: 28px; }
}
/* Иконка, вставленная вместо эмодзи в строке текста: выравниваем по базовой
   линии, иначе она «висит» выше строчных букв. */
.ic-inline { display: inline-flex; vertical-align: -3px; margin: 0 2px; }

/* ==========================================================================
   ГРУППЫ НАГРАД: РИТМ
   --------------------------------------------------------------------------
   Тот же приём, что у .ex-group на экране тренировок (см. ниже): раздел
   берёт общий .section-head, рассчитанный на разделы верхнего уровня
   (64px сверху + линия), поэтому первой группе линию и отступ снимаем —
   заголовок «Награды» уже отделил её от шапки экрана.
   ========================================================================== */
#ach-grid > .ach-group:first-child > .ach-group-head {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
.ach-group + .ach-group > .ach-group-head {
  margin-top: var(--space-40);
  padding-top: var(--space-20);
}
/* Заголовок группы — ступень «секция» из лестницы (24 против 36 титула):
   разделов восемь, и каждый обязан читаться частью экрана, а не своим
   экраном. */
.ach-group-head h3 {
  font-size: var(--text-subheading);
  font-weight: 700;
}
@media (max-width: 600px) {
  .ach-group + .ach-group > .ach-group-head {
    margin-top: var(--space-32);
    padding-top: var(--space-16);
  }
  .ach-group-head h3 { font-size: var(--text-body); }
}

/* ==========================================================================
   ТРЕНИРОВКИ НА ТЕЛЕФОНЕ: ДВЕ КОЛОНКИ
   --------------------------------------------------------------------------
   Замер критика на 375: двадцать карточек в одну колонку по ~118px — это
   2400px прокрутки, чтобы просто увидеть список режимов. Плюс в каждой
   карточке иконка сидит в углу, а справа от неё полстроки пустоты: колонка
   шириной 343px занята текстом на 130.

   Две колонки убирают и то и другое разом: экран тренировок с 2114px ужался
   до 1445 при том, что карточек стало больше (появились четыре группы).

   Правило стоит на 600, а не на 820, хотя панель меню ломается именно на
   820. Причина в замере: `auto-fill, minmax(200px, 1fr)` из style.css даёт
   1 колонку на 375, 2 на 601, 3 на 820 и 5 на 1280 — то есть в одну колонку
   сетка сваливается только на телефоне, а жёсткие две на 820 дали бы
   карточки по 390px, ту же самую полупустую строку, только шире.
   Проверено на 375 / 601 / 820 / 1280.
   ========================================================================== */
@media (max-width: 600px) {
  .ex-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-8);
  }
  /* Пара в строке всегда одной высоты: элемент грид-сетки по умолчанию
     тянется на всю строку (align-self: stretch), и пока это не отменено,
     «Карточки» и «Слово и картинка» стоят вровень. Правило не пишем — важно
     не сломать; отмечено, чтобы никто не поставил сюда align-items: start. */
  .ex-card {
    padding: var(--space-12);
    gap: var(--space-4);
  }
  /* В колонке 168px 28-пиксельная иконка над 16-пиксельным именем делает
     плитку верхотяжёлой. 24 — та же роль, спокойнее пропорция. */
  .ex-icon { width: 24px; height: 24px; margin-bottom: var(--space-4); }
  .ex-name { line-height: var(--leading-tight); }

  /* Описание убираем С ЭКРАНА, но не из документа.
     Ширина колонки — 144px под текст, это ~12 знаков в строке. Обрезка в две
     строки давала «Слово ↔ перевод, с…» и «Выбери правильный…»: подсказка
     ничего не подсказывает, а выглядит как оборванная вёрстка. Свободный
     перенос — три-четыре рваные строки на карточку и лента на три экрана,
     с которой всё и начиналось.
     Имя плюс своя иконка выбор обеспечивают: «Кроссворд», «Синонимы»,
     «Аудирование» — это меню, а не статья.
     Поэтому не display: none, а служебное скрытие: с экрана уходит, в
     дереве доступности остаётся. Иначе на телефоне скринридер терял то,
     что на десктопе проговаривается — и подсказку получали только зрячие. */
  .ex-desc {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
}

/* ==========================================================================
   ЧАТ: КОРОБКА ДЕРЖАЛА 400px ПУСТОТЫ
   --------------------------------------------------------------------------
   .chat-wrap ростом в экран плюс .chat-box { flex: 1 } — коробка всегда во
   всю высоту, даже когда в ней одно приветствие. На десктопе это ~400px
   пустого поля под единственным пузырём: выглядит не как начало разговора,
   а как незагрузившийся блок.

   На широком экране коробка растёт по содержимому и упирается в потолок;
   ряд ввода едет следом. Пустоты нет вовсе, а длинная переписка всё так же
   прокручивается внутри, а не растягивает страницу.
   ========================================================================== */
@media (min-width: 821px) {
  .chat-wrap { height: auto; min-height: 0; }
  .chat-box {
    flex: 0 1 auto;
    /* Пол и потолок вместо «во всю высоту». 260px — примерно три реплики:
       ниже коробка превращается в щель и перестаёт читаться как место для
       разговора, выше начинается та самая пустота. Между ними она растёт
       по содержимому.
       Остаток пустоты при одном приветствии уходит НАВЕРХ (см. правило
       margin-top: auto ниже): 130px над репликой читаются как «разговор
       только начался», те же 130px под ней читались как незагрузившийся
       блок. Это и была вся разница. */
    min-height: 260px;
    max-height: min(56vh, 520px);
    overflow-y: auto;
  }
}
/* На телефоне коробка обязана занимать экран: поле ввода должно стоять внизу,
   над панелью, куда дотягивается палец. Значит, пустота там неизбежна — но
   она уходит НАВЕРХ: реплики прижимаются к низу, как в любом мессенджере, и
   последняя всегда рядом с полем ввода.
   Именно margin-top: auto у первого ребёнка, а не justify-content: flex-end:
   при переполнении flex-end в прокручиваемом контейнере отрезает верх ленты
   и до первых реплик не добраться, а автоотступ схлопывается в ноль и
   прокрутка работает штатно. На десктопе свободного места нет — правило
   там ничего не делает. */
.chat-box > :first-child { margin-top: auto; }

/* Подсказки под лентой на 375 не помещались в строку и вставали столбиком —
   три кнопки во всю ширину, 200px по высоте, отобранные у самой переписки.
   Ряд с горизонтальной прокруткой: третья подсказка наполовину видна за
   краем и сама показывает, что ряд продолжается. Клавиатуре это не мешает —
   браузер подводит кнопку в видимую часть при переходе табом. */
@media (max-width: 600px) {
  .chat-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;                 /* полоса тут только шумит */
    margin: var(--space-8) 0;
    padding-bottom: var(--space-4);        /* место фокусному кольцу */
  }
  .chat-chips::-webkit-scrollbar { display: none; }
  .chat-chips .chip { flex: none; }
}

/* ==========================================================================
   МЕНЮ: ИКОНКА + ПОДПИСЬ
   --------------------------------------------------------------------------
   В разметке появились .nav-ico и .nav-label. Иконка сознательно НЕ красится
   собственным правилом: svg рисуется stroke="currentColor", а у .nav-btn уже
   описаны все три состояния цвета — покой --ink-soft (5,5:1 днём, 8,1:1
   ночью), наведение --ink, активный --accent (5,2:1 на своей подложке днём,
   5,8:1 ночью). Иконка наследует их бесплатно и не может рассинхронизоваться
   с подписью — а отдельное правило на цвет иконки именно этим и кончается.
   ========================================================================== */

/* Десктоп: строка. Зазор задаём явно — на пробеле между инлайновыми span
   он зависит от кегля и гарнитуры и разъезжается между пунктами. */
.topbar .nav-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  white-space: nowrap;
}
.nav-ico { flex: none; }

/* --------------------------------------------------------------------------
   Нижняя панель на телефоне и планшете: иконка над подписью
   -------------------------------------------------------------------------- */
@media (max-width: 820px) {
  :root {
    /* Не токен, а константа раскладки: высота панели входит в четыре расчёта
       ниже. Держать её числом в четырёх местах — верный способ разъехаться
       на первой же правке (так и было: 64px против фактических 61). */
    --tabbar-h: 66px;
  }

  .topbar .nav-btn {
    position: relative;
    flex-direction: column;
    justify-content: center;
    gap: var(--space-4);
    min-height: 44px;                  /* цель нажатия под палец ребёнка */
    padding: var(--space-4);
    /* Маленький боковой зазор: без него фон активного пункта растекался
       на всю ячейку и залезал под подписи соседей (клетки делят ширину
       поровну, gap у панели нулевой) — «Словарь» заезжал на «Тренировки». */
    margin: 0 2px;
    line-height: 1.15;
  }
  /* Общий бокс: без него пять иконок разного рисунка встанут на разной
     высоте и подписи поедут ступенькой. */
  .nav-ico { width: 22px; height: 22px; }
  .nav-ico > .ic { width: 100%; height: 100%; }

  /* 11px, а не 12: при пяти пунктах на 360px внутренняя ширина кнопки —
     около 61px, а «Тренировки» на 12px занимает 66 и упирается. Отсюда и
     брались обрезки. На 11px слово встаёт целиком с запасом, а разборчивость
     держит иконка над ним — читать подпись по буквам тут не нужно, она
     подтверждает выбор, а не сообщает его. */
  .nav-label {
    font-size: 11px;
    letter-spacing: -0.01em;
    white-space: nowrap;
    overflow: visible;
  }

  /* Счётчик словаря нельзя оставлять в потоке: он растягивает подпись
     «Словарь 1» до ширины, которой в пятипунктовой панели нет.
     Угол над иконкой — привычное место счётчика в нижней панели. */
  .topbar .nav-btn .badge {
    position: absolute;
    top: 0;
    left: 50%;
    margin-left: var(--space-4);
    padding: 0 5px;
    line-height: 1.5;
  }

  /* Все размеры вокруг панели считаются от одной константы: контент не должен
     уезжать под неё ни на телефоне, ни на планшете. */
  main { padding-bottom: calc(var(--tabbar-h) + var(--space-8) + env(safe-area-inset-bottom)); }
  .site-footer { margin-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom)); }
  /* Высота чата = экран минус то, что его обрамляет. 81px — это шапка (57)
     плюс верхний отступ экрана (24), измерено, а не прикинуто. Прежняя
     константа 130 брала панель за 73 и оставляла ряду ввода 1px до неё:
     формально не перекрытие, визуально кнопка отправки лежала на панели. */
  .chat-wrap { height: calc(100dvh - 81px - var(--tabbar-h)); }
}

/* ==========================================================================
   ГРУППЫ ТРЕНИРОВОК: РИТМ
   --------------------------------------------------------------------------
   Экран разбит на четыре .ex-group, и каждая взяла общий .section-head —
   а он рассчитан на разделы верхнего уровня: 64px сверху, волосяная линия,
   24px снизу. Сброс для первого раздела написан как `.wide > .section-head`
   и на вложенные группы не попадает, поэтому над первой группой вставало
   64+24px пустоты сразу под заголовком экрана, да ещё с линией поперёк —
   заголовок «Тренировки» оказывался отрезан от своего же содержимого.
   ========================================================================== */

/* Первая группа продолжает заголовок экрана — ей ни линия, ни отступ не нужны */
.ex-groups > .ex-group:first-child > .ex-group-head {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
/* Остальные — соседи внутри одного экрана, а не разделы: 40px вместо 64.
   Линию оставляем: это единственный разделитель на сайте, и здесь он как раз
   по делу — четыре сетки подряд без неё сливаются в одну. */
.ex-group + .ex-group > .ex-group-head {
  margin-top: var(--space-40);
  padding-top: var(--space-20);
}
/* Заголовок группы спорил с заголовком экрана: 28/700 против 36/800 — шаг
   всего в 1,29, а групп четыре и они повторяются вниз по странице.
   24px даёт ступень в полтора раза: «Тренировки» однозначно старше, группы
   однозначно её части. Правило точечное — h3 на других экранах не трогаем. */
.ex-group-head h3 {
  font-size: var(--text-subheading);
  font-weight: 700;
}
@media (max-width: 600px) {
  .ex-group + .ex-group > .ex-group-head {
    margin-top: var(--space-32);
    padding-top: var(--space-16);
  }
  .ex-group-head h3 { font-size: var(--text-body); }
}

/* ==========================================================================
   ШАПКА УПРАЖНЕНИЯ НА ТЕЛЕФОНЕ
   --------------------------------------------------------------------------
   .ex-head — это ряд из кнопки «← Тренировки» и заголовка. На 375px кнопка
   с подписью забирала ~300px из 343, заголовку оставалось 179, и «Выбор
   варианта» ломался на две строки поверх кнопки. Шапка выходила 617px —
   больше трети экрана до того, как ученик увидит сам вопрос.

   Лечим не переносом, а весом: на телефоне кнопка сжимается до стрелки
   (подпись убираем ЗРИТЕЛЬНО, не из документа — она остаётся именем
   кнопки для скринридера), заголовок опускается на ступень кегля.
   ========================================================================== */
@media (max-width: 600px) {
  .ex-head {
    gap: var(--space-8);
    margin-bottom: var(--space-12);
  }
  /* Круглая кнопка-стрелка: цель нажатия 44px остаётся, ширина падает
     с 300 до 44. Отрицательный отступ съедает внутренний padding кнопки,
     который для одной стрелки слишком велик. */
  .ex-head .btn[data-nav] {
    flex: none;
    width: 44px; height: 44px;
    padding: 0;
    border-radius: 50%;
    font-size: 0;                     /* прячем подпись, но не текстовый узел */
    line-height: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  /* Стрелка живёт в том же текстовом узле, что и подпись, — вернуть ей
     кегль отдельным правилом нельзя. Рисуем её псевдоэлементом. */
  .ex-head .btn[data-nav]::before {
    content: "←";
    font-size: 20px;
    line-height: 1;
  }
  /* 22px, а не токен: между --text-subheading (24) и --text-body ступени нет,
     а 24 всё ещё ломает «Выбор варианта» надвое на 360px. */
  .ex-head h2 {
    font-size: 22px;
    line-height: 1.15;
    min-width: 0;                     /* иначе flex-элемент не даёт себя сжать */
  }
}

/* ==========================================================================
   АВАТАР УЧЕНИКА В ПАНЕЛИ РЕПЕТИТОРА
   --------------------------------------------------------------------------
   Буква имени в кружке вместо эмодзи 🧑‍🎓. Двадцать одинаковых значков в
   списке не различают учеников — буква различает, и читается быстрее имени.
   Кружок шалфейный, буква чернилами: 11,5:1 днём, 11,6:1 ночью.
   ========================================================================== */
.stu-avatar {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 1;
  /* Кириллица в кружке сидит чуть выше оптического центра из-за того, что
     у прописных нет нижних выносных. Сдвиг возвращает её на место. */
  padding-top: 0.06em;
}
.stu-avatar[data-size="small"] { width: 40px; height: 40px; font-size: 17px; }
.stu-avatar[data-size="mid"]   { width: 56px; height: 56px; font-size: 24px; }

/* ==========================================================================
   МЕСТО В РЕЙТИНГЕ
   --------------------------------------------------------------------------
   Призовая тройка: значок и цифра рядом. Значок акцентом — он метит три
   строки из десяти, ради этого и стоит. Цифру оставляем: без неё второе
   место не отличить от третьего, а именно это в рейтинге и смотрят.
   ========================================================================== */
.lb-place.lb-top {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  color: var(--accent);
}
.lb-place.lb-top b { font-weight: 700; }

/* ==========================================================================
   ПЛАШКА НАГРАДЫ
   --------------------------------------------------------------------------
   Три поломки разом, все три от одного корня — центрирование через transform.

   1. В style.css плашка центрируется как left:50% + translate(-50%). В
      motion.css у неё же есть анимация появления, а она заканчивается на
      transform:none — и стирает сдвиг на -50%. Плашка вставала левым краем
      ровно по центру экрана.
   2. У position:fixed без ширины доступная ширина считается от left до
      правого края окна. При left:50% это половина экрана: 187px из 375.
      Плашка сжималась до min-content, и «Награда: Первый урок» переносилось
      в три строки, при том что max-width стоял 345px.
   3. bottom:26px — это под нижней панелью меню (её высота --tabbar-h).
      Плашка ложилась поверх кнопок.

   Лечим по разделению обязанностей: центрирование берут на себя поля,
   transform остаётся анимации и больше ни за что не отвечает.
   ========================================================================== */
.ach-toast {
  left: 0;
  right: 0;
  width: fit-content;
  max-width: min(92vw, 420px);
  margin-inline: auto;
  /* Сдвиг за край окна снимаем: показом теперь заведует анимация из
     motion.css, а два механизма на одном свойстве и дали поломку. */
  transform: none;
}
.ach-toast.show { transform: none; }
/* Текст плашки не переносится по одному слову на строку: заголовок держим
   в одну строку, описание — сколько нужно, но по ширине контейнера. */
.ach-toast > span { min-width: 0; }
.ach-toast b { white-space: nowrap; }
/* Название награды крупнее её условия: сейчас оба 16px, и строка «Награда:
   Первый урок» весит столько же, сколько служебная подпись под ней. */
.ach-toast b { font-size: var(--text-body); line-height: 1.25; }
.ach-toast i { font-size: var(--text-caption); line-height: 1.3; }   /* ступень 20/18 не читалась — на шкале caption заметный шаг */

@media (max-width: 820px) {
  /* Над панелью меню, а не поверх неё */
  .ach-toast {
    bottom: calc(var(--tabbar-h) + var(--space-12) + env(safe-area-inset-bottom));
  }
  /* На 375px «Награда: Первый урок» в одну строку не помещается никогда —
     здесь перенос разрешаем, иначе плашка вылезет за экран. */
  .ach-toast b { white-space: normal; }
}

/* ==========================================================================
   ПРАВКИ ПО РАЗБОРУ КРИТИКА
   ========================================================================== */

/* --- 1. Знак ответа падал на вторую строку -------------------------------
   .ans-mark — инлайновый спан, а внутри него svg с display:block из правила
   .ic выше по файлу. Блок внутри строки рвёт строку: список подпрыгивал
   с 48 до 65px на каждом отвеченном варианте, а галочка вставала ПОД
   текстом слева. Вариант делаем флексом — знак уходит вправо, высота
   строки не меняется. */
.mcq-option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
}
.ans-mark { margin-left: 0; flex: none; display: inline-flex; }
.ans-mark .ic { display: block; }

/* --- 2. Вариант ответа выглядел отключённым -------------------------------
   Заливка --surface-alt в этом интерфейсе означает «неактивно»: ровно ею
   красятся :disabled и вторичные карточки. А рамка 2px цветом --line-soft
   даёт к своей же заливке 1,1:1 — её физически не видно, два пикселя
   нарисованы впустую. Приводим к виду остальных кликабельных элементов. */
.mcq-option:not(.right):not(.wrong) {
  background: var(--surface);
  border: 1.5px solid var(--line);
}
.mcq-option:not(.right):not(.wrong):hover {
  background: var(--accent-soft);
  border-color: var(--accent);
}

/* --- 3. Активная вкладка входа выглядела МЕНЕЕ активной -------------------
   Базовая .tab имеет рамку --line (#6E7A72), а .tab.active затянута в общую
   группу с рамкой --line-soft (#D8DBD4) — то есть у выбранной вкладки рамка
   СВЕТЛЕЕ, чем у невыбранной, а больше они ничем не отличаются. Это первый
   экран, который видит платящий репетитор. */
.tab.active {
  background: var(--accent);
  color: var(--on-accent);
  border-color: var(--accent);
  font-weight: 700;
}
.tab.active:hover { background: var(--accent-deep); border-color: var(--accent-deep); }

/* --- 4. Наведение и текущий пункт меню различались на 4 единицы -----------
   :hover #ECEEE8 против .active #E2EAE1 — на глаз неотличимо, и непонятно,
   где ты находишься. Текущему пункту добавляем то, чего у наведения нет
   в принципе: подчёркивание акцентом и вес. */
.topbar .nav-btn.active {
  font-weight: 700;
  box-shadow: inset 0 -2px 0 var(--accent);
}
@media (max-width: 820px) {
  /* В нижней панели подчёркивание снизу пришлось бы на самый край экрана —
     там его не видно. Метим сверху, над иконкой. */
  .topbar .nav-btn.active { box-shadow: inset 0 2px 0 var(--accent); }
}

/* --- 5. Пастель на наградах ------------------------------------------------
   Глина, лаванда и небо — самые тёплые и самые яркие пятна на шалфейной
   странице, причём стоят они на главной. Заказчик тёплую гамму отверг прямо.
   Убираем не различие уровней, а способ его выразить: три ступени того же
   шалфея читаются как лестница (бронза → серебро → золото) лучше, чем три
   несравнимых оттенка — у розового и голубого нет порядка, у светлоты есть. */
.sticker.got-clay,                                  /* бронза */
.sticker.got-mint {
  background: var(--accent-soft);
  color: var(--ink);
  border: 1px solid transparent;
}
.sticker.got-sky {                                  /* серебро */
  background: color-mix(in oklab, var(--accent) 26%, var(--surface));
  color: var(--ink);
  border: 1px solid color-mix(in oklab, var(--accent) 42%, var(--surface));
}
.sticker.got-lavender {                             /* золото */
  background: var(--accent);
  color: var(--on-accent);
  border: 1px solid var(--accent);
}
.sticker.got-lavender .ic { color: var(--on-accent); }
/* Рекорд блица приходит из JS без класса уровня и потому выглядел закрытым,
   хотя это достижение. Даём ему вид полученной награды нижней ступени. */
.sticker:not([class*="got-"]):not(.locked) {
  background: var(--accent-soft);
  color: var(--ink);
  border: 1px solid transparent;
}

/* --- 6. На ноутбуке кнопка «Дальше» упиралась в край окна -----------------
   Замер на 1280×720: низ кнопки ровно 720 — ученик отвечает, а следующего
   шага не видно, пока не проскроллит. Съедают высоту шапка страницы (61),
   заголовок упражнения 36px и счётчик «1/8» отдельной строкой с полями
   10/18.

   Условие по ВЫСОТЕ, а не по ширине: проблема не в узости окна, а в низком
   экране ноутбука. На высоком мониторе ритм заголовков (36px, как на
   остальных экранах) остаётся нетронутым. */
@media (min-width: 821px) and (max-height: 820px) {
  .ex-head { margin-bottom: var(--space-8); }
  .ex-head h2 { font-size: var(--text-subheading); }   /* 36 → 24 */
  .test-counter { margin: 0 0 var(--space-8); }
  /* Карточка вопроса и так по центру колонки — снимаем лишний верхний
     отступ, он дублирует зазор от шапки. */
  #screen-exercise .narrow > .card:first-of-type { margin-top: 0; }
  /* После шапки не хватало ещё 17px, и браузер доскроливал сам. Забираем их
     у полей карточки: 40px сверху и снизу на низком экране — это роскошь,
     из-за которой не видно следующего шага. */
  #screen-exercise .card { padding-top: var(--space-24); padding-bottom: var(--space-24); }
  #screen-exercise .quiz-word { margin-bottom: var(--space-16); }
}

/* --- 7. Лестница заголовков на главной была перевёрнута --------------------
   Замер: приветствие «Привет, Ваня!» 28px/800, заголовки секций 28px/700,
   заголовок ВНУТРИ карточки 38px/800. При этом титулы остальных экранов
   («Мой словарь», «Тренировки», «Награды») — 36px/800.

   Три следствия: титул главной оказался мельче титула любого другого
   экрана; титул и подзаголовки секций различались только весом 800 против
   700, а шаг в 100 единиц веса уровнем не читается; и самым крупным на
   странице был текст внутри карточки — то есть деталь весила больше, чем
   всё, что её содержит.

   Ставим одну лестницу на весь продукт: 36 экран / 28 карточка /
   24 секция / 18 подпись. Каждая ступень — заметный шаг, а не оттенок. */
.dash-head h1, .dash-head h2 { font-size: var(--text-heading); }        /* 28 → 36 */
.today-title                 { font-size: var(--text-heading-sm); }     /* 38 → 28 */
#screen-dashboard .section-head h2,
#screen-dashboard .section-head h3 { font-size: var(--text-subheading); } /* 28 → 24 */

@media (max-width: 600px) {
  /* Токены сами роняют --text-heading до 28px на ≤900px, так что титул
     экрана уже уменьшен. Здесь опускаем на ступень только то, что ниже
     по лестнице, — иначе на 375px три уровня схлопнутся в один.
     20px литералом: между --text-subheading (24) и --text-body (18)
     ступени в шкале нет, а тут она нужна. */
  .today-title { font-size: var(--text-subheading); }
  #screen-dashboard .section-head h2,
  #screen-dashboard .section-head h3 { font-size: 20px; }
}

/* ==========================================================================
   ФОТОГРАФИЯ В ПЛИТКЕ СЛОВА
   --------------------------------------------------------------------------
   .word-art задумана под эмодзи: flex-контейнер с крупным font-size. Для
   110 конкретных слов внутрь теперь приходит <img> — растягиваем его на всю
   плитку. Радиус наследуем: у плитки он свой на каждый размер (24/20/13/10),
   и жёсткое значение здесь рассинхронизировалось бы при первой же правке.
   ========================================================================== */
/* Правило на самой картинке, а не на «.word-art img»: плитку рисуют четыре
   разных места, и одно из них — .hw-word-art в панели репетитора. Пока
   размер зависел от класса родителя, там картинка разворачивалась
   в натуральные 480×480 и накрывала половину экрана. */
.word-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  display: block;
}
/* Плитка с фотографией не должна светить подложкой из-под краёв: у эмодзи
   подложка была фоном, у фотографии она видна только как ободок.
   overflow тут обязателен: без него картинка вылезает за скруглённый угол. */
.word-art:has(.word-photo),
.hw-word-art:has(.word-photo) { overflow: hidden; padding: 0; }

/* --- Фотография на карточке и в вопросе крупнее ---------------------------
   92px хватало эмодзи — он и рисуется как знак. Фотография на 92px
   нечитаема: яблоки превращаются в красно-зелёное пятно, а именно связь
   «вижу предмет → вспоминаю слово» и есть смысл картинки в карточке.
   Растим только там, где плитка — главное на экране, и только если внутри
   действительно фото: у эмодзи прежний размер остаётся верным. */
.flashcard .word-art-big:has(.word-photo),
#flash-art:has(.word-photo) {
  width: 168px; height: 168px;
  border-radius: 28px;
  margin-bottom: var(--space-16);
}
.word-quiz-card .word-art-mid:has(.word-photo) {
  width: 132px; height: 132px;
  border-radius: 24px;
}
@media (max-width: 480px) {
  .flashcard .word-art-big:has(.word-photo),
  #flash-art:has(.word-photo) { width: 132px; height: 132px; border-radius: 24px; }
  .word-quiz-card .word-art-mid:has(.word-photo) { width: 108px; height: 108px; }
}
/* Стороны карточки позиционированы абсолютно — этого требует переворот в
   3D, и из-за этого высоту карточки они не поднимают. Пока внутри был
   эмодзи, фиксированных 320px хватало; фотография на 168px добавляет
   ~76px, и подсказка с кнопками вылезали за белое поле наружу. */
.flashcard:has(.word-photo), .flashcard-inner:has(.word-photo) { min-height: 404px; }
@media (max-width: 600px) {
  .flashcard:has(.word-photo), .flashcard-inner:has(.word-photo) { min-height: 352px; }
}

/* ==========================================================================
   ПОЛЕ ПАРОЛЯ С ГЛАЗКОМ
   --------------------------------------------------------------------------
   Кнопка лежит ВНУТРИ поля справа. Полю добавляем отступ ровно под неё,
   иначе длинный пароль уезжает под кнопку и последние символы не видны —
   то есть глазок мешал бы ровно тому, ради чего его ставили.
   ========================================================================== */
.pass-field { position: relative; display: block; }
.pass-field > input { width: 100%; padding-right: 46px; }
.pass-eye {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;                      /* цель нажатия под палец — конвенция 44px */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  border-radius: 50%;
  color: var(--ink-soft);            /* 5,5:1 — это контрол, а не декор */
  cursor: pointer;
}
.pass-eye:hover { color: var(--ink); background: var(--surface-alt); }
.pass-eye:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ==========================================================================
   МОДАЛКА НА СТРАНИЦЕ УЧЕНИКА
   --------------------------------------------------------------------------
   .modal / .modal-card / .recovery-code описаны в css/tutor.css, а он
   подключён только к панели репетитора. Ученику нужен тот же набор —
   повторяем его здесь одним блоком, чтобы не тащить весь tutor.css.
   Значения те же, расхождение между двумя страницами тут было бы багом.
   ========================================================================== */
#logout-modal {
  position: fixed;
  inset: 0;
  /* Затемнение чернилами темы, а не чёрным: на тёплой бумаге чистый
     чёрный даёт грязный серый ореол по краю карточки. */
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  display: grid;
  place-items: center;
  padding: var(--space-20);
  z-index: 60;
}
#logout-modal.hidden { display: none; }
#logout-modal .modal-card {
  background: var(--surface);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-lift);
  padding: var(--space-24);
  max-width: 420px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;               /* на 375px в альбомной ориентации не влезает */
}
#logout-modal h3 {
  font-weight: 800;
  font-size: var(--text-subheading);
  margin-bottom: var(--space-8);
}
#logout-modal .recovery-code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--text-heading-sm);
  font-weight: 800;
  letter-spacing: .08em;
  text-align: center;
  background: var(--accent-soft);
  color: var(--accent);
  border: 2px dashed var(--accent-line);
  border-radius: var(--radius-card);
  padding: var(--space-16) var(--space-12);
  margin: var(--space-8) 0 var(--space-12);
  user-select: all;               /* тап по коду выделяет его целиком */
}
/* Галочка и подпись — одна цель нажатия: попасть пальцем в чекбокс 13px
   на телефоне нельзя, а промах здесь стоит потерянного аккаунта. */
.ack-row {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  margin: var(--space-16) 0;
  padding: var(--space-8);
  min-height: 44px;
  cursor: pointer;
  border-radius: var(--radius-input);
}
.ack-row:hover { background: var(--surface-alt); }
.ack-row input { width: 20px; height: 20px; flex: none; accent-color: var(--accent); }
/* Отключённая кнопка выхода должна ЧИТАТЬСЯ отключённой: пока код не
   отмечен как записанный, выходить нельзя. */
#logout-go:disabled { opacity: .45; cursor: not-allowed; }
.link-btn.danger { color: var(--red); font-weight: 700; }

/* ==========================================================================
   ПАПКИ СЛОВАРЯ
   --------------------------------------------------------------------------
   Свой ряд под фильтрами «новые / учу / выучено». Ряд отдельный, потому что
   это другой вопрос к словарю: те фильтры про то, насколько слово выучено,
   и их ставит система; папки заводит сам ученик под свою задачу.
   ========================================================================== */
.dict-folders {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  margin-top: var(--space-12);
  padding-top: var(--space-12);
  border-top: var(--border-w) solid var(--line-soft);
}
.dict-folders.hidden { display: none; }
/* Ряд стоит всегда, даже без папок: иначе завести первую можно только
   через безымянный значок в строке слова, а способ, который невозможно
   найти, равносилен отсутствующему. Пока папок нет — здесь подсказка,
   зачем они, и кнопка «+ Папка». */
.dict-folders .folders-hint {
  color: var(--ink-soft);
  font-size: var(--text-body-sm);
  align-self: center;
  margin-right: auto;               /* кнопка «+ Папка» уходит вправо */
}
.chip-add {
  border-style: dashed;             /* «здесь ещё ничего нет, но можно завести» */
  font-weight: 700;
}
.folder-del {
  align-self: center;
  color: var(--red);
  font-size: var(--text-body-sm);
}
@media (max-width: 600px) {
  /* На 375px подсказка занимает три строки и отжимает кнопку за экран */
  .dict-folders .folders-hint { display: none; }
}
.dict-folder b {
  margin-left: 4px;
  font-weight: 700;
  opacity: .7;                      /* счётчик — подпись к имени, не имя */
}

/* Кнопка «в папку» в строке слова. По умолчанию приглушена: в списке из
   сорока слов сорок акцентных значков превращаются в шум. Слово, уже
   разложенное по папкам, метится акцентом — это и есть полезный сигнал. */
.d-fold {
  flex: none;
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  border-radius: 50%;
  color: var(--ink-soft);
  cursor: pointer;
}
.d-fold:hover { color: var(--ink); background: var(--surface-alt); }
.d-fold.has { color: var(--accent); }
.d-fold:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Окно выбора папки: те же стили, что у окна выхода. */
#folder-modal {
  position: fixed;
  inset: 0;
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  display: grid;
  place-items: center;
  padding: var(--space-20);
  z-index: 60;
}
#folder-modal.hidden { display: none; }
#folder-modal .modal-card {
  background: var(--surface);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-lift);
  padding: var(--space-24);
  max-width: 420px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
}
#folder-modal h3 {
  font-weight: 800;
  font-size: var(--text-subheading);
  margin-bottom: var(--space-12);
}
/* Список папок прокручивается сам: при двадцати папках окно иначе
   вырастает выше экрана и кнопка «Готово» уезжает за край. */
.folder-pick { max-height: 40vh; overflow-y: auto; margin-bottom: var(--space-12); }
.folder-pick .ack-row { margin: 0; }

/* ==========================================================================
   ГЛАВНАЯ КНОПКА — НА ЦВЕТЕ ЗАКАЗЧИКА
   --------------------------------------------------------------------------
   #65D97A светлый: белым по нему 1,79:1, читать нельзя. Тёмными чернилами
   по нему — 7,7:1 днём и 9,0:1 ночью. Поэтому кнопка заливается им, а
   текст на ней тёмный. Это не компромисс: светлая заливка с тёмной
   подписью — нормальная и часто более живая пара, чем тёмная с белой,
   и именно она даёт тот свежий зелёный, который был нужен.

   Наведение — темнее заливки, а не светлее: светлее уже некуда, а
   направление изменения должно быть одно на весь интерфейс.
   ========================================================================== */
.btn-primary {
  background: var(--accent-fill);
  color: var(--on-accent-fill);
  /* Обводка обязательна, и вот почему. Подпись на кнопке читается
     прекрасно — 7,7:1. А вот сама заливка #65D97A на бумаге #F6F5F0
     даёт 1,64:1 при требуемых 3,0: границу кнопки видно плохо, и для
     человека со слабым контрастным зрением главная кнопка сливается
     с фоном — он видит текст, но не видит, что это кнопка.
     --accent-line даёт 3,33:1 и требование закрывает.

     Не border, а внутренняя тень: border добавил бы кнопке два
     пикселя ширины и сдвинул всю уже собранную вёрстку. Общая тень
     перечислена следом — иначе это правило её затрёт, оно ниже. */
  box-shadow: inset 0 0 0 1px var(--accent-line), var(--shadow);
}
.btn-primary:hover {
  background: color-mix(in oklab, var(--accent-fill) 84%, var(--ink));
  color: var(--on-accent-fill);
}
.btn-primary:active {
  background: color-mix(in oklab, var(--accent-fill) 74%, var(--ink));
}
/* Отмеченный верный ответ — та же заливка: «правильно» и «главное
   действие» в интерфейсе говорят одним голосом. */
.mcq-option.right {
  background: var(--accent-fill);
  color: var(--on-accent-fill);
  border-color: var(--accent-fill);
}
.mcq-option.right .ans-mark { color: var(--on-accent-fill); }
/* Полученная награда высшей ступени — тоже заливка, а не тёмный акцент */
.sticker.got-lavender {
  background: var(--accent-fill);
  color: var(--on-accent-fill);
  border-color: var(--accent-fill);
}
.sticker.got-lavender .ic { color: var(--on-accent-fill); }

/* ==========================================================================
   «ЧТО ТРЕНИРУЕМ» — ВЫБОР ПАПОК НАД СПИСКОМ УПРАЖНЕНИЙ
   --------------------------------------------------------------------------
   Стоит выше упражнений, потому что отвечает на более ранний вопрос: не
   «как тренировать», а «что». Появляется, только когда папки заведены —
   ряд с единственной кнопкой «весь словарь» это подпись к отсутствию
   выбора.
   ========================================================================== */
.train-scope {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
  margin-bottom: var(--space-20);
  padding-bottom: var(--space-16);
  border-bottom: var(--border-w) solid var(--line-soft);
}
.train-scope.hidden { display: none; }
.scope-label {
  color: var(--ink-soft);
  font-size: var(--text-body-sm);
  margin-right: var(--space-4);
}
.scope-chip b { margin-left: 4px; font-weight: 700; opacity: .7; }
@media (max-width: 600px) {
  /* На 375px подпись съедает строку, а смысл её и так виден по месту */
  .scope-label { display: none; }
  .train-scope { margin-bottom: var(--space-16); }
}

/* ==========================================================================
   ВИДЕОУРОК У УЧЕНИКА
   --------------------------------------------------------------------------
   Стоит выше карточки «прямо сейчас»: когда репетитор позвал, живой человек
   в комнате важнее любой тренировки. Когда урок не идёт — блок приглушён
   и не спорит с главным действием дня.
   ========================================================================== */
.lesson-card {
  display: flex;
  align-items: center;
  gap: var(--space-16);
  margin-bottom: var(--space-16);
}
.lesson-card .cat-avatar { width: 52px; flex: none; }
.lesson-text { flex: 1 1 auto; min-width: 0; }
.lesson-kicker {
  font-weight: 700;
  color: var(--ink-soft);
  margin: 0 0 2px;
  font-size: var(--text-body-sm);
}
.lesson-note { margin: 0; color: var(--ink-soft); font-size: var(--text-body-sm); }
.lesson-go { flex: none; text-decoration: none; }

/* Урок идёт — блок выходит на первый план. Разница не только в цвете:
   при дальтонизме подложка и обводка читаются, а оттенок нет. */
#lesson-box.lesson-live .lesson-card {
  background: var(--accent-soft);
  border: var(--border-w) solid var(--accent-line);
}
#lesson-box.lesson-live .lesson-kicker { color: var(--accent); }

@media (max-width: 600px) {
  /* На 375px кнопка рядом с текстом оставляет тексту 90px — уводим её вниз
     во всю ширину, это и цель нажатия крупнее. */
  .lesson-card { flex-wrap: wrap; }
  .lesson-go { width: 100%; text-align: center; }
}

/* ==========================================================================
   «СОБЕРИ ВЫРАЖЕНИЕ»
   --------------------------------------------------------------------------
   Строка собранного отделена от плиток: без неё непонятно, что уже нажато,
   а порядок слов — ровно то, что тут и проверяется.
   ========================================================================== */
.built-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-8);
  min-height: 48px;              /* не прыгает, когда появляется первое слово */
  align-items: center;
  padding: var(--space-8);
  margin-bottom: var(--space-16);
  border-bottom: 2px dashed var(--line-control);
}
.scr-tile.built {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: var(--accent-line);
  cursor: default;
}
.scr-tile.used { opacity: .35; cursor: not-allowed; }

/* ==========================================================================
   ВЫБОР ВЫРАЖЕНИЙ
   --------------------------------------------------------------------------
   Список длинный и добавляют из него пачкой, поэтому строка сделана целиком
   кнопкой: попасть пальцем в плюсик 16px на телефоне нельзя, а промах здесь
   стоит лишнего слова в словаре.
   ========================================================================== */
.phrase-add-row { margin-top: var(--space-12); }
.modal-wide .modal-card, #phrases-modal .modal-card { max-width: 560px; }
#phrases-modal {
  position: fixed; inset: 0;
  background: color-mix(in srgb, var(--ink) 55%, transparent);
  display: grid; place-items: center;
  padding: var(--space-20); z-index: 60;
}
#phrases-modal.hidden { display: none; }
#phrases-modal .modal-card {
  background: var(--surface);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-lift);
  padding: var(--space-24);
  width: 100%;
  max-height: 88vh;
  display: flex;
  flex-direction: column;
}
#phrases-modal h3 { font-weight: 800; font-size: var(--text-subheading); margin-bottom: var(--space-8); }
.phrase-list {
  flex: 1 1 auto;
  overflow-y: auto;
  margin: var(--space-12) 0;
  min-height: 120px;
}
.phrase-row {
  display: flex;
  align-items: center;
  gap: var(--space-12);
  width: 100%;
  min-height: 48px;
  padding: var(--space-8) var(--space-12);
  margin-bottom: var(--space-4);
  background: var(--surface-alt);
  border: var(--border-w) solid transparent;
  border-radius: var(--radius-input);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.phrase-row:hover { border-color: var(--accent-line); }
.phrase-row.added { background: var(--accent-soft); border-color: var(--accent-line); }
.phrase-en { font-weight: 700; color: var(--ink); flex: 0 0 40%; }
.phrase-ru { color: var(--ink-soft); flex: 1 1 auto; min-width: 0; }
.phrase-mark {
  flex: none; width: 24px; text-align: center;
  color: var(--ink-soft); font-weight: 700;
}
.phrase-row.added .phrase-mark { color: var(--accent); }
@media (max-width: 600px) {
  /* На 375px в строку не помещаются оба языка — перевод уходит вниз */
  .phrase-row { flex-wrap: wrap; }
  .phrase-en { flex: 1 1 auto; }
  .phrase-ru { flex: 1 0 100%; font-size: var(--text-body-sm); }
}

/* ==========================================================================
   РАЗБОР КРИТИКА: то, что видно глазами
   ========================================================================== */

/* --- 1. Голубая подсветка тапа на телефоне -------------------------------
   При каждом касании по шалфейному продукту вспыхивал андроидный циан
   (rgba(51,181,229,.4) — значение по умолчанию). Своя обратная связь уже
   есть: :active сжимает элемент на 3%. Заодно снимаем выделение текста
   на кнопках — долгое нажатие выделяло подпись вместо нажатия. */
* { -webkit-tap-highlight-color: transparent; }
button, .chip, .ex-card, .mcq-option, .scr-tile, .phrase-row, .dict-row {
  -webkit-user-select: none;
  user-select: none;
}
/* Слово и перевод копировать НУЖНО — ученик уносит их в тетрадь */
.d-en, .d-ru, .quiz-word, .recovery-code { user-select: text; }

/* --- 2. Шапка на телефоне просвечивала -----------------------------------
   Фон был полупрозрачным в расчёте на размытие, а размытие на ≤600px
   отключено (backdrop-filter создаёт containing block и ломает нижнее
   меню). В итоге сквозь «Савелий» читался контент страницы. */
@media (max-width: 600px) {
  .topbar { background: var(--paper); }
}

/* --- 3. Иконка внутри кнопки уезжала на свою строку ----------------------
   `.ic { display: block }` верно для контейнеров, где иконка одна, и
   ломает кнопку с подписью: у «Голосовой чат» микрофон вставал отдельной
   строкой сверху. Правило `.btn .ic { vertical-align }` при этом было
   мёртвым — к блочным элементам vertical-align не применяется. */
.btn > .ic,
.msg-say > .ic {
  display: inline-block;
  vertical-align: -4px;
}

/* --- 4. Заглушка вместо фотографии -----------------------------------------
   Раньше в плитке без фото стоял эмодзи, и это было не «немного», а почти
   везде: фото есть у 110 слов из 2500. Список словаря из четырнадцати строк
   давал четырнадцать разноцветных системных значков — самые яркие пиксели
   экрана, а ночью единственные цветные. Теперь монолинейная иконка
   категории: один цвет, одна толщина, наследует тон родителя. */
.word-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: var(--ink-soft);
}
.word-icon .ic { width: 55%; height: 55%; }
/* На крупной плитке (карточка, вопрос) иконка не должна выглядеть
   растянутым пятном — там она мельче относительно бокса. */
.word-art-big .word-icon .ic,
.word-art-mid .word-icon .ic { width: 44%; height: 44%; }

/* --- 5. Создание папки прямо в ряду --------------------------------------
   Вместо системного prompt(). Поле занимает место кнопки, поэтому ряд
   не прыгает, а видно, сколько текста влезает: «Неправильные глаголы»
   в системном окне не проверить. */
.folder-new-inline {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  flex-wrap: wrap;
}
.folder-new-inline input {
  flex: 1 1 180px;
  min-width: 160px;
  padding: 6px 12px;
  border-radius: var(--radius-btn);
  border: var(--border-w) solid var(--line-control);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--text-body-sm);
}

/* --- 6. Упражнения на большом экране --------------------------------------
   Замер критика на 1280×800: карточка 420×320 — шесть процентов площади,
   по 460px пустой бумаги с каждой стороны, слово-герой 36px. Мобильная
   версия при этом сделана хорошо, то есть десктоп проигрывал телефону.
   Ширину поднимаем только там, где на экране ОДИН объект: карточка слова
   и вопрос. Списки и хаб остаются узкими — строку длиннее 70 знаков
   читать тяжелее, а не легче. */
@media (min-width: 1024px) {
  #screen-exercise .narrow,
  #screen-trainer .narrow { max-width: 720px; }

  .flashcard { max-width: 680px; }
  .flashcard, .flashcard-inner { min-height: 380px; }
  .flashcard:has(.word-photo), .flashcard-inner:has(.word-photo) { min-height: 460px; }
  /* Слово — герой карточки, ему и размер. clamp, а не фикс: на 1024
     и на 1920 колонка одна и та же, но воздух вокруг разный. */
  .flashcard .quiz-word { font-size: clamp(38px, 4vw, 58px); }
  .word-quiz-card .quiz-word { font-size: clamp(34px, 3.4vw, 46px); }

  /* Вариант ответа шириной 660px с текстом 16px — это строка, в которой
     нечего читать. Крупнее и выше: попасть мышью тоже проще. */
  .mcq-option {
    min-height: 58px;
    font-size: var(--text-body);
    padding-top: var(--space-12);
    padding-bottom: var(--space-12);
  }
}

/* Подпись под полем: объясняет, зачем поле нужно и обязательно ли оно.
   У почты ученика это единственный способ сказать «можно не заполнять» —
   без подписи ребёнок либо пропускает поле, либо вписывает наугад. */
.field-hint {
  display: block;
  margin-top: var(--space-4);
  font-size: var(--text-body-sm);
  font-weight: 400;
  color: var(--ink-soft);
  line-height: var(--leading-snug);
}

/* ==========================================================================
   ЗАГОЛОВОК РАЗДЕЛА, ПОЛУЧИВШИЙ ФОКУС ПРОГРАММНО
   --------------------------------------------------------------------------
   При смене раздела фокус уезжает на заголовок — иначе незрячий ученик
   не понимает, что экран сменился (см. announceScreen в js/app.js).
   Рамку при этом рисовать не надо: её ставят человеку, который ведёт
   фокус клавиатурой, а тут его перенесла программа.

   Одного `outline: none` НЕ ХВАТАЕТ, и это стоило мне зелёной рамки
   вокруг заголовка на боевом. Общее правило фокуса в savely.css ловит
   всё, у чего есть [tabindex] — а заголовку мы его как раз проставляем, —
   и рисует кольцо через box-shadow, а не outline. Гасим оба.

   И да, Chrome считает :focus-visible сработавшим даже когда фокус
   перенесён программой, если элемент сам по себе не фокусируемый.
   Полагаться на «браузер сам разберётся» тут нельзя.
   ========================================================================== */
h1[tabindex="-1"]:focus,
h2[tabindex="-1"]:focus,
h1[tabindex="-1"]:focus-visible,
h2[tabindex="-1"]:focus-visible { outline: none; box-shadow: none; }

/* h1 обёрнут вокруг кнопки «Савелий» в шапке — заголовок нужен программе
   чтения, а выглядеть должно ровно как раньше: весь вид рисует .brand
   внутри, сам h1 не приносит ни отступов, ни своего кегля. */
/* display:contents тут был бы соблазнителен — вёрстка не меняется вовсе.
   Но именно у заголовков он исторически выбрасывал элемент из дерева
   доступности (Chrome чинил это дважды, Safari и Firefox — позже), а
   ради дерева доступности h1 сюда и добавлен. Поэтому обычный flex:
   он занимает ровно место кнопки, которая и была флекс-элементом. */
.brand-h1 { display: flex; align-items: center; margin: 0;
  font-size: inherit; font-weight: inherit; line-height: inherit; }

/* Согласие на обработку данных при регистрации.
   Отличается от .ack-row при выходе двумя вещами: текст здесь длинный
   (две-три строки), поэтому галочка равняется по ВЕРХУ, а не по центру —
   иначе она уезжает к середине абзаца и перестаёт читаться как пара
   к первой строке. И кегль мельче: это юридическая приписка, а не
   действие, ради которого пришли. */
/* .auth-card label в style.css ставит display:block — а нам нужен flex,
   иначе галочка и текст просто текут строкой, а gap и выравнивание
   не работают вовсе. Селектор поэтому длиннее, чем хотелось бы: он
   должен перебить правило для всех label внутри карточки входа. */
.auth-card label.consent-row,
.consent-row {
  display: flex;
  align-items: flex-start;
  font-size: var(--text-caption);
  color: var(--ink-soft);
  line-height: 1.45;
  margin: var(--space-12) 0 var(--space-16);
}
.consent-row input { margin-top: 2px; }
/* Вес сбрасываем: .auth-card label ставит 800 для подписей полей
   («Имя», «Пароль»), и согласие наследовало их жирность — читалось
   как заголовок, хотя это приписка мелким шрифтом. */
.consent-row span { font-weight: 500; }
.consent-row a { color: var(--accent); }

/* ==========================================================================
   КРОССВОРД НА ТЕЛЕФОНЕ
   --------------------------------------------------------------------------
   Сетка тянулась на всю доступную ширину и делила её на число столбцов.
   На кроссворде в десять клеток и экране 360px это ~32px на клетку —
   меньше минимального размера, в который человек попадает пальцем (44px),
   да ещё и с трёхпиксельным зазором между ними. Промахи по соседней
   клетке читаются как «не набирается».

   Поэтому клетке задан минимум, а сетке разрешено уезжать вбок
   с прокруткой: лучше прокрутить кроссворд, чем не попасть по клетке.
   ========================================================================== */
@media (max-width: 560px) {
  .cw-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; padding-bottom: 4px; }
  .cw-grid {
    grid-auto-columns: 44px;   /* палец ребёнка: 40 было впритык, с видео видно промахи */
    grid-auto-flow: row;
    width: max-content;
    max-width: none;
    margin-inline: 0;
  }
  .cw-cell, .cw-empty { width: 44px; }
  .cw-cell { font-size: var(--text-body, 19px); }
  /* Строка текущего слова прилипает сверху: пока печатаешь в клетку,
     видно, ЧТО ты вообще отгадываешь, — на телефоне список подсказок
     в этот момент за экраном. */
  .cw-current { position: sticky; top: 0; background: var(--paper); z-index: 3; padding: 6px 0; }
}

/* Объяснение под разобранным вопросом. Отступ сверху больше, чем снизу:
   оно относится к вариантам над ним, а не к кнопке «Дальше» под ним. */
.quiz-why {
  margin: var(--space-16) auto 0;
  max-width: 34rem;
  text-align: center;
  line-height: 1.5;
}

/* Личный код на экране результата теста. Крупно и отдельной карточкой:
   это единственный ключ от аккаунта, и увидеть его ученик должен один
   раз, но так, чтобы запомнить или переписать. */
.code-card {
  margin: var(--space-20) auto;
  max-width: 26rem;
  border: 2px solid var(--accent-line);
  background: var(--accent-soft);
  text-align: center;
}
.code-card-title { font-weight: 700; margin-bottom: var(--space-8); }
.code-card .code-value {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--text-heading-sm);
  letter-spacing: 0.06em;
  margin: var(--space-8) 0;
  user-select: all;
}
.code-card .muted-small { margin-bottom: var(--space-12); }

/* Подсказка «здесь нет озвучки» над разделом «На слух». Карточкой,
   а не серой строкой: это не сноска, а единственный выход из тупика —
   в ней личный код, по которому ученик войдёт в нормальном браузере. */
.audio-help {
  margin-bottom: var(--space-16);
  border: 2px solid var(--accent-line);
  background: var(--accent-soft);
  text-align: left;
}
.audio-help p { margin: 0 0 var(--space-8); }
.audio-help p:last-child { margin-bottom: 0; }
.audio-help-code {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  flex-wrap: wrap;
}
.audio-help .code-value {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--text-subheading);
  letter-spacing: 0.04em;
  user-select: all;
}

/* Разбор своего текста в «Своих предложениях»: список «было → стало».
   Слева, а не по центру, — это список правок, его читают построчно,
   и зачёркнутое с исправленным должны стоять в одной колонке. */
.gc-list {
  margin: var(--space-12) auto 0;
  max-width: 34rem;
  text-align: left;
  padding-left: var(--space-20);
  line-height: var(--leading-normal);
  font-size: var(--text-body-sm);
  color: var(--ink);
}
.gc-list li { margin-bottom: var(--space-8); }
.gc-list s { color: var(--ink-soft); }
.gc-list b { color: var(--accent); }

/* «Эти слова ещё не твои» на экране результата упражнения.
   Отделено карточкой: это предложение, а не часть отчёта, и его легко
   пропустить, если оно сольётся со строкой «верно 5 из 8». */
.missed-card {
  margin: var(--space-20) auto 0;
  max-width: 34rem;
  text-align: left;
  border: 2px solid var(--accent-line);
  background: var(--accent-soft);
}
.missed-head { font-weight: 700; color: var(--ink); margin-bottom: var(--space-8); }
.missed-list {
  color: var(--ink-soft);
  font-size: var(--text-caption);
  line-height: 1.55;
  margin-bottom: var(--space-12);
}

/* ==========================================================================
   ГРАММАТИКА И СЛОВООБРАЗОВАНИЕ
   ========================================================================== */
/* Предложение с пропуском. Кегль меньше слова-героя: здесь важна
   не сама фраза, а то, что стоит в пропуске. */
.wf-sentence {
  display: block;
  font-family: var(--font-ui);
  font-size: var(--text-body-sm, 17px);
  font-weight: 500;
  line-height: 1.55;
  max-width: 30rem;
  margin-inline: auto;
}
/* Пропуск — видимое место, а не три подчёркивания: подчёркивания
   на телефоне сливаются с рамкой поля ввода под ними. */
.wf-gap {
  display: inline-block;
  min-width: 4.5em;
  border-bottom: 2px solid var(--accent-line);
  color: var(--accent);
  text-align: center;
  vertical-align: baseline;
}
/* Выбор темы грамматики. Список, а не сетка плиток: названия тем
   разной длины, и в сетке они рвались бы посреди слова. */
.gr-topics {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  max-width: 30rem;
  margin: var(--space-16) auto 0;
}
.gr-topic {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  width: 100%;
  min-height: 52px;
  padding: var(--space-12) var(--space-16);
  text-align: left;
  background: var(--surface);
  border: 2px solid var(--border);
  border-radius: var(--radius-card, 16px);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--text-body-sm, 17px);
  font-weight: 700;
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.gr-topic:hover { border-color: var(--accent-line); transform: translateY(-1px); }
.gr-topic:active { transform: translateY(0); }
.gr-topic-count {
  flex: none;
  color: var(--ink-soft);
  font-weight: 500;
  font-size: var(--text-caption);
}

/* Уровень слов для тренировок — над рядом папок, тем же тоном */
.train-level {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-8) var(--space-12);
  margin-bottom: var(--space-16);
}
.train-level .scope-label { display: inline; }
.train-level-select {
  font-family: inherit; font-size: var(--text-caption); font-weight: 700; color: var(--ink);
  padding: 8px 12px; border: 2px solid var(--border); border-radius: 10px; background: var(--surface); outline: none;
}
.train-level-select:focus { border-color: var(--accent); }
.train-level .muted-small { flex-basis: 100%; }

/* Подмешивать ли новые слова уровня — тем же рядом, что и выбор уровня */
.train-mix {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-8) var(--space-12);
  margin: calc(-1 * var(--space-8)) 0 var(--space-16);
}
.train-mix-label {
  display: inline-flex; align-items: center; gap: var(--space-8);
  min-height: 44px;                  /* цель нажатия: весь ряд, а не квадратик */
  font-size: var(--text-caption); font-weight: 500; color: var(--ink); cursor: pointer;
}
/* Область нажатия во весь ряд: на телефоне в чекбокс 16×16 не попасть.
   22px — сам квадрат тоже виден и попадается под палец ребёнка. */
.train-mix-label input { width: 22px; height: 22px; accent-color: var(--accent); cursor: pointer; }
.train-mix .muted-small { flex-basis: 100%; }

/* ===== Неправильные глаголы =====
   Два поля рядом на большом экране и столбиком на телефоне: подписи
   «Past Simple» и «Participle II» длинные, в один ряд на 320px они
   ломаются посреди слова. */
.irr-forms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: var(--space-12);
  margin-top: var(--space-16);
  text-align: left;
}
.irr-field { display: block; }
/* Подпись поля в две строки: сверху экзаменационное название, снизу
   школьное. Ученик знает своё — какое именно, зависит от учебника. */
.irr-label {
  display: block;
  font-size: var(--text-caption);
  font-weight: 700;
  color: var(--ink);
}
.irr-label i {
  display: block;
  font-style: normal;
  font-weight: 500;
  color: var(--ink-soft);
}
.irr-forms .type-input { margin-top: var(--space-4); }
.irr-forms .type-input.bad { border-color: var(--red); background: var(--surface-alt); }
/* Правильные три формы после ошибки — крупно и в одну строку: именно
   такой цепочкой их и заучивают вслух. */
.irr-answer {
  margin: var(--space-8) 0 0;
  font-size: var(--text-subheading);
  font-weight: 800;
  color: var(--ink);
}
/* Пояснение под названием группы. В .gr-topic оно не помещалось в строку,
   поэтому уводим на вторую и делаем тише основного текста. */
.irr-group-hint {
  display: block;
  margin-top: 2px;
  font-style: normal;
  font-weight: 500;
  font-size: var(--text-caption);
  color: var(--ink-soft);
  white-space: normal;
}
.gr-topic.irr-group { align-items: flex-start; }
/* Таблица прокручивается внутри себя: четыре колонки на 320px не влезают
   никак, а горизонтальная прокрутка всей страницы — это баг. */
.irr-table-wrap {
  overflow-x: auto;
  margin: var(--space-16) 0;
  border: 2px solid var(--border);
  border-radius: var(--radius-card, 16px);
  background: var(--surface);
}
.irr-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-caption);
}
.irr-table th,
.irr-table td {
  padding: var(--space-8) var(--space-12);
  text-align: left;
  white-space: nowrap;
  border-bottom: 1px solid var(--line-soft);
}
.irr-table th {
  font-weight: 700;
  color: var(--ink-soft);
  background: var(--surface-alt);
  position: sticky;
  top: 0;
}
.irr-table td { font-weight: 700; color: var(--ink); }
.irr-table .irr-ru { font-weight: 500; color: var(--ink-soft); white-space: normal; }
.irr-table tr:last-child td { border-bottom: 0; }

/* Подсказка в строке «Тренируем»: папки складываются */
.scope-note { flex-basis: 100%; font-size: var(--text-caption, 13px); color: var(--ink-soft); margin-top: 2px; }
