/* ============================================================
   core.css — мокапы v2 «светлый терминал»
   Источник токенов: style-core.html (не менять значения без сверки
   с ним). Канонические компоненты используются ВСЕМИ экранами —
   локальный <style> в экране допускается только для уникальной
   геометрии (см. docs/superpowers/plans/2026-07-11-mockups-v2-redesign.md).
   ============================================================ */

/* ── токены ── */
:root, :root[data-theme="light"] {
  /* --bg история: #FCFBF8 (тёплый) → #FEFEFC → #FFFFFF (правка 12.07, «фон
     светлее до белого»). Правка владельца 19.07, волна 5, п. h: чисто-белый
     фон читался слишком холодно/безлико — вернули к историческому тёплому
     #FCFBF8. --surface НЕ трогаем — он и был #FFFFFF, а значит уже (и снова)
     светлее --bg на 2–4 уровня RGB, «карточка приподнята над фоном» не
     схлопывается сам по себе; --line (обводка карточек) тоже не менялся —
     историческое значение то же самое, что текущее. */
  /* правка владельца 01.08: светлая тема сделана ещё светлее — фон и
     рамка подняты на пару уровней, --surface остаётся чистым белым. */
  --bg: #FEFDFA;
  --surface: #FFFFFF;
  --frame-bg: #FCFCFA;         /* фон .frame — отдельный от --bg токен (правка
                                   владельца 12.07: --bg сравнялся с --surface,
                                   .frame нужен оттенок, чтобы не сливаться с карточкой) */
  --text: #241C15;
  --text-2: #6E6259;
  --line: #E9E2D9;
  --accent: #58381F;
  --accent-hover: #422A16;
  --accent-soft: #F0E7DC;      /* подложка выбранного */
  --ok: #3E7C4F;
  --ok-soft: #E4EFE7;
  --err: #B4423A;
  --err-soft: #F6E6E4;
  --warn: #B07C2E;
  --warn-soft: #F5ECDA;
  --cell-solved: #58381F;      /* карта 27: решено */
  --cell-touched: #D9C3AC;     /* карта 27: пробовал */
  --on-accent: #FFFFFF;
  /* сложность — «светофор» (правка владельца 12.07 вечер, заменяет прежнюю
     карамельно-охра палитру целиком): лёгкий травяной (НЕ равен --ok),
     дальше жёлтый → оранжевый → красный; гроб ярче/краснее (правка 12.07
     полдень) — контраст с оранжевым --d3; вся палитра заметно ярче —
     правка 14.07. Правка владельца 17.07: --d2/--d3 были слишком похожи —
     базовый (--d2) сделан явно желтее, повышенный (--d3) явно оранжевее.
     Значения продублированы в light :root, dark :root и @media-фолбэке
     ниже — менять везде разом. */
  --d1: #2FA84F;   /* лёгкий */
  --d2: #EAB308;   /* базовый */
  --d3: #F26B0F;   /* повышенный */
  --grob: #E52521; /* гроб */
  /* золото — 100 баллов на варианте, отдельно от --warn (тот же тёплый угол,
     но приглушённее): --gold заметно желтее/чище (правка владельца 15.07) */
  --gold: #8A6D00;
  --gold-soft: #FBEFB8;
  /* маркер-палитра (эпик W, классная работа): второй акцентный тон для
     тулбара рисования (свотч 2 из 4, рядом с --accent/--ok/--err) — тёплый
     янтарь, не пересекается по смыслу со сложностью d1–d3/grob */
  --accent-mid: #A8672E;
  /* подсветка Python (#18, правка владельца 17.07 — «ещё цветастее»): две
     НОВЫЕ холодные краски специально для токенов кода — намеренно не
     похожи на тёплую основную палитру и не пересекаются по смыслу с
     сложностью/статусами. --tok-const — константы True/False/None,
     --tok-self — self/cls и параметры функций. Декораторы и f-строки
     переиспользуют --gold/--accent стилем (курсив/жирный), новых токенов
     под них не заводили — см. .tok-* ниже. */
  --tok-const: #2B5C86;
  --tok-self: #7A4A8F;
  /* правка владельца 19.07, волна 5, п.3 (h): «подсветка Python в светлой
     теме — цветастее» (тёмная владельцу нравится, не трогаем — см. блок
     dark ниже, там те же 3 токена намеренно РАВНЫ старым значениям).
     Раньше .tok-kw/.tok-num/.tok-fn были псевдонимами --accent/--accent-mid/
     --warn — в светлой теме все трое эспрессо-коричневые оттенки, код
     читался почти монохромным. Теперь — отдельные токены (тот же приём,
     что уже был у --tok-const/--tok-self: «намеренно не похожи на тёплую
     основную палитру»), три новых холодных/ярких тона, не пересекающихся
     ни друг с другом, ни с --tok-const (синий)/--tok-self (лиловый), ни со
     статусными/сложностными цветами сайта: --tok-kw — малиновый (ключевые
     слова), --tok-num — тёмный янтарь (числа), --tok-fn — тёмная бирюза
     (имена функций). .tok-str/.tok-op/.tok-comment/.tok-builtin/.tok-deco
     не трогали — строки уже отличимо зелёные (--ok), операторы/комментарии
     намеренно приглушены (не должны спорить с остальным кодом), builtin —
     уже свой --gold. */
  --tok-kw: #A6316F;
  --tok-num: #A85D00;
  --tok-fn: #1B7F91;
}
:root[data-theme="dark"] {
  --bg: #1A1512;
  --surface: #211B17;
  --frame-bg: #1A1512;         /* = --bg, тёмная тема не менялась */
  --text: #EDE6DD;
  --text-2: #A39588;
  --line: #352C25;
  --accent: #D8C29C;           /* шампань; запасной — карамель #C99B6B */
  --accent-hover: #E6D4B4;
  --accent-soft: #322B1E;
  --ok: #7FB48D;
  --ok-soft: #22301F;
  --err: #D08078;
  --err-soft: #38221F;
  --warn: #CFA45E;
  --warn-soft: #342A18;
  --cell-solved: #D8C29C;
  --cell-touched: #4A3B2E;
  --on-accent: #1A1512;
  --d1: #4FD07A;
  --d2: #F7D74A;
  --d3: #FF7A2E;
  --grob: #FF6259;
  --gold: #FFD447;
  --gold-soft: #40330A;
  --accent-mid: #D99A5E;
  --tok-const: #8FC1E8;
  --tok-self: #D6A9E8;
  /* правка владельца 19.07, волна 5, п.3 (h): значения РАВНЫ старым
     --accent/--accent-mid/--warn тёмной темы (D8C29C/D99A5E/CFA45E) — .tok-kw/
     .tok-num/.tok-fn раньше БЫЛИ этими токенами напрямую, тёмная тема не
     меняется ни на пиксель, просто явно продублирована под новые имена,
     чтобы не наследовать светлые значения из :root по остаточному каскаду
     (см. светлый блок выше — там же подробное обоснование правки). */
  --tok-kw: #D8C29C;
  --tok-num: #D99A5E;
  --tok-fn: #CFA45E;
}
/* фолбэк по системной теме, пока nav.js/head-сниппет ещё не отработал
   (data-theme на <html> ставит инлайн-сниппет в head — см. README) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --bg: #1A1512; --surface: #211B17; --frame-bg: #1A1512; --text: #EDE6DD; --text-2: #A39588;
    --line: #352C25; --accent: #D8C29C; --accent-hover: #E6D4B4;
    --accent-soft: #322B1E; --ok: #7FB48D; --ok-soft: #22301F;
    --err: #D08078; --err-soft: #38221F; --warn: #CFA45E; --warn-soft: #342A18;
    --cell-solved: #D8C29C; --cell-touched: #4A3B2E; --on-accent: #1A1512;
    --d1: #4FD07A; --d2: #F7D74A; --d3: #FF7A2E; --grob: #FF6259;
    --gold: #FFD447; --gold-soft: #40330A; --accent-mid: #D99A5E;
    --tok-const: #8FC1E8; --tok-self: #D6A9E8;
    --tok-kw: #D8C29C; --tok-num: #D99A5E; --tok-fn: #CFA45E;
  }
}

/* ── серийные цвета для многосерийных графиков (правка владельца 18.07,
   волна 4, дефект 3б): «СТАТИСТИКА ПО ПРОБНИКАМ» на group-page.html красила
   каждого участника var(--accent)/var(--accent-mid) — в тёмной теме оба
   тёплый тан, серии почти неразличимы, стартовые точки одной даты
   сливались. До 6 серий, цвета — ПРОИЗВОДНЫЕ от уже существующих
   семантических токенов через var()/color-mix (не новые hex). Объявлены
   ОДИН раз (не триплицируются по light/dark/media-фолбэку, как токены
   выше): var()-ссылка внутри резолвится лениво в момент фактического
   использования custom property, поэтому --series-N сам подхватывает
   текущий --accent/--ok/--err/--warn темы без ручного дублирования. */
/* ПЕРЕСОБРАНО правкой владельца 19.07 (волна 7, аудит): палитра серий была
   собрана из СТАТУСНЫХ токенов (--ok/--err/--warn) — и ученик, которому
   достался --err, читался как «ошибка/просрочка», хотя это просто вторая
   линия графика. Категориальная палитра обязана быть независимой от
   статусной: берём НЕстатусные краски, уже живущие в системе, — акцент
   (шампань), лиловый и синий из палитры кода, янтарь маркера, золото.
   Статусные красный/зелёный в серии не входят вовсе. */
:root {
  --series-1: var(--accent);
  --series-2: var(--tok-self);
  --series-3: var(--tok-const);
  --series-4: var(--accent-mid);
  --series-5: var(--gold);
  --series-6: color-mix(in srgb, var(--tok-self) 50%, var(--accent) 50%);
}

/* ── база ── */
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* При ручной смене темы краткие hover-переходы карточек не должны
   догонять уже перекрашенный подвал и фон. nav.html ставит этот класс
   ровно на кадр смены CSS-токенов, поэтому вся страница переключается
   одновременно, а обычные интерактивные transition сохраняются. */
html.theme-switching *,
html.theme-switching *::before,
html.theme-switching *::after { transition: none !important; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: "Inter", -apple-system, sans-serif;
  font-size: 16.5px;
  line-height: 1.55;
  /* Тема меняется одновременно с поверхностями и футером: отдельный
     transition у body оставлял старый фон ещё на 180 мс. */
}
.mono { font-family: "JetBrains Mono", ui-monospace, monospace; }
h1, h2, h3 { margin: 0; }
p { margin: 0 0 13.2px; }

/* ============================================================
   Иконки (правка владельца 18.07, волна 4, п. 3): эмодзи-иконки
   (📄 ▶ ✎ 🗑 🔔 🌙 ⚙ и т.п.) на Windows/Android разъезжались и
   слипались с текстом — заменены одним inline-SVG-набором в стиле
   Lucide (24×24, stroke 1.65px, currentColor, без заливки). Символы
   живут в ОДНОМ скрытом спрайте, который nav.js идемпотентно вставляет
   первым узлом body на каждом экране (см. nav.js, конец файла) —
   здесь только класс-хайдер спрайта и класс самой иконки.
   Разметка использования: <svg class="ic" aria-hidden="true"><use
   href="#ic-имя"></use></svg>; где иконка без текста рядом несёт
   смысл сама по себе — title/aria-label вешается на КНОПКУ, не на svg.
   Эмодзи остаются только там, где они контент (в мокапах — нигде);
   типографские глифы ✓ ✕ – ◉ ↑ → в ячейках матриц и в хвостах ссылок
   («все уроки →») — не иконки, типографика, тронуты не были.
   ============================================================ */
.ic-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.ic {
  width: 17.6px; height: 17.6px; flex: none;
  stroke: currentColor; fill: none; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
  vertical-align: -3.3px; /* центрирует иконку на строке текста вне флекс-контейнеров */
}
.ic-xs { width: 13.2px; height: 13.2px; }
.ic-sm { width: 15.4px; height: 15.4px; }
.ic-lg { width: 22px; height: 22px; }
/* ── icon-label: обёртка icon+текст ТАМ, где родитель — не .btn/.chip/
   .premium-lock/.premium-inline-warn (у них уже flex+gap) — напр. вкладка
   сегмента, заголовок хаб-карточки. Без нового родителя-флекса gap между
   svg и текстовым узлом не работает (у text node нет собственного gap) ── */
