:root {
  --ground:#f5fafc; --surface:#fff; --ink:#17324d; --dim:#4c6076; --faint:#5d7086;
  --line:#dbe6ec; --accent:#0b6e80; --accent-soft:#e8f8fa; --accent-line:#bfe6ec;
  /* --line рисует декоративные рамки карточек, и её 1.26 к фону там уместны.
     Но той же линией была обведена рамка поля ввода, а она — единственный
     признак, что перед тобой поле: WCAG просит 3:1, поэтому у полей и кнопок
     своя граница. Значения подобраны как самые мягкие из проходящих порог */
  --field:#7e8c99;
  /* акцент под курсором: у заливной кнопки граница уже акцентная, и наведение
     на ней было ничем не видно */
  --accent-hi:#08525f;
  /* поверх акцентной заливки: в тёмной теме акцент яркий,
     и белая надпись на нём даёт всего 2.9 при норме 4.5 */
  --on-accent:#fff;
  --ok:#1e7a4c; --ok-soft:#e7f4ec; --err:#c0392b; --err-soft:#fdedeb;
  --warn:#976000; --warn-soft:#fbf1de;
  /* Второй фирменный цвет. Одна бирюза даёт холодный монохром: фиолетовый
     держит вторую половину градиента и второе свечение на фоне. */
  --accent2:#6539be; --accent2-soft:#eee8fc;
  /* Ещё два цвета той же семьи: ими разводятся плитки разделов, чтобы
     четыре дороги не выглядели одним блоком. Больше цветов не заводить —
     палитра держится на бирюзе и фиолете, остальное их оттенки. */
  --sky:#0a6a9e; --mint:#0c7a67;
  /* Цвета стеклянных шариков на фоне. Они одни на обе темы и нарочно не
     привязаны к акцентам: в светлой теме акцент затемнён ради контраста
     текста, а шарик от этого выходил грязно-серым. На шариках текста нет,
     и им можно быть яркими — такими, как на рисунке первого экрана. */
  --bub-a:#55c8d8; --bub-b:#7651c9; --bub-c:#4fe3cc;
  /* Цвет плитки по умолчанию; ряд разделов переопределяет его каждому */
  --tint:var(--accent);
  /* Фон страницы: координатная сетка, два свечения вверху, виньетка по
     краям. Полупрозрачные нарочно — они ложатся на любой фон темы. */
  --grid-line:rgba(23,50,77,.05);
  --glow-a:rgba(11,110,128,.13); --glow-b:rgba(101,57,190,.10);
  --vignette:rgba(23,50,77,.05);
  --shadow:0 1px 2px rgba(19,26,46,.05), 0 12px 32px -12px rgba(19,26,46,.12);
  --ease-out:cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out:cubic-bezier(0.77, 0, 0.175, 1);
  /* Три длительности на всё движение. Раньше их было одиннадцать вразнобой,
     и одинаковые по смыслу переходы шли с разной скоростью. */
  --t-press:120ms;   /* отклик на нажатие */
  --t-fast:160ms;    /* наведение, подсказка, выпадающее меню */
  --t-base:220ms;    /* смена состояния на экране */
  /* Unbounded — только крупные заголовки, цифры и знак платформы: он
     широкий и характерный, абзац им набирать нельзя. */
  --display:"Unbounded", var(--sans);
  /* Golos Text — основной шрифт интерфейса, теории и заданий: у него
     полноценная кириллица и спокойный рисунок на длинном тексте. Лежит
     рядом с сайтом; системный — только на время загрузки. */
  --sans:"Golos Text", -apple-system, BlinkMacSystemFont, ui-sans-serif,
    "Segoe UI", sans-serif;
  --mono:"JetBrains Mono", ui-monospace, Menlo, monospace;
  --page:1120px;

  /* Шкала отступов. Их было тридцать восемь значений на двести семьдесят два
     места: соседние блоки отстояли друг от друга на .55 и .6 без всякой
     причины, и вёрстка держалась на глазомере. Тринадцать ступеней подобраны
     по самим отступам, чтобы вид не поехал: заметно сдвинулись три места
     из двухсот семидесяти двух. */
  --s-3xs:0.2rem;
  --s-2xs:0.35rem;
  --s-xs:0.5rem;
  --s-sm:0.7rem;
  --s-md:0.9rem;
  --s-lg:1.1rem;
  --s-xl:1.4rem;
  --s-2xl:1.7rem;
  --s-3xl:2.1rem;
  --s-4xl:2.6rem;
  --s-5xl:3.0rem;
  --s-6xl:4.0rem;
  --s-7xl:6.0rem;


  /* Шкала кеглей. Раньше размеров было тридцать четыре, и половина
     встречалась по одному разу: подписи под соседними карточками отличались
     на сотую долю rem — глазом не поймать, но набор выглядит неряшливо.
     Одиннадцать ступеней на всё, от микрометки до заголовка главной. */
  --t-2xs:0.68rem;
  --t-xs:0.76rem;
  --t-sm:0.85rem;
  --t-md:0.93rem;
  --t-base:1.0rem;
  --t-lg:1.1rem;
  --t-xl:1.25rem;
  --t-2xl:1.45rem;
  --t-3xl:1.6rem;
  --t-4xl:1.8rem;
  --t-5xl:2.1rem;


  /* Радиусы: одна документированная шкала вместо восьми случайных значений.
     Правило: мелочь (бейдж, номер) — sm, интерактивное (кнопка, поле,
     вариант) — md, контейнеры (карточка, выпадашка) — lg. */
  --r-sm:6px; --r-md:12px; --r-lg:18px; --r-xl:24px; --r-full:999px;

  /* Стекло. Плавающее над страницей — шапка, меню, подсказка — не закрашивает
     содержимое, а пропускает его размытым: видно, что под панелью что-то есть
     и куда оно уехало. Материал только для плавающего: под текстом задания
     фон обязан быть плотным, иначе условие читается поверх чужих строк. */
  --glass:color-mix(in srgb, var(--surface) 72%, transparent);
  --glass-blur:saturate(1.8) blur(20px);
  /* волосяная грань сверху: у Apple ею отделяют стекло от того, что под ним */
  --glass-edge:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);

  /* Пружина: движение останавливается не сразу, а чуть перелетев и вернувшись,
     как физический предмет. Кривая записана точками — обычной кубической
     кривой перелёт не описать. */
  --ease-spring:linear(0, .006, .026, .056, .096, .213, .368, .55, .747, .864,
    .938, .984, 1.007, 1.014, 1.012, 1.007, 1.002, .999, .998, 1);

  /* Нативные элементы — флажок, выпадающий список, календарь, полоса
     прокрутки — рисует браузер, и он берёт цвет отсюда. Без этого
     переключатель темы менял страницу, а флажки оставались из другой:
     белые квадраты на тёмном. */
  color-scheme:light;

  /* Слои: раскладка по назначению, а не «поставлю 9999 и разберусь» */
  --z-sticky:20; --z-drop:30; --z-nav:40; --z-tip:60; --z-grain:70; --z-mark:80;
}
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) {
  color-scheme:dark; --ground:#07070d; --surface:#101019; --ink:#eeeef6; --dim:#b9b9cd; --faint:#8e8ea8;
  --line:#2e2e42; --accent:#55c8d8; --accent-soft:#0c2731; --accent-line:#1c4652;
  --field:#6e7287; --accent-hi:#4fe3cc;
  --on-accent:#04040a;
  --ok:#46d98b; --ok-soft:#0d2a1c; --err:#ff5f6d; --err-soft:#2c1216;
  --warn:#ffb03b; --warn-soft:#2c2010;
  --accent2:#a78bfa; --accent2-soft:#1a1230;
  --sky:#4cb8e8; --mint:#4fe3cc;
  --grid-line:rgba(238,238,246,.04);
  --glow-a:rgba(85,200,216,.10); --glow-b:rgba(118,81,201,.15);
  --vignette:rgba(4,4,10,.38);
  /* тень тонирована под фон: чистый чёрный на синеватом тёмном
     выглядит грязным пятном, а не глубиной */
  --shadow:0 1px 2px rgba(4,6,14,.55), 0 12px 32px -12px rgba(4,6,14,.7);
  --glass:color-mix(in srgb, var(--surface) 66%, transparent);
  --glass-edge:inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent); } }
:root[data-theme="dark"] {
  color-scheme:dark; --ground:#07070d; --surface:#101019; --ink:#eeeef6; --dim:#b9b9cd; --faint:#8e8ea8;
  --line:#2e2e42; --accent:#55c8d8; --accent-soft:#0c2731; --accent-line:#1c4652;
  --field:#6e7287; --accent-hi:#4fe3cc;
  --on-accent:#04040a;
  --ok:#46d98b; --ok-soft:#0d2a1c; --err:#ff5f6d; --err-soft:#2c1216;
  --warn:#ffb03b; --warn-soft:#2c2010;
  --accent2:#a78bfa; --accent2-soft:#1a1230;
  --sky:#4cb8e8; --mint:#4fe3cc;
  --grid-line:rgba(238,238,246,.04);
  --glow-a:rgba(85,200,216,.10); --glow-b:rgba(118,81,201,.15);
  --vignette:rgba(4,4,10,.38);
  /* тень тонирована под фон: чистый чёрный на синеватом тёмном
     выглядит грязным пятном, а не глубиной */
  --shadow:0 1px 2px rgba(4,6,14,.55), 0 12px 32px -12px rgba(4,6,14,.7);
  --glass:color-mix(in srgb, var(--surface) 66%, transparent);
  --glass-edge:inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent); }

* { box-sizing:border-box; }
/* label и прочие блочные правила перебивают hidden, и скрытое поле
   остаётся на виду — возвращаем атрибуту силу */
[hidden] { display:none !important; }
/* Колонка во всю высоту окна: на короткой странице — рейтинге с одним
   учеником, пустом списке работ — подвал повисал посреди экрана, а под ним
   зияла половина монитора */
body { margin:0; min-height:100dvh; display:flex; flex-direction:column;
  /* Фон — не ровная заливка: еле заметная координатная сетка и два
     цветовых свечения вверху. Свечения не повторяются и уходят вместе со
     страницей: ТЗ просит их только на первом экране, и под длинным
     условием задания цветное пятно мешало бы читать. */
  background:
    radial-gradient(58% 42% at 6% 0%, var(--glow-a), transparent 70%) no-repeat,
    radial-gradient(52% 38% at 96% 1%, var(--glow-b), transparent 70%) no-repeat,
    linear-gradient(var(--grid-line) 1px, transparent 1px) 0 0 / 100% 48px,
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px) 0 0 / 48px 100%,
    /* виньетка: края окна глубже середины. Закреплена за окном, а не за
       страницей, — иначе на длинном уроке она темнит середину текста */
    radial-gradient(128% 74% at 50% 0%, transparent 54%, var(--vignette))
      center / cover no-repeat fixed,
    var(--ground);
  color:var(--ink); font-family:var(--sans);
  font-size:16.5px; line-height:1.6; -webkit-font-smoothing:antialiased;
  /* На айфоне касание кнопки вспыхивает серым прямоугольником поверх нашей
     формы — свой отклик у нас есть, чужой мешает */
  -webkit-tap-highlight-color:transparent; }
:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
#taskcard:focus { outline:none; }
#taskcard:focus-visible { outline:2px solid var(--accent); outline-offset:4px; }
a { color:var(--accent); }
/* Высота строки у заголовков своя. Текстовые 1.6 рассчитаны на длинный
   абзац мелким кеглем; крупный заголовок с таким просветом распадается на
   отдельные строки, и «Кислородсодержащие органические вещества» читается
   как список, а не как название. Чем крупнее кегль, тем плотнее строки. */
h1,h2,h3 { letter-spacing:-.02em; font-weight:600;
  text-wrap:balance; line-height:1.25; }
/* Unbounded только на двух верхних ступенях. У него широкая посадка: он
   держит характер на крупном кегле и рассыпает строку на мелком, поэтому
   h3 и подписи внутри карточек остаются основным шрифтом. */
h1,h2 { font-family:var(--display); font-weight:700; letter-spacing:-.035em; }
h1 { line-height:1.15; }
/* Крупный заголовок: первая строка страницы держит экран одна. Unbounded
   шире привычного, поэтому кегль на ступень ниже прежнего — иначе
   «Кислородсодержащие органические вещества» не встаёт в две строки */
h1 { font-size:clamp(1.75rem, 1.45rem + 1.2vw, 2.15rem); letter-spacing:-.04em;
  margin:0 0 var(--s-xs); } h2 { font-size:var(--t-xl); margin:var(--s-3xl) 0 var(--s-md); }
h3 { font-size:var(--t-base); margin:0 0 var(--s-2xs); }
/* Заголовок без пояснения под ним прилипал к первой карточке: «Что задано»
   и карточка работы стояли в восьми точках друг от друга */
h1:not(:has(+ p, + .lead, + .auth-sub)) { margin-bottom:var(--s-lg); }

header { position:sticky; top:0; z-index:var(--z-sticky); background:var(--surface);
  border-bottom:1px solid var(--line); }
/* Шапка стоит поверх страницы всё занятие. Полупрозрачность с размытием даёт
   понять, что под ней что-то проехало, — но только там, где браузер умеет
   размывать: без него полупрозрачный фон превратился бы в мешанину строк. */
@supports (backdrop-filter: blur(1px)) {
  header { background:var(--glass); backdrop-filter:var(--glass-blur);
    -webkit-backdrop-filter:var(--glass-blur); box-shadow:var(--glass-edge); }
}
.bar { max-width:var(--page); margin:0 auto; display:flex;
  padding:var(--s-sm) max(var(--s-xl), env(safe-area-inset-right))
    var(--s-sm) max(var(--s-xl), env(safe-area-inset-left));
  gap:var(--s-2xs) var(--s-md); align-items:center; flex-wrap:wrap; }
