/* ============================================================================
   СУПРЕМУМ · theory-seg.css — сегментная страница темы
   ============================================================================
   СОБРАН АВТОМАТИЧЕСКИ: python tools\design_system\bridge_seg.py build
   Руками не правится — правь SEG_LAYOUT в сборщике.

   Склеен из пяти частей, каждая следующая перебивает предыдущую:
     1. theory-paper.css — токены + мост старых имён + канон 512 страниц
     2. sup-base.css     — фундамент: типографика, врезки, viz-block, кнопки
     3. paper.css        — шелл: шапка сайта, крошки, подвал
     4. раскладка групп  — ряд грида = группа сегментов + её липкий визуал
     5. sup-controls.css — кнопки, переключатели, ползунки интерактива

   Канон идёт ПЕРВЫМ намеренно. Он и фундамент задают для одного селектора
   разные свойства, поэтому вычитание пересечений (74 селектора) унесло бы
   и то, чего в фундаменте нет. Порядком же теряется только конфликтующее.

   ВНИМАНИЕ, КЕГЛЬ. Канон держит базу 16px: у 512 страниц холсты и viewBox
   интерактивов подбирались под неё. Фундамент ставит 20px (--fs-root) и,
   идя следом, побеждает. Это осознанно — стандарт проекта 20px, ученик не
   должен всматриваться, — но каждая переносимая страница обязана быть
   осмотрена: интерактив, рассчитанный на 16px, может выйти за края.

   ПОДКЛЮЧЕНИЕ НА СТРАНИЦЕ ТЕМЫ:
       <link rel="stylesheet" href="/assets/theory-seg.css">
       <script src="/assets/sup-tone.js"></script>
       ... <script src="/assets/paper.js"></script>
   sup-tone.js — обычным тегом, без defer: он ставит тему и тональность до
   первого кадра. paper.js — в конце body: он пишет --hdr-real, от которой
   считается липкость визуала.
   ============================================================================ */


/* ─── 1/4 theory-paper.css ──────────────────────────────────── */
/* ============================================================================
   СУПРЕМУМ · theory-paper.css — страницы теории на новом фундаменте
   ============================================================================
   СОБРАН АВТОМАТИЧЕСКИ: python tools\design_system\bridge_theory.py build-paper
   Руками не правится.

   Склеен из трёх частей:
     1. sup-tokens-paper.css — токены оформления, тональность «тушь и бумага»
     2. sup-compat.css  — перевод старых имён переменных на новые токены
     3. theory.css      — канон правил 512 страниц, БЕЗ блоков переменных

   Блоки :root и [data-theme="dark"] из канона вырезаны: значения теперь
   приходят из слоя токенов. Остальные правила канона не тронуты — они
   опираются на те же имена переменных, что и раньше, и мост их подставляет.

   Поэтому страницу теории достаточно переключить с theory.css на этот файл.
   Разметка не меняется ни на одной из 512 страниц.

   ПЕРЕВОД СТРАНИЦЫ — ДВЕ СТРОКИ, А НЕ ОДНА. С тех пор как оформление
   выбирает ученик в кабинете, рядом со ссылкой на стили ставится ещё одна:

       <link rel="stylesheet" href="/assets/theory-paper.css">
       <script src="/assets/sup-tone.js"></script>

   Обычным тегом, без defer: он ставит выбранные тему и тональность на <html>
   до первого кадра. Без него страница теории откроется в базовой палитре
   и перекрасится на глазах — либо не перекрасится вовсе.
   ============================================================================ */

/* ─── 1/4 ТОКЕНЫ ─────────────────────────────────────────── */
/* ============================================================================
   СУПРЕМУМ · sup-tokens-paper.css — токены тональностей «Тушь и бумага»
   ============================================================================
   СОБРАН АВТОМАТИЧЕСКИ: python tools\design_system\palette.py write-paper
   Руками не правится — правь PAPER_RAW / PAPER_FIX в palette.py: генератор
   проверит условия приёмки (контраст на композитных стеклянных подложках,
   различимость при дальтонизме) и перезапишет этот файл.

   Источник значений — холст Claude Design «СУПРЕМУМ Темы» (утверждён
   2026-08-20). Тональность выбирается атрибутом data-tone на <html>:
     (нет)                «Тушь и бумага» — 5–6 класс, витрина, кабинет
     data-tone="graphite" «Графит»        — 7–9 класс
     data-tone="news"     «Газета»        — 10–11 класс и ЕГЭ
   Светлая/тёмная — data-theme (ключ localStorage geo-theme, менять нельзя).
   ============================================================================ */

/* ── ТИПОГРАФИКА · общая для тональностей ─────────────────
   База 20px (на ≤720px база опускается до 18px в paper-base.css —
   иначе мера строки на телефоне падает ниже 30 знаков). */
:root {
  --font-display: 'Literata', Georgia, 'Times New Roman', serif;
  --font-body: 'IBM Plex Sans', system-ui, -apple-system, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, 'Cascadia Mono', monospace;
  --font-math: 'KaTeX_Math', 'CMU Serif', 'Times New Roman', serif;

  --fs-root: 20px;
  --fs-3xl: clamp(1.6rem, 5.2vw, 2.5rem);
  --fs-2xl: clamp(1.3rem, 3.6vw, 1.75rem);
  --fs-xl: clamp(1.15rem, 2.4vw, 1.35rem);
  --fs-lg: 1.15rem;
  --fs-md: 1rem;
  --fs-sm: 0.875rem;
  --fs-xs: 0.775rem;

  --lh-3xl: 1.12; --lh-2xl: 1.22; --lh-xl: 1.32; --lh-lg: 1.45;
  --lh-md: 1.55; --lh-sm: 1.5; --lh-xs: 1.45;

  /* Веса ограничены файлами шрифтов: Plex Sans 400…600 (700 даст фальшивый
     жирный), Plex Mono только 500, Literata 200…900. */
  --fw-normal: 400; --fw-medium: 500; --fw-semi: 600;
  --fw-bold: 600; --fw-display: 700; --fw-mono: 500;
}

/* ── РАЗМЕРЫ · шкала 4px ── */
:root {
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 20px;
  --sp-6: 24px; --sp-8: 32px; --sp-10: 40px; --sp-12: 48px;
  --sp-16: 64px; --sp-20: 80px; --sp-24: 96px;

  --measure: 34.9rem;   /* 66 знаков; НЕ в ch — ширина ch зависит от шрифта */
  --page-max: 1180px;
  --page-pad: clamp(var(--sp-4), 4vw, var(--sp-12));
  --tap: 44px;
  --radius-pill: 999px;
}

/* ── ДВИЖЕНИЕ · интерфейс, а не презентация ── */
:root {
  --dur-instant: 80ms; --dur-fast: 150ms; --dur-base: 240ms; --dur-slow: 420ms;
  --ease-out: cubic-bezier(.2, .8, .3, 1);
  --ease-in-out: cubic-bezier(.6, 0, .3, 1);
  --stagger: 60ms;
  --ring-width: 2px; --ring-offset: 2px;
}

/* ── СЛОИ ── */
:root {
  --z-header: 50; --z-sticky: 40; --z-overlay: 80; --z-modal: 90; --z-toast: 100;
}

/* ── ФАКТУРА · миллиметровка ── */
:root {
  --grid-line: color-mix(in oklab, var(--n-5) 34%, transparent);
  --grid-step: 24px;
  --grid-step-fine: 6px;
}

/* Маркер живости файла для линтера сборки. */
:root { --sup-paper: 1; }


/* ── Тушь и бумага · светлая ── */
html:root {
  color-scheme: light;
  --n-0: #fffefb;
  --n-2: #eae7e0;
  --n-3: #ded9d0;
  --n-5: #c0ad98;
  --bg: #f3f1ec;
  --bg-sunken: #e7e3da;
  --surface: rgba(255,254,251,.90);
  --surface-2: rgba(255,254,251,.72);
  --sunken: rgba(35,33,29,.05);
  --text: #23211d;
  --text-strong: #14120f;
  --text-soft: #67645e;
  --text-faint: #858179;
  --border: rgba(35,33,29,.15);
  --border-strong: rgba(35,33,29,.30);
  --rule: rgba(35,33,29,.22);
  --accent: #2b2823;
  --accent-hover: #14120f;
  --accent-fg: #fbfaf7;
  --accent-soft: rgba(35,33,29,.07);
  --accent-line: rgba(35,33,29,.28);
  --pen: #a04a3c;
  --ok: #3f6b4f;
  --err: #9c4038;
  --warn: #8a6a22;
  --info: #3e5574;
  --ok-soft: rgba(63,107,79,.11);
  --err-soft: rgba(156,64,56,.10);
  --warn-soft: rgba(138,106,34,.12);
  --info-soft: rgba(62,85,116,.10);
  --ok-fg: #2c4d38;
  --err-fg: #722d27;
  --warn-fg: #5f4915;
  --info-fg: #2a4052;
  --fig-1: #23211d;
  --fig-2: #3c5a72;
  --fig-3: #3f6b4f;
  --fig-4: #8a6a22;
  --fig-5: #6f587b;
  --fig-6: #b55f48;
  --ring: #2b2823;
  --glass: rgba(255,254,251,.66);
  --glass-strong: rgba(255,254,251,.90);
  --glass-border: rgba(255,255,255,.78);
  --glass-top: rgba(255,255,255,.90);
  --glass-hover: rgba(255,254,251,.99);
  --hair: rgba(35,33,29,.12);
  --sh: rgba(35,33,29,.13);
  --solid: #fffefb;
  --bloom: rgba(255,255,255,.85);
  --shadow-raised: 0 1px 2px rgba(35,33,29,.06);
  --shadow-float: 0 8px 32px rgba(35,33,29,.13);
  --radius-sm: 10px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-xl: 22px;
  --radius-xs: 6px;
  --fill-1: rgba(35,33,29,0.20);
  --fill-2: rgba(60,90,114,0.20);
  --fill-3: rgba(63,107,79,0.20);
  --fill-4: rgba(138,106,34,0.20);
  --fill-5: rgba(111,88,123,0.20);
  --fill-6: rgba(181,95,72,0.20);
}
html:root:where([data-theme="light"]) {
  color-scheme: light;
  --n-0: #fffefb;
  --n-2: #eae7e0;
  --n-3: #ded9d0;
  --n-5: #c0ad98;
  --bg: #f3f1ec;
  --bg-sunken: #e7e3da;
  --surface: rgba(255,254,251,.90);
  --surface-2: rgba(255,254,251,.72);
  --sunken: rgba(35,33,29,.05);
  --text: #23211d;
  --text-strong: #14120f;
  --text-soft: #67645e;
  --text-faint: #858179;
  --border: rgba(35,33,29,.15);
  --border-strong: rgba(35,33,29,.30);
  --rule: rgba(35,33,29,.22);
  --accent: #2b2823;
  --accent-hover: #14120f;
  --accent-fg: #fbfaf7;
  --accent-soft: rgba(35,33,29,.07);
  --accent-line: rgba(35,33,29,.28);
  --pen: #a04a3c;
  --ok: #3f6b4f;
  --err: #9c4038;
  --warn: #8a6a22;
  --info: #3e5574;
  --ok-soft: rgba(63,107,79,.11);
  --err-soft: rgba(156,64,56,.10);
  --warn-soft: rgba(138,106,34,.12);
  --info-soft: rgba(62,85,116,.10);
  --ok-fg: #2c4d38;
  --err-fg: #722d27;
  --warn-fg: #5f4915;
  --info-fg: #2a4052;
  --fig-1: #23211d;
  --fig-2: #3c5a72;
  --fig-3: #3f6b4f;
  --fig-4: #8a6a22;
  --fig-5: #6f587b;
  --fig-6: #b55f48;
  --ring: #2b2823;
  --glass: rgba(255,254,251,.66);
  --glass-strong: rgba(255,254,251,.90);
  --glass-border: rgba(255,255,255,.78);
  --glass-top: rgba(255,255,255,.90);
  --glass-hover: rgba(255,254,251,.99);
  --hair: rgba(35,33,29,.12);
  --sh: rgba(35,33,29,.13);
  --solid: #fffefb;
  --bloom: rgba(255,255,255,.85);
  --shadow-raised: 0 1px 2px rgba(35,33,29,.06);
  --shadow-float: 0 8px 32px rgba(35,33,29,.13);
  --radius-sm: 10px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-xl: 22px;
  --radius-xs: 6px;
  --fill-1: rgba(35,33,29,0.20);
  --fill-2: rgba(60,90,114,0.20);
  --fill-3: rgba(63,107,79,0.20);
  --fill-4: rgba(138,106,34,0.20);
  --fill-5: rgba(111,88,123,0.20);
  --fill-6: rgba(181,95,72,0.20);
}

/* ── Тушь и бумага · тёмная (явный выбор) ── */
html:root:where([data-theme="dark"]) {
  color-scheme: dark;
  --n-0: #262421;
  --n-2: #262421;
  --n-3: #332f2a;
  --n-5: #5b5349;
  --bg: #1c1b18;
  --bg-sunken: #141311;
  --surface: rgba(38,36,32,.88);
  --surface-2: rgba(50,47,42,.78);
  --sunken: rgba(0,0,0,.26);
  --text: #e8e5de;
  --text-strong: #fbf9f5;
  --text-soft: #a6a197;
  --text-faint: #857f75;
  --border: rgba(240,236,228,.14);
  --border-strong: rgba(240,236,228,.26);
  --rule: rgba(240,236,228,.18);
  --accent: #e8e5de;
  --accent-hover: #fbf9f5;
  --accent-fg: #1c1b18;
  --accent-soft: rgba(232,229,222,.13);
  --accent-line: rgba(232,229,222,.30);
  --pen: #d98b7e;
  --ok: #7fb894;
  --err: #e0918a;
  --warn: #cfb370;
  --info: #97b1d3;
  --ok-soft: rgba(127,184,148,.13);
  --err-soft: rgba(224,145,138,.13);
  --warn-soft: rgba(207,179,112,.13);
  --info-soft: rgba(151,177,211,.13);
  --ok-fg: #b6d9c3;
  --err-fg: #f0c2bd;
  --warn-fg: #ecd7ac;
  --info-fg: #c4d8e6;
  --fig-1: #e8e5de;
  --fig-2: #93b3cc;
  --fig-3: #7fb894;
  --fig-4: #d4b071;
  --fig-5: #bb9ac0;
  --fig-6: #be7a68;
  --ring: #e8e5de;
  --glass: rgba(46,44,40,.60);
  --glass-strong: rgba(38,36,32,.88);
  --glass-border: rgba(240,236,228,.11);
  --glass-top: rgba(240,236,228,.09);
  --glass-hover: rgba(54,51,46,.94);
  --hair: rgba(251,249,245,.10);
  --sh: rgba(0,0,0,.40);
  --solid: #262421;
  --bloom: rgba(240,236,228,.06);
  --shadow-raised: none;
  --shadow-float: 0 10px 34px rgba(0,0,0,.45);
  --radius-sm: 10px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-xl: 22px;
  --radius-xs: 6px;
  --fill-1: rgba(232,229,222,0.22);
  --fill-2: rgba(147,179,204,0.22);
  --fill-3: rgba(127,184,148,0.22);
  --fill-4: rgba(212,176,113,0.22);
  --fill-5: rgba(187,154,192,0.22);
  --fill-6: rgba(190,122,104,0.22);
}

/* ── Графит · светлая ── */
html:root:where([data-tone="graphite"]) {
  color-scheme: light;
  --n-0: #fcfdfe;
  --n-2: #e6e9eb;
  --n-3: #d8dcdf;
  --n-5: #aab2b7;
  --bg: #eff1f2;
  --bg-sunken: #e3e6e8;
  --surface: rgba(252,253,254,.90);
  --surface-2: rgba(252,253,254,.72);
  --sunken: rgba(30,35,38,.05);
  --text: #1e2326;
  --text-strong: #0f1315;
  --text-soft: #5e676c;
  --text-faint: #7c858a;
  --border: rgba(30,35,38,.15);
  --border-strong: rgba(30,35,38,.30);
  --rule: rgba(30,35,38,.22);
  --accent: #232a2e;
  --accent-hover: #0f1315;
  --accent-fg: #fbfcfd;
  --accent-soft: rgba(30,35,38,.07);
  --accent-line: rgba(30,35,38,.28);
  --pen: #3d6478;
  --ok: #35695a;
  --err: #96413c;
  --warn: #7d6524;
  --info: #3a517a;
  --ok-soft: rgba(53,105,90,.11);
  --err-soft: rgba(150,65,60,.10);
  --warn-soft: rgba(125,101,36,.12);
  --info-soft: rgba(58,81,122,.10);
  --ok-fg: #254b41;
  --err-fg: #6d2f2b;
  --warn-fg: #584718;
  --info-fg: #274050;
  --fig-1: #1e2326;
  --fig-2: #37556d;
  --fig-3: #35695a;
  --fig-4: #7d6524;
  --fig-5: #685e80;
  --fig-6: #6c3c4b;
  --ring: #232a2e;
  --glass: rgba(252,253,254,.66);
  --glass-strong: rgba(252,253,254,.90);
  --glass-border: rgba(255,255,255,.80);
  --glass-top: rgba(255,255,255,.92);
  --glass-hover: rgba(252,253,254,.99);
  --hair: rgba(30,35,38,.12);
  --sh: rgba(30,35,38,.13);
  --solid: #fcfdfe;
  --bloom: rgba(255,255,255,.88);
  --shadow-raised: 0 1px 2px rgba(30,35,38,.06);
  --shadow-float: 0 8px 32px rgba(30,35,38,.13);
  --radius-sm: 10px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-xl: 22px;
  --radius-xs: 6px;
  --fill-1: rgba(30,35,38,0.20);
  --fill-2: rgba(55,85,109,0.20);
  --fill-3: rgba(53,105,90,0.20);
  --fill-4: rgba(125,101,36,0.20);
  --fill-5: rgba(104,94,128,0.20);
  --fill-6: rgba(108,60,75,0.20);
}
html:root:where([data-tone="graphite"][data-theme="light"]) {
  color-scheme: light;
  --n-0: #fcfdfe;
  --n-2: #e6e9eb;
  --n-3: #d8dcdf;
  --n-5: #aab2b7;
  --bg: #eff1f2;
  --bg-sunken: #e3e6e8;
  --surface: rgba(252,253,254,.90);
  --surface-2: rgba(252,253,254,.72);
  --sunken: rgba(30,35,38,.05);
  --text: #1e2326;
  --text-strong: #0f1315;
  --text-soft: #5e676c;
  --text-faint: #7c858a;
  --border: rgba(30,35,38,.15);
  --border-strong: rgba(30,35,38,.30);
  --rule: rgba(30,35,38,.22);
  --accent: #232a2e;
  --accent-hover: #0f1315;
  --accent-fg: #fbfcfd;
  --accent-soft: rgba(30,35,38,.07);
  --accent-line: rgba(30,35,38,.28);
  --pen: #3d6478;
  --ok: #35695a;
  --err: #96413c;
  --warn: #7d6524;
  --info: #3a517a;
  --ok-soft: rgba(53,105,90,.11);
  --err-soft: rgba(150,65,60,.10);
  --warn-soft: rgba(125,101,36,.12);
  --info-soft: rgba(58,81,122,.10);
  --ok-fg: #254b41;
  --err-fg: #6d2f2b;
  --warn-fg: #584718;
  --info-fg: #274050;
  --fig-1: #1e2326;
  --fig-2: #37556d;
  --fig-3: #35695a;
  --fig-4: #7d6524;
  --fig-5: #685e80;
  --fig-6: #6c3c4b;
  --ring: #232a2e;
  --glass: rgba(252,253,254,.66);
  --glass-strong: rgba(252,253,254,.90);
  --glass-border: rgba(255,255,255,.80);
  --glass-top: rgba(255,255,255,.92);
  --glass-hover: rgba(252,253,254,.99);
  --hair: rgba(30,35,38,.12);
  --sh: rgba(30,35,38,.13);
  --solid: #fcfdfe;
  --bloom: rgba(255,255,255,.88);
  --shadow-raised: 0 1px 2px rgba(30,35,38,.06);
  --shadow-float: 0 8px 32px rgba(30,35,38,.13);
  --radius-sm: 10px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-xl: 22px;
  --radius-xs: 6px;
  --fill-1: rgba(30,35,38,0.20);
  --fill-2: rgba(55,85,109,0.20);
  --fill-3: rgba(53,105,90,0.20);
  --fill-4: rgba(125,101,36,0.20);
  --fill-5: rgba(104,94,128,0.20);
  --fill-6: rgba(108,60,75,0.20);
}

/* ── Графит · тёмная (явный выбор) ── */
html:root:where([data-tone="graphite"][data-theme="dark"]) {
  color-scheme: dark;
  --n-0: #22262a;
  --n-2: #22262a;
  --n-3: #2d3237;
  --n-5: #59636a;
  --bg: #16191b;
  --bg-sunken: #101314;
  --surface: rgba(34,38,42,.88);
  --surface-2: rgba(46,51,56,.78);
  --sunken: rgba(0,0,0,.26);
  --text: #e4e9eb;
  --text-strong: #f8fbfc;
  --text-soft: #9aa4aa;
  --text-faint: #7d868c;
  --border: rgba(228,233,235,.14);
  --border-strong: rgba(228,233,235,.26);
  --rule: rgba(228,233,235,.18);
  --accent: #e4e9eb;
  --accent-hover: #f8fbfc;
  --accent-fg: #16191b;
  --accent-soft: rgba(228,233,235,.13);
  --accent-line: rgba(228,233,235,.30);
  --pen: #8fb3c4;
  --ok: #6fae98;
  --err: #e0918a;
  --warn: #ceb67c;
  --info: #97b3d6;
  --ok-soft: rgba(111,174,152,.13);
  --err-soft: rgba(224,145,138,.13);
  --warn-soft: rgba(206,182,124,.13);
  --info-soft: rgba(151,179,214,.13);
  --ok-fg: #a9d3c4;
  --err-fg: #f0c2bd;
  --warn-fg: #e6d3ae;
  --info-fg: #c1d7e0;
  --fig-1: #e4e9eb;
  --fig-2: #8bb3ca;
  --fig-3: #6fae98;
  --fig-4: #cfb079;
  --fig-5: #b69abf;
  --fig-6: #e87044;
  --ring: #e4e9eb;
  --glass: rgba(42,47,51,.60);
  --glass-strong: rgba(34,38,42,.88);
  --glass-border: rgba(228,233,235,.11);
  --glass-top: rgba(228,233,235,.09);
  --glass-hover: rgba(48,54,58,.94);
  --hair: rgba(248,251,252,.10);
  --sh: rgba(0,0,0,.40);
  --solid: #22262a;
  --bloom: rgba(228,233,235,.06);
  --shadow-raised: none;
  --shadow-float: 0 10px 34px rgba(0,0,0,.45);
  --radius-sm: 10px;
  --radius-md: 12px;
  --radius-lg: 18px;
  --radius-xl: 22px;
  --radius-xs: 6px;
  --fill-1: rgba(228,233,235,0.22);
  --fill-2: rgba(139,179,202,0.22);
  --fill-3: rgba(111,174,152,0.22);
  --fill-4: rgba(207,176,121,0.22);
  --fill-5: rgba(182,154,191,0.22);
  --fill-6: rgba(232,112,68,0.22);
}

/* ── Газета · светлая ── */
html:root:where([data-tone="news"]) {
  color-scheme: light;
  --n-0: #ffffff;
  --n-2: #efece7;
  --n-3: #e2ded7;
  --n-5: #b6b1a9;
  --bg: #faf8f4;
  --bg-sunken: #efece7;
  --surface: rgba(255,255,255,.94);
  --surface-2: rgba(255,255,255,.80);
  --sunken: rgba(19,19,19,.04);
  --text: #1b1b1a;
  --text-strong: #131313;
  --text-soft: #5e5c58;
  --text-faint: #807d78;
  --border: rgba(19,19,19,.18);
  --border-strong: rgba(19,19,19,.38);
  --rule: rgba(19,19,19,.28);
  --accent: #131313;
  --accent-hover: #000000;
  --accent-fg: #faf8f4;
  --accent-soft: rgba(19,19,19,.06);
  --accent-line: rgba(19,19,19,.32);
  --pen: #b3261e;
  --ok: #2f6b45;
  --err: #b3261e;
  --warn: #7a5a10;
  --info: #2b4f7d;
  --ok-soft: rgba(47,107,69,.10);
  --err-soft: rgba(179,38,30,.09);
  --warn-soft: rgba(122,90,16,.11);
  --info-soft: rgba(43,79,125,.09);
  --ok-fg: #1f4b30;
  --err-fg: #801a14;
  --warn-fg: #543e0b;
  --info-fg: #1e3958;
  --fig-1: #131313;
  --fig-2: #2b4f7d;
  --fig-3: #2f6b45;
  --fig-4: #7a5a10;
  --fig-5: #60486a;
  --fig-6: #9e335f;
  --ring: #131313;
  --glass: rgba(255,255,255,.74);
  --glass-strong: rgba(255,255,255,.94);
  --glass-border: rgba(19,19,19,.10);
  --glass-top: rgba(255,255,255,.90);
  --glass-hover: #ffffff;
  --hair: rgba(19,19,19,.14);
  --sh: rgba(19,19,19,.10);
  --solid: #ffffff;
  --bloom: rgba(255,255,255,.75);
  --shadow-raised: 0 1px 2px rgba(19,19,19,.05);
  --shadow-float: 0 6px 24px rgba(19,19,19,.10);
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 10px;
  --radius-xl: 12px;
  --radius-xs: 4px;
  --fill-1: rgba(19,19,19,0.20);
  --fill-2: rgba(43,79,125,0.20);
  --fill-3: rgba(47,107,69,0.20);
  --fill-4: rgba(122,90,16,0.20);
  --fill-5: rgba(96,72,106,0.20);
  --fill-6: rgba(158,51,95,0.20);
}
html:root:where([data-tone="news"][data-theme="light"]) {
  color-scheme: light;
  --n-0: #ffffff;
  --n-2: #efece7;
  --n-3: #e2ded7;
  --n-5: #b6b1a9;
  --bg: #faf8f4;
  --bg-sunken: #efece7;
  --surface: rgba(255,255,255,.94);
  --surface-2: rgba(255,255,255,.80);
  --sunken: rgba(19,19,19,.04);
  --text: #1b1b1a;
  --text-strong: #131313;
  --text-soft: #5e5c58;
  --text-faint: #807d78;
  --border: rgba(19,19,19,.18);
  --border-strong: rgba(19,19,19,.38);
  --rule: rgba(19,19,19,.28);
  --accent: #131313;
  --accent-hover: #000000;
  --accent-fg: #faf8f4;
  --accent-soft: rgba(19,19,19,.06);
  --accent-line: rgba(19,19,19,.32);
  --pen: #b3261e;
  --ok: #2f6b45;
  --err: #b3261e;
  --warn: #7a5a10;
  --info: #2b4f7d;
  --ok-soft: rgba(47,107,69,.10);
  --err-soft: rgba(179,38,30,.09);
  --warn-soft: rgba(122,90,16,.11);
  --info-soft: rgba(43,79,125,.09);
  --ok-fg: #1f4b30;
  --err-fg: #801a14;
  --warn-fg: #543e0b;
  --info-fg: #1e3958;
  --fig-1: #131313;
  --fig-2: #2b4f7d;
  --fig-3: #2f6b45;
  --fig-4: #7a5a10;
  --fig-5: #60486a;
  --fig-6: #9e335f;
  --ring: #131313;
  --glass: rgba(255,255,255,.74);
  --glass-strong: rgba(255,255,255,.94);
  --glass-border: rgba(19,19,19,.10);
  --glass-top: rgba(255,255,255,.90);
  --glass-hover: #ffffff;
  --hair: rgba(19,19,19,.14);
  --sh: rgba(19,19,19,.10);
  --solid: #ffffff;
  --bloom: rgba(255,255,255,.75);
  --shadow-raised: 0 1px 2px rgba(19,19,19,.05);
  --shadow-float: 0 6px 24px rgba(19,19,19,.10);
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 10px;
  --radius-xl: 12px;
  --radius-xs: 4px;
  --fill-1: rgba(19,19,19,0.20);
  --fill-2: rgba(43,79,125,0.20);
  --fill-3: rgba(47,107,69,0.20);
  --fill-4: rgba(122,90,16,0.20);
  --fill-5: rgba(96,72,106,0.20);
  --fill-6: rgba(158,51,95,0.20);
}