.icon-label { display: inline-flex; align-items: center; gap: 4.4px; }
a { color: inherit; }
img { max-width: 100%; }

/* ── степени/индексы в условиях (правка владельца 17.07): юникод-надстрочные
   (10¹⁰) у Inter мелкие и сидят криво — условия переведены на честные
   <sup>/<sub>, тут — нормальные метрики, чтобы строка не «прыгала» (дефолтный
   vertical-align: super/sub у браузера раздувает line-height). В проде
   условия рендерит KaTeX (уже вендорный, internal/web/static/katex) — здесь,
   в мокапе, достаточно правильно посаженных тегов. */
sup, sub { font-size: .75em; line-height: 0; position: relative; vertical-align: baseline; }
sup { top: -0.5em; }
sub { bottom: -0.25em; }

a.link { color: var(--accent); text-decoration: none; border-bottom: 1.1px solid color-mix(in srgb, var(--accent) 35%, transparent); }
a.link:hover { border-bottom-color: var(--accent); }

/* ── контейнеры ── */
.container-read, .container-work {
  margin: 0 auto;
  padding: 35.2px 35.2px 88px;
}
.container-read { max-width: 792px; }
.container-work { max-width: 1254px; }
@media (max-width: 480px) {
  .container-read, .container-work { padding-left: 17.6px; padding-right: 17.6px; }
}

/* ── строка заголовка страницы ── */
.page-head {
  display: flex;
  align-items: center;
  gap: 17.6px;
  margin-bottom: 22px; /* 32→22px — заголовок не отрывается от поиска/фильтров (правка владельца 15.07) */
  flex-wrap: wrap;
}
.page-head .back {
  color: var(--text-2);
  text-decoration: none;
  font-size: 15.4px;
  white-space: nowrap;
}
.page-head .back:hover { color: var(--accent); }
.page-head .titles { display: flex; flex-direction: column; gap: 3.3px; }
.page-head h1 { font-size: 28.6px; font-weight: 650; letter-spacing: -.02em; }
.page-head .sub { color: var(--text-2); font-size: 14.85px; }
.page-head .spacer { flex: 1; }
.page-head .actions { display: flex; align-items: center; gap: 8.8px; }

/* ── ярлык секции ── */
.section-label {
  font-family: "JetBrains Mono", monospace;
  font-size: 14.3px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--text-2);
  margin: 50.6px 0 17.6px;
}
.section-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 13.2px;
  margin: 50.6px 0 17.6px;
  flex-wrap: wrap;
}
.section-label-row .section-label { margin: 0; }
.section-label-row .actions { display: flex; align-items: center; gap: 8.8px; }
.section-label:first-child, .section-label-row:first-child { margin-top: 0; }

/* ── карточка / рамка ── */
.card { background: var(--surface); border: 1.1px solid var(--line); border-radius: 8.8px; padding: 22px 24.2px; }
.card + .card { margin-top: 17.6px; }
/* в грид-сетках вертикальный отступ даёт gap — глушим стопочное правило.
   .teaser-grid добавлена правкой владельца 17.07: без сброса .card+.card
   утекший margin-top ломал grid stretch — карточки «с чего начать» на
   guest-home.html получались РАЗНОЙ высоты (2-я/3-я короче на 17.6px и сдвинуты
   вниз), хотя auto-fit грид по умолчанию должен растягивать все строки ровно.
   .course-grid добавлена правкой владельца 18.07, волна 4, дефект 11: та же
   гоча на courses.html — «пляшут по вертикали» оказалось не про grid stretch
   (он и так по умолчанию align-items:stretch), а про 2-ю/3-ю карточки
   витрины, ловившие лишний margin-top от .card+.card */
.stat-grid > .card, .pupil-grid > .card, .hub-grid > .card, .teaser-grid > .card, .course-grid > .card, .pricing-cards > .card { margin-top: 0; }
.frame { background: var(--frame-bg); border: 1.1px solid var(--line); border-radius: 8.8px; padding: 15.4px 17.6px; min-width: 0; }

/* ── полоска уровня сложности слева на карточке (каталог задач, правка 12.07) ── */
.card--lvl { border-left-width: 4.4px; border-left-style: solid; }
.card--lvl.lvl-1 { border-left-color: var(--d1); }
.card--lvl.lvl-2 { border-left-color: var(--d2); }
.card--lvl.lvl-3 { border-left-color: var(--d3); }
.card--lvl.lvl-4 { border-left-color: var(--grob); }
/* hover карточки задачи — рамка цветом уровня, не accent (правка 12.07 полдень);
   специфичнее .task-full:hover (2 класса) в catalog.html — 3 класса+:hover бьёт его */
.card--lvl.lvl-1:hover { border-color: var(--d1); }
.card--lvl.lvl-2:hover { border-color: var(--d2); }
.card--lvl.lvl-3:hover { border-color: var(--d3); }
.card--lvl.lvl-4:hover { border-color: var(--grob); }

/* ── индикатор уровня сложности: 4 горизонтальные полоски одинаковой длины,
   стопкой сверху вниз (flex-direction: column — i:nth-child(1..N) красятся
   сверху; правка владельца 15.07 вечер — было column-reverse/снизу, читалось
   как «столбик роста», а нужен обратный «градусник»-счёт с верхней полоски),
   закрашено N полосок по уровню цветом уровня, остальные — var(--line)
   (правка владельца 14.07, заменяет прежний wifi-мотив столбиков нарастающей
   высоты; цвет-светофор не тронут) ── */
/* правка владельца 15.07: заметно мельче — единственное место использования
   .lvl-bars это catalog.html, меняем размер глобально (grep подтвердил) */
.lvl-bars { display: inline-flex; flex-direction: column; gap: 1.65px; cursor: default; }
.lvl-bars i { display: block; width: 12.1px; height: 2.2px; border-radius: 1.1px; background: var(--line); }
.lvl-bars.lvl-1 i:nth-child(1) { background: var(--d1); }
.lvl-bars.lvl-2 i:nth-child(1),
.lvl-bars.lvl-2 i:nth-child(2) { background: var(--d2); }
.lvl-bars.lvl-3 i:nth-child(1),
.lvl-bars.lvl-3 i:nth-child(2),
.lvl-bars.lvl-3 i:nth-child(3) { background: var(--d3); }
.lvl-bars.lvl-4 i { background: var(--grob); }
/* полоски + текстовый уровень вместе (каталог задач, правка 12.07 полдень) */
.lvl-indicator { display: inline-flex; align-items: center; gap: 6.6px; }
/* правка владельца 31.07: подпись уровня красится цветом уровня, а не
   остаётся серой — светофор сложности должен читаться одинаково на всех
   экранах, где уровень вообще показан (каталог, свой банк, карточка) */
.lvl-indicator.lvl-1 .task-lvl { color: var(--d1); }
.lvl-indicator.lvl-2 .task-lvl { color: var(--d2); }
.lvl-indicator.lvl-3 .task-lvl { color: var(--d3); }
.lvl-indicator.lvl-4 .task-lvl { color: var(--grob); }
/* бейдж уровня — там, где полоски не к месту (шапка карточки задачи) */
.badge.lvl-1 { color: var(--d1); background: color-mix(in srgb, var(--d1) 16%, transparent); }
.badge.lvl-2 { color: var(--d2); background: color-mix(in srgb, var(--d2) 16%, transparent); }
.badge.lvl-3 { color: var(--d3); background: color-mix(in srgb, var(--d3) 16%, transparent); }
.badge.lvl-4 { color: var(--grob); background: color-mix(in srgb, var(--grob) 16%, transparent); }

/* ── легенда сложности (каталог задач, под карточкой фильтров): точки 11px +
   текст, правка 12.07 полдень ── */
.lvl-legend { display: flex; gap: 19.8px; flex-wrap: wrap; font-size: 14.3px; color: var(--text-2); }
.lvl-legend span { display: inline-flex; align-items: center; gap: 6.6px; }
.lvl-legend i { width: 8.8px; height: 8.8px; border-radius: 50%; display: inline-block; flex: none; } /* мельче — правка 15.07, единственное место использования — catalog.html */

/* ── карточка-статистика (главная ученика/репетитора: ряд KPI) ── */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(187px, 1fr)); gap: 15.4px; }
/* на узком экране auto-fill с полом 187px схлопывался в ОДНУ колонку — четыре
   плитки вытягивались почти на 770px вертикали (правка владельца 19.07,
   волна 7, аудит мобильного). Ниже 616px раскладываем строго 2×2: пол
   колонки снимаем, minmax(0,1fr) позволяет плиткам ужиматься. */
@media (max-width: 560px) {
  .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 11px; }
}
.stat-card { display: block; text-decoration: none; color: inherit; }
a.stat-card:hover { border-color: var(--accent); }
.stat-value { font-family: "JetBrains Mono", monospace; font-size: 26.4px; font-weight: 650; letter-spacing: -.01em; }
.stat-label { color: var(--text-2); font-size: 14.3px; margin-top: 6.6px; }

/* ── кнопки ── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6.6px;
  border-radius: 7.7px; padding: 8.8px 17.6px; font: inherit; font-size: 15.4px;
  font-weight: 550; cursor: pointer; border: 1.1px solid transparent;
  text-decoration: none; white-space: nowrap;
}
.btn-primary { background: var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-hover); }
.btn-ghost { background: transparent; border-color: var(--line); color: var(--text); }
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
.btn-sm { padding: 5.5px 12.1px; font-size: 13.75px; border-radius: 6.6px; }
/* ── опасное действие (удалить группу/аккаунт) — модификатор поверх ghost ── */
.btn-ghost.btn-danger { border-color: var(--err); color: var(--err); }
.btn-ghost.btn-danger:hover { background: var(--err-soft); border-color: var(--err); color: var(--err); }
/* ── кнопка недоступна (напр. «Назначить» при неснятом премиум-гейте в модалке) ── */
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn-primary:disabled:hover { background: var(--accent); }
.btn-ghost:disabled:hover { border-color: var(--line); color: var(--text); }
.btn:focus-visible, .chip:focus-visible, .segment a:focus-visible,
.segment button:focus-visible, a.list-row:focus-visible, .pupil-card:focus-visible,
[data-row-link]:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 3px;
}
@media (prefers-reduced-motion: no-preference) {
  .btn, .chip, .segment a, .segment button, a.list-row, .pupil-card {
    transition: background-color .16s ease, border-color .16s ease,
      color .16s ease, box-shadow .16s ease, transform .16s ease;
  }
  .btn:not(:disabled):active { transform: translateY(1px); }
  .pupil-card:hover { transform: translateY(-2px); }
}

/* ── переключатель-сегмент (правка владельца 17.07: сегмент может содержать
   и button, не только a — напр. источник каталога «банк ⇌ мои задачи ✦»,
   где base-вариант не ссылка, а триггер апселла) ── */
.segment { display: inline-flex; border: 1.1px solid var(--line); border-radius: 7.7px; overflow: hidden; }
.segment a, .segment button {
  padding: 7.7px 15.4px; font: inherit; font-size: 14.85px; color: var(--text-2); text-decoration: none;
  background: transparent; border: none; cursor: pointer;
}
.segment a + a, .segment a + button, .segment button + a, .segment button + button { border-left: 1.1px solid var(--line); }
.segment a.active, .segment button.active { background: var(--accent-soft); color: var(--text); font-weight: 550; }
.segment a:hover:not(.active), .segment button:hover:not(.active) { color: var(--accent); }

/* ── бейджи ── */
.badge { display: inline-block; border-radius: 5.5px; padding: 2.2px 8.8px; font-size: 13.75px; font-weight: 550; white-space: nowrap; }
.badge.ok   { color: var(--ok);   background: var(--ok-soft); }
.badge.err  { color: var(--err);  background: var(--err-soft); }
.badge.warn { color: var(--warn); background: var(--warn-soft); }
.badge.acc  { color: var(--accent); background: var(--accent-soft); }
.badge.muted { color: var(--text-2); background: var(--bg); border: 1.1px solid var(--line); }
/* ── 100 баллов — лёгкая золотая плашка (обводка + soft-подложка), не тяжёлая
   заливка: владелец — «крупно и цвет не выделяющийся», нужно компактнее
   обычного .badge и заметнее .badge.ok, но не давить рядом с нейтральными
   бейджами (каталог вариантов + карточка варианта, правка владельца 15.07
   пачка N+3, заменяет прежнюю тяжёлую заливку) ── */