/* навигация переносится внутри себя, а бренд и аккаунт держат свои края */
.nav { display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2xs) var(--s-md);
  margin-right:auto; min-width:0; }
.gap { margin-right:auto; }
/* на широком экране details не участвует в раскладке — виден просто ряд ссылок */
/* Шапка гостя: три ссылки до входа. На узком экране остаётся одна — рядом
   стоят «Войти», «Регистрация» и переключатель темы, и на 360 точках шапка
   ломалась на две строки */
.guest-nav a { white-space:nowrap; }
@media (max-width: 620px) { .guest-nav a:not(:first-child) { display:none; } }
.navbox { display:contents; }
.navbox > summary { display:none; }
/* у преподавателя пунктов вдвое больше — на планшете в них не влезает 1000px */
@media (max-width: 1100px) { .bar { gap:var(--s-2xs) var(--s-sm);
  padding:var(--s-sm) max(var(--s-lg), env(safe-area-inset-right))
  var(--s-sm) max(var(--s-lg), env(safe-area-inset-left)); } }
.bar a { text-decoration:none; color:var(--dim); font-size:var(--t-md); }
.bar a:hover { color:var(--accent); }
/* Текущий раздел. Подчёркивание, а не заливка: ряд из семи ссылок с плашкой
   превращается в пёстрый забор, а тонкая линия читается сразу и не спорит
   с акцентной кнопкой рядом. */
.bar a.here, .bar .menu > summary.here { color:var(--ink); font-weight:600;
  box-shadow:inset 0 -2px 0 var(--accent); }
.bar a.here:hover { color:var(--accent); }

/* Хлебные крошки: где я и как вернуться на шаг назад. */
.crumbs { display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2xs);
  font-size:var(--t-sm); color:var(--dim); margin:0 0 var(--s-xs); }
.crumbs a { color:var(--dim); text-decoration:none; }
.crumbs a:hover { color:var(--accent); text-decoration:underline; }
.crumbs span[aria-hidden] { color:var(--faint); }
.crumbs [aria-current] { color:var(--ink); }
.bar .brand { font-family:var(--display); font-size:var(--t-xl); font-weight:600;
  color:var(--ink); flex:none; }
main { max-width:var(--page); margin:0 auto; width:100%; flex:1 0 auto;
  padding:var(--s-3xl) max(var(--s-xl), env(safe-area-inset-right))
          var(--s-6xl)
          max(var(--s-xl), env(safe-area-inset-left)); }
.eyebrow { font-family:var(--mono); font-size:var(--t-2xs); text-transform:uppercase;
  letter-spacing:.12em; color:var(--faint); margin:var(--s-3xl) 0 var(--s-md); }
.lead { color:var(--dim); max-width:60ch; }

.grid { display:grid; gap:var(--s-sm); grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); }
/* Карточки работ своей ширины: одна-единственная растягивалась во всю полосу,
   и название с полоской прогресса терялись по краям */
.grid.cards-wide { grid-template-columns:repeat(auto-fill, minmax(min(100%, 19rem), 1fr)); }
/* Строка поиска: поле тянется, список и кнопка по содержимому */
.findrow { flex-wrap:nowrap; align-items:stretch; }
.findrow > .grow { flex:1; min-width:0; }
@media (max-width: 560px) { .findrow { flex-wrap:wrap; } .findrow .btn { flex:1; } }
.table { display:grid; gap:var(--s-xs); grid-template-columns:repeat(auto-fill,minmax(215px,1fr)); }
.card, .cell, .course { background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); box-shadow:var(--shadow); }
.card { padding:var(--s-xl) var(--s-2xl); border-radius:var(--r-lg); }
/* Две карточки подряд сливались рамками в одну двойную линию: сводка в
   кабинете и графики под ней, «Персональные данные» и «Телеграм» в профиле.
   В сетке промежуток задаёт сама сетка — там отступ сдвинул бы ряд. */
:is(.card, .startbar) + :is(.card, .startbar) { margin-top:var(--s-md); }
:is(.grid, .table, .courses, .modules, .offer, .pickers) > .card { margin-top:0; }
/* Заголовок в начале карточки: отступ над h2 рассчитан на поток страницы, и
   внутри плитки над названием варианта висела пустая полоса */
:is(.card, .course, .startbar) > :is(h2, h3):first-child { margin-top:0; }
/* Первое поле формы-карточки: отступ над подписью складывался с полем
   карточки, и форма начиналась с пустой полосы */
form.card > input[type=hidden]:first-child + label,
form.card > input[type=hidden]:first-child + .row > label:first-child,
/* то же, когда поля первой строки обёрнуты в колонки: «Цель на неделю»,
   «Привязать родителя» начинались с пустой полосы над подписью */
form.card > div.row:first-of-type > div > label,
.card > form.row:first-child > div > label { margin-top:0; }
/* Подписи в строке переносятся под друг друга, и зазор строки добавлялся
   к отступу подписи — поля шли неровно */
.row:has(> label) { row-gap:0; }
/* Ширина колонки с заданием. На широком экране карточка растягивалась во
   всю страницу, а условие и варианты оставались слева — половина карточки
   пустовала. Держим колонку такой, чтобы строка была около 70 знаков. */
/* Занятие идёт одной колонкой: заголовок, задание, подсказки и примечания
   стоят по одной границе. Разъехавшись, они читались как разные страницы. */
.lesson { max-width:46rem; margin-inline:auto; }
/* приглашение в телеграм и сообщение после действия стояли во всю страницу
   над узкой колонкой занятия и читались как чужой блок */
main:has(> .lesson) > :is(.flash, .tg-invite, .confirm-note) { max-width:46rem;
  margin-inline:auto; }
.actions-gap { margin-top:var(--s-xl); }
.actions-under { margin-bottom:var(--s-lg); }

/* ── рейтинг ─────────────────────────────────────────── */
.my-place { margin-bottom:var(--s-lg); border-color:var(--accent); }
.my-place .place { font-family:var(--mono); font-size:var(--t-xl); color:var(--accent); }
tr.mine { background:var(--accent-soft); }
td.score { font-family:var(--mono); color:var(--accent); font-weight:600; }
/* Условие — то, ради чего открыт экран, и читают его с формулами: индексы
   мельче основного текста в полтора раза, и на общем кегле «Fe2O3» уже плохо
   разбирается. Ставим на ступень крупнее и даём строкам чуть больше воздуха */
.q { font-size:var(--t-lg); line-height:1.55; margin:0 0 var(--s-lg); }
.task-meta { display:flex; gap:var(--s-xs); align-items:center; margin-bottom:var(--s-md);
  font-size:var(--t-xs); color:var(--dim); }
.course { display:block; padding:var(--s-lg) var(--s-lg); text-decoration:none; color:inherit;
  position:relative; overflow:hidden; }
@media (hover: hover) and (pointer: fine) {
  .course:hover, .cell:hover { border-color:var(--tint); transform:translateY(-2px); }
}
.cell { display:flex; flex-direction:column; min-height:118px; padding:var(--s-sm) var(--s-md);
  text-decoration:none; color:inherit; position:relative; }
.cell .head { display:flex; justify-content:space-between; font-family:var(--mono);
  font-size:var(--t-2xs); color:var(--faint);
  /* метка «новое» выше строки текста, и название в плитке с ней стояло ниже,
     чем у соседки без неё: ряд шёл лесенкой */
  align-items:center; min-height:1.5rem; }
.cell .z { font-family:var(--mono); font-size:var(--t-3xl); margin:var(--s-3xs) 0 var(--s-2xs);
  color:var(--accent); }
/* Название сразу под шапкой, метка — по нижнему краю: раньше вниз тянулось
   название, и в соседних карточках ряда «пробник» стоял на разной высоте */
.cell .label { font-size:var(--t-sm); line-height:1.35; margin-top:var(--s-2xs); }
/* «пробник» стоял поверх карточки и налезал на длинное название темы:
   «Периодический закон и закономерности» упирался прямо в него. Метка идёт
   последней строкой в потоке — заслонять ей теперь нечего */
.cell .lock { align-self:flex-end; margin-top:auto; padding-top:var(--s-2xs);
  font-size:var(--t-2xs);
  color:var(--faint); }
/* «новое» на витрине: заметно, но не кричит — тема ценна содержимым,
   а не тем, что её недавно пополнили. Метка идёт в строке с номером задания,
   а не поверх карточки: справа там счётчик заданий, и его не закрыть */
.fresh { display:inline-block; font-family:var(--mono); font-size:var(--t-2xs);
  text-transform:uppercase; letter-spacing:.06em; padding:var(--s-3xs) var(--s-2xs);
  border-radius:var(--r-full); background:var(--accent-soft); color:var(--accent);
  /* без обводки: в витрине таких меток дюжина, и обведённые пилюли рябили
     сильнее, чем сообщали */
  border:1px solid transparent; vertical-align:middle; line-height:1.35; }
.fresh.big { font-size:var(--t-2xs); margin-left:var(--s-xs); }
.bar-progress { height:3px; background:var(--line); border-radius:var(--r-sm); margin-top:var(--s-xs); }
.bar-progress i { display:block; height:100%; background:var(--ok); border-radius:var(--r-sm); }

.btn { display:inline-block; font:inherit; font-size:var(--t-md); padding:var(--s-xs) var(--s-lg);
  white-space:nowrap;
  border-radius:var(--r-full); border:1px solid var(--field); background:var(--surface);
  color:var(--ink); cursor:pointer; text-decoration:none; }
.btn:hover { border-color:var(--accent); color:var(--accent); }
.btn.primary { background:var(--accent); border-color:var(--accent);
  box-shadow:0 6px 18px -10px var(--accent);
  color:var(--on-accent); font-weight:500; }
.btn.primary:hover { background:var(--accent-hi); border-color:var(--accent-hi);
  color:var(--on-accent); }
.btn:disabled { opacity:.45; cursor:not-allowed; }
/* Заливная кнопка, пока нечего нажимать: полупрозрачный акцент выглядел
   грязным пятном — спокойнее показать её обычной, но приглушённой */
.btn.primary:disabled { background:transparent; color:var(--dim);
  border-color:var(--line); opacity:1; box-shadow:none; }
/* Плотная карточка для списков: очередь работ из тридцати записей в обычных
   карточках растягивалась на пять экранов */
.card.tight { padding:var(--s-md) var(--s-lg); }
input, textarea, select { font:inherit; font-size:var(--t-md); padding:var(--s-xs) var(--s-sm);
  border:1px solid var(--field); border-radius:var(--r-md); background:var(--surface);
  color:var(--ink); width:100%; }
input:hover, textarea:hover, select:hover { border-color:var(--accent-line); }
input:focus, textarea:focus, select:focus { border-color:var(--accent);
  /* кольцо вокруг поля: одной сменой цвета рамки фокус на тёмном фоне почти
     не читался, особенно в длинной форме */
  box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
  outline:none; }
textarea { min-height:8rem; resize:vertical; font-family:var(--sans); }
/* Выпадающий список был на шесть точек ниже поля рядом: браузер рисует его
   своей высотой, и в строке «тема · заданий · срок» подписи стояли лесенкой.
   Высота одна на все однострочные поля, стрелка своя — системная у каждого
   браузера разная и в тёмной теме оставалась светлой. Стрелка нарисована
   двумя градиентами, поэтому берёт цвет из темы. */
input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]),
select { min-height:2.625rem; }
select:not([multiple]) { appearance:none; -webkit-appearance:none;
  padding-right:var(--s-3xl); cursor:pointer;
  background-image:linear-gradient(45deg, transparent 50%, var(--dim) 50%),
    linear-gradient(135deg, var(--dim) 50%, transparent 50%);
  background-position:right 1.05rem center, right .75rem center;
  background-size:.32rem .32rem; background-repeat:no-repeat; }
label { display:block; margin:var(--s-md) 0 var(--s-3xs); font-size:var(--t-sm); color:var(--dim); }
form.stack { max-width:26rem; }
/* Заявка на занятия: два коротких поля в строку, поэтому колонка шире обычной
   формы — иначе «Имя» и «Как связаться» ужимаются до неудобного */
form.stack.lead-form { max-width:44rem; }
form.stack.lead-form .row > .grow label, form.stack.lead-form .row { width:100%; }
/* подсказка под такой формой — той же ширины, что сама форма */
.mw-form { max-width:44rem; }
/* Вход и регистрация — единственное на странице, и прижатая влево колонка
   выглядела обрывком: ставим по центру вместе с заголовком. */
.narrow-page { max-width:30rem; margin-inline:auto; }
.narrow-page h1, .narrow-page .lead { text-align:center; }
.narrow-page form.stack, .narrow-page .narrow { max-width:none; }
.narrow-page h2 { margin-top:var(--s-3xl); }
/* Профиль — не окно входа, а страница из нескольких разделов: заголовок по
   центру над левыми «Пароль» и «Персональные данные» читался отдельно */
.narrow-page.profile-page h1 { text-align:left; }
/* Кнопка входа во всю ширину карточки, а ссылка на соседнюю форму — вопросом
   под ней: рядом с кнопкой подчёркнутое «Ещё нет аккаунта» спорило с ней
   и выглядело второй кнопкой */
.auth-actions { flex-direction:column; align-items:stretch; gap:var(--s-sm); }
.auth-actions .btn { width:100%; text-align:center; padding-block:var(--s-sm); }
/* свечение под кнопкой во всю ширину разливалось розовым пятном */
.auth-actions .btn.primary { box-shadow:0 8px 20px -16px var(--accent); }
.auth-actions .hint { text-align:center; }
/* Страница занятий: рассказ и заявка рядом, а не друг под другом. Форма
   стояла последней, ниже четырёх карточек, и на большом экране рядом с ней
   оставалось полполосы пустоты */
.offer { display:grid; gap:var(--s-3xl) var(--s-4xl);
  grid-template-columns:minmax(0, 1.15fr) minmax(22rem, .85fr); align-items:start; }