/* ── Газета · тёмная (явный выбор) ── */
html:root:where([data-tone="news"][data-theme="dark"]) {
  color-scheme: dark;
  --n-0: #242321;
  --n-2: #242321;
  --n-3: #31302d;
  --n-5: #5c5a55;
  --bg: #191817;
  --bg-sunken: #111110;
  --surface: rgba(36,35,33,.90);
  --surface-2: rgba(48,46,43,.80);
  --sunken: rgba(0,0,0,.28);
  --text: #f2f0ec;
  --text-strong: #ffffff;
  --text-soft: #a3a09a;
  --text-faint: #827f79;
  --border: rgba(242,240,236,.16);
  --border-strong: rgba(242,240,236,.34);
  --rule: rgba(242,240,236,.22);
  --accent: #f2f0ec;
  --accent-hover: #ffffff;
  --accent-fg: #191817;
  --accent-soft: rgba(242,240,236,.12);
  --accent-line: rgba(242,240,236,.32);
  --pen: #e0645a;
  --ok: #6fae8b;
  --err: #e88a80;
  --warn: #e8b45f;
  --info: #8fb0d4;
  --ok-soft: rgba(111,174,139,.13);
  --err-soft: rgba(232,138,128,.13);
  --warn-soft: rgba(232,180,95,.13);
  --info-soft: rgba(143,176,212,.13);
  --ok-fg: #a8d4bd;
  --err-fg: #f4bfb8;
  --warn-fg: #f0d7a6;
  --info-fg: #c2d6e8;
  --fig-1: #f2f0ec;
  --fig-2: #8fb0d4;
  --fig-3: #6fae8b;
  --fig-4: #e8b45f;
  --fig-5: #bb9ab9;
  --fig-6: #e36157;
  --ring: #f2f0ec;
  --glass: rgba(44,42,40,.62);
  --glass-strong: rgba(36,35,33,.90);
  --glass-border: rgba(242,240,236,.12);
  --glass-top: rgba(242,240,236,.08);
  --glass-hover: rgba(52,50,47,.95);
  --hair: rgba(255,255,255,.10);
  --sh: rgba(0,0,0,.42);
  --solid: #242321;
  --bloom: rgba(242,240,236,.05);
  --shadow-raised: none;
  --shadow-float: 0 8px 28px rgba(0,0,0,.45);
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 10px;
  --radius-xl: 12px;
  --radius-xs: 4px;
  --fill-1: rgba(242,240,236,0.22);
  --fill-2: rgba(143,176,212,0.22);
  --fill-3: rgba(111,174,139,0.22);
  --fill-4: rgba(232,180,95,0.22);
  --fill-5: rgba(187,154,185,0.22);
  --fill-6: rgba(227,97,87,0.22);
}

/* Пользователь не выбирал тему — идём за системой. */
@media (prefers-color-scheme: dark) {
  html:root:where(:not([data-theme="light"])) {
    color-scheme: dark;
    --n-0: #262421;
    --n-2: #262421;
    --n-3: #332f2a;
    --n-5: #5b5349;
    --bg: #1c1b18;
    --bg-sunken: #141311;
    --surface: rgba(38,36,32,.88);
    --surface-2: rgba(50,47,42,.78);
    --sunken: rgba(0,0,0,.26);
    --text: #e8e5de;
    --text-strong: #fbf9f5;
    --text-soft: #a6a197;
    --text-faint: #857f75;
    --border: rgba(240,236,228,.14);
    --border-strong: rgba(240,236,228,.26);
    --rule: rgba(240,236,228,.18);
    --accent: #e8e5de;
    --accent-hover: #fbf9f5;
    --accent-fg: #1c1b18;
    --accent-soft: rgba(232,229,222,.13);
    --accent-line: rgba(232,229,222,.30);
    --pen: #d98b7e;
    --ok: #7fb894;
    --err: #e0918a;
    --warn: #cfb370;
    --info: #97b1d3;
    --ok-soft: rgba(127,184,148,.13);
    --err-soft: rgba(224,145,138,.13);
    --warn-soft: rgba(207,179,112,.13);
    --info-soft: rgba(151,177,211,.13);
    --ok-fg: #b6d9c3;
    --err-fg: #f0c2bd;
    --warn-fg: #ecd7ac;
    --info-fg: #c4d8e6;
    --fig-1: #e8e5de;
    --fig-2: #93b3cc;
    --fig-3: #7fb894;
    --fig-4: #d4b071;
    --fig-5: #bb9ac0;
    --fig-6: #be7a68;
    --ring: #e8e5de;
    --glass: rgba(46,44,40,.60);
    --glass-strong: rgba(38,36,32,.88);
    --glass-border: rgba(240,236,228,.11);
    --glass-top: rgba(240,236,228,.09);
    --glass-hover: rgba(54,51,46,.94);
    --hair: rgba(251,249,245,.10);
    --sh: rgba(0,0,0,.40);
    --solid: #262421;
    --bloom: rgba(240,236,228,.06);
    --shadow-raised: none;
    --shadow-float: 0 10px 34px rgba(0,0,0,.45);
    --radius-sm: 10px;
    --radius-md: 12px;
    --radius-lg: 18px;
    --radius-xl: 22px;
    --radius-xs: 6px;
    --fill-1: rgba(232,229,222,0.22);
    --fill-2: rgba(147,179,204,0.22);
    --fill-3: rgba(127,184,148,0.22);
    --fill-4: rgba(212,176,113,0.22);
    --fill-5: rgba(187,154,192,0.22);
    --fill-6: rgba(190,122,104,0.22);
  }
  html:root:where([data-tone="graphite"]:not([data-theme="light"])) {
    color-scheme: dark;
    --n-0: #22262a;
    --n-2: #22262a;
    --n-3: #2d3237;
    --n-5: #59636a;
    --bg: #16191b;
    --bg-sunken: #101314;
    --surface: rgba(34,38,42,.88);
    --surface-2: rgba(46,51,56,.78);
    --sunken: rgba(0,0,0,.26);
    --text: #e4e9eb;
    --text-strong: #f8fbfc;
    --text-soft: #9aa4aa;
    --text-faint: #7d868c;
    --border: rgba(228,233,235,.14);
    --border-strong: rgba(228,233,235,.26);
    --rule: rgba(228,233,235,.18);
    --accent: #e4e9eb;
    --accent-hover: #f8fbfc;
    --accent-fg: #16191b;
    --accent-soft: rgba(228,233,235,.13);
    --accent-line: rgba(228,233,235,.30);
    --pen: #8fb3c4;
    --ok: #6fae98;
    --err: #e0918a;
    --warn: #ceb67c;
    --info: #97b3d6;
    --ok-soft: rgba(111,174,152,.13);
    --err-soft: rgba(224,145,138,.13);
    --warn-soft: rgba(206,182,124,.13);
    --info-soft: rgba(151,179,214,.13);
    --ok-fg: #a9d3c4;
    --err-fg: #f0c2bd;
    --warn-fg: #e6d3ae;
    --info-fg: #c1d7e0;
    --fig-1: #e4e9eb;
    --fig-2: #8bb3ca;
    --fig-3: #6fae98;
    --fig-4: #cfb079;
    --fig-5: #b69abf;
    --fig-6: #e87044;
    --ring: #e4e9eb;
    --glass: rgba(42,47,51,.60);
    --glass-strong: rgba(34,38,42,.88);
    --glass-border: rgba(228,233,235,.11);
    --glass-top: rgba(228,233,235,.09);
    --glass-hover: rgba(48,54,58,.94);
    --hair: rgba(248,251,252,.10);
    --sh: rgba(0,0,0,.40);
    --solid: #22262a;
    --bloom: rgba(228,233,235,.06);
    --shadow-raised: none;
    --shadow-float: 0 10px 34px rgba(0,0,0,.45);
    --radius-sm: 10px;
    --radius-md: 12px;
    --radius-lg: 18px;
    --radius-xl: 22px;
    --radius-xs: 6px;
    --fill-1: rgba(228,233,235,0.22);
    --fill-2: rgba(139,179,202,0.22);
    --fill-3: rgba(111,174,152,0.22);
    --fill-4: rgba(207,176,121,0.22);
    --fill-5: rgba(182,154,191,0.22);
    --fill-6: rgba(232,112,68,0.22);
  }
  html:root:where([data-tone="news"]:not([data-theme="light"])) {
    color-scheme: dark;
    --n-0: #242321;
    --n-2: #242321;
    --n-3: #31302d;
    --n-5: #5c5a55;
    --bg: #191817;
    --bg-sunken: #111110;
    --surface: rgba(36,35,33,.90);
    --surface-2: rgba(48,46,43,.80);
    --sunken: rgba(0,0,0,.28);
    --text: #f2f0ec;
    --text-strong: #ffffff;
    --text-soft: #a3a09a;
    --text-faint: #827f79;
    --border: rgba(242,240,236,.16);
    --border-strong: rgba(242,240,236,.34);
    --rule: rgba(242,240,236,.22);
    --accent: #f2f0ec;
    --accent-hover: #ffffff;
    --accent-fg: #191817;
    --accent-soft: rgba(242,240,236,.12);
    --accent-line: rgba(242,240,236,.32);
    --pen: #e0645a;
    --ok: #6fae8b;
    --err: #e88a80;
    --warn: #e8b45f;
    --info: #8fb0d4;
    --ok-soft: rgba(111,174,139,.13);
    --err-soft: rgba(232,138,128,.13);
    --warn-soft: rgba(232,180,95,.13);
    --info-soft: rgba(143,176,212,.13);
    --ok-fg: #a8d4bd;
    --err-fg: #f4bfb8;
    --warn-fg: #f0d7a6;
    --info-fg: #c2d6e8;
    --fig-1: #f2f0ec;
    --fig-2: #8fb0d4;
    --fig-3: #6fae8b;
    --fig-4: #e8b45f;
    --fig-5: #bb9ab9;
    --fig-6: #e36157;
    --ring: #f2f0ec;
    --glass: rgba(44,42,40,.62);
    --glass-strong: rgba(36,35,33,.90);
    --glass-border: rgba(242,240,236,.12);
    --glass-top: rgba(242,240,236,.08);
    --glass-hover: rgba(52,50,47,.95);
    --hair: rgba(255,255,255,.10);
    --sh: rgba(0,0,0,.42);
    --solid: #242321;
    --bloom: rgba(242,240,236,.05);
    --shadow-raised: none;
    --shadow-float: 0 8px 28px rgba(0,0,0,.45);
    --radius-sm: 6px;
    --radius-md: 8px;
    --radius-lg: 10px;
    --radius-xl: 12px;
    --radius-xs: 4px;
    --fill-1: rgba(242,240,236,0.22);
    --fill-2: rgba(143,176,212,0.22);
    --fill-3: rgba(111,174,139,0.22);
    --fill-4: rgba(232,180,95,0.22);
    --fill-5: rgba(187,154,185,0.22);
    --fill-6: rgba(227,97,87,0.22);
  }
}

/* ─── 2/4 МОСТ СТАРЫХ ИМЁН ───────────────────────────────── */
/* ============================================================================
   СУПРЕМУМ · sup-compat.css — мост от старых имён к новым токенам
   ============================================================================
   Требует sup-tokens.css. Подключается СРАЗУ ПОСЛЕ него и ПЕРЕД
   theory.css / practice.css / exam.css / app-shell.css / supremum.css.

   ЗАЧЕМ. На сайте 512 страниц теории, 56 страниц практики и 4 экзаменационных.
   В их разметке и во встроенных стилях зашиты старые имена переменных:
   --glass-bg, --glass-border, --muted, --c-blue, --shadow-md и ещё 64 штуки.
   Переписать их означало бы править 572 файла. Этот файл переводит все старые
   имена на новые токены, поэтому страницы получают новое оформление
   БЕЗ ЕДИНОЙ ПРАВКИ РАЗМЕТКИ.

   Файл переходный. Порядок вывода из работы описан в плане (фаза 8): по мере
   того как раздел переводится на новые имена, соответствующие строки отсюда
   удаляются. Пустой файл = миграция закончена.

   ВАЖНО ПРО ПОРЯДОК. У страниц теории встроенный <style> идёт ПОСЛЕ ссылки
   на CSS и перебивает его. Если на странице остался свой --accent или свой
   фон body, он победит этот мост. Такие остатки ищет
   tools\design_system\audit_inline.py — он же считает, сколько их осталось.
   ============================================================================ */

/* СПЕЦИФИЧНОСТЬ. Блок намеренно html:root, а не голый :root. Слой токенов
   (sup-tokens-paper.css) выровнен на (0,1,1) во всех шести сочетаниях
   тональность × тема — иначе базовая тональность проигрывала палитре v1
   в supremum.css (html[data-theme="dark"], тоже (0,1,1)). Мост обязан
   оставаться ПОСЛЕДНИМ словом для старых имён: три из них — --glass,
   --glass-strong, --glass-border — он намеренно переводит на плотные
   поверхности. При голом :root (0,1,0) мост проигрывал бы токенам, и
   полупрозрачность вернулась бы под учебный текст. */
html:root {
  /* ── Подложки и поверхности ──────────────────────────────────────────────
     Раньше это было «стекло»: полупрозрачный белый плюс backdrop-filter.
     Стало непрозрачной поверхностью. Полупрозрачность убрана сознательно:
     на ней невозможно гарантировать контраст текста — он зависит от того,
     что оказалось под ней, и WCAG-замер на стекле нарушался легче всего. */
  --bg-base: var(--bg);
  --glass: var(--surface);
  --glass-bg: var(--surface);
  --glass-strong: var(--surface);
  --card: var(--surface);
  --tile-bg: var(--n-2);
  --viz-bg: var(--surface);
  --open-pt-fill: var(--surface);
  --ui-surface: var(--surface);
  --ui-surface-strong: var(--n-2);
  --ui-track: var(--n-3);

  /* ── Границы и линии ── */
  --glass-border: var(--border);
  --ui-border: var(--border);

  /* ── ЧЕРТЁЖНАЯ ПУНКТИРНАЯ ЛИНИЯ ≠ ВОЛОСЯНАЯ ЛИНЕЙКА ──────────────────────
     Здесь стояло --c-dashed: var(--rule), и это была подмена роли. --rule —
     разделительная линейка вёрстки, ей положено быть еле заметной; --c-dashed
     на 289 страницах рисует вспомогательное построение — и не только штрихом:
     264 страницы берут её из JS и красят ею ПОДПИСЬ («a», «D»), то есть текст.
     Замер на «сумме углов треугольника»: подпись «a» к холсту 2.00 → 1.57
     в светлой теме и 2.25 → 1.67 в тёмной при пороге 4.5. Единственное место,
     где перевод сделал читаемость хуже, чем была.

     Ведём на --text-soft — роль «текст второго плана», у которой генератор
     палитры проверяет ≥ 4.5 на каждой подложке во всех шести сочетаниях
     тональность × тема (tools\design_system\palette.py, check_paper, п. 1).
     Стало 5.63…6.67. Она нейтральная — вспомогательная линия остаётся
     подчинённой, ровно как исходный #94a3b8, а не превращается во второй
     чернильный объект.

     Почему не --fig-1: контраст был бы 15.8, но fig-1 — это те же чернила,
     что и --c-base, которым нарисована сама фигура (расстояние 0.000), то есть
     построение слилось бы с построенным на КАЖДОЙ странице. Остаток: до
     --fig-5 нейтраль отходит на 0.051…0.077 при пороге различимости линий
     0.075 — принято, потому что пунктир отличается от сплошной штрихом,
     а не только цветом. */
  --c-dashed: var(--text-soft);

  /* ── Текст ── */
  --muted: var(--text-soft);
  --gray: var(--text-soft);
  --c-base: var(--text);
  --text-lh: var(--lh-md);

  /* ── Градиентные пятна на body: ВЫКЛЮЧЕНЫ ────────────────────────────────
     Три радиальных градиента pink/blue/purple лежали на body и в theory.css,
     и в supremum.css. Это самый узнаваемый штамп ИИ-вёрстки, и на странице
     теории они были почти вдвое насыщеннее, чем в хабе (.55 против .28) —
     то есть половины сайта расходились даже по фону. Правила, рисующие
     их, никуда не делись, но красить им теперь нечем: прозрачное.
     Взамен фактура из мира предмета — клетка, см. sup-base.css. */
  --glow-1: transparent;
  --glow-2: transparent;
  --glow-3: transparent;
  --glow1: transparent;
  --glow2: transparent;
  --glow3: transparent;

  /* ── Тени ────────────────────────────────────────────────────────────────
     Было три ступени в theory.css плюс одна безымянная --shadow в остальных
     файлах. Сведены к возвышению из токенов: сначала граница, тень только
     у того, что действительно висит над страницей. */
  --shadow: var(--shadow-raised);
  --shadow-sm: var(--shadow-raised);
  --shadow-md: var(--shadow-float);
  --shadow-lg: var(--shadow-modal);
  --glass-shadow: var(--shadow-float);
  --ui-shadow: var(--shadow-raised);
  --ui-shadow-lift: var(--shadow-float);

  /* ── Радиусы ─────────────────────────────────────────────────────────────
     Было 14 разных значений в practice.css, 12 в exam.css, 11 в app-shell.css
     и 22px литералом в supremum.css. Стало шесть ступеней из токенов. */
  --ui-radius: var(--radius-md);
  --ui-radius-card: var(--radius-lg);
  --radius-md-sb: var(--radius-md);
  --radius-lg-sb: var(--radius-lg);

  /* ── Движение и фокус ── */
  --ui-ease: var(--ease-out);
  --ease-out-sb: var(--ease-out);
  --ui-ring: var(--ring);
  --shadow-sm-sb: var(--shadow-raised);

  /* ── Раскладка ── */
  --hdr: 64px;
  --sbw: 260px;      /* ширина сайдбара оболочки */

  /* ── Смысловые цвета под старыми именами ─────────────────────────────────
     Здесь ничего не «переименовано ради красоты»: старые имена --green/--red/
     --amber/--blue означали ровно роли «верно / ошибка / внимание / факт»
     и теперь ведут на смысловые токены. */
  --green: var(--ok);
  --red: var(--err);
  --amber: var(--warn);
  --blue: var(--info);

  --def-bg: var(--ok-soft);
  --def-border: var(--ok);
  --exmp-bg: var(--info-soft);
  --exmp-border: var(--info);
  --memo-bg: var(--warn-soft);
  --memo-border: var(--warn);
  --mist-bg: var(--err-soft);
  --mist-border: var(--err);
  --mistake-bg: var(--err-soft);
  --mistake-border: var(--err);

  /* Мягкие подложки-«дымки» из старой системы */
  --f-green: var(--ok-soft);
  --f-red: var(--err-soft);
  --f-orange: var(--warn-soft);
  --f-blue: var(--info-soft);
  --f-purple: var(--accent-soft);

  /* ── ПАЛИТРА ЧЕРТЕЖА ─────────────────────────────────────────────────────
     Это НЕ смысловые цвета. Ими раскрашивают разные объекты одного чертежа:
     прямая a синяя, прямая b фиолетовая, дуга угла оранжевая. Задача здесь
     другая — чтобы линии отличались ДРУГ ОТ ДРУГА, а не от «ошибки».

     Сами значения --fig-* живут в sup-tokens.css: это НОВЫЕ имена, часть
     системы, а не совместимость. Здесь только перевод старых имён на них.

     Осторожно: на старых страницах дуги углов рисуются --c-red, и на чертеже
     это читается как «здесь ошибка», хотя значит «вот этот угол». Поэтому
     --c-red ведёт НЕ на смысловой красный, а на винный --fig-6: тёплый
     и заметный, но не кричащий «неверно».

     Свести --c-red и --c-orange в один цвет нельзя: 180 страниц из 516
     используют их ОДНОВРЕМЕННО, то есть различают этим два разных угла
     на одном чертеже. Проверено перебором по всем материалам.

     ── ПОЧЕМУ ИМЯ ВСЁ-ТАКИ ВАЖНО (пересмотрено 21.08.2026) ────────────────
     Сначала имена считались неважными: раз цвета лишь отличают объекты друг
     от друга, любая перестановка годится. Перебор по материалам это опроверг:
     99 страниц теории из 511 называют цвет СЛОВАМИ в тексте — «перетаскивай
     оранжевые точки», «потяни за вершину синего треугольника», «площади
     синего и зелёного квадратов». Всего 247 упоминаний. При прежней
     перестановке синий уезжал в чернила, а оранжевый в лиловый, и текст
     переставал описывать то, что ученик видит.

     Различимость от перестановки не страдает: все шесть --fig-* уже
     проверены генератором палитры на различимость, в том числе при
     дальтонизме. Поэтому имена сопоставлены по смыслу.

     Исключение — --c-teal: седьмого цвета в палитре нет, а бирюзовый
     встречается на 3 страницах и ни разу не назван словом в тексте
     (проверено). Он и уступает чернила, оставаясь отличимым от синего
     и зелёного, с которыми делит чертёж. */
  --c-blue: var(--fig-2);     /* синевато-серый  */
  --c-green: var(--fig-3);    /* зелёный         */
  --c-orange: var(--fig-4);   /* охра, тёплый    */
  --c-purple: var(--fig-5);   /* лиловый         */
  --c-red: var(--fig-6);      /* терракота       */
  --c-teal: var(--fig-1);     /* чернила, см. исключение выше */

  /* Акцент главы в практике: по умолчанию чернила. Разделы, где у главы свой
     оттенок, переопределяют эту переменную у себя — она для этого и заведена. */
  --chapter-accent: var(--accent);
}

/* ── ЧТО МОСТ ПОЧИНИТЬ НЕ МОЖЕТ ────────────────────────────────────────────
   Переменную он подменяет, а захардкоженное значение — нет. По переписи
   (tools\design_audit\token_census.py) литеральных цветов вне блоков токенов:
   practice.css — 57, exam.css — 40, theory.css — 24, app-shell.css — 19,
   supremum.css — 16. Их правят в самих файлах, раздел за разделом, и это
   и есть содержание задач 8.5–8.8 плана.

   Три места видны сразу и правятся первыми, потому что бьют по всем страницам:
   · body { font-family: 'Segoe UI', system-ui } в theory.css и
     -apple-system в supremum.css — шрифт мимо системы, страница выглядит
     по-разному на Windows и на Mac;
   · backdrop-filter: blur(...) на шапке, карточках и коробке интерактива;
   · transform: translateY(-2px) на наведении у кнопок, карточек и разделов.
   ──────────────────────────────────────────────────────────────────────── */

/* ─── 3/4 КАНОН ПРАВИЛ (без блоков переменных) ───────────── */
/* theory.css — общие стили страниц теории СУПРЕМУМ.
 * Собран автоматически: tools\theory_css\theory_css.py build
 * Источник канона — canon.json (порог: правило не реже чем в 15% из 512 страниц).
 * Руками не правим значения вслепую: страницы могут нести inline-override,
 * который идёт после <link> и перебивает этот файл. */