.badge.perfect { color: var(--gold); background: var(--gold-soft); border: 1.1px solid var(--gold); font-weight: 600; }

/* ── полная карточка задачи (каталог, «Мои задачи» — эпик F): id · статус ·
   уровень, затем полное условие, затем теги. Правый кластер шапки
   (статус+индикатор уровня) прижат вправо через .tf-right. Изначально жила
   только в catalog.html — переиспользуется my-tasks.html (правка владельца
   17.07), поэтому канонизирована здесь ── */
.task-full { display: block; text-decoration: none; color: inherit; }
.task-full:hover { border-color: var(--accent); }
.tf-head { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; margin-bottom: 11px; }
.tf-head .tf-right { margin-left: auto; display: inline-flex; align-items: center; gap: 11px; }
.tf-cond { font-size: 15.4px; line-height: 1.6; color: var(--text); margin: 0 0 13.2px; }
.tf-tags { color: var(--text-2); font-size: 13.75px; }

/* ── строка задачи (канонический вид в любом списке) ── */
.task-row {
  display: flex; align-items: center; gap: 15.4px; flex-wrap: wrap;
  padding: 13.2px 17.6px; border: 1.1px solid var(--line); border-radius: 8.8px; background: var(--surface);
  text-decoration: none; color: inherit;
}
.task-row + .task-row { margin-top: 8.8px; }
.task-id { font-family: "JetBrains Mono", monospace; font-size: 14.3px; font-weight: 550; color: var(--accent); }
.task-title { flex: 1; font-size: 15.95px; min-width: 132px; }
.task-lvl { font-size: 13.75px; color: var(--text-2); }
.task-row:hover { border-color: var(--accent); }

/* ── формы ответа (answer_shape), разметка/поведение — из
   docs/mockups/answer-forms.html, цвета — на токены core ── */
.answer-cells { display: inline-block; max-width: 100%; overflow-x: auto; }
.answer-cells--single { display: inline-flex; align-items: center; gap: 11px; }
.answer-cells--single .ac-label { font-size: 14.3px; font-weight: 550; color: var(--text-2); white-space: nowrap; }
.answer-cells--single input {
  min-width: 154px; height: 48.4px; padding: 0 15.4px; text-align: center;
  font-family: "JetBrains Mono", monospace; font-size: 17.6px;
  color: var(--text); background: var(--bg);
  border: 1.1px solid var(--line); border-radius: 7.7px; outline: none;
}
.answer-cells--single input:focus { border-color: var(--accent); box-shadow: 0 0 0 3.3px var(--accent-soft); }
.answer-cells--single input:read-only { color: var(--text-2); background: var(--bg); }
.answer-cells table { border-collapse: collapse; font-family: "JetBrains Mono", monospace; table-layout: fixed; }
.answer-cells th {
  background: var(--bg); color: var(--text-2); font-weight: 550; font-size: 14.3px;
  border: 1.1px solid var(--line); height: 48.4px; text-align: center; padding: 0 8.8px;
}
/* правка владельца 17.07: было background/border-color: transparent — угловая
   ячейка выпадала из border-collapse (соседние грани резались до прозрачных),
   сетка казалась рваной. Теперь угловая ячейка — такая же полноценная ячейка
   сетки, как colh/rowh, просто без подписи. */
.answer-cells th.corner { width: 48.4px; background: var(--bg); }
.answer-cells th.rowh { width: 48.4px; }
.answer-cells th.colh { width: 121px; min-width: 70.4px; }
.answer-cells td { border: 1.1px solid var(--line); padding: 0; background: var(--surface); height: 48.4px; width: 121px; }
/* правило клетки сетки scoped на td: без него оно шло ПОСЛЕ
   .answer-cells--single input при равной специфичности и снимало у
   однострочного поля рамку и фон — поле ответа становилось невидимым */
.answer-cells td input {
  width: 100%; height: 48.4px; min-width: 48.4px; border: 0; background: transparent;
  text-align: center; font-family: "JetBrains Mono", monospace; font-size: 16.5px; color: var(--text); outline: none;
}
.answer-cells td input:focus { outline: 2.2px solid var(--accent); outline-offset: -2.2px; background: var(--accent-soft); }

/* ── форма-таблица R×C с запасом строк (правка владельца 14.07, №25 — до 10
   строк пар, мокапный компромисс 5×2): модификатор поверх .answer-cells. ── */
.answer-grid-hint { color: var(--text-2); font-size: 13.2px; margin-top: 8.8px; }
.answer-grid th { font-size: 13.2px; }

/* «Ответ» стоит после инструментов в том же контейнере. Базовый селектор
   .section-label:first-child обнуляет отступ, потому что это первый узел
   внутри якоря #task-answer; возвращаем отдельный, осмысленный интервал. */
.task-answer-anchor > .task-answer-label { margin-top: 30.8px; }

/* ── карта 27 номеров ── */
/* overflow-x — на узких экранах 9 колонок не влезают в контейнер; скроллится
   внутри себя (как .code), а не раздувает страницу.
   Правка владельца 18.07, волна 4, п. 4: раньше ячейка была фиксированной
   44px и сетка (396px) жалась влево широких карточек (container-work —
   1254px) — справа пустовала половина карточки. Теперь ячейка растёт от
   40 до 61.6px вслед за доступной шириной (minmax), а justify-content:center
   центрирует получившийся блок треков внутри карточки без вычисления
   отдельного max-width самой сетки — на 1254px-карточке блок ~624.8px и по
   центру, на 1760px+ не раздувается (container-work сам капается на
   1254px, поэтому больше 61.6px ячейка никогда не станет). Квадратность
   ячейки теперь держит aspect-ratio, а не жёсткая height — она честно
   следует фактической ширине трека на любом промежуточном значении. */
.map27 { display: grid; grid-template-columns: repeat(9, minmax(44px, 61.6px)); justify-content: safe center; gap: 8.8px; max-width: 100%; overflow-x: auto; }
.map27 span, .map27 a {
  aspect-ratio: 1; border-radius: 6.6px; border: 1.1px solid var(--line);
  display: flex; align-items: center; justify-content: center;
  font-family: "JetBrains Mono", monospace; font-size: 13.75px; color: var(--text-2);
  background: var(--surface); cursor: default;
}
.map27 a { text-decoration: none; cursor: pointer; }
.map27 .solved  { background: var(--cell-solved); border-color: var(--cell-solved); color: var(--on-accent); font-weight: 550; }
.map27 .touched { background: var(--cell-touched); border-color: var(--cell-touched); color: var(--text); }
/* охват по количеству решённых (правка владельца 11.07): градации интенсивности акцента —
   0 / 1–2 / 3–5 / 6+. .solved/.touched выше остаются алиасами для ДРУГОЙ семантики
   (отпечаток сложности варианта, .diff-bar в variant-card.html/variants-list.html) —
   их не трогать. */
.map27 .c0 { background: var(--surface); border-color: var(--line); color: var(--text-2); }
.map27 .c1 { background: var(--cell-touched); border-color: var(--cell-touched); color: var(--text); }
/* доля solved в средней ступени снижена 45%→30% (правка владельца 19.07,
   волна 7, аудит «цифры в клетках проваливаются по контрасту»): при 45%
   заливка выходила ровно в середину тона, и --text не контрастировал с ней
   ни в светлой, ни в тёмной теме. На 30% ступень уверенно держится на
   стороне --cell-touched, где --text читается в обеих темах; монотонность
   шкалы 0→1–2→3–5→6+ сохранена, направление ступеней в обеих темах одно —
   от фона к акценту (инверсия светлоты между темами неизбежна и не меняет
   порядок ступеней). */
.map27 .c2 {
  background: color-mix(in srgb, var(--cell-solved) 30%, var(--cell-touched));
  border-color: color-mix(in srgb, var(--cell-solved) 30%, var(--cell-touched));
  color: var(--text); font-weight: 600;
}
.map27 .c3 { background: var(--cell-solved); border-color: var(--cell-solved); color: var(--on-accent); font-weight: 550; }
.map27--mini { grid-template-columns: repeat(9, 30.8px); gap: 4.4px; }
.map27--mini span { height: 30.8px; border-radius: 5.5px; font-size: 11px; }

/* ── слой «точность» карты 27 (H/M/L/F — высокая/средняя/низкая/мало данных):
   жила локально дублем на student-home.html/student-card.html — теперь
   переиспользуется обоими без копипасты (правка владельца 17.07). group-page.html
   красит accuracy на СВОЕЙ разметке `.gmap-cells` (27-в-ряд, не 9×3 `.map27`) —
   там останется локальным, разный селектор ── */
.map27 .acc-high { background: var(--ok-soft); border-color: var(--ok-soft); color: var(--ok); font-weight: 550; }
.map27 .acc-mid  { background: var(--warn-soft); border-color: var(--warn-soft); color: var(--warn); font-weight: 550; }
.map27 .acc-low  { background: var(--err-soft); border-color: var(--err-soft); color: var(--err); font-weight: 550; }
/* «мало данных» — приглушено ЦВЕТОМ, без opacity (правка владельца 19.07,
   волна 7, аудит: opacity .5 поверх и без того приглушённого --text-2 роняла
   цифры ниже читаемого контраста в обеих темах) */
.map27 .acc-few  { color: var(--text-2); }

/* ── легенда карты (охват/точность) — общая для student-home.html,
   student-card.html, group-page.html (правка владельца 17.07, была
   продублирована втроём с идентичными правилами) ── */
.map-legend { display: flex; gap: 19.8px; flex-wrap: wrap; margin-top: 15.4px; font-size: 13.75px; color: var(--text-2); }
.map-legend span { display: inline-flex; align-items: center; gap: 6.6px; }
.map-legend i { width: 13.2px; height: 13.2px; border-radius: 3.3px; display: inline-block; }

/* ── «детальнее ▾» под картой — раскрывает таблицу по номерам (правка владельца
   17.07: была только на student-home.html, теперь нужна и student-card.html) ── */
.map-detail { margin-top: 15.4px; }
.map-detail-body { margin-top: 15.4px; }
.map-detail-body[hidden] { display: none; }

/* ── id варианта («В_003» и т.п.) — виден только репетитору (правка владельца
   12.07 полдень); data-role на body ставит nav.js в самом начале своей работы ── */
body:not([data-role="tutor"]) .tutor-only-id { display: none; }

/* ── действия, доступные только ВЛАДЕЛЬЦУ (правка владельца 18.07, волна 3,
   п. h): экспорт классной работы в zip существует ради /конспект-скилла и
   Obsidian-vault владельца — для остальных репетиторов платформы (is_admin
   не выставлен, vault'а нет) кнопка бессмысленна. data-owner="1" ставит
   nav.js только демо-роли «владелец» (тот же приём, что data-tier у
   tutor-premium) ── */
body:not([data-owner="1"]) .owner-only { display: none; }

/* ── отпечаток сложности варианта: 27 столбиков высотой по уровню
   (карточка варианта + список вариантов).
   Баг-фикс владельца 16.07 (пачка N+5, #6): столбики №6 и выше базового
   (повышенный 28.6px / гроб 37.4px) обрезались сверху — контейнер не резервировал
   места под самый высокий бар (37.4px) + gap (4.4px) + подпись .n (11px шрифт,
   но line-height наследуется от body:1.55 не переопределён → фактическая
   строка ≈17.05px, не 11px) + обводку .solved (outline 1.1px + offset 1.1px ≈
   2.2px клиренса сверху). align-items:flex-end сам по себе не клиппит, но
   overflow-x:auto на этом контейнере (нужен для горизонтального скролла
   27 баров на узких экранах) уводит computed overflow-y тоже в нeвidimый
   режим — без явного height-бюджета браузер мог схлопывать высоту до
   самого частого (базового) бара. Сайт целиком на box-sizing:border-box
   (глобальный `* {}` в начале файла) — min-height здесь считается ВМЕСТЕ
   с паддингом, это учтено в арифметике ниже. Фикс: контент максимум
   34(бар)+4(gap)+15.5(подпись)+2(обводка) ≈ 61.6px, паддинг 4+4=8.8px →
   min-height 70.4px с запасом; overflow-y:visible — подстраховка на случай,
   если браузер всё же уважает явное значение по этой оси. */