.offer .grid { grid-template-columns:1fr 1fr; }
.offer-form { position:sticky; top:calc(var(--s-6xl) + var(--s-md)); }
.offer-form form.stack.lead-form { max-width:none; }
@media (max-width: 900px) {
  .offer { grid-template-columns:1fr; gap:var(--s-2xl); }
  .offer-form { position:static; }
  .offer .grid { grid-template-columns:repeat(auto-fit, minmax(15rem, 1fr)); } }
/* Короткая страница по центру экрана: форма входа висела в верхней трети,
   а под ней оставалось полтора экрана пустоты */
.page-middle { display:flex; flex-direction:column; justify-content:center;
  min-height:min(62vh, 34rem); }
.auth-sub { text-align:center; color:var(--dim); margin:0 0 var(--s-xl); }
.auth-card { background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:var(--s-2xl); box-shadow:var(--shadow); }
.auth-card > label:first-of-type { margin-top:0; }
@media (max-width: 720px) { .auth-card { padding:var(--s-lg); } }

.opts { list-style:none; margin:0; padding:0; display:grid; gap:var(--s-xs); }
.opt { display:flex; gap:var(--s-md); align-items:center; width:100%; text-align:left;
  background:var(--surface); color:var(--ink); font:inherit; border:1px solid var(--line);
  border-radius:var(--r-md); padding:var(--s-sm) var(--s-md); cursor:pointer; }
/* Номер варианта — кружком: он всюду читается как метка выбора, а квадрат
   спорил с прямоугольной кнопкой, в которой стоит */
.opt .n { flex:none; width:1.7rem; height:1.7rem; border-radius:50%; font-family:var(--mono);
  border:1px solid var(--line); display:grid; place-items:center; font-size:var(--t-xs);
  color:var(--dim); }
.opt[aria-pressed="true"] { border-color:var(--accent); background:var(--accent-soft); }
.opt[aria-pressed="true"] .n { background:var(--accent); border-color:var(--accent);
  color:var(--surface); }
.opt.right, .pair.right { border-color:var(--ok); background:var(--ok-soft); }
.opt.right.missed { border-style:dashed; background:transparent; }
.opt.right.missed .n { background:transparent; color:var(--ok); }
.opt.wrong, .pair.wrong { border-color:var(--err); background:var(--err-soft); }
/* после проверки номер перекрашивается вместе с рамкой: акцентный кружок
   на зелёной карточке читался как «выбрано», а не как «верно» */