* {
  box-sizing: border-box;
}
a, button {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
  font-family: inherit;
}
a:focus-visible, button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--bg-base),0 0 0 4px var(--ring);
  border-radius: var(--radius-sm);
}
body {
  font-family: 'Segoe UI',system-ui,sans-serif;
  color: var(--text);
  margin: 0;
  padding-top: var(--hdr);
  background: var(--bg-base);
  background-image: radial-gradient(ellipse 50% 60% at 15% 10%,var(--glow-1),transparent),radial-gradient(ellipse 55% 65% at 85% 15%,var(--glow-2),transparent),radial-gradient(ellipse 60% 70% at 50% 95%,var(--glow-3),transparent);
  background-attachment: fixed;
  transition: background .3s,color .3s;
}
header.page-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--hdr);
  z-index: 100;
  background: var(--glass-strong);
  backdrop-filter: blur(18px);
  border-bottom: 1px solid var(--glass-border);
  color: var(--text);
  display: flex;
  align-items: center;
  padding: 0 24px;
  gap: 16px;
}
.theme-toggle {
  margin-left: auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: 1px solid var(--glass-border);
  background: var(--tile-bg);
  color: var(--text);
  font-size: 1.1rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform .15s;
}
.theme-toggle:hover {
  transform: scale(1.08);
}
.page-split {
  display: block;
}
.theory-col {
  width: 50%;
  box-sizing: border-box;
  padding: 28px 32px 80px;
  min-height: calc(100vh - var(--hdr));
}
.interactive-col {
  position: fixed;
  right: 0;
  top: var(--hdr);
  width: 50%;
  height: calc(100vh - var(--hdr));
  z-index: 50;
  overflow: hidden;
  padding: 16px 20px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
}
.interactive-col svg, .interactive-col canvas, .interactive-col .viz-block {
  margin: 0 auto;
}
@keyframes cardFadeUp {to { opacity: 1; transform: translateY(0); }}
.card {
  background: var(--glass-bg);
  backdrop-filter: blur(14px);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-sm);
  padding: 24px 26px;
  margin: 20px 0;
  color: var(--text);
  opacity: 0;
  transform: translateY(10px);
  animation: cardFadeUp .5s var(--ease-out) forwards;
}
.card h1 {
  font-size: 1.55rem;
  margin: 0 0 6px;
}
.card h2 {
  font-size: 1.3rem;
  margin: 0 0 12px;
  color: var(--text-soft);
  line-height: 1.3;
}
.card p, .card li, .definition, .highlight, .memo, .mistake {
  line-height: var(--text-lh);
}
.katex {
  font-size: 1.08em;
  vertical-align: middle;
}
.card p:has(> .katex-display:only-child) {
  text-align: center;
}
.katex-display {
  text-align: center;
  text-align-last: center;
}
.katex-display .katex {
  font-size: 1.25em;
}
.card p:has(> .katex:only-child), .card p:has(> .katex-display:only-child), .card li:has(> .katex:only-child), .definition:has(> .katex-display:only-child), .highlight:has(> .katex-display:only-child), .memo:has(> .katex-display:only-child), .mistake:has(> .katex-display:only-child) {
  text-align: center !important;
  text-align-last: center !important;
}
@keyframes fadeIn {from { opacity:0; } to { opacity:1; }}
@keyframes drawStroke {to { stroke-dashoffset: 0; }}
@keyframes fadeScaleIn {from { opacity:0; transform:scale(0.6); } to { opacity:1; transform:scale(1); }}
.card p:has(> .katex:only-child), .card p:has(> .katex-display:only-child), .card li:has(> .katex:only-child), .definition:has(> .katex:only-child), .definition:has(> .katex-display:only-child), .highlight:has(> .katex:only-child), .highlight:has(> .katex-display:only-child), .memo:has(> .katex:only-child), .memo:has(> .katex-display:only-child), .mistake:has(> .katex:only-child), .mistake:has(> .katex-display:only-child) {
  text-align: center !important;
  text-align-last: center !important;
}
.definition, .highlight, .memo, .mistake {
  gap: 12px;
  align-items: flex-start;
  border-radius: var(--radius-lg);
  padding: 14px 18px;
  margin: 20px 0;
  border: 1px solid transparent;
  color: var(--text);
  transition: background .3s,border-color .3s;
}
.definition {
  background: var(--def-bg);
  border-color: rgba(5,150,105,.25);
}
.highlight {
  background: var(--exmp-bg);
  border-color: rgba(37,99,235,.25);
}
.memo {
  background: var(--memo-bg);
  border-color: rgba(217,119,6,.25);
}
.mistake {
  background: var(--mist-bg);
  border-color: rgba(220,38,38,.25);
}
[data-theme="dark"] .mistake {
  background: rgba(248,113,113,.1);
  border-left-color: #f87171;
}
.definition .a-icon, .highlight .a-icon, .memo .a-icon, .mistake .a-icon {
  font-size: 1.05rem;
  line-height: 1.5;
  flex-shrink: 0;
}
.proof-step.step-visible {
  opacity: 1;
  transform: translateY(0);
}
.definition .a-body, .highlight .a-body, .memo .a-body, .mistake .a-body {
  font-size: .96rem;
  line-height: 1.55;
}
.proof-step {
  display: flex;
  gap: 14px;
  align-items: flex-start;
  padding: 12px 0;
  border-bottom: 1px solid var(--glass-border);
}
.btn {
  --h: 2.35rem;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 16px;
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: .88rem;
  cursor: pointer;
  border: 1px solid transparent;
  transition: background .18s,color .18s,border-color .18s,transform .12s var(--ease-out),box-shadow .18s;
}
.hl:hover {
  background: #fef3c7;
}
.definition .a-title, .highlight .a-title, .memo .a-title, .mistake .a-title {
  font-weight: 700;
  margin-bottom: 2px;
}
.btn:active {
  transform: scale(.96);
}
[data-theme="dark"] .hl:hover {
  background: rgba(251,191,36,.25);
}
.proof-step:last-child {
  border-bottom: none;
}
.btn:disabled {
  opacity: .55;
  cursor: default;
  transform: none !important;
}
.proof-step .num {
  background: var(--c-blue);
  color: white;
  border-radius: 50%;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  flex-shrink: 0;
  font-size: .9rem;
}
.hl {
  cursor: pointer;
  border-bottom: 1.5px dashed #d97706;
  transition: background .1s;
}
.btn-outline {
  background: var(--glass-strong);
  color: var(--text);
  border-color: var(--glass-border);
}
.btn-outline:hover:not(:disabled) {
  background: var(--tile-bg);
  border-color: var(--accent);
}
.proof-step .txt {
  font-size: .96rem;
  line-height: 1.55;
}
.viz-inner {
  padding: 22px 22px 8px;
}
.step-btn:hover {
  background: var(--glass-strong);
  border-color: var(--accent);
}
.btn-outline.active, .btn-outline.active:hover:not(:disabled) {
  background: var(--accent);
  color: white;
  border-color: var(--accent);
}
.viz-block {
  background: var(--viz-bg);
  backdrop-filter: blur(16px);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-md);
  overflow: hidden;
  margin: 24px 0;
  user-select: none;
  -webkit-user-select: none;
}
.tab-btn {
  background: var(--tile-bg);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-md);
  padding: 8px 14px;
  font-size: .84rem;
  font-weight: 600;
  cursor: pointer;
  color: var(--text);
  transition: all .15s var(--ease-out);
}
.tab-btn:hover {
  background: var(--glass-strong);
  border-color: var(--accent);
}
.viz-block canvas, .viz-block svg {
  touch-action: none;
  margin: 0 auto;
}
.btn-sm {
  --h: 1.95rem;
  padding: 0 12px;
  font-size: .8rem;
}
.tab-btn.active {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}
.viz-caption {
  padding: 10px 18px;
  font-size: 0.88rem;
  color: var(--gray);
  text-align: center;
  border-top: 1px solid var(--glass-border);
  background: var(--glass-bg);
}
.step-btn {
  --h: 2.1rem;
  align-items: center;
  justify-content: center;
  padding: 0 16px;
  border-radius: var(--radius-md);
  font-weight: 600;
  font-size: .85rem;
  cursor: pointer;
  border: 1px solid var(--glass-border);
  background: var(--accent);
  color: white;
  transition: background .2s ease,opacity .2s ease,transform .12s var(--ease-out);
}
.step-btn:disabled {
  opacity: .55;
  cursor: default;
}
.tab-row {
  gap: 8px;
  justify-content: center;
  padding: 10px 14px 0;
}
.formula-box {
  padding: 10px 18px;
  font-size: .92rem;
  text-align: center;
  border-top: 1px solid var(--glass-border);
  background: var(--glass-bg);
  color: var(--text);
  line-height: 1.6;
  min-height: 1.4em;
  transition: color .2s;
}
.formula-box b {
  color: var(--accent);
}
.step-btn:active {
  transform: scale(.96);
}
.status-badge {
  display: inline-block;
  margin-top: 6px;
  padding: 3px 12px;
  border-radius: 999px;
  font-size: 1rem;
  font-weight: 700;
}
.slider-row span.lbl {
  font-size: .85rem;
  color: var(--gray);
}
.slider-row {
  align-items: center;
  gap: 10px;
  padding: 10px 18px 0;
}
.slider-row span.val {
  font-size: .9rem;
  font-weight: 600;
  text-align: right;
  color: var(--text);
}
.viz-block svg circle, .viz-block svg line, .viz-block svg text, .viz-block svg rect, .viz-block svg polyline, .viz-block svg path {
  transition: stroke .2s ease,fill .2s ease,stroke-width .2s ease,opacity .2s ease;
}
.slider-row input[type=range] {
  accent-color: var(--accent);
}
.topic-nav {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: stretch;
  gap: 12px;
  margin-top: 48px;
}
.topic-nav-side {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-decoration: none;
  min-width: 0;
  overflow: hidden;
  width: fit-content;
  max-width: 100%;
  background: var(--glass-bg);
  backdrop-filter: blur(10px);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  padding: 12px 16px;
  box-shadow: var(--shadow-sm);
  transition: transform .15s var(--ease-out),background .15s,box-shadow .15s;
}
.draw-in-stroke {
  animation: drawStroke .7s ease forwards;
}
.draw-in-fill {
  transform-box: fill-box;
  transform-origin: center;
  animation: fadeScaleIn .4s ease forwards;
}
.topic-nav-side:hover {
  transform: translateY(-2px);
  background: var(--tile-bg);
  box-shadow: var(--shadow-md);
}
.topic-nav-side:empty {
  background: none;
  border: none;
  padding: 0;
  width: auto;
  box-shadow: none;
}
.draw-in-fade {
  animation: fadeIn .5s ease forwards;
}
.topic-nav-prev {
  text-align: left;
  justify-self: start;
}
.topic-nav-next {
  text-align: right;
  justify-self: end;
}
.viz-block svg circle {
  transition: fill .2s ease,opacity .2s ease;
}
.topic-nav-meta {
  font-size: .8rem;
  color: var(--gray);
  white-space: nowrap;
}
.topic-nav-title {
  font-size: clamp(.74rem,1.55vw,.98rem);
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
  display: block;
}
.topic-nav-all {
  justify-self: center;
  align-self: center;
  background: var(--accent);
  color: white;
  text-decoration: none;
  font-weight: 600;
  padding: 10px 22px;
  border-radius: var(--radius-md);
  font-size: .92rem;
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
  transition: transform .15s var(--ease-out),box-shadow .15s;
}
.topic-nav-all:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

@media (max-width: 720px) {
  .theory-col {
    width: 100%;
  }
}