.diff-bars { display: flex; align-items: flex-end; gap: 4.4px; max-width: 100%; overflow-x: auto; overflow-y: visible; padding: 4.4px 0 4.4px; min-height: 70.4px; }
.diff-bar { display: flex; flex-direction: column; align-items: center; gap: 4.4px; flex: none; width: 19.8px; }
.diff-bar .bar { width: 100%; border-radius: 2.2px 2.2px 0 0; }
.diff-bar .n { font-size: 11px; line-height: 1.3; color: var(--text-2); }
.diff-bar.lvl-legkiy .bar    { background: var(--d1); }
.diff-bar.lvl-bazovyi .bar   { background: var(--d2); }
.diff-bar.lvl-povyshen .bar  { background: var(--d3); }
.diff-bar.lvl-grob .bar      { background: var(--grob); }
/* решённый номер — обводка accent на баре (правка владельца 14.07, карточка варианта;
   переиспользуется — модификатор общий с variants-list.html, там просто не выставляется) */
.diff-bar.solved .bar { outline: 1.1px solid var(--accent); outline-offset: 1.1px; }
.diff-bar.solved .n { color: var(--accent); font-weight: 600; }
/* компактный вариант — список вариантов: без подписей номеров (.n скрыт —
   гарантированно не участвует в высоте), тоньше; свой (меньший) min-height —
   мини-бары максимум 16.5px (гроб, JS-высоты 4/7/11/16.5px в variants-list.html),
   полный запас от .diff-bars был бы пустым полем. border-box: паддинг 1+1=2.2px
   входит в min-height, контент-бюджет 16.5px бара + 2.2px запас ≈ 18.7px → 20.9px. */
.diff-bars--mini { gap: 2.2px; padding: 1.1px 0; min-height: 20.9px; }
.diff-bars--mini .diff-bar { width: 5.5px; gap: 0; }
.diff-bars--mini .diff-bar .n { display: none; }

/* ── плотная таблица репетитора ── */
.table-dense { border-collapse: collapse; width: 100%; font-size: 14.85px; }
.table-dense th { text-align: left; font-weight: 550; color: var(--text-2); font-size: 13.75px; padding: 8.8px 13.2px; border-bottom: 1.1px solid var(--line); }
.table-dense td { padding: 9.9px 13.2px; border-bottom: 1.1px solid var(--line); }
.table-dense tr:last-child td { border-bottom: none; }
.table-dense .mono { font-size: 13.75px; }
.table-dense tr.few { color: var(--text-2); }

/* ── спарклайн активности (главная репетитора, вместо map27--mini в карточке) ──
   position:relative — правка владельца 18.07 (волна 3, п. a): якорь для
   .spark-tip ниже, подсказка спарклайна позиционируется абсолютно внутри
   этого блока (см. рендер в nav.js) */
.spark { display: flex; align-items: flex-end; gap: 3.3px; height: 39.6px; position: relative; }
.spark i { flex: 1; min-width: 3.3px; border-radius: 2.2px 2.2px 0 0; background: var(--line); display: block; font-style: normal; cursor: default; }
.spark i.active { background: var(--accent); }

/* ── подсказка спарклайна (правка владельца 18.07, волна 3, п. a): наведение/
   тап на столбик .spark i — показывает эту плавающую карточку (дата жирным,
   строки «кто · сколько» по data-spark-days[i].by, если разбивка есть, иначе
   просто «дата · решено N»), снизу приглушённое «всего N». Спарклайн уже
   канонический компонент трёх экранов (tutor-home/students/group-page) —
   подсказка едет вместе с ним сюда же, а не остаётся локальной. Позиция —
   по центру столбика, left считает JS (nav.js) и прижимает края внутрь
   .spark, чтобы не вылезала за карточку. Раскрывается ВНИЗ (top, не bottom) —
   спот-чек владельца 18.07: спарклайн стоит в первой карточке страницы, и
   подсказка вверх налезала на h1 «Главная»; вниз она ложится на собственную
   подпись спарклайна/карточку, что нормально для временного оверлея. Переключение видимости — класс
   .show, а не атрибут [hidden]: у .spark-tip и так свой display (нужен
   flex-direction для строк подсказки), а класс с явным display бьёт
   [hidden] (гоча уже разбиралась для .btn/.badge/.form-grid/.field ниже
   по файлу) — проще не создавать саму коллизию, чем добавлять ещё один
   guard-селектор */
.spark-tip {
  position: absolute; top: calc(100% + 8.8px); left: 0; z-index: 85;
  display: none; flex-direction: column; gap: 2.2px;
  width: max-content; max-width: 220px;
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 8.8px;
  padding: 8.8px 11px; box-shadow: none; pointer-events: none;
  font-size: 13.2px; line-height: 1.4; color: var(--text); text-align: left;
}
.spark-tip.show { display: flex; }
.spark-tip-date { font-family: "JetBrains Mono", monospace; font-weight: 650; font-size: 13.75px; }
.spark-tip-row { color: var(--text); }
.spark-tip-total { color: var(--text-2); margin-top: 2.2px; }

/* ── карточка ученика в сетке репетитора (главная + страница группы) ── */
.pupil-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(264px, 1fr)); gap: 17.6px; }
.pupil-card { display: block; text-decoration: none; color: inherit; }
.pupil-card:hover { border-color: var(--accent); }
/* ── карточка ученика переверстана правкой владельца 19.07, волна 6
   («не нравится, как выглядят строки про дз и пробники»). Фиксированный
   каркас, без дублей: имя (+ маркер внимания) → спарклайн → ряд бейджей-
   метрик (дз состоянием-цветом, пробник — если есть) → приглушённый хвост
   «решено N · 2 недели». Раньше пробник шёл ДВАЖДЫ — строкой «пробник · 76»
   и бейджем-флагом «сдал пробник · 76». Классы общие для students.html и
   group-page.html. Старые .pupil-status/.pupil-line/.pupil-flags удалены —
   в разметке обоих экранов заменены на структуру ниже. ── */
.pupil-head { display: flex; align-items: center; gap: 8.8px; margin-bottom: 13.2px; }
.pupil-name { font-weight: 600; font-size: 16.5px; }
/* маркер внимания — точка у имени (просрочка/ждёт разбора); не бейдж, чтобы
   не спорить с цветными бейджами метрик ниже */
.pupil-alert { width: 7.7px; height: 7.7px; border-radius: 50%; background: var(--err); flex: none; }
.pupil-alert--soft { background: var(--accent); }
.pupil-metrics { display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap; margin-top: 13.2px; }
.pupil-note { color: var(--text-2); font-size: 13.75px; }
.pupil-foot { color: var(--text-2); font-size: 13.75px; margin-top: 11px; }
.pupil-foot .mono { color: var(--text); font-weight: 600; }

/* ── блок группы (главная репетитора): выделяет секцию группы среди личных учеников ── */
.group-block {
  background: color-mix(in srgb, var(--surface) 40%, var(--bg));
  border: 1.1px solid var(--line); border-radius: 11px; padding: 22px; margin-bottom: 30.8px;
}
.group-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 13.2px; margin-bottom: 17.6px; flex-wrap: wrap; }
/* обёртка заголовок+гейт-заметка (правка владельца 17.07: раньше это была
   <a class="group-head-link"> — целиком блок группы стал кликабельным через
   onclick, заголовок больше не отдельная ссылка, титры — просто колонка) */
.group-titles { display: flex; flex-direction: column; gap: 4.4px; flex: 1; min-width: 0; }
.group-title { font-size: 17.6px; font-weight: 600; }
.group-note { color: var(--text-2); font-size: 13.75px; margin-top: 4.4px; }
.group-head .actions { display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap; }
.group-head .actions .link { white-space: nowrap; }

/* ── универсальная строка списка ── */
.list-row {
  display: flex; align-items: center; gap: 13.2px;
  min-height: 48.4px; padding: 11px 4.4px; border-bottom: 1.1px solid var(--line);
  font-size: 15.4px; color: inherit; text-decoration: none;
}
.list-row:last-child { border-bottom: none; }
.list-row .lr-text { flex: 1; min-width: 0; }
.list-row .lr-icons { display: flex; gap: 8.8px; color: var(--text-2); font-size: 14.3px; }
a.list-row:hover { color: var(--accent); }
/* строка-ссылка со своей кнопкой внутри («снять» у назначенного пробника на
   group-page): пока курсор на кнопке, текст строки не подкрашивается — иначе
   подсвечены оба (правка владельца 19.07, волна 5, п. c) */
a.list-row:hover:has(a:hover, button:hover) { color: inherit; }

/* ── строка списка, кликабельная ЦЕЛИКОМ через JS (div с делегированным click,
   не <a>) — модификатор поверх .list-row, добавляет рамку-подсветку по
   наведению. Правка владельца 19.07, волна 5, п. a/b/c: раньше жила дублем в
   .probe-row (student-home.html, разбор пробников) и .attempt-row
   (variant-card.html, список попыток варианта) — канонизирована сюда при
   втором использовании. Обе точечные жалобы владельца решены заменой border
   на outline:
   п. a — рамка на hover рисовалась border'ом почти впритык к тексту:
   .list-row даёт всего 4.4px горизонтального паддинга (рассчитан на строки БЕЗ
   собственной рамки — границу несёт card-обёртка), а строке с СВОЕЙ рамкой
   нужно больше воздуха. Паддинг здесь ≥ горизонтального паддинга .card (24.2px).
   п. b — border реагировал на .list-row:last-child { border-bottom: none }
   (нижняя грань гасла на последней строке, подсветка казалась «разомкнутой
   вниз»/вытекающей за карточку). outline — независимый от border слой, не
   участвует в этом каскаде вообще и не завязан на border-bottom соседей;
   outline-offset отрицательный держит кольцо ВНУТРИ рамки строки, повторяя её
   геометрию один в один, включая последнюю строку без правок под неё отдельно.
   Тот же приём уже есть в lesson-tutor.html (.hwmx-cell:hover).
   п. c — вложенный ховер: наведение на дочернюю ссылку/кнопку (чип ошибки,
   «открыть») не должно подсвечивать ещё и всю строку — :has() гасит контур
   строки, пока курсор физически над дочерним интерактивным элементом. Не JS:
   :has() — Selectors 4, базовая поддержка всех актуальных движков (Chrome/
   Edge/Safari с 2023, Firefox с 2023) — код мокапов и так не целится в
   браузеры древнее этого (details/summary, accent-color, color-mix() без
   префиксов и фолбэков по всему core.css). ── */
.list-row.list-row--hoverable {
  padding: 11px 24.2px;
  border-radius: 6.6px;
  cursor: pointer;
  outline: 1.1px solid transparent;
  outline-offset: -1.1px;
}
.list-row--hoverable:hover { outline-color: var(--accent); }
.list-row--hoverable:hover:has(a:hover, button:hover) { outline-color: transparent; }
/* …но подсветка обязана ПЕРЕЕХАТЬ на сам дочерний элемент, а не пропасть
   совсем (спот-чек владельца 19.07: «полностью убрал обводку в ховере» —
   гашение контура строки читалось как отсутствие подсветки вообще, потому
   что у чипа-номера собственный ховер был еле заметен: смена цвета 1.1px-
   рамки на и без того цветном .err-chip-фоне). Кольцо ребёнка — тот же
   контур тем же accent, только offset ПОЛОЖИТЕЛЬНЫЙ: чип маленький,
   кольцо снаружи читается лучше, чем внутри. Скоуп — только строки
   .list-row--hoverable: чипы-фильтры на других экранах живут своей
   логикой .active и лишнее кольцо им не нужно. */
.list-row--hoverable .chip:hover,
.list-row--hoverable .btn:hover {
  outline: 1.1px solid var(--accent);
  outline-offset: 1.1px;
  border-color: var(--accent);
}

/* ── пустое состояние ── */
.empty { color: var(--text-2); font-size: 15.4px; padding: 11px 4.4px; }
.empty a.link { margin-left: 6.6px; }

/* ── код ── */
.code {
  background: var(--bg); border: 1.1px solid var(--line); border-radius: 8.8px;
  padding: 15.4px 17.6px; font-family: "JetBrains Mono", monospace; font-size: 14.3px; line-height: 1.6;
  overflow-x: auto;
}
.code .k { color: var(--accent); font-weight: 550; }
.code .c { color: var(--text-2); }
.code .ln { display: inline-block; width: 1.6em; margin-right: 13.2px; color: var(--text-2); user-select: none; text-align: right; }
.code .line { display: block; padding: 1.1px 6.6px; margin: 0 -6.6px; border-radius: 4.4px; white-space: pre; }
.code .line.hl { background: var(--accent-soft); }