.opt.right .n { background:var(--ok); border-color:var(--ok); color:#fff; }
.opt.wrong .n { background:var(--err); border-color:var(--err); color:#fff; }
/* «расположите в порядке»: место в ответе видно прямо на выбранном варианте,
   иначе после трёх нажатий не вспомнить, что за чем шло */
.opt .rank { margin-left:auto; flex:none; min-width:1.5rem; height:1.5rem; padding:0 var(--s-2xs);
  border-radius:var(--r-full); background:var(--accent); color:var(--on-accent);
  font-family:var(--mono); font-size:var(--t-xs); display:grid; place-items:center; }
.opt.right .rank { background:var(--ok); color:#fff; }
.opt.wrong .rank { background:var(--err); color:#fff; }
.pair { display:grid; grid-template-columns:1fr auto; gap:var(--s-sm); align-items:center;
  border:1px solid var(--line); border-radius:var(--r-md); padding:var(--s-xs) var(--s-md); }
.pair .k, .code { font-family:var(--mono); color:var(--accent); }
.code { font-size:var(--t-xs); background:var(--accent-soft); border:1px solid var(--accent-line);
  padding:var(--s-3xs) var(--s-xs); border-radius:var(--r-sm); }
.legend { font-family:var(--mono); font-size:var(--t-2xs); text-transform:uppercase;
  letter-spacing:.1em; color:var(--faint); margin:var(--s-lg) 0 var(--s-xs); }
.actions { display:flex; gap:var(--s-xs); align-items:center; margin-top:var(--s-xl); flex-wrap:wrap; }
.verdict { display:inline-flex; align-items:center; gap:var(--s-2xs);
  font-size:var(--t-sm); font-weight:600; padding:var(--s-2xs) var(--s-md); border-radius:var(--r-full); }
.verdict::before { font-size:var(--t-base); line-height:1; }
.verdict.ok { color:var(--ok); background:var(--ok-soft); }
.verdict.ok::before { content:"✓"; }
.verdict.no { color:var(--err); background:var(--err-soft); }
.verdict.no::before { content:"✕"; }
.note { padding:var(--s-sm) var(--s-md); border-radius:var(--r-md); font-size:var(--t-sm); margin-top:var(--s-md); }
.note.warn { background:var(--warn-soft); color:var(--warn); }
.note.ok { background:var(--ok-soft); color:var(--ok); }
.note.dim { background:var(--surface); border:1px solid var(--line); color:var(--dim); }
.solution { margin-top:var(--s-md); padding:var(--s-md) var(--s-lg); border-radius:var(--r-md);
  background:var(--ok-soft); border:1px solid var(--ok); }
/* условие письменной работы — то же чтение с формулами, что и в тренажёре */
.paper-body { font-size:var(--t-lg); line-height:1.55; }
.solution h4 { margin:0 0 var(--s-2xs); font-weight:600; font-size:var(--t-base); color:var(--ok); }
img { max-width:100%; mix-blend-mode:multiply; }
/* фотография — не штриховая схема: перемножение с тёмным фоном
   превращает её в чёрный прямоугольник */
img.photo { mix-blend-mode:normal; }
img.fig { display:block; margin:var(--s-3xs) 0 var(--s-md); height:auto; max-width:100%; }
/* формулы приходят чёрным по белому — на тёмном фоне их надо вывернуть */
/* Инверсия — для схем и формул, набранных чёрным по белому.
   Фотографии объектов в заданиях ВПР так уродуются, их не трогаем. */
:root[data-theme="dark"] img:not(.photo) { filter:invert(1) hue-rotate(180deg);
  mix-blend-mode:screen; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) img:not(.photo) {
  filter:invert(1) hue-rotate(180deg); mix-blend-mode:screen; } }
table { width:100%; border-collapse:collapse; font-size:var(--t-md); }
.tablewrap { max-width:100%; overflow-x:auto;
  /* скрытая подпись в шапке стоит абсолютно, и без опоры её место считалось
     от страницы: таблица шириной в экран раздвигала всю страницу вбок */
  position:relative; }
th, td { text-align:left; padding:var(--s-xs) var(--s-xs); border-bottom:1px solid var(--line);
  vertical-align:middle; }
th { font-size:var(--t-xs); text-transform:uppercase; letter-spacing:.06em; color:var(--faint);
  font-weight:500; }
/* Подсветка строки под курсором: в таблице учеников и тем глаз теряет строку
   на полпути, а данные в ней стоят рядом с чужими */
@media (hover: hover) and (pointer: fine) {
  tbody tr:hover td, table tr:hover td { background:color-mix(in srgb, var(--ink) 4%, transparent); } }
/* Сообщение после действия. Раньше все они были одного тревожного цвета,
   и «Задано: 3 задания» выглядело ровно как «Не подошли почта или пароль».
   Полоска слева: зелёная — получилось, жёлтая — не вышло. */
.flash { background:var(--surface); border:1px solid var(--line); color:var(--ink);
  box-shadow:inset 3px 0 0 var(--ok);
  padding:var(--s-xs) var(--s-md); border-radius:var(--r-md); margin-bottom:var(--s-lg); }
.flash.warn { box-shadow:inset 3px 0 0 var(--warn); background:var(--warn-soft); }
/* Индексы и заряды в формулах. line-height:0 обязателен: иначе строка с
   «Fe2O3» становится выше соседних, и абзац с формулами идёт лесенкой */
sub, sup { font-size:.72em; line-height:0; position:relative; vertical-align:baseline; }
sup { top:-.45em; }
sub { bottom:-.2em; }
/* Приборная панель кабинета: показатели разделены волосяной линией, иначе
   «4 задания решено 0 верно 4 попытки» читались одной строкой цифр */
.stats { display:flex; gap:0; flex-wrap:wrap; padding:var(--s-md) var(--s-lg);
  align-items:center; row-gap:var(--s-md); }
.stats > .stat { padding-inline:var(--s-2xl); border-left:1px solid var(--line); }
.stats > .stat:first-child { padding-left:0; border-left:0; }
@media (max-width: 720px) {
  /* показатели переносились рвано: линия-разделитель оставалась в начале
     второй строки и висела ни к чему. Сетка в две колонки без линий */
  .stats { display:grid; grid-template-columns:1fr 1fr; gap:var(--s-md) var(--s-lg); }
  .stats > .stat { padding-inline:0; border-left:0; }
  .stats > .stat:first-child { grid-column:1 / -1; }
}
.stat b { display:block; font-family:var(--mono); font-size:var(--t-2xl); font-weight:500;
  line-height:1.2; }
.stat span { font-size:var(--t-xs); color:var(--dim); }

/* защита контента */
.guard { user-select:none; -webkit-user-select:none; }
#wm { position:fixed; inset:0; pointer-events:none; z-index:var(--z-mark); opacity:.06;
  font-family:var(--mono); font-size:13px; color:var(--ink); overflow:hidden; }
#wm span { position:absolute; white-space:nowrap; transform:rotate(-30deg);
  letter-spacing:.18em; }
/* Печать закрыта: сфотографировать экран можно, а вынести банк пачкой листов
   не выйдет. Исключение — собранный вариант в руках преподавателя: его для
   того и собирают, чтобы раздать на контрольной */
/* Лист для контрольной собирается на странице варианта и виден только
   принтеру: на экране преподаватель работает с тренажёром и таблицей */
.printsheet { display:none; }
@media print {
  body { display:none; }
  /* на бумаге фон страницы только тратит краску */
  body.printable { display:block; background:#fff; color:#000; }
  body.printable header, body.printable footer, body.printable .actions,
  body.printable .crumbs, body.printable #wm, body.printable .flash,
  body.printable .tg-invite, body.printable form,
  body.printable .bubbles { display:none; }
  body.printable main { max-width:none; padding:0; }
  body.printable .card { border:0; box-shadow:none; padding:0 0 var(--s-lg); }
  /* задание не должно разрываться между листами посреди вариантов */
  body.printable .card, body.printable .opt { break-inside:avoid; }
  /* на бумаге остаётся только лист: ни экранной карточки, ни служебной
     таблицы, ни подсказок про клавиши */
  body.printable h1, body.printable .lead, body.printable h2,
  body.printable #trainer, body.printable .hintbar, body.printable .progress-row,
  body.printable .timerbox, body.printable .result, body.printable table,
  body.printable .note { display:none; }
  body.printable .printsheet { display:block; }
  body.printable .printsheet-head { display:block; font-size:1.3rem; margin:0 0 .3rem; }
  .printsheet-line { font-size:.85rem; color:#000; margin:0 0 1rem; }
  .pr-task { break-inside:avoid; margin-bottom:1rem; }
  .pr-q { margin:0 0 .3rem; }
  .pr-opts { display:flex; flex-direction:column; gap:.1rem; margin-left:1rem; }
  .pr-pairs { display:flex; gap:2rem; margin-left:1rem; }
  .pr-pairs div { display:flex; flex-direction:column; gap:.1rem; }
  .pr-ans { margin:.35rem 0 0; color:#444; }
  .printsheet, .printsheet * { color:#000; background:none; }
}


/* ── переключатель темы ─────────────────────────────── */
.theme { display:grid; place-items:center; width:2.1rem; height:2.1rem; padding:0;
  border-radius:var(--r-md); border:1px solid var(--line); background:var(--surface);
  cursor:pointer; color:var(--dim); flex:none; }
.theme:hover { border-color:var(--accent); color:var(--accent); }
.theme .ico { width:1.15rem; height:1.15rem; }
.theme .moon { display:none; }
:root[data-theme="dark"] .theme .moon { display:block; }
:root[data-theme="dark"] .theme .sun { display:none; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .theme .moon
  { display:block; } :root:not([data-theme="light"]) .theme .sun { display:none; } }

/* ── графика ────────────────────────────────────────── */
/* Первый экран. Раньше заголовок и колба висели прямо на фоне страницы, и
   главная начиналась так же ровно, как любой список тем. Подложка отделяет
   вступление от витрины: свечение уходит от колбы и гаснет к тексту, чтобы
   не спорить с ним за внимание. */
.hero-art { display:grid; grid-template-columns:1fr auto; gap:var(--s-3xl); align-items:center;
  padding:var(--s-xl) var(--s-3xl); margin-bottom:var(--s-2xl); border:1px solid var(--line);
  border-radius:var(--r-xl);
  /* Подложка из обоих фирменных цветов: бирюза со стороны рисунка,
     фиолет из дальнего угла. Одним цветом первый экран выходил плоским */
  background:
    radial-gradient(96% 120% at 88% 4%,
      color-mix(in srgb, var(--accent) 16%, transparent), transparent 62%),
    radial-gradient(80% 110% at 4% 96%,
      color-mix(in srgb, var(--accent2) 14%, transparent), transparent 60%),
    var(--surface); }
.hero-art h1 { font-size:clamp(1.9rem, 1.15rem + 2.6vw, 2.5rem); margin-bottom:var(--s-xs); }
/* ── тепловая карта тем ─────────────────────────────────── */
/* Четыре ступени вместо плавной заливки: цвет должен читаться с одного
   взгляда, а шестнадцать оттенков одного зелёного различает только автор.
   Заливки те же, что у сообщений об успехе и ошибке, — контраст к тексту
   у них уже проверен. */
.heat td { text-align:center; font-family:var(--mono); font-size:var(--t-xs);
  padding:var(--s-2xs) var(--s-xs); }
.heat .hot0 { background:var(--err-soft); }
.heat .hot1 { background:var(--warn-soft); }
.heat .hot2 { background:var(--accent-soft); }
.heat .hot3 { background:var(--ok-soft); }
.heat .hot-none { color:var(--faint); }
/* Имена ученикам — по вертикали: в строку двадцать имён растягивают таблицу
   на три экрана, и тема с её названием уезжает из поля зрения */
.heat .who { vertical-align:bottom; font-weight:500; }
.heat .who span { display:inline-block; writing-mode:vertical-rl; transform:rotate(180deg);
  max-height:7rem; overflow:hidden; font-size:var(--t-xs); }
.heat th[scope="row"] { white-space:nowrap; font-weight:400; text-align:left; }

/* ── каталог: панель отбора и список строками ──────────── */
/* На телефоне панель закрыта и занимает одну строку; на широком экране её
   раскрывает ui.js. Стрелку сводки рисует браузер — своей не заводим. */
.filters summary { cursor:pointer; font-weight:500; }
.filters[open] summary { margin-bottom:var(--s-md); }
.filters-body { display:grid; gap:var(--s-md);
  grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); align-items:end; }
.filters-body .actions { grid-column:1 / -1; }

/* Список тем строками: в каталоге их шесть десятков, и карточками они заняли
   бы пять экранов. Строка держит номер, название, метки и прогресс. */
.rows { display:grid; gap:var(--s-2xs); }
.rows > a { display:grid; grid-template-columns:2.6rem 1fr auto; gap:var(--s-md);
  align-items:center; text-decoration:none; color:inherit; background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-md);
  padding:var(--s-sm) var(--s-lg); transition:border-color var(--t-fast) var(--ease-out),
  transform var(--t-fast) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .rows > a:hover { border-color:var(--accent); transform:translateY(-1px); }
}
.rows .no { font-family:var(--mono); font-size:var(--t-sm); color:var(--accent);
  text-align:center; }
.rows .name { font-weight:500; }
.rows .sub { display:block; font-size:var(--t-xs); color:var(--faint); }
.rows .aside { display:flex; align-items:center; gap:var(--s-sm); flex-wrap:wrap;
  font-size:var(--t-xs); color:var(--dim); justify-content:flex-end; }
.rows .bar-progress { width:3.5rem; margin:0; flex:none; }
/* На телефоне метки и число заданий уходят под название: в три колонки
   строка ломалась на пять и название читалось по одному слову */
@media (max-width: 560px) {
  .rows > a { grid-template-columns:2.2rem 1fr; gap:var(--s-sm); }
  .rows .aside { grid-column:2; justify-content:flex-start; }
}

/* ── стеклянные шарики на фоне ──────────────────────────── */
/* Слой закреплён за окном и уведён под содержимое: `z-index:-1` кладёт его
   поверх заливки страницы, но ниже любого текста, — поэтому ни одному блоку
   не нужно знать о нём и получать свой `position`. */
.bubbles { position:fixed; inset:0; z-index:-1; pointer-events:none; overflow:hidden; }
/* Шарик собирается из трёх заливок: блик сверху слева, отражённый свет
   снизу справа и само тело. Внутренняя тень даёт толщину стекла, внешняя —
   свечение, из-за которого шарик не выглядит наклейкой. */
.bubbles span { position:absolute; border-radius:50%; opacity:.62;
  background:
    radial-gradient(circle at 30% 26%, rgba(255,255,255,.95), rgba(255,255,255,0) 40%),
    radial-gradient(circle at 74% 80%, var(--bub-b), transparent 55%),
    radial-gradient(circle at 44% 40%, var(--bub-c), var(--bub-a) 55%, var(--bub-b));
  box-shadow:
    inset -6px -9px 18px color-mix(in srgb, var(--bub-b) 40%, transparent),
    inset 5px 6px 14px rgba(255, 255, 255, .35),
    0 18px 40px -12px color-mix(in srgb, var(--bub-a) 55%, transparent); }
.bubbles > :nth-child(1) { left:3%;  top:14%; width:7.5rem; height:7.5rem; }
.bubbles > :nth-child(2) { right:5%; top:27%; width:5.2rem; height:5.2rem; opacity:.55; }
.bubbles > :nth-child(3) { left:9%;  top:63%; width:3.4rem; height:3.4rem; opacity:.5; }
.bubbles > :nth-child(4) { right:9%; top:72%; width:8.5rem; height:8.5rem; opacity:.45; }
.bubbles > :nth-child(5) { left:47%; top:6%;  width:2.4rem; height:2.4rem; opacity:.58; }
.bubbles > :nth-child(6) { right:28%; top:90%; width:4rem; height:4rem; opacity:.4; }
.bubbles > :nth-child(7) { left:22%; top:44%; width:1.8rem; height:1.8rem; opacity:.62; }
/* На телефоне остаются четыре: остальные приходятся на середину экрана,
   где под ними сплошные карточки, — их всё равно не видно */
@media (max-width: 760px) {
  .bubbles > :nth-child(n+5) { display:none; }
  .bubbles > :nth-child(1) { width:5rem; height:5rem; }
  .bubbles > :nth-child(4) { width:5.5rem; height:5.5rem; }
}
/* Движение — только transform, и только если человек не просил его убрать.
   Всплывают медленно и недалеко: это фон, а не мультфильм. */
@keyframes drift { to { transform:translate3d(.9rem, -2.6rem, 0) scale(1.05); } }
@media (prefers-reduced-motion: no-preference) {
  .bubbles span { animation:drift 26s var(--ease-in-out) infinite alternate; }
  .bubbles > :nth-child(2) { animation-duration:34s; animation-delay:-7s; }
  .bubbles > :nth-child(3) { animation-duration:21s; animation-delay:-3s; }
  .bubbles > :nth-child(4) { animation-duration:39s; animation-delay:-12s; }
  .bubbles > :nth-child(5) { animation-duration:18s; animation-delay:-5s; }
  .bubbles > :nth-child(6) { animation-duration:31s; animation-delay:-9s; }
  .bubbles > :nth-child(7) { animation-duration:24s; animation-delay:-2s; }
}

/* Ключевое слово заголовка — градиентом от бирюзы к фиолету. Единственное
   место с градиентным текстом на всей платформе: приём работает ровно до
   тех пор, пока встречается один раз. */
.grad { background:linear-gradient(96deg, var(--accent-hi), var(--accent) 40%, var(--accent2));
  -webkit-background-clip:text; background-clip:text; color:transparent; }
/* В режиме высокой контрастности градиент не рисуется вовсе, и надпись
   осталась бы прозрачной — возвращаем ей системный цвет текста */
@media (forced-colors: active) { .grad { background:none; color:CanvasText; } }

/* Чем наполнена платформа — капсулами под заголовком */
.facts { list-style:none; display:flex; flex-wrap:wrap; gap:var(--s-xs);
  margin:var(--s-lg) 0 0; padding:0; }
.facts li { display:flex; align-items:baseline; gap:var(--s-3xs); font-size:var(--t-xs);
  color:var(--dim); border:1px solid var(--accent-line); border-radius:var(--r-full);
  padding:var(--s-2xs) var(--s-md); background:var(--accent-soft); }
/* Вторая и третья капсулы — сиреневые: три одинаковые читались одной
   длинной плашкой, а это три разные цифры */
.facts li:nth-child(2) { border-color:color-mix(in srgb, var(--accent2) 30%, transparent);
  background:var(--accent2-soft); }
.facts b { font-family:var(--mono); font-size:var(--t-sm); color:var(--ink); }
.facts li:nth-child(1) b { color:var(--accent); }
.facts li:nth-child(2) b { color:var(--accent2); }
.facts li:nth-child(3) b { color:var(--sky); }

/* Четыре шага обучения. Номер рисуем сами: браузерный маркер уводит цифру
   в поле списка, а здесь она — часть карточки */
.how { list-style:none; counter-reset:шаг; display:grid; gap:var(--s-md); padding:0;
  grid-template-columns:repeat(auto-fit, minmax(210px, 1fr)); margin:0 0 var(--s-2xl); }
.how li { counter-increment:шаг; display:grid; gap:var(--s-3xs); background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s-lg); }
.how li::before { content:counter(шаг); font-family:var(--mono); font-size:var(--t-xs);
  color:var(--on-accent); width:1.6rem; height:1.6rem; border-radius:50%;
  display:grid; place-items:center; margin-bottom:var(--s-2xs);
  background:linear-gradient(140deg, var(--accent), var(--accent2)); }
.how b { font-size:var(--t-base); }
.how span { color:var(--dim); font-size:var(--t-sm); line-height:1.5; }

/* Приветствие вошедшего: та же подложка, но ниже — это не витрина, а
   строка «с чего начать сегодня» */
.hero-art.hello { padding-block:var(--s-xl); }
.hero-art.hello h1 { font-size:clamp(1.7rem, 1.2rem + 1.6vw, 2.1rem); }
.hero-art.hello .lead { margin:0; }
.hero-art.hello .actions { margin-top:var(--s-lg); }
/* главная кнопка — первой: у преподавателя она меняется по тому, что ждёт,
   и на телефоне залитая оказывалась во втором ряду */
.hero-art.hello .actions .btn.primary { order:-1; }
.hello-goal { display:grid; justify-items:center; gap:var(--s-2xs); text-decoration:none;
  color:var(--dim); font-size:var(--t-sm); }
.hello-goal:hover { color:var(--accent); }
.ring.big { width:6rem; height:6rem; font-size:var(--t-2xl); color:var(--ink); }
.ring.big i { width:5rem; height:5rem; display:flex; flex-direction:column; line-height:1.05;
  align-items:center; justify-content:center; font-family:var(--mono); }
.goal-of { display:none; }
.ring.big small { font-size:var(--t-2xs); color:var(--faint); }
/* Рисунок первого экрана. Свечение под ним — чтобы стеклянная колба не
   выглядела наклейкой поверх страницы: у неё появляется своя подсветка,
   как у предмета, который сам светится. */
.hero-pic { width:min(330px, 34vw); height:auto; justify-self:end;
  filter:drop-shadow(0 18px 42px color-mix(in srgb, var(--accent2) 30%, transparent))
         drop-shadow(0 0 34px color-mix(in srgb, var(--accent) 22%, transparent)); }
/* Phosphor Icons (MIT) из общего спрайта: одна геометрия, одна толщина.
   Своя остаётся только колба — это знак бренда, он же в фавиконке. */
.ico { width:1.5rem; height:1.5rem; color:var(--accent); flex:none; fill:currentColor; }
.ico.soon { color:var(--faint); }
.course .ico { margin-bottom:var(--s-xs); }
/* Пятно в углу плитки: чёткий круг с острым краем читался как обрезанная
   картинка, поэтому размываем в свечение — оно подсказывает, что плитка
   нажимается, и не спорит с содержимым */
/* Свой цвет у каждой плитки: ряд одинаковых карточек читался как один
   блок, а не как четыре разные дороги. Цвет назначается по месту в ряду, а
   не по названию раздела: названия — это данные, «ВПР-8» завтра сменится. */
.courses > :nth-child(1) { --tint:var(--accent); }
.courses > :nth-child(2) { --tint:var(--sky); }
.courses > :nth-child(3) { --tint:var(--accent2); }
.courses > :nth-child(4) { --tint:var(--mint); }
.courses > :nth-child(5) { --tint:var(--sky); }
.course .ico { color:var(--tint); }
.course::after { content:""; position:absolute; right:-40px; bottom:-40px; width:150px;
  height:150px; border-radius:50%; z-index:0; pointer-events:none;
  /* в покое его не видно: на светлом фоне розовое пятно в углу каждой
     плитки читалось как грязь на экране, а не как подсказка */
  background:radial-gradient(circle, color-mix(in srgb, var(--tint) 20%, transparent),
             transparent 70%); opacity:0;
  transition:opacity var(--t-base) var(--ease-out), transform var(--t-base) var(--ease-out); }
@media (hover: hover) and (pointer: fine) {
  .course:hover::after { opacity:1; transform:scale(1.15); }
}
.course > * { position:relative; z-index:1; }
.ring { --p:0; width:2.6rem; height:2.6rem; border-radius:50%; display:grid;
  place-items:center; font-family:var(--mono); font-size:var(--t-2xs); color:var(--dim);
  background:conic-gradient(var(--ok) calc(var(--p) * 1%), var(--line) 0); flex:none; }
.ring i { display:grid; place-items:center; width:2.05rem; height:2.05rem;
  border-radius:50%; background:var(--surface); font-style:normal; }
.badge { display:inline-flex; align-items:center; gap:var(--s-2xs); font-family:var(--mono);
  font-size:var(--t-2xs); text-transform:uppercase; letter-spacing:.06em; padding:var(--s-3xs) var(--s-xs);
  border-radius:var(--r-full); background:var(--ground); color:var(--faint); }
.stat { display:flex; gap:var(--s-sm); align-items:center; }

/* ── телефон и планшет ──────────────────────────────── */
@media (max-width: 860px) {
  body { font-size:16px; }
  main { padding:var(--s-xl) max(var(--s-lg), env(safe-area-inset-right))
    var(--s-5xl)
    max(var(--s-lg), env(safe-area-inset-left)); }
  .bar { gap:var(--s-sm); flex-wrap:wrap;
    padding:var(--s-sm) max(var(--s-lg), env(safe-area-inset-right))
    var(--s-sm) max(var(--s-lg), env(safe-area-inset-left)); }
  .bar .brand { font-size:var(--t-base); white-space:nowrap; }
  .bar a:not(.brand):not(.btn) { font-size:var(--t-sm); }
  h1 { font-size:var(--t-2xl); }
  .hero-art { grid-template-columns:1fr; gap:var(--s-md); padding:var(--s-lg) var(--s-lg); }
  /* колба под текстом занимала пол-экрана до первой кнопки — на телефоне
     она знак над заголовком, как значок приложения */
  /* На телефоне рисунок идёт первым и крупно: он и есть первое, что видно.
     Прежний контурный значок стоял в углу ростом с букву — от картинки в
     такую величину остаётся мутное пятно */
  .hero-pic { width:min(190px, 46vw); order:-1; justify-self:center; }
  /* цель на телефоне — строкой под кнопкой, кольцо слева от подписи */
  .hello-goal { grid-auto-flow:column; justify-content:start; align-items:center;
    gap:var(--s-sm); }
  .ring.big { width:3rem; height:3rem; font-size:var(--t-base); }
  .ring.big i { width:2.45rem; height:2.45rem; }
  /* в маленьком кольце «из 10» не помещается — уходит в подпись рядом */
  .ring.big small { display:none; }
  .goal-of { display:inline; }
  .grid { grid-template-columns:repeat(auto-fill, minmax(160px, 1fr)); }
  .table { grid-template-columns:repeat(auto-fill, minmax(140px, 1fr)); }
  .card { padding:var(--s-lg) var(--s-lg); }
  .stats { gap:var(--s-lg); }
  .tablewrap { overflow-x:auto; }
  table { white-space:nowrap; }
  /* Первая колонка — название темы или имя: в одну строку оно уходило за
     край карточки, и «Строение электронных оболочек атомов» обрывалось на
     «атом». Переносим его, цифры справа остаются в строку */
  td:first-child { white-space:normal; min-width:9rem; }
  .pair { grid-template-columns:1fr; gap:var(--s-xs); }
  .pair select { width:100%; }
}
@media (max-width: 560px) {
  .bar { gap:var(--s-xs) var(--s-md); }
  .grid, .table { grid-template-columns:1fr 1fr; }
  /* Модуль — длинное название из учебника: в половине экрана «неорганических»
     рвалось посреди слова, а «Периодический закон…» шло столбиком в семь
     строк. Одна колонка, и пустые модули ниже не растягиваются под соседа */
  .grid.modules { grid-template-columns:1fr; }
  .actions .btn { flex:1; text-align:center; }
  .opt { padding:var(--s-sm); }
  form.stack { max-width:none; }
}
/* Гостевая шапка на телефоне: марка, «Войти», «Регистрация» и тема не
   влезали в строку, и кнопка регистрации уезжала на вторую. Регистрация
   есть на первом экране и под формой входа — в шапке хватает «Войти» */
@media (max-width: 480px) { .bar .signup { display:none; } }
@media (max-width: 380px) {
  .grid, .table { grid-template-columns:1fr; }
  /* на узком телефоне шапка ломалась на две строки: марка занимала столько,
     что кнопке темы места не оставалось */
  .bar .brand { font-size:var(--t-md); }
  .bar { gap:var(--s-2xs) var(--s-xs);
    padding:var(--s-sm) max(var(--s-sm), env(safe-area-inset-right))
    var(--s-sm) max(var(--s-sm), env(safe-area-inset-left)); }
}

/* пальцем по экрану — цели не меньше 44px, как просят гайдлайны */
@media (pointer: coarse) {
  .btn, .opt, select, input, .theme, .as-link { min-height:44px; }
  .opt .n { width:1.9rem; height:1.9rem; }
}
@media (prefers-reduced-motion: reduce) {
  /* Меньше и мягче, а не «выключить всё»: движение убираем, проявление и
     смену цвета оставляем — по ним видно, что состояние изменилось. */
  *, *::before, *::after {
    transition-property:opacity, color, background-color, border-color, box-shadow
      !important; }
}

.cell.named { min-height:92px; }
.cell.named .label { font-size:var(--t-base); font-weight:600;
  line-height:1.25; margin-top:var(--s-2xs); letter-spacing:-.01em; }
/* «Кислородсодержащие» шире ячейки только на телефоне, в две колонки: там
   перенос по слогам (lang="ru" даёт русские правила). На широком экране
   слово помещается, а дефисы в каждом третьем заголовке рябили */
@media (max-width: 560px) { .cell.named .label { hyphens:auto; font-size:var(--t-md); } }
/* «кислородсодержащие» длиннее ячейки — переносим, а не выпускаем за край.
   break-word, а не anywhere: рвём только то, что правда не влезает, иначе
   условие задачи на телефоне превращается в лесенку из дефисов */
.cell, .course, .card { min-width:0; overflow-wrap:break-word; }
.opt span, .pair span { min-width:0; overflow-wrap:break-word; }


/* ── графики ────────────────────────────────────────── */
:root { --data-ok:#0d9488; --data-bad:#c0392b; --grid:#e8ebf2; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) {
  --data-ok:#2dd4bf; --data-bad:#ef5350; --grid:#242a38; } }
:root[data-theme="dark"] { --data-ok:#2dd4bf; --data-bad:#ef5350; --grid:#242a38; }

.chart { width:100%; height:auto; display:block; overflow:visible; }
.chart .grid { stroke:var(--grid); stroke-width:1; }
.chart .tick, .chart .rowlabel, .chart .rowval { font-family:var(--mono); font-size:11px;
  fill:var(--faint); }
.chart .rowlabel { font-family:var(--sans); font-size:12.5px; fill:var(--dim); }
.chart .rowval { fill:var(--ink); font-variant-numeric:tabular-nums; }
.chart .track { fill:var(--grid); }
.chart .seg.ok { fill:var(--data-ok); }
.chart .seg.bad { fill:var(--data-bad); }
.chart .hit { fill:transparent; cursor:default; }
.chart .hit:hover { fill:var(--ink); fill-opacity:.045; }

.charttip { position:absolute; z-index:var(--z-tip); pointer-events:none; opacity:0;
  transition:opacity .12s; background:var(--ink); color:var(--ground);
  font-size:var(--t-xs); padding:var(--s-2xs) var(--s-xs); border-radius:var(--r-sm); white-space:nowrap;
  box-shadow:0 4px 14px rgba(0,0,0,.25); }
.charttip.on { opacity:1; }
.legend-row { display:flex; gap:var(--s-lg); flex-wrap:wrap; font-size:var(--t-xs);
  color:var(--dim); margin:var(--s-3xs) 0 var(--s-md); }
.legend-row span { display:flex; align-items:center; gap:var(--s-2xs); }
.legend-row i { width:11px; height:11px; border-radius:var(--r-sm); display:inline-block; }
.chart-card h2 { font-size:var(--t-base); margin:0 0 var(--s-2xs); }
.chart-card { padding:var(--s-lg) var(--s-xl); }
.chart-card h3 { margin:0 0 var(--s-3xs); }
.chart-card .sub { font-size:var(--t-sm); color:var(--dim); margin:0 0 var(--s-sm); }
details.astable { margin-top:var(--s-xs); }
details.astable summary { cursor:pointer; font-size:var(--t-sm); color:var(--accent); }
@media (prefers-reduced-motion: reduce) { .charttip { transition:none; } }


/* ── свёрнутое меню управления ──────────────────────── */
.menu { position:relative; }
.menu summary { cursor:pointer; font-size:var(--t-md); color:var(--dim); list-style:none;
  display:flex; align-items:center; gap:var(--s-2xs); }
.menu summary::-webkit-details-marker { display:none; }
.menu summary::after { content:""; width:.36rem; height:.36rem; margin:0 0 var(--s-3xs) var(--s-3xs);
  border-right:1.5px solid currentColor; border-bottom:1.5px solid currentColor;
  transform:rotate(45deg); opacity:.7; transition:transform var(--t-fast) var(--ease-out); }
.menu[open] summary::after { transform:translateY(.2rem) rotate(225deg); }
.menu summary:hover { color:var(--accent); }
.menu[open] summary { color:var(--accent); }
.menu-drop { position:absolute; right:0; top:calc(100% + .5rem); z-index:var(--z-drop);
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
  box-shadow:var(--shadow); padding:var(--s-2xs); display:grid; gap:var(--s-3xs); min-width:12rem;
  /* растёт от своей кнопки: меню, возникающее из центра, читается как чужое */
  transform-origin:top right; animation:drop 320ms var(--ease-spring) both; }
@supports (backdrop-filter: blur(1px)) {
  .menu-drop { background:var(--glass); backdrop-filter:var(--glass-blur);
    -webkit-backdrop-filter:var(--glass-blur);
    box-shadow:var(--glass-edge), var(--shadow); }
}
@media (max-width: 860px) { .menu-drop { transform-origin:top left; } }
@keyframes drop { from { opacity:0; transform:scale(0.97) translateY(-2px); } }
.menu-drop a { display:block; padding:var(--s-xs) var(--s-sm); border-radius:var(--r-md); font-size:var(--t-md);
  color:var(--ink); text-decoration:none; white-space:nowrap; }
.menu-drop a:hover { background:var(--accent-soft); color:var(--accent); }
/* Выход отправляется формой, а не переходом по ссылке — кнопке возвращаем вид
   такого же пункта меню, как соседний «Профиль» */
.menu-drop form { margin:0; }
.menu-drop .as-link { display:block; width:100%; text-align:left; font:inherit;
  font-size:var(--t-md); padding:var(--s-xs) var(--s-sm); border-radius:var(--r-md);
  background:none; border:0; color:var(--ink); white-space:nowrap; cursor:pointer; }
.menu-drop .as-link:hover { background:var(--accent-soft); color:var(--accent); }
@media (max-width: 860px) { .menu-drop { right:auto; left:0; } }


/* ── согласия и правовой подвал ─────────────────────── */
.consents { display:grid; gap:var(--s-sm); margin:var(--s-lg) 0 var(--s-2xs); }
.check { display:flex; gap:var(--s-xs); align-items:flex-start; margin:0; font-size:var(--t-sm);
  line-height:1.45; color:var(--ink); cursor:pointer; }
input[type=checkbox], input[type=radio], progress { accent-color:var(--accent); }
/* Кнопку выбора файла рисует браузер, и рядом с нашими она выглядела
   чужой — серая, с системным шрифтом. Ученик видит её каждый раз, когда
   присылает фотографию решения. */
input[type=file] { padding:var(--s-2xs); }
input[type=file]::file-selector-button {
  margin-right:var(--s-sm); padding:var(--s-2xs) var(--s-md);
  border:1px solid var(--field); border-radius:var(--r-full);
  background:var(--surface); color:var(--ink); font:inherit; cursor:pointer;
  transition:border-color var(--t-fast) var(--ease-out); }
input[type=file]::file-selector-button:hover { border-color:var(--accent); color:var(--accent); }
.check input { width:1.15rem; height:1.15rem; flex:none; margin-top:var(--s-3xs);
  min-height:0; accent-color:var(--accent); cursor:pointer; }
.check span { color:var(--dim); }
.check a { color:var(--accent); }
/* Подвал стоял на своей ширине, уже страницы, и его строка начиналась на
   шестьдесят точек правее заголовков. Теперь линия во всю ширину, как у
   шапки, а ссылки — по той же границе, что содержимое */
footer.legal { margin-top:auto; border-top:1px solid var(--line); }
footer.legal .foot { max-width:var(--page); margin:0 auto; display:flex; gap:var(--s-2xs) var(--s-xl);
  flex-wrap:wrap; align-items:baseline;
  padding:var(--s-xl) max(var(--s-xl), env(safe-area-inset-right))
    calc(var(--s-3xl) + env(safe-area-inset-bottom)) max(var(--s-xl), env(safe-area-inset-left)); }
footer.legal .brandline { margin-right:auto; font-family:var(--display); font-size:var(--t-sm);
  color:var(--dim); }
footer.legal a { font-size:var(--t-sm); color:var(--faint); text-decoration:none; }
@media (max-width: 860px) {
  footer.legal .foot { flex-direction:column; gap:var(--s-xs);
    padding-left:max(var(--s-lg), env(safe-area-inset-left));
    padding-right:max(var(--s-lg), env(safe-area-inset-right)); }
}
footer.legal a:hover { color:var(--accent); }


/* ── приглашение к занятию ──────────────────────────── */
.startbar { display:flex; gap:var(--s-lg); align-items:center; justify-content:space-between;
  flex-wrap:wrap; border:1px solid var(--accent-line);
  background:linear-gradient(100deg,
    color-mix(in srgb, var(--accent-soft) 70%, var(--surface)),
    color-mix(in srgb, var(--accent2-soft) 55%, var(--surface)));
  border-radius:var(--r-lg); padding:var(--s-lg) var(--s-xl); margin:0 0 var(--s-2xl); box-shadow:var(--shadow); }
.startbar h2 { font-family:var(--display); font-size:var(--t-lg); letter-spacing:-.03em;
  margin:0 0 var(--s-3xs); }
/* строка поиска на главной: до неё доходят чаще, чем до раздела «Поиск» */
.findbar { display:flex; gap:var(--s-xs); margin:0 0 var(--s-lg); }
.findbar .grow { flex:1; min-width:0; }
/* на телефоне кнопка остаётся в строке: во всю ширину под полем она
   выглядела второй главной кнопкой экрана */
@media (max-width: 560px) { .findbar .btn { flex:none; } }
@media (max-width: 560px) { .startbar .btn { width:100%; text-align:center; } }


/* ── прогресс внутри подборки ───────────────────────── */
.progress-row { display:flex; align-items:center; gap:var(--s-md); margin:0 0 var(--s-md); }
.progress-bar { flex:1; height:4px; background:var(--line); border-radius:var(--r-sm);
  overflow:hidden; }
.progress-bar i { display:block; height:100%; width:100%; background:var(--accent);
  border-radius:var(--r-sm); transform-origin:left;
  /* растягиваем, а не меняем ширину: width заставляет браузер пересчитывать
     раскладку на каждом задании, transform идёт мимо неё */
  transition:transform var(--t-base) var(--ease-out); }
.progress-label { font-family:var(--mono); font-size:var(--t-xs); color:var(--faint);
  white-space:nowrap; font-variant-numeric:tabular-nums; }
.hintbar { display:flex; gap:var(--s-lg); flex-wrap:wrap; margin-top:var(--s-md);
  font-size:var(--t-xs); color:var(--faint); }
.hintbar kbd { font-family:var(--mono); font-size:var(--t-xs); background:var(--ground);
  border:1px solid var(--line); border-bottom-width:2px; border-radius:var(--r-sm);
  padding:var(--s-3xs) var(--s-2xs); color:var(--dim); }
@media (pointer: coarse) { .hintbar { display:none; } }
@media (prefers-reduced-motion: reduce) { .progress-bar i { transition:none; } }


/* ── итог подборки ──────────────────────────────────── */
/* Обычное свойство браузер не умеет плавно менять, поэтому объявляем его типом —
   тогда заполнение кольца делает CSS, а не таймер. Это ещё и надёжнее: в фоновой
   вкладке таймер замирает, и результат остался бы нулевым. */
@property --p { syntax:'<number>'; inherits:false; initial-value:0; }

.done-card { text-align:center; padding:var(--s-4xl) var(--s-2xl); border-radius:var(--r-xl); }
.done-ring { --p:0; width:6.5rem; height:6.5rem; margin:0 auto; border-radius:50%;
  transition:--p 700ms var(--ease-out);
  display:grid; place-items:center;
  background:conic-gradient(var(--ok) calc(var(--p) * 1%), var(--line) 0); }
.done-ring i { display:grid; place-items:center; width:5.3rem; height:5.3rem;
  border-radius:50%; background:var(--surface); font-style:normal;
  font-family:var(--mono); font-size:var(--t-xl); color:var(--ink);
  font-variant-numeric:tabular-nums; }
.done-card h2 { font-size:var(--t-2xl); line-height:1.2; }
@media (prefers-reduced-motion: reduce) { .done-ring { transition:none; } }


/* ── возвращение к прерванному ──────────────────────── */
.resume-card { text-align:center; padding:var(--s-2xl) var(--s-xl); }
.resume-card .actions { justify-content:center; }


/* ── отклик на нажатие ───────────────────────────────────
   Тренажёр открывают десятки раз в день, поэтому движение здесь
   на грани заметности: только подтвердить, что нажатие услышано. */
.btn:active:not(:disabled),
.opt:active:not(:disabled),
.cell:active,
.course:active { transform:scale(0.97); transition:transform var(--t-press) var(--ease-out); }
.btn, .opt, .cell, .course {
  transition:border-color .14s, background .14s, box-shadow .14s,
             transform 420ms var(--ease-spring); }

/* ── появление того, что видят редко ─────────────────────
   Экран итога и приглашение продолжить возникают на месте задания —
   без перехода это выглядит как сбой отрисовки. */
@keyframes rise {
  from { opacity:0; transform:translateY(8px) scale(0.985); }
  to   { opacity:1; transform:none; }
}
.done-card, .resume-card { animation:rise 320ms var(--ease-out) both; }
.done-ring { animation:rise 380ms var(--ease-out) both; }

/* Разбор ответа появляется после каждой проверки — это уже десятки раз
   за занятие, поэтому только быстрое проявление, без сдвига.
   Кадры, а не переход: пустой ярлык вердикта висит в разметке с самого
   начала и оживает сменой класса — переходу тут не от чего оттолкнуться,
   а перезапуска с нуля бояться нечего, карточка каждый раз новая. */
@keyframes reveal { from { opacity:0; } to { opacity:1; } }
.solution, .verdict.ok, .verdict.no { animation:reveal 140ms var(--ease-out) both; }

/* Смена задания. Не анимация, а проявление на грани заметности: сюда
   заходят десятки раз за занятие, и всё, что дольше, начнёт мешать.
   Внутри задания карточка перерисовывается на каждый выбор варианта —
   там класса нет, иначе экран мигал бы при каждом нажатии. */
/* Ширина строки в задании ограничена читаемостью (72 знака), а карточка
   тянулась во всю полосу: на широком экране условие и варианты жались
   влево, а справа оставалась пустая треть карточки */
/* По левому краю, вместе с подсказками про клавиши: по центру тренажёр
   на странице варианта стоял отдельно от карточки настроек над ним и от
   строки подсказок под ним. В колонке занятия ширину и так задаёт колонка */
#trainer, .hintbar { max-width:56rem; }
/* между карточкой настроек варианта и тренажёром стоит сценарий таймера,
   поэтому соседство ищем через него, а не вплотную */
main .card ~ #trainer { margin-top:var(--s-xl); }
.trainer-done > .hintbar, .trainer-done > #trainer ~ .actions { display:none; }
#trainer .card.enter { animation:reveal 110ms var(--ease-out) both; }

/* Сообщение об успехе или ошибке приходит после действия — редко. */
.flash { animation:rise 260ms var(--ease-out) both; }

@media (prefers-reduced-motion: reduce) {
  /* не выключаем совсем: остаётся проявление без движения */
  .done-card, .resume-card, .done-ring, .flash { animation:reveal 160ms linear both; }
  .menu-drop { animation:reveal 120ms linear both; }
  .startbar, .courses > .course, .planned { animation:reveal 200ms linear both;
    animation-delay:0ms; }
  .btn:active, .opt:active, .cell:active, .course:active { transform:none; }
  .progress-bar i, .done-ring, .skip { transition:none; }
}

/* имя пользователя — кружок с буквой, чтобы шапка не росла от длинных имён */
.acct summary { display:inline-flex; align-items:center; gap:var(--s-2xs); max-width:9rem; }
.acct summary b { display:grid; place-items:center; width:1.6rem; height:1.6rem; flex:none;
  border-radius:50%; background:var(--accent-soft); color:var(--accent);
  font-family:var(--sans); font-size:var(--t-xs); font-weight:600; }
.acct summary span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* строка длиннее ~75 знаков читается тяжело — держим текст в комфортной мере */
main p:not([class]), main li { max-width:72ch; }
/* Вариант ответа — не абзац: «амины» незачем ограничивать длиной строки,
   а карточка из-за этого выглядела недоклеенной с правого края */
main li.opt, main li.pair, main .opts li { max-width:none; }

/* ── шапка на телефоне: список под кнопкой ───────────── */
/* max-height — про телефон, повёрнутый набок: там ширины хватает,
   а высоты 375px, и три ряда шапки съедают треть экрана */
@media (max-width: 860px), (max-height: 500px) {
  /* nav уходит в выпадашку, поэтому вправо всех толкает сам бренд */
  .bar .brand { margin-right:auto; }
  .navbox { display:block; }
  .navbox > summary { display:grid; place-items:center; width:2.4rem; height:2.4rem;
    order:9; border:1px solid var(--line); border-radius:var(--r-md); background:var(--surface);
    color:var(--dim); cursor:pointer; list-style:none; }
  .navbox > summary::-webkit-details-marker { display:none; }
  .navbox > summary .ico { width:1.3rem; height:1.3rem; color:inherit; }
  .navbox[open] > summary { border-color:var(--accent); color:var(--accent); }
  .navbox > .nav { position:absolute; left:0; right:0; top:100%; margin:0;
    flex-direction:column; align-items:stretch; gap:0; padding:var(--s-2xs) var(--s-md) var(--s-md);
    background:var(--surface); border-bottom:1px solid var(--line);
    box-shadow:var(--shadow); z-index:var(--z-nav);
    /* тот же материал, что у шапки, из-под которой список выезжает */
    backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur);
    max-height:calc(100dvh - 4rem); overflow-y:auto; overscroll-behavior:contain; }
  .navbox > .nav > a, .navbox > .nav .menu > summary { padding:var(--s-sm) var(--s-2xs);
    font-size:var(--t-base); border-bottom:1px solid var(--line); }
  .navbox > .nav .menu { position:static; }
  .navbox > .nav .menu-drop { position:static; box-shadow:none; border:0;
    padding:0 0 0 var(--s-md); background:none; }
  .navbox > .nav .menu-drop a { padding:var(--s-xs) var(--s-2xs); border-bottom:1px solid var(--line);
    border-radius:0; }
  .bar { position:relative; }
  .acct summary span { display:none; }
  /* в вертикальном списке подчёркивание снизу сливается с разделителем строк,
     поэтому текущий пункт помечается полосой слева */
  .navbox > .nav > a.here, .navbox > .nav .menu > summary.here {
    box-shadow:inset 3px 0 0 var(--accent); padding-left:var(--s-sm); }
}

/* на телефоне знаков помещается вдвое меньше, поэтому те же .10 бьют по глазам */
@media (max-width: 720px) { #wm { opacity:.055; font-size:11px; } }

/* формулы вырезаны из PDF мелкими — 80px структурной формулы не разглядеть.
   Увеличиваем только их: крупные схемы и таблицы остаются как есть */
img.fig.zoom, .figwrap img.zoom { zoom:1.75; }

/* Схема со страницы сборника снята во всю ширину набора: на телефоне она
   ужимается до высоты в три десятка точек, и формулу не разобрать. Держим её
   крупнее ширины экрана, а обёртка прокручивается вбок — страница при этом
   стоит на месте */
/* min-width:0 обязателен: внутри кнопки варианта обёртка — элемент flex, а
   тот по умолчанию не сжимается уже содержимого, и прокрутка не включается */
.figwrap { display:block; min-width:0; max-width:100%; overflow-x:auto; }
@media (max-width:700px) {
  .figwrap img.wide { max-width:none; zoom:.5; }
}


/* строка вместо графика, пока решать нечего */
.empty { color:var(--faint); font-size:var(--t-md); margin:var(--s-xs) 0 0; }
.chart-card.nodata .legend-row, .chart-card.nodata .astable,
.chart-card.nodata .sub { display:none; }

/* ── разделы: одна крупная плитка и стопка помельче ──── */
.courses { display:grid; gap:var(--s-md); grid-template-columns:1.35fr 1fr; align-items:stretch; }
/* Крупная плитка тянется на два ряда соседней стопки, и при коротком тексте
   внизу оставалась пустота в треть карточки: описание прижимаем к низу */
.courses .lead-course { grid-row:span 2; display:flex; flex-direction:column; }
.courses .lead-course .course-blurb { margin-bottom:0; }
.courses .lead-course .course-foot { margin:auto 0 0; padding-top:var(--s-lg);
  font-size:var(--t-sm); color:var(--faint);
  /* линия вместо пустоты: плитка выше соседей на две строки, и просвет под
     текстом читался как обрыв, а не как поле */
  border-top:1px solid var(--line); }
/* Раздел, которого ещё нет: та же плитка, но пунктиром и без наведения —
   видно, что это обещание, а не ссылка */
/* Карточка ребёнка в кабинете родителя: кольцо цели и строка о неделе */
.kid-card { display:flex; gap:var(--s-md); align-items:center; }
.kid-card h3 { margin:0 0 var(--s-3xs); }
.kid-card .course-meta { display:block; margin:0; }
.courses .planned-card { grid-column:1 / -1; border-style:dashed;
  background:transparent; box-shadow:none; }
.courses .planned-card h3 { color:var(--dim); }
.courses .planned-card::after { content:none; }
/* Число у соседних плиток — той же породы, что у главной, но на ступень
   тише: иначе три одинаковых больших цифры спорят друг с другом */
.courses .course:not(.lead-course) .course-count { margin:var(--s-2xs) 0 0; }
/* В одну строку: числу с подписью незачем занимать две, а «в 28 темах»
   становится третьей — на карточке в две строки текста это уже столбик */
.courses .course:not(.lead-course) .course-count b { display:inline;
  font-size:var(--t-3xl); margin-right:var(--s-3xs); }
.courses .course:not(.lead-course) .course-meta { margin:var(--s-3xs) 0 0; }
.courses .course h3 { font-size:var(--t-lg); }
.courses .lead-course h3 { font-size:var(--t-4xl); letter-spacing:-.02em; margin-bottom:var(--s-3xs); }
.course-count { font-size:var(--t-md); color:var(--dim); margin:var(--s-2xs) 0 var(--s-sm); }
.course-count b { font-family:var(--mono); font-size:var(--t-5xl); color:var(--tint); font-weight:500;
  font-variant-numeric:tabular-nums; letter-spacing:-.02em; display:block; line-height:1.05; }
.course-blurb { font-size:var(--t-md); max-width:40ch; text-wrap:pretty; }

/* Плитка во всю ширину под крупной и стопкой: школьная программа — второе
   направление курса, и в узкой колонке она читалась бы наравне с ВПР.
   Текст слева, счёт справа: взгляд идёт по строке, а не прыгает вниз. */
.wide-course { grid-column:1 / -1; }
.wide-course-body { display:flex; gap:var(--s-md) var(--s-3xl); align-items:baseline;
  justify-content:space-between; flex-wrap:wrap; }
.wide-course .course-blurb { margin:var(--s-3xs) 0 0; max-width:46ch; }
.wide-course .course-count { margin:0; text-align:right; }
.wide-course .course-count b { display:inline; font-size:var(--t-3xl); margin-right:var(--s-3xs); }
@media (max-width: 720px) {
  .wide-course .course-count { text-align:left; }
}

/* ── школьный курс: модули ───────────────────────────── */
/* Модуль без материалов остаётся в сетке — программа видна целиком, — но
   приглушён: иначе половина витрины спорит за внимание с тем, что открыто. */
.course-soon { background:var(--ground); border-style:dashed; }
.course-soon h3 { color:var(--dim); font-weight:500; }
.course-soon .code, .course-soon .badge { opacity:.7; }
.course-soon::after { display:none; }
@media (hover: hover) and (pointer: fine) {
  .course-soon:hover { border-color:var(--line); transform:none; }
}

/* Витрина модулей открывается изредка — появление по очереди уместно. */
@media (prefers-reduced-motion: no-preference) {
  .modules > .course { animation:rise 340ms var(--ease-out) both; }
  .modules > .course:nth-child(2) { animation-delay:40ms; }
  .modules > .course:nth-child(3) { animation-delay:80ms; }
  .modules > .course:nth-child(4) { animation-delay:120ms; }
  .modules > .course:nth-child(n+5) { animation-delay:160ms; }
}
@media (prefers-reduced-motion: reduce) {
  .modules > .course { animation:reveal 200ms linear both; animation-delay:0ms; }
}
.course-meta { font-size:var(--t-sm); color:var(--dim); font-variant-numeric:tabular-nums;
  margin:var(--s-2xs) 0 0; }
.planned { color:var(--faint); font-size:var(--t-md); max-width:56ch; margin:var(--s-2xl) 0 0;
  text-wrap:pretty; }
@media (max-width: 720px) {
  .courses { grid-template-columns:1fr; }
  .courses .lead-course { grid-row:auto; }
  .courses .lead-course h3 { font-size:var(--t-2xl); }
  .course-count b { font-size:var(--t-4xl); }
}

/* ── доступность и типографика ──────────────────────── */
/* ссылка для тех, кто ходит с клавиатуры: не надо прощёлкивать всю навигацию */
.skip { position:absolute; left:.75rem; top:.75rem; z-index:var(--z-mark);
  padding:var(--s-xs) var(--s-md); border-radius:var(--r-md); background:var(--accent);
  color:var(--on-accent); font-size:var(--t-md); text-decoration:none;
  transform:translateY(-4.5rem);
  transition:transform var(--t-fast) var(--ease-out); }
.skip:focus { transform:none; }
main:focus { outline:none; }

/* заголовок не должен оставлять одно слово на последней строке */
h1, h2, h3, .lead-course h3 { text-wrap:balance; }
p, li, .lead { text-wrap:pretty; }

/* цифры в статистике и таблицах выстраиваются по разрядам */
.stat b, .ring, .done-ring, table td, table th, .code, .badge, .progress-label,
.course-count, .course-meta { font-variant-numeric:tabular-nums; }

/* Зерно: ровные заливки на большом экране выглядят стерильно, шум даёт
   бумажную фактуру. Слой закреплён и не ловит мышь — при прокрутке он не
   перерисовывается, поэтому на телефоне не стоит ничего. */
body::after {
  content:""; position:fixed; inset:0; pointer-events:none; z-index:var(--z-grain);
  opacity:.02;
  background-image:url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='140' height='140'>\
<filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3'/>\
</filter><rect width='140' height='140' filter='url(%23n)'/></svg>");
}
:root[data-theme="dark"] body::after { opacity:.035; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body::after { opacity:.035; }
}

/* ── служебные классы вместо повторяющихся inline-стилей ── */
/* ── шкала отступов ──────────────────────────────────────
   Раньше отступы задавались прямо в шаблонах: .5rem, .8rem, .9rem, 1rem,
   1.2rem, 1.5rem — шесть значений для одного и того же промежутка, и
   соседние блоки стояли на разной высоте. Теперь три ступени на всё. */
.mt-1 { margin-top:var(--s-xs); } .mt-2 { margin-top:var(--s-lg); } .mt-3 { margin-top:var(--s-2xl); }
.mb-1 { margin-bottom:var(--s-xs); } .mb-2 { margin-bottom:var(--s-lg); }
.mb-3 { margin-bottom:var(--s-2xl); } .mb-xs { margin-bottom:var(--s-3xs); }
.my-1 { margin:var(--s-xs) 0; } .my-2 { margin:var(--s-lg) 0; } .my-0 { margin:0; }
/* Пояснение под полем или заголовком: одна строка серым. */
.hint { margin:0; font-size:var(--t-sm); color:var(--dim); }
.pre-line { white-space:pre-line; }
/* Список без маркеров: перечень открытых разделов — это строки данных,
   а не пункты рассуждения */
ul.plain { list-style:none; margin:0; padding:0; display:grid; gap:var(--s-xs); }
/* Строка с иконкой или пометкой перед текстом. */
.row-tight { display:flex; gap:var(--s-2xs); align-items:center; }
/* Кого выбрать: флажки учеников. Строкой с переносом десяток имён ложится
   рваными рядами — глаз не находит нужное. Колонки одной ширины читаются
   сверху вниз, а класс на тридцать человек прокручивается внутри себя,
   чтобы кнопка «Задать» не уезжала за нижний край экрана. */
.pickers { display:grid; gap:var(--s-2xs) var(--s-md);
  grid-template-columns:repeat(auto-fill, minmax(12rem, 1fr));
  max-height:15rem; overflow-y:auto; }
/* Ролик занятия: рамка держит пропорции 16:9, пока видео грузится. */
.video-frame { position:relative; padding-top:56.25%; margin-top:var(--s-md); }
.video-frame iframe { position:absolute; inset:0; width:100%; height:100%;
  border:0; border-radius:var(--r-md); }
.empty-state { text-align:center; padding:var(--s-5xl) 0; }
.empty-state .lead { margin-inline:auto; }
.indent { padding-left:var(--s-lg); }

/* ── итог варианта ───────────────────────────────────── */
/* Точка у пункта меню: есть несданное. Цифру ставить не стоит — она
   заставляет считать, а нужно только знать, что работа есть. */
.dot { display:inline-block; width:.42rem; height:.42rem; border-radius:var(--r-full);
  background:var(--accent); vertical-align:middle; margin-left:var(--s-3xs); }

/* ── вычитка потоком ─────────────────────────────────── */
.task-head { display:flex; gap:var(--s-xs); align-items:center; flex-wrap:wrap;
  margin-bottom:var(--s-xs); }
/* Ничья картинка при вычитке: рядом с ней путь, который вставляют в строку
   варианта после «|». Подпись мелкая и моноширинная — её копируют, а не читают. */
.freepic { margin:0; max-width:12rem; }
.freepic figcaption { word-break:break-all; color:var(--faint); }

.blank-topics { margin:var(--s-xs) 0 0; padding-left:var(--s-lg); }
.blank-topics li { margin-top:var(--s-3xs); }

/* Что именно не так с заданием — списком, а не одним «требует вычитки»:
   преподаватель читает пять сотен карточек подряд и должен понимать, куда
   смотреть, не перечитывая условие целиком. */
.flaws { display:flex; gap:var(--s-2xs) var(--s-md); flex-wrap:wrap; list-style:none;
  margin:0 0 var(--s-sm); padding:var(--s-2xs) var(--s-xs); font-size:var(--t-sm); color:var(--warn);
  background:var(--warn-soft); border-radius:var(--r-sm); }
.flaws li::before { content:"— "; color:var(--faint); }
form[data-review] { transition:opacity var(--t-base) var(--ease-out),
  transform var(--t-base) var(--ease-out); }
form[data-review].current { border-color:var(--accent); }
/* карточка уходит, а не исчезает: иначе непонятно, приняли действие или нет */
form[data-review].gone { opacity:0; transform:translateY(-6px); pointer-events:none; }
form[data-review].failed { border-color:var(--err); }
@media (prefers-reduced-motion: reduce) {
  form[data-review].gone { transform:none; }
}

.timerbox { border-color:var(--accent-line); transition:border-color var(--t-base) ease; }
.timerbox.soon { border-color:var(--warn); }
.timerbox.over { border-color:var(--err); }
.timer-row { display:flex; gap:var(--s-md); align-items:baseline; }
.timer-row .push { margin-left:auto; }
.clock { font-family:var(--mono); font-size:var(--t-3xl); font-variant-numeric:tabular-nums; }
.timerbox.soon .clock { color:var(--warn); }
.timerbox.over .clock { color:var(--err); }

.result { border-color:var(--accent-line); }
.result-row { display:flex; gap:var(--s-sm); align-items:baseline; flex-wrap:wrap; }
.result-primary { font-family:var(--mono); font-size:var(--t-4xl); color:var(--ink); }
.result-primary i { font-style:normal; font-size:var(--t-base); color:var(--faint); }
.result-score { font-family:var(--mono); font-size:var(--t-4xl); color:var(--warn);
  margin-left:var(--s-xs); }
.result-score.ok { color:var(--ok); }
.narrow-sm { max-width:14rem; }
.ta-sm { min-height:3rem; } .ta-md { min-height:7rem; } .ta-lg { min-height:6rem; }
/* Поле растёт под содержимое: в списке вариантов их пять, а видно было три,
   и правка шла вслепую, с прокруткой внутри поля. Где браузер этого ещё не
   умеет, остаётся min-height выше. */
textarea { field-sizing:content; max-height:60vh; }
.t-err { color:var(--err); }
.mono { font-family:var(--mono); }
.t-warn { color:var(--warn); }
.t-ok { color:var(--ok); }
.t-accent { color:var(--accent); }
.t-dim { color:var(--dim); }
.small { font-size:var(--t-sm); }
.smaller { font-size:var(--t-xs); }
.narrow { max-width:32rem; }
.row { display:flex; gap:var(--s-md); align-items:end; flex-wrap:wrap; }
/* Ширины полей формы — шкала вместо девяти разных чисел вразнобой.
   Поля одного смысла (число, дата, короткий код) выстраиваются по одной
   границе, и форма перестаёт выглядеть собранной на глаз. */
.w-sm { width:6rem; } .w-md { width:8rem; } .w-lg { width:10rem; }
.w-auto { width:auto; max-width:100%; }
/* Выпадающий список шириной по содержимому — это ширина самого длинного
   названия темы: он уезжал за правый край страницы вместе с прокруткой,
   а прижатый к 100% занимал всю строку рядом с тремя короткими соседями. */
select.w-auto { max-width:min(100%, 24rem); }
/* Строка подписей под заголовком: кто, когда, чего ждёт. */
/* ряды при переносе — плотно: на телефоне каждая подпись уходила на свою
   строку, и зазор между ними растягивал карточку вдвое */
.meta { display:flex; flex-wrap:wrap; align-items:baseline; gap:0 var(--s-xs);
  font-size:var(--t-sm); color:var(--dim); }
.row > .grow { flex:1; min-width:14rem; }
.row > .grow-sm { flex:1; min-width:12rem; }

/* браузерный серый плейсхолдера даёт 3.77 к фону поля — ниже нормы */
::placeholder { color:var(--dim); opacity:1; }

/* ── появление витрины ──────────────────────────────────
   Только на главной и только один раз при загрузке: карточки входят по
   очереди, и глаз успевает считать порядок — сперва основной раздел,
   потом остальные. В тренажёре ничего подобного нет: туда заходят по
   десять раз на дню, и там любая задержка мешает. */
@media (prefers-reduced-motion: no-preference) {
  .startbar, .courses > .course, .planned {
    animation:rise 380ms var(--ease-out) both;
  }
  .courses > .course:nth-child(2) { animation-delay:60ms; }
  .courses > .course:nth-child(3) { animation-delay:120ms; }
  .courses > .course:nth-child(n+4) { animation-delay:180ms; }
  .planned { animation-delay:220ms; }
}

/* ── задание из варианта ВПР ────────────────────────────
   Раньше сюда клали скриншот чужой страницы целиком, вместе с их панелью
   навигации, а поля ответа висели отдельным списком внизу. Теперь условие
   собрано из частей: рисунки, таблица сопоставления, поля ввода. */
.vpr-text { margin:var(--s-3xs) 0 var(--s-md); max-width:70ch; }
.vpr-text b { color:var(--accent); }

.vpr-figs { display:grid; gap:var(--s-sm); margin:0 0 var(--s-xl);
  grid-template-columns:repeat(auto-fit, minmax(150px, 1fr)); }
.vpr-fig { display:flex; flex-direction:column; align-items:center; gap:var(--s-xs);
  padding:var(--s-md) var(--s-sm); border:1px solid var(--line); border-radius:var(--r-lg);
  background:var(--surface); cursor:pointer; text-align:center; font-size:var(--t-sm);
  transition:border-color .16s, background .16s; }
.vpr-fig img { max-width:100%; height:auto; border-radius:var(--r-sm); }
.vpr-fig input { accent-color:var(--accent); width:1.15rem; height:1.15rem; }
.vpr-fig:has(input:checked) { border-color:var(--accent); background:var(--accent-soft); }
.vpr-figs .vpr-fig.right { border-color:var(--ok); background:var(--ok-soft); }
.vpr-figs .vpr-fig.wrong { border-color:var(--err); background:var(--err-soft); }
@media (hover: hover) and (pointer: fine) {
  .vpr-fig:hover { border-color:var(--accent); }
}

.vpr-grid { width:100%; border-collapse:collapse; }
.vpr-grid thead th { padding:var(--s-xs) var(--s-xs); vertical-align:bottom; text-align:center;
  font-size:var(--t-sm); font-weight:500; text-transform:none; letter-spacing:0;
  color:var(--dim); }
.vpr-grid thead img { display:block; margin:0 auto var(--s-xs); max-width:120px; height:auto;
  border-radius:var(--r-sm); }
.vpr-grid tbody th { text-align:left; font-size:var(--t-sm); font-weight:500; color:var(--dim);
  text-transform:none; letter-spacing:0; white-space:nowrap; padding-right:var(--s-md); }
.vpr-grid td { padding:var(--s-2xs) var(--s-2xs); }
.vpr-grid select, .vpr-grid input { width:100%; min-width:8.5rem; }
.vpr-grid td.right select, .vpr-grid td.right input { border-color:var(--ok); }
.vpr-grid td.wrong select, .vpr-grid td.wrong input { border-color:var(--err); }
.vpr-unit { color:var(--dim); font-size:var(--t-sm); white-space:nowrap; }
.want { display:block; margin-top:var(--s-3xs); font-family:var(--mono); font-size:var(--t-xs);
  color:var(--ok); }

@media (max-width: 620px) {
  .vpr-grid thead img { max-width:80px; }
  .vpr-grid select, .vpr-grid input { min-width:7rem; font-size:var(--t-sm); }
  .vpr-grid tbody th { white-space:normal; }
}

/* ── разбор задания ВПР преподавателем ──────────────── */
.vpr-editor { display:grid; gap:var(--s-xl); grid-template-columns:minmax(0,1fr) 22rem;
  align-items:start; }
.vpr-scan { position:sticky; top:5rem; }
.vpr-scan img { width:100%; border:1px solid var(--line); border-radius:var(--r-md); }
.vpr-block { border:1px solid var(--line); border-radius:var(--r-lg); padding:var(--s-md) var(--s-lg);
  margin:0 0 var(--s-md); background:var(--surface); }
.vpr-block legend { font-family:var(--mono); font-size:var(--t-xs); color:var(--faint);
  text-transform:uppercase; letter-spacing:.06em; padding:0 var(--s-2xs); }
.vpr-block label { display:block; margin:var(--s-sm) 0 0; }
.vpr-block textarea { width:100%; font-family:var(--mono); font-size:var(--t-sm); }
@media (max-width: 900px) {
  .vpr-editor { grid-template-columns:1fr; }
  .vpr-scan { position:static; }
}

/* отклик на отправку вопроса: без него непонятно, ушло или нет */
/* Разметка тренажёра, которую рисует скрипт. Раньше она держалась на
   атрибутах style= прямо в строках JS: мимо шкал отступов и кеглей, и
   поправить её, не открывая скрипт, было нельзя. */
.done-card h2 { margin:var(--s-sm) 0 var(--s-2xs); }
.done-card .done-note { color:var(--dim); margin:0 0 var(--s-xl); }
.actions.center { justify-content:center; }
.askbox { margin-top:var(--s-lg); }
.askbox summary { cursor:pointer; font-size:var(--t-sm); color:var(--accent); }
.askbox #qtext { min-height:4rem; margin-top:var(--s-xs); }
.scanfield { max-width:14rem; font-family:var(--mono); }
.fig.framed { border:1px solid var(--line); border-radius:var(--r-sm); }
.numrow { display:flex; gap:var(--s-sm); align-items:center; max-width:20rem; }
.numrow input { font-family:var(--mono); }
.qmsg { font-size:var(--t-sm); color:var(--dim); }
.qmsg.ok { color:var(--ok); }
.qmsg.bad { color:var(--err); }

/* ── выбор роли при регистрации ─────────────────────── */
.roles { border:0; padding:0; margin:0 0 var(--s-2xs); }
.roles legend { font-size:var(--t-sm); color:var(--dim); padding:0; margin-bottom:var(--s-xs); }
.role { display:flex; gap:var(--s-sm); align-items:flex-start; margin:0 0 var(--s-xs);
  padding:var(--s-sm) var(--s-md); border:1px solid var(--line); border-radius:var(--r-md);
  background:var(--surface); cursor:pointer; transition:border-color .16s, background .16s; }
.role input { accent-color:var(--accent); margin-top:var(--s-3xs); width:1.15rem; height:1.15rem;
  flex:none; }
.role b { display:block; font-weight:500; font-size:var(--t-md); }
.role small { color:var(--dim); font-size:var(--t-sm); }
.roles .role:has(input:checked) { border-color:var(--accent); background:var(--accent-soft); }
@media (hover: hover) and (pointer: fine) { .role:hover { border-color:var(--accent); } }
.invite small { display:block; margin-top:var(--s-2xs); font-size:var(--t-xs); }

/* код привязки телеграма: его переписывают руками, поэтому крупно */
.tgcode { margin:var(--s-3xs) 0 var(--s-xs); }
.tgcode code { font-family:var(--mono); font-size:var(--t-base); letter-spacing:.06em;
  padding:var(--s-xs) var(--s-md); border-radius:var(--r-md); background:var(--ground);
  border:1px solid var(--line); display:inline-block; user-select:all; }
.vpr-block.guess { border-color:var(--warn); }

/* Ловушка для роботов в форме регистрации: не видна, но остаётся в разметке */
.trap { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

/* ── панель ответа на телефоне ────────────────────────
   У задания с картинкой и шестью вариантами кнопка «Проверить» уезжала за
   нижний край: ученик выбирал ответ, а потом искал, чем его подтвердить.
   Панель прилипает к низу экрана, пока карточка видна, и сделана из того же
   стекла, что шапка, — под ней видно, что условие продолжается. */
@media (max-width: 860px) {
  #trainer .actions:has(#check) {
    position:sticky; bottom:0; z-index:var(--z-sticky);
    /* Снизу без отрицательного отступа: под панелью всегда что-то есть —
       разбор, «Спросить преподавателя», — и отрицательный отступ затягивал
       это под неё: у «Как решать» пропадал верхний край */
    margin:var(--s-xl) calc(-1 * var(--s-lg)) var(--s-sm);
    padding:var(--s-sm) var(--s-lg)
            calc(var(--s-sm) + env(safe-area-inset-bottom));
    background:var(--surface); box-shadow:0 -1px 0 var(--line);
  }
  @supports (backdrop-filter: blur(1px)) {
    #trainer .actions:has(#check) { background:var(--glass);
      backdrop-filter:var(--glass-blur); -webkit-backdrop-filter:var(--glass-blur); }
  }
  /* разбор ответа идёт под панелью — он не должен кончаться прямо под ней */
  #trainer .card { padding-bottom:var(--s-2xs); }
}

/* Напоминание подтвердить почту: строка и кнопка рядом, на телефоне — друг
   под другом. Место занимает мало: висит на каждой странице, пока не нажмут */
.confirm-note { display:flex; gap:var(--s-md) var(--s-lg); align-items:center;
  flex-wrap:wrap; justify-content:space-between; margin:0 0 var(--s-lg); }
.confirm-note form { margin:0; }
.confirm-note .btn { border-color:var(--warn); color:var(--warn); }

/* Приглашение подключить телеграм и напоминание про почту: строка и кнопки
   рядом, на телефоне — друг под другом. Висит на каждой странице, поэтому
   занимает одну строку и не кричит цветом */
.tg-invite { display:flex; gap:var(--s-md) var(--s-lg); align-items:center;
  flex-wrap:wrap; justify-content:space-between; margin:0 0 var(--s-xl); }
.hero-art + .tg-invite { margin-top:calc(-1 * var(--s-md)); }
@media (max-width: 560px) { .tg-invite .btn { padding-block:var(--s-2xs); } }
.tg-done form { margin:0; }

/* Команда привязки и кнопка «Копировать» — в одну строку, на узком экране
   переносом. Код набирают вслепую, пальцем его точно не выделить */
.tgcode { display:flex; gap:var(--s-xs); align-items:center; flex-wrap:wrap; }

/* ── строки и ширины: вместо стиля в разметке ─────────────
   Стиль, прописанный прямо в теге, не переиспользуется и не подчиняется
   шкале: рядом оказываются gap .4 и gap .6 без всякой причины. */
.row-base { display:flex; gap:var(--s-xs); align-items:baseline; flex-wrap:wrap; }
.row-mid { display:flex; gap:var(--s-xs); align-items:center; flex-wrap:wrap; }
.mw-sm { max-width:18rem; } .mw-md { max-width:26rem; }
.minw-sm { min-width:9rem; } .minw-md { min-width:12rem; }
/* Сводка под графиком и подпись внизу страницы */
.footnote { margin-top:var(--s-4xl); color:var(--faint); font-size:var(--t-xs); }
/* Раскрывающийся заголовок: рука показывает, что по нему нажимают */
summary.link { cursor:pointer; color:var(--accent); }

/* Кнопка опасного действия: удаление аккаунта, данных, группы. Красной
   должна быть рамка, а не заливка — заливка кричит там, где нужно просто
   предупредить, что назад дороги нет */
.btn.danger { border-color:var(--err); color:var(--err); }
.btn.danger:hover { background:var(--err-soft); border-color:var(--err); color:var(--err); }
/* Кнопка внутри таблицы: там строки плотные, и обычная разгоняет их вдвое */
.btn.accent { border-color:var(--accent); color:var(--accent); }
.btn.accent:hover { background:var(--accent-soft); }
.btn.small { padding:var(--s-3xs) var(--s-sm); font-size:var(--t-sm); min-height:0; }
/* Поля внутри строки таблицы — ростом с маленькую кнопку рядом: полноразмерный
   список возле «Сменить» выглядел на голову выше и раздувал строку */
td :is(select, input:not([type=checkbox]):not([type=radio])) { min-height:2.1rem;
  padding-top:var(--s-3xs); padding-bottom:var(--s-3xs); font-size:var(--t-sm); }
td .btn.small { min-height:2.1rem; }
/* Строка правки темы: поля ростом с кнопку рядом, ширины по смыслу */
.topics-edit td { vertical-align:middle; padding-block:var(--s-2xs); }
.topics-edit td:nth-child(1) input { width:6.5rem; }
.topics-edit td:nth-child(2) input { width:4rem; }
.topics-edit td:nth-child(3) input { min-width:16rem; }
.topics-edit td:nth-child(4) input { width:4.5rem; }
.topics-edit form { display:none; }
/* у модулей свои колонки: название длинное, классы короткие */
.modules-edit td { vertical-align:middle; padding-block:var(--s-2xs); }
.modules-edit form { display:none; }
.modules-edit td:nth-child(2) input { width:100%; min-width:16rem; }
.modules-edit td:nth-child(3) input { width:5rem; }
.modules-edit td:nth-child(4) input { width:100%; min-width:14rem; }
.topics-edit form.inline { display:inline; }
.center { text-align:center; } .nowrap { white-space:nowrap; }
/* опасное действие — в стороне от частых «Годится» и «Скрыть» */
.actions .push-right, .sub-head .push-right { margin-left:auto; }
/* Присланная работа в очереди: имя и работа крупно, остальное строкой ниже */
.sub-head { display:flex; flex-wrap:wrap; align-items:baseline; gap:var(--s-2xs) var(--s-sm); }
.sub-head b { font-size:var(--t-base); }
.sub-head span:not(.badge) { color:var(--dim); }
/* Заявка на занятия: кто и что пишет — слева, статус — справа той же строкой */
.lead-card { display:flex; gap:var(--s-xs) var(--s-lg); align-items:flex-start;
  flex-wrap:wrap; justify-content:space-between; }
.lead-card > .grow { flex:1; min-width:min(100%, 18rem); }
.lead-card form { margin:0; }
select.compact { min-height:2.1rem; padding-top:var(--s-3xs); padding-bottom:var(--s-3xs);
  font-size:var(--t-sm); }
/* Для экранной читалки: подпись есть, на экране её не видно */
.visually-hidden { position:absolute; width:1px; height:1px; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap; }
@media (pointer: coarse) { .btn.small { min-height:36px; } }
/* Просроченное обращение по 152-ФЗ: закон даёт срок, и он не должен теряться
   в списке */
.card.overdue { border-color:var(--err); }
/* Цветная метка в легенде графика */
.dot-ok { background:var(--data-ok); } .dot-bad { background:var(--data-bad); }
/* Номер ошибки на странице 404 и 500 */
.error-code { font-family:var(--mono); font-size:3.4rem; color:var(--accent);
  letter-spacing:-.03em; }
.mt-0 { margin-top:0; } .mb-0 { margin-bottom:0; } .gap-1 { gap:var(--s-2xs); }
.t-base { font-size:var(--t-base); } .t-ink { color:var(--ink); }
.smallest { font-size:var(--t-xs); }
/* Строка, на которую стоит посмотреть, — полоской слева, а не заливкой во
   всю ширину: четыре залитых строки подряд читаются как поломка таблицы,
   а не как пометка. */
.bg-warn { background:color-mix(in srgb, var(--warn-soft) 50%, transparent);
  box-shadow:inset 3px 0 0 var(--warn); }
.framed { border:1px solid var(--line); border-radius:var(--r-md); }

/* ── переход между страницами ────────────────────────────
   Платформа многостраничная: каждый переход — новая загрузка, и содержимое
   моргает белым. Браузеры умеют сшивать соседние страницы сами; шапка при
   этом остаётся на месте, потому что названа отдельно и не участвует в
   растворении. Кто не умеет — просто перейдёт как раньше. */
@view-transition { navigation: auto; }
header { view-transition-name: site-header; }
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-old(root) { animation:reveal 90ms var(--ease-out) reverse both; }
  ::view-transition-new(root) { animation:reveal 140ms var(--ease-out) both; }
}
/* Движение убрано — но и мгновенная подмена не нужна: оставляем проявление */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root), ::view-transition-new(root) { animation:none; }
}