@media (min-width: 1200px) {
  .card p, .card li, .definition, .highlight, .memo, .mistake {
    text-align: justify;
    text-align-last: left;
    hyphens: auto;
    -webkit-hyphens: auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .card {
    opacity: 1;
    transform: none;
  }
  .card, .viz-block svg *, .btn, .hl, .step-btn {
    animation: none !important;
    transition: none !important;
  }
  * {
    animation: none !important;
    transition: none !important;
  }
}

/* Дописывается в конец theory.css при сборке (theory_css.py build).
 * Сюда идут общие компоненты, которых нет ни на одной старой странице,
 * поэтому канон их вывести не может. */

/* Блок предпосылок на странице темы (роудмап, задача 5.5).
 * Ставится первым в theory-col: ученик видит, что нужно знать, до того как начнёт читать. */
.prereq {
  margin: 0 0 18px;
  padding: 12px 16px;
  border-left: 3px solid var(--c-purple, #7c3aed);
  border-radius: var(--radius-md, .75rem);
  /* фиксированная фиолетовая дымка, а не переменная темы: часть страниц
   * переопределяет --f-purple только в одном режиме, и в другом получалось
   * светлое на светлом. Полупрозрачный акцент читается на любом фоне */
  background: rgba(124,58,237,.12);
  color: inherit;
  font-size: .95rem;
  line-height: 1.6;
}
.prereq-title {
  display: block;
  margin-bottom: 4px;
  font-weight: 600;
  opacity: .85;
}
.prereq a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--c-purple, #7c3aed);
  text-underline-offset: 3px;
}
.prereq a + a::before {
  content: " · ";
  text-decoration: none;
  opacity: .5;
}
.prereq a:hover {
  text-decoration-thickness: 2px;
}

/* Куда эта тема ведёт на экзамене. Пара к .prereq: тот же приём с полосой
 * слева, но в конце теории и другого цвета — предпосылки смотрят назад,
 * этот блок вперёд. Цвет фиксированный, а не переменная темы: часть страниц
 * переопределяет акценты только в одном режиме (те же грабли, что у .prereq). */
.exam-link {
  margin: 22px 0 0;
  padding: 12px 16px;
  border-left: 3px solid var(--c-green, #059669);
  border-radius: var(--radius-md, .75rem);
  background: rgba(5, 150, 105, .12);
  color: inherit;
  font-size: .95rem;
  line-height: 1.6;
}
/* Тема может работать сразу на два экзамена — тогда строк две, и вторая
 * отбивается сверху, а не липнет к номерам первой. */
.exam-link-row + .exam-link-row {
  margin-top: 10px;
}
.exam-link-title {
  display: block;
  margin-bottom: 4px;
  font-weight: 600;
  opacity: .85;
}
.exam-link a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: var(--c-green, #059669);
  text-underline-offset: 3px;
  white-space: nowrap;
}
.exam-link a + a::before {
  content: " · ";
  text-decoration: none;
  opacity: .5;
}
.exam-link a:hover {
  text-decoration-thickness: 2px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   МОБИЛЬНЫЕ ДЕФЕКТЫ СТРАНИЦ ТЕОРИИ. Замер 09.09.2026,
   tools\design_audit\audit_paper.py --gruppa teoriya --shirina 360:
   17 страниц из 48 распирали окно (до 591px при ширине 360), тач-цели 38×38
   при норме 44, кегль до 11.5px, навигация «пред./все/след.» стояла в три
   колонки и обрезала названия тем до ~63px.

   Правки лежат здесь, а не в поправках моста bridge_theory.py, сознательно:
   отсюда они попадают в канон theory.css и чинят все 511 страниц разом,
   а не только 48 переведённых на «тушь и бумагу». Дефект один — и лечится
   один раз.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── 1. ШИРОКОЕ СОДЕРЖИМОЕ ПРОКРУЧИВАЕТСЯ ВНУТРИ СЕБЯ ─────────────────────
   Кто именно распирал страницу (замер: самый глубокий предок, который шире
   своего клиента и не прокручивается) — .expr-display 59 случаев,
   .proof-step 85, врезки 17, абзац 7. Внутри везде одно и то же: формула
   KaTeX, которой страницы задают white-space: nowrap, и она честно не
   переносится. Обрезать её нельзя — ученик потеряет хвост разложения
   («96 = 2·2·2·2·2·3» на теме 19 обрывалось на четвёртой двойке); уменьшать
   кегль до нечитаемого — тоже. Значит блок с формулой получает собственную
   полосу: содержимое прокручиваемого контейнера имеет право быть шире экрана.

   Приём взят у .scroll-x из sup-base.css (на страницах теории того файла
   нет — подключён только theory.css, поэтому набор свойств повторён здесь,
   а не унаследован).

   overflow-y: hidden стоит не для красоты: при overflow-x: auto браузер всё
   равно вычисляет y как auto, и без явного значения появляется лишняя
   вертикальная полоса в один пиксель. Всплывающих подсказок, которые могло
   бы обрезать, в этих блоках нет — только текст, формула и таблица. */
.expr-display, .katex-display,
.definition, .highlight, .memo, .mistake,
.proof-step > :not(.num) {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  /* Шаг доказательства — flex-строка «кружок с номером + содержимое».
     Без min-width: 0 flex-элемент отказывается сжиматься уже содержимого,
     и прокрутка внутри него не включается вовсе. Содержимое бывает и <div>,
     и <span class="txt"> — во flex-контейнере оба блочные, поэтому селектор
     по «всё, кроме номера», а не по имени тега. */
  min-width: 0;
}

/* ЗАПАС ПО ВЫСОТЕ — ПЛАТА ЗА ГОРИЗОНТАЛЬНУЮ ПРОКРУТКУ, И ОН ОБЯЗАТЕЛЕН.
   overflow-x: auto заставляет браузер посчитать и вертикальную ось: visible
   там превращается в auto, и мы ставим hidden, чтобы не появлялась вторая,
   ненужная полоса. Но у KaTeX содержимое строки выше самой строки — дробь и
   корень вылезают за строчный бокс. Замер на 128 страницах: у .katex-display
   срезалось до 9px НАСТОЯЩИХ чернил (не распорок — считались только текстовые
   узлы и элементы с рамкой, то есть дробная черта и знак корня), у шага
   доказательства и абзаца до 5px.

   Поэтому блокам без собственных вертикальных полей дают внутренний запас, а
   внешние поля уменьшают ровно на столько же: место на странице то же, а
   формула цела. У врезок (.definition и соседи) и у .expr-display свои 10–14px
   padding уже есть — замер показал у них ноль потерь, их не трогаем.

   .formula-box в список прокручиваемых сознательно НЕ входит: распирающим
   страницу он ни разу не оказался, а padding в 10px не покрывает его 2px
   потерь — выгоды нет, а риск испортить полосу под интерактивом на 343
   страницах есть. */
.katex-display {
  padding-block: 10px;
  margin-block: calc(1em - 10px);   /* у KaTeX здесь margin: 1em 0 */
}
.proof-step > :not(.num) {
  /* Во flex поля не схлопываются, поэтому отрицательное поле гасит padding
     ровно: строка шага стоит там же, где стояла, и номер не разъезжается
     с текстом. В обычном потоке такой приём был бы опаснее. */
  padding-block: 8px;
  margin-block: -8px;
}

/* Абзац теории со «строчной» формулой. Прокрутка здесь — последнее средство
   и потому только на телефоне и только там, где формула действительно есть:
   на 360px строка вида «a · 10 = …» шире колонки, а перенести её нельзя
   (страницы задают .katex { white-space: nowrap }, и это правильно — формулу
   не рвут посередине). Обычные абзацы остаются обычным потоком: чем меньше
   блоков превращено в порт прокрутки, тем меньше поводов что-нибудь срезать. */
@media (max-width: 720px) {
  .theory-col p:has(.katex) {
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
    padding-block: 8px;
    margin-block: calc(1em - 8px);   /* поле абзаца по умолчанию 1em */
  }
  /* Пункт списка с формулой — то же самое, но поля у <li> нулевые, поэтому
     запас гасится отрицательным полем. Маркер списка рисуется вне основного
     бокса и прокруткой не срезается — проверено замером положения маркера. */
  .theory-col li:has(.katex) {
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
    padding-block: 8px;
    margin-block: -8px;
  }
}

/* Таблицы (43 страницы) стоят в разметке голым <table> без обёртки, поэтому
   прокрутить их можно только сделав блоком самим. На широком экране приём
   не нужен и меняет ширину таблицы, поэтому он включается только на телефоне.
   Честная починка — обёртка .scroll-x в разметке; она требует правки страниц. */
@media (max-width: 720px) {
  .theory-col table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: thin;
  }
}

/* ── 2. КОЛЬЦО ФОКУСА ────────────────────────────────────────────────────
   В каноне кольцо нарисовано двумя тенями при outline: none. Тень едет по
   transition кнопки (.18s) и обрезается любым предком с overflow — а мы его
   только что добавили пункту 1. Поэтому рядом ставится настоящий outline:
   он появляется в тот же кадр и наружу от рамки, как на главной и во всём
   остальном слое (sup-base.css). Тени не трогаем: по ним проверку ведёт
   probe.js, и убрать их значит получить ложную тревогу вместо починки. */
a:focus-visible, button:focus-visible,
summary:focus-visible, [tabindex]:focus-visible {
  outline: var(--ring-width, 2px) solid var(--ring, var(--accent));
  outline-offset: var(--ring-offset, 2px);
}

/* ── 3. КЕГЛЬ ПОДПИСЕЙ ───────────────────────────────────────────────────
   Базовый кегль этих страниц — 16px, и поднимать его целиком нельзя
   (решение записано в поправках моста: под 16px подобраны холсты 511 тем).
   Но подписи навигации жили на .72….92rem, то есть 11.5…14.7px — это ниже
   порога читаемости 15.5px. Поднимаем именно их: они вне интерактива,
   размеры холстов от этого не меняются. */
.topic-nav-all {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;       /* было 43 — не дотягивало до нормы касания ровно на пиксель */
  font-size: 1rem;
}
.topic-nav-title { font-size: 1.02rem; }   /* было clamp(.74rem,1.55vw,.98rem) → 11.8px */
.topic-nav-meta  { font-size: .98rem; }    /* было .8rem → 12.8px */
.proof-step .num { font-size: .98rem; }    /* было .9rem → 14.4px */
.viz-caption     { font-size: .98rem; }    /* было .88rem → 14.1px */
.prereq, .exam-link { font-size: .98rem; } /* было .95rem → 15.2px */

/* Рубрикатор «Предмет · класс · глава» набран .72rem в АТРИБУТЕ style —
   на всех 511 страницах одинаково. Атрибут перебивается только знаком
   важности; правка разметки 511 файлов ради одного объявления дороже. */
header.page-header > div > div:first-child {
  font-size: .98rem !important;
}

/* ── 4. ТАЧ-ЦЕЛИ И ОРГАНЫ УПРАВЛЕНИЯ НА ТЕЛЕФОНЕ ─────────────────────────
   Норма касания 44px. На столе кнопки специально остаются прежними: их
   размер держит раскладку интерактива на 511 страницах, а мышью попасть
   в 38px не проблема. Поэтому весь блок — под указателем пальца.

   --h правит и высоту, и ширину: круглые .btn-icon берут ширину из той же
   переменной (page-inline .btn-icon { width: var(--h) }), так что одного
   объявления хватает на обе стороны кнопки. */
@media (max-width: 720px), (pointer: coarse) {
  .btn, .btn-sm, .step-btn { --h: 2.75rem; min-height: var(--h); font-size: 1rem; }
  .tab-btn { min-height: 44px; font-size: .98rem; }
  /* Кружок смены темы: одной ширины мало — он стоит во flex-шапке и
     сжимался до 41px. Норму держит min-width плюс запрет на сжатие. */
  .theme-toggle {
    width: 44px; height: 44px; min-width: 44px;
    flex-shrink: 0; font-size: 1.25rem;
  }
  /* .mini-btn (40 страниц) объявлена во встроенном <style>, а он идёт после
     ссылки на общий файл — равная специфичность проиграет. Отсюда предок. */
  .interactive-col .mini-btn, .viz-block .mini-btn {
    min-height: 44px;
    min-width: 44px;   /* короткая подпись («56») давала кнопку в 41px по ширине */
    font-size: .95rem;
  }
  .interactive-col .stmt-desc, .viz-block .stmt-desc { font-size: .98rem; }

  /* Ещё три кнопки виджетов, объявленные во встроенном <style> страниц:
     .zoom-btn (74 страницы, кружки увеличения у чертежа), .scene-tab (20,
     переключение сцен) и .pill-btn (5). Все давали 26×26…39×21 при норме 44,
     то есть промах пальцем на каждом втором нажатии у младших. Предок в
     селекторе нужен по той же причине, что и у .mini-btn: встроенный <style>
     идёт после ссылки на общий файл и при равной специфичности побеждает. */
  .interactive-col .zoom-btn, .viz-block .zoom-btn,
  .interactive-col .scene-tab, .viz-block .scene-tab,
  .interactive-col .pill-btn, .viz-block .pill-btn,
  .interactive-col .num-chip, .viz-block .num-chip,
  .interactive-col .op-btn, .viz-block .op-btn,
  .interactive-col .mult-btn, .viz-block .mult-btn,
  .interactive-col .scale-btn, .viz-block .scale-btn {
    min-height: 44px;
    min-width: 44px;
    font-size: .95rem;
  }

  /* Ползунок (151 страница) отдавал 16px по высоте — это высота дорожки,
     а не зона пальца. Бегунок при этом крупнее, но попасть по нему мимо
     дорожки нельзя. Высоту задаём самому полю, вид дорожки не трогаем. */
  .interactive-col input[type="range"], .viz-block input[type="range"] {
    height: 44px;
  }

  /* Ссылки на номера экзамена (301 страница) — строка 30×21 в ряду, где
     между соседними ссылками около 4px. Поэтому зона растягивается
     псевдоэлементом ВВЕРХ и ВНИЗ, а вбок почти нет: расширить по горизонтали
     значило бы накрыть соседний номер, и нажатие по краю «№ 7» открывало бы
     «№ 8» — промах, который хуже мелкой цели. */
  .exam-link a {
    position: relative;
  }
  .exam-link a::after {
    content: "";
    position: absolute;
    inset: -12px -2px;
  }
}

/* ── 5. НАВИГАЦИЯ «ПРЕД. / ВСЕ ТЕМЫ / СЛЕД.» НА ТЕЛЕФОНЕ ────────────────
   Три колонки на 360px давали каждой боковой ссылке ~63px ширины, и
   название темы схлопывалось в «Пример…». Складываем в столбец: порядок в
   разметке уже правильный (пред. → все темы → след.), а название переводим
   в несколько строк — многоточие здесь скрывало ровно то, ради чего ссылка
   и нужна. */
@media (max-width: 720px) {
  .topic-nav { grid-template-columns: minmax(0, 1fr); }
  .topic-nav-side { width: auto; max-width: 100%; }
  .topic-nav-title {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
  }
  .topic-nav-meta { white-space: normal; }
}

/* ── 6. ЗАЛИТОЕ ВЫБРАННОЕ СОСТОЯНИЕ ПЕРЕКЛЮЧАТЕЛЕЙ (аудит 27.09.2026) ────
   Страницы тем пишут выбранную кнопку литералом: заливка var(--accent) или
   фигурный цвет var(--c-blue/--c-purple/…), подпись color: white / #fff.
   В тёмной теме эти заливки светлые — белое по светлому, контраст 1,26
   («Пример 1», «Объём», «Менелай», «Прямая»; 2,2 у «25» и «+»). Подпись
   берётся парой к заливке: --accent-fg светлеет днём и темнеет ночью вместе
   с чернилами и фигурными цветами. Префикс html поднимает вес над правилом
   страницы (оно подключено позже и того же веса). В список вошли ТОЛЬКО
   классы, у которых на всех страницах выбранное состояние — сплошная
   заливка (перепись правил по 511 страницам): .num-chip.active на шести
   темах 5 класса — обводка без заливки, поэтому его здесь нет.
   :not(.tab-btn) — кнопка, которая одновременно вкладка («Многоугольники»:
   .tab-btn.n-btn), выбранной рисуется светлой плашкой слоя, а не заливкой. */
html :is(.step-btn, .mini-btn, .btn-outline, .scene-tab, .scene-tab-btn, .preset-btn,
  .sub-tab-btn, .op-tab, .op-btn, .n-btn, .mag-btn, .value-tab, .target-btn, .sub-btn,
  .scale-btn, .mult-btn, .ladder-chip, .ladder-base-btn, .lab-prop-btn, .lab-base-btn,
  .eq-chip, .ctor-chip).active:not(.tab-btn),
html .step-btn.tab.active,   /* 0,3,1 — страница пишет .step-btn.tab.active (0,3,0) */
html .pipe-node.active .pipe-circle {
  color: var(--accent-fg);
}

/* ── 7. ЗНАЧОК СОСТОЯНИЯ ЧЕРТЕЖА (.status-badge) ─────────────────────────
   Две беды из аудита 27.09.2026.
   1) white-space: nowrap из общего значка: «✗ пока не выполнен — a и b
      не параллельны» на 390px уходил за край панели и обрезался.
   2) Подпись красилась фигурным цветом (--c-red, --c-orange) по бледной
      заливке того же тона — контраст 2,9–3,3 (нужно 4,5). Страницы задают
      цвет по-разному (.ok/.bad/.no/.neutral/.warn — у разных тем разный
      тон), поэтому цвет не переназначается по классу: заливка букв
      смешивается с собственным цветом значка и цветом основного текста.
      currentColor внутри -webkit-text-fill-color — это цвет самого значка,
      так что оттенок (зелёный, терракотовый, охра) сохраняется, а буквы
      темнеют днём и светлеют ночью до читаемого. */
html .status-badge {
  white-space: normal;
  max-width: 100%;
  -webkit-text-fill-color: color-mix(in oklab, currentColor 55%, var(--text-strong));
}

/* ── 8. СТРОКА ПОЛЗУНКА НА ТЕЛЕФОНЕ ──────────────────────────────────────
   Страницы фиксируют ширину подписи (.lbl { width: 172px; flex-shrink: 0 }),
   а у ползунка минимальная ширина по умолчанию — его собственная (≈130px).
   На 390px подпись + ползунок + число не помещались: число «8» и край
   ползунка уходили за панель. Подпись получает право переноситься,
   ползунок — сжиматься до разумного минимума. */
@media (max-width: 720px) {
  html .slider-row > .lbl { flex-shrink: 1; min-width: 0; }
  html .slider-row > input[type="range"] { min-width: 5rem; }
}

/* ── 9. НОМЕР В СПИСКЕ ПРАКТИКИ ТЕМЫ ─────────────────────────────────────
   Цифра --text-soft на подложке --glass-border давала 4,0–4,3 (нужно 4,5). */
html .pr-item:not(.is-main) .pr-num { color: var(--text); }

/* ── 10. ЦЕЛЬ НАЖАТИЯ КНОПОК ПАНЕЛИ НА ТЕЛЕФОНЕ ──────────────────────────
   Шаги, мини-кнопки и вкладки сцен стояли высотой 39–40px при норме 44
   (замер аудита 27.09.2026 на 390px). Добавляется высота, а не поля:
   подпись остаётся по центру, панель вырастает на 4–5px на ряд. */
@media (max-width: 720px) {
  html :is(.step-btn, .mini-btn, .scene-tab, .scene-tab-btn, .preset-btn) { min-height: var(--tap, 44px); }
}

/* ─── 4/4 ПОПРАВКИ К КАНОНУ ──────────────────────────────── */

/* БАЗОВЫЙ КЕГЛЬ НА ЭТИХ СТРАНИЦАХ ОСТАЁТСЯ 16px — ЭТО РЕШЕНИЕ, НЕ УПУЩЕНИЕ.
   Стандарт проекта — 20px, и он действует для новых страниц (sup-base.css
   ставит html { font-size: var(--fs-root) }). Здесь мы его сознательно НЕ
   применяем: 512 готовых страниц несут встроенный интерактив, размеры холстов
   и viewBox в котором подбирались при 16px. Подъём базы на 25% переложит
   каждую страницу и может поломать ровно то, что сейчас работает.

   Поэтому подъём кегля — отдельный, проверяемый шаг после сверки отпечатком
   (задача 8.6 плана), а не побочный эффект смены палитры. Включается строкой
       html { font-size: var(--fs-root); }
   и проверяется на разделе целиком, а не на одной странице. */

/* Шрифт. В каноне стоит body { font-family: 'Segoe UI', system-ui } —
   системный стек, из-за которого страница выглядит по-разному на Windows
   и на Mac. Заменяем на свои файлы. */
body {
  font-family: var(--font-body);
  font-size: var(--fs-md);
  line-height: var(--lh-md);
  font-synthesis-weight: none;
  background-image: none;   /* три радиальных пятна больше не рисуются вовсе */
  background-color: var(--bg);
  padding-top: 0;
}
h1, h2, h3, h4, .card h1, .card h2 {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  color: var(--text-strong);
}

/* Размытие. Когда «стекло» у шапки, карточек и коробки интерактива разом,
   иерархии нет: всё выглядит одинаково важным. */
header.page-header, .card, .viz-block, .topic-nav-side, .viz-caption, .formula-box {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
header.page-header {
  position: sticky;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  height: auto;
  min-height: var(--hdr);
}

/* Подскок на наведении. Один и тот же жест был навешан на кнопки, карточки,
   разделы и переключатель темы — движение перестаёт что-либо значить.
   Взамен меняется граница и заливка. */
.topic-nav-side:hover, .topic-nav-all:hover, .theme-toggle:hover {
  transform: none;
  border-color: var(--accent);
}
.btn:active, .step-btn:active { transform: none; }

/* Текст на залитых чернилами элементах: днём бумажный, ночью чернильный.
   В каноне здесь стоит литеральный white — в тёмной теме это белое по белому. */
.tab-btn.active, .btn-outline.active, .btn-outline.active:hover:not(:disabled),
.step-btn, .topic-nav-all, .proof-step .num {
  color: var(--accent-fg);
  background: var(--accent);
  border-color: var(--accent);
}

/* Связь текст ↔ чертёж: в каноне подчёркивание и подсветка заданы литералами
   #d97706 и #fef3c7, мимо темы. */
.hl { border-bottom-color: var(--accent-line); }
.hl:hover { background: var(--accent-soft); border-bottom-color: var(--accent); }

/* Блок предпосылок: в каноне у него намеренно фиксированная фиолетовая дымка
   rgba(124,58,237,.12) — её ставили, потому что переменная темы работала
   только в одном режиме. С единым слоем токенов костыль не нужен. */
.prereq {
  background: var(--accent-soft);
  border-left-color: var(--accent-line);
  border-radius: var(--radius-md);
}
.prereq a { text-decoration-color: var(--accent); }

/* Врезки: скругление и толщина левой границы из токенов, знак обязателен */
.definition, .highlight, .memo, .mistake {
  border-radius: var(--radius-md);
  border-left-width: 3px;
}

/* Формула не должна выпирать за край на узком экране: в каноне у .katex
   стоит font-size 1.08em, а выключная формула не умеет прокручиваться. */
.katex { font-size: 1em; }
.katex-display { overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain; }
.katex-display > .katex { font-size: 1.15em; }

/* Раскладка страницы темы: в каноне .theory-col задан width: 50% при
   .page-split { display: block } и .interactive-col { position: fixed }.
   Переводим на грид — фиксированная колонка перекрывала текст на узких
   экранах и мешала печати. */
.page-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
.theory-col { width: auto; min-width: 0; }
/* Липкая колонка отсчитывается ОТ НИЗА ШАПКИ, а не от верха окна.
   top: 0 здесь уже стоял и стоил дорого: шапка тоже липкая (см. выше,
   position: sticky) и лежит выше по z-index (100 против 50 в каноне), так что
   верхние 64px интерактива уезжали под неё. Замер на теореме Пифагора при
   прокрутке 1200px: ряд вкладок оказывался на высоте 23…69px, и
   elementFromPoint по центру каждой из трёх кнопок возвращал header.page-header
   — клик по вкладкам физически не проходил. Ряд управления стоит первым
   элементом интерактива на 52 страницах из 511.

   Формула та же, что в sup-base.css: сначала фактическая высота шапки
   (--hdr-real, её пишет paper.js там, где он подключён), иначе объявленная
   --hdr, иначе 64px. Объявленной недостаточно: у шапки здесь height: auto
   и min-height, при другом кегле она выше 64px. */
.interactive-col {
  position: sticky;
  top: var(--hdr-real, var(--hdr, 64px));
  height: calc(100vh - var(--hdr-real, var(--hdr, 64px)));
  width: auto;
  min-width: 0;
}
@media (max-width: 900px) {
  .page-split { grid-template-columns: minmax(0, 1fr); }
  .interactive-col { position: static; height: auto; min-height: 60vh; }
}

@media print {
  .interactive-col, .topic-nav, header.page-header { display: none !important; }
  .page-split { grid-template-columns: 1fr; }
}

/* Заливки фигур --f-*. Мост вёл их на врезочные *-soft (7…13 %), и это
   подмена роли: soft — подложка под текст врезки, заливка — цвет области
   чертежа, которую ученик должен ОТЛИЧИТЬ от соседней. Замер 08.09.2026 на
   Пифагоре, простых числах и многогранниках (tools\design_audit, OKLab):
   среднее расстояние между пятью заливками поверх сцены — 0,012 (светлая)
   и 0,020 (тёмная) при 0,053 / 0,052 в каноне theory.css; три квадрата
   Пифагора на тёмной теме читались как один серый. Читаемость подписи
   внутри заливки при этом была в норме (--text поверх заливки ≥ 8,7) —
   страдала различимость областей, а не текст.

   Заливка = свой контурный цвет с прозрачностью: пара «контур/заливка» на
   180 страницах остаётся одного оттенка, а тональность (бумага/графит/газета)
   подхватывается сама. Значения --fill-N (литеральные rgba, 20 % днём и 22 %
   ночью) даёт palette.py: 94 страницы из 106 в разделе 5–6 кладут заливку
   в canvas.fillStyle через getComputedStyle, поэтому color-mix здесь нельзя.
   Соответствие то же, что у моста для --c-*: blue→2, green→3, orange→4,
   purple→5, red→6. Специфичность html:root (0,1,1) — как у моста, и блок
   стоит позже него. */
html:root {
  --f-blue:   var(--fill-2);
  --f-green:  var(--fill-3);
  --f-orange: var(--fill-4);
  --f-purple: var(--fill-5);
  --f-red:    var(--fill-6);
}

/* ─── 2/4 sup-base.css ──────────────────────────────────────── */
/* ============================================================================
   СУПРЕМУМ · sup-base.css — слой 2: база и компоненты
   ============================================================================
   Требует sup-tokens.css (слой 1) — подключается ПЕРЕД этим файлом.
   Ни одного значения мимо токенов: цвет, кегль, отступ, радиус, длительность
   берутся только через var(). Это проверяет линтер сборки.

   ИМЕНА КЛАССОВ ЗДЕСЬ НЕ НОВЫЕ. Они повторяют то, что уже стоит в разметке
   512 страниц теории (.card, .definition, .highlight, .memo, .mistake,
   .viz-block, .tab-btn, .btn, .topic-nav, .theory-col …). Благодаря этому
   готовая страница получает новое оформление БЕЗ ПРАВКИ РАЗМЕТКИ — достаточно
   заменить подключаемый CSS. Переименовывать классы здесь нельзя: это тут же
   отрежет 512 страниц от системы.

   ПРИНЦИП: цвет несёт смысл, а не украшает.
   Каркас нейтральный, крупных окрашенных плоскостей нет. Возвышение даёт
   граница, а не тень и не размытие. Насыщенный цвет только там, где значит.
   ============================================================================ */

/* ── СБРОС ─────────────────────────────────────────────────────────────── */

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

html {
  font-size: var(--fs-root);
  -webkit-text-size-adjust: 100%;
  /* Страница не скроллится по горизонтали никогда. На боевом pricing.html
     контент разъезжался до 778px при вьюпорте 360 — вот от чего страховка. */
  overflow-x: clip;
}

body {
  min-height: 100vh;
  overflow-x: clip;
  font-family: var(--font-body);
  font-size: var(--fs-md);
  line-height: var(--lh-md);
  font-weight: var(--fw-normal);
  font-synthesis-weight: var(--font-synthesis);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg, video, canvas { display: block; max-width: 100%; }
input, button, textarea, select { font: inherit; color: inherit; }
button { background: none; border: none; cursor: pointer; }
table { border-collapse: collapse; }
ul[class], ol[class] { list-style: none; padding: 0; }
p, h1, h2, h3, h4, li, dt, dd { overflow-wrap: break-word; }

/* ── ФАКТУРА: миллиметровка вместо градиентных пятен ───────────────────────
   Раньше на body лежали три радиальных градиента (и в theory.css, и в
   supremum.css) — самый узнаваемый штамп ИИ-вёрстки. Здесь фактура из мира
   предмета: клетка, затухающая к низу по mask-image, чтобы не спорить
   с чертежом. Рисуется на ::before, а не на background самого body: так она
   не прокручивается вместе с длинным текстом и не мешает печати. */

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(to right, var(--grid-line) 0 1px, transparent 1px 100%),
    linear-gradient(to bottom, var(--grid-line) 0 1px, transparent 1px 100%);
  background-size: var(--grid-step) var(--grid-step);
  -webkit-mask-image: linear-gradient(to bottom, rgb(0 0 0 / .55), transparent 62%);
  mask-image: linear-gradient(to bottom, rgb(0 0 0 / .55), transparent 62%);
}

/* Раздел, которому нужна мелкая клетка — как в тетради по математике */
.texture-fine::before { background-size: var(--grid-step-fine) var(--grid-step-fine); }
.texture-none::before { display: none; }

/* ── ТИПОГРАФИКА ───────────────────────────────────────────────────────── */

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--fw-display);
  font-optical-sizing: auto;
  color: var(--text-strong);
  text-wrap: balance;
}
h1 { font-size: var(--fs-3xl); line-height: var(--lh-3xl); letter-spacing: -.012em; }
h2 { font-size: var(--fs-2xl); line-height: var(--lh-2xl); letter-spacing: -.008em; }
h3 { font-size: var(--fs-xl);  line-height: var(--lh-xl); }
h4 { font-size: var(--fs-lg);  line-height: var(--lh-lg); }

p { text-wrap: pretty; }

/* Ссылка узнаётся подчёркиванием, а не только цветом: при дальтонизме цвет
   ссылки от текста не отличить. Отступ подчёркивания — чтобы не резало буквы. */
a {
  color: var(--text-strong);
  text-decoration-line: underline;
  text-decoration-color: var(--accent-line);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 3px;
  transition: text-decoration-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
a:hover { text-decoration-color: var(--accent); }

/* Числа, координаты, показания приборов. tabular-nums обязательно: без него
   цифры в живом показании дёргаются по ширине при каждом изменении. */
.mono, .num, .readout-v, code, kbd, samp {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "zero" 1;
}

/* Рубрикатор над заголовком: «ГЛАВА 3 · § 22». Номер параграфа живёт здесь,
   а не внутри названия темы — внутри он ломал разбивку строк заголовка. */
.rubric {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-size: var(--fs-xs);
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--text-soft);
}

.lead { font-size: var(--fs-lg); line-height: var(--lh-lg); color: var(--text-soft); }
.meta { font-size: var(--fs-xs); line-height: var(--lh-xs); color: var(--text-soft); }
.measure { max-width: var(--measure); }

/* Подпись бренда: подчёркивание ручкой учителя. Только витрина — на учебных
   страницах красный обязан значить ошибку и ничего больше. */
.pen-mark {
  position: relative;
  white-space: nowrap;
}
.pen-mark::after {
  content: "";
  position: absolute;
  left: -.06em; right: -.06em; bottom: -.16em;
  height: .13em;
  border-radius: var(--radius-pill);
  background: var(--pen);
  /* Неровный край: линия нарисована рукой, а не векторным редактором */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 3%, #000 97%, transparent);
  mask-image: linear-gradient(to right, transparent, #000 3%, #000 97%, transparent);
  transform: rotate(-.35deg);
}

/* ── ДОСТУПНОСТЬ ───────────────────────────────────────────────────────── */

a, button, summary, [role="button"] {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: var(--ring-width) solid var(--ring);
  outline-offset: var(--ring-offset);
  border-radius: var(--radius-xs);
}

.skip-link {
  position: absolute;
  left: var(--sp-4); top: -100px;
  z-index: calc(var(--z-header) + 10);
  padding: var(--sp-3) var(--sp-6);
  background: var(--accent); color: var(--accent-fg);
  font-weight: var(--fw-semi); text-decoration: none;
  border-radius: 0 0 var(--radius-md) var(--radius-md);
  transition: top var(--dur-fast) var(--ease-out);
}
.skip-link:focus { top: 0; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Любая цель — не меньше 44×44, даже если значок 16px */
.tap, .icon-btn {
  min-width: var(--tap); min-height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}

/* ── ЗНАЧКИ ────────────────────────────────────────────────────────────── */

.icon {
  width: 1em; height: 1em;
  flex-shrink: 0; pointer-events: none;
  stroke: currentColor; fill: none; stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round;
}
.icon-lg { width: 1.4em; height: 1.4em; }

/* ── ПРИМИТИВЫ РАСКЛАДКИ ───────────────────────────────────────────────── */

.wrap {
  width: 100%; max-width: var(--page-max);
  margin-inline: auto; padding-inline: var(--page-pad);
}
.stack { display: flex; flex-direction: column; }
.stack > * + * { margin-top: var(--flow, var(--sp-4)); }
.cluster {
  display: flex; flex-wrap: wrap;
  gap: var(--gap, var(--sp-3)); align-items: center;
}
.grid-auto {
  display: grid; gap: var(--gap, var(--sp-4));
  grid-template-columns: repeat(auto-fit, minmax(min(var(--col, 260px), 100%), 1fr));
}
/* Грид-потомок по умолчанию не сжимается уже содержимого — корень переполнений */
.grid-auto > *, .wrap > * { min-width: 0; }

/* Широкое содержимое прокручивается внутри себя, а не ломает страницу */
.scroll-x {
  overflow-x: auto; overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

/* ── ШАПКА ─────────────────────────────────────────────────────────────── */

header.page-header, header.site, .sup-header {
  position: sticky; top: 0;
  z-index: var(--z-header);
  display: flex; align-items: center; gap: var(--sp-4);
  min-height: var(--hdr, 64px);
  padding: var(--sp-2) var(--page-pad);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  color: var(--text);
  /* Никакого backdrop-filter: когда «стекло» у всего, иерархии нет.
     Шапка непрозрачна — под ней текст, его не должно быть видно насквозь. */
}

.logo {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--tap);   /* это ссылка на главную — значит тач-цель */
  text-decoration: none; color: var(--text-strong);
}
.logo-mark {
  width: 40px; height: 40px; flex-shrink: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 3px;
  font-family: var(--font-display);
  font-weight: var(--fw-display); font-size: .8rem; line-height: 1;
  color: var(--accent-fg); background: var(--accent);
  border-radius: var(--radius-sm);
}
/* Черта под «sup» — та же ручка учителя, что в подписи бренда */
.logo-mark::after {
  content: ""; width: 22px; height: 2px;
  border-radius: var(--radius-pill); background: var(--pen);
}
.logo-name {
  display: block;
  font-family: var(--font-display); font-weight: var(--fw-display);
  font-size: var(--fs-lg); letter-spacing: .1em; line-height: 1.1;
}
.logo-sub {
  display: block;
  font-size: var(--fs-xs); color: var(--text-soft); letter-spacing: .02em;
}

nav.site { display: flex; gap: var(--sp-1); flex-wrap: wrap; }
nav.site a {
  display: inline-flex; align-items: center;
  min-height: var(--tap); padding: 0 var(--sp-3);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  color: var(--text-soft); text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
nav.site a:hover { background: var(--n-2); color: var(--text-strong); }
/* Текущий раздел: заливка чернилами. Не только цвет — ещё и вес, чтобы
   положение читалось при любом зрении. */
nav.site a[aria-current], nav.site a.active {
  background: var(--accent); color: var(--accent-fg);
  font-weight: var(--fw-semi);
}
.header-right { display: flex; align-items: center; gap: var(--sp-2); margin-left: auto; }

/* Ноутбучные ширины: между полным экраном и телефоном была дыра. Замер
   (tools\redesign — снимок шапки на 1440/1100/900) показывал на 1100px две
   строки навигации и шапку в 109px вместо 66px: пункты не помещались рядом
   с логотипом и подписью под ним, а компактный режим включался только
   с 700px. Двухстрочная навигация на ноутбуке — это сломанная шапка,
   а не адаптация.

   Освобождаем место в том порядке, в каком элементы нужны: сначала уходит
   пояснение под названием (оно для первого знакомства, а не для навигации),
   потом поджимаются поля пунктов. Сами пункты остаются видимыми.

   Пороги взяты с запасом, а не впритык к замеру: на грани страницы
   расходились между собой из-за десятка пикселей в правой группе кнопок,
   и «починено» держалось бы до первого нового пункта меню. */
@media (max-width: 1400px) {
  .logo-sub { display: none; }
  nav.site a { padding-inline: var(--sp-2); }
}
@media (max-width: 1180px) {
  header.page-header, header.site, .sup-header { gap: var(--sp-3); }
  nav.site { gap: 0; }
  nav.site a { font-size: var(--fs-xs); padding-inline: var(--sp-1); }
}

/* Планшет: пункты в один ряд с логотипом уже не помещаются. Две строки тут
   неизбежны, но они должны быть задуманными, а не рваным переносом, при
   котором на вторую строку сползает один случайный пункт. Поэтому меню
   целиком уходит на свою строку во всю ширину и прокручивается вбок. */
@media (max-width: 950px) {
  header.page-header, header.site, .sup-header {
    flex-wrap: wrap;   /* иначе последняя кнопка уезжает за край и обрезается */
  }
  .logo { flex: 1 1 auto; min-width: 0; }
  .header-right { flex-shrink: 0; }
  nav.site {
    order: 3; width: 100%; flex-wrap: nowrap;
    overflow-x: auto; padding-bottom: var(--sp-1);
    scrollbar-width: none;
  }
  nav.site::-webkit-scrollbar { display: none; }
  nav.site a { flex: 0 0 auto; }
}

/* Узкий экран: в шапке остаётся только логотип и действия. Хлебные крошки
   на 390px не помещаются и начинают наезжать друг на друга — их место
   в теле страницы, а не в сдавленной шапке. */
@media (max-width: 700px) {
  header.page-header, header.site, .sup-header {
    gap: var(--sp-2);
    padding-inline: var(--sp-4);
  }
  header.page-header .crumbs, header.site .crumbs { display: none; }
  .logo-name { font-size: var(--fs-md); }
}

/* Узкий экран: поля карточек и врезок поджимаются. Иначе на 390px от меры
   строки остаётся 20 знаков — по три слова в строке, читать неудобно. */
@media (max-width: 560px) {
  .card { padding: var(--sp-4); }
  .definition, .highlight, .memo, .mistake, .callout {
    padding: var(--sp-3) var(--sp-4);
    gap: var(--sp-2);
  }
  .theory-col { padding-inline: var(--sp-3); }
}

/* ── КНОПКИ ────────────────────────────────────────────────────────────── */
/* Наведение меняет заливку и границу, а не подбрасывает элемент вверх.
   translateY(-2px) на кнопках, карточках и разделах разом — один жест на всё,
   от него ушли сознательно. */

.btn {
  --btn-h: 2.4rem;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--sp-2);
  min-height: var(--btn-h); padding: 0 var(--sp-4);
  border: 1px solid transparent; border-radius: var(--radius-md);
  font-size: var(--fs-sm); font-weight: var(--fw-semi);
  text-decoration: none; cursor: pointer;
  /* Длинную надпись переносим, а не выпускаем за край экрана: на 390px
     кнопка «Записаться на бесплатное занятие» шире самого экрана. */
  max-width: 100%;
  white-space: normal;
  text-align: center;
  text-wrap: balance;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
/* Короткие кнопки в плотных рядах переносить не нужно */
.btn-sm, .tab-btn, .step-btn { white-space: nowrap; }
@media (max-width: 560px) {
  /* Главное действие на телефоне занимает всю ширину — так его не промахнуть */
  .btn-lg { width: 100%; }
}
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: default; }

/* Главное действие: чернила днём, мел ночью */
.btn-primary, .btn.primary, .btn-login, .topic-nav-all {
  background: var(--accent); color: var(--accent-fg);
  border-color: var(--accent);
}
.btn-primary:hover:not(:disabled), .btn.primary:hover:not(:disabled),
.btn-login:hover, .topic-nav-all:hover {
  background: var(--accent-hover); border-color: var(--accent-hover);
}

.btn-outline, .btn-ghost, .btn.secondary {
  background: var(--surface); color: var(--text-strong);
  border-color: var(--border-strong);
}
.btn-outline:hover:not(:disabled), .btn-ghost:hover, .btn.secondary:hover:not(:disabled) {
  background: var(--n-2); border-color: var(--accent);
}
/* Выбранное состояние переключателя — заливка, плюс вес шрифта */
.btn-outline.active, .btn-outline[aria-pressed="true"] {
  background: var(--accent); color: var(--accent-fg);
  border-color: var(--accent); font-weight: var(--fw-semi);
}

.btn-quiet {
  background: transparent; color: var(--text-soft); border-color: transparent;
}
.btn-quiet:hover:not(:disabled) { background: var(--n-2); color: var(--text-strong); }

/* Малая кнопка выглядит плотно, но зона нажатия всё равно не меньше 44×44.
   Расширяем её невидимым слоем, а не самой кнопкой: иначе в плотной шапке
   кнопки начали бы расталкивать друг друга. Промахи на телефоне —
   не мелочь, это правило C8. */
.btn-sm {
  --btn-h: 2rem;
  padding: 0 var(--sp-3);
  font-size: var(--fs-xs);
  position: relative;
}
.btn-sm::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: 50%; transform: translateY(-50%);
  height: var(--tap);
  min-width: var(--tap);
}
.btn-lg { --btn-h: 3rem; padding: 0 var(--sp-6); font-size: var(--fs-md); }

.theme-toggle, .theme-btn {
  width: var(--tap); height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text-soft); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.theme-toggle:hover, .theme-btn:hover { background: var(--n-2); color: var(--text-strong); }

/* ── КАРТОЧКА ──────────────────────────────────────────────────────────── */
/* Возвышение даёт граница. Тень — только у того, что действительно висит. */

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-raised);
  padding: var(--sp-6);
  color: var(--text);
}
.card > * + * { margin-top: var(--sp-4); }
.card h2 { font-size: var(--fs-2xl); }
.card h3 { font-size: var(--fs-xl); }
.card-float { box-shadow: var(--shadow-float); }

