/* ============================================================
   prod.css — дельта прода поверх core.css.

   core.css = копия docs/mockups-v2/core.css, синхронизируется скриптом
   scripts/sync-core-css.sh (копия + вырезка демо-блоков мокапа) — руками
   его не правят, иначе следующая синхронизация затрёт правку. Всё, чего
   в мокапе нет или что в проде должно быть иначе, живёт здесь и
   подключается ПОСЛЕ core.css (layout.html), поэтому при равной
   специфичности выигрывает.
   ============================================================ */

/* ── шрифты локально, офлайн — та же конвенция, что KaTeX/htmx в
   internal/web/static (CLAUDE.md: JS-сборки нет, всё через go:embed).
   В мокапах @font-face нет вовсе: там шрифты подключены страницей ── */
@font-face { font-family: 'Inter'; font-weight: 400; font-style: normal; font-display: swap; src: url('/static/fonts/Inter-Regular.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-weight: 500; src: url('/static/fonts/Inter-Medium.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'Inter'; font-weight: 600; src: url('/static/fonts/Inter-SemiBold.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'Inter'; font-weight: 700; src: url('/static/fonts/Inter-Bold.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; font-weight: 400; src: url('/static/fonts/JetBrainsMono-Regular.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; font-weight: 500; src: url('/static/fonts/JetBrainsMono-Medium.woff2') format('woff2'); font-display: swap; }
@font-face { font-family: 'JetBrains Mono'; font-weight: 600; src: url('/static/fonts/JetBrainsMono-SemiBold.woff2') format('woff2'); font-display: swap; }

/* Пилот каталога и карточки задачи (17.09): новые роли поверх действующей
   палитры. core.css остаётся копией мокапа до отдельной смены источника. */
:root {
  --control-border: #B6A698;
  --focus-ring: #7D5030;
  --difficulty-1-text: #23763B;
  --difficulty-2-text: #806000;
  --difficulty-3-text: #A34500;
  --difficulty-4-text: #A52D29;
}
:root[data-theme="dark"] {
  --control-border: #746354;
  --focus-ring: #E6D4B4;
  --difficulty-1-text: #78D99A;
  --difficulty-2-text: #F7D74A;
  --difficulty-3-text: #FF9B61;
  --difficulty-4-text: #FF8E87;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --control-border: #746354;
    --focus-ring: #E6D4B4;
    --difficulty-1-text: #78D99A;
    --difficulty-2-text: #F7D74A;
    --difficulty-3-text: #FF9B61;
    --difficulty-4-text: #FF8E87;
  }
}

a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
input.input:focus-visible,
select.input:focus-visible,
textarea.input:focus-visible,
.answer-cells input:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

/* Яркие d1–d4 остаются маркерами. Текст уровня получает отдельный
   контрастный тон, чтобы не исчезать на белой и тёмной поверхностях. */
.lvl-indicator.lvl-1 .task-lvl, .badge.lvl-1 { color: var(--difficulty-1-text); }
.lvl-indicator.lvl-2 .task-lvl, .badge.lvl-2 { color: var(--difficulty-2-text); }
.lvl-indicator.lvl-3 .task-lvl, .badge.lvl-3 { color: var(--difficulty-3-text); }
.lvl-indicator.lvl-4 .task-lvl, .badge.lvl-4 { color: var(--difficulty-4-text); }

/* ── масштаба здесь больше нет: решение владельца 26.07 — прод показывает
   вёрстку так, будто включён зум 110%, но свойством zoom не пользуется.
   Множитель 1.1 применяется к px-значениям при синхронизации ядра
   (scripts/sync-core-css.sh), поэтому геометрия «зумленная», а сам зум
   отсутствует: на узких экранах он давал горизонтальное переполнение, а в
   части движков ведёт себя не как масштаб макета ── */

/* ── панель фильтров каталога и банка вариантов: поиск + мультиселекты в
   одну строку с переносом на узком экране. Компонент появился на вёрстке
   прода (в мокапах фильтры собраны иначе), поэтому живёт здесь ── */
.filter-selects { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* на десктопе строка держится ОДНОЙ (правка владельца 12.08): «скрыть
   решённые» уезжало на вторую строку, потому что у каждого поля стояла
   жёсткая min-width: 150px — сумма шести полей плюс галочки не влезала в
   1254px. Перенос строки flexbox решает по БАЗИСУ, а не по min-width,
   поэтому базис у полей — по содержимому (flex-basis: auto): подписи
   разной длины («все теги» короче «все источники») больше не выравнены
   насильно, и весь набор помещается в строку. Свободное место делят
   поиск и селекты, галочка и ссылка сброса не жмутся вовсе. Ещё 46px на
   строку дают шаг сетки 8px вместо 12 и поля селектов поуже — без них
   поиск сжимался до «поиск: id, тем». */
/* сама строка — во всю ширину карточки: как flex-элемент по содержимому она
   не добирала ~90px, и поиск жался, пока справа стояла пустота */
.filter-card > .filter-selects { flex: 1 1 100%; }
.filter-selects .search-field { flex: 3 1 150px; min-width: 108px; }
.filter-selects .search-field .input { min-width: 0; width: 100%; }
.filter-selects .multiselect { flex: 1 1 auto; width: auto; min-width: 0; }
.filter-selects .multiselect > .ms-field { padding-left: 11px; padding-right: 11px; gap: 7px; }
.filter-selects .check-row,
.filter-selects .filter-clear { flex: none; white-space: nowrap; }
.catalog-filters { border-color: var(--control-border); }
.catalog-results { scroll-margin-top: 88px; }
.catalog-results .task-full {
  scroll-margin-top: 88px;
  transition: border-color .12s ease;
}
.catalog-results .task-full:hover { background: var(--frame-bg); }
.catalog-results .task-full:focus-within { border-color: var(--focus-ring); }
.catalog-results .tf-head { gap: 8.8px; margin-bottom: 13.2px; }
.catalog-results .tf-cond { max-width: 75ch; font-size: 16.5px; line-height: 1.62; }
.catalog-results .tf-tags { margin-top: 13.2px; }
/* на 375px мультиселекты вставали столбиком по одному, и панель фильтров
   занимала полтора экрана до первой задачи: раскладываем их по двое в ряд,
   а расшифровку цветов уровня прячем — она объясняет точку в углу
   карточки и на узком экране только съедает высоту */
@media (max-width: 700px) {
  .filter-selects .multiselect { flex: 1 1 calc(50% - 4px); min-width: 0; }
  .filter-selects .search-field { flex: 1 1 100%; }
  .filter-card .lvl-legend { display: none; }
  /* панель мультиселекта на телефоне — во всю ширину строки фильтров, а не
     от своего поля вправо: у правой колонки она уезжала за край экрана и
     обрезала подписи (заметно стало на вложенных источниках). Поле
     перестаёт быть системой координат (position: static), ей становится
     сама строка — панель раскрывается под всем блоком фильтров. */
  .filter-selects { position: relative; }
  .filter-selects .multiselect { position: static; }
  .filter-selects .multiselect-panel { left: 0; right: 0; width: auto; max-width: none; }
}

/* ── третье состояние опции мультиселекта «исключить» (правка владельца
   01.08): клик выбирает, ещё клик — исключает. Штрих в квадратике рисует
   сам браузер (JS ставит indeterminate), здесь только цвет и зачёркнутая
   подпись; в мокапах состояния нет ── */
/* .ms-not — скрытый чекбокс «исключить»: hidden его и так прячет, правило
   нужно, чтобы класс не считался осиротевшим и не мог всплыть в потоке */
.ms-not { display: none; }
.ms-option { justify-content: flex-start; gap: 4.4px; }
.ms-select { display: flex; align-items: center; gap: 8.8px; min-height: 44px; min-width: 0; flex: 0 1 auto; cursor: pointer; }
.ms-label { min-width: 0; overflow-wrap: anywhere; }
.ms-exclude {
  flex: none; min-height: 30.8px; padding: 2.2px 5.5px; border: 1.1px solid var(--control-border);
  border-radius: 5.5px; color: var(--text-2); background: var(--surface); font: inherit; font-size: 12.1px;
  cursor: pointer;
}
.ms-exclude:hover, .ms-exclude[aria-pressed="true"] { border-color: var(--err); color: var(--err); }
.ms-option.is-excluded .ms-exclude { background: var(--err-soft); }
.ms-option.is-excluded { color: var(--err); }
.ms-option.is-excluded .ms-label { text-decoration: line-through; }
.ms-option.is-excluded input[type="checkbox"] { accent-color: var(--err); }
.ms-option.is-excluded:hover { background: color-mix(in srgb, var(--err) 12%, transparent); }

/* ── имя вошедшего в меню аккаунта: в мокапах панель только с пунктами
   («Аккаунт» / «Глазами ученика» / «Выйти»), но тогда логин не виден
   нигде — единственный способ понять, под кем ты вошёл, — этот строкой ── */
.avatar-menu .panel .menu-name {
  display: block; padding: 8px 12px 6px; color: var(--text-2);
  font-size: 12.5px; border-bottom: 1px solid var(--line); margin-bottom: 4px;
}
/* ── тариф строкой того же меню (правка владельца 19.08): план и остаток
   дней читаются с любого экрана, а не только из /account. Сама строка —
   ссылка на тарифы (правка владельца 21.08), поэтому подсветку наведения
   она берёт от общего правила пунктов панели; свои display и padding
   перебивают его по весу селектора ── */
.avatar-menu .panel .menu-plan {
  display: flex; align-items: baseline; justify-content: space-between; gap: 11px;
  padding: 6px 12px 8px; margin-bottom: 4px; border-bottom: 1px solid var(--line);
}
.menu-plan .mp-name { font-size: 13.5px; font-weight: 600; color: var(--accent); }
.menu-plan .mp-left {
  font-family: "JetBrains Mono", monospace; font-size: 12px; color: var(--text-2);
}
.menu-plan--off .mp-name { color: var(--err); }

/* ── переключатель слоёв карты 27 (охват ⇌ точность): в мокапах он живёт
   локально на каждом экране (.map-head + .segment), в проде карта —
   общий партиал numbermap на трёх экранах, поэтому ряд-обёртка здесь ── */
.map-switch-row { display: flex; justify-content: center; margin-bottom: 16px; }

/* ── гостевая шапка на узких экранах: «регистрация» + «войти» + тумблер темы
   не влезают в 375px (шапка мокапов гостю рисует только кнопку входа).
   Ссылку регистрации прячем — она есть на самой странице входа ── */
@media (max-width: 700px) {
  .site-header .reg-link { display: none; }
  .site-header .burger .panel { left: auto; right: 0; }
  /* и поджимаем саму шапку: логотип + раздел + бургер + действия в 375px
     влезают только с меньшими отступами (масштаб ×1.1 сделал их щедрее) */
  .site-header { padding-left: 11px; padding-right: 11px; gap: 8px; }
  /* круглые контролы шапки (колокольчик, тема, аватар) на 375px тоже
     поджимаем: в масштабе ×1.1 втроём они не влезали рядом с логотипом
     и подписью раздела */
  .site-header .header-actions { gap: 6px; }
  .site-header .bell-btn,
  .site-header .theme-toggle,
  .site-header .avatar { width: 34px; height: 34px; }
}
@media (max-width: 350px) {
  .site-header { padding-inline: 8px; gap: 6px; }
  .site-header .current-section { display: none; }
  .site-header .header-actions { gap: 4px; }
  .lp-hero .lp-mapcard { padding: 14px; }
  .lp-hero .lp-mapcard-head { flex-wrap: wrap; gap: 4px 12px; }
  .lp-hero .lp-mapcard .map27 { grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 3px; }
}

/* ============================================================
   Мост к неперевёрстанным экранам.

   При переходе на ядро v2 старый style.css был удалён, а экраны, которые
   ещё не переверстаны (аккаунт, дз, уроки, корзина, сдачи, вход, /gaps),
   продолжают пользоваться его классами — и остались без стилей. Здесь
   минимальные правила на токенах ядра: экран выглядит опрятно до своей
   переверстки, а не «голым html». Класс уезжает отсюда, когда его экран
   переверстают по мокапу.

   Тест TestTemplateClassesDescribedInCSS следит, чтобы новый класс
   разметки не появился без описания ни здесь, ни в ядре, ни локально.
   ============================================================ */

/* утилиты, которые используются и новыми экранами */
.quiet { color: var(--text-2); font-size: 14.3px; }
.spacer-h { flex: 1; }

/* строка-подпись и мета-информация */
.stat { color: var(--text-2); font-size: 14.3px; }
.meta { color: var(--text-2); font-size: 13.2px; }
.count { color: var(--text-2); font-size: 13.2px; }
.val { font-family: "JetBrains Mono", ui-monospace, monospace; }
.fail { color: var(--err); font-size: 14.3px; }

/* формы в строку и панели фильтров старых экранов */
.inline { display: inline-flex; align-items: center; gap: 8.8px; flex-wrap: wrap; }
.filters, .filter-card { display: flex; align-items: center; gap: 13.2px; flex-wrap: wrap; margin-bottom: 17.6px; }
/* ряд действий формы (урок, конспект, снимок): «Сохранить»/«Отмена» —
   раньше жил одинаковыми локальными копиями в шаблонах, правка ревью 22.08
   свела в одну */
.lt-actions { display: flex; gap: 8.8px; flex-wrap: wrap; align-items: center; }
.trainer-top-row { display: flex; align-items: center; gap: 13.2px; flex-wrap: wrap; }

/* таблицы старых экранов */
.tasks { width: 100%; border-collapse: collapse; font-size: 14.3px; }
.tasks th, .tasks td { text-align: left; padding: 6.6px 8.8px; border-bottom: 1.1px solid var(--line); }
.tasks th { color: var(--text-2); font-weight: 550; font-size: 13.2px; }

/* заголовок секции с кнопкой справа: «УСЛОВИЕ · Маркер» (правка владельца
   09.08 — тумблер уехал к условию, туда, где рисуют) */
.section-label.cond-label {
  display: flex; align-items: center; justify-content: space-between; gap: 13.2px;
  text-transform: none; letter-spacing: normal;
}
.section-label.cond-label .marker-toggle { text-transform: none; letter-spacing: normal; }
.section-label.cond-label .condition-title { text-transform: none; }

/* ── маркер поверх условия задачи: холст ровно по блоку условия, панель
   под рядом инструментов. Канонизировано сюда при ВТОРОМ использовании
   (правка владельца 09.08): те же тумблер и холст появились в сессии
   тренажёра, а копия стилей в двух локальных <style> разъезжается ── */
.marker-bar { display: flex; align-items: center; gap: 13.2px; flex-wrap: nowrap; margin-top: 11px; min-width: 0; overflow-x: auto; }
.marker-bar[hidden] { display: none; }
.draw-eraser-pop { position: fixed; z-index: 92; padding: 7px; background: var(--surface); border: 1px solid var(--line); border-radius: 8px; box-shadow: 0 12px 30px rgba(0,0,0,.18); }
.draw-eraser-pop[hidden] { display: none; }
.marker-bar .draw-group:first-child { position: relative; }
.draw-erase-mode { border: 0; background: none; color: var(--text); padding: 7px 9px; font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap; border-radius: 6px; }
.draw-erase-mode:hover { background: var(--frame-bg); }
.marker-bar .cw-tools.draw-toolbar { flex-wrap: nowrap; flex: none; min-width: max-content; }
.marker-bar .draw-group { flex: none; }
.marker-bar .quiet { display: none; }
.draw-width-label { display: flex; align-items: center; gap: 8px; color: var(--text-2); font-size: 12px; }
.draw-width-label input { width: 112px; accent-color: var(--accent); }
.draw-width-label output { min-width: 20px; color: var(--text); font-variant-numeric: tabular-nums; }
/* панель маркера — .draw-toolbar ядра (её собирает draw.js), рядом с
   тумблером «Маркер»: своей рамки и отступа снизу здесь не надо */
.cw-tools.draw-toolbar { margin-bottom: 0; padding: 6.6px 11px; gap: 13.2px; }
.cw-canvas-host { position: relative; }
/* в режиме урока холст висит поверх условия всегда (он — зеркало айпада),
   но перехватывает перо только при включённом маркере: иначе ссылки на
   приложения внутри условия становятся некликабельными */
.cw-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  touch-action: none; cursor: crosshair; pointer-events: none;
}
.cw-canvas.cw-draw { pointer-events: auto; }

/* условие задачи и конспект урока — читаемая колонка текста */
.condition, .lesson-notes { line-height: 1.6; }
.task-page-head { margin-bottom: 30.8px; }
.task-page .condition { max-width: 75ch; font-size: 16.5px; line-height: 1.62; }
.task-condition-section { margin-bottom: 35.2px; }
.task-jump {
  display: inline-flex; margin-top: 17.6px; padding: 8.8px 0; color: var(--accent);
  font-weight: 600; text-underline-offset: 3px;
}
.task-answer-anchor { scroll-margin-top: 88px; }
/* потолок ширины картинки условия (правка владельца 08.08): без него
   схема из 1-го задания растягивалась на весь монитор, а рядом такая же
   картинка 3-го смотрелась нормально — разница в исходном разрешении
   файла. 560px — колонка текста условия, схема ЕГЭ читается целиком.
   Конспект урока — та же мера: скриншоты туда кладут любого размера */
.condition img, .lesson-notes img, .tf-cond img { max-width: min(100%, 560px); height: auto; }
/* Листинги в markdown сохраняют отступы, но длинная строка прокручивается
   внутри блока, а не растягивает всю страницу на телефоне. */
.condition pre, .lesson-notes pre, .solution-block pre, .tf-cond pre {
  max-width: 100%; overflow-x: auto;
}
.lesson-notes { overflow-x: auto; }
@media (max-width: 700px) {
  .lesson-notes pre, .lesson-notes pre code { white-space: pre-wrap; overflow-wrap: anywhere; }
}

/* ── таблицы В КОНТЕНТЕ (условие задачи, решение, конспект урока): в vault
   их пишут markdown-таблицами (таблица истинности №2, соответствие №1),
   goldmark отдаёт голый <table> — без правил он выглядел рассыпанными
   столбиками цифр без сетки. В мокапах таблиц условия нет вовсе, поэтому
   компонент прод-дельта. Широкая таблица скроллится внутри себя ── */
.condition table, .lesson-notes table, .solution-block table, .tf-cond table {
  border-collapse: collapse; margin: 15.4px 0;
  font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 14.3px;
}
.condition th, .condition td,
.lesson-notes th, .lesson-notes td,
.solution-block th, .solution-block td,
.tf-cond th, .tf-cond td {
  border: 1.1px solid var(--line); padding: 6.6px 13.2px; text-align: center;
  /* минимальная ширина ячейки (аудит 16.08): у таблиц истинности часть
     ячеек пустая, столбцы схлопывались в ноль, и строка «0 1 0 0»
     читалась как «0100» */
  min-width: 26.4px;
}
.condition th, .lesson-notes th, .solution-block th, .tf-cond th {
  background: var(--frame-bg); color: var(--text); font-weight: 600;
}
/* пустая шапка (у таблицы истинности первый ряд без подписей) не должна
   выглядеть полосой-пустышкой во всю ширину */
.condition thead:empty, .tf-cond thead:empty { display: none; }

/* бейджи и чипы старых экранов */
.tag, .status-badge, .tutor-badge {
  display: inline-flex; align-items: center; gap: 4.4px;
  padding: 2.2px 8.8px; border-radius: 999px; font-size: 12.1px;
  background: var(--frame-bg); border: 1.1px solid var(--line); color: var(--text-2);
}

/* сдачи и ревью (эпик B, экран ещё не переверстан) */
.code-review { font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 13.2px; line-height: 1.5; }
.review-comment { border-left: 3.3px solid var(--accent); padding: 6.6px 11px; margin: 6.6px 0; background: var(--accent-soft); border-radius: 0 6.6px 6.6px 0; }
.submission-img { max-width: 100%; border: 1.1px solid var(--line); border-radius: 8.8px; }
.submit-solution { margin-top: 17.6px; }

/* карта прогресса: партиал переехал на .map27 ядра, но старые имена ещё
   встречаются в решателе и групповой тепловой карте */
.numbermap { display: grid; grid-template-columns: repeat(9, minmax(44px, 61.6px)); gap: 8.8px; justify-content: safe center; }
.numcell { border: 1.1px solid var(--line); border-radius: 6.6px; text-align: center; font-size: 13.2px; }
.numcell-n { font-family: "JetBrains Mono", ui-monospace, monospace; }

/* ── кнопка выбора файла: ядро задаёт рамку самому input[type=file], но
   внутренняя кнопка остаётся браузерной — на тёмной теме это белый
   прямоугольник посреди формы. В мокапах загрузки файлов нет вовсе ── */
.input[type="file"]::file-selector-button {
  font: inherit; font-size: 13.75px; font-weight: 550;
  color: var(--text); background: var(--surface);
  border: 1.1px solid var(--line); border-radius: 6.6px;
  padding: 4.4px 11px; margin-right: 11px; cursor: pointer;
}
.input[type="file"]::file-selector-button:hover { border-color: var(--accent); color: var(--accent); }

/* ── экраны входа (вход, регистрация, восстановление, приглашение):
   мокапа под них нет, поэтому компонент прода — узкая колонка с одной
   карточкой на словаре ядра (.card/.field/.input/.btn). До этого они
   рендерились голыми <input> и <button> браузера: на тёмной теме
   получались белые коробки посреди страницы ── */
/* ── поле пароля с кнопкой «показать»: набирать вслепую восьмизначный
   пароль на телефоне — главный источник опечаток при входе ── */
/* подпись раздела в мобильной шапке: длинное «расписание» выталкивало
   колокольчик и аватар за экран (390px) — подпись ужимается с многоточием,
   а не двигает соседей (поймано на пересборке /schedule 22.08) */
.current-section { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 0 1 auto; }

.pass-field { position: relative; }
.pass-field .input { padding-right: 88px; }
.pass-eye {
  position: absolute; top: 50%; right: 8.8px; transform: translateY(-50%);
  background: none; border: 0; padding: 4.4px 6.6px; cursor: pointer;
  font: inherit; font-size: 13.2px; color: var(--text-2);
}
.pass-eye:hover { color: var(--accent); }

.auth { max-width: 462px; }
.auth-card { display: flex; flex-direction: column; gap: 17.6px; }

/* Общая форма авторизации, включая восстановление пароля и соцвход. */
.authx {
  max-width: 1122px; margin: 0 auto; padding: 35.2px 35.2px 44px;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 528px);
  gap: 52.8px; align-items: center;
}
.authx.authx--single { grid-template-columns: minmax(0, 528px); justify-content: center; }
@media (max-width: 980px) {
  .authx { grid-template-columns: 1fr; gap: 30.8px; padding: 30.8px 17.6px 52.8px; }
  /* на телефоне сперва форма: пришедший на /login знает, зачем пришёл */
  .authx-panel { order: 2; }
  .authx-card { order: 1; }
}
@media (max-width: 700px) { .authx { padding-left: 17.6px; padding-right: 17.6px; } }
.authx-panel { min-width: 0; }
.authx-title { font-size: clamp(28.6px, 3.6vw, 44px); line-height: 1.06; letter-spacing: -.03em; font-weight: 700; max-width: 16ch; text-wrap: balance; }
/* хвост заголовка отдельной строкой: акцентная часть — вторая
   строка, а не случайный обрыв посреди фразы */
.authx-title .accent { display: block; color: var(--accent); }
.authx-lead { color: var(--text-2); font-size: 17.6px; line-height: 1.6; margin-top: 15.4px; max-width: 46ch; }

.authx-card {
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 19.8px;
  padding: 26.4px; display: flex; flex-direction: column; gap: 13.2px;
  box-shadow: 0 22px 44px -30.8px rgba(36, 28, 21, .35);
}
/* форма регистрации обязана помещаться в экран целиком (правка владельца
   22.08): с капчей и двумя согласиями кнопка «Зарегистрироваться» уезжала
   под сгиб. Вертикальный ритм ужат, без потери полей и согласий. */
.authx-card .auth-form { gap: 11px; }
.authx-card .check-row + .check-row { margin-top: -2.2px; }
:root[data-theme="dark"] .authx-card { box-shadow: 0 22px 44px -30.8px rgba(0, 0, 0, .6); }
@media (max-width: 560px) { .authx-card { padding: 22px; border-radius: 15.4px; } }
.authx-head h1 { font-size: 24.2px; font-weight: 700; letter-spacing: -.02em; line-height: 1.2; }
.authx-head .sub { color: var(--text-2); font-size: 13.75px; line-height: 1.45; margin-top: 4.4px; }
.auth-signin { margin-top: 10px; color: var(--text-2); font-size: 13px; }
/* в развороте кнопка на всю ширину карточки: это единственное действие */
.authx-card .auth-form .btn { align-self: stretch; height: 46.2px; }
.authx-card .auth-links { flex-direction: row; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 6.6px 17.6px; padding-top: 11px; border-top: 1.1px solid var(--line); }
.authx-card .check-row { align-items: flex-start; font-size: 14.3px; line-height: 1.5; }
.authx-card .check-row input[type="checkbox"] { flex: none; margin-top: 2.2px; }
.authx ::selection { background: var(--accent-soft); color: var(--text); }
/* низкое окно (ноутбук, окно не на весь экран): форма важнее панели —
   ужимаем ритм, а на совсем низком прячем панель и центрируем карточку.
   Правка владельца 22.08: «не помещается на одной странице» */
@media (max-height: 820px) {
  .authx { padding-top: 22px; padding-bottom: 26.4px; gap: 39.6px; }
  .authx-card { padding: 22px; gap: 11px; }
  .authx-card .auth-form { gap: 8.8px; }
  .authx-head h1 { font-size: 22px; }
  .authx-lead { margin-top: 11px; }
}
@media (max-height: 660px) and (min-width: 981px) {
  .authx { grid-template-columns: minmax(0, 528px); justify-content: center; }
  .authx-panel { display: none; }
}
/* совсем низкое окно: форма ужимается до предела, но остаётся целой —
   ни одно поле и ни одна галочка не прячутся */
@media (max-height: 700px) {
  .authx { padding-top: 13.2px; padding-bottom: 17.6px; }
  .authx-card { padding: 17.6px; gap: 8.8px; }
  .authx-card .auth-form { gap: 6.6px; }
  .authx-head h1 { font-size: 19.8px; }
  .authx-head .sub { font-size: 13.2px; margin-top: 2.2px; }
  .authx-card .field label { font-size: 12.65px; }
  .authx-card .input { padding-top: 7.7px; padding-bottom: 7.7px; }
  .authx-card .check-row { font-size: 13.2px; }
  .authx-card .auth-form .btn { height: 41.8px; }
  .authx-card .auth-links { padding-top: 8.8px; font-size: 13.75px; }
}
.auth-form { display: flex; flex-direction: column; gap: 15.4px; }
.auth-form .btn { align-self: flex-start; }
.auth-error {
  padding: 8.8px 13.2px; border-radius: 7.7px;
  background: var(--err-soft); color: var(--err); font-size: 14.3px;
}
/* Виджет Яндекс SmartCaptcha: собственную разметку рисует их скрипт, нам
   остаётся отбить его от соседних полей формы. */
.smart-captcha { margin: 2.2px 0; min-height: 0; }
/* соцвход: фирменные логотипы провайдеров — заливные, в отличие от
   контурного набора .ic (icons.html в стиле Lucide) */
.ic-brand { fill: currentColor; stroke: none; }
.acc-label--prov { display: inline-flex; align-items: center; gap: 6.6px; }
/* соцвход: разделитель «или» и кнопки провайдеров под формой */
.auth-alt { display: flex; flex-direction: column; gap: 11px; }
.auth-alt-sep { display: flex; align-items: center; gap: 11px; color: var(--text-2); font-size: 13.2px; }
.auth-alt-sep::before, .auth-alt-sep::after { content: ""; flex: 1; height: 1px; background: var(--line); }
.auth-prov { align-self: stretch; justify-content: center; text-align: center; }
/* Фирменные цвета кнопок входа: их узнают быстрее подписи, и они одни и те
   же в обеих темах — синий ВК читается и на светлом, и на тёмном. Цвета
   заданы литералами, а не токенами: это чужой бренд, а не наша палитра. */
.auth-prov--vk { background: #0077ff; border-color: #0077ff; color: #fff; }
.auth-prov--vk:hover { background: #0068df; border-color: #0068df; color: #fff; }
.auth-prov--ya { background: #000; border-color: #000; color: #fff; }
.auth-prov--ya:hover { background: #1c1c1c; border-color: #1c1c1c; color: #fff; }
.auth-links { display: flex; flex-direction: column; align-items: flex-start; gap: 8.8px; color: var(--text-2); font-size: 14.3px; }
/* согласие занимает три строки — .check-row центрует по вертикали, и
   галочка уезжала на середину абзаца */
.auth .check-row { align-items: flex-start; font-size: 14.3px; line-height: 1.5; }
.auth .check-row input[type="checkbox"] { flex: none; margin-top: 2.2px; }
/* оговорка о конклюдентном принятии соглашения: она под кнопкой и не
   требует действия, поэтому мельче полей и приглушена — но остаётся
   читаемой, иначе принятие перестаёт быть информированным */
.auth-legal { color: var(--text-2); font-size: 13.2px; line-height: 1.45; }

/* Вход и регистрация: компактные формы и полезное превью кабинета. */
.auth-entry { max-width: 1200px; padding: 52px 32px 64px; gap: clamp(48px, 7vw, 96px); grid-template-columns: minmax(0, 1.1fr) minmax(0, .9fr); }
.auth-entry.authx--single { grid-template-columns: minmax(0, 500px); padding-block: 36px 48px; }
.auth-entry .authx-card { padding: 32px; border-radius: 12px; gap: 22px; box-shadow: none; }
.auth-entry .authx-head h1 { font-size: 28px; line-height: 1.2; font-weight: 650; letter-spacing: -.025em; }
.auth-entry .authx-head .sub { margin-top: 10px; font-size: 15px; line-height: 1.6; }
.auth-entry .auth-form { gap: 18px; }
.auth-entry .input { min-height: 48px; border-radius: 7px; font-size: 16px; }
.auth-entry .input::placeholder { color: var(--text-2); opacity: 1; }
.auth-entry .field label { font-size: 14px; font-weight: 500; }
.auth-entry .field label .quiet { font-size: 12px; font-weight: 400; }
.auth-entry .auth-form > .btn { min-height: 48px; border-radius: 7px; font-size: 15px; }
.auth-entry .auth-links { padding-top: 20px; line-height: 1.6; }
.auth-entry .check-row { font-size: 13px; line-height: 1.6; gap: 10px; }
.auth-entry .check-row input { width: 18px; height: 18px; accent-color: var(--accent); }
.auth-entry .auth-legal { font-size: 12px; line-height: 1.6; }
.auth-entry .pass-eye { min-height: 44px; min-width: 70px; font-size: 12px; }
.auth-optional { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.auth-optional .field { min-width: 0; }
.auth-optional .auth-email { grid-column: 1 / -1; }
.auth-field-note { color: var(--text-2); font-size: 12px; line-height: 1.5; margin: 10px 0 0; }
.auth-details { border-block: 1px solid var(--line); padding-block: 4px; }
.auth-details summary { cursor: pointer; padding: 10px 0; font-size: 14px; line-height: 1.5; }
.auth-details summary::marker { color: var(--accent); }
.auth-details summary .quiet { font-size: 12px; }
.auth-details[open] { padding-bottom: 14px; }
.auth-details .auth-optional { margin-top: 8px; }
.auth-entry .auth-legal { margin: 0; }
.auth-preview-label { color: var(--text-2); font-size: 13px; line-height: 1.5; margin-bottom: 18px; }
.auth-preview .authx-title { font-size: clamp(30px, 3.2vw, 42px); line-height: 1.13; font-weight: 600; letter-spacing: -.025em; }
.auth-preview .authx-lead { margin-top: 18px; max-width: 42ch; font-size: 16px; }
.auth-preview-figure { margin: 28px 0 20px; }
.auth-preview-figure img { display: block; width: 100%; height: auto; border: 1px solid var(--line); border-radius: 8px; }
.auth-preview-figure figcaption { color: var(--text-2); margin-top: 10px; font-size: 12px; line-height: 1.5; }
.auth-preview-link { display: inline-block; padding-block: 10px; font-size: 14px; }
@media (prefers-reduced-motion: no-preference) {
  .auth-entry .input { transition: border-color 150ms ease, box-shadow 150ms ease; }
}
@media (max-height: 660px) and (min-width: 981px) {
  .auth-entry { grid-template-columns: minmax(0, 500px); justify-content: center; padding-block: 24px; }
}
@media (max-width: 980px) {
  .auth-entry { max-width: 564px; grid-template-columns: 1fr; padding-block: 32px 48px; gap: 40px; }
  .auth-preview { order: 2; }
}
@media (max-width: 560px) {
  .auth-entry, .auth-entry.authx--single { padding: 28px 18px 40px; }
  .auth-entry .authx-card { padding: 24px 20px; gap: 22px; }
  .auth-entry .authx-head h1 { font-size: 26px; }
  .auth-entry .authx-head .sub { font-size: 14px; }
  .auth-entry .auth-form { gap: 16px; }
  .auth-optional { grid-template-columns: 1fr; gap: 16px; }
  .auth-preview { display: none; }
}


/* обёртка приложений в шапке редактора: нужна только как адрес для
   hx-swap-oob (комната урока меняет задачу без перезагрузки), поэтому
   собственной коробки у неё нет — подпись и скрытые ссылки остаются
   прямыми детьми flex-ряда .py-shell-head */
.py-files { display: contents; }

/* ============================================================
   CodeMirror внутри «окна редактора» .py-shell.

   Мокап (task-card.html) держит в теле окна пару «гуттер + textarea» и
   подсветки не имеет вовсе. Прод пошёл дальше и вендорит CodeMirror
   (решение владельца 31.07) — он рисует свой гуттер и свои токены, но со
   светлой темой по умолчанию: на тёмной теме сайта получалось белое поле
   с фиолетовыми ключевыми словами. Здесь CodeMirror вписывается в окно:
   рамку и фокус-кольцо даёт .py-shell, фон общий, токены — палитра
   .tok-* ядра (та же, что у статической подсветки pyhl.js).
   ============================================================ */
.py-shell-body > .CodeMirror {
  flex: 1; min-width: 0; height: auto; min-height: 176px;
  border: 0; border-radius: 0; background: transparent; color: var(--text);
  font-family: "JetBrains Mono", ui-monospace, monospace; font-size: 14.3px; line-height: 1.7;
  padding: 8.8px 0;
}
/* Лигатуры выключены везде, где показан КОД (правка владельца 09.08):
   в окне редактора и в блоках кода контента — условие задачи, решение,
   конспект урока, статическая подсветка .code. В JetBrains Mono «!=»
   склеивается в «≠», «>=» в «≥»; ученик пишет код на экзамене посимвольно
   и сверяет его с тем, что видит, — символа, которого нет на клавиатуре,
   в листинге быть не должно. Гасим и liga, и calt: у этого шрифта пары
   кода собирает именно контекстная альтернатива. */
.py-shell-body > .CodeMirror,
.py-shell-body .py-editor,
.py-shell .py-output,
pre, code, .code {
  font-variant-ligatures: none;
  font-feature-settings: "liga" 0, "clig" 0, "calt" 0;
}
.py-shell-body .CodeMirror-gutters { background: var(--frame-bg); border-right: 1.1px solid var(--line); }
.py-shell-body .CodeMirror-linenumber { color: var(--text-2); }
.py-shell-body .CodeMirror-cursor { border-left: 1.65px solid var(--text); }
.py-shell-body .CodeMirror-selected { background: var(--accent-soft); }
.py-shell-body .CodeMirror-line::selection,
.py-shell-body .CodeMirror-line > span::selection { background: var(--accent-soft); }
.py-shell-body .CodeMirror-activeline-background { background: var(--frame-bg); }
.py-shell-body .CodeMirror .cm-keyword  { color: var(--tok-kw); font-weight: 600; }
.py-shell-body .CodeMirror .cm-string,
.py-shell-body .CodeMirror .cm-string-2 { color: var(--ok); }
.py-shell-body .CodeMirror .cm-number   { color: var(--tok-num); }
.py-shell-body .CodeMirror .cm-comment  { color: var(--text-2); font-style: italic; }
.py-shell-body .CodeMirror .cm-def      { color: var(--tok-fn); }
.py-shell-body .CodeMirror .cm-builtin  { color: var(--gold); }
.py-shell-body .CodeMirror .cm-operator { color: var(--text-2); }
.py-shell-body .CodeMirror .cm-atom     { color: var(--tok-const); font-weight: 600; }
.py-shell-body .CodeMirror .cm-variable-2,
.py-shell-body .CodeMirror .cm-variable-3 { color: var(--tok-self); }
.py-shell-body .CodeMirror .cm-meta     { color: var(--gold); font-style: italic; }
/* пустой ВЫВОД до первого запуска — «—», ошибка красит текст (.err ядра) */
.py-shell-output .py-output { max-height: 330px; overflow: auto; }

/* ── бейдж-ссылка: в мокапах бейдж всегда <span>, а прод вешает на него
   ссылку (дз урока на карточке ученика, брифы главной, лента уроков) —
   и получал подчёркивание из браузерного дефолта поверх плашки ── */
a.badge { text-decoration: none; }
a.badge:hover { filter: brightness(1.06); }

/* форма под раскрытием: карточка ученика и страница группы не должны
   открываться с распахнутой формой записи урока на пол-карточки — в
   мокапах на её месте одна кнопка «+ урок» */
.inline-form > summary { display: inline-flex; cursor: pointer; list-style: none; }
.inline-form > summary::-webkit-details-marker { display: none; }
.inline-form[open] > summary { margin-bottom: 11px; }

/* svg-график баллов пробников: разметку рисует cabinet.js, поэтому стили
   обязаны быть общими. Жили на странице группы (в мокапе — на экране), а
   главная ученика рисует тот же компонент — и её подписи осей получали
   браузерный чёрный fill, невидимый в тёмной теме (правка владельца
   09.08). Цвета — токенами: график живёт в обеих темах. */
.score-chart { display: block; }
.score-chart-scroll { overflow-x: auto; }
.sc-grid { stroke: var(--line); stroke-width: 1; }
.sc-grid--base { stroke: var(--text-2); }
.sc-grid-label, .sc-date { fill: var(--text-2); font-size: 12.1px; }
/* Дата отделяет столбик от названия пробника в SVG-подписи. */
.sc-date { font-weight: 600; }
.sc-variant { fill: var(--text-2); font-size: 10.45px; }
.sc-trend { fill: none; stroke-width: 2; }
.sc-dot { fill: var(--surface); stroke-width: 2; }
/* балл точки красится инлайном в цвет серии — здесь только начертание */
.sc-score { font-size: 12.1px; font-weight: 600; }
/* столбик сольного графика: цвет инлайном, как у .sc-trend/.sc-dot. Линии
   среднего у графика нет (владелец снял её 10.08) — лучший и последний
   балл называет сводка под графиком */
.sc-bar { }
.chart-legend { display: flex; gap: 15.4px; flex-wrap: wrap; margin-top: 8.8px; font-size: 13.75px; color: var(--text-2); }
.chart-legend i { width: 8.8px; height: 8.8px; border-radius: 50%; display: inline-block; margin-right: 5.5px; }
.attn-line-text { min-width: 0; }

/* Баннер режима просмотра админом (эпик H): полоса под шапкой на каждой
   странице — забыть, под кем смотришь, не должно быть возможно. */
.impersonation-banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 13.2px; flex-wrap: wrap;
  padding: 8.8px 17.6px;
  background: var(--warn-soft, var(--accent-soft));
  border-bottom: 1.1px solid var(--line);
  font-size: 14.3px;
}

/* Баннер режима урока (эпик W): пока идёт классная работа, «В классную
   работу» на карточках складывает разборы именно в неё — состояние
   должно быть видно на любом экране. */
.classwork-banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 13.2px; flex-wrap: wrap;
  padding: 8.8px 17.6px;
  background: var(--accent-soft);
  border-bottom: 1.1px solid var(--accent);
  font-size: 14.3px;
}
.classwork-banner .cw-actions { display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap; }
.classwork-banner .cw-actions form { display: flex; align-items: center; gap: 6.6px; }
.classwork-banner .input { padding: 4.4px 8.8px; font-size: 13.2px; }
/* полоса плана разбора: занимает всю ширину под шапкой баннера — задач в
   плане бывает десяток, в одну строку с кнопками они не помещаются */
.classwork-banner .cw-plan { flex-basis: 100%; display: flex; gap: 6.6px; flex-wrap: wrap; }
.classwork-banner .cw-plan .chip[data-done] { color: var(--ok); border-color: var(--ok); }

/* Комната урока (пересборка 23.08): рабочее место занятия у репетитора и
   экран ученика — один шаблон, разные ветки. */
/* ряд управления уроком: режим ученика, доска, выход в каталог */
.room-bar {
  display: flex; align-items: center; gap: 13.2px; flex-wrap: wrap;
  padding: 8.8px 13.2px; margin-bottom: 15.4px;
  background: var(--frame-bg); border: 1.1px solid var(--line); border-radius: 8.8px;
}
/* полоса состава урока: все задачи занятия разом, как клетки дз. ✓ —
   разбор уже сохранён, рамкой акцента помечена задача РЕПЕТИТОРА (в
   свободном режиме ученик сидит на своей и должен видеть, где идёт разбор) */
.room-strip { display: flex; gap: 6.6px; flex-wrap: wrap; margin-bottom: 15.4px; }
.room-task-slot { display: inline-flex; align-items: stretch; }
.room-task-slot .chip { border-radius: 0 6.6px 6.6px 0; }
.room-strip .chip[data-done] { color: var(--ok); border-color: var(--ok); }
.room-strip .chip[data-at] { box-shadow: 0 0 0 2.2px var(--accent-soft); }
/* в отслеживании полоса ученику не кликается: задачу ведёт репетитор */
.room-strip[data-locked] .chip { pointer-events: none; opacity: .75; }

/* Ручка порядка задач: самостоятельная кнопка, чтобы перетаскивание не
   конфликтовало с открытием задачи. Стрелки работают при фокусе с клавиатуры. */
.task-order-grip {
  flex: none; display: inline-grid; place-items: center;
  width: 30.8px; min-height: 30.8px; padding: 0;
  color: var(--text-2); background: transparent;
  border: 1.1px solid transparent; border-radius: 6.6px;
  cursor: grab; touch-action: none;
}
.task-order-grip:hover { color: var(--accent); background: var(--accent-soft); }
.task-order-grip:focus-visible { outline: 2.2px solid var(--accent); outline-offset: 1.1px; }
.task-order-grip:active, [data-task-order-item].is-dragging .task-order-grip { cursor: grabbing; }
[data-task-order-item].is-dragging { opacity: .72; }
[data-task-order-list].is-saving .task-order-grip { pointer-events: none; opacity: .45; }
.task-order-grip--chip {
  width: 27.5px; min-height: 100%;
  border-color: var(--line); border-right: 0; border-radius: 6.6px 0 0 6.6px;
  background: var(--surface);
}
.task-order-status {
  display: block; width: 100%; margin-top: 6.6px;
  color: var(--text-2); font-size: 12.1px;
}
.task-order-status.is-error { color: var(--err); }
/* строка автосохранения разбора: кнопка-страховка, поле ответа и статус */
.cw-auto-row {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  margin-top: 13.2px;
}
.cw-auto-row .input { max-width: 220px; }

/* Баннеры подписки (G-1): триал — мягкое напоминание тёплым акцентом,
   expired — плашка read-only тревожным цветом (кабинет пишет только
   после продления). Полировка вёрстки — за дизайн-эпиком. */
.banner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 13.2px; flex-wrap: wrap;
  padding: 8.8px 17.6px;
  border-bottom: 1.1px solid var(--line);
  font-size: 14.3px;
}
.banner-trial { background: var(--warn-soft); border-bottom-color: var(--warn); }
.banner-expired { background: var(--err-soft); border-bottom-color: var(--err); color: var(--err); }
.banner-upsell { background: var(--accent-soft); border-bottom-color: var(--accent); }
/* «идёт урок» (эпик live): вход в комнату у ученика на главной и в ленте */
.banner-live { background: var(--ok-soft); border-bottom-color: var(--ok); }

/* Поверхности участников live-урока (эпик live) на экране репетитора:
   карточка на ученика, внутри вкладки «код ⇄ доска». Вёрстка намеренно
   скромная — это рабочая панель урока, а не витрина. */
.live-mine { display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap; margin: 13.2px 0; }
.live-mine-label { color: var(--text-2); font-size: 13.2px; }
.live-peer { margin-bottom: 13.2px; }
.live-peer-head { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; margin-bottom: 8.8px; }
.live-peer-tabs { flex: 0 0 auto; }
.live-peer-code { width: 100%; }
.live-peer-out { margin-top: 8.8px; max-height: 176px; overflow: auto; }
.live-peer-board { position: relative; width: 100%; aspect-ratio: 4 / 3;
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 8.8px; }
.live-peer-canvas { position: absolute; inset: 0; width: 100%; height: 100%;
  touch-action: none; cursor: crosshair; }
.live-peer-codebox { position: relative; }

/* Маркер поверх кода (решение 6 спеки): холст накрывает панель целиком —
   он зеркало чужого пера, — но ввод перехватывает только с тумблера,
   иначе редактор не кликнуть и строку не выделить. */
.py-live-bar { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; margin-bottom: 8.8px; }
.py-live-tools.draw-toolbar { margin-bottom: 0; padding: 6.6px 11px; gap: 13.2px; }
.py-ran { color: var(--text-2); font-size: 13.2px; }
.py-live-host { position: relative; }
.py-live-canvas {
  position: absolute; inset: 0; width: 100%; height: 100%; z-index: 2;
  pointer-events: none; touch-action: none;
}
.py-live-canvas.live-draw { pointer-events: auto; cursor: crosshair; }

/* Подвал с юридическими ссылками: они должны быть доступны с любой
   страницы — требование к сервису, обрабатывающему персональные данные. */
/* С 08.08 подвал в колонках (спека 2026-08-08-guest-site-design): продуктовые
   пункты ушли из гостевой шапки, и колонка «Банк» здесь — единственный
   путь гостя в каталог с карточки задачи. Живёт в prod.css, а не в
   шаблоне: у layout.html своего <style> нет. */
.site-footer {
  display: flex; gap: 44px; flex-wrap: wrap; justify-content: center;
  align-items: flex-start;
  padding: 26.4px 17.6px calc(64px + env(safe-area-inset-bottom, 0px)); margin-top: 44px;
  border-top: 1.1px solid var(--line);
  font-size: 13.75px;
}
.site-footer a { color: var(--text-2); text-decoration: none; }
.site-footer a:hover { color: var(--accent); }
.footer-col { display: flex; flex-direction: column; gap: 6.6px; }
.footer-head {
  color: var(--text-2); font-size: 12.1px;
  letter-spacing: .08em; text-transform: uppercase;
}
@media (max-width: 700px) { .site-footer { gap: 26.4px; } }

/* ── .py-file: скрытые ссылки на приложения задачи внутри панели питона —
   источник файлов для виртуальной ФС раннера (pyeditor.js). Атрибут hidden
   их и так прячет, правило нужно, чтобы класс не считался осиротевшим ── */
.py-file { display: none; }

/* ── явная иерархия и выбор группы в панели источников (правка владельца
   01.08). Галочка группы — свой квадратик слева от названия: клик по ней
   выбирает/исключает всю ветку, клик по названию только раскрывает. Ветки
   разной глубины разведены отступом и линией, чтобы «ФИПИ → апробации →
   источник» читались как три уровня, а не как один список ── */
.ms-group > summary { display: flex; align-items: center; gap: 8px; }
.ms-group-name { flex: 1; min-width: 0; }
.ms-group-box {
  flex: none; width: 15px; height: 15px; border-radius: 4px;
  border: 1px solid var(--line); background: var(--surface);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; line-height: 1; cursor: pointer;
}
.ms-group-box:hover { border-color: var(--accent); }
.ms-group-box.is-on { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.ms-group-box.is-on::before { content: "✓"; }
.ms-group-box.is-excluded { border-color: var(--err); color: var(--err); }
.ms-group-box.is-excluded::before { content: "−"; }
/* вложенность видна отступом и левой линией, а не только величиной сдвига */
.ms-group--lvl2 { margin-left: 10px; padding-left: 8px; border-left: 1px solid var(--line); }
.ms-group--lvl1 > summary { font-weight: 600; }
.ms-group--lvl2 > summary { font-weight: 500; color: var(--text-2); }
/* сброс всех фильтров каталога — рядом с кнопкой «Фильтр» */
.filter-clear { font-size: 13px; white-space: nowrap; }

/* ── карточка ученика в проде — div[data-row-link], а не <a> (D1 аудита
   07.08: внутри появилась своя ссылка — бейдж дз, а вложенный <a>
   невалиден). В мокапе карточка осталась ссылкой, поэтому курсор-руку
   даём здесь, дельтой прода ── */
.pupil-card[data-row-link] { cursor: pointer; }

/* ── «очистить» в шапке панели ВЫВОД (правка владельца 08.08): запуск
   больше не стирает предыдущий вывод сам, чистит только эта кнопка.
   Ссылка-кнопка, а не .btn: она внутри лейбла секции ── */
.py-output-label { display: flex; align-items: center; justify-content: space-between; gap: 8.8px; }
.py-out-clear {
  border: 0; background: none; padding: 0; cursor: pointer;
  font: inherit; text-transform: none; letter-spacing: 0;
  color: var(--text-2); text-decoration: underline;
}
.py-out-clear:hover { color: var(--text); }

/* ── фокус-кольцо «окна редактора» (правка владельца 08.08): при клике в
   CodeMirror ядро подсвечивало .py-shell целиком — акцентная рамка плюс
   кольцо 3.3px вокруг всего блока, и это перетягивало на себя внимание с
   условия. Гасим кольцо и оставляем обычную рамку: курсор в редакторе и
   так виден ── */
.py-shell:focus-within { border-color: var(--line); box-shadow: none; }

/* ── доска-черновик у задачи (правки владельца 08–09.08): полноэкранная
   сцена с объектами (условие, картинки, надписи, фигуры, штрихи),
   инструменты сверху, свойства инструмента — во всплывающей панели.
   Компонента нет в мокапах — прод-дельта ── */
.task-board { min-width: 0; }
/* кнопки черновика в один ряд и одной высоты (правка владельца 09.08:
   раньше «Открыть доску» стояла ниже — у неё была своя обёртка) */
.draft-row { display: flex; flex-wrap: wrap; align-items: flex-start; gap: 11px; }
.draft-row > * { flex: 0 0 auto; }
/* кнопки входов — одной высоты и по одной линии: у редактора и доски
   разные обёртки, и без общего правила они разъезжались по вертикали
   (правка владельца 09.08) */
.draft-row .btn { height: 38.5px; align-items: center; }
/* у панели питона свой верхний отступ (она живёт и вне этого ряда) —
   в ряду он сдвигал её кнопку ниже соседних */
.draft-row > .py-panel { margin-top: 0; }
.draft-row > .is-open { flex: 1 1 100%; }
.tool-btn-wide { gap: 6.6px; }

.board-stage {
  position: fixed; inset: 0; z-index: 90; display: flex; flex-direction: column;
  background: var(--bg);
}
.board-stage[hidden] { display: none; }
body.board-open { overflow: hidden; }

.board-bar {
  position: relative; z-index: 4; display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap;
  padding: 8.8px 15.4px; border-bottom: 1.1px solid var(--line); background: var(--surface);
}
.board-title { font-weight: 600; font-size: 14.3px; }
.board-bar-group { display: flex; align-items: center; gap: 6.6px; }
.board-bar-spacer { flex: 1; }
.board-zoom { width: auto; padding: 0 8.8px; font-family: "JetBrains Mono", monospace; font-size: 12.65px; }
.board-body { flex: 1; display: flex; align-items: stretch; min-height: 0; }
.board-tool-row { position: relative; display: flex; align-items: center; gap: 8px; min-width: 0; }
.board-save-status { white-space: nowrap; font-size: 12px; display: inline-flex; align-items: center; gap: 5px; }
.board-save-status .ic { width: 15px; height: 15px; }

/* компактная горизонтальная полоса инструментов */
.board-tools { display: flex; align-items: center; gap: 3px; min-width: 0; }
.tool-btn {
  width: 39.6px; height: 39.6px; border-radius: 9.9px; border: 1.1px solid transparent;
  background: none; color: var(--text-2); cursor: pointer; display: inline-flex;
  align-items: center; justify-content: center; padding: 0; position: relative;
}
.tool-btn:hover { background: var(--frame-bg); color: var(--text); }
.tool-btn.active { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }
.tool-sep { width: 1px; height: 24px; background: var(--line); margin: 0 5px; flex: none; }
.board-shape-wrap { position: relative; display: inline-flex; align-items: center; flex: none; }
.board-shape-wrap > .tool-btn { border-radius: 9px 0 0 9px; }
.board-shape-arrow { width: 18px; height: 39.6px; padding: 0; margin-left: -1px; border: 1px solid transparent; border-radius: 0 9px 9px 0; color: var(--text-2); background: none; cursor: pointer; }
.board-shape-arrow .ic { width: 13px; height: 13px; }
.board-shape-arrow:hover, .board-shape-arrow[aria-expanded="true"] { background: var(--frame-bg); color: var(--text); }
.board-shape-menu { position: absolute; top: calc(100% + 8px); left: 0; z-index: 11; display: flex; gap: 3px; padding: 5px; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 12px 30px rgba(0,0,0,.18); }
.board-shape-menu[hidden] { display: none; }
.board-mode-btn { border: 1px solid var(--line); background: var(--surface); color: var(--text); border-radius: 7px; padding: 7px 9px; font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap; }
.board-mode-btn[hidden] { display: none; }
.board-props-toggle[hidden] { display: none; }
.board-props-toggle:disabled { opacity: .4; cursor: default; }

/* параметры выбранного инструмента поверх доски, без сдвига листа */
.board-props {
  position: absolute; top: calc(100% + 12px); right: 0; z-index: 10;
  display: grid; gap: 12px; padding: 14px; min-width: 250px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 12px 30px rgba(0,0,0,.18);
}
.board-props[hidden] { display: none; }
.board-props .board-swatches[hidden], .board-props .board-width[hidden] { display: none; }
.board-swatches { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.board-swatch {
  width: 28.6px; height: 28.6px; border-radius: 50%; border: 2.2px solid transparent;
  outline: 1.1px solid var(--line); cursor: pointer; padding: 0; position: relative;
}
.board-swatch.active { border-color: var(--surface); outline: 2.2px solid var(--text); }
/* свой цвет: кружок последнего выбранного + кольцо-подсказка, что его
   можно сменить снова */
.board-swatch--pick {
  display: inline-flex; align-items: center; justify-content: center; overflow: hidden;
  background: conic-gradient(#e5484d, #f76b15, #ffc53d, #30a46c, #3b82f6, #8b5cf6, #e5484d);
}
.board-swatch--pick::after {
  content: ""; position: absolute; inset: 7.7px; border-radius: 50%;
  border: 1.65px solid rgba(255, 255, 255, .85);
}
.board-swatch--pick input { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.board-width { display: flex; align-items: center; gap: 8px; }
/* вертикальный ползунок толщины — как в панели на скрине владельца */
.board-width input[type="range"] { width: 190px; accent-color: var(--accent); }
.board-width-val { color: var(--text-2); font-size: 12.1px; }

.board-viewport { flex: 1; position: relative; overflow: hidden; touch-action: none; background: var(--bg); }
.board-viewport.is-hand { cursor: grab; }
.board-viewport.is-grabbing { cursor: grabbing; }
.board-viewport.is-draw { cursor: crosshair; }
.board-world {
  position: absolute; top: 0; left: 0; transform-origin: 0 0; background: #fbfaf7;
  background-image:
    linear-gradient(to right, rgba(0, 0, 0, .06) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(0, 0, 0, .06) 1px, transparent 1px);
  background-size: 40px 40px;
  box-shadow: 0 0 0 1.1px var(--line);
}
.board-sheet-selection { position: absolute; inset: 0; outline: 2px solid var(--accent); pointer-events: none; }
.board-sheet-selection[hidden] { display: none; }
.board-sheet-grip { position: absolute; width: 20px; height: 20px; right: -10px; bottom: -10px; background: var(--accent); border: 2px solid #fff; border-radius: 4px; cursor: nwse-resize; pointer-events: auto; }
@media (max-width: 900px) { .board-tool-row { order: 5; width: 100%; } .board-tools { flex-wrap: nowrap; } }
.board-objects { position: absolute; inset: 0; }
.board-canvas { position: absolute; left: 0; top: 0; pointer-events: none; }
/* объекты не ловят указатель, пока не включено выделение: иначе штрих
   поверх условия «цеплялся» бы за текст */
.board-obj { position: absolute; color: #1a1a1a; }
.board-viewport:not(.is-select) .board-obj { pointer-events: none; user-select: none; }
/* запертый базовый слой доски сдачи (работа ученика под пометками
   репетитора): ни ручки размера, ни выделения — курсор обычный */
.board-obj.is-locked { cursor: default; }
.board-obj--task {
  background: #fff; border: 1.1px solid rgba(0, 0, 0, .12); border-radius: 11px;
  padding: 19.8px 22px; box-shadow: 0 4.4px 15.4px rgba(0, 0, 0, .08); line-height: 1.6;
}
/* условие на доске — цельный объект, «вставленная картинка» (правка
   владельца 11.08): текст не выделяется мышью и не перехватывает
   перетаскивание карточки, ручка размера тянет масштаб всего блока */
.board-obj--task, .board-obj--task * { user-select: none; }
.board-obj--task .board-obj-body { pointer-events: none; }
.board-obj--task img, .board-obj--image img { max-width: 100%; height: auto; display: block; }
.board-obj--task table { border-collapse: collapse; margin: 11px 0; }
.board-obj--task td, .board-obj--task th { border: 1px solid rgba(0, 0, 0, .25); padding: 3.3px 6.6px; }
.board-obj--text { white-space: pre-wrap; font-weight: 600; line-height: 1.25; }
.board-obj.is-selected { outline: 2.2px solid var(--accent); outline-offset: 4.4px; }
.board-obj-grip {
  position: absolute; right: -7.7px; bottom: -7.7px; width: 17.6px; height: 17.6px;
  border-radius: 4.4px; border: 1.1px solid var(--accent); background: #fff; cursor: nwse-resize;
  display: none;
}
.board-viewport.is-select .board-obj-grip { display: block; }
.board-selbox { position: absolute; border: 1.65px dashed var(--accent); border-radius: 4.4px; pointer-events: none; }
.board-marquee {
  position: absolute; border: 1.65px dashed var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent); pointer-events: none;
}
.board-text-input {
  position: absolute; min-width: 176px; background: rgba(255, 255, 255, .96);
  border: 1.65px solid var(--accent); border-radius: 6.6px; padding: 4.4px 6.6px;
  font-weight: 600; line-height: 1.25; resize: both;
}

/* буква клавиши на кнопке инструмента (правка владельца 22.08): подсказку
   в title видно только по наведению, а на планшете — никогда */
.tool-key {
  position: absolute; right: 3.3px; bottom: 2.2px; font: 500 9.35px/1 "JetBrains Mono", monospace;
  color: var(--text-2); opacity: .75; pointer-events: none;
}
.draw-btn { position: relative; }
.draw-key {
  position: absolute; right: 2.2px; bottom: 1.1px; font: 500 8.8px/1 "JetBrains Mono", monospace;
  color: var(--text-2); opacity: .75; pointer-events: none;
}

/* поповер размера листа (правка владельца 22.08): лист конечен, но
   растягивается — «+ вниз», «+ вправо» и поля с пределами */
.board-sheet-wrap { position: relative; }
.board-sheet-pop {
  position: absolute; top: calc(100% + 6.6px); right: 0; z-index: 5; width: 264px;
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 11px;
  padding: 13.2px; box-shadow: 0 8.8px 26.4px rgba(0, 0, 0, .12); display: grid; gap: 8.8px;
}
.board-sheet-pop[hidden] { display: none; }
.board-sheet-pop label { display: grid; gap: 4.4px; font-size: 13.2px; color: var(--text-2); }
.board-sheet-acts { display: flex; gap: 6.6px; flex-wrap: wrap; }
.board-sheet-note { font-size: 12.1px; min-height: 1em; }

/* происхождение задачи в карточке каталога — рядом с id, приглушённо
   (правка владельца 08.08): «Резерв 22.06.26», «Иволгин Л.» */
.task-src { color: var(--text-2); font-size: 13.2px; }
/* бейдж «ученики» на карточке каталога (правка владельца 22.08): текст
   переменной длины («решили 3 · смотрели 1 · на уроке 1») не должен
   резаться однострочным .badge */
.tf-pupils { max-width: 100%; white-space: normal; }
/* клетка состава варианта, закрытая до решения (variant.html): та же
   геометрия, что у ссылки-клетки, но без наведения и курсора */
.diff-bar.is-locked { cursor: default; opacity: .75; }

/* квадратик опции мультиселекта не ловит клик сам (баг владельца 09.08):
   браузер переключал его ДО нашего обработчика и откатывал ПОСЛЕ — клик
   по квадратику то перескакивал через состояние, то не срабатывал вовсе.
   Клик всегда обрабатывает строка .ms-option целиком */
.ms-option input[type="checkbox"] { pointer-events: none; }

/* окно подтверждения (confirm.js) — узкое, вопрос заголовком, действия
   в ряд. Оверлей и рамка — .modal-overlay/.modal ядра */
.confirm-modal { max-width: 396px; }
.confirm-modal .modal-head { margin-bottom: 13.2px; }
.confirm-modal .modal-head h2 { font-size: 16.5px; font-weight: 600; line-height: 1.4; }
.confirm-modal .modal-actions { margin-top: 0; }
/* опасное подтверждение — красная главная кнопка: btn-danger в ядре
   рассчитан на ghost-кнопку (красный текст), а здесь нужен акцент */
.confirm-modal .btn-primary.btn-danger {
  background: var(--err); border-color: var(--err); color: #fff;
}

/* окно холста Черепахи (pyturtle.js) — плавающее окно поверх страницы, как
   Turtle Graphics в IDLE: задание 6 решают питоновским turtle, а рисунок в
   поток вывода не влезает. Разметку ставит JS, поэтому сканер классов
   шаблонов пропажу этих правил не увидит — держит TestTurtleWindowStyles */
.tw-window {
  position: fixed; z-index: 60; display: flex; flex-direction: column;
  min-width: 280px; min-height: 220px; overflow: hidden;
  background: var(--surface); border: 1.1px solid var(--line);
  border-radius: 11px; box-shadow: 0 18px 44px rgba(0, 0, 0, .22);
}
/* ручки размера по всем сторонам и углам: нативный resize: both тянет
   только правый нижний угол, а окно чаще расширяют влево и вверх
   (правка владельца 10.08). Ручки прозрачные — видно курсор, не рамку */
.tw-grip { position: absolute; z-index: 3; touch-action: none; }
.tw-grip-n { top: 0; left: 10px; right: 10px; height: 6px; cursor: ns-resize; }
.tw-grip-s { bottom: 0; left: 10px; right: 10px; height: 6px; cursor: ns-resize; }
.tw-grip-w { left: 0; top: 10px; bottom: 10px; width: 6px; cursor: ew-resize; }
.tw-grip-e { right: 0; top: 10px; bottom: 10px; width: 6px; cursor: ew-resize; }
.tw-grip-nw { top: 0; left: 0; width: 12px; height: 12px; cursor: nwse-resize; }
.tw-grip-se { bottom: 0; right: 0; width: 12px; height: 12px; cursor: nwse-resize; }
.tw-grip-ne { top: 0; right: 0; width: 12px; height: 12px; cursor: nesw-resize; }
.tw-grip-sw { bottom: 0; left: 0; width: 12px; height: 12px; cursor: nesw-resize; }
/* свой display бьёт [hidden] (мокапная ловушка): без этой строки крестик
   окно не закрывал (жалоба владельца 10.08) */
.tw-window[hidden] { display: none; }
.tw-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8.8px;
  padding: 6.6px 8.8px 6.6px 13.2px; cursor: move; user-select: none;
  background: var(--frame-bg); border-bottom: 1.1px solid var(--line);
}
.tw-title { font-size: 13.2px; font-weight: 600; }
.tw-btn {
  background: none; border: 1.1px solid var(--line); border-radius: 7.7px;
  color: var(--text-2); font: inherit; font-size: 12.1px; cursor: pointer;
  padding: 2.2px 8.8px;
}
.tw-btn:hover { color: var(--text); border-color: var(--text-2); }
.tw-close { font-size: 15.4px; line-height: 1; padding: 0 6.6px; }
/* холст больше окна (screensize) — окно его прокручивает, как tkinter */
.tw-body { flex: 1; min-height: 0; overflow: auto; }
.tw-canvas { display: block; }

/* ── карта 27: непрерывная шкала охвата (правка владельца 10.08) ──
   Прежние ступени c0..c3 упирались в потолок «6+», а за подготовку номер
   прорешивают десятками — карта становилась ровно тёмной. Теперь заливку
   считает сервер (coverageFill: доля от самого прорешанного номера) и
   отдаёт инлайном; классу остаётся только цвет цифры, который инлайн не
   знает. Пустая клетка по-прежнему .c0 ядра. */
.map27 .cell-fill { color: var(--text); font-weight: 550; }
.map27 .cell-fill--deep { color: var(--on-accent); }
/* штриховка «разбирали на уроке, сам не решал» (спека 2026-08-24): ложится
   background-image ПОВЕРХ заливки клетки, поэтому заливка приходит из Go
   через background-color, а не shorthand — shorthand стёр бы диагональ.
   Тон берётся от текста темы: диагональ одинаково читается и на пустой
   клетке, и на тёмной половине шкалы, а свой hex пришлось бы держать в
   двух темах. */
/* диагональ ДВУХТОНОВАЯ — тёмная линия и светлая рядом: клетка под ней
   бывает любой яркости (пустая, средняя заливка, пик шкалы), и одноцветная
   штриховка на половине из них тонула. Пара линий читается на любом фоне в
   обеих темах — тот же приём, что у гравировки. */
.map27 .cell-hatch, .lg-hatch {
  background-image:
    repeating-linear-gradient(45deg, transparent 0 3px,
      color-mix(in srgb, var(--text) 34%, transparent) 3px 4.6px),
    repeating-linear-gradient(45deg,
      color-mix(in srgb, var(--on-accent) 34%, transparent) 0 1.6px,
      transparent 1.6px 4.6px);
}
.lg-hatch { background-color: var(--cell-touched); }
/* ── карта 27: цветные слои «точность» и «цена» ────────────────────────
   Цена (правка владельца 15.08) — сколько попыток номер стоит субъекту
   (среднее attempts_count по честно решённым). Шкала ОБРАТНАЯ точности:
   дешевле лучше, поэтому дешёвая клетка зелёная, дорогая красная — тот же
   словарь цветов, что у точности, одна легенда на оба слоя.

   Заливку обоих слоёв считаем здесь, а не берём токеном *-soft ядра
   (правка владельца 21.08: «в тёмной теме плоховато читается»). В тёмной
   теме подложки ядра почти равны поверхности карточки — --ok-soft
   #22301F против --surface #211B17, — и карта точности выглядела ровным
   тёмным полем: клетки не отличались от фона, читались одни цифры.
   Заливка — тон, подмешанный к поверхности, и доля тона в тёмной теме
   БОЛЬШЕ: там подложке нужно уйти от фона дальше, чем на белом. Рамка —
   тот же тон гуще: она отделяет соседние клетки одного цвета. Цифра —
   --text, а не собственный тон клетки: на цветной заливке контраст выше
   в обеих темах. Три состояния токенов, как в ядре: светлая по умолчанию,
   тёмная по data-theme и по системной теме до отработки head-сниппета. */
:root { --map-tone: 30%; --map-tone-line: 52%; }
:root[data-theme="dark"] { --map-tone: 42%; --map-tone-line: 62%; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) { --map-tone: 42%; --map-tone-line: 62%; }
}
.map27 .acc-high, .map27 .price-cheap {
  background: color-mix(in srgb, var(--ok) var(--map-tone), var(--surface));
  border-color: color-mix(in srgb, var(--ok) var(--map-tone-line), var(--surface));
}
.map27 .acc-mid, .map27 .price-mid {
  background: color-mix(in srgb, var(--warn) var(--map-tone), var(--surface));
  border-color: color-mix(in srgb, var(--warn) var(--map-tone-line), var(--surface));
}
.map27 .acc-low, .map27 .price-dear {
  background: color-mix(in srgb, var(--err) var(--map-tone), var(--surface));
  border-color: color-mix(in srgb, var(--err) var(--map-tone-line), var(--surface));
}
.map27 .acc-high, .map27 .acc-mid, .map27 .acc-low,
.map27 .price-cheap, .map27 .price-mid, .map27 .price-dear { color: var(--text); font-weight: 600; }
/* «мало данных» (точность по двум попыткам, цена по одной задаче) —
   приглушено цветом цифры, без заливки */
.map27 .acc-few, .map27 .price-few { color: var(--text-2); }
/* свотчи легенды — той же заливкой, что клетки: иначе легенда объясняет
   цвета, которых в тёмной теме не видно */
.lg-tone { border: 1.1px solid transparent; }
.lg-ok   { background: color-mix(in srgb, var(--ok) var(--map-tone), var(--surface));   border-color: color-mix(in srgb, var(--ok) var(--map-tone-line), var(--surface)); }
.lg-warn { background: color-mix(in srgb, var(--warn) var(--map-tone), var(--surface)); border-color: color-mix(in srgb, var(--warn) var(--map-tone-line), var(--surface)); }
.lg-err  { background: color-mix(in srgb, var(--err) var(--map-tone), var(--surface));  border-color: color-mix(in srgb, var(--err) var(--map-tone-line), var(--surface)); }
.lg-none { background: var(--surface); border-color: var(--line); }
/* цена по одной решённой задаче — среднее ещё случайно, поэтому цифра в
   таблице «детальнее» приглушена, как «мало данных» у точности */
.num-price--few { color: var(--text-2); }

/* легенда-градиент: сам переход цвета вместо перечисления ступеней.
   Подписи стоят по краям полосы (правка владельца 17.08), поэтому зазор
   между элементами здесь меньше общего .map-legend — иначе «решал реже» и
   «чаще» отрывались от градиента и читались как отдельные пункты */
.map-legend--scale { align-items: center; gap: 8.8px; }
.map-legend-side { white-space: nowrap; }
.map-legend-scale {
  display: inline-block; width: 132px; height: 8.8px; border-radius: 4.4px;
  background: linear-gradient(to right, var(--cell-touched), var(--cell-solved));
}

/* ── главная ученика: «что решено» вместо отдельной секции решений
   (правка владельца 10.08). Задача с прикреплённым решением помечена в
   самом списке, номер несёт счётчик — секцию «ПРИКРЕПЛЁННЫЕ РЕШЕНИЯ»
   слили сюда ── */
.chip--sub { border-color: var(--accent); box-shadow: inset 0 0 0 1.1px var(--accent); }
/* автор комментария ревью: ассистент кабинета, когда владелец разрешил
   показывать его имя ученику (правка владельца 23.08) */
.cc-author { color: var(--text); font-weight: 600; }

/* карточка ассистентов в /account (пересборка 23.08): строка человека,
   блок приглашения и ряд «места» — раньше всё это лежало в .acc-row, и
   кнопки с подписями сбивались в кашу */
/* чип «кабинет: Имя» в шапке у ассистента (правка владельца 23.08): вместо
   полосы во всю ширину на каждом экране — заметная, но своя строка меты */
.as-chip {
  display: inline-flex; align-items: center; gap: 5.5px; margin-left: 11px;
  padding: 3.3px 9.9px; border-radius: 99px; border: 1.1px solid var(--accent);
  background: var(--accent-soft); color: var(--text); font-size: 13.2px;
  font-weight: 550; text-decoration: none; white-space: nowrap;
}
.as-chip:hover { border-color: var(--text); }
.as-chip .ic { width: 14.3px; height: 14.3px; }
/* строка роли в меню под аватаром — как строка тарифа рядом */
/* закрытое владельцем действие: кнопка на месте, но мёртвая и серая —
   видно, что действие есть и что его закрыли (правка владельца 23.08) */
.btn--locked, .btn--locked:hover {
  background: var(--frame-bg); border-color: var(--line); color: var(--text-2);
  cursor: not-allowed; opacity: .65; box-shadow: none; text-decoration: none;
}

/* строка ассистента на его главной: кабинет и открытые области — вместо
   полосы во всю ширину на каждом экране (правка владельца 23.08) */
.as-access {
  display: flex; align-items: center; gap: 13.2px; flex-wrap: wrap;
  margin: 0 0 17.6px; padding: 8.8px 13.2px; border-radius: 9.9px;
  border: 1.1px solid var(--line); background: var(--frame-bg); font-size: 13.75px;
}
.as-access-who { font-weight: 550; }
.as-access-areas { color: var(--text-2); }

/* списки областей в карточке «где вы ассистент»: бейджи в строку */
.as-areas-list { display: flex; flex-wrap: wrap; gap: 6.6px; align-items: center; }

.menu-role { display: flex; align-items: baseline; justify-content: space-between; gap: 11px; }
.menu-role .mp-left { color: var(--text-2); font-size: 13.2px; }

.as-row { display: flex; align-items: center; gap: 13.2px; flex-wrap: wrap;
  padding: 11px 0; border-bottom: 1.1px solid var(--line); }
.as-who { font-weight: 600; }
.as-row-acts { display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap; margin-left: auto; }
.as-row-acts form { margin: 0; }
.as-invite { padding: 13.2px 0; border-bottom: 1.1px solid var(--line); }
.as-invite-acts { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; margin-top: 8.8px; }
.as-seats { display: flex; align-items: center; gap: 13.2px; flex-wrap: wrap; padding: 13.2px 0; }
.as-seats-text { color: var(--text-2); font-size: 14.3px; }
.as-seats-acts { display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap; margin-left: auto; }
.as-seats-acts form { margin: 0; }
.as-note { margin: 0; font-size: 13.75px; }
.pf-subs { color: var(--accent); font-size: 13.2px; }

/* задача, где ученик открывал решение: в картине умений она не считается
   умением, поэтому в списке помечена приглушённо (спека попыток 11.08) */
.chip--revealed { border-style: dashed; color: var(--text-2); }

/* заметка ученика к прикреплённому решению (правка владельца 11.08):
   короткий текст над кодом, читается как реплика, а не как часть решения */
.sub-note { color: var(--text); white-space: pre-line; line-height: 1.55; }

/* ── список сданных попыток варианта: строка на попытку, под ней —
   ошибочные номера ссылками. Разделитель между строками, а не карточка на
   каждую: попыток бывает пять-шесть, и рамки превращали бы список в
   лестницу. Канонизировано в prod.css при втором использовании (правка
   владельца 11.08): та же идиома на карточке варианта (ПОПЫТКИ) и на
   главной ученика (РАЗБОРЫ ПРОБНИКОВ) — там строки были одноуровневой
   лентой «дата · название · бейдж», где балл, название и список ошибок
   лежали в одной строке текста и не читались ── */
.attempt-list { display: flex; flex-direction: column; gap: 0; }
/* строка кликабельна целиком (ведёт в разбор), поэтому у неё курсор и
   подсветка на наведение — отдельной ссылки «разбор →» больше нет */
.attempt-row {
  padding: 11px 13.2px; border-bottom: 1.1px solid var(--line);
  margin: 0 -13.2px; border-radius: 8.8px; cursor: pointer;
}
.attempt-row:hover { background: var(--frame-bg); }
.attempt-row:focus-visible { outline: 2.2px solid var(--accent); outline-offset: -2.2px; }
.attempt-row:last-of-type { border-bottom: 0; }
.attempt-head { display: flex; align-items: baseline; gap: 11px; flex-wrap: wrap; }
.attempt-when { font-size: 17.6px; font-weight: 650; color: var(--text); }
/* название варианта — рядом с датой, обычным весом: дата ведёт список
   (он хронологический), название отвечает «какой вариант» */
.attempt-title { font-size: 15.4px; color: var(--text); overflow-wrap: anywhere; }
.attempt-score { color: var(--text-2); font-size: 14.3px; }
.attempt-score .sub { font-size: 13.2px; }
/* тестовый балл попытки — плашка по порогам ЕГЭ (44 — минимум, 70 — «хорошо»):
   правка владельца 13.08, балл должен читаться, а не теряться в строке */
.attempt-test {
  display: inline-flex; align-items: baseline; gap: 5.5px;
  padding: 3.3px 11px; border-radius: 999px;
}
.attempt-test b { font-size: 19.8px; font-weight: 650; line-height: 1.1; }
.attempt-test .u { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; opacity: .8; }
.attempt-test--low  { color: var(--err);  background: var(--err-soft); }
.attempt-test--mid  { color: var(--warn); background: var(--warn-soft); }
.attempt-test--high { color: var(--ok);   background: var(--ok-soft); }
/* лучшая попытка: золотой рант слева и крупная золотая искра у балла —
   метка читается боковым зрением и не спорит с датой (правка владельца
   13.08).

   Золото своё, а не --gold: в светлой теме тот токен — тёмная олива
   (#8A6D00), на кремовом фоне она читается коричневой, а метка достижения
   обязана выглядеть золотом в обеих темах (правка владельца 13.08).
   Три состояния, как у токенов ядра: светлая по умолчанию, тёмная по
   data-theme и по системной теме до отработки head-сниппета. */
:root { --best-gold: #C4941A; }
:root[data-theme="dark"] { --best-gold: #FFD447; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) { --best-gold: #FFD447; }
}
.attempt-row.is-best { box-shadow: inset 3.3px 0 0 var(--best-gold); }
/* полоска разбора с ЦВЕТОМ УЧАСТНИКА (статистика пробников группы, правка
   владельца 21.08): рант слева и имя красятся закреплённым за учеником
   тоном (--who ставит шаблон из seriesColors), поэтому сдачи одного
   человека видно одним взглядом — легенда, как у графика, не нужна */
.attempt-row--who { box-shadow: inset 3.3px 0 0 var(--who); padding-left: 15.4px; }
/* имя чуть уведено к тексту: чистый --warn на белом даёт контраст 3,7 —
   мало для 14px, а рант рядом несёт тон в полную силу и личность
   участника всё равно читается */
.attempt-who { color: color-mix(in srgb, var(--who) 70%, var(--text)); font-weight: 600; font-size: 14.3px; text-decoration: none; }
.attempt-who:hover { text-decoration: underline; }
/* секция пробника в статистике группы: подзаголовок над своей карточкой
   полосок — без него сдачи разных пробников шли одним списком и
   сливались (правка владельца 21.08) */
.probnik-group + .probnik-group { margin-top: 17.6px; }
.probnik-group-head { display: flex; align-items: baseline; gap: 8.8px; flex-wrap: wrap; margin: 0 0 6.6px 4.4px; }
.probnik-group-title { font-weight: 600; font-size: 15.4px; }
.probnik-group-meta { color: var(--text-2); font-size: 13.2px; }
.attempt-best { display: inline-flex; align-items: center; color: var(--best-gold); }
.attempt-best .ic { width: 26.4px; height: 26.4px; }
/* балл уезжает вправо: колонка баллов читается сверху вниз одним взглядом */
.attempt-head .spacer { flex: 1 1 auto; }
.attempt-errs { display: flex; align-items: center; gap: 6.6px; flex-wrap: wrap; margin-top: 6.6px; }
.attempt-errs .lbl { color: var(--text-2); font-size: 13.2px; margin-right: 4.4px; }
.attempt-errs a.badge { text-decoration: none; }
.attempt-errs a.badge:hover { outline: 1.1px solid var(--err); }
.attempt-train { margin-left: 8.8px; }

/* ── Варианты и попытки: спокойный рабочий путь (этап 5B) ────────────
   Банк ведёт к составу, затем к попытке; решатель держит ответ раньше
   вторичных инструментов. Это только представление: режимы попытки,
   серверный счётчик, черновики и HTMX-фрагменты остаются прежними. */
.variant-bank-head .sub { max-width: 660px; }
.variant-bank-toolbar { position: relative; }
.variant-bank .filter-selects { align-items: end; }
.variant-bank .station-row { padding: 5.5px 0; }
.variant-bank .variant-year { margin-top: 30.8px; }
.variant-bank .variant-grid { grid-template-columns: repeat(auto-fill, minmax(246px, 1fr)); gap: 15.4px; }
.variant-bank .variant-card { min-height: 176px; padding: 15.4px; gap: 8.8px; }
/* Карточки читаются по горизонталям: название всегда резервирует две
   строки, даже если конкретный вариант помещается в одну. Иначе id и
   отпечаток сложности «прыгают» вверх между соседними плитками. */
.variant-bank .v-card-head { min-height: 39.9px; align-items: flex-start; }
.variant-bank .v-card-title { min-height: 39.9px; padding-right: 72px; }
.variant-bank .v-card-actions { gap: 8.8px; }
.variant-bank .v-card-actions .btn,
.variant-bank .assign-pop > summary { min-height: 37.4px; display: inline-flex; align-items: center; }
.variant-bank .v-status { top: 13.2px; right: 15.4px; }
.variant-bank [data-year-toggle]:focus-visible,
.variant-bank [data-kind-toggle]:focus-visible {
  outline: 2.2px solid var(--accent); outline-offset: 3.3px; border-radius: 4.4px;
}

.variant-object-header { margin-bottom: 30.8px; }
.variant-profile { padding: 24.2px; }
.variant-profile .diff-bars { min-height: 58px; align-items: end; }
.variant-detail .map-legend { row-gap: 8.8px; }
.variant-start {
  max-width: 704px; padding: 19.8px 22px; border: 1.1px solid var(--line);
  border-left: 3.3px solid var(--accent); border-radius: 8.8px;
  background: var(--frame-bg);
}
.variant-start .solve-row { gap: 13.2px; }
.variant-start .btn { min-height: 44px; }
.variant-start .station-toggle-control { min-height: 44px; padding: 0 8.8px; }
.variant-start .station-hint { line-height: 1.5; max-width: 620px; }
.variant-detail .attempt-list { margin-bottom: 30.8px; }

.variant-solve { padding-bottom: 44px; }
.variant-solve-status {
  padding: 11px 13.2px; border: 1.1px solid var(--line); border-radius: 8.8px;
  background: var(--frame-bg);
}
.variant-solve-status .btn,
.variant-solve .numnav,
.variant-solve .exambar .iconbtn,
.variant-solve .exambar .reviewbadge { min-height: 44px; min-width: 44px; }
.variant-solve-condition { margin-top: 4.4px; }
.variant-solve-condition .condition { margin-bottom: 0; }
.variant-solve-answer {
  margin-top: 17.6px; padding: 17.6px; border: 1.1px solid var(--line);
  border-radius: 8.8px; background: var(--surface);
}
.variant-solve-answer > .stat { margin-top: 0 !important; }
.variant-solve-answer .answerbar { margin-top: 15.4px; flex-wrap: wrap; }
.variant-solve-answer .answerbar .btn { min-height: 44px; }
.variant-solve-workspace {
  margin-top: 17.6px; padding-top: 15.4px; border-top: 1.1px solid var(--line);
}
.variant-solve-workspace-head {
  display: flex; align-items: baseline; gap: 11px; flex-wrap: wrap;
  margin-bottom: 11px; color: var(--text-2); font-size: 13.2px;
}
.variant-solve-workspace-head .tag-label {
  color: var(--text-2); font-size: 12.1px; letter-spacing: .08em;
}
.variant-solve-workspace .draft-row .btn { min-height: 44px; height: auto; }
.variant-solve .numcell:focus-visible,
.variant-solve .numnav:focus-visible,
.variant-solve .exambar .iconbtn:focus-visible { outline: 2.2px solid currentColor; outline-offset: 2.2px; }

.variant-result-head { margin-bottom: 30.8px; }
.variant-result-summary { border-left: 3.3px solid var(--accent); }
.variant-result-next { padding: 15.4px 17.6px; border: 1.1px solid var(--line); border-radius: 8.8px; background: var(--frame-bg); }
.variant-result-next .r-err-label { margin-bottom: 11px; }
.variant-result-table { max-width: 100%; }
.variant-result-table .table-dense { min-width: 660px; }
.variant-result .submit-solution > summary { min-height: 44px; display: inline-flex; align-items: center; }

.station-entry { padding-top: max(30.8px, env(safe-area-inset-top)); padding-bottom: max(30.8px, env(safe-area-inset-bottom)); }
.station-entry-kicker {
  font: 600 12.1px/1 "JetBrains Mono", ui-monospace, monospace;
  letter-spacing: .08em; color: var(--text-2);
}
.station-entry .choice-row > *,
.station-entry .bigbtn,
.station-entry .ghostbtn { min-height: 48px; }
.station-entry .err { margin: 0; }
.station-entry .step > p { line-height: 1.55; }

@media (max-width: 700px) {
  .variant-bank .variant-grid { grid-template-columns: minmax(0, 1fr); }
  .variant-bank .variant-card { min-height: 0; }
  .variant-bank .v-card-actions .btn,
  .variant-bank .assign-pop > summary { min-height: 44px; }
  .variant-bank .station-row { margin-left: 0; width: 100%; }
  .variant-object-header, .variant-result-head { margin-bottom: 22px; }
  .variant-profile, .variant-solve-answer { padding: 15.4px; }
  .variant-start { padding: 15.4px; }
  .variant-start .solve-row { align-items: flex-start; flex-direction: column; }
  .variant-solve .solve-top-actions { margin-left: 0; width: 100%; }
  .variant-solve .solve-top-actions .btn { flex: 1 1 160px; }
  .variant-solve .solve-nav-row { gap: 6.6px; }
  .variant-solve-workspace-head { align-items: flex-start; flex-direction: column; gap: 4.4px; }
  .variant-result-summary { gap: 19.8px; }
  .variant-result .result-mistakes .badges .badge { min-height: 44px; display: inline-flex; align-items: center; }
  .station-entry { align-items: stretch; text-align: left; padding-left: 17.6px; padding-right: 17.6px; }
  .station-entry .step, .station-entry .choice-row, .station-entry .reggroup { align-items: stretch; text-align: left; }
  .station-entry .choice-row { flex-direction: column; }
  .station-entry .choice-row > * { width: 100%; }
  .station-entry .grp input { width: 100%; max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
  .variant-bank *, .variant-detail *, .variant-solve *, .variant-result *, .station-entry * { scroll-behavior: auto; }
}

/* ── бейдж со сплошной заливкой ───────────────────────────────────────
   «решено» в ряду ответов диапазона 19-21 (правка владельца 12.08):
   мягкий фон .badge.ok рядом с ответами читался как бледная подсказка,
   а это итог задания. Текст — цветом фона страницы: в светлой теме он
   ложится на тёмно-зелёный, в тёмной — на светло-зелёный. */
.badge.badge--solid { color: var(--bg); background: var(--ok); }

/* ── вердикт проверки (правка владельца 20.08): раньше под кнопкой
   «Проверить» стояли два бейджа в строку — цветной ярлык и серая подпись, —
   и рядом с раскрывашкой «ПОПЫТКИ», где та же фраза повторялась крупным
   моноширинным, зона читалась как свалка. Теперь вердикт — одна полоса с
   цветным кантом слева: слово, пояснение, дальше ничего ── */
.verdict-host { margin-top: 14px; }
.verdict-host:empty { display: none; }
.verdict { display: flex; align-items: baseline; gap: 8.8px; flex-wrap: wrap;
  padding: 8.8px 13.2px; border-radius: 8.8px; border: 1.1px solid var(--line);
  border-left-width: 3.3px; background: var(--surface); }
.verdict b { font-size: 16.5px; font-weight: 650; }
.verdict span { color: var(--text-2); font-size: 14.3px; }
.verdict--ok { border-left-color: var(--ok); }
.verdict--ok b { color: var(--ok); }
.verdict--err { border-left-color: var(--err); }
.verdict--err b { color: var(--err); }
/* частичный балл 26/27: не победа и не провал — тёплый кант, как у любого
   «наполовину» в кабинете */
.verdict--part { border-left-color: var(--warn); }
.verdict--part b { color: var(--warn); }

/* «показать ответ» — кнопка и подпись в одной строке; подпись объясняет,
   почему второго предупреждения не будет */
.reveal-row { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; margin-top: 22px; }
.reveal-note { margin: 17.6px 0 8.8px; max-width: 62ch; }

/* ── лента попыток (спека отображения 12.08): что ученик вводил, чем
   кончилось и откуда. Один компонент на карточке задачи, экране попыток
   ученика и в раскрывашке таблицы ── */
.att-block { margin-top: 26.4px; }
/* шапка раскрывашки: «Попытки» обычным регистром и сводка рядом — капслок
   моноширинным спорил с самим вердиктом выше (правка владельца 20.08) */
.att-toggle { font-family: inherit; text-transform: none; letter-spacing: 0; font-size: 15.4px; }
.att-summary { color: var(--text-2); font-weight: 400; text-transform: none; letter-spacing: 0; margin-left: 8.8px; }
.att-feed { display: flex; flex-direction: column; gap: 4.4px; }
/* align-items: flex-start, а не center — у 25 и 27 ответ многострочный
   (см. .ans-lines ниже), и время с вердиктом должны стоять у ПЕРВОЙ строки
   ответа, а не уезжать в середину столбика */
.att-row { display: flex; align-items: flex-start; gap: 11px; padding: 7.7px 13.2px; border: 1.1px solid var(--line); border-radius: 8.8px; }
/* колонки выровнены: время фиксированной ширины, ответ тянется, источник
   прижат вправо — иначе строки «плясали» по длине ответа */
.att-time { flex: 0 0 88px; color: var(--text-2); font-size: 12.65px; line-height: 1.7; }
.att-given { flex: 1; overflow-wrap: anywhere; font-size: 15.4px; font-weight: 550; }
.att-row .att-src { margin-left: auto; text-align: right; }

/* ── матричный ответ печатается СТРОКАМИ (правка владельца 15.08): у 25 и
   27 ответ — таблица пар, а HTML схлопывал переводы строк в ленту цифр
   «200004 4 200034 6 …», где не видно границы пары. Правило одно на все
   места, где показывают ответ (лента попыток, эталон на карточке задачи);
   разбор варианта держит свой .ans-cell — там ещё и геометрия таблицы ── */
.ans-lines { white-space: pre-line; line-height: 1.5; }
.att-sub, .att-src { color: var(--text-2); font-size: 13.2px; }
/* заход, закрытый сбросом репетитора: история, а не текущая работа */
.att-run.is-archived { opacity: .6; }
.att-run-sep { color: var(--text-2); font-size: 13.2px; margin: 8.8px 0 4.4px; }
/* вход «попытки» в строке состава набора */
.hw-task-att { font-size: 13.2px; white-space: nowrap; }

/* «до этого …» — сводка архивных заходов адресата рядом со статусом
   пункта: приглушённая, чтобы не спорить со статусом; у личного набора
   это ссылка на попытки, у группового — счёт участников без ссылки.
   Живёт на карточке набора и на карточке урока (пересборка 22.08). */
.hw-prior {
  flex: none; font-size: 13.2px; color: var(--text-2); white-space: nowrap;
  border: 1.1px solid var(--line); border-radius: 999px; padding: 2.2px 8.8px;
  text-decoration: none;
}
a.hw-prior:hover { color: var(--text); border-color: var(--accent); }
.hw-prior--group { cursor: default; }

/* полоса прогресса набора: решённое акцентом, «смотрел ответ»
   приглушённым. Одна цифра «2/3» уравнивала решение и просмотр ответа, а
   пункт закрывает только верный ответ (спека попыток 11.08). Ленты уроков
   и карточка урока рисуют её одинаково. */
.lf-bar { flex: 0 1 176px; min-width: 88px; height: 6.6px; border-radius: 3.3px; background: var(--frame-bg); overflow: hidden; display: flex; }
.lf-bar i { display: block; height: 100%; }
.lf-bar-solved { background: color-mix(in srgb, var(--accent) 60%, var(--accent-mid)); }
.lf-bar-revealed { background: var(--cell-touched); }
/* клетка матрицы урока ведёт на попытки ученика (спека отображения 12.08) */
.lt-cell-link { color: inherit; text-decoration: none; display: block; }
.lt-cell-link:hover { color: var(--accent); }
/* горизонтальная прокрутка широкой таблицы: канонизировано при втором
   пользователе (карточка ученика + таблица попыток) */
.table-scroll { overflow-x: auto; }
/* фильтры таблицы попыток — одна строка, как в очереди ревью */
.att-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 11px; margin-bottom: 17.6px; }
.att-check { display: flex; align-items: center; gap: 6.6px; color: var(--text-2); font-size: 14.3px; }
/* пустая раскрывашка строки места не занимает */
.att-drawer:empty { display: none; }
/* ряд действий под лентой: «открыть задачу» и сброс захода */
.att-actions { display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap; margin-top: 22px; }

/* ── строка события внимания: общий партиал attnrow у двух экранов —
   очереди /attention и очереди «РАЗОБРАТЬ» на главной репетитора
   (пересборка 15.08). Скоуп .list-row обязателен: одноимённый .att-row
   ленты попыток выше — другой компонент, и правила смешивать нельзя.

   Пересборка 21.08: текст в две строки — «кто · что» сверху, возраст
   приглушённой строкой ниже. Одной строкой субъект, суть и дата сливались,
   и очередь читалась медленно. У ученических строк вместо аватара — диск
   вида события, цвет по смыслу: дедлайн тревожный, ревью хорошая новость,
   пробник — «сядь и реши» ── */
.list-row.att-row { gap: 13.2px; }
.att-text { display: flex; flex-direction: column; gap: 2.2px; }
.att-line { display: flex; align-items: baseline; gap: 7.7px; flex-wrap: wrap; }
.att-who { font-weight: 600; }
.att-what { color: var(--text-2); }
.att-what.task-id { color: var(--accent); }
.att-meta { color: var(--text-2); font-size: 13.2px; }
.att-kind {
  width: 33px; height: 33px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
}
.att-kind .ic { width: 15.4px; height: 15.4px; }
.att-kind--deadline { color: var(--err); background: var(--err-soft); }
.att-kind--review { color: var(--ok); background: var(--ok-soft); }
.att-kind--probnik { color: var(--warn); background: var(--warn-soft); }
/* бейдж и действие — колонки постоянной ширины (пересборка 19.08): без них
   «просрочено» и «ждёт» стояли на разной горизонтали в каждой строке, и
   очередь приходилось читать построчно */
.att-badge-col { flex: 0 0 99px; display: flex; justify-content: flex-end; }
.att-act { flex: 0 0 auto; min-width: 176px; display: flex; justify-content: flex-end; gap: 6.6px; }
@media (max-width: 700px) {
	.att-badge-col, .att-act { flex: none; min-width: 0; }
}

/* ── сокращённая строка внимания (партиал attnmini, блок «ТРЕБУЕТ
   ВНИМАНИЯ» главной репетитора, правка владельца 21.08): одна строка,
   точка цветом бейджа, вся строка — ссылка; кнопок нет, разбор — на
   полной странице /attention ── */
.att-mini { gap: 11px; padding: 8.8px 13.2px; min-height: 0; }
.att-mini-copy { min-width: 0; }
.att-mini-main { display: block; }
.att-mini-meta { display: block; }
.att-mini .att-line { row-gap: 2.2px; }
.att-dot { width: 8.8px; height: 8.8px; border-radius: 50%; flex: none; }
.att-dot--err { background: var(--err); }
.att-dot--warn { background: var(--warn); }
.att-dot--acc { background: var(--accent); }
.att-dot--ok { background: var(--ok); }
/* на телефоне бейдж не зажимает текст в столбик, а уходит второй строкой
   под него (отступ = точка 8.8 + зазор 11) */
@media (max-width: 640px) {
  .att-mini { flex-wrap: wrap; row-gap: 4.4px; }
  .att-mini .lr-text { flex: 1 1 calc(100% - 44px); }
  .att-mini .badge { margin-left: 19.8px; }
}

/* Меню разделов раскрывается нативным details: работает и с касанием,
   и с клавиатурой, без отдельной логики наведения. */
.nav-drop { position: relative; display: inline-flex; align-items: center; }
.nav-drop-top { cursor: pointer; list-style: none; color: var(--text-2); }
.nav-drop-top::-webkit-details-marker { display: none; }
.nav-drop-top:hover, .nav-drop[open] > .nav-drop-top { color: var(--accent); }
.nav-drop-top:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 4px; border-radius: 3px; }
.nav-drop.active > .nav-drop-top { color: var(--text); font-weight: 550; }
/* стрелка вниз — рисуем псевдоэлементом, чтобы не заводить иконку ради
   восьми пикселей */
.nav-drop-top::after {
  content: ""; width: 0; height: 0; margin-left: 5.5px;
  border-left: 3.3px solid transparent; border-right: 3.3px solid transparent;
  border-top: 3.85px solid currentColor; opacity: .7;
}
.nav-drop-panel {
  position: absolute; top: 100%; left: -13.2px; z-index: 60;
  display: none; flex-direction: column; gap: 2px; min-width: 190px;
  margin-top: 8.8px; padding: 6px; border: 1.1px solid var(--line);
  border-radius: 11px; background: var(--surface); box-shadow: 0 8.8px 26.4px rgba(0,0,0,.18);
}
.nav-drop[open] > .nav-drop-panel { display: flex; }
.nav-drop-panel a { white-space: nowrap; padding: 8px 10px; border-radius: 6px; }
.nav-drop-panel a:hover, .nav-drop-panel a:focus-visible { background: var(--accent-soft); }
/* В бургер-панели раскрытые разделы остаются в потоке. */
.burger .panel .nav-drop { display: block; }
.burger .panel .nav-drop-panel {
  position: static; display: none; margin: 2px 0 8px 13.2px; padding: 0;
  border: none; box-shadow: none; background: transparent; min-width: 0;
}
.burger .panel .nav-drop[open] > .nav-drop-panel { display: flex; }
.burger .panel .nav-drop-top {
  display: block; width: 100%; padding: 8.8px 11px; font-size: 15.4px;
  border: 0; border-radius: 6px; color: var(--text); text-align: left;
}
.burger .panel .nav-drop[open] > .nav-drop-top { background: var(--accent-soft); color: var(--accent); }
.burger .panel .nav-drop-top::after { float: right; margin-top: 8px; }

/* ── ссылка-приглашение с кнопкой копирования (аудит 16.08): адрес
   показываем целиком (схема+хост), чтобы его можно было переслать, и
   даём кнопку рядом. Длинный токен ломает модалку — поэтому перенос по
   любому символу, а не по словам ── */
.invite-link { display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap; }
.invite-link code { word-break: break-all; font-size: 13.2px; color: var(--text-2); }

/* ── поиск внутри выпадашки фильтра (аудит 16.08): тегов в банке сотни, и
   плоский список без строки-фильтра нечитаем. Скрытые поиском опции и
   группы гасим явным guard'ом — у .ms-option свой display:flex, и голый
   [hidden] его не перебивает ── */
.ms-search { margin: 0 6.6px 8.8px; padding: 6.6px 8.8px; font-size: 14.3px; }
.ms-option[hidden], .ms-group[hidden] { display: none; }

/* ── знак recursum в шапке ─────────────────────────────────────────────
   Растр в двух вариантах: тёмная тушь и золото. Тема зрителя решает, кто
   виден: на светлом фоне золото выцветает, на тёмном тушь тонет. Знак
   вместо прежней сетки точек (.logo-mark), поэтому размер задан здесь ── */
.logo-img { width: 38px; height: 38px; display: block; flex: none;
   /* знак растёт, шапка — нет: отрицательные поля съедают прибавку, иначе
      строка шапки становится выше кнопок справа (правка владельца 17.08) */
   margin: -5px 0; }
.logo-img--dark { display: none; }
[data-theme="dark"] .logo-img { display: none; }
[data-theme="dark"] .logo-img--dark { display: block; }

/* ── МОБИЛЬНАЯ ШИРИНА 375 (аудит 17.08) ────────────────────────────────
   Четыре места, где десктопная раскладка не складывалась на телефоне.
   Правила живут в прод-дельте, а не в ядре: core.css синхронизируется с
   мокапом, а мокапов под 375 нет ── */

/* а) строка события внимания (партиал attnrow: очередь /attention и блок
   РАЗОБРАТЬ на главной репетитора). В одну строку 375 px не влезали ни
   бейдж, ни кнопка: бейдж «7/100» ложился поверх имени, а «Посмотреть
   результат» уезжала за правый край. Переносим: текст занимает строку
   целиком, бейдж и действие идут второй. Аватар и диск вида на этой
   ширине прячем: колонка в 33px отжимала текст, строки раздувались втрое,
   а имя жирным — и так якорь чтения */
@media (max-width: 640px) {
  .list-row.att-row { flex-wrap: wrap; gap: 6.6px 11px; }
  .list-row.att-row .avatar, .list-row.att-row .att-kind { display: none; }
  .list-row.att-row .lr-text { flex: 1 1 100%; }
  .list-row.att-row .btn { margin-left: auto; }
  .list-row.att-row .att-act { margin-left: auto; }
}

/* б, в) карта 27 на телефоне: 9 колонок по 44 px с зазорами — это 466 px,
   и на 375 карта уезжала в горизонтальную прокрутку, обрезая клетки 6, 15
   и 24 ровно посередине. Отдаём ширину сетке: колонки делят её поровну,
   квадратность держит aspect-ratio ядра */
@media (max-width: 480px) {
  .map27 { grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 4.4px; overflow-x: visible; }
  .map27 span, .map27 a { font-size: 12.1px; }
  /* фасет НОМЕР в «Моих работах»: своя сетка по 33 px плюс подпись слева
     не оставляли места последнему столбцу — подпись уходит на свою строку,
     клетки делят ширину */
  .tt-frow--map { flex-direction: column; align-items: stretch; }
  .tt-frow--map .tt-flabel { width: auto; }
  .tt-map { flex: 1 1 auto; }
  /* специфичность выше, чем у правила в <style> самой страницы: инлайновый
     блок идёт в <head> ПОСЛЕ prod.css, и при равном весе побеждал бы он */
  .tt-frow--map .tt-map .map27 { grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 3.3px; }
}

/* г) фасет ИСХОД в «Моих работах» переносится, а не прокручивается: чипы
   видов над ним переносятся, и две соседние строки фильтров вели себя
   по-разному. Ядро на телефоне пускает .chip-row в горизонтальный скролл
   (так задумана шапка) — здесь это перебиваем адресно */
@media (max-width: 700px) {
  .tt-facets .chip-row { flex-wrap: wrap; overflow-x: visible; }
}

/* ── сравнение тарифов (партиал plancompare.html, правка владельца 19.08):
   две карточки со списком возможностей, у каждой строки — подробное
   пояснение по наведению. Живёт в prod.css, а не в <style> страницы:
   компонент общий у /pricing и /for/tutor.

   Строка — .tip-host ядра, поэтому позиционирование пузыря уже есть; здесь
   только геометрия строки, метки состояния и то, чего у ядра нет: пузырь
   у нижних строк уезжал бы за карточку, поэтому у длинных списков он
   прижимается к правому краю на узком экране (см. медиа ниже). ── */
/* шапки карточек выравниваются между собой (subgrid): подпись Премиума
   длиннее на строку, и без этого списки возможностей ехали по вертикали —
   сравнивать построчно было нельзя. Браузер без subgrid просто получит
   две auto-строки внутри карточки, вёрстка не ломается */
.pcmp { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-rows: auto auto; gap: 17.6px; margin-top: 19.8px; align-items: start; }
@media (max-width: 700px) {
  .pcmp { grid-template-columns: minmax(0, 1fr); }
  .pcmp-cta { white-space: normal; line-height: 1.3; }
}
.pcmp-card { display: grid; grid-row: span 2; grid-template-rows: subgrid; gap: 13.2px; align-content: start; }
.pcmp-card--premium { border-color: var(--accent); }
.pcmp-head { display: flex; flex-direction: column; gap: 6.6px; }
.pcmp-name { font-weight: 650; font-size: 17.6px; }
.pcmp-price { font-family: "JetBrains Mono", monospace; font-size: 30.8px; font-weight: 700; letter-spacing: -.01em; }
.pcmp-period { font-size: 14.3px; font-weight: 500; color: var(--text-2); }
.pcmp-sub { color: var(--text-2); font-size: 14.3px; line-height: 1.6; }
.pcmp-cta { margin-top: 4.4px; text-align: center; }
.pcmp-list { list-style: none; margin: 0; padding: 0; border-top: 1.1px solid var(--line); }
/* строка: метка состояния + текст. Курсор help — подсказка о том, что у
   строки есть что рассказать (у .tip-host ядра стоит default) */
.pcmp-row { display: flex; align-items: flex-start; gap: 8.8px; padding: 8.8px 0; border-bottom: 1.1px solid var(--line); font-size: 14.3px; line-height: 1.5; cursor: help; }
.pcmp-row:hover, .pcmp-row:focus { outline: none; background: var(--surface-2); }
.pcmp-mark { flex: 0 0 auto; margin-top: 1.1px; }
.pf--yes .pcmp-mark { color: var(--ok); }
/* закрытая строка приглушена целиком: список Базового читается как «вот
   это есть, а вот это под замком», без второй колонки крестиков */
.pf--no { color: var(--text-2); }
.pf--no .pcmp-mark { color: var(--line); }
.pcmp-text { flex: 1 1 auto; }
.pcmp-note { color: var(--text-2); font-size: 12.65px; white-space: nowrap; }
.pcmp-tip { font-size: 13.2px; line-height: 1.6; color: var(--text-2); font-weight: 400; }
.pcmp-tip b { color: var(--text); font-weight: 600; }
.pcmp-foot { color: var(--text-2); font-size: 13.2px; margin-top: 13.2px; }
/* пузырь шире карточки на телефоне выходил за экран правым краем: прижимаем
   его к левому краю окна ширины строки */
@media (max-width: 700px) {
  .pcmp-row .tip-bubble { left: 0; right: 0; width: auto; max-width: none; }
}

/* ── тариф репетитора: карточка /account и апселл страницы-гейта
   (пересборка 19.08). До этого план описывался абзацем в /account, а
   отказ гейта — двумя строками текста: понять, за что доплата, можно было
   только упершись в неё. Состав строк — planFeatures (тот же справочник,
   что у таблицы /pricing), поэтому апселл не обещает больше, чем гейты
   закрывают ── */
.plan-card { display: flex; flex-direction: column; gap: 17.6px; }
.plan-now { display: flex; align-items: flex-start; justify-content: space-between; gap: 13.2px; flex-wrap: wrap; }
.plan-name { font-size: 19.8px; font-weight: 650; letter-spacing: -.01em; }
.plan-sub { color: var(--text-2); font-size: 14.3px; margin-top: 3.3px; }
.plan-more { border-top: 1.1px solid var(--line); padding-top: 15.4px; }
.plan-more-head { font-size: 14.3px; font-weight: 600; margin-bottom: 11px; }
.plan-more-head .mono { color: var(--accent); }
/* две колонки на широком экране: список из шести пунктов столбиком
   выглядел длиннее, чем есть, и карточка тарифа занимала экран */
.plan-list { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(264px, 1fr)); gap: 6.6px 22px; }
.plan-list li { position: relative; padding-left: 22px; color: var(--text-2); font-size: 14.85px; }
.plan-list li::before { content: "+"; position: absolute; left: 4.4px; color: var(--accent); font-weight: 650; }
.plan-acts { display: flex; align-items: center; gap: 13.2px; flex-wrap: wrap; }

/* ── тарифы v3: ставки за место, счёт кабинета, калькулятор, школьный
   режим. Карточки сравнения (.pcmp-*) выше показывают «от N ₽ за ученика»:
   включённого пула больше нет, цена — сумма ставок по местам ── */
/* ступени ставки под ценой: цена на карточке — первые пять мест, дальше
   место дешевеет, и об этом надо сказать рядом с числом (правка владельца 23.08) */
.pcmp-steps { color: var(--text-2); font-size: 13.2px; margin-top: -2.2px; }
/* подпись периода — текстовым шрифтом: моно под цифрой читался как код */
.pcmp-price .pcmp-period { display: block; font-family: "Inter", system-ui, sans-serif; font-size: 13.2px; font-weight: 500; letter-spacing: 0; margin-top: 2.2px; }
/* дельта Премиума словами («+300 ₽/мес для ваших 2 учеников») — длинная
   фраза, в моно переносилась по буквам */
.plan-delta { color: var(--accent); font-weight: 600; }

/* состав и счёт в карточке тарифа /account и в админке */
.acc-invite { display: flex; flex-direction: column; gap: 8.8px; align-items: flex-start; }
.admin-sub-form { display: flex; align-items: center; gap: 6.6px; flex-wrap: wrap; }
.plan-bill { border-top: 1.1px solid var(--line); padding-top: 13.2px; display: flex; flex-direction: column; gap: 6.6px; }
.plan-bill-row { display: flex; justify-content: space-between; gap: 13.2px; font-size: 14.3px; }
.plan-bill-row.quiet { font-size: 13.2px; }
.plan-bill-total { margin-top: 4.4px; padding-top: 8.8px; border-top: 1.1px dashed var(--line); font-size: 15.4px; }
.plan-bill-total b { font-size: 17.6px; }

/* правила счёта — четыре карточки в ряд */
.pricing-rules { display: grid; grid-template-columns: repeat(auto-fit, minmax(242px, 1fr)); gap: 13.2px; }
.pricing-rules .rule { display: flex; flex-direction: column; gap: 6.6px; margin-top: 0; }
.pricing-rules .rule p { color: var(--text-2); font-size: 13.75px; line-height: 1.6; margin: 0; }
.rule-head { font-weight: 650; font-size: 15.4px; letter-spacing: -.01em; }

/* раскладка «сколько это стоит»: суммы по числу учеников. Цифры
   моноширинные и по правому краю, средняя цена места приглушена — она тут
   главное доказательство, что место дешевеет (правка владельца 23.08) */
.ptable-wrap { overflow-x: auto; }
.ptable { width: 100%; border-collapse: collapse; font-size: 14.3px; }
.ptable th, .ptable td { padding: 8.8px 11px; text-align: right; white-space: nowrap; border-bottom: 1.1px solid var(--line); }
.ptable th:first-child, .ptable td:first-child { text-align: left; }
.ptable thead th { color: var(--text-2); font-size: 12.65px; font-weight: 600; letter-spacing: .02em; }
.ptable tbody tr:last-child td { border-bottom: 0; }
.ptable tbody tr:hover { background: var(--surface-2); }
.ptable-sum { font-weight: 650; }
.ptable-avg { color: var(--text-2); font-size: 13.2px; }
.ptable-foot { color: var(--text-2); font-size: 13.2px; margin: 13.2px 0 0; }

/* калькулятор: поля в ряд, результат тремя ячейками */
.calc { margin-top: 0; }
.calc-form { display: flex; align-items: flex-end; gap: 13.2px; flex-wrap: wrap; }
.calc-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 11px; flex: 1 1 640px; }
.calc-fields .field span { display: block; color: var(--text-2); font-size: 12.65px; margin-bottom: 4.4px; }
.calc-fields .input { width: 100%; }
.calc-submit { flex: none; }
.calc-result { display: grid; grid-template-columns: repeat(auto-fit, minmax(198px, 1fr)); gap: 13.2px; margin-top: 17.6px; }
.calc-cell { border: 1.1px solid var(--line); border-radius: var(--r, 12px); padding: 13.2px 15.4px; display: flex; flex-direction: column; gap: 4.4px; }
.calc-cell--prem { border-color: var(--accent); background: var(--accent-soft); }
.calc-cap { color: var(--text-2); font-size: 12.65px; }
.calc-val { font-size: 26.4px; font-weight: 700; letter-spacing: -.01em; line-height: 1.1; }
.calc-note { color: var(--text-2); font-size: 13.2px; }

/* школьный режим: цена слева, условия справа */
.school { display: grid; grid-template-columns: minmax(220px, 290px) minmax(0, 1fr); gap: 26.4px; align-items: start; }
@media (max-width: 760px) { .school { grid-template-columns: 1fr; } }
.school-price { display: flex; flex-direction: column; gap: 6.6px; padding: 15.4px; border-radius: var(--r, 12px); background: var(--surface-2); }
.school-name { font-size: 17.6px; font-weight: 650; letter-spacing: -.01em; margin-bottom: 4.4px; }
.school-sum { display: flex; align-items: baseline; gap: 8.8px; }
.school-sum b { font-size: 30.8px; font-weight: 700; letter-spacing: -.01em; }
.school-sum span { color: var(--text-2); font-size: 13.2px; }
.school-plus { color: var(--text-2); font-size: 22px; line-height: 1; padding-left: 2.2px; }
.school-year { color: var(--text-2); font-size: 13.2px; line-height: 1.5; margin-top: 4.4px; }
.school-example { font-size: 13.75px; font-weight: 650; color: var(--accent); }
.school-body { display: flex; flex-direction: column; gap: 13.2px; }
.school-lead { margin: 0; font-size: 14.85px; line-height: 1.6; }
.school-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6.6px; }
.school-list li { position: relative; padding-left: 19.8px; color: var(--text-2); font-size: 14.3px; line-height: 1.55; }
.school-list li::before { content: ""; position: absolute; left: 4.4px; top: 9.9px; width: 6.6px; height: 6.6px; border-radius: 50%; background: var(--accent); }
.school-list li b { color: var(--text); font-weight: 600; }
.school-acts { display: flex; gap: 8.8px; flex-wrap: wrap; }

/* ── Корзина сбора дз (спека 2026-08-21-hw-basket-design) ──────────────
   Липкая колонка рядом с каталогом: состав компактный, каталог остаётся
   карточками с условиями. Стили общие, потому что панель — партиал
   (basket.html), у партиала своего <style> нет. */
.bsk { position: sticky; top: 88px; align-self: start; }
.bsk-card { padding: 15.4px; display: flex; flex-direction: column; gap: 11px; }
.bsk-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8.8px; }
.bsk-title { font-weight: 650; }
.bsk-count { font-family: "JetBrains Mono", monospace; font-size: 13.2px; color: var(--text-2); }
.bsk-dest { font-size: 13.2px; color: var(--accent); font-weight: 550; }
.bsk-list { display: flex; flex-direction: column; gap: 4.4px; max-height: 46vh; overflow-y: auto; }
/* «уже в плане / в дз / в шаблоне» — состав ЦЕЛИ, а не корзины: тише
   набираемого, но читаемо, и отделено линией (правка владельца 23.08) */
/* переключатель режимов сбора и ручка перетаскивания (правка владельца
   23.08): порядок правят перетаскиванием, стрелок больше нет */
.bsk-modes { width: 100%; }
.bsk-grip { flex: none; color: var(--text-2); cursor: grab; font-size: 13.2px;
  line-height: 1; user-select: none; touch-action: none; }
.bsk-grip:active { cursor: grabbing; }
.bsk-drag-row { border-radius: 4.4px; }
.bsk-dragging { opacity: .6; background: var(--surface-2); }
.bsk-have { display: flex; flex-direction: column; gap: 4.4px; padding-bottom: 8.8px;
  border-bottom: 1px solid var(--line); max-height: 30vh; overflow-y: auto; }
.bsk-have-head { font-size: 11.55px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-2); }
.bsk-have-row .bsk-label { color: var(--text-2); }
.bsk-have-row .bsk-label:hover { color: var(--accent); }
.bsk-row { display: flex; align-items: center; gap: 6.6px; padding: 3.3px 0; }
/* цветная метка уровня вместо слова: в колонке 300px подпись «повышенный»
   вытесняла бы id задачи */
.bsk-lvl { flex: none; width: 4.4px; height: 15.4px; border-radius: 2.2px; background: var(--line); }
.bsk-lvl.lvl-1 { background: var(--d1); }
.bsk-lvl.lvl-2 { background: var(--d2); }
.bsk-lvl.lvl-3 { background: var(--d3); }
.bsk-lvl.lvl-4 { background: var(--grob); }
.bsk-label { flex: 1; min-width: 0; font-family: "JetBrains Mono", monospace; font-size: 12.65px;
  color: var(--text); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bsk-label:hover { color: var(--accent); }
.bsk-gone { color: var(--text-2); text-decoration: line-through; }
.bsk-acts { display: flex; align-items: center; gap: 2.2px; flex: none; }
.bsk-btn { background: none; border: 0; color: var(--text-2); cursor: pointer; font-size: 13.2px;
  line-height: 1; padding: 3.3px 4.4px; border-radius: 4.4px; }
.bsk-btn:hover:not(:disabled) { background: var(--frame-bg); color: var(--text); }
.bsk-btn:disabled { opacity: .3; cursor: default; }
.bsk-x:hover:not(:disabled) { color: var(--err); }
.bsk-empty { color: var(--text-2); font-size: 13.2px; line-height: 1.45; }
.bsk-tools { display: flex; align-items: center; gap: 13.2px; border-top: 1.1px solid var(--line); padding-top: 8.8px; }
.bsk-link { background: none; border: 0; padding: 0; cursor: pointer; color: var(--accent);
  font-size: 13.2px; font-family: inherit; }
.bsk-link:hover { text-decoration: underline; }
.bsk-form { display: flex; flex-direction: column; gap: 8.8px; border-top: 1.1px solid var(--line); padding-top: 11px; }
.bsk-field { display: flex; flex-direction: column; gap: 3.3px; }
.bsk-lab { font-size: 12.1px; color: var(--text-2); }
.bsk-check { font-size: 13.2px; }
.bsk-go { width: 100%; }
.bsk-more { font-size: 13.2px; }
.bsk-more summary { color: var(--text-2); cursor: pointer; }
.bsk-more > *:not(summary) { margin-top: 6.6px; }
.bsk-more input.input { width: 100%; }
/* кнопка «+ в набор» на карточке каталога: форма, потому что правка
   корзины — POST (без JS работает так же, просто с перезагрузкой) */
.tf-basket { display: inline-flex; }
/* пункт корзины прячется, пока она пуста; guard [hidden] обязателен —
   у ссылок шапки свой display, и он бьёт атрибут */
.nav-basket[hidden] { display: none; }
/* счётчик корзины в шапке: пока набор не выдан, к сбору можно вернуться */
.nav-basket-count { font-family: "JetBrains Mono", monospace; }
@media (max-width: 900px) {
  .bsk { position: static; }
  .bsk-list { max-height: none; }
}

/* ── видео урока строками (миграция 0032, запрос владельца 23.08): их
   несколько и у каждого своё название. Форма — пары «название + ссылка»;
   название уже, чем ссылка: имя короткое, а ссылка длинная и её читают
   целиком. Крестик убирает строку, «+ ещё видео» добавляет пустую
   (cabinet.js, [data-video-rows]). Общий вид на четырёх экранах: карточка
   урока, конспект, шаблон урока, форма нового урока ── */
/* группа строк — свой отступ снизу: под ней идёт следующее поле формы */
.vid-field { gap: 4.4px; }
.vid-rows { display: flex; flex-direction: column; gap: 6.6px; }
.vid-row { display: flex; align-items: center; gap: 6.6px; }
.vid-row .vid-title { flex: 0 1 220px; min-width: 0; }
.vid-row .vid-url { flex: 1 1 260px; min-width: 0; }
.vid-drop {
  flex: none; padding: 5.5px 9.9px; line-height: 1; font-size: 15.4px;
  color: var(--text-2); border-color: transparent;
}
.vid-drop:hover { border-color: var(--err); color: var(--err); }
.vid-hint { color: var(--text-2); font-size: 13.2px; margin-bottom: 2.2px; }
.vid-add { align-self: flex-start; margin-top: 6.6px; }
.lm-files { display: flex; flex-direction: column; gap: 8.8px; margin-top: 6.6px; }
.lm-file-row { display: flex; align-items: center; gap: 11px; }
.lm-file-row .lm-file-title { flex: 1 1 240px; min-width: 0; }
.lm-file-row .lm-file-name {
  flex: 0 1 auto; min-width: 0; color: var(--text-2); font-size: 13.2px;
  overflow-wrap: anywhere;
}
.lm-file-drop {
  flex: none; padding: 5.5px 9.9px; line-height: 1; font-size: 15.4px;
  color: var(--text-2); border-color: transparent;
}
.lm-file-drop:hover { border-color: var(--err); color: var(--err); }
.lh-materials {
  display: flex; flex-direction: column; align-items: flex-start; gap: 13.2px;
  margin-bottom: 22px;
}
.lh-materials .materials-row { gap: 8.8px 11px; }
@media (max-width: 560px) {
  /* на телефоне пара уходит в две строки: 220px названия и ссылка рядом
     не помещаются, и оба поля становились нечитаемо узкими */
  .vid-row { flex-wrap: wrap; }
  .vid-row .vid-title { flex: 1 1 100%; }
  .vid-row .vid-url { flex: 1 1 calc(100% - 46.2px); }
  .lm-file-row { flex-wrap: wrap; }
  .lm-file-row .lm-file-title { flex-basis: 100%; }
}

/* ── модалка «Добавить ученика»: способ = блок с именем (пересборка 23.08).
   Раньше здесь стояли подряд четыре формы без подписей — три способа
   завести ученика и создание группы, — и выбрать было не из чего, потому
   что выбор не был назван. Теперь у каждого способа заголовок, строка
   объяснения и один ряд контролов; группа уехала в свою модалку. ── */
.add-ways { display: flex; flex-direction: column; gap: 19.8px; }
.add-way + .add-way { border-top: 1.1px solid var(--line); padding-top: 19.8px; }
.add-way h3 { font-size: 15.4px; font-weight: 600; letter-spacing: -.01em; }
.add-way-note { color: var(--text-2); font-size: 13.2px; line-height: 1.5; margin: 3.3px 0 11px; }
.add-way-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8.8px; }
/* .input в ядре — width:100%, в ряду он растянул бы строку на себя одного:
   поле тянется флексом, выпадашка и кнопка держат свою ширину */
.add-way-row .input { flex: 1 1 140px; width: auto; min-width: 0; }
.add-way-row select.input { flex: 0 0 auto; width: auto; }
.add-way-row .btn { flex: none; }
/* ссылка-приглашение стоит НАД своей формой: сперва то, что копируют */
.add-way .invite-link { margin-bottom: 8.8px; }
.add-way-foot { margin: 11px 0 0; }
.modal--narrow { max-width: 400px; }
@media (max-width: 560px) {
  /* на телефоне поле и кнопка в строку не помещаются: поле сжималось до
     нечитаемого, а кнопка переносилась в одиночку. flex-basis в колонке
     задаёт ВЫСОТУ — без сброса поле вытягивалось на 140px */
  .add-way-row { flex-direction: column; align-items: stretch; }
  .add-way-row .input, .add-way-row select.input { flex: 0 0 auto; width: 100%; }
  .add-way-row .btn { width: 100%; }
}

/* Оплата кабинета (эпик G-2): кнопки периода стоят рядом с «сравнить
   тарифы», строка про оферту — под ними мелко и спокойно. */
.plan-pay { display: inline-flex; }
.plan-legal { margin-top: 8.8px; font-size: 13.2px; line-height: 1.5; }

/* Раскрывашка секции (.fold) — переехала из <style> карточки ученика
   (правка владельца 23.08): блоки «что решал» и «последние работы» живут
   отдельным партиалом, и стиль общего компонента обязан быть общим. */
.fold { margin-top: 22px; }
.fold > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 11px; margin-bottom: 13.2px;
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary .section-label { margin: 0; }
.fold > summary:hover .section-label { color: var(--accent); }
.fold-note { color: var(--text-2); font-size: 13.75px; }
.fold .spacer { flex: 1; }
.fold-caret { color: var(--text-2); font-size: 13.2px; transition: transform .12s, color .12s; }
.fold[open] > summary .fold-caret { color: var(--accent); transform: rotate(180deg); }

/* Очередь ожидания места и назначенные изменения тарифа (эпик G-2). */
.plan-wait { margin-top: 13.2px; padding-top: 11px; border-top: 1.1px solid var(--line); }
.plan-wait-head { font-size: 13.2px; color: var(--text-2); margin-bottom: 6.6px; }
.plan-wait-row { display: flex; align-items: center; justify-content: space-between; gap: 11px; padding: 4.4px 0; }
.plan-wait-acts { display: inline-flex; gap: 6.6px; align-items: center; }
.plan-pending { margin-top: 11px; display: flex; align-items: center; gap: 8.8px; font-size: 14.3px; color: var(--text-2); }

/* Комната — рабочее пространство: задача и код рядом, доски на весь урок. */
.lesson-room { max-width: 1584px; }
.lesson-room .page-head { margin: 24px 0 20px; }
.lesson-room .page-head h1 { font-size: 28px; line-height: 1.2; }
.lesson-room .room-bar { padding: 0 0 20px; border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; flex-wrap: wrap; gap: 12px; }
.lesson-room .room-bar .section-label { font-family: inherit; letter-spacing: 0; text-transform: none; }
.lesson-room .segment button { white-space: normal; }
.lesson-boards { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 20px 0; }
.lesson-boards-heading { display: grid; gap: 4px; font-size: 15px; }
.lesson-board-links { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.lesson-board-links .task-board { margin: 0; }
.lesson-board-links [data-board-open] { border: 1px solid var(--line); background: var(--surface); min-height: 40px; }
.lesson-room .room-strip { padding: 12px 0 20px; margin: 0 0 24px; border-bottom: 1px solid var(--line); gap: 8px; }
.lesson-workspace { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 36px; align-items: start; }
.lesson-problem, .lesson-code { min-width: 0; }
.lesson-room .section-label { margin-top: 24px; }
.lesson-problem > .section-label:first-child, .lesson-code > .section-label:first-child { margin-top: 0; }
.lesson-room .condition { padding: 0; font-size: 16px; line-height: 1.7; overflow-wrap: anywhere; }
.lesson-room .condition table { max-width: 100%; }
.lesson-room .condition img { max-width: 100%; height: auto; }
.lesson-code { padding-left: 28px; border-left: 1px solid var(--line); }
.lesson-code .live-peer { padding: 0; margin: 24px 0; border: 0; background: transparent; box-shadow: none; }
.lesson-code .live-peer-head { gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.lesson-code .live-peer-head strong { font-size: 17px; }
.lesson-code .live-mine { flex-wrap: wrap; }
.lesson-code .live-peer-codebox { display: block; background: var(--surface); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.lesson-code .live-peer-codebox > .CodeMirror { display: block; height: auto; min-height: 160px; max-height: 440px; padding: 12px 0; }
.lesson-code .CodeMirror-scroll { max-height: 416px; }
.lesson-code .live-peer-out { padding: 12px 16px; margin: 0; border-top: 1px solid var(--line); background: var(--frame-bg); }
.lesson-room .cw-auto-row { gap: 8px; }
.lesson-room .cw-auto-row [data-cw-answer] { min-width: 0; max-width: none; flex: 1 1 100%; }
.lesson-room .py-panel { margin-top: 12px; }
.lesson-room .py-live-bar { flex-wrap: wrap; gap: 8px; }
.lesson-room .board-stage .board-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lesson-room .board-bar [data-board-status] { font-size: 12px; max-width: 180px; }
@media (max-width: 1000px) {
  .lesson-workspace { grid-template-columns: 1fr; gap: 28px; }
  .lesson-code { padding: 24px 0 0; border-left: 0; border-top: 1px solid var(--line); }
  .lesson-boards { align-items: flex-start; flex-direction: column; }
}
@media (max-width: 560px) {
  .lesson-room .page-head h1 { font-size: 24px; }
  .lesson-room .room-bar > .section-label { width: 100%; }
  .lesson-room .room-bar .segment { display: flex; width: 100%; }
  .lesson-room .room-bar .segment button { flex: 1; padding: 8px; }
  .lesson-room .board-bar { flex-wrap: wrap; height: auto; gap: 6px; padding: 8px; }
  .lesson-room .board-bar [data-board-status] { order: 3; width: 100%; max-width: none; }
  .lesson-room .board-title { max-width: 120px; }
  .lesson-room .board-tools { max-height: 70dvh; overflow-y: auto; }
  .lesson-room .marker-bar .quiet { display: none; }
}

body:has(.lesson-room) .classwork-banner .cw-plan { display: none; }

/* Файлы и показ кода в комнате занятия. */
[data-room-tutor-share][hidden] { display:none; }
[data-room-upload] { margin-top:22px; }
[data-room-upload] input[type="file"] { min-width:0; max-width:100%; flex:1; }
[data-room-files-host] .chip { max-width:100%; overflow-wrap:anywhere; white-space:normal; }
[data-board-task-select] { width:auto; max-width:200px; }
@media (max-width:700px) {
  [data-board-live-url] .board-bar { flex-wrap:wrap; }
  [data-board-live-url] .board-title { display:none; }
  [data-board-live-url] [data-board-status] { order:2; width:100%; }
  [data-board-task-select] { max-width:140px; }
}

@media (max-width: 700px) {
  .catalog-filters { padding: 15.4px; }
  .catalog-results .task-full { padding: 15.4px; }
  .catalog-results .tf-head .tf-right { width: 100%; margin-left: 0; justify-content: space-between; }
  .catalog-results .tf-cond, .task-page .condition { font-size: 16.5px; }
  .task-page-head { margin-bottom: 22px; }
}

@media (prefers-reduced-motion: reduce) {
  .catalog-results .task-full { transition: none; }
}

/* ── этап 2: путь ученика ───────────────────────────────────────────────
   Главная, уроки, карточка дз и настройки тренажёра сохраняют серверные
   данные и действия, но сначала показывают работу, к которой нужно
   вернуться. Скоуп страниц не затрагивает кабинеты репетитора. */
.student-home .page-head,
.lessons-page--student .page-head,
.homework-set-page--student .page-head,
.trainer-page .page-head { margin-bottom: 20px; }

.student-now,
.hw-student-next {
  display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center; gap: 18px; padding: 22px 24px; margin: 0 0 28px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--accent-soft);
}
.student-now.is-overdue,
.hw-student-next.is-overdue { border-color: var(--err); }
.student-now-copy h2,
.hw-student-next h2,
.trainer-setup-head h2 { margin: 0; font-size: 19px; line-height: 1.25; letter-spacing: -.018em; }
.student-now-copy p,
.hw-student-next p,
.trainer-setup-head p { margin: 5px 0 0; color: var(--text-2); font-size: 14px; line-height: 1.5; }
.student-now-progress { display: grid; justify-items: end; gap: 2px; color: var(--text-2); font-size: 13px; white-space: nowrap; }
.student-now-progress strong { color: var(--text); font-size: 18px; }
.student-continue { margin-bottom: 28px; }
.student-continue .section-label,
.student-start .section-label { margin-top: 0; }
.student-continue-list { padding-top: 4px; padding-bottom: 4px; }
.student-start { margin-bottom: 30px; }
.student-start .sh-start-grid { grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.student-start .sh-start-card { min-height: 112px; padding: 18px; border-radius: 12px; }
.student-start .sh-start-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.student-summary { margin-bottom: 0; }
.student-summary .stat-card { min-height: 122px; }
.student-home--linked .stat-grid:first-of-type { margin-top: 0; }

.lessons-page--student .page-head .sub { max-width: 48ch; }
.student-lessons-rail { margin-top: 28px; }
.lessons-page--student .lf-next,
.lessons-page--student .lesson-card { border-radius: 12px; }
.lessons-page--student .lf-next { border-color: var(--line); }
.lessons-page--student .lf-next:focus-visible,
.lessons-page--student .lesson-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 5px; }
.lessons-page--student .lf-hw { gap: 10px; }

.homework-set-page--student .page-head .titles { max-width: 62ch; }
.hw-student-next { grid-template-columns: minmax(0, 1fr) auto auto; margin-bottom: 30px; padding: 26px 28px; background: var(--surface); }
.hw-student-next.is-overdue { background: color-mix(in srgb, var(--err-soft) 45%, var(--surface)); border-color: var(--line); }
.hw-deadline { display: grid; gap: 4px; padding: 4px 24px; border-left: 1px solid var(--line); }
.hw-deadline-label { font-size: 13px; font-weight: 650; color: var(--text-2); }
.hw-deadline-date { font-size: 23px; font-weight: 700; line-height: 1.25; font-variant-numeric: tabular-nums; }
.hw-deadline-note { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text-2); }
.hw-deadline--overdue .hw-deadline-date,
.hw-deadline--overdue .hw-deadline-note { color: var(--err); }
.hw-deadline--soon .hw-deadline-date,
.hw-deadline--soon .hw-deadline-note { color: var(--accent); }
.hw-task-list { padding: 24px; border-radius: 12px; }
.homework-set-page--student .hw-task-line { margin: 0; }
.homework-set-page .hw-task-line + .hw-task-line { margin-top: 12px; }
.homework-set-page .task-row { min-height: 72px; padding: 18px 20px; border-radius: 12px; }
.homework-set-page .hw-task-line.is-done .task-row { padding-left: 18px; border-left-width: 2px; }
.homework-set-page .task-row:hover { border-color: var(--accent-mid); background: color-mix(in srgb, var(--accent-soft) 25%, var(--surface)); }
.homework-set-page .task-snippet { margin-top: 6px; line-height: 1.5; }
.homework-set-page--student .task-row:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.lesson-page .lh-hero,
.lesson-page .lh-materials,
.lesson-page .lh-set { padding: 24px; border-radius: 12px; }
.lesson-page .task-row { padding: 16px 20px; border-radius: 12px; }
.lesson-page .task-row:hover { border-color: var(--accent-mid); }

.trainer-page { max-width: 920px; }
.trainer-page .page-head .titles { max-width: 58ch; }
.trainer-setup { padding: 26px; }
.trainer-setup-head { padding-bottom: 20px; margin-bottom: 20px; border-bottom: 1px solid var(--line); }
.trainer-setup-form { display: block; }
.trainer-setup .filter-selects { gap: 10px; }
.trainer-numbers { padding-top: 6px; }
.trainer-setup-actions { display: flex; align-items: center; justify-content: flex-start; margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line); }
.trainer-page .num-pick span,
.trainer-page .lvl-pick span { min-height: 44px; }
.trainer-page .stepper-controls button,
.trainer-page .stepper-remove { min-width: 44px; min-height: 44px; }
.trainer-page .stepper-controls .val { min-height: 38px; }
.trainer-page .num-all .link { display: inline-flex; align-items: center; min-height: 44px; }
.trainer-page .trainer-guest-cta { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line); }

@media (max-width: 700px) {
  .lesson-page .lh-hero,
  .lesson-page .lh-materials,
  .lesson-page .lh-set { padding: 18px; }
  .lesson-page .task-row { padding: 14px 16px; }
  .hw-task-list { padding: 16px; }
  .homework-set-page .task-row { padding: 16px; }
  .homework-set-page .hw-task-line.is-done .task-row { padding-left: 14px; }
  .student-now,
  .hw-student-next { grid-template-columns: 1fr auto; padding: 18px; gap: 14px; }
  .hw-student-next > div:first-child { grid-column: 1 / -1; }
  .hw-deadline { padding: 12px 0 0; border-left: 0; border-top: 1px solid var(--line); }
  .student-now-progress { justify-items: start; }
  .student-now .btn { grid-column: 1 / -1; width: 100%; }
  .student-start .sh-start-grid { grid-template-columns: 1fr; }
  .student-start .sh-start-card { min-height: 96px; }
  .trainer-setup { padding: 18px; }
}

@media (max-width: 480px) {
  .student-now,
  .hw-student-next { grid-template-columns: 1fr; }
  .student-now-progress { justify-items: start; }
  .hw-student-next .btn { width: 100%; }
  .trainer-page .num-pick span { min-height: 40px; }
}

@media (prefers-reduced-motion: reduce) {
  .student-start .sh-start-card { transition: none; }
}

/* Stage 5A — live-урок: на широком экране две независимые рабочие
   колонки, на телефоне узлы переставляет room-layout.js без копирования.
   Серверный порядок остаётся понятным no-JS fallback. */
.lesson-workspace { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: 36px; align-items: start; }
.lesson-column { display: grid; min-width: 0; align-content: start; gap: 28px; }
.lesson-column--problem { min-width: 0; }
.lesson-column--surface { padding-left: 28px; border-left: 1px solid var(--line); }
.lesson-code, .lesson-participants, .lesson-actions { min-width: 0; }
.lesson-code { padding-left: 0; border-left: 0; }
.lesson-actions > .section-label:first-child { margin-top: 0; }
.lesson-participants { padding-top: 2px; }
.lesson-participants .live-mine { margin-top: 0; }
.lesson-participants .section-label { margin-top: 20px; }
.lesson-room .live-mine {
  padding: 12px 0; margin-bottom: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.lesson-room .live-mine-label { flex: 1 1 100%; font-size: 14px; }
.lesson-room .live-peer { margin: 0; padding: 20px 0; border-bottom: 1px solid var(--line); }
.lesson-room .live-peer-head { align-items: flex-start; margin-bottom: 12px; }
.lesson-room .live-peer-head strong { flex: 1 1 150px; }
.lesson-room .live-peer-head .btn { white-space: normal; }
.lesson-room .room-featured { margin-top: 0; padding-top: 2px; }
.lesson-room .room-featured .py-live-host,
.lesson-room .room-featured [data-featured-out] { border: 1px solid var(--line); border-radius: 8px; background: var(--surface); }
.lesson-room .room-featured [data-featured-out] { margin-top: 8px; padding: 12px; }

@media (max-width: 1000px) {
  /* Без JS колонки складываются в серверном порядке. Это оставляет условие,
     проверку и файл доступными; JS уточняет порядок для живой поверхности. */
  .lesson-workspace { display: block; }
  .lesson-column { gap: 28px; }
  .lesson-column--surface { padding: 28px 0 0; margin-top: 28px; border-left: 0; border-top: 1px solid var(--line); }
  .lesson-workspace.is-mobile-order { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-areas: "problem" "code" "featured" "participants" "actions"; gap: 0; }
  .lesson-workspace.is-mobile-order > [data-room-surface] { min-width: 0; }
  .lesson-workspace.is-mobile-order > .lesson-problem { grid-area: problem; }
  .lesson-workspace.is-mobile-order > .lesson-code { grid-area: code; padding: 28px 0 0; margin-top: 28px; border-top: 1px solid var(--line); }
  .lesson-workspace.is-mobile-order > .room-featured { grid-area: featured; padding-top: 28px; margin-top: 28px; border-top: 1px solid var(--line); }
  .lesson-workspace.is-mobile-order > .lesson-participants { grid-area: participants; padding-top: 28px; margin-top: 28px; border-top: 1px solid var(--line); }
  .lesson-workspace.is-mobile-order > .lesson-actions { grid-area: actions; padding-top: 28px; margin-top: 28px; border-top: 1px solid var(--line); }
  .lesson-workspace.is-mobile-order > .lesson-column { display: none; }
  .lesson-room .live-mine .btn,
  .lesson-room .live-peer-head .btn { min-height: 44px; }
}

/* Тач-цель доски соответствует остальным рабочим контролам; размеры сцены
   и транспорт доски не меняются. */
@media (pointer: coarse) {
  .board-stage .draw-btn,
  .board-stage .tool-btn { min-width: 44px; min-height: 44px; }
  .board-stage .board-bar { gap: 8px; }
}

@media (prefers-reduced-motion: reduce) {
  .board-stage, .board-stage * { scroll-behavior: auto; }
}

/* Остальные рабочие поверхности урока: тот же спокойный порядок и доступ
   к управлению, без изменения холста, снимка или live-транспорта. */
.live-peer-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.classwork-board-page .page-head { margin-bottom: 18px; }
.classwork-board-page .draw-toolbar { margin-bottom: 12px; }
.classwork-board-page [data-board-form] { display: flex; align-items: center; gap: 10px; }
.classwork-item-page .cw-strip a { min-height: 44px; display: inline-grid; place-items: center; }
.classwork-item-page .cw-strip a:focus-visible { outline: 2px solid var(--focus-ring, var(--accent)); outline-offset: 3px; }

@media (max-width: 700px) {
  .pen-bar { gap: 8px; padding: 10px 14px; }
  .pen-bar .btn { min-height: 44px; }
  .pen-sec { padding: 18px 14px; scroll-margin-top: 68px; }
  .pen-tools.draw-toolbar { width: 100%; padding: 8px; }
  .classwork-board-page .board-wrap { min-height: 0; }
  .classwork-item-page .page-head { gap: 12px; }
}

@media (pointer: coarse) {
  .live-peer-actions .btn,
  .py-live-bar .btn,
  .classwork-board-page [data-board-form] .btn,
  .classwork-item-page .page-head .btn { min-height: 44px; }
}

/* Stage 5C — проверка сдач: очередь ведёт к следующей работе, а экран
   сдачи держит один рабочий маршрут: материал → комментарий → публикация. */
.review-queue { max-width: 1040px; }
.review-queue-count { font-variant-numeric: tabular-nums; }
.review-queue .rq-card { min-width: 0; }
.review-queue .rq-card:focus-within { border-color: var(--focus-ring); }
.review-queue .rq-chip { min-height: 44px; }
.review-queue .chip-row .chip,
.review-queue .rq-toolbar .btn,
.review-queue .rq-go { min-height: 44px; }
.review-queue .rq-done summary { min-height: 44px; display: flex; align-items: center; }

.review-workspace { max-width: 1100px; }
.review-workspace .rv-hw-strip { scroll-padding-inline: 8px; }
.review-workspace .rv-hw-cell { scroll-snap-align: start; }
.review-workspace .rv-hw-cell:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 3px; }
.review-surface { max-width: 78ch; }
.review-solution { min-width: 0; overflow-x: auto; }
.review-solution .submission-img { display: block; margin-inline: auto; }
.review-readonly { padding: 12px 14px; border: 1px solid var(--warn); border-radius: 8px; background: var(--warn-soft); }
.review-solo-state { padding: 12px 14px; border: 1px solid var(--line); border-radius: 8px; background: var(--frame-bg); }
.review-action-stack { margin-top: 44px; max-width: 820px; }
.review-action-stack > .section-label { margin-top: 0; }
.review-action-note { margin: -7px 0 13px; color: var(--text-2); font-size: 14px; line-height: 1.5; }
.review-comment-form { margin-top: 0; }
.review-finish-card { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 14px; padding: 14px 16px; border: 1px solid var(--line); border-radius: 8px; background: var(--frame-bg); }
.review-finish-card .score-hint { flex: 1 1 320px; margin: 0; }
.review-next { min-height: 44px; margin-top: 14px; }

@media (max-width: 700px) {
  .review-queue .rq-card { padding: 14px; }
  .review-workspace .page-head { align-items: flex-start; }
  .review-workspace .page-head .back { min-height: 44px; display: inline-flex; align-items: center; }
  .review-workspace .rv-hw-strip { margin-bottom: 20px; }
  .review-workspace .rv-meta { margin-bottom: 18px; }
  .review-workspace .card { padding: 16px; }
  .review-workspace .ac-compare { gap: 18px; }
  .review-workspace .cc-head { align-items: flex-start; flex-wrap: wrap; }
  .review-workspace .cc-acts { width: 100%; margin-left: 0; }
  .review-workspace .btn-sm,
  .review-workspace .cc-jump { min-height: 44px; }
  .review-finish-card { align-items: stretch; }
  .review-finish-card .btn { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
  .review-queue .rq-done-head,
  .review-queue .rq-done-head::before { transition: none; }
}


/* Stage 6A — публичный вход: единый спокойный маршрут без обещаний и
   без изменения CSRF, captcha или возвращающего next. */

/* Выбранный номер: сначала подробность текущего слоя, повторный клик — каталог. */
.map27 a[data-map-cell].is-selected {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.map27 a[data-map-cell]:focus-visible,
.map-selection a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.map-selection {
  display: flex;
  align-items: center;
  gap: 10px 17px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding: 12px 0 0;
  border-top: 1px solid var(--line);
  font-size: 13.2px;
}
.map-selection[hidden] { display: none; }
.map-selection strong { font-family: "JetBrains Mono", ui-monospace, monospace; color: var(--accent); }
.map-selection > span { color: var(--text-2); }
.map-selection-actions { display: flex; gap: 10px 18px; flex-wrap: wrap; margin-left: auto; }
@media (max-width: 700px) {
  .map-selection-actions { margin-left: 0; width: 100%; }
}

/* Очередь внимания читается строками. Ширина текста не зависит от длины
   кнопок: на узких экранах действия уходят ниже, а не сжимают название. */
.attn-group > .card { padding: 0; border: 0; background: transparent; }
.attn-group .list-row.att-row {
  display: grid;
  grid-template-columns: 33px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 8px 16px;
  padding: 16px 20px;
  border: 1px solid transparent;
  border-top-color: var(--line);
  border-radius: 10px;
  min-width: 0;
}
.attn-group .list-row.att-row:hover,
.attn-group .list-row.att-row:focus-visible {
  border-color: var(--line);
  background: var(--surface);
}
.attn-group .att-row .lr-text { min-width: 0; }
.attn-group .att-row .att-line { line-height: 1.42; }
.attn-group .att-row .att-badge-col { min-width: 0; flex: none; }
.attn-group .att-row .att-act { min-width: 0; flex: none; margin: 0; }
.attn-group .att-row .att-act form { margin: 0; }
.attn-group .att-move { position: relative; }
.attn-group .att-move > summary { list-style: none; cursor: pointer; }
.attn-group .att-move > summary::-webkit-details-marker { display: none; }
.attn-group .att-move-form { position: absolute; top: calc(100% + 8px); left: 0; z-index: 7; display: flex; align-items: center; gap: 8px; padding: 12px; background: var(--surface); border: 1px solid var(--line); border-radius: 9px; box-shadow: 0 12px 32px rgba(0,0,0,.18); }
.attn-group .att-move-form .input { width: 145px; min-height: 38px; padding: 6px 9px; font-size: 13px; }
.attn-group .att-row .btn { white-space: nowrap; }
/* Полная навигация кабинета шире 900px. Свертываем её до того, как
   аватар и переключатель темы начнут выталкиваться за край экрана. */
@media (min-width: 701px) and (max-width: 1050px) {
  .site-header { gap: 14px; padding-inline: 22px; }
  .site-header .site-nav { display: none; }
  .site-header .current-section { display: inline; }
  .site-header .burger { display: block; }
}
@media (max-width: 700px) {
  .page-head .actions { flex-wrap: wrap; max-width: 100%; justify-content: flex-start; }
}
@media (max-width: 1050px) {
  .attn-group .list-row.att-row { grid-template-columns: 33px minmax(0, 1fr) auto; }
  .attn-group .att-row .att-act { grid-column: 2 / -1; justify-content: flex-start; }
}
@media (max-width: 640px) {
  .attn-group .list-row.att-row { grid-template-columns: minmax(0, 1fr); gap: 7px; padding: 17px 16px; }
  .attn-group .att-row .lr-text { grid-column: 1; }
  .attn-group .att-row .att-line { display: block; }
  .attn-group .att-row .att-who { display: block; margin-bottom: 2px; }
  .attn-group .att-row .att-what { display: block; overflow-wrap: anywhere; }
  .attn-group .att-row .att-badge-col { grid-column: 1; justify-content: flex-start; }
  .attn-group .att-row .att-act { grid-column: 1; flex-wrap: wrap; justify-content: flex-start; }
  .attn-group .att-move-form { flex-wrap: wrap; width: min(280px, calc(100vw - 58px)); }
  .attn-group .att-row .btn { margin-left: 0; }
}

/* Восстановленный общий стиль форм входа и продолжения регистрации. */
.authx-card .auth-error { margin: 0; }
.auth--handoff { max-width: 528px; }
.auth--handoff .auth-card { border-radius: 15.4px; }
.authx .pass-eye, .auth--handoff .pass-eye { min-height: 44px; min-width: 44px; }
.authx .auth-form .btn, .auth--handoff .auth-form .btn { min-height: 44px; }
.authx .auth-error, .auth--handoff .auth-error { border: 1px solid color-mix(in srgb, var(--err) 45%, var(--line)); }
@media (prefers-reduced-motion: reduce) { .authx *, .auth--handoff * { scroll-behavior: auto; } }

/* Демо — это один ясный вход в настоящий сценарий, не витрина одинаковых
   карточек. Плашка в кабинете сохраняет контекст, но не спорит с работой. */
.demo-page { max-width: 900px; margin: 0 auto; padding: 70px 35.2px 88px; }
.demo-intro { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 35.2px; align-items: end; padding-bottom: 35.2px; border-bottom: 1.1px solid var(--line); }
.demo-intro h1 { max-width: 16ch; font-size: clamp(35.2px, 4.2vw, 52.8px); line-height: 1.05; letter-spacing: -.03em; font-weight: 700; text-wrap: balance; }
.demo-intro p { max-width: 55ch; margin-top: 17.6px; color: var(--text-2); font-size: 17.6px; line-height: 1.6; }
.demo-intro .btn { min-height: 48px; white-space: nowrap; }
.demo-note { max-width: 60ch; margin-top: 17.6px; color: var(--text-2); font-size: 14.3px; line-height: 1.55; }
.demo-banner { display: flex; align-items: center; justify-content: center; gap: 13.2px 22px; padding: 9.9px 17.6px; border-bottom: 1.1px solid var(--line); background: var(--frame-bg); color: var(--text-2); font-size: 13.75px; line-height: 1.45; }
.demo-banner b { color: var(--text); font-weight: 650; }
.demo-banner a { color: var(--accent); text-underline-offset: 3px; }
.demo-banner form { display: inline; }
.demo-banner button { padding: 0; border: 0; background: transparent; color: var(--text-2); font: inherit; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.demo-banner button:hover, .demo-banner button:focus-visible { color: var(--text); }
@media (max-width: 700px) {
  .demo-page { padding: 44px 17.6px 61.6px; }
  .demo-intro { grid-template-columns: 1fr; gap: 26.4px; padding-bottom: 26.4px; }
  .demo-intro .btn { width: 100%; }
  .demo-banner { align-items: flex-start; justify-content: flex-start; flex-wrap: wrap; gap: 6.6px 17.6px; }
}


/* Stage 6B — операционные поверхности: таблица листается локально,
   действия остаются удобными на телефоне, служебные состояния не теряют путь. */
.ops-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ops-table { min-width: 680px; }
.ops-action { min-height: 44px; }
.service-state { max-width: 68ch; }
.service-state .btn { min-height: 44px; margin-top: 4px; }
@media (max-width: 700px) { .ops-table-wrap { margin-inline: -17.6px; border-radius: 0; border-inline: 0; } }

/* Превью следует выбранной теме; скрытая картинка исключена из дерева доступности. */
:root:not([data-theme="dark"]) .landing-dark { display: none; }
:root[data-theme="dark"] .landing-light { display: none; }

/* Столбики истории ведут к разбору конкретной попытки. */
.sc-attempt { cursor: pointer; }
.sc-attempt:hover .sc-bar { opacity: .8; }
.sc-attempt:focus-visible { outline: none; }
.sc-attempt:focus-visible .sc-hit { stroke: var(--accent); stroke-width: 2px; }