/* ── подсветка синтаксиса Python (#18, правка владельца 16.07, расширена
   правкой владельца 17.07 — «ещё цветастее»): тема-независимые токен-классы
   для статических код-блоков, расставляет pyhl.js (regex-токенизатор,
   докладывает <span class="tok-…"> вокруг токенов, экранирует HTML).
   Продовая подсветка будет серверной — это мокап-уровень (backend-ideas
   п.39). Палитра — почти целиком уже существующие токены core.css;
   исключение — --tok-const/--tok-self (см. блок токенов выше), заведены
   специально под код, т.к. существующие семантические цвета (ok/err/warn/
   d1–d3/grob) заняты статусами и сложностью — переиспользовать их для кода
   означало бы врать смыслом. Декораторы/f-строки — не новый hex, а
   курсив/жирность поверх уже занятых цветов, чтобы не превращать блок в
   «ёлку». */
/* .tok-kw/.tok-num/.tok-fn — правка владельца 19.07, волна 5, п.3 (h): были
   псевдонимами --accent/--accent-mid/--warn (см. новые --tok-kw/--tok-num/
   --tok-fn в блоке токенов выше — там же полное обоснование и почему тёмная
   тема не меняется). */
.tok-kw { color: var(--tok-kw); font-weight: 600; }
.tok-str { color: var(--ok); }
.tok-num { color: var(--tok-num); }
.tok-comment { color: var(--text-2); font-style: italic; }
.tok-fn { color: var(--tok-fn); }
.tok-builtin { color: var(--gold); }
.tok-op { color: var(--text-2); }
.tok-const { color: var(--tok-const); font-weight: 600; }
.tok-self { color: var(--tok-self); }
.tok-deco { color: var(--gold); font-style: italic; }
.tok-interp-brace { color: var(--accent); font-weight: 700; }

/* ── заглушка иллюстрации к условию (задачи с рисунком/графом — карточка
   задачи, каталог); вынесена из task-card.html в core.css, т.к. переиспользует
   catalog.html (правка владельца 15.07) ── */
.img-placeholder {
  aspect-ratio: 16 / 9; margin-top: 17.6px; border-radius: 8.8px;
  background: var(--line); display: flex; align-items: center; justify-content: center;
  color: var(--text-2); font-size: 14.3px;
}
/* ── модификатор для каталога задач: базовый .img-placeholder на детальной
   карточке задачи (task-card.html) крупный — это уместно там, а в каталоге
   (превью в свёрнутой карточке) был во всю ширину, что владелец счёл
   баннером; ужимаем ТОЛЬКО в каталоге через доп. класс (правка 15.07) ── */
.img-placeholder--inline { max-width: 374px; }

/* ── поля форм (канонический вид input/select/textarea) ── */
.field { display: flex; flex-direction: column; gap: 6.6px; }
.field label { font-size: 13.75px; color: var(--text-2); font-weight: 550; }
.input {
  font: inherit; font-size: 15.4px; color: var(--text);
  background: var(--bg); border: 1.1px solid var(--line); border-radius: 7.7px;
  padding: 9.9px 13.2px; outline: none; width: 100%;
}
.input:focus { border-color: var(--accent); box-shadow: 0 0 0 3.3px var(--accent-soft); }
textarea.input { resize: vertical; min-height: 88px; font-family: inherit; line-height: 1.5; }
input[type="file"].input { padding: 7.7px 11px; }
/* ── select.input: каретка отодвинута от края (правка владельца 15.07,
   пачка N+3) — была впритык к правому краю, теперь свой воздух;
   кастомная svg-каретка (шеврон вниз), т.к. appearance:none убирает
   нативную. Цвет каретки — hex-литерал, ИСКЛЮЧЕНИЕ из правила
   «цвета только через var()»: в data-URI переменную не подставить,
   поэтому захардкожены ровно значения --text-2 светлой/тёмной темы ── */
select.input {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%236E6259' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13.2px center;
  padding-right: 37.4px;
}
:root[data-theme="dark"] select.input {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23A39588' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) select.input {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6' fill='none'%3E%3Cpath d='M1 1L5 5L9 1' stroke='%23A39588' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
}
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(231px, 1fr)); gap: 15.4px; }
.check-row { display: flex; align-items: center; gap: 6.6px; font-size: 15.4px; cursor: pointer; } /* gap 8→6.6px — чекбокс ближе к тексту, правка владельца 15.07 */

/* ── мультивыбор фильтра (правка владельца 15.07, пачка N+3): решение
   владельца — ВСЕ селект-фильтры сайта переводятся на мультивыбор
   (образец — раскрывающийся список с чекбоксами, +/− у групп, крестик
   сброса в шапке). Компонент на нативном <details> — без тяжёлого JS.
   Разметка (переиспользуй на любом экране):
   <details class="multiselect">
     <summary class="ms-field"><span class="ms-value" data-ms-value>Любая</span><span class="ms-caret"></span></summary>
     <div class="multiselect-panel">
       <div class="ms-panel-head"><span>Тип</span><button type="button" class="ms-reset" data-ms-reset>сбросить ×</button></div>
       <!-- одноуровневый список: -->
       <div class="ms-options"><label class="ms-option"><input type="checkbox" value="…">Текст</label>…</div>
       <!-- ИЛИ двухуровневое дерево: -->
       <details class="ms-group" open><summary>Официальные</summary>
         <div class="ms-options">…</div>
       </details>
     </div>
   </details>
   Живое поведение (реальная фильтрация) или презентационное (чекбоксы
   переключаются, значение поля обновляется, список карточек не пересобирается)
   — решает конкретный экран через свой <script>. ── */
.multiselect { position: relative; }
.multiselect > .ms-field {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 8.8px;
  font: inherit; font-size: 15.4px; color: var(--text);
  background: var(--bg); border: 1.1px solid var(--line); border-radius: 7.7px;
  padding: 9.9px 13.2px; width: 100%;
}
.multiselect > .ms-field::-webkit-details-marker { display: none; }
.multiselect[open] > .ms-field { border-color: var(--accent); box-shadow: 0 0 0 3.3px var(--accent-soft); }
.ms-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-2); }
.ms-value.has-value { color: var(--text); font-weight: 550; }
/* каретка — CSS-треугольник рамкой (не картинка), цвет через var(), без
   hex-исключений; поворачивается на 180° при открытой панели */
.ms-caret { flex: none; width: 8.8px; height: 8.8px; position: relative; margin-top: -2.2px; }
.ms-caret::before {
  content: ""; position: absolute; left: 1.1px; top: 0; width: 7.7px; height: 7.7px;
  border: solid var(--text-2); border-width: 0 1.65px 1.65px 0; transform: rotate(45deg);
}
.multiselect[open] .ms-caret::before { top: 3.3px; transform: rotate(225deg); }
/* ── гоча закрытого <details>: у панелей задан свой display (block/flex), а
   он перебивает скрытие содержимого закрытого details — панель рисуется
   position:absolute за краем и раздувает горизонтальный скролл (заметно на
   412.5px: шапка и фильтры каталога давали до 143px переполнения). Гасим явно,
   тот же приём, что guard'ы [hidden] в остальном ядре ── */
details:not([open]) > .panel,
details:not([open]) > .multiselect-panel { display: none; }

.multiselect-panel {
  position: absolute; top: calc(100% + 6.6px); left: 0; z-index: 70;
  min-width: 100%; width: max-content; max-width: min(374px, 90vw);
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 8.8px;
  padding: 8.8px; box-shadow: none; max-height: 352px; overflow-y: auto;
}
.ms-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 11px;
  font-family: "JetBrains Mono", monospace; font-size: 12.65px; font-weight: 600;
  color: var(--text-2); text-transform: uppercase; letter-spacing: .06em;
  padding: 2.2px 6.6px 8.8px; margin-bottom: 4.4px; border-bottom: 1.1px solid var(--line);
}
.ms-reset { border: none; background: transparent; color: var(--text-2); cursor: pointer; font: inherit; font-size: 13.2px; padding: 2.2px 4.4px; border-radius: 4.4px; }
.ms-reset:hover { color: var(--accent); }
.ms-options { display: flex; flex-direction: column; gap: 1.1px; }
.ms-option {
  display: flex; align-items: center; gap: 8.8px;
  min-height: 35.2px; padding: 4.4px 6.6px; border-radius: 6.6px; cursor: pointer;
  font-size: 14.85px; color: var(--text);
}
.ms-option:hover { background: var(--accent-soft); }
.ms-option input[type="checkbox"] { width: 16.5px; height: 16.5px; accent-color: var(--accent); flex: none; }
/* группа дерева (второй уровень) — вложенный details, +/− через ::before */
.ms-group { border: none; }
.ms-group > summary {
  list-style: none; cursor: pointer; display: flex; align-items: center; gap: 7.7px;
  font-size: 14.85px; font-weight: 600; color: var(--text); padding: 6.6px; border-radius: 6.6px;
}
.ms-group > summary::-webkit-details-marker { display: none; }
.ms-group > summary::before {
  content: "+"; display: inline-flex; align-items: center; justify-content: center;
  width: 15.4px; flex: none; color: var(--text-2); font-family: "JetBrains Mono", monospace;
}
.ms-group[open] > summary::before { content: "−"; }
.ms-group > summary:hover { color: var(--accent); }
.ms-group:hover > summary::before { color: var(--accent); }
.ms-group .ms-options { padding-left: 22px; margin-bottom: 2.2px; }
.ms-group + .ms-group { margin-top: 1.1px; }

/* ── подсказка под тумблером «режим станции» — было локально только в
   variant-card.html, теперь переиспользуется и в variants-list.html
   (один тумблер на страницу, правка владельца 15.07) — вынесено в core ── */
.station-hint { font-size: 12.1px; color: var(--text-2); max-width: 462px; }
.check-row input[type="checkbox"] { width: 17.6px; height: 17.6px; accent-color: var(--accent); }

/* ── info-tip — маленький кружок-подсказка «?», текст всплывает по ховеру/фокусу
   (правка владельца 15.07: заменяет всегда-видимую .station-hint рядом с
   тумблером режима станции на variants-list.html — переиспользуемый компонент,
   без теней) ── */
.info-tip {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  width: 17.6px; height: 17.6px; border-radius: 50%; border: 1.1px solid var(--text-2);
  color: var(--text-2); font-size: 12.1px; line-height: 1; cursor: default; flex: none;
}
.info-tip:hover, .info-tip:focus-within { color: var(--accent); border-color: var(--accent); }
.info-tip__bubble {
  position: absolute; bottom: calc(100% + 8.8px); left: 50%; transform: translateX(-50%);
  z-index: 80; width: max-content; max-width: 286px;
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 7.7px;
  padding: 8.8px 11px; font-size: 13.2px; line-height: 1.4; color: var(--text);
  font-weight: 400; text-align: left; box-shadow: none;
  display: none;
}
.info-tip:hover .info-tip__bubble, .info-tip:focus-within .info-tip__bubble { display: block; }

/* ── модальное окно (переиспользуемое: новый урок и т.п.) ── */
.modal-overlay {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(0, 0, 0, .45);
  display: flex; align-items: center; justify-content: center; padding: 17.6px;
}
.modal-overlay[hidden] { display: none; }
.modal {
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 11px;
  padding: 24.2px; width: 100%; max-width: 528px; max-height: calc(100vh - 35.2px); overflow-y: auto;
}
.modal-head { display: flex; align-items: center; justify-content: space-between; gap: 13.2px; margin-bottom: 19.8px; }
.modal-head h2 { font-size: 18.7px; font-weight: 600; }
.modal-close {
  border: none; background: transparent; color: var(--text-2); cursor: pointer;
  font-size: 19.8px; line-height: 1; padding: 4.4px 6.6px; border-radius: 6.6px;
}
.modal-close:hover { color: var(--accent); }
.modal-actions { display: flex; align-items: center; gap: 11px; margin-top: 19.8px; }

/* ── выезжающая справа панель-«дровер» (правка владельца 16.07, #17, эпик W):
   «▦ Доска к задаче» на task-card.html/homework-solve.html открывает
   привязанную доску ИНЛАЙН, не выходя из задачи — внутри iframe с
   board.html. Переиспользуемый компонент (два экрана) — в core.css. */