/* Карточка-ссылка: наведение подсвечивает границу, элемент не прыгает */
a.card, .card-link {
  display: block; text-decoration: none; color: inherit;
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
a.card:hover, .card-link:hover { border-color: var(--accent); background: var(--n-2); }

.section-title {
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-size: var(--fs-xs); letter-spacing: .1em; text-transform: uppercase;
  color: var(--text-soft);
}

/* ── ВРЕЗКИ: определение, пример, запомни, ошибка ──────────────────────────
   ГЛАВНОЕ ПРАВИЛО: у врезки ВСЕГДА есть знак, а не только цвет. При красно-
   зелёной слепоте «запомни» (янтарь) и «ошибка» (красный) неразличимы —
   различает их именно значок и слово в заголовке.

   Раскладка сознательно flex, но у .definition и соседей в старой разметке
   бывает два поколения содержимого: новое с .a-icon/.a-title/.a-body и старое
   простым текстом. Поэтому flex включается ТОЛЬКО когда внутри есть .a-icon —
   иначе inline-элементы вроде <strong> становились бы флекс-элементами
   и врезка разъезжалась по ширине. Этот случай уже ломал 206 страниц. */

.definition, .highlight, .memo, .mistake, .callout {
  display: block;
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid transparent;
  border-left-width: 3px;
  border-radius: var(--radius-md);
  line-height: var(--lh-md);
  color: var(--text);
}
.definition:has(.a-icon), .highlight:has(.a-icon),
.memo:has(.a-icon), .mistake:has(.a-icon), .callout:has(.a-icon) {
  display: flex; gap: var(--sp-3); align-items: flex-start;
}

.definition { background: var(--ok-soft);   border-color: var(--ok);   }
.highlight  { background: var(--info-soft); border-color: var(--info); }
.memo       { background: var(--warn-soft); border-color: var(--warn); }
.mistake    { background: var(--err-soft);  border-color: var(--err);  }

.definition .a-title { color: var(--ok-fg);   }
.highlight  .a-title { color: var(--info-fg); }
.memo       .a-title { color: var(--warn-fg); }
.mistake    .a-title { color: var(--err-fg);  }

.definition .a-icon { color: var(--ok);   }
.highlight  .a-icon { color: var(--info); }
.memo       .a-icon { color: var(--warn); }
.mistake    .a-icon { color: var(--err);  }

.a-icon {
  flex-shrink: 0;
  width: 1.25em; height: 1.25em;
  margin-top: .18em;
  font-size: var(--fs-md); line-height: 1;
}
.a-title {
  display: block;
  font-weight: var(--fw-semi);
  margin-bottom: var(--sp-1);
}
.a-body {
  font-size: var(--fs-md); line-height: var(--lh-md);
  /* Обязательно: без этого выключная формула внутри врезки растягивает её
     за край экрана. Флекс-элемент по умолчанию не сжимается уже содержимого,
     а .katex-display шириной в 500px как раз такое содержимое. */
  min-width: 0;
  flex: 1 1 auto;
}
.a-body > * + * { margin-top: var(--sp-2); }

/* ── БЛОК ПРЕДПОСЫЛОК ──────────────────────────────────────────────────── */

.prereq {
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent-line);
  border-radius: var(--radius-md);
  background: var(--accent-soft);
  font-size: var(--fs-sm); line-height: var(--lh-sm);
}
.prereq-title {
  display: block; margin-bottom: var(--sp-1);
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-soft);
}
.prereq a { color: var(--text-strong); text-decoration-color: var(--accent); }
.prereq a + a::before { content: " · "; text-decoration: none; opacity: .5; }

/* ── ЗНАЧКИ СОСТОЯНИЯ ──────────────────────────────────────────────────── */
/* У значка всегда есть текст. Цветной кружок без подписи — не сообщение. */

.badge, .chip, .status-badge {
  display: inline-flex; align-items: center; gap: var(--sp-1);
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  background: var(--n-2); color: var(--text-soft);
  font-size: var(--fs-xs); font-weight: var(--fw-medium);
  white-space: nowrap;
}
.badge-ok   { background: var(--ok-soft);   border-color: var(--ok);   color: var(--ok-fg);   }
.badge-err  { background: var(--err-soft);  border-color: var(--err);  color: var(--err-fg);  }
.badge-warn { background: var(--warn-soft); border-color: var(--warn); color: var(--warn-fg); }
.badge-info { background: var(--info-soft); border-color: var(--info); color: var(--info-fg); }
.badge-free { background: var(--ok-soft); border-color: var(--ok); color: var(--ok-fg); }
.badge.soon { background: var(--warn-soft); border-color: var(--warn); color: var(--warn-fg); }

/* ── ПОЛОСА ПРОГРЕССА ──────────────────────────────────────────────────── */
/* Сплошная заливка, не градиент: градиент здесь ничего не кодировал. */

.progress-track {
  height: 6px; border-radius: var(--radius-pill);
  background: var(--n-3); overflow: hidden;
}
.progress-fill {
  height: 100%; border-radius: var(--radius-pill);
  background: var(--accent);
  transition: width var(--dur-base) var(--ease-out);
}
.progress-fill.is-ok { background: var(--ok); }

/* ── ТАБЛИЦА ───────────────────────────────────────────────────────────── */

.table { width: 100%; font-size: var(--fs-sm); }
.table th, .table td {
  padding: var(--sp-3) var(--sp-4);
  text-align: left; border-bottom: 1px solid var(--border);
}
.table th {
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-size: var(--fs-xs); letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-soft);
}
.table tbody tr:hover { background: var(--n-2); }
.table td.num { font-variant-numeric: tabular-nums; text-align: right; }

/* ── ПОЛЯ ВВОДА ────────────────────────────────────────────────────────── */

.field {
  display: flex; flex-direction: column; gap: var(--sp-1);
}
.field > label { font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-soft); }
.input, input[type="text"], input[type="number"], textarea, select {
  min-height: var(--tap);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text);
  font-size: var(--fs-md);
  transition: border-color var(--dur-fast) var(--ease-out);
}
.input:hover, input[type="text"]:hover { border-color: var(--n-5); }
.input.is-err { border-color: var(--err); }
.input.is-ok  { border-color: var(--ok); }

/* Ползунок: цвет ручки из токена, дорожка нейтральная.
   Высота задана тач-целью: у голого range зона нажатия ~16px, и попасть
   в него пальцем почти нельзя. */
input[type="range"] {
  accent-color: var(--accent);
  min-height: var(--tap);
  flex: 1 1 120px;
}

/* ── РАСКЛАДКА СТРАНИЦЫ ТЕМЫ ───────────────────────────────────────────────
   Теория слева, интерактив справа. Правая половина липкая и ровно в высоту
   окна: прокрутки ВНУТРИ интерактива быть не должно — если панели не влезают,
   сокращается содержимое панелей, а не добавляется скролл.

   Высота считается от фактической высоты шапки (--hdr-real, её пишет скрипт),
   а не от объявленной: при другом кегле они не совпадают и блок вылезал вниз. */

.page-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: start;
}
.theory-col {
  min-width: 0;
  padding: var(--sp-8) var(--sp-8) var(--sp-24);
}
.theory-col > * + * { margin-top: var(--sp-5); }

.interactive-col, .viz-col {
  position: sticky;
  top: var(--hdr-real, var(--hdr, 64px));
  height: calc(100vh - var(--hdr-real, var(--hdr, 64px)));
  min-width: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: var(--sp-2);
  padding: var(--sp-4);
  overflow: hidden;
}

@media (max-width: 900px) {
  .page-split { grid-template-columns: minmax(0, 1fr); }
  .theory-col { padding: var(--sp-5) var(--sp-4) var(--sp-12); }
  .interactive-col, .viz-col {
    position: static; height: auto;
    min-height: 60vh; padding: var(--sp-3) var(--sp-4) var(--sp-8);
  }
}

/* ── КОРОБКА ИНТЕРАКТИВА ──────────────────────────────────────────────────
   Сцена занимает всю доступную высоту, панели стоят под ней и меряются своей
   высотой. Холст выведен из потока: иначе SVG держит собственную высоту
   и не даёт сцене ужаться. */

.viz-block {
  width: 100%;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-raised);
  overflow: hidden;
  user-select: none; -webkit-user-select: none;
}
.viz-block .stage { position: relative; min-height: 0; }
.viz-block .stage > svg, .viz-block .stage > canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
}
/* pan-y, а не none: палец на чертеже листает страницу (жалоба ученика 01.10.2026,
   замер — 480 из 511 тем не листались). Перетаскиваемое забирает жест само:
   touchstart с preventDefault у ручки (151 тема) или touch-action: none на ручке. */
.viz-block svg, .viz-block canvas { touch-action: pan-y pinch-zoom; margin-inline: auto; }
.viz-inner { padding: var(--sp-5) var(--sp-5) var(--sp-2); }

.viz-caption {
  padding: var(--sp-3) var(--sp-5);
  border-top: 1px solid var(--border);
  background: var(--n-2);
  font-size: var(--fs-xs); line-height: var(--lh-xs);
  color: var(--text-soft); text-align: center;
}

/* Панель показаний — как на приборе: подпись капслоком, значение моношрифтом.
   tabular-nums не даёт цифрам дёргаться при перетаскивании. */
.readout {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(84px, 100%), 1fr));
  border-top: 1px solid var(--border);
  background: var(--n-2);
}
.readout-cell {
  padding: var(--sp-2) var(--sp-3);
  border-right: 1px solid var(--border);
  text-align: center;
}
.readout-cell:last-child { border-right: none; }
.readout-k {
  display: block;
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-soft);
}
/* Буква на чертеже (α, β) — не подпись. Прописные у .readout-k превращали
   α в «Α» и β в «Β»: под чертежом с α и β стояли латинские на вид A и B.
   Набирается как метка фигуры — математическим курсивом, без разрядки. */
.readout-k.readout-sym {
  text-transform: none; letter-spacing: 0;
  font-family: var(--font-math); font-style: italic; font-weight: 400;
  font-size: var(--fs-md);
  /* строка той же высоты, что у соседней подписи «сумма», — значения
     в ячейках остаются на одной линии */
  line-height: calc(var(--fs-xs) * var(--lh-xs));
}
.readout-v {
  display: block;
  font-size: var(--fs-lg); color: var(--text-strong);
}
.readout-cell.is-total .readout-v { color: var(--accent); }

.formula-box {
  padding: var(--sp-3) var(--sp-5);
  border-top: 1px solid var(--border);
  background: var(--n-2);
  text-align: center; min-height: 1.4em;
  font-size: var(--fs-md); color: var(--text);
}
.formula-box b { color: var(--text-strong); font-weight: var(--fw-semi); }

/* ── ПЕРЕКЛЮЧАТЕЛЬ ВКЛАДОК ────────────────────────────────────────────────
   Сегментированный переключатель: рамка одна на всю группу, внутри — кнопки.
   Так видно, что это выбор одного из нескольких, а не набор отдельных кнопок. */

.tab-row {
  display: flex; gap: 0;
  width: fit-content; max-width: 100%;
  padding: 3px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  background: var(--n-2);
  /* На телефоне три-четыре вкладки в строку не влезают, и последняя уезжает
     за край экрана — до неё физически нельзя дотянуться. Поэтому ряд
     прокручивается внутри себя. Полосу прокрутки прячем: на тач-экране она
     не нужна, а на десктопе ряд обычно влезает целиком. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.tab-row::-webkit-scrollbar { display: none; }
.tab-row > .tab-btn { flex-shrink: 0; }
.tab-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap);   /* тач-цель: без этого выходило 43px — мимо на 1px */
  padding: var(--sp-2) var(--sp-4);
  border: none; border-radius: var(--radius-sm);
  background: transparent; color: var(--text-soft);
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.tab-btn:hover { color: var(--text-strong); }
.tab-btn.active, .tab-btn[aria-selected="true"] {
  background: var(--surface); color: var(--text-strong);
  font-weight: var(--fw-semi);
  box-shadow: var(--shadow-raised);
}

/* ── ПОШАГОВЫЙ РАЗБОР ──────────────────────────────────────────────────── */

.step-btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--sp-2);
  min-height: 2.2rem; padding: 0 var(--sp-4);
  border: 1px solid var(--accent); border-radius: var(--radius-md);
  background: var(--accent); color: var(--accent-fg);
  font-size: var(--fs-sm); font-weight: var(--fw-semi); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.step-btn:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); }
.step-btn:disabled { opacity: .45; cursor: default; }

.proof-step {
  display: flex; gap: var(--sp-4); align-items: flex-start;
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--border);
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.proof-step:last-child { border-bottom: none; }
.proof-step.step-visible { opacity: 1; transform: none; }
.proof-step .num {
  flex-shrink: 0;
  width: 1.7em; height: 1.7em;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-pill);
  background: var(--accent); color: var(--accent-fg);
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-size: var(--fs-sm);
}
.proof-step .txt { font-size: var(--fs-md); line-height: var(--lh-md); }

.slider-row {
  display: flex; align-items: center; gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-5) 0;
}
.slider-row span.lbl { font-size: var(--fs-sm); color: var(--text-soft); }
.slider-row span.val {
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm); color: var(--text-strong);
  text-align: right; min-width: 3.5ch;
}

/* ── СВЯЗЬ ТЕКСТ ↔ ЧЕРТЁЖ ─────────────────────────────────────────────────
   Наведение на упоминание в тексте подсвечивает элемент на чертеже.
   Пунктир под словом показывает, что связь есть, до наведения. */

.hl {
  cursor: pointer;
  border-bottom: 1.5px dashed var(--accent-line);
  transition: background-color var(--dur-instant) var(--ease-out),
              border-color var(--dur-instant) var(--ease-out);
}
.hl:hover, .hl.is-active {
  background: var(--accent-soft);
  border-bottom-color: var(--accent);
}

/* ── НАВИГАЦИЯ ПО ТЕМАМ ────────────────────────────────────────────────── */

.topic-nav {
  display: grid; grid-template-columns: 1fr auto 1fr;
  align-items: stretch; gap: var(--sp-3);
  margin-top: var(--sp-12);
}
.topic-nav-side {
  display: flex; flex-direction: column; gap: 2px;
  min-width: 0; max-width: 100%; width: fit-content;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  background: var(--surface); text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.topic-nav-side:hover { border-color: var(--accent); background: var(--n-2); }
.topic-nav-side:empty { background: none; border: none; padding: 0; }
.topic-nav-prev { justify-self: start; text-align: left; }
.topic-nav-next { justify-self: end; text-align: right; }
.topic-nav-meta {
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-size: var(--fs-xs); color: var(--text-soft); white-space: nowrap;
}
.topic-nav-title {
  display: block; max-width: 100%;
  font-size: var(--fs-sm); font-weight: var(--fw-semi);
  color: var(--text-strong);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.topic-nav-all {
  justify-self: center; align-self: center;
  display: inline-flex; align-items: center;
  min-height: var(--tap); padding: 0 var(--sp-5);
  border-radius: var(--radius-md);
  font-size: var(--fs-sm); font-weight: var(--fw-semi);
  text-decoration: none; white-space: nowrap;
}

/* На узком экране три колонки навигации сжимаются до 37px, и от названия темы
   остаётся одна буква с многоточием. Ставим в столбец: лучше три полные строки,
   чем три обрубка. */
@media (max-width: 640px) {
  .topic-nav { grid-template-columns: minmax(0, 1fr); gap: var(--sp-2); }
  .topic-nav-side { width: 100%; }
  .topic-nav-prev, .topic-nav-next { justify-self: stretch; text-align: left; }
  .topic-nav-all { justify-self: stretch; justify-content: center; }
}

/* ── ХЛЕБНЫЕ КРОШКИ ───────────────────────────────────────────────────── */

.crumbs {
  display: flex; align-items: center; gap: var(--sp-2);
  flex-wrap: wrap; font-size: var(--fs-sm); color: var(--text-soft);
}
.crumbs a { color: var(--text-soft); text-decoration: none; }
.crumbs a:hover { color: var(--text-strong); text-decoration: underline; }
.crumbs .sep { color: var(--n-5); }
.crumbs .here { color: var(--text-strong); font-weight: var(--fw-medium); }

/* ── ФУТЕР ─────────────────────────────────────────────────────────────── */

footer.site {
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: var(--sp-3);
  padding: var(--sp-6) var(--page-pad);
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm); color: var(--text-soft);
}
footer.site a { display: inline-flex; align-items: center; min-height: var(--tap); }

/* ── МАТЕМАТИКА (KaTeX) ────────────────────────────────────────────────────
   Кегль формулы НЕ увеличиваем глобально: раньше стояло 1.08em с nowrap,
   и на узком экране формула выпирала за край. Выключной формуле разрешено
   быть крупнее, но она умеет прокручиваться внутри себя. */

.katex { font-size: 1em; }
.katex-display {
  margin: var(--sp-4) 0;
  text-align: center;
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  padding-bottom: 2px;
}
.katex-display > .katex { font-size: 1.15em; }

/* Правила «абзац из одной формулы — по центру» (p:has(> .katex:only-child))
   здесь больше нет: :only-child не видит текстовые узлы, и по центру вставал
   любой абзац «текст + одна формула». На боевых страницах 30.09.2026 таких
   абзацев не нашлось (формула там не прямой ребёнок <p>), но новый раздел
   формул на это наступил. Выключная формула центрируется сама (.katex-display выше). */

/* Метки на чертежах — курсивная засечка, как в учебнике */
.svg-label, text.label {
  font-family: var(--font-math);
  font-style: italic;
}

/* ── ДВИЖЕНИЕ ──────────────────────────────────────────────────────────── */

.reveal {
  opacity: 0; transform: translateY(10px);
  animation: sup-reveal var(--dur-slow) var(--ease-out) forwards;
  animation-delay: calc(var(--i, 0) * var(--stagger));
}
@keyframes sup-reveal { to { opacity: 1; transform: none; } }

/* Обводка появляется прорисовкой — как будто чертят от руки */
@keyframes sup-draw { to { stroke-dashoffset: 0; } }
@keyframes sup-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes sup-pop  { from { opacity: 0; transform: scale(.7); } to { opacity: 1; transform: none; } }

.draw-in-stroke { animation: sup-draw var(--dur-slow) var(--ease-out) forwards; }
.draw-in-fade   { animation: sup-fade var(--dur-base) var(--ease-out) forwards; }
.draw-in-fill   {
  transform-box: fill-box; transform-origin: center;
  animation: sup-pop var(--dur-base) var(--ease-out) forwards;
}

/* Не «замедлить», а выключить: анимация появления обязана оставить элемент
   видимым, иначе при выключенном движении контент просто исчезнет. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal, .proof-step { opacity: 1; transform: none; animation: none; }
  .draw-in-stroke, .draw-in-fade, .draw-in-fill { animation: none; opacity: 1; }
  /* Прорисовка ставит начальное состояние В АТРИБУТ style: смещение пунктира
     на всю длину и opacity: 0. В обычном режиме его перебивает анимация
     (forwards), а без анимаций перебить нечему — линия не рисовалась вовсе:
     283 страницы теории из 511, у 157 пустые чертежи (замер 07.10.2026).
     !important даёт ровно конечное состояние анимации, без движения. */
  .draw-in-stroke { stroke-dashoffset: 0 !important; }
  .draw-in-fade, .draw-in-fill, .draw-in-point { opacity: 1 !important; }
}

/* ── ПЕЧАТЬ ────────────────────────────────────────────────────────────────
   Ученик распечатывает тему, чтобы решать ручкой. Фактура, интерактив
   и навигация на бумаге не нужны. */

@media print {
  body::before, .interactive-col, .viz-col, .topic-nav,
  header.page-header, header.site, footer.site, .theme-toggle { display: none !important; }
  .page-split { grid-template-columns: 1fr; }
  .card { border: 1px solid #999; box-shadow: none; break-inside: avoid; }
  .definition, .highlight, .memo, .mistake { break-inside: avoid; }
}

/* ─── 3/4 paper.css ─────────────────────────────────────────── */
/* ============================================================================
   СУПРЕМУМ · paper.css — слой «Тушь и бумага» поверх sup-base.css
   ============================================================================
   Порядок подключения на странице:
     ../../assets/sup-fonts.css
     assets/sup-tokens-paper.css   (генерируется, руками не правится)
     ../../assets/sup-base.css     (компоненты, имена классов 512 страниц)
     assets/paper.css              (этот файл: стекло, оболочка, мобильная база)

   Правило слоёв (docs\design-system.md §2): стекло — только навигационный
   слой (.g1: шапка, сайдбар, липкие панели), лист .g2/.card ≥ 0.88
   непрозрачности, длинный учебный текст — только на плотном.
   ============================================================================ */

/* Тень модального слоя. В генераторе токенов её нет: там живут только
   значения с холста, а эта тень выводится из цвета тени тональности (--sh)
   и потому одинаково верна во всех шести сочетаниях тон × тема.
   Единственный потребитель — мост старых имён (--shadow-lg в sup-compat.css). */
:root { --shadow-modal: 0 16px 40px var(--sh); }

/* ── МОБИЛЬНАЯ БАЗА ───────────────────────────────────────────────────────
   База 20px из токенов; на телефоне 18px, иначе мера строки на 375px
   падает до 29 знаков (норма телефона 30–40). Минимум текста вне холста —
   15.5px АБСОЛЮТНЫХ: при базе 18px ступень xs (0.775rem = 13.9px) проваливает
   пол, поэтому на телефоне xs поднимается до 15.5px явно. */
@media (max-width: 720px) {
  html { font-size: 18px; }
  :root, [data-tone] { --fs-xs: 0.8612rem; } /* 15.5px при базе 18px */
}

/* Тач-цель ≥ 44px у любой кнопки. Базовый .btn при мобильной базе даёт
   43.2px (2.4rem × 18) — добираем полом, а не сменой шкалы. */
.btn { min-height: max(var(--btn-h), var(--tap)); }

/* Атрибут hidden обязан побеждать display компонентов (грабля §11.2) */
[hidden] { display: none !important; }

/* Фоновое свечение страницы — одно, в верхнем углу, не спорит с чертежом */
body {
  background-color: var(--bg);
  background-image: radial-gradient(900px 480px at 10% -12%, var(--bloom), transparent 70%);
  background-attachment: fixed;
}

/* ── СТЕКЛО: ДВА УРОВНЯ ───────────────────────────────────────────────────
   g1 — плавающее (blur 20): шапка, сайдбар, липкая панель, всплывающее.
   g2 и card — лист: карточка, плитка, панель.

   ЛИСТ БОЛЬШЕ НЕ РАЗМЫВАЕТ ФОН, И ЭТО НЕ ЭКОНОМИЯ НА ВИДЕ. Замер 09.09.2026:
   на хабе алгебры 357 элементов несли backdrop-filter — это карточки тем,
   которых там 333. Прокрутка теряла треть кадров: 25 кадров из 63 дольше
   20мс, худший 59мс. Стоит убрать размытие с листа — 0 просевших из 106,
   худший 17.9мс. Тени при этом ни при чём: без них картина не менялась.

   А видно ли то, за что платили? Попиксельное сравнение снимков со стеклом
   и без, обе темы: доля пикселей, отличающихся заметнее порога, — 0.00%
   на хабах и главной, 0.73% на каталоге; самый непохожий пиксель отличается
   на 3 из 255 при пороге заметности около 2. Причина простая: лист стоит
   на непрозрачности ≥0.88, сквозь него нечего размывать.

   Навигационный слой (g1, шапка, подвал) размытие сохраняет: там элементов
   единицы, они лежат поверх бегущего текста, и там эффект виден. */

.g1 {
  background: var(--glass);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  backdrop-filter: blur(20px) saturate(1.3);
  border: 1px solid var(--glass-border);
  box-shadow: 0 8px 32px var(--sh), 0 0 0 1px var(--hair), inset 0 1px 0 var(--glass-top);
}
.g2, .card {
  background: var(--glass-strong);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius-lg);
  box-shadow: 0 1px 2px var(--sh), 0 0 0 1px var(--hair);
}
header.site, footer.site {
  background: var(--glass);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  backdrop-filter: blur(20px) saturate(1.3);
}
.btn-outline { background: var(--glass-strong); border-color: var(--border-strong); }
.card-link:hover { background: var(--glass-hover); border-color: var(--accent); }
.progress-track { background: var(--hair); }

/* Плотная подложка контентного слоя: длинный текст, поле ответа, разбор */
.sheet {
  background: var(--solid);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-raised);
}

/* ── СТЕКЛО СНИМАЕТСЯ НА ВРЕМЯ ПРОКРУТКИ ──────────────────────────────────
   Замер 09.09.2026. Липкая шапка с backdrop-filter заставляет браузер
   пересчитывать размытие на каждом кадре: хаб теории терял 26 кадров из 88,
   худший 40мс. Радиус ни при чём — blur 20, 12 и 6 стоят одинаково, платим
   за композитинг, а не за вычисление. Не помогли ни translateZ, ни contain,
   ни will-change: 23–27 просевших во всех трёх случаях.

   Убрать стекло совсем — значит изменить вид шапки на 6–18% пикселей, а это
   утверждённый облик навигационного слоя. Поэтому размытие снимается только
   НА ВРЕМЯ ДВИЖЕНИЯ: пока страница едет, шапку никто не разглядывает.
   Класс ставит paper.js, снимает через 120мс после последнего события.

   Итог замера: хаб теории 26 → 1 просевший, главная 13 → 0, а вид в покое
   отличается от нынешнего на 0.00% пикселей.

   Правило намеренно на `*`, а не на списке имён. Список уже протёк: он
   перечислял шапку, подвал, сайдбар и панели, а на справочнике формул
   размытие несёт ещё и шапка аккордеона глав — страница осталась
   единственной непочиненной (22 просевших кадра из 99). Всякий новый
   компонент со стеклом протечёт так же, и заметит это не автор,
   а ученик. Во время движения размытие не нужно нигде. */
html.is-scrolling * {
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
}

/* Фолбэки стекла. Уровней было три; первым стоял свой переключатель
   «Стекло / Плотно» в шапке — он убран вместе с кнопкой, потому что кнопка
   без объяснения рядом с логотипом ничего человеку не сообщала. Остались
   два уровня, и оба срабатывают сами, без участия человека.
   Если понадобится вернуть ручной выбор — ему место в настройках кабинета,
   рядом с тональностью, а не в шапке.
   1) браузер без backdrop-filter; */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .g1, .g2, .card, header.site, footer.site, .shell-topbar, #appnav, .tabbar {
    background: var(--solid);
  }
}
/* 3) системная просьба меньше прозрачности; */
@media (prefers-reduced-transparency: reduce) {
  .g1, .g2, .card, header.site, footer.site, .shell-topbar, #appnav, .tabbar {
    background: var(--solid);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}