/* ── кольцо цели в кабинете ──────────────────────────────
   Кабинет открывают раз в день-два, и кольцо — это отчёт о сделанном.
   Заполняется при появлении: видно, что цифра означает долю, а не просто
   стоит рядом. В тренажёре ничего подобного нет — туда заходят постоянно. */
@media (prefers-reduced-motion: no-preference) {
  .stat .ring { animation:ring-fill 900ms var(--ease-out) both; }
}
@keyframes ring-fill { from { --p:0; } }

/* ── появление графика ───────────────────────────────────
   Столбцы вырастают снизу за треть секунды и больше не двигаются: читать
   их это не мешает, а страница перестаёт возникать разом. */
@media (prefers-reduced-motion: no-preference) {
  .chart .bar-g { animation:bar-grow 360ms var(--ease-out) both;
    transform-origin:center bottom; }
  .chart .bar-g:nth-child(n+2) { animation-delay:20ms; }
  .chart .bar-g:nth-child(n+6) { animation-delay:40ms; }
  .chart .bar-g:nth-child(n+12) { animation-delay:60ms; }
}
@keyframes bar-grow { from { transform:scaleY(.02); opacity:0; } }

/* ── смена темы ──────────────────────────────────────────
   Тему переключают редко, зато резкая подмена всех цветов бьёт по глазам,
   особенно вечером при переходе в тёмную. Красим на четверть секунды и
   только по нажатию кнопки: постоянный переход на каждом элементе стоил бы
   отрисовки на каждое наведение. */
@media (prefers-reduced-motion: no-preference) {
  :root.theming, :root.theming * {
    transition:background-color 240ms var(--ease-out),
               color 240ms var(--ease-out),
               border-color 240ms var(--ease-out) !important;
  }
}

/* Подсказка вместо пустого места, когда разбора нет: ссылка в строке текста,
   а не кнопка — предложение, а не требование */
.ask-hint .as-link { font: inherit; background: none; border: 0; padding: 0;
  color: var(--accent); text-decoration: underline; cursor: pointer; }