.drawer-overlay {
  position: fixed; inset: 0; z-index: 210;
  background: rgba(0, 0, 0, .35);
  display: flex; justify-content: flex-end;
}
.drawer-overlay[hidden] { display: none; }
.drawer {
  width: min(616px, 92vw); height: 100%;
  background: var(--surface); border-left: 1.1px solid var(--line);
  display: flex; flex-direction: column;
}
.drawer-head {
  display: flex; align-items: center; justify-content: space-between; gap: 13.2px;
  padding: 13.2px 17.6px; border-bottom: 1.1px solid var(--line); flex: none;
}
.drawer-head h2 { font-size: 16.5px; font-weight: 600; }
.drawer-close {
  border: none; background: transparent; color: var(--text-2); cursor: pointer;
  font-size: 19.8px; line-height: 1; padding: 4.4px 6.6px; border-radius: 6.6px;
}
.drawer-close:hover { color: var(--accent); }
.drawer-body { flex: 1; min-height: 0; }
.drawer-body iframe { width: 100%; height: 100%; border: 0; display: block; }

/* ── kbd-чип («нажми /», подсказка горячей клавиши) ── */
.kbd {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: "JetBrains Mono", monospace; font-size: 12.1px; color: var(--text-2);
  border: 1.1px solid var(--line); border-radius: 4.4px; padding: 1.1px 6.6px; line-height: 1.5;
  background: var(--surface);
}

/* ── поле поиска с kbd-чипом внутри (каталог задач, каталог вариантов) ── */
.search-field { position: relative; flex: none; }
.search-field .input { padding-right: 37.4px; min-width: 242px; }
.search-field .kbd { position: absolute; right: 8.8px; top: 50%; transform: translateY(-50%); pointer-events: none; }

/* ── чипы (фильтры, вложения) ── */
/* десктоп — перенос строк; мобиле — одна строка, скролл внутри себя (см. .site-header) */
.chip-row { display: flex; gap: 6.6px; flex-wrap: wrap; }
@media (max-width: 700px) {
  .chip-row { flex-wrap: nowrap; overflow-x: auto; padding-bottom: 2.2px; }
}
.chip {
  display: inline-flex; align-items: center; gap: 5.5px; flex: none;
  font-family: "JetBrains Mono", monospace; font-size: 13.75px; font-weight: 550;
  color: var(--text-2); background: var(--surface); border: 1.1px solid var(--line);
  border-radius: 6.6px; padding: 6.6px 11px; cursor: pointer; text-decoration: none;
}
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip.active { background: var(--accent-soft); color: var(--text); border-color: var(--accent-soft); font-weight: 600; }

/* ── шапка (инжектится nav.js) ── */
.site-header {
  display: flex; align-items: center; gap: 28.6px;
  padding: 13.2px 35.2px;
  border-bottom: 1.1px solid var(--line);
  background: var(--surface);
  position: sticky; top: 0; z-index: 50;
}
.logo { display: flex; align-items: center; gap: 9.9px; font-weight: 650; font-size: 17.05px; letter-spacing: -.01em; text-decoration: none; color: var(--text); flex: none; }
.logo-mark { display: grid; grid-template-columns: repeat(3, 5.5px); gap: 2.2px; }
.logo-mark i { width: 5.5px; height: 5.5px; border-radius: 1.1px; background: var(--accent); opacity: .35; }
.logo-mark i:nth-child(1), .logo-mark i:nth-child(5), .logo-mark i:nth-child(6), .logo-mark i:nth-child(9) { opacity: 1; }
.site-nav { display: flex; align-items: center; gap: 22px; font-size: 15.95px; }
.site-nav a { color: var(--text-2); text-decoration: none; display: inline-flex; align-items: center; gap: 6.6px; }
.site-nav a.active { color: var(--text); font-weight: 550; }
.site-nav a:hover { color: var(--accent); }
.nav-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17.6px; height: 17.6px; padding: 0 4.4px; border-radius: 8.8px;
  background: var(--err); color: #fff; font-size: 11.55px; font-weight: 650;
  font-family: "JetBrains Mono", monospace;
}
.header-spacer { flex: 1; }
.header-actions { display: flex; align-items: center; gap: 11px; flex: none; }
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.1px solid var(--line); background: transparent; color: var(--text-2);
  border-radius: 7.7px; padding: 7.7px 11px; font: inherit; font-size: 14.85px; cursor: pointer;
}
.theme-toggle:hover { color: var(--accent); border-color: var(--accent); }
/* ── колокольчик «Требует внимания» в шапке (вместо пункта меню, правка 15.07) ── */
.bell-btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center;
  border: 1.1px solid var(--line); background: transparent; color: var(--text-2);
  border-radius: 7.7px; padding: 6.6px 9.9px; font-size: 16.5px; line-height: 1;
  text-decoration: none; cursor: pointer; overflow: visible;
}
.bell-btn:hover { color: var(--accent); border-color: var(--accent); }
.bell-btn.active { border-color: var(--accent); color: var(--accent); }
.bell-btn .bell-badge { position: absolute; top: -5.5px; right: -5.5px; }
.avatar {
  width: 33px; height: 33px; border-radius: 50%; flex: none;
  background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center;
  font-size: 13.2px; font-weight: 650; font-family: "JetBrains Mono", monospace;
  text-decoration: none;
}
.current-section { display: none; color: var(--text-2); font-size: 15.4px; }
.burger { display: none; position: relative; }
.burger summary {
  list-style: none; cursor: pointer; color: var(--text-2);
  display: inline-flex; align-items: center; justify-content: center;
  padding: 6.6px 9.9px; border-radius: 6.6px; border: 1.1px solid var(--line);
}
.burger summary::-webkit-details-marker { display: none; }
.burger[open] summary { border-color: var(--accent); color: var(--accent); }
.burger .panel {
  position: absolute; top: calc(100% + 6.6px); left: 0; min-width: 198px;
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 8.8px;
  padding: 6.6px; display: flex; flex-direction: column; gap: 2.2px; z-index: 60;
}
.burger .panel a {
  color: var(--text); text-decoration: none; padding: 8.8px 11px; border-radius: 6.6px; font-size: 15.4px;
  display: flex; align-items: center; gap: 6.6px;
}
.burger .panel a:hover { background: var(--accent-soft); }
.burger .panel a.active { color: var(--accent); font-weight: 550; }

/* Навигация репетитора перестаёт влезать раньше, чем телефонный
   breakpoint: на планшете и узком ноутбуке оставляем тот же компактный
   header, а не горизонтальный скролл страницы. Содержимое кабинета при
   этом остаётся планшетным. */
@media (max-width: 1100px) {
  .site-header { padding: 11px 17.6px; gap: 13.2px; }
  .site-nav { display: none; }
  .current-section { display: inline; }
  .burger { display: block; }
}

/* ============================================================
   Классная работа (эпик W) — маркер, доска, python-редактор.
   Продукт/модель — docs/superpowers/specs/2026-07-15-classwork-design.md.
   Экраны — docs/mockups-v2/task-card.html (маркер+python), board.html,
   lesson-student.html (секция «Классная работа»), classwork-item.html
   (просмотр снимка ученику). Движок канваса — draw.js (вешает обработчики
   по data-draw-* атрибутам ниже). Реального Pyodide/WASM в мокапе нет —
   python-панель имитирует запуск (мок прогресс-бара + пред-запечённый вывод).
   ============================================================ */

/* ── тулбар рисования: инструмент / цвет / толщина / отменить-очистить,
   группы разделены тонкой чертой; поведение — draw.js по data-атрибутам ── */
.draw-toolbar {
  display: flex; align-items: center; gap: 17.6px; flex-wrap: wrap;
  background: var(--frame-bg); border: 1.1px solid var(--line); border-radius: 8.8px;
  padding: 11px 15.4px; margin-bottom: 15.4px;
}
.draw-toolbar[hidden] { display: none; }
.draw-group { display: flex; align-items: center; gap: 6.6px; }
.draw-group + .draw-group { padding-left: 17.6px; border-left: 1.1px solid var(--line); }
.draw-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 33px; height: 33px; border-radius: 6.6px; border: 1.1px solid var(--line);
  background: var(--surface); color: var(--text-2); font-size: 15.4px; line-height: 1;
  cursor: pointer; padding: 0;
}
.draw-btn:hover { border-color: var(--accent); color: var(--accent); }
.draw-btn.active { background: var(--accent-soft); border-color: var(--accent); color: var(--text); }
/* свотчи цвета — кружки заливкой var(--…), сама заливка идёт через инлайн
   style="background:var(--…)" в разметке экрана (см. task-card.html/board.html) */
.draw-swatch {
  width: 24.2px; height: 24.2px; border-radius: 50%; border: 2.2px solid var(--surface);
  outline: 1.1px solid var(--line); cursor: pointer; padding: 0;
}
.draw-swatch.active { outline: 2.2px solid var(--text); }
/* толщина — три горизонтальные полоски нарастающей высоты внутри .draw-btn */
.draw-width i { display: block; width: 15.4px; border-radius: 2.2px; background: currentColor; color: var(--text-2); }
.draw-width.active i { color: var(--text); }
.draw-width[data-draw-width="2"] i { height: 2.2px; }
.draw-width[data-draw-width="4"] i { height: 4.4px; }
.draw-width[data-draw-width="8"] i { height: 7.7px; }

/* ── оверлей маркера: canvas поверх произвольного блока (условие задачи,
   холст доски). Интерактивен только пока хост в режиме рисования —
   вне этого режима клики проходят сквозь (текст условия остаётся
   выделяемым), поэтому пере­ключатель «Маркер» не мешает обычному чтению ── */
.marker-host { position: relative; }
.marker-layer {
  position: absolute; inset: 0; z-index: 5;
  /* canvas — replaced-элемент: inset:0 сам по себе НЕ растягивает его до
     размеров хоста (сохранял бы intrinsic 300×150), нужен явный 100%×100% */
  width: 100%; height: 100%;
  touch-action: none; pointer-events: none;
}
.marker-host.drawing .marker-layer { pointer-events: auto; cursor: crosshair; }

/* ── python-панель (карточка задачи, позже — тренажёр/станция): свёрнуто —
   кнопка «Открыть редактор», развёрнуто — «окно редактора» (.py-shell,
   правка владельца 17.07, волна 2: раньше была голая textarea на мягком фоне
   + тулбар НАД ней отдельным блоком, владелец счёл неаккуратным). Теперь —
   одна рамка со скруглением: шапка (имя файла · чип «в ФС» · Запустить/Стоп)
   → тело (гуттер номеров строк + textarea без своей рамки, тот же фон, что
   и гуттер, просвечивает сквозь transparent) → статус-строка → панель ВЫВОД
   со своей подписью — всё внутри одного окна, не разрозненными блоками
   страницы. Подсветка кода сознательно НЕ добавлена (#18 — textarea без
   спанов, решение #18 не пересматривается) — гуттер это не нарушает, это
   просто номера строк, не токены синтаксиса. JS синхронизации гуттера —
   там же, где и остальной py-JS (task-card.html, единственный экран с
   интерактивным редактором; classwork-item.html показывает только
   .py-output готовым текстом, без редактора и без гуттера). ── */
.py-panel { margin-top: 4.4px; }
.py-body[hidden] { display: none; }
.py-body { margin-top: 15.4px; }

.py-shell {
  border: 1.1px solid var(--line); border-radius: 8.8px; overflow: hidden;
  background: var(--frame-bg);
}
.py-shell:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3.3px var(--accent-soft); }
.py-shell-head {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  padding: 11px 15.4px; background: var(--surface); border-bottom: 1.1px solid var(--line);
}
.py-shell-filename { font-family: "JetBrains Mono", monospace; font-size: 13.75px; font-weight: 600; color: var(--text); }
.py-fs-chip {
  display: inline-flex; align-items: center; gap: 5.5px;
  font-size: 13.2px; color: var(--text-2); background: var(--frame-bg);
  border: 1.1px solid var(--line); border-radius: 6.6px; padding: 4.4px 9.9px;
}
.py-shell-actions { display: flex; align-items: center; gap: 8.8px; margin-left: auto; }

.py-shell-body { display: flex; align-items: stretch; }
/* гуттер — номера строк, невыделяемый (user-select:none), синхронизируется
   JS-ом и по вводу (число строк), и по скроллу textarea (scrollTop); тот же
   шрифт/line-height, что у .py-editor — строки совпадают один в один */