/* 4) телефон: blur рвёт прокрутку на бюджетном Android. */
@media (max-width: 720px) {
  .g1, .g2, .card, header.site, footer.site, .shell-topbar, #appnav, .tabbar {
    background: var(--solid);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* Край шапки совпадает с краем текста.
   В sup-base шапка отбита `--page-pad` (48px на широком экране), а колонка
   теории — `--sp-8` (32px): логотип стоял на 16px правее первой буквы текста.
   Промах в полтора десятка пикселей читается хуже, чем честно разная отбивка,
   поэтому у шапки теперь тот же край, что у содержимого. На узком экране
   правило из sup-base (≤700px → sp-4) остаётся в силе и перебивает это. */
header.site, header.page-header, .sup-header { padding-inline: var(--sp-8); }

/* Шапка при прокрутке уплотняется, а не висит прозрачной над текстом */
header.site { transition: min-height var(--dur-base) var(--ease-out); }
html[data-scrolled] header.site { min-height: 52px; }

/* ── ПУБЛИЧНАЯ ШАПКА НА ТЕЛЕФОНЕ: бургер вместо трёх рядов ────────────────
   Замер 09.09.2026 на зеркале, ширины 360 и 390, обе темы. Шапка разъезжалась
   на три ряда и 169px высоты при норме чертежа (§3) «одна строка, ≤64px»,
   а меню прокручивалось вбок вообще без признака прокрутки: scrollWidth 437
   при clientWidth 296, последний пункт «Тарифы» не был виден никогда —
   ученик не знал, что за краем что-то есть.

   Из трёх возможных решений выбран бургер. Подсказка о прокрутке (градиент
   у края) чинит незаметность, но не высоту: меню остаётся отдельной строкой,
   то есть две строки и ~117px — чертёж этого не разрешает. Перенос части
   пунктов отпадает: их шесть и лишних среди них нет.

   Что остаётся в шапке на телефоне: логотип, «Войти» и бургер. Переключатель
   темы уезжает внутрь панели — тему выбирают один раз, а войти зовут с каждой
   страницы, и при равном месте уступает та кнопка, к которой возвращаются реже.

   Панель на широком экране — display: contents, то есть её в раскладке нет
   вовсе: шапка собирается ровно так же, как до появления бургера, и правила
   sup-base для ширин 1400/1180 продолжают работать как работали. */
.nav-panel { display: contents; }
.nav-panel-foot { display: none; }
/* Размер задаём явно: .nav-burger — не .theme-toggle, размер тач-цели ей
   ниоткуда не достаётся. Замер поймал кнопку 30×20 при норме 44×44. */
.nav-burger {
  display: none;
  width: var(--tap); height: var(--tap);
  align-items: center; justify-content: center;
  border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text-soft); cursor: pointer;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.nav-burger:hover { background: var(--n-2); color: var(--text-strong); }
.nav-burger .i-close { display: none; }
.nav-burger[aria-expanded="true"] .i-open { display: none; }
.nav-burger[aria-expanded="true"] .i-close { display: inline-flex; }

/* Всё, что ниже, ограничено шапкой С ПАНЕЛЬЮ (`:has(.nav-panel)`) и самой
   панелью. Иначе правила достались бы и шапке старой разметки — а там меню
   не в панели, бургера нет, и столбик пунктов раздул бы шапку вместо того,
   чтобы её починить. Такая шапка на слое ещё осталась (formulas.html). */
@media (max-width: 950px) {
  /* Перебивает flex-wrap из sup-base: переносить больше нечего, меню ушло
     в панель, а логотипу разрешено сжиматься, но не выдавливать кнопки. */
  header.site:has(.nav-panel) { flex-wrap: nowrap; }
  header.site:has(.nav-panel) .logo { flex: 0 1 auto; min-width: 0; }
  header.site:has(.nav-panel) .logo-name { white-space: nowrap; }
  header.site:has(.nav-panel) .header-right { flex-shrink: 0; margin-left: auto; }
  .theme-wide { display: none; }
  .nav-burger { display: inline-flex; }

  /* Панель висит под шапкой, а не раздвигает её: высота шапки не зависит
     от того, открыто меню или нет. Подложка плотная (--solid), потому что
     под ней учебный текст — сквозь меню его быть видно не должно. */
  .nav-panel {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    padding: var(--sp-2) var(--sp-4) var(--sp-4);
    background: var(--solid);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-modal);
    max-height: calc(100dvh - var(--hdr, 64px));
    overflow-y: auto;
  }
  header.site[data-nav-open] .nav-panel { display: block; }

  /* Пункты в столбик во всю ширину панели: прокрутки вбок больше нет,
     каждый пункт — тач-цель во всю строку. */
  .nav-panel nav.site {
    order: 0; width: auto;
    flex-direction: column; flex-wrap: nowrap;
    gap: 2px; padding-bottom: 0; overflow-x: visible;
  }
  .nav-panel nav.site a {
    width: 100%; flex: 0 0 auto;
    font-size: var(--fs-sm); padding-inline: var(--sp-3);
  }

  .nav-panel-foot {
    display: block;
    margin-top: var(--sp-2); padding-top: var(--sp-2);
    border-top: 1px solid var(--hair);
  }
  .theme-row {
    width: 100%; height: auto; min-height: var(--tap);
    justify-content: flex-start; gap: var(--sp-3);
    padding-inline: var(--sp-3);
    border-color: transparent; background: transparent;
  }
}
.theme-row-label { font-size: var(--fs-sm); }

/* ── ПОДВАЛ: разделы и документы разными группами ─────────────────────────
   Юридические ссылки не мешаются в один ряд с разделами: это разный род
   ссылок, и в одном ряду последняя из них упиралась в край без поля. */
.foot-links {
  display: flex; flex-wrap: wrap;
  align-items: center; justify-content: flex-end;
  gap: var(--sp-2) var(--sp-6);
  min-width: 0;
}
.foot-legal { color: var(--text-soft); }
@media (max-width: 720px) {
  .foot-links { justify-content: flex-start; }
}

/* ── ОБОЛОЧКА ПЛАТФОРМЫ: сайдбар + верхняя полоса ───────────────────────── */

.shell {
  display: grid;
  grid-template-columns: 264px minmax(0, 1fr);
  min-height: 100dvh;
  align-items: start;
}
#appnav {
  position: sticky; top: 0;
  height: 100dvh;
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-5) var(--sp-4);
  background: var(--glass);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  backdrop-filter: blur(20px) saturate(1.3);
  border-right: 1px solid var(--hair);
  overflow-y: auto;
}
#appnav .logo { margin-bottom: var(--sp-4); }
#appnav a.nav-item {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--tap); padding: 0 var(--sp-3);
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  color: var(--text-soft); text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
#appnav a.nav-item:hover { background: var(--sunken); color: var(--text-strong); }
#appnav a.nav-item[aria-current] {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 var(--accent);
  color: var(--text-strong); font-weight: var(--fw-semi);
}
#appnav .nav-foot { margin-top: auto; }

/* ── ПРОФИЛЬ В ПОДВАЛЕ САЙДБАРА ───────────────────────────────────────────
   Вход в кабинет и тариф стоят рядом с именем, внизу — там, где их ищут.
   В шапке сайдбара тарифу не место: шапка отвечает за бренд, а не за то,
   сколько ученик платит. */
#appnav .nav-profile {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--tap); padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--glass-strong);
  color: var(--text); text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
#appnav .nav-profile:hover { background: var(--glass-hover); border-color: var(--accent); }
#appnav .nav-avatar {
  flex-shrink: 0;
  width: 34px; height: 34px; border-radius: var(--radius-pill);
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--accent-fg);
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-size: var(--fs-xs); letter-spacing: .04em;
}
#appnav .nav-who { display: flex; flex-direction: column; min-width: 0; }
#appnav .nav-name {
  font-size: var(--fs-sm); font-weight: var(--fw-medium); color: var(--text-strong);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
#appnav .nav-plan {
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-size: var(--fs-xs); letter-spacing: .06em;
  color: var(--text-soft); white-space: nowrap;
  /* в 264px сайдбара длинная подпись тарифа рвала полосу горизонтальной
     прокруткой — теперь она усекается, а не растягивает блок */
  overflow: hidden; text-overflow: ellipsis;
}
/* Подпись бренда в узком сайдбаре переносилась на две строки и толкала
   меню вниз. Место подписи — публичная шапка, здесь она лишняя. */
#appnav .logo-sub { display: none; }
/* Никакой горизонтальной прокрутки в навигации: она всегда означает промах
   вёрстки, а не намерение */
#appnav { overflow-x: hidden; }
#appnav .nav-profile .icon { margin-left: auto; flex-shrink: 0; color: var(--text-soft); }

.shell-main { min-width: 0; }
.shell-topbar {
  position: sticky; top: 0; z-index: var(--z-header);
  display: flex; align-items: center; gap: var(--sp-4);
  min-height: 60px; padding: var(--sp-2) var(--sp-6);
  background: var(--glass);
  -webkit-backdrop-filter: blur(20px) saturate(1.3);
  backdrop-filter: blur(20px) saturate(1.3);
  border-bottom: 1px solid var(--hair);
}
.shell-content { padding: var(--sp-6) var(--sp-6) var(--sp-24); }

/* Режим фокуса (тренажёр, пробник): сайдбара нет, только полоса */
.shell-focus { display: block; }

/* Нижняя панель на телефоне — тот же реестр пунктов, другой вид */
.tabbar { display: none; }
@media (max-width: 900px) {
  .shell { grid-template-columns: minmax(0, 1fr); }
  #appnav { display: none; }
  .shell-content { padding: var(--sp-4) var(--sp-4) calc(var(--sp-24) + 56px); }
  .tabbar {
    position: fixed; inset: auto 0 0 0; z-index: var(--z-header);
    display: grid; grid-auto-flow: column; justify-items: center;
    padding: var(--sp-1) var(--sp-2) calc(var(--sp-1) + env(safe-area-inset-bottom));
    background: var(--glass);
    -webkit-backdrop-filter: blur(20px) saturate(1.3);
    backdrop-filter: blur(20px) saturate(1.3);
    border-top: 1px solid var(--hair);
  }
  .tabbar a {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 2px; min-height: var(--tap); min-width: var(--tap); padding: 0 var(--sp-2);
    border-radius: var(--radius-sm);
    font-size: var(--fs-xs); font-weight: var(--fw-medium);
    color: var(--text-soft); text-decoration: none;
  }
  .tabbar a[aria-current] {
    color: var(--text-strong); font-weight: var(--fw-semi);
    background: var(--accent-soft);
  }
}

/* ── СЕГМЕНТ-КОНТРОЛ: уровни, классы, фильтры ───────────────────────────── */

.seg {
  display: inline-flex; gap: 2px;
  padding: 3px;
  background: var(--sunken);
  border: 1px solid var(--hair);
  border-radius: var(--radius-md);
}
.seg > button, .seg > a {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-1);
  min-height: 38px; min-width: 44px; padding: 0 var(--sp-4);
  border-radius: calc(var(--radius-md) - 3px);
  font-size: var(--fs-sm); font-weight: var(--fw-medium);
  color: var(--text-soft); text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.seg > button:hover, .seg > a:hover { color: var(--text-strong); }
/* Сегмент выглядит плотно (38px), но зона нажатия не меньше 44×44 —
   добирается невидимым слоем, как у малой кнопки в sup-base */
.seg > button, .seg > a { position: relative; }
.seg > button::after, .seg > a::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: 50%; transform: translateY(-50%);
  height: var(--tap);
  min-width: var(--tap);
}
.seg > [aria-pressed="true"], .seg > [aria-current], .seg > .active {
  background: var(--solid);
  box-shadow: var(--shadow-raised), 0 0 0 1px var(--hair);
  color: var(--text-strong); font-weight: var(--fw-semi);
}

/* Капсула-фильтр (формулы, разделы) */
.chip {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  min-height: 40px; padding: 0 var(--sp-4);
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: var(--glass-strong);
  color: var(--text); font-size: var(--fs-sm); font-weight: var(--fw-medium);
  cursor: pointer; text-decoration: none;
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.chip:hover { background: var(--glass-hover); border-color: var(--accent); }
/* Капсула 40px ростом — тач-зона добирается невидимым слоем до 44 */
.chip { position: relative; }
.chip::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: 50%; transform: translateY(-50%);
  height: var(--tap);
  min-width: var(--tap);
}
.chip[aria-pressed="true"], .chip.active {
  background: var(--accent); border-color: var(--accent);
  color: var(--accent-fg); font-weight: var(--fw-semi);
}

/* ── СОСТОЯНИЯ АСИНХРОННЫХ БЛОКОВ ────────────────────────────────────────── */

.skeleton {
  position: relative; overflow: hidden;
  background: var(--sunken);
  border-radius: var(--radius-sm);
  min-height: 1em;
  color: transparent !important;
  user-select: none;
}
.skeleton::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--glass-top), transparent);
  transform: translateX(-100%);
  animation: sup-shimmer 1.4s var(--ease-in-out) infinite;
}
@keyframes sup-shimmer { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .skeleton::after { animation: none; } }

.empty-state {
  display: flex; flex-direction: column; align-items: center; gap: var(--sp-3);
  padding: var(--sp-10) var(--sp-6);
  text-align: center; color: var(--text-soft);
}

/* ── МЕЛОЧИ ──────────────────────────────────────────────────────────────── */

/* Карта номеров экзамена: плитка-квадрат, знак + число */
.exam-map {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(52px, 1fr));
  gap: var(--sp-2);
}
.exam-tile {
  aspect-ratio: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--glass-strong);
  color: var(--text); font-family: var(--font-mono); font-weight: var(--fw-mono);
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.exam-tile:hover { border-color: var(--accent); background: var(--glass-hover); }
.exam-tile .n { font-size: var(--fs-sm); }
.exam-tile .s { font-size: var(--fs-xs); }
.exam-tile.is-ok { background: var(--ok-soft); border-color: var(--ok); color: var(--ok-fg); }
.exam-tile.is-warn { background: var(--warn-soft); border-color: var(--warn); color: var(--warn-fg); }
.exam-tile.is-err { background: var(--err-soft); border-color: var(--err); color: var(--err-fg); }
/* Не text-faint: номер на плитке — рабочая ссылка, контраст нужен полный */
.exam-tile.is-none { border-style: dashed; color: var(--text-soft); }

/* Рубрика блока предпосылок стоит на цветной подложке — text-soft там
   на кромке 4.5, берём основной цвет текста */
.prereq-title { color: var(--text); }

/* Топбар и правая часть шапки на телефоне переносятся, а не выпирают */
@media (max-width: 700px) {
  .shell-topbar { flex-wrap: wrap; padding-inline: var(--sp-4); }
  .header-right { flex-wrap: wrap; justify-content: flex-end; }
}

/* Столбики «задач в день» */
.bars { display: flex; align-items: flex-end; gap: var(--sp-1); height: 96px; }
.bars > i {
  flex: 1 1 0; min-width: 6px;
  background: var(--accent-soft);
  border-radius: 3px 3px 0 0;
}
.bars > i.hot { background: var(--accent); }

/* Плитки серии в тренажёре */
.streak-row { display: flex; gap: var(--sp-2); }
.streak-cell {
  width: 40px; height: 40px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  background: var(--ok-soft); color: var(--ok-fg);
  font-weight: var(--fw-semi);
}
.streak-cell.todo { background: transparent; border-style: dashed; color: var(--text-faint); }

/* Подчёркивание ручкой в герое — подпись бренда, не интерактив */
.pen-under {
  background-image: linear-gradient(var(--pen), var(--pen));
  background-repeat: no-repeat;
  background-size: 100% 3px;
  background-position: 0 96%;
  padding-bottom: 2px;
}

/* Аккордеон глав.
   Заголовок главы — не одна жирная строка, а три доли с разным весом:
   номер моношрифтом, название антиквой, счётчик тем прижат к правому краю.
   Стрелка НЕ тянется margin-left:auto: два автоотступа в одной строке делят
   свободное место пополам, и счётчик переставал доходить до правого края. */
details.chapter { border: 1px solid var(--border); border-radius: var(--radius-lg); }
details.chapter summary {
  display: flex; align-items: center; gap: var(--sp-3);
  min-height: var(--tap); padding: var(--sp-3) var(--sp-5);
  cursor: pointer; list-style: none;
  font-weight: var(--fw-normal); color: var(--text-strong);
}
details.chapter summary .ch-n {
  font-family: var(--font-mono); font-weight: var(--fw-mono);
  font-size: var(--fs-xs); letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-soft); flex-shrink: 0;
}
details.chapter summary .ch-t {
  font-family: var(--font-display); font-weight: var(--fw-semi);
  font-size: var(--fs-lg); line-height: 1.25; color: var(--text-strong);
  min-width: 0;
}
/* Счётчик тем и любая мета в шапке главы — по правому краю, до стрелки */
details.chapter summary > .meta,
details.chapter summary > .ch-meta {
  margin-left: auto; flex-shrink: 0; text-align: right;
}
details.chapter summary::-webkit-details-marker { display: none; }
details.chapter summary::after {
  /* отступ слева даёт gap строки; собственный margin здесь только удвоил бы его */
  content: ""; flex-shrink: 0;
  width: 9px; height: 9px;
  border-right: 2px solid var(--text-soft);
  border-bottom: 2px solid var(--text-soft);
  transform: rotate(45deg);
  transition: transform var(--dur-fast) var(--ease-out);
}
details.chapter[open] summary::after { transform: rotate(-135deg); }
details.chapter .chapter-body { padding: 0 var(--sp-5) var(--sp-5); }

/* Печать: стекло и панели навигации на бумаге не нужны */
@media print {
  .g1, .g2, .card { background: #fff; -webkit-backdrop-filter: none; backdrop-filter: none; }
  #appnav, .shell-topbar, .tabbar, .theme-toggle { display: none !important; }
  body { background: #fff; }
}

/* ─── 4/4 РАСКЛАДКА СЕГМЕНТНОЙ СТРАНИЦЫ ─────────────────────── */

/* Стекло навигации: значения сняты со слоя токенов сборщиком, не руками */
html:root { --glass-nav: rgba(255,254,251,.66); }
html:root:where([data-theme="light"]) { --glass-nav: rgba(255,254,251,.66); }
html:root:where([data-theme="dark"]) { --glass-nav: rgba(46,44,40,.60); }
html:root:where([data-tone="graphite"]) { --glass-nav: rgba(252,253,254,.66); }
html:root:where([data-tone="graphite"][data-theme="light"]) { --glass-nav: rgba(252,253,254,.66); }
html:root:where([data-tone="graphite"][data-theme="dark"]) { --glass-nav: rgba(42,47,51,.60); }
html:root:where([data-tone="news"]) { --glass-nav: rgba(255,255,255,.74); }
html:root:where([data-tone="news"][data-theme="light"]) { --glass-nav: rgba(255,255,255,.74); }
html:root:where([data-tone="news"][data-theme="dark"]) { --glass-nav: rgba(44,42,40,.62); }
@media (prefers-color-scheme: dark) {
  html:root:where(:not([data-theme="light"])) { --glass-nav: rgba(46,44,40,.60); }
  html:root:where([data-tone="graphite"]:not([data-theme="light"])) { --glass-nav: rgba(42,47,51,.60); }
  html:root:where([data-tone="news"]:not([data-theme="light"])) { --glass-nav: rgba(44,42,40,.62); }
}

/* ── СТЕКЛО НАВИГАЦИОННОГО СЛОЯ ───────────────────────────────────────────
   См. докстринг сборщика: --glass здесь намеренно плотный (мост старых имён),
   поэтому шапка и подвал берут прозрачность из отдельного токена. */
header.site, footer.site { background: var(--glass-nav, var(--glass)); }

/* Фолбэки paper.css (телефон, просьба меньше прозрачности, браузер без размытия)
   ставят шапке --solid, но этот слой стоит ниже и перебивал их своим стеклом.
   Замер 13.09.2026 на 375px: фон шапки 0.6 без размытия на всех 105 темах, и
   пока страница едет (html.is-scrolling снимает blur), текст читается сквозь
   шапку — скрины Анатолия с телефона. Фолбэки повторены здесь, после стекла. */
@media (max-width: 720px), (prefers-reduced-transparency: reduce) {
  header.site, footer.site { background: var(--solid); }
}
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  header.site, footer.site { background: var(--solid); }
}

/* ── ВОЗДУХ МЕЖДУ КОЛОНКАМИ ТАБЛИЦЫ ────────────────────────────────────────
   Жалоба Анатолия 13.09.2026: «тесные колонки и внутри элементы почти друг на
   друге». Замер measure_tables.py: у таблиц без своего оформления (темы 50, 55,
   85–88) поле ячейки — браузерный 1px, содержимое соседних колонок в 2px друг
   от друга. :where() — нулевая специфичность: правило заменяет только значение
   браузера и уступает любой таблице, которую страница оформила сама. */
:where(.topic-seg, .viz-block) :where(td, th) + :where(td, th) { padding-left: 14px; }
:where(.topic-seg, .viz-block) :where(td, th) { padding-block: 6px; }

/* ── ПОЛОСА СТРАНИЦЫ ──────────────────────────────────────────────────────
   Половина на половину во всю ширину экрана: на страницу смотрят ради
   чертежа, поэтому отдавать ему меньше места, чем тексту, нечестно. */
.topic-page {
  width: 100%;
  max-width: none;
  padding-inline: var(--sp-8);
  padding-block: var(--sp-6) var(--sp-24);
}

/* Путь читается как навигация, а не как сноска мелким серым */
.topic-page > .crumbs { color: var(--text); font-size: var(--fs-sm); }
/* Штамп рубрики стоял вплотную под путём (зазор 0–3px): на телефоне
   последняя строка пути и рамка штампа слипались (аудит 27.09.2026). */
.topic-page > .crumbs { margin-bottom: var(--sp-3); }
.topic-page > .crumbs .here { font-weight: var(--fw-semi); color: var(--text-strong); }

/* ── РУБРИКАТОР ───────────────────────────────────────────────────────────
   «Глава 1 · § 1» приглушённым цветом терялся на фоне — ученик не находил,
   где он. Это чертёжный штамп: моношрифт на подложке с чернильной риской. */
.rubric-stamp {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  /* риска слева прямая: со скруглением читается как открывающая скобка */
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  background: var(--sunken);
  color: var(--text);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  letter-spacing: .04em;
  /* Регистр — как набран, а не как велит правило. Строчными штамп набирает
     тот, кто его составляет («глава 4 · §2, п.5»); понижение регистра из
     общего `.rubric` ломало римские номера глав (у алгебры 10–11 «глава II»
     читалась как «глава ii» — слово, а не номер), а без указания вовсе он
     наследовал от `.rubric` заглавные и кричал «ГЛАВА II · § 8 · П.8.1». */
  text-transform: none;
}

/* ── ТЕЛО ТЕМЫ: РЯД = ГРУППА СЕГМЕНТОВ + ЕЁ ВИЗУАЛ ────────────────────────
   Левая ячейка ряда — сколько угодно смысловых блоков; правая — один визуал,
   липкий в пределах этого ряда. Выше своей группы он не появляется, ниже
   не продолжается: границы ряда грида и есть границы его жизни. */
.topic-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-10) var(--sp-8);
  align-items: start;
}
/* ИНТЕРАКТИВУ — БОЛЬШЕ ШИРИНЫ (01.10.2026, просьба Анатолия «чуть увеличить
   интерактивы»). Замер на 406 темах при 1440px: у 75 % чертёж упирается в
   ширину панели (630px), а не в высоту экрана — значит, крупнее он станет
   только от ширины. От 1280px правой половине отдаётся 55 %: колонка текста
   остаётся ~55 знаков в строке при кегле 20px. Уже — пополам, как было. */
@media (min-width: 1280px) {
  .topic-body { grid-template-columns: minmax(0, .9fr) minmax(0, 1.1fr); }
}

/* Невидимая мерка высоты, по которой 406 тем подгоняют чертёж под экран
   (segFitBox в скриптах страниц: «экран − 170px»). Запас был с лихвой:
   панели при нём занимали 732 из 804px высоты. −120 отдаёт чертежу ещё
   50px, панели по-прежнему влезают (замер fitexp: выше экрана 3 из 399
   против 2). !important — высоту мерке ставит скрипт инлайн-стилем. */
.seg-fit-box { height: max(320px, calc(100vh - 120px)) !important; }
/* ...и ширина мерки — ширина СОДЕРЖИМОГО панели (02.10.2026). Мерка стоит
   position: absolute; width: 100%, а у ячейки .seg-viz не было своего
   position — мерка мерила всё окно (1440px), скрипты 406 тем ставили чертежу
   ширину 776px в панели 688, высоту — от 776. CSS ужимал ширину, высота
   оставалась: чертёж выше нужного, рисунок сдвинут, панель длиннее экрана.
   Поля мерки = рамка панели + поля .viz-inner (+1px на округление дробной
   ширины ячейки): скрипты страниц вычитают
   padding мерки из её ширины, как вычитали поля прежней колонки. */
.seg-viz { position: relative; }
.seg-viz > .seg-fit-box {
  box-sizing: border-box;
  padding-inline: calc(var(--sp-5) + 2px) !important;
}

.seg-group {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-10);
}
.topic-seg { min-width: 0; }
.topic-seg > * + * { margin-top: var(--sp-4); }
.topic-seg > h2:first-child { margin-top: 0; }
/* Внешние поля врезок (канон: .mistake/.memo — 20px сверху и снизу) на краях
   сегмента не схлопываются с отбивкой группы — группа flex-колонка, — а
   прибавляются к ней: 40 + 20 = 60px дыры (02.10.2026). Край сегмента —
   это край, отбивку задаёт группа. */
.topic-seg > :first-child { margin-top: 0; }
.topic-seg > :last-child { margin-bottom: 0; }
/* Сегмент без своего заголовка — продолжение предыдущего (одиночная «Частая
   ошибка», «Запомни», «Из жизни»: 655 таких на 330 темах). Отбивка раздела
   (40px) отрывала его от текста, к которому он относится; шаг внутри раздела
   — 16px, как между блоками одного сегмента. */
.seg-group > .topic-seg + .topic-seg:not(:has(> :is(h2, h3):first-child)) {
  margin-top: calc(var(--sp-4) - var(--sp-10));
}

/* Шапка темы живёт внутри первой группы, а не отдельной полосой сверху:
   иначе правая половина экрана пустует от крошек до первого визуала. */
.topic-head > * + * { margin-top: var(--sp-3); }

/* ── ВИЗУАЛ ПО ВЫСОТЕ ГРУППЫ ──────────────────────────────────────────────
   Ячейка тянется на всю высоту ряда, визуал в ней липкий. Потолок — высота
   экрана: интерактив обязан помещаться целиком, прокрутки внутри блока
   быть не должно. */
/* РАЗНЫЕ ВЕЩИ: высота ЯЧЕЙКИ и высота ПАНЕЛИ.
   Ячейка обязана быть растянута на всю группу (align-self: stretch) — иначе
   панели негде липнуть: sticky живёт только в пределах своего родителя, и при
   сжатой до содержимого ячейке панель просто уезжает вверх под шапку (проверено
   12.09.2026: первая строка интерактива оказалась срезана шапкой).
   А вот САМА панель растягиваться не должна. Первым заходом здесь стоял
   display: flex, и он растягивал панель по высоте ячейки: у короткого
   интерактива под ним оставалось до 600px пустоты. display: block оставляет
   панели высоту её содержимого, сохраняя ячейке полную высоту группы. */