.py-gutter {
  flex: none; overflow: hidden; user-select: none; -webkit-user-select: none;
  padding: 15.4px 11px 15.4px 15.4px; text-align: right; min-width: 33px;
  font-size: 14.3px; line-height: 1.7; color: var(--text-2);
  background: var(--frame-bg); border-right: 1.1px solid var(--line);
}
/* редактор Pyodide — textarea, без спанов-подсветки (#18): теперь БЕЗ
   собственной рамки/фона — рамку и фокус-кольцо даёт .py-shell целиком
   (.py-shell:focus-within выше), фон общий с гуттером (transparent
   просвечивает --frame-bg родителя) */
.py-editor {
  flex: 1; display: block; width: 100%; min-height: 176px; resize: vertical; box-sizing: border-box;
  background: transparent; color: var(--text); border: 0; border-radius: 0;
  padding: 15.4px 17.6px; font-family: "JetBrains Mono", monospace; font-size: 14.3px; line-height: 1.7;
  outline: none; white-space: pre; overflow: auto;
}

.py-loading[hidden] { display: none; }
.py-loading { padding: 11px 17.6px; background: var(--frame-bg); border-top: 1.1px solid var(--line); }
.py-loading-bar { height: 4.4px; border-radius: 2.2px; background: var(--line); overflow: hidden; }
.py-loading-bar i { display: block; height: 100%; width: 0%; background: var(--accent); border-radius: 2.2px; transition: width 1s linear; }
.py-loading-label { display: block; margin-top: 6.6px; font-size: 13.2px; color: var(--text-2); }

/* статус-строка — часть окна редактора, не отдельный текст страницы */
.py-shell-status {
  padding: 7.7px 17.6px; font-size: 12.1px; color: var(--text-2);
  background: var(--surface); border-top: 1.1px solid var(--line);
}

/* панель ВЫВОД — своя подпись + моно-текст, часть окна (была отдельным
   .section-label страницы над голым .py-output) */
.py-shell-output { padding: 13.2px 17.6px 15.4px; background: var(--surface); border-top: 1.1px solid var(--line); }
.py-output-label {
  font-family: "JetBrains Mono", monospace; font-size: 12.1px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .08em; color: var(--text-2); margin-bottom: 8.8px;
}
/* .py-output — используется и ВНЕ .py-shell (classwork-item.html: готовый
   вывод снимка классной работы, без редактора и без окна) — правила не
   зависят от .py-shell-output, чтобы не сломать этот самостоятельный кейс */
.py-output {
  background: var(--frame-bg); border: 1.1px solid var(--line); border-radius: 8.8px;
  padding: 13.2px 15.4px; font-family: "JetBrains Mono", monospace; font-size: 14.3px; line-height: 1.6;
  white-space: pre-wrap; word-break: break-word; color: var(--text); min-height: 22px;
}
.py-output.err { color: var(--err); }
.py-actions { display: flex; align-items: center; gap: 11px; margin-top: 13.2px; flex-wrap: wrap; }

/* ── карточка-снимок классной работы (лента урока: превью рисунка + № +
   заголовок + свёрнутый код) и карточка доски в той же сетке ── */
.cw-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(242px, 1fr)); gap: 17.6px; }
/* ячейка сетки — контекст позиционирования для крестика .cw-del (крестик —
   СОСЕД ссылки-карточки, не потомок: клик по нему не ведёт по ссылке) */
.cw-cell { position: relative; display: flex; }
.cw-cell > .cw-card { flex: 1; }
.cw-card { position: relative; display: block; padding: 0; overflow: hidden; text-decoration: none; color: inherit; }
.cw-card:hover { border-color: var(--accent); }
.cw-preview {
  position: relative; aspect-ratio: 16 / 10; background: var(--frame-bg);
  border-bottom: 1.1px solid var(--line);
}
.cw-preview canvas { display: block; width: 100%; height: 100%; }
.cw-body { padding: 13.2px 15.4px 15.4px; }
.cw-title { display: flex; align-items: baseline; gap: 6.6px; font-size: 14.85px; font-weight: 600; margin-bottom: 8.8px; }
.cw-code { max-height: 83.6px; overflow: hidden; font-size: 13.2px; padding: 8.8px 11px; }
/* крестик удаления снимка/доски — только в режиме правки классной работы */
.cw-del {
  position: absolute; top: 8.8px; right: 8.8px; z-index: 6;
  width: 26.4px; height: 26.4px; border-radius: 50%; border: 1.1px solid var(--line);
  background: var(--surface); color: var(--err); font-size: 14.3px; line-height: 1;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.cw-del[hidden] { display: none; }
.cw-del:hover { border-color: var(--err); background: var(--err-soft); }
/* карточка доски в общей сетке — без превью-рисунка, просто ярлык-заглушка */
.cw-board-card { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8.8px; min-height: 165px; text-align: center; }
.cw-board-icon { font-size: 24.2px; color: var(--text-2); }

/* ── баннер режима урока (инжектится nav.js, только tutor): sticky сразу
   под шапкой сайта — точный top (высота .site-header) выставляет JS,
   т.к. высота хедера отличается на мобильной раскладке ── */
.lesson-banner {
  position: sticky; top: 0; z-index: 90;
  display: flex; align-items: center; gap: 13.2px; flex-wrap: wrap;
  padding: 11px 35.2px;
  background: var(--accent-soft); border-bottom: 1.1px solid var(--accent);
  font-size: 14.85px;
}
.lesson-banner .lb-text { display: inline-flex; align-items: center; gap: 8.8px; font-weight: 550; }
.lesson-banner .lb-actions { display: flex; align-items: center; gap: 8.8px; margin-left: auto; }
/* ── точка «идёт сейчас» (правка владельца 18.07, волна 4): раньше 🟢 перед
   «Идёт урок» — эмодзи-кружок рендерился по-разному на Windows/Android;
   это НЕ статус «готово» (галочка была бы неверной по смыслу — урок ещё
   идёт), поэтому не иконка-галочка, а простая цветная точка через
   существующий токен --ok, тот же приём, что легенды карт/сложности
   (.map-legend i, .lvl-legend i) ── */
.lb-dot { width: 8.8px; height: 8.8px; border-radius: 50%; background: var(--ok); flex: none; display: inline-block; }
@media (max-width: 700px) {
  .lesson-banner { padding: 11px 17.6px; }
}

/* ── guard [hidden] для канонических компонентов с собственным display:
   авторское правило display бьёт UA-правило [hidden]{display:none} (тот же
   гоча, что уже погашен для .context-plate/.modal-overlay локально) — для
   элементов, что переключаются атрибутом hidden из JS классной работы
   (.btn — «Стоп»/предзагрузка, .badge — «сохранено», .form-grid/.field —
   поля формы завершения урока в модалке nav.js) ── */
.btn[hidden], .badge[hidden], .form-grid[hidden], .field[hidden] { display: none; }

/* ============================================================
   Правки владельца 17.07 (пачка N+6) — каркас ролей: премиум-тир
   репетитора, режим «глазами ученика», аватар-дропдаун, гейт премиума.
   ============================================================ */

/* ── аватар-дропдаун (было — просто ссылка на account.html): тот же паттерн,
   что мобильный бургер (<details>/<summary>, закрытие по клику вне — общий
   обработчик в nav.js), только открывается от аватарки и панель прижата
   к правому краю (у бургера — к левому) ── */
.avatar-menu { position: relative; }
.avatar-menu > summary { list-style: none; cursor: pointer; }
.avatar-menu > summary::-webkit-details-marker { display: none; }
.avatar-menu .panel {
  position: absolute; top: calc(100% + 6.6px); right: 0; min-width: 231px;
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 8.8px;
  padding: 6.6px; display: flex; flex-direction: column; gap: 2.2px; z-index: 60;
}
.avatar-menu .panel a, .avatar-menu .panel button {
  color: var(--text); text-decoration: none; padding: 8.8px 11px; border-radius: 6.6px; font-size: 15.4px;
  display: flex; align-items: center; gap: 6.6px; text-align: left; border: none; background: none;
  font: inherit; cursor: pointer; width: 100%;
}
.avatar-menu .panel a:hover, .avatar-menu .panel button:hover { background: var(--accent-soft); }

/* ── баннер «Вы смотрите сайт как ученик» — тот же компонент, что баннер
   режима урока (.lesson-banner из core.css), просто другой текст/действие;
   инжектится nav.js, взаимоисключающе с баннером урока (одна плашка за раз) ── */
.student-view-banner { background: var(--frame-bg); border-bottom: 1.1px solid var(--line); }

/* ── премиум: тир — НЕ значение data-role (иначе разваливаются существующие
   селекторы body[data-role="tutor"]), а параллельный атрибут data-tier
   ("base"/"premium") на <body>, который ставит nav.js только когда
   отображаемая роль — tutor (в режиме «глазами ученика» роль отображается
   как student, атрибута data-tier тогда нет вовсе). Утилиты ниже —
   переключить base/premium-вариант одного и того же действия. Переименование
   free→base — правка владельца 18.07, волна 4: бесплатного тарифа нет,
   базовый и премиум оба платные (вход — триал), словарь `free` в коде
   вводил в заблуждение ── */
body[data-tier="premium"] .tier-base-only { display: none; }
body:not([data-tier="premium"]) .tier-premium-only { display: none; }

/* ── бейдж «✦ премиум» (статический, без клика) — отдельный оттенок от
   .badge.acc, чтобы не путать с «сдал пробник» и т.п. ── */
.badge.premium { color: var(--accent); background: var(--accent-soft); border: 1.1px solid var(--accent); }

/* ── премиум-замок: заменяет недоступное на base-тире действие (было —
   просто серый текст «✦ премиум»). Выглядит как ghost-кнопка, клик открывает
   модалку-апселл (см. паттерн ниже). Переиспользуется на students.html,
   group-page.html, student-card.html, catalog.html, variant-builder.html ── */
.premium-lock {
  display: inline-flex; align-items: center; gap: 6.6px;
  font: inherit; font-size: 13.75px; font-weight: 550; color: var(--text-2);
  background: var(--frame-bg); border: 1.1px solid var(--line); border-radius: 7.7px;
  padding: 6.6px 13.2px; cursor: pointer; white-space: nowrap;
}
.premium-lock:hover { border-color: var(--accent); color: var(--accent); }

/* ── предупреждение прямо в форме (напр. модалка «Назначить задачу»: выбран
   личный адресат — участник группы, тир base) — тот же смысл, что
   premium-lock, но строкой внутри формы, не отдельным контролом ── */
.premium-inline-warn {
  display: flex; align-items: center; gap: 8.8px; flex-wrap: wrap;
  background: var(--accent-soft); border: 1.1px solid var(--accent); color: var(--text);
  border-radius: 7.7px; padding: 11px 13.2px; font-size: 14.3px; margin-top: 15.4px;
}
.premium-inline-warn[hidden] { display: none; }

/* ── модалка-апселл (единая на все экраны с премиум-гейтом): разметка ВСЕГДА
   одинаковая, меняется только текст (data-upsell-text на триггере читает JS
   экрана и подставляет в [data-upsell-text-target]). Правка владельца 18.07,
   волна 4, п.2: голое «О тарифах» → href="#" раздражало уже ПЛАТЯЩЕГО
   базового клиента сильнее, чем гостя («замок без цены») — добавлена строка
   дельты цены (плейсхолдер .value-placeholder, см. ниже — цена не согласована
   владельцем, docs/backend-ideas.md п.61-62) и ссылка «сравнить тарифы» на
   реальный pricing.html вместо href="#". Переиспользуй как есть:
   <div class="modal-overlay" data-upsell-modal hidden>
     <div class="modal" role="dialog" aria-modal="true" aria-labelledby="upsell-title">
       <div class="modal-head"><h2 id="upsell-title">Доступно в Премиуме</h2>
         <button type="button" class="modal-close" data-upsell-close aria-label="Закрыть">✕</button></div>
       <p data-upsell-text-target>…</p>
       <p class="upsell-delta">Премиум: <span class="value-placeholder">+— ₽/мес</span></p>
       <div class="modal-actions">
         <a class="btn btn-primary" href="pricing.html">Сравнить тарифы</a>
         <button type="button" class="btn btn-ghost" data-upsell-close>Закрыть</button>
       </div>
     </div>
   </div>
   Триггеры — любой элемент с data-premium-upsell + data-upsell-text="…текст…".
   Использует .modal-overlay/.modal уже существующие, своих правил не требует. ── */
.upsell-delta { font-size: 14.85px; color: var(--text-2); margin-top: -6.6px; }

/* ============================================================
   Правки владельца 17.07 (пачка N+6) — волна 2: экраны ученика + станция.
   ============================================================ */

/* ── tip-host/tip-bubble: тултип с произвольным содержимым по ховеру/фокусу
   ХОСТА (в отличие от .info-tip триггер — сам контент/текст, не отдельная
   иконка «?»). Переиспользуется на solver-result.html (шкала первичный→
   тестовый над «первичный N/29» и «тестовый балл N») и trainer.html (список
   номеров тренировки при наведении на строку истории). Рамка+фон по токенам,
   без тени (box-shadow:none — тот же принцип, что у .info-tip__bubble).
   Разметка:
   <span class="tip-host" tabindex="0">…видимый текст…
     <span class="tip-bubble">…содержимое тултипа…</span>
   </span> ── */
.tip-host { position: relative; cursor: default; }
.tip-bubble {
  position: absolute; top: calc(100% + 8.8px); left: 0; z-index: 90;
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 8.8px;
  padding: 11px 13.2px; box-shadow: none; width: max-content; max-width: min(352px, 90vw);
  display: none; text-align: left; font-weight: 400;
}
.tip-host:hover .tip-bubble, .tip-host:focus .tip-bubble, .tip-host:focus-within .tip-bubble { display: block; }

/* ============================================================
   Правки владельца 17.07 — волна 2 (редизайны экранов, забракованных
   словом «не нравится»): главная репетитора, уроки репетитора, страница
   группы, корзина каталога, окно Python-редактора (выше), доска.
   ============================================================ */

/* ── лента событий (главная репетитора, tutor-home.html): хронология по
   дням, строка — время(mono, приглушённое) + кто + что, целиком кликабельна.
   Помечена владельцем как вероятно переиспользуемый компонент — поэтому
   здесь, а не локально в tutor-home.html, хотя сейчас использует её только
   один экран ── */
.feed-day {
  font-family: "JetBrains Mono", monospace; font-size: 12.65px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .08em; color: var(--text-2);
  padding: 15.4px 4.4px 6.6px;
}
.feed-day:first-child { padding-top: 6.6px; }
.feed-row {
  display: flex; align-items: baseline; gap: 13.2px;
  padding: 9.9px 4.4px; border-bottom: 1.1px solid var(--line);
  text-decoration: none; color: inherit; font-size: 14.85px;
}
.feed-row:last-child { border-bottom: none; }
.feed-row:hover { color: var(--accent); }
.feed-time { flex: none; color: var(--text-2); font-size: 13.75px; min-width: 46.2px; }
.feed-who { font-weight: 600; }
.feed-text { flex: 1; min-width: 0; }

/* ── карточка урока (лента репетитора/ученика): дата(mono, приглушённая) +
   тема в заголовке, статус справа; карточка кликабельна целиком (курсор +
   ховер-рамка). Правка владельца 17.07 (волна 2): раньше жила локальным
   дублем только на student-lessons.html — теперь её же идиому получают
   lessons.html (репетитор, редизайн «ближе к урокам ученика») и
   group-page.html (уроки группы) — канонизирована сюда при третьем экране ── */
.lesson-card { cursor: pointer; }
.lesson-card:hover { border-color: var(--accent); }
/* вложенный ховер (правка владельца 19.07, волна 5, п. c — тот же приём, что у
   .list-row--hoverable выше и .group-block на students.html): карточка урока
   несёт внутри свои чипы материалов и иконки правки/удаления — пока курсор над
   ними, подсвечивается только они, рамка карточки возвращается к покою, иначе
   выделяются оба сразу. Экраны: lessons, student-lessons, group-page. */
.lesson-card:hover:has(a:hover, button:hover) { border-color: var(--line); }
.lesson-card h3 { font-size: 18.7px; font-weight: 600; margin: 2.2px 0 4.4px; }
.lesson-row-head { display: flex; align-items: center; gap: 13.2px; flex-wrap: wrap; }
.lesson-row-head h3 { flex: 1; min-width: 0; margin: 0; }
.lesson-head-date { color: var(--text-2); font-weight: 500; }
.lesson-hw-row { display: flex; align-items: center; gap: 11px; flex-wrap: wrap; margin-top: 11px; }
.lesson-hw-row .mono { color: var(--text); font-weight: 650; }
/* активные уроки — акцентная подложка + левая грань (правка владельца
   17.07, волна 1, идиома .daily-card) */
.lesson-card.is-active { background: var(--accent-soft); border-left: 3.3px solid var(--accent); }

/* ── материалы урока — компактный ряд кнопок-чипов (было локально только на
   lesson-student.html; правка владельца 17.07, волна 2 — теперь и карточки
   ленты уроков репетитора/группы используют тот же ряд, канонизирована) ── */
.materials-row { display: flex; gap: 11px; flex-wrap: wrap; }
/* правка владельца 18.07, волна 4, дефект 10 — было временно закрыто
   word-spacing на общем текстовом узле «📄 Конспект» (эмодзи+слово одним
   узлом, gap:6.6px у .btn разводит только отдельные flex-элементы, не текст
   внутри одного узла). Теперь эмодзи заменены на <svg class="ic"><use>
   (см. блок «Иконки» в начале файла) — это САМ ПО СЕБЕ отдельный
   flex-элемент внутри уже-флексового .btn, костыль word-spacing больше не
   нужен и убран. */

/* ============================================================
   Правки владельца 18.07 — волна 4: монетизационная воронка
   (pricing.html, футер, апселлы с дельтой цены, read-only кабинет при
   неактивной подписке, секция «Тариф» в аккаунте). Раздел спеки —
   2026-07-11-ui-redesign-v2-design.md, «Правки владельца 18.07 — волна 4»,
   блок «2) Монетизационная воронка»; сумма фич по тирам — backend-ideas.md
   п.59-62.
   ============================================================ */

/* ── плейсхолдер ЕЩЁ НЕ СОГЛАСОВАННОГО владельцем числа (цена подписки,
   срок триала, лимиты базового тарифа — backend-ideas.md п.61) — ЗАМЕТНО
   оформлен (mono, тёплый warn-тон, пунктирное подчёркивание), чтобы бросался
   в глаза и его нельзя было забыть подставить реальным значением перед
   показом клиентам. Используется в pricing.html, апселл-модалках/инлайн-
   предупреждениях премиума и секции «Тариф» на account.html. ── */
.value-placeholder {
  font-family: "JetBrains Mono", monospace; font-weight: 700; color: var(--warn);
  border-bottom: 1.1px dashed var(--warn); white-space: nowrap; padding: 0 1.1px;
}

/* ── read-only кабинет при неактивной подписке (решение владельца 18.07,
   волна 4, п.4: «данные видны, действия заперты» — альтернатива «заглушка
   экрана целиком» отклонена). Помечает write-действия репетитора — «+ урок»,
   «назначить», «снять», «прорешать заново», «оставить ревью» и т.п. GET-
   навигация (карточки учеников, история, объекты) НЕ помечается этим
   классом — читать кабинет можно всегда, коллекция примеров пока —
   students.html/student-card.html (задача 4). При активном
   body[data-sub="expired"] элемент гаснет и перестаёт быть кликабельным;
   pointer-events — единственный способ «выключить» именно <a href>, у
   которой нет нативного disabled. В реальном проде это дополнится
   server-side 403 на тех же write-хендлерах (backend-ideas п.60) — здесь
   чисто клиентский демо-guard. ── */
body[data-sub="expired"] .write-action {
  opacity: .45;
  pointer-events: none;
  cursor: not-allowed;
}

/* ── плашка «подписка не активна» — модификатор поверх .lesson-banner
   (тёплый warn вместо accent-soft лесенки урока/«глазами ученика» — другой
   смысл, другой цвет по той же логике, что статусные токены). Инжектится
   nav.js на каждом экране с body[data-sub="expired"] ── */
.sub-expired-banner { background: var(--warn-soft); border-bottom-color: var(--warn); }

/* ── подвал сайта (задача 3): контакты/оферта/«для репетиторов»/«тарифы».
   Общий компонент — инжектится nav.js ПОСЛЕДНИМ элементом body (тот же
   приём, что шапка первым), а не копируется в разметку каждого экрана: 30+
   файлов мокапов делают ручное дублирование заведомо хуже одной точки
   истины. Появляется на каждом экране, где подключён nav.js (кроме
   probnik-station.html — она самодостаточна и не подключает nav.js вовсе,
   как и шапка/демо-плашка). ── */
.site-footer { border-top: 1.1px solid var(--line); background: var(--frame-bg); margin-top: 70.4px; padding: 35.2px; }
.footer-inner { max-width: 1254px; margin: 0 auto; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 26.4px; }
.footer-brand { display: flex; flex-direction: column; gap: 8.8px; max-width: 352px; font-weight: 650; font-size: 15.95px; }
.footer-brand p { margin: 0; color: var(--text-2); font-size: 14.3px; font-weight: 400; line-height: 1.5; }
.footer-links { display: flex; flex-wrap: wrap; gap: 8.8px 22px; font-size: 14.85px; align-content: flex-start; }
.footer-links a { color: var(--text-2); text-decoration: none; }
.footer-links a:hover { color: var(--accent); }
.footer-bottom { max-width: 1254px; margin: 22px auto 0; padding-top: 17.6px; border-top: 1.1px solid var(--line); color: var(--text-2); font-size: 13.2px; }
@media (max-width: 700px) {
  .site-footer { padding: 26.4px 17.6px; margin-top: 44px; }
  .footer-inner { flex-direction: column; gap: 19.8px; }
}

/* ============================================================
   Правка владельца 19.07, волна 5, п.2: предпросмотр условия по наведению.
   Общий компонент (не завязан на конкретный экран) — при наведении на
   строку задачи (`.task-row` или обёртку) показывает всплывающую панель с
   ПОЛНЫМ условием задачи, БЕЗ ответа/решения (экзаменационная семантика —
   ответу и решению в предпросмотре не место). Первое применение —
   lesson-student.html (классная работа + список домашки); задел на
   остальные списки задач (каталог, домашка-решатель, набор дз) — компонент
   и auto-init в nav.js уже общие, конкретному экрану достаточно расставить
   разметку (см. README).
   Разметка на строке-триггере:
     <a class="task-row" href="…" data-task-preview data-preview-num="27">
       …обычное содержимое строки…
       <template data-preview-cond><p>…полное условие…</p></template>
     </a>
   `<template>` — не рендерится браузером сам по себе (родная особенность
   тега, без доп. CSS) и не мешает flex-раскладке строки; JS (nav.js) читает
   .innerHTML шаблона и вставляет в общий (один на страницу, переиспользуемый,
   а не по узлу на строку) плавающий блок ниже. Позиционирование — JS,
   `position:fixed` в вьюпорте, прижимается внутрь окна по обеим осям (не
   вылезает ни вбок, ни вниз/вверх) — тот же принцип «прижать край», что уже
   есть у .spark-tip/.multiselect-panel, только в 2 измерениях сразу, т.к.
   блок крупнее и может не поместиться и по высоте. pointer-events:none —
   это чистая подсказка, не должна перехватывать клик по строке или по
   тому, что случайно окажется под ней. Задержка появления/сокрытия и
   guard «нет hover-указателя — компонента нет вовсе» (тач/узкий экран,
   тап продолжает вести по ссылке строки как обычно) — в nav.js. ── */
.task-preview {
  position: fixed; z-index: 95;
  width: min(550px, calc(100vw - 35.2px));
  max-height: min(60vh, 572px);
  overflow-y: auto;
  background: var(--surface); border: 1.1px solid var(--line); border-radius: 8.8px;
  padding: 15.4px 17.6px; box-shadow: none; pointer-events: none;
  display: none; font-size: 14.85px; line-height: 1.6; color: var(--text);
}
/* класс с явным display бьёт [hidden] — тут используем класс .show, а не
   атрибут, так что коллизии нет (та же гоча уже разбиралась у .spark-tip/
   .info-tip__bubble/.tip-bubble — здесь просто нет [hidden] на этом узле) */
.task-preview.show { display: block; }
.task-preview-head {
  font-family: "JetBrains Mono", monospace; font-size: 12.65px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .07em; color: var(--text-2);
  margin-bottom: 11px;
}
.task-preview p { margin: 0 0 11px; }
.task-preview p:last-child { margin-bottom: 0; }