.seg-viz {
  min-width: 0;
  align-self: stretch;
  display: block;
}
.seg-viz > .viz-block, .seg-viz > .viz-stack {
  position: sticky;
  top: calc(var(--hdr-real, var(--hdr, 64px)) + var(--sp-4));
  width: 100%;
  max-width: none;
  /* Канон даёт блоку margin: 24px сверху и снизу. В старой раскладке это был
     отступ между блоками колонки, здесь же нижний margin съедает 24px хода
     липкости: панель отрывается от верха и уходит под шапку за 24px до конца
     группы, хотя место ещё есть. Верхний — отбивает панель от заголовка. */
  margin: 0;
  /* Потолок — высота экрана: интерактив обязан быть виден целиком,
     прокрутки внутри панели нет. */
  max-height: calc(100vh - var(--hdr-real, var(--hdr, 64px)) - var(--sp-8));
  overflow: visible;
}

/* ПАНЕЛЬ ВЫШЕ ЭКРАНА ЛИПКОЙ НЕ БЫВАЕТ.
   У четырёх тем интерактив не помещается в экран (у «Задач на совместную
   работу» — 1226px при экране 960). Липкая панель с потолком по высоте экрана
   в этом случае молча обрезает низ: у .viz-block стоит overflow: hidden, и
   ученик не видит ни нижней части чертежа, ни того, что она есть. Прокрутку
   внутри панели правила проекта запрещают — значит такая панель просто
   перестаёт быть липкой и едет вместе с текстом, целиком.
   Класс ставит paper.js, потому что «выше экрана» — это замер, а не свойство
   разметки: высота зависит от кегля, темы и ширины окна. */
.seg-viz > .viz-block.is-tall, .seg-viz > .viz-stack.is-tall {
  position: static;
  max-height: none;
}

/* ОДИН ИНТЕРАКТИВ НА ВСЮ ТЕМУ — ПО ЦЕНТРУ СВОЕЙ ПОЛОВИНЫ ЭКРАНА.
   У 451 темы из 511 справа ровно одна панель. Прилипшая к шапке, она висела
   у верхнего края, а под ней пустовала половина экрана. Такая панель липнет
   не к шапке, а так, чтобы её центр стоял в центре видимой области под
   шапкой (решение Анатолия 26.09.2026). Где панелей больше одной — они
   сменяют друг друга по группам, и верхний край остаётся общей опорой.
   Отступ — замер высоты панели, поэтому его считает paper.js (--solo-top)
   и пересчитывает, когда панель меняет высоту (переключение сцены). */
.seg-viz > .viz-block.is-solo {
  top: var(--solo-top, calc(var(--hdr-real, var(--hdr, 64px)) + var(--sp-4)));
}

/* Стек панелей: когда панелей в группе больше одной, они идут друг под другом
   и липнут вместе. Такой стек появляется там, где панелей больше, чем
   смысловых блоков, и лишней иначе досталась бы пустая группа. */
.viz-stack { display: flex; flex-direction: column; gap: var(--sp-4); }
.viz-stack > .viz-block { margin: 0; }

/* Единый левый край внутри блоков группы: без этого у заголовка, врезки
   и карточки получаются разные вертикали и колонка выглядит ступеньками. */
.topic-seg > .card, .topic-seg > .sheet,
.topic-seg > .definition, .topic-seg > .highlight,
.topic-seg > .memo, .topic-seg > .mistake {
  padding: var(--sp-5) var(--sp-6);
}

/* ── ЗАГОЛОВОК ПАНЕЛИ ─────────────────────────────────────────────────────
   Раньше у интерактива был ряд вкладок, и ученик видел, что именно перед ним:
   «Решето Эратосфена», «НОД по разложению». Вкладки распущены по группам, но
   имя панели обязано остаться: на него ссылается сам текст темы — «пройди
   по шагам в блоке „Решето Эратосфена“ справа» (65 таких мест в 22 темах).
   Без имени ссылка указывала бы в никуда. */
.viz-title {
  margin: 0 0 var(--sp-3);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--border);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  font-weight: var(--fw-semi);
  letter-spacing: .02em;
  color: var(--text-strong);
}

/* ── ШАГИ РАЗБОРА В ТЕКСТЕ ВИДНЫ СРАЗУ ────────────────────────────────────
   Самая дорогая находка раскатки (независимая проверка 12.09.2026, тема
   «Степень числа»): в фундаменте .proof-step объявлен как появляющийся —
   opacity: 0 плюс сдвиг, а показывает его класс .step-visible, который
   ставит JS степпера. В панели визуала это верно: шаги там открываются по
   одному. Но в ТЕКСТЕ темы шаги — обычный разбор примера, и показывать их
   некому: на 55 страницах из 94 класса .step-visible в разметке нет вовсе.
   Старый слой держал их видимыми (display: flex без opacity), поэтому при
   переводе на фундамент разбор молча исчезал: у ученика на месте трёх шагов
   примера и двух правил НОД/НОК оставались пустые прогалы по 170px.

   Ни один датчик этого не увидел: разметка на месте, текст на месте, ошибок
   нет — просто прозрачно. Поэтому проверка видимости добавлена в
   check_seg_pages.py отдельным пунктом. */
.topic-seg .proof-step,
.topic-seg .proof-group,
.topic-seg .proof-group-title,
/* Та же беда, найденная 14.09.2026 при переводе геометрии: страницы 7–9
   классов объявляют появляющимися ещё и врезки —
   `.definition, .highlight, .memo, .proof-step { opacity: 0 }` одной
   строкой, — а показывал их наблюдатель, которого в новой раскладке нет.
   На 45 страницах формулировки теорем стояли на месте, занимали высоту и
   были невидимы. Правило снимает прозрачность у врезок В ТЕКСТЕ темы;
   в панели визуала появление по-прежнему работает. */
.topic-seg .definition,
.topic-seg .highlight,
.topic-seg .memo,
.topic-seg .mistake {
  opacity: 1;
  transform: none;
}

/* ── ОДИН СПОСОБ ВЫКЛЮЧКИ НА ВСЮ КОЛОНКУ ──────────────────────────────────
   Канон выключает по формату (justify) только .card p, .card li и врезки, и
   только от 1200px. В сегментной раскладке карточек-обёрток больше нет, и на
   широком экране выходил разнобой в одной колонке: врезка набрана по формату
   с растянутыми пробелами, соседний абзац — по левому краю. Разнобой виден
   даже тому, кто не знает слова «выключка». Набираем всё по левому краю, как
   в эталоне: в колонке ~700px русский текст без переносов даёт при выключке
   по формату заметные «реки». */
.topic-seg p, .topic-seg li,
.topic-seg .definition, .topic-seg .highlight,
.topic-seg .memo, .topic-seg .mistake {
  text-align: left;
  hyphens: manual;
  -webkit-hyphens: manual;
}

/* ── ДЛИННАЯ ФОРМУЛА В ПАНЕЛИ ──────────────────────────────────────────────
   Формулы на страницах теории набраны с white-space: nowrap — перенести их
   нельзя, и на узком экране длинная формула уходит за оба края панели сразу
   (замер 12.09.2026: «(8+9)·2−5·5+360:6:10» при ширине экрана 375 имела
   правый край 441, то есть хвост и начало были обрезаны молча — прокрутки
   у страницы нет). Поэтому такая формула получает собственное окно прокрутки.

   text-align: center здесь снимается не из вкусовых соображений: у блока с
   прокруткой центрирование отрезает НАЧАЛО строки, и первым, чего лишается
   ученик, оказывается левый край выражения — там, где стоит первое действие. */
.viz-block .expr-display {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}
.viz-block .expr-display > .katex { display: inline-block; }

/* Ячейка грида по умолчанию не уже своего содержимого (min-width: auto), и
   длинная формула распирала панель изнутри: замер на «Хотя бы один» — .viz-inner
   595px при самой панели 311px, формула уходила за экран, а её собственная
   прокрутка не включалась, потому что распираемому блоку нечего прокручивать.
   min-width: 0 возвращает панели право сжать содержимое до своей ширины. */
.viz-block > * { min-width: 0; }

/* Чертёж не шире своей панели. Прежде это держали сами страницы правилом
   `.interactive-col svg, .interactive-col canvas { max-width: 100%; width: 100% }`
   (193 темы алгебры) — колонки .interactive-col в сегментной раскладке нет,
   правило мёртвое и при переводе снимается, а замены в слое не было. Итог:
   SVG брал естественную ширину из viewBox (449px на экране 375), распирал
   панель изнутри и вылезал за край экрана вместе с рядами кнопок под ним. */
.viz-block :is(svg, canvas) { max-width: 100%; }

/* Обёртка панели не шире самой панели. Она блочная и внутри flex-колонки
   берёт ширину по содержимому: SVG объявляет `width: 100%`, ширину считать
   не от чего, и браузер берёт естественную из viewBox — 465px обёртки при
   панели 335 на экране 375. Ряды кнопок под чертежом уезжали вместе с ней. */
.viz-block > .viz-inner { width: 100%; }

/* Ряд вкладок внутри панели. Канон центрирует его (justify-content: center),
   фундамент даёт ему прокрутку (overflow-x: auto) — вместе они прячут первую
   кнопку: при переполнении центровка сдвигает ряд влево за начало полосы, а
   прокрутка меньше нуля не бывает. Замер 12.09.2026, «Понятие отношения» на
   375px: ряд 414px в блоке 267px, кнопка «Разностное» на x = −90, недоступна.
   Ширина ряда и так по содержимому (fit-content), центровать внутри нечего. */
.viz-block .tab-row { justify-content: flex-start; }
/* А сам ряд — по центру панели, полями (02.10.2026, «интерактивы уехали
   относительно центра»): прижатый влево ряд над центрованным чертежом стоял
   на 120px левее оси панели у 300+ тем. Поля, в отличие от justify-content,
   кнопку не прячут: не влезающий ряд берёт всю ширину, поля схлопываются
   в ноль, и прокрутка начинается с первой кнопки. */
.viz-block .tab-row { margin-inline: auto; }
/* Не влезающий ряд переносится, а не прячет вкладки за прокруткой — как на
   телефоне. На 1024px у «Задач на построение» рядом с колонкой зума
   «Задача 4» уезжала в прокрутку, и о ней можно было не узнать. */
.viz-block .tab-row { flex-wrap: wrap; overflow: visible; justify-content: center; }
/* Всё, что уже панели, — по оси панели (02.10.2026). Прежняя колонка
   интерактива была flex-колонкой с align-items: center, и страницы писались
   под неё: ползунки max-width 400px, плашки inline-flex «перпендикулярны»,
   формулы max-width 440px. В блочной .viz-inner они легли к левому краю под
   центрованным чертежом (16 тем, сдвиг до 300px). Блоки центрируются полями;
   строчные — выравниванием строк обёртки, а у самих детей оно возвращается
   к началу с нулевым весом (:where) — их собственные правила сильнее. */
.seg-viz .viz-block > .viz-inner { text-align: center; }
:where(.seg-viz .viz-block > .viz-inner > *) { text-align: start; }
html .topic-body .seg-viz .viz-block > .viz-inner > :not(.zoom-controls) { margin-inline: auto; }

/* ── ТЕЛЕФОН ──────────────────────────────────────────────────────────────
   Один столбец, визуал встаёт после текста своей группы: прочитал мысль —
   потрогал. Липкость снимается: на телефоне она съедает пол-экрана. */
@media (max-width: 900px) {
  .topic-body { grid-template-columns: minmax(0, 1fr); gap: var(--sp-8); }
  .seg-viz { display: block; align-self: auto; }
  .seg-viz > .viz-block { position: static; max-height: none; }
  /* Кегль формулы в панели привязан к ширине экрана: сначала уменьшаем
     набор и только потом отдаём остаток прокрутке. */
  .viz-block .expr-display { font-size: clamp(.95rem, 4.4vw, 1.3rem); }

  /* ── ДЛИННАЯ ФОРМУЛА В ТЕКСТЕ ТЕМЫ ──────────────────────────────────────
     Формула набрана с white-space: nowrap и перенестись не может, а ширина
     экрана телефона её не вмещает: замер 12.09.2026 на 26 страницах из 44 —
     «a·0,1 = a·1/10 = a:10 …» имела правый край 512 при ширине экрана 375.
     Страница при этом горизонтально не прокручивается, поэтому хвост
     формулы пропадал молча — ученик видел обрывок и не знал об этом.

     Уменьшать кегль до влезания нельзя: такие формулы ушли бы в нечитаемые
     8–9px. Поэтому формула получает СВОЙ горизонтальный сдвиг: inline-block
     с overflow-x делает её отдельной прокручиваемой полосой внутри строки,
     не трогая ни абзац, ни страницу. vertical-align: bottom держит её на
     строке: у элемента с прокруткой базовая линия иначе уезжает вниз. */
  .topic-seg .katex {
    display: inline-block;
    max-width: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    vertical-align: bottom;
  }

  /* Формула в заголовке — строчная. Полоса прокрутки ей не нужна (формулы
     заголовков короткие), а inline-block с vertical-align: bottom поднимал её
     над строкой: KaTeX в заголовке 28px при кегле 23px, и прижатый к низу
     бокс вылезал вверх — «соотношения a = b·c» на скрине Анатолия 13.09.2026. */
  .topic-seg :is(h1, h2, h3, h4) .katex {
    display: inline;
    max-width: none;
    overflow: visible;
    vertical-align: baseline;
  }

  /* ── ТАБЛИЦА ШИРЕ ЭКРАНА ────────────────────────────────────────────────
     Тот же дефект, но формула тут ни при чём: замер 12.09.2026 на шестом
     классе — таблица «Русский язык / Логический язык» 398px, «Произведение /
     множитель» 516px при колонке текста 311px. Внешним уходил за край сам
     table, и прокрутка формулы внутри ячейки его не спасала. Таблица
     получает свою полосу; max-content держит узкую таблицу по содержимому,
     иначе блок растянулся бы на всю колонку, а рамка — вместе с ним. */
  .topic-seg table {
    display: block;
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    /* Скрытый узел KaTeX .katex-mathml (position: absolute) без позиционированного
       предка раскладывается от корня и раздувает ширину страницы мимо прокрутки
       таблицы: tema50 — 379 при 375 после воздуха между колонками (13.09.2026). */
    position: relative;
  }

  /* ── ФОРМУЛА В ПАНЕЛИ ШИРЕ ЭКРАНА ──────────────────────────────────────
     В каноне .formula-box намеренно не прокручивается: «распирающим страницу
     он ни разу не оказался». На шестом классе оказался — замер 12.09.2026:
     «AB — часть прямой, ограниченная точками A и B» 473px в .formula-box
     шириной 269px, «(+5) − (−3) = (+5) + (+3) = +8» 398px в .formula-line.
     Формулу пишет скрипт, перенестись она не может. Тот же приём, что у
     формулы в тексте: своя полоса прокрутки у самой формулы, а не у блока. */
  /* ── КНОПКИ ШАГОВ ШИРЕ ЭКРАНА ───────────────────────────────────────────
     Замер 12.09.2026 (датчик научили смотреть кнопки): «Показать недостающий
     множитель» 319px в ряду 269px — .step-btn набран без переноса, а ряд
     .step-row в одну строку и по центру, поэтому соседняя «Сбросить» уезжала
     за ЛЕВЫЙ край. Ряд переносится, длинная подпись — внутри кнопки. */
  .viz-block .step-row { flex-wrap: wrap; }
  .viz-block .step-btn { white-space: normal; max-width: 100%; }

  /* Список классов протёк: локальные блоки страниц (.phrase-answer в «Перевод
     фраз») в него не входят — замер 13.09.2026, «0+1+…+9» на 89px за краем
     панели. Общее правило на любую формулу панели сдвигало соседние на 3–9px
     на 25 темах, поэтому paper.js сначала ужимает не влезающую формулу (до 65 %
     кегля) и помечает .k-wide только те, что не влезли и после этого. */
  .viz-block :is(.formula-box, .formula-line, .rule-box) .katex,
  .viz-block .katex.k-wide {
    display: inline-block;
    max-width: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
    vertical-align: bottom;
  }
}
  /* ── НЕСКОЛЬКО ТРЕНАЖЁРОВ НА ОДИН ПАРАГРАФ ─────────────────────────────
     Связь теория → практика не один-к-одному: у 61 темы геометрии тренажёров
     от двух до шести (у «Координат точки и вектора» — шесть). Прежде кнопка
     вела на один, и остальные со страницы были недостижимы вовсе. */
  .v2 .pr-list { display: flex; flex-direction: column; gap: 2px; margin-top: var(--sp-4); }
  .v2 .pr-item {
    display: flex; align-items: center; gap: 14px; padding: 12px 14px;
    border-radius: 10px; text-decoration: none; color: inherit;
    border: 1px solid transparent;
  }
  .v2 .pr-item:hover { background: var(--glass-bg); border-color: var(--glass-border); }
  /* Главная тема параграфа — та, чьё название ближе к названию темы. */
  .v2 .pr-item.is-main { border-color: var(--accent); background: var(--glass-bg); }
  .v2 .pr-num {
    flex: none; width: 26px; height: 26px; border-radius: 50%;
    display: grid; place-items: center; font-size: .78rem;
    background: var(--glass-border); color: var(--text-soft);
  }
  /* Цифра на акценте красится фоном страницы, а не белым: акцент светлый в
     тёмной теме (#e8e5de) — белая цифра на нём сливалась и была почти не
     видна. --bg контрастен акценту в обеих темах по построению палитры. */
  .v2 .pr-item.is-main .pr-num { background: var(--accent); color: var(--bg); }
  .v2 .pr-name { flex: 1; min-width: 0; font-weight: 500; }
  .v2 .pr-tag { flex: none; font-size: .78rem; color: var(--text-soft); }
  .v2 .pr-go { flex: none; opacity: .5 }
  /* Телефон: строка в одну линию не помещается — замер показал, что подпись
     уровней и стрелка вылезают за карточку до 55 px. Название переносится,
     подпись уходит под него, стрелка снимается. */
  @media (max-width: 560px) {
    .v2 .pr-item { flex-wrap: wrap; row-gap: 2px; padding: 10px 12px; }
    /* Название занимает остаток строки и переносится ВНУТРИ себя. При
       `flex: 1 1 auto` длинное название («Теорема, обратная теореме
       Пифагора») уходило на новую строку целиком, а номер оставался
       висеть на предыдущей. */
    .v2 .pr-name { flex: 1 1 0; }
    .v2 .pr-tag { flex: 1 0 100%; padding-left: 40px; font-size: .74rem; }
    .v2 .pr-go { display: none; }
  }


/* ── «ТЕЛЕФОН v2» ──────────────────────────────────────────────────────────
   Работает только под классом .v2 — его ставит paper.js (и /assets/phone-v2.js,
   который paper.js подгружает) на всех страницах с theory-seg.css; ?v2=0 снимает.
   Пилот на 10 темах — 13.09.2026 днём, раскатка на все 105 — 13.09.2026 ночью.
   Замер 13.09.2026, 105 тем на 375px: фраза «потяни / переключи» в 3,6 экрана
   (медиана) от панели; поля 32+32 и 20+20 оставляли чертежу 269px из 375,
   подписи 5–7px; 19 панелей выше экрана, у 24 тем вкладки уезжали вбок.
   Решения Анатолия: весь набор, подписи от 12px, сначала 10 тем. */
@media (max-width: 900px) {
  /* Панель — врезка в колонке текста, оформленная как «Из жизни»: рамка 1px,
     полоса слева 3px, тот же радиус. Отзыв Анатолия 13.09.2026 с телефона:
     панель во всю ширину экрана выпадала из оформления страницы. Цвет полосы —
     чернила (фирменный), а не синий «Из жизни»: панель не должна читаться как
     пример. Чертежу остаётся 335 − 4 − 2·8 ≈ 315px (во всю ширину было 359,
     до v2 — 269). overflow: clip — у подписи панели свой фон, без обрезки он
     вылезал бы за скруглённые углы; clip, а не hidden: прокрутки не заводит. */
  .v2 .topic-page { padding-inline: 20px; }
  .v2 .seg-viz .viz-block {
    border: 1px solid var(--border-strong);
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-md);
    overflow: clip;
  }
  .v2 .seg-viz .viz-inner { padding-inline: 8px; }
  /* мерка ширины — по тем же полям, что у панели на телефоне */
  .v2 .seg-viz > .seg-fit-box { padding-inline: 10px !important; }

  /* ── РИТМ ПО ВЕРТИКАЛИ: ТРИ СТУПЕНИ ─────────────────────────────────────
     Замер 13.09.2026, 10 тем пилота на 375px: между соседними блоками
     встречалось 23 разных отступа, от 10 до 84px. Причины:
     · поля врезок и панели не схлопываются через край ячейки грида и
       прибавляются к зазору между разделами: 40 + 20 = 60, 40 + 24 = 64;
     · раздел без заголовка, начатый врезкой: 20 + 40 + 20 = 80;
     · зазор между группами 32, между разделами внутри группы 40;
     · у шапки 18 (поле .prereq) + 40 = 58.
     Ступени: 16 — абзац за абзацем, 24 — врезка, таблица, панель,
     40 — новый раздел и блоки после теории. */
  .v2 .topic-body, .v2 .seg-group { row-gap: 40px; }
  .v2 .topic-seg > :is(p, ul, ol) { margin-block: 16px 0; }
  .v2 .topic-seg > :is(.definition, .highlight, .memo, .mistake, .callout, .seg-viz, table) {
    margin-block: 24px;
  }
  /* Скрытое доказательство (шаги ещё не открыты) — пустой блок со своим полем. */
  .v2 .topic-seg > div:has(> .proof-step):not(:has(> .step-visible)) { margin-block: 0; }
  /* Края раздела — после ступеней: специфичность у них та же (0,3,0), побеждает порядок. */
  .v2 .topic-seg > :first-child { margin-top: 0; }
  .v2 .topic-seg > :last-child { margin-bottom: 0; }
  .v2 .topic-head .prereq { margin-bottom: 0; }
  /* Панель, которой не нашлось места в тексте, остаётся после группы —
     прижимаем её к своему тексту на ступень врезки: 40 − 16 = 24. */
  .v2 .topic-body > .seg-viz { margin-top: -16px; }
  .v2 .topic-page > .cta-row { margin-block: 40px 0; }
  .v2 .topic-page > .exam-link { margin-top: 24px; }
  .v2 .topic-page > .topic-nav { margin-top: 40px; }

  /* ── ТЕКСТ И ФОРМУЛЫ В УЗКОЙ КОЛОНКЕ ───────────────────────────────────
     Замер 13.09.2026, 105 тем на 375px (tools\redesign\phone\measure_text.py):
     · все 3778 формул в тексте были полосами со своей прокруткой, хотя
       прокручивать нужно было 143. У полосы (inline-block) браузер вправе
       перенести строку вплотную к ней — 101 знак на 54 темах отрывался:
       «такое число c» ↵ «, чтобы…», «тоже пара (» ↵ «3·4=12)»;
     · 405 рваных строк (заполнены < 60 %): переносы слов выключены, а в
       колонке ~30 знаков, во врезке с полями 24px — ~26.
     Опыт на тех же 105: переносы слов 405 → 216, поля врезок 16px 405 → 336,
     вместе со строчными формулами — 192 рваных и 27 оторванных.
     Какая формула влезает, знает только скрипт: .v2-fit ставит phone-v2.js,
     он же клеит знак к формуле в .v2-glue. */
  .v2 .topic-seg :is(p, li, td, .definition, .highlight, .memo, .mistake) {
    hyphens: auto; -webkit-hyphens: auto;
  }
  .v2 .topic-seg .katex { hyphens: manual; -webkit-hyphens: manual; }
  .v2 .topic-seg :is(.definition, .highlight, .memo, .mistake) { padding-inline: 16px; }
  .v2 .topic-seg .katex.v2-fit { display: inline; overflow: visible; max-width: none; }
  .v2 .v2-glue { white-space: nowrap; }
  /* Вертикальный чертёж при расширении только вырос бы — держим его долей экрана. */
  .v2 .seg-viz svg { max-height: min(56vh, 440px); }
  /* Вкладки переносятся, а не прячутся за прокруткой ряда. */
  .v2 .viz-block .tab-row {
    flex-wrap: wrap; overflow: visible; width: auto; justify-content: center; gap: 4px;
  }
  .v2 .viz-block .tab-row > .tab-btn { flex-shrink: 1; }
  /* Ровно два ползунка — в два столбца (пара одним столбцом занимала 184px).
     Только ровно два: у «Среднего арифметического» три-четыре ползунка с длинными
     подписями, в узком столбце подпись переносилась и ряд рос с 66 до 116px —
     панель становилась выше, а не ниже (пилот 13.09.2026). */
  .v2 .viz-inner:has(> .slider-row ~ .slider-row):not(:has(> .slider-row ~ .slider-row ~ .slider-row)) > .slider-row {
    display: inline-block; width: calc(50% - 4px); vertical-align: top;
  }
  /* Подпись панели — обычный блок: у 36 тем локальный display:flex рвал текст
     с формулами в колонки. */
  .v2 .seg-viz .viz-caption { display: block; font-size: .85rem; line-height: 1.35; padding-block: 8px; }
  /* Кнопка возврата к тексту после перехода по ссылке к панели. */
  .v2 .v2-back {
    position: fixed; left: 50%; transform: translateX(-50%);
    bottom: calc(16px + env(safe-area-inset-bottom, 0px)); z-index: 60;
    min-height: 44px; padding: 0 20px; border-radius: 22px;
    border: 1px solid var(--border-strong); background: var(--surface);
    color: var(--text-strong); box-shadow: var(--shadow-raised);
    font: inherit; font-size: .95rem; cursor: pointer;
  }
}

@media print {
  .seg-viz, .topic-nav { display: none !important; }
  .topic-body { grid-template-columns: minmax(0, 1fr); }
}

/* ─── 5/5 ОРГАНЫ УПРАВЛЕНИЯ (sup-controls.css) ──────────────────── */
/* ============================================================================
   СУПРЕМУМ · sup-controls.css — органы управления интерактива темы
   ============================================================================
   Кнопки, переключатели сцен, ползунки и зум внутри страницы темы. Источник
   для theory-seg.css: сборщик (tools\design_system\bridge_seg.py) дописывает
   этот файл пятым слоем, после раскладки. Отдельно не подключается.
   Поведение (бегунок переключателя, капля чернил, подсказка значения у
   ползунка) — в sup-controls.js, его подгружает paper.js.

   ПОЧЕМУ ОБЩИЙ СЛОЙ, А НЕ ПРАВКА СТРАНИЦ. У 511 тем органы управления
   набраны своими классами — переписью 01.10.2026 насчитано 45 разных
   (.divisor-btn, .num-chip, .scene-tab, .mag-btn …) и 188 кнопок, которые
   страницы создают скриптом. Каждый класс оформлен локальным <style> на
   старых мостовых именах (--tile-bg, --glass-border), отсюда разнобой:
   на одной теме вкладки — серые плашки, на соседней — три одинаковые
   чёрные кнопки, где не видно, какая выбрана («Скалярное произведение
   в пространстве»).

   СПЕЦИФИЧНОСТЬ. Локальный <style> страницы стоит ПОСЛЕ ссылки на этот
   слой, поэтому при равной специфичности побеждает он. Основа здесь —
   (0,1,2): «html .topic-body button». Она перебивает одноклассовые правила
   страниц (.divisor-btn — 0,1,0) и уступает составным (.tabs .btn.active —
   0,3,0): там страница выражает СОСТОЯНИЕ, и отнимать его нельзя.
   Кнопки с цветом-смыслом (.correct, .wrong …) исключены через :where —
   исключение не добавляет специфичности.

   ЯЗЫК. Тушь и бумага: заливка — чернила (--accent), бумага — --solid,
   а фирменная красная риска из знака «sup» (--pen) работает пером учителя:
   ею подчёркивается кнопка под курсором, ею помечен центр ручки ползунка
   и рамка фокуса. Других цветов слой не вводит.
   ============================================================================ */

html {
  --sx-spring: cubic-bezier(.34, 1.4, .64, 1);
  --sx-glide: cubic-bezier(.22, 1, .36, 1);
  /* rem здесь 20px (база страниц темы): 2.3rem = 46px, тач-цель 44 с запасом */
  --sx-h: 2.3rem;
  --sx-fs: .9rem;
}

/* ── ОСНОВА: КОНТУРНАЯ КНОПКА ─────────────────────────────────────────────
   Неизвестная кнопка по умолчанию контурная: среди сорока пяти классов
   большинство — выбор варианта, а не главное действие. Залить всё чернилами
   значит потерять разницу между «выбрать» и «сделать».

   РАСКЛАДКА — НУЛЕВОЙ СПЕЦИФИЧНОСТЬЮ. display, выравнивание и поля живут
   в :where() и уступают любому правилу страницы: у 22 тем .step-btn объявлена
   display:block — кнопкой во всю ширину, — а прежняя версия слоя ставила ей
   inline-flex (независимое ревью 01.10.2026). Облик (рамка, фон, кегль) —
   со специфичностью (0,1,2): он и есть смысл слоя. */
:where(html .topic-body button:not(.correct, .wrong, .ok, .bad, .no, .yes, .right, .err, .good, .neg, .pos, .chip-neg, .sx-skip, [data-sx-skip])) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45em;
  padding: 0 1.05rem;
}
:where(html .topic-body button)[hidden] { display: none; }
html .topic-body button:where(:not(.correct, .wrong, .ok, .bad, .no, .yes, .right, .err, .good, .neg, .pos, .chip-neg, .sx-skip, [data-sx-skip])) {
  position: relative;
  isolation: isolate;
  min-height: var(--sx-h);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-md);
  background-color: var(--solid);
  color: var(--text-strong);
  font-family: var(--font-body);
  font-size: var(--sx-fs);
  font-weight: var(--fw-medium);
  line-height: 1.2;
  letter-spacing: .005em;
  cursor: pointer;
  box-shadow: 0 1px 0 var(--hair), 0 1px 2px var(--sh);
  transition:
    transform .32s var(--sx-spring),
    background-color .18s var(--ease-out),
    border-color .18s var(--ease-out),
    color .18s var(--ease-out),
    box-shadow .18s var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}

/* Перо под надписью: та же красная риска, что в знаке «sup». Растёт из
   центра на наведении — ответ «эта кнопка нажмётся», без подпрыгивания. */
html .topic-body button:where(:not(.correct, .wrong, .ok, .bad, .no, .yes, .right, .err, .good, .neg, .pos, .chip-neg, .sx-skip, [data-sx-skip], .zoom-btn))::before {
  content: "";
  position: absolute;
  left: 22%; right: 22%;
  bottom: 5px;
  height: 2px;
  border-radius: 2px;
  background: var(--pen);
  transform: scaleX(0);
  transform-origin: 50% 50%;
  opacity: 0;
  transition: transform .36s var(--sx-glide), opacity .2s var(--ease-out);
  pointer-events: none;
  z-index: -1;
}
/* Наведение — только у контурной невыбранной кнопки. Выбранная и главная
   исключены через :where (без прибавки специфичности): иначе наведение
   перекрашивало выбранную в бумагу при светлом тексте — нечитаемо, а у
   главной сбрасывало блик. Меняется только background-color, не весь фон. */
@media (hover: hover) {
  html .topic-body button:where(:not(.correct, .wrong, .ok, .bad, .no, .yes, .right, .err, .good, .neg, .pos, .chip-neg, .sx-skip, [data-sx-skip], .active, .is-active, .selected, .on, .chip-active, .hi-active, [aria-pressed="true"], [aria-selected="true"], .step-btn, .btn-primary, .btn.primary, .primary, .lab-stepper-btn, .stepper-btn, [data-sx="primary"])):hover:not(:disabled) {
    border-color: var(--accent);
    background-color: var(--n-0);
    box-shadow: 0 1px 0 var(--hair), 0 4px 14px -6px var(--sh);
  }
  html .topic-body button:where(:not(.correct, .wrong, .ok, .bad, .no, .yes, .right, .err, .good, .neg, .pos, .chip-neg, .sx-skip, [data-sx-skip], .zoom-btn)):hover:not(:disabled)::before {
    transform: scaleX(1);
    opacity: 1;
  }
}

/* Нажатие — физический отклик: кнопка вдавливается и пружинит обратно */
html .topic-body button:where(:not(.sx-skip, [data-sx-skip])):active:not(:disabled) {
  transform: scale(.955) translateY(1px);
  transition-duration: .08s;
}

/* Фокус с клавиатуры — пером, с отступом от края (как кольцо shadcn) */
html .topic-body button:focus-visible {
  outline: 2px solid var(--pen);
  outline-offset: 2px;
}

html .topic-body button:disabled {
  opacity: .42;
  cursor: not-allowed;
  box-shadow: none;
}

/* ── ГЛАВНОЕ ДЕЙСТВИЕ: ЧЕРНИЛА ────────────────────────────────────────────
   «Показать шаг», «Проверить», «Запустить». По наведению по кнопке проходит
   блик — как по свежей туши; сама кнопка не двигается. Отработавшая
   (:disabled) исключена — у неё своё правило ниже. */
html .topic-body :is(.step-btn, .btn-primary, .btn.primary, .primary, .lab-stepper-btn, .stepper-btn, [data-sx="primary"]):where(:not(.sx-seg > *, :disabled)) {
  border-color: var(--accent);
  background-color: var(--accent);
  background-image: linear-gradient(105deg,
    transparent 0 38%,
    color-mix(in srgb, var(--accent-fg) 22%, transparent) 46%,
    transparent 54% 100%);
  background-size: 260% 100%;
  background-position: 120% 0;
  background-repeat: no-repeat;
  color: var(--accent-fg);
  font-weight: var(--fw-semi);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--accent-fg) 14%, transparent),
    0 1px 2px var(--sh),
    0 6px 16px -10px var(--accent);
  transition:
    transform .32s var(--sx-spring),
    background-position .7s var(--sx-glide),
    background-color .18s var(--ease-out),
    box-shadow .18s var(--ease-out);
}
html .topic-body :is(.step-btn, .btn-primary, .btn.primary, .primary, .lab-stepper-btn, .stepper-btn, [data-sx="primary"]):where(:not(.sx-seg > *))::before {
  background: var(--pen);
  bottom: 4px;
}
@media (hover: hover) {
  html .topic-body :is(.step-btn, .btn-primary, .btn.primary, .primary, .lab-stepper-btn, .stepper-btn, [data-sx="primary"]):where(:not(.sx-seg > *)):hover:not(:disabled) {
    background-color: var(--accent-hover);
    border-color: var(--accent-hover);
    background-position: -20% 0;
    box-shadow:
      inset 0 1px 0 color-mix(in srgb, var(--accent-fg) 18%, transparent),
      0 2px 4px var(--sh),
      0 10px 22px -12px var(--accent);
  }
}
/* «Все шаги показаны» — кнопка своё отработала: не серый призрак, а
   спокойная печать на бумаге с галочкой. */
html .topic-body :is(.step-btn, .lab-stepper-btn, .stepper-btn):disabled {
  opacity: 1;
  background: var(--sunken);
  border-color: var(--border);
  color: var(--text-soft);
  box-shadow: none;
}
html .topic-body :is(.step-btn, .lab-stepper-btn, .stepper-btn):disabled::after {
  content: "";
  display: inline-block;
  width: .45em; height: .8em;
  margin-left: .5em;
  border: solid var(--ok);
  border-width: 0 2px 2px 0;
  transform: translateY(-.12em) rotate(45deg);
  vertical-align: middle;
}

/* ── ВЫБРАННОЕ ВНЕ ГРУППЫ ─────────────────────────────────────────────────
   Плашка-вариант, которая выбрана, а бегунка у её ряда нет (сетка чисел,
   одиночный переключатель): заливка чернилами и перо под надписью.
   Список исключений — тот же, что у основы. */
html .topic-body button:is(.active, .is-active, .selected, .on, .chip-active, .hi-active, [aria-pressed="true"], [aria-selected="true"]):where(:not(.sx-seg > *, .correct, .wrong, .ok, .bad, .no, .yes, .right, .err, .good, .neg, .pos, .chip-neg, .sx-skip, [data-sx-skip])) {
  border-color: var(--accent);
  background-color: var(--accent);
  color: var(--accent-fg);
  font-weight: var(--fw-semi);
}
@media (hover: hover) {
  html .topic-body button:is(.active, .is-active, .selected, .on, .chip-active, .hi-active, [aria-pressed="true"], [aria-selected="true"]):where(:not(.sx-seg > *, .correct, .wrong, .ok, .bad, .no, .yes, .right, .err, .good, .neg, .pos, .chip-neg, .sx-skip, [data-sx-skip])):hover:not(:disabled) {
    background-color: var(--accent-hover);
    border-color: var(--accent-hover);
  }
}
html .topic-body button:is(.active, .is-active, .selected, .on, .chip-active, .hi-active, [aria-pressed="true"], [aria-selected="true"]):where(:not(.sx-seg > *, .zoom-btn, .correct, .wrong, .ok, .bad, .no, .yes, .right, .err, .good, .neg, .pos, .chip-neg, .sx-skip, [data-sx-skip]))::before {
  transform: scaleX(1);
  opacity: 1;
}

/* ── ПЕРЕКЛЮЧАТЕЛЬ СЦЕН С БЕГУНКОМ ────────────────────────────────────────
   Ряд кнопок, где одна выбрана, — это выбор одного из нескольких. Его
   опознаёт sup-controls.js и ставит класс .sx-seg: ряд становится общей
   дорожкой, а под выбранной кнопкой живёт чернильный бегунок, который
   переезжает к новой кнопке — видно, ОТКУДА и КУДА переключилась сцена.
   !important здесь осознанный: класс ставит только наш скрипт, а страницы
   красят выбранную кнопку правилами с id (#tabButtons .step-btn.active) —
   под бегунком закрашенная кнопка его бы закрыла. Свой ::before у
   контейнера ряда страницы не используют (перепись 01.10.2026). */
html .topic-body .sx-seg {
  position: relative;
  isolation: isolate;
  width: fit-content;
  max-width: 100%;
  padding: 3px !important;
  gap: 2px !important;
  border: 1px solid var(--border) !important;
  border-radius: calc(var(--radius-md) + 4px) !important;
  background: var(--sunken) !important;
  box-shadow: inset 0 1px 2px var(--sh);
}
/* Свои ряды страниц стояли по центру растянутого блока (justify-content:
   center) — ужатый до содержимого, ряд центрируется полями. Штатный .tab-row
   с 02.10.2026 тоже по центру (правило в слое раскладки): поля, в отличие
   от justify-content, первую кнопку за край прокрутки не прячут. */
html .topic-body .sx-seg { margin-inline: auto; }
html .topic-body .sx-seg > button {
  min-height: calc(var(--sx-h) - 4px);
  padding: 0 .75rem;
  font-size: var(--fs-sm);
  border-color: transparent !important;
  background: transparent !important;
  box-shadow: none !important;
  color: var(--text-soft) !important;
  font-weight: var(--fw-medium) !important;
  z-index: 1;
  transition: color .25s var(--ease-out), transform .32s var(--sx-spring);
}
/* Подпись сцены — в одну строку: «Лестница / степеней» в две строки
   растягивала бегунок вдвое по высоте. Не влезает ряд — переносится ряд. */
html .topic-body .sx-seg > button { white-space: nowrap; }
html .topic-body .sx-seg > button::before { display: none; }
html .topic-body .sx-seg > button::after { content: none !important; }
@media (hover: hover) {
  /* выбранная — на бегунке, её цвет не трогаем: тёмный текст на тёмных чернилах */
  html .topic-body .sx-seg > button:hover:not(:disabled, .sx-on) { color: var(--text-strong) !important; }
}
html .topic-body .sx-seg > button.sx-on {
  color: var(--accent-fg) !important;
  font-weight: var(--fw-semi) !important;
}
/* Пока бегунок не измерен (первый кадр, или скрипт не загрузился) —
   выбранная кнопка закрашена сама, чтобы выбор был виден всегда. */
html .topic-body .sx-seg:not(.sx-ready) > button.sx-on {
  background: var(--accent) !important;
}
/* Бегунок — псевдоэлемент самого ряда, а не вставленный узел: страницы
   адресуют кнопки по индексу (tabs.children[i]) и по lastElementChild, и
   лишний ребёнок сдвинул бы им счёт. Положение и размер пишет скрипт
   в --sx-tx/--sx-ty/--sx-tw/--sx-th. */
html .topic-body .sx-seg::before {
  content: "";
  position: absolute;
  left: 0; top: 0;
  z-index: 0;
  width: var(--sx-tw, 0px);
  height: var(--sx-th, 0px);
  transform: translate(var(--sx-tx, 0px), var(--sx-ty, 0px));
  border-radius: var(--radius-md);
  background:
    linear-gradient(var(--pen), var(--pen)) 50% calc(100% - 4px) / 36% 2px no-repeat,
    var(--accent);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--accent-fg) 14%, transparent),
    0 2px 6px -1px var(--sh),
    0 8px 18px -12px var(--accent);
  opacity: 0;
  pointer-events: none;
  transition:
    transform .46s var(--sx-spring),
    width .46s var(--sx-glide),
    height .46s var(--sx-glide),
    opacity .2s var(--ease-out);
}
html .topic-body .sx-seg.sx-ready::before { opacity: 1; }
html .topic-body .sx-seg.sx-still::before { transition: opacity .2s var(--ease-out); }

/* ── ЗУМ И СБРОС ВИДА ─────────────────────────────────────────────────────
   Круглые кнопки-инструменты: бумага с тонкой рамкой. Сброс на наведении
   проворачивается — значок сам объясняет, что он делает. */
html .topic-body .zoom-btn {
  width: 2.25rem; height: 2.25rem;
  min-height: 0;
  padding: 0;
  border-radius: 50%;
  font-size: 1.05rem;
  font-weight: var(--fw-semi);
  line-height: 1;
  background: var(--solid);
  backdrop-filter: none;
}
@media (hover: hover) {
  html .topic-body .zoom-btn:hover:not(:disabled) {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-fg);
  }
  html .topic-body .zoom-btn[title*="брос"]:hover:not(:disabled) { transform: rotate(-200deg); }
  html .topic-body .zoom-btn[title*="брос"] { transition: transform .6s var(--sx-glide), background-color .18s, color .18s, border-color .18s; }
}

/* Ряд сцен не заходит под колонку зума. Колонка .zoom-controls стоит в
   правом верхнем углу панели поверх всего, а ряд вкладок прижат влево и
   растёт вправо: у «Медиан, биссектрис, высот» последняя вкладка
   «Гомотетия» уходила под «+» и не нажималась (01.10.2026). Ряд оставляет
   колонке её ширину и, не влезая, прокручивается внутри себя, как и прежде. */
html .topic-body .viz-block:has(.zoom-controls) .tab-row { max-width: calc(100% - 3.1rem); }
/* С 02.10.2026 ряд стоит по центру панели (поля), а колонка зума — справа.
   По центру ряд встаёт, только если влезает мимо колонки с обеих сторон —
   это замер, его делает sup-controls.js (.sx-mid). Иначе — прижат влево. */
/* .seg-viz в селекторе — вес: общее «всё уже панели — по центру» из слоя
   раскладки стоит позже и иначе перебило бы это правило. */
html .topic-body .seg-viz .viz-block:has(.zoom-controls) .tab-row:not(.sx-mid),
html .topic-body .viz-block:has(.zoom-controls) .tab-row:not(.sx-mid) { margin-inline: 0 auto; }

/* ── ПОЛОСА ХОДА ПОСТРОЕНИЯ ───────────────────────────────────────────────
   «Шаг 1 из 5» у задач на построение (8 тем геометрии): заливка была
   градиентом в чужой фиолетовый #a855f7, а дорожка — --glass-border, почти
   белая на бумаге. Дорожка — как у ползунка, заливка — чернила с пером на
   переднем крае. */
html .topic-body .progress-track { background: var(--n-3); box-shadow: inset 0 1px 1px var(--sh); }
html .topic-body .progress-fill {
  background:
    linear-gradient(var(--pen), var(--pen)) 100% 0 / 3px 100% no-repeat,
    var(--accent);
}

/* ── КАПЛЯ ЧЕРНИЛ ─────────────────────────────────────────────────────────
   Нажатие расходится каплей от точки касания — отклик, что нажатие принято,
   даже если сцена отвечает не сразу. Каплю вставляет sup-controls.js. */
.sx-ink {
  position: absolute;
  left: var(--x); top: var(--y);
  width: var(--d); height: var(--d);
  margin: calc(var(--d) / -2) 0 0 calc(var(--d) / -2);
  border-radius: 50%;
  background: currentColor;
  opacity: .22;
  transform: scale(0);
  pointer-events: none;
  z-index: -1;
  animation: sx-ink .62s var(--sx-glide) forwards;
}
@keyframes sx-ink {
  to { transform: scale(1); opacity: 0; }
}
/* Капле нужна рамка кнопки, иначе она выплеснется за края. Обрезает её
   отдельная подложка, а не overflow: hidden у самой кнопки — у .btn-sm
   невидимая тач-зона живёт в ::after и под обрезкой перестала бы ловить палец. */
.sx-ink-clip {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  overflow: hidden;
  pointer-events: none;
  z-index: -1;
}

/* ── ПОЛЗУНОК ─────────────────────────────────────────────────────────────
   Дорожка — тонкая линия, пройденная часть залита чернилами. Ручка — лист
   бумаги в чернильном ободке с красной точкой пера в центре. Под пальцем
   ручка вырастает и показывает значение над собой (sup-controls.js).
   Заливка считается от ЦЕНТРА ручки, а не от края дорожки: --sx-f (0…1)
   ставит скрипт, а 11px — радиус ручки, иначе у краёв заливка обгоняет
   ручку или отстаёт от неё. */
html .topic-body input[type="range"] {
  --sx-f: .5;
  --sx-r: 11px;
  --sx-fill: calc(var(--sx-r) + var(--sx-f) * (100% - 2 * var(--sx-r)));
  -webkit-appearance: none;
  appearance: none;
  min-height: var(--tap, 44px);
  margin: 0;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  accent-color: var(--accent);
  touch-action: pan-y;
}
html .topic-body input[type="range"]:focus { outline: none; }

html .topic-body input[type="range"]::-webkit-slider-runnable-track {
  height: 6px;
  border-radius: 999px;
  background:
    linear-gradient(to right, var(--accent) 0 var(--sx-fill), transparent var(--sx-fill)),
    var(--n-3);
  box-shadow: inset 0 1px 1px var(--sh);
}
html .topic-body input[type="range"]::-moz-range-track {
  height: 6px;
  border-radius: 999px;
  background: var(--n-3);
  box-shadow: inset 0 1px 1px var(--sh);
}
html .topic-body input[type="range"]::-moz-range-progress {
  height: 6px;
  border-radius: 999px;
  background: var(--accent);
}

html .topic-body input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 22px; height: 22px;
  margin-top: -8px;
  border: 2px solid var(--accent);
  border-radius: 50%;
  background: radial-gradient(circle, var(--pen) 0 2.6px, var(--solid) 3.2px);
  box-shadow: 0 1px 3px var(--sh), 0 0 0 0 var(--accent-soft);
  transition: transform .3s var(--sx-spring), box-shadow .25s var(--ease-out);
}
html .topic-body input[type="range"]::-moz-range-thumb {
  width: 18px; height: 18px;
  border: 2px solid var(--accent);
  border-radius: 50%;
  background: radial-gradient(circle, var(--pen) 0 2.6px, var(--solid) 3.2px);
  box-shadow: 0 1px 3px var(--sh), 0 0 0 0 var(--accent-soft);
  transition: transform .3s var(--sx-spring), box-shadow .25s var(--ease-out);
}
@media (hover: hover) {
  html .topic-body input[type="range"]:hover:not(:disabled)::-webkit-slider-thumb {
    box-shadow: 0 1px 3px var(--sh), 0 0 0 7px var(--accent-soft);
  }
  html .topic-body input[type="range"]:hover:not(:disabled)::-moz-range-thumb {
    box-shadow: 0 1px 3px var(--sh), 0 0 0 7px var(--accent-soft);
  }
}
html .topic-body input[type="range"]:active:not(:disabled)::-webkit-slider-thumb,
html .topic-body input[type="range"].sx-drag::-webkit-slider-thumb {
  transform: scale(1.22);
  box-shadow: 0 2px 6px var(--sh), 0 0 0 9px var(--accent-soft);
}
html .topic-body input[type="range"]:active:not(:disabled)::-moz-range-thumb,
html .topic-body input[type="range"].sx-drag::-moz-range-thumb {
  transform: scale(1.22);
  box-shadow: 0 2px 6px var(--sh), 0 0 0 9px var(--accent-soft);
}
html .topic-body input[type="range"]:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 1px 3px var(--sh), 0 0 0 3px var(--solid), 0 0 0 5px var(--pen);
}
html .topic-body input[type="range"]:focus-visible::-moz-range-thumb {
  box-shadow: 0 1px 3px var(--sh), 0 0 0 3px var(--solid), 0 0 0 5px var(--pen);
}
html .topic-body input[type="range"]:disabled { opacity: .45; cursor: not-allowed; }

/* Подпись ползунка «Число a … 45» — строкой НАД дорожкой. Общий слой делает
   .slider-row гибкой строкой, и блок .slider-label сжимался до содержимого:
   значение прилипало к подписи — «Число a45», «Длина на карте2,8см»
   (14 тем 5–6 класса, замер 01.10.2026). */
html .topic-body .slider-row:has(> .slider-label) { flex-wrap: wrap; row-gap: 2px; }
html .topic-body .slider-row > .slider-label { flex: 1 0 100%; gap: 12px; }

/* Значение над ручкой — пока её держат. Живёт в <body>, а не в панели:
   у панели overflow: clip, и подсказка у верхнего края срезалась бы. */
.sx-bubble {
  position: fixed;
  left: 0; top: 0;
  z-index: 70;
  padding: 3px 9px 4px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-fg);
  font-family: var(--font-mono);
  font-size: .82rem;
  font-weight: var(--fw-semi);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 6px 18px -8px var(--sh);
  transform: translate(-50%, -100%) translateY(-14px) scale(.6);
  transform-origin: 50% 100%;
  opacity: 0;
  transition: transform .28s var(--sx-spring), opacity .16s var(--ease-out);
}
.sx-bubble::after {
  content: "";
  position: absolute;
  left: 50%; bottom: -4px;
  width: 8px; height: 8px;
  margin-left: -4px;
  background: inherit;
  border-radius: 1px;
  transform: rotate(45deg);
}
.sx-bubble.is-on {
  transform: translate(-50%, -100%) translateY(-14px) scale(1);
  opacity: 1;
}

/* ── ПОДСВЕТКА «ТЕКСТ ↔ ЧЕРТЁЖ» ───────────────────────────────────────────
   Замер 01.10.2026: 200 тем при наведении на подчёркнутое слово красят его
   фон в #fef3c7, а фигуру — в #f59e0b (жёлтое и оранжевое из чужой палитры).
   В тёмной теме светлый текст ложился на светло-жёлтую плашку и не читался.
   Цвет приходит из скрипта страницы инлайн-стилем и атрибутом, поэтому
   перехват такой: у атрибута SVG нулевая специфичность — его перебивает
   любое правило; инлайн-стиль фона — только !important. Цвет подсветки —
   перо (--pen): оно своё в каждой тональности и теме. */
html .topic-body [fill="#f59e0b" i] { fill: var(--pen); }
html .topic-body [stroke="#f59e0b" i] { stroke: var(--pen); }
html .topic-body .hl[style*="background"] {
  background: color-mix(in srgb, var(--pen) 16%, transparent) !important;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--pen) 16%, transparent);
  border-radius: 3px;
  color: inherit;
}

/* ── ТИХИЙ РЕЖИМ ──────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  html .topic-body button,
  html .topic-body button::before,
  html .topic-body .sx-seg::before,
  html .topic-body input[type="range"]::-webkit-slider-thumb,
  .sx-bubble { transition: none !important; }
  .sx-ink { display: none; }
  html .topic-body button:active:not(:disabled) { transform: none; }
  html .topic-body .zoom-btn:hover { transform: none !important; }
}

/* ── ТЕЛЕФОН ──────────────────────────────────────────────────────────────
   Высота кнопки — тач-цель; ряд-переключатель переносится, и бегунок
   переезжает и по строкам (скрипт меряет обе оси). */
@media (max-width: 900px) {
  /* 2.2rem = 44px — ровно тач-цель. Выше не надо: каждая лишняя строчка
     кнопок отнимает высоту у чертежа, а панель ужимается по чертежу. */
  html { --sx-h: 2.2rem; --sx-fs: .85rem; }
  html .topic-body .sx-seg { flex-wrap: wrap; justify-content: center; }
  html .topic-body .sx-seg > button { padding: 0 .8rem; }
}
