/* ═══════════════════════════════════════════════════════════════════
   ТОКЕНЫ ДИЗАЙНА — «Чертёжный инструмент»
   Кривые — единственный источник цвета на холсте; интерфейс намеренно
   нейтрально-графитовый, с одним смелым малиновым акцентом, который не
   совпадает ни с одним цветом кривой (его нельзя спутать с D/S/MR/MC…).
   Светлая тема — по умолчанию (класс/проектор), тёмная — вечерняя работа.
   ═══════════════════════════════════════════════════════════════════ */
:root {
  color-scheme: light;

  /* ── Палитра кривых (ЗАФИКСИРОВАНА — одинакова в обеих темах) ── */
  --curve-d:     #2F6FED;   /* спрос D                       */
  --curve-s:     #E0563B;   /* предложение S                 */
  --curve-mr:    #8B3FE0;   /* предельный доход MR           */
  --curve-mc:    #119C8A;   /* предельные издержки MC        */
  --curve-tax:   #2E9E44;   /* налог / субсидия / бюджет     */
  --curve-dwl:   #8C8C84;   /* потери общества DWL           */
  --curve-reg:   #B5791F;   /* регулируемая цена (потолок/пол/МРОТ) */
  --curve-ghost: #9AA0A6;   /* «было» (исходное состояние)   */
  --c-warn:      #C2680F;   /* предупреждение                */
  --c-bad:       #D23B3B;   /* дефицит / ошибка              */
  --c-profit:    #2E9E44;   /* прямоугольник прибыли фирмы   */
  /* Б42. Рыночная цена в сцене издержек. Раньше она рисовалась цветом
     регулируемой цены (--curve-reg), а он совпадает с цветом AVC до последнего
     разряда — при том что весь смысл сцены в том, где линия цены пересекает
     AVC. Свой тон, не совпадающий ни с одной кривой издержек: от MC (172°),
     ATC (220°), AVC (36°), AFC (268°) и VC (217°) он отстоит не меньше чем на
     50°. В других сценах цена по-прежнему янтарная: там AVC нет. */
  --c-price:     #B0338C;   /* линия рыночной цены на графике затрат */
  /* Галочка на образце цвета. Светлая в ОБЕИХ темах и не меняется вместе с
     ними: образец залит цветом кривой, а не поверхностью, и «чернила по
     теме» на тёмно-синем образце стали бы невидимы. Читаемость на любом
     образце держит тень под галочкой, а не сам цвет. */
  --sw-check:    #ffffff;   /* галочка на образце цвета (обе темы)  */
  /* ── ПАЛИТРА ГРУПП В «СЛОЖЕНИИ СПРОСОВ» (решение владельца 25.08) ──
     Восемь цветов для кривых-слагаемых. Канонические --curve-d и --curve-s
     в неё НЕ входят: они закреплены за суммарными кривыми, и пока группа
     красилась общей палитрой выбора (её первые два цвета — те же синий и
     оранжевый), «спрос первой группы» был неотличим от «рыночного спроса».

     Палитра — СВОЙ список из восьми тонов, а не ссылки на чужие токены.
     Три из них совпадают с существующими до разряда (--curve-mr, --curve-reg,
     --c-price), остальные пять свои: у трёх пришлось опустить светлоту
     (см. ниже про дорожку ползунка), два тона в палитре продукта отсутствовали.

     ⚠️ ЧИСЛА, ПО КОТОРЫМ ЭТИ ВОСЕМЬ ОТОБРАНЫ (замер 25.08, sum_visual_probe):
     контраст к холсту не ниже 3,25:1 в обеих темах; наименьшее расстояние
     между любыми двумя из десяти цветов холста (восемь групп плюс
     канонические D и S) — 30,5 по CIE Lab, и две самые тесные пары продукт
     уже носит (D × фиолетовый 37,3, S × янтарный 37,9). От малинового акцента
     интерфейса каждый отстоит не меньше чем на 27,6 — столько же, сколько
     давно живущий --c-price.

     ⚠️ СВЕТЛОТА КАЖДОГО ТОНА ДЕРЖИТСЯ НИЖЕ 0,245, И ЭТО НЕ ВКУС.
     Цвет кривой уходит в accent-color её ползунка, а Chromium САМ решает,
     какой краской рисовать незалитую часть дорожки, и переключается на
     тёмную, когда акцент слишком светлый. Порог измерен перебором 25.08:
     при относительной светлоте 0,2487 дорожка ещё светлая, при 0,2545 уже
     тёмная. Первые взятые бирюзовый #119C8A (0,2573), зелёный #2E9E44
     (0,2545) и розовый #BE7878 (0,2574) давали на панели три тёмные дорожки
     из восьми — рядом с пятью светлыми это читалось как разное состояние
     ползунков. Тона опущены до 0,203–0,206; настройками CSS этого не лечится.

     Тона:
       --sum-g1 фиолетовый   #8B3FE0 — совпадает с --curve-mr;
       --sum-g2 бирюзовый    #0F8C7C — тот же тон, что --curve-mc, но темнее;
       --sum-g3 янтарный     #B5791F — совпадает с --curve-reg;
       --sum-g4 зелёный      #2A8F3E — тот же тон, что --curve-tax, но темнее;
       --sum-g5 маджента     #B0338C — совпадает с --c-price;
       --sum-g6 сиреневый    #8A6FB0 — дымчатый родственник фиолетового,
           светлее и вдвое менее насыщенный: рядом читается как «тише»;
       --sum-g7 сланцевый    #666C7E — единственный неяркий тон палитры,
           он держит место спокойного цвета;
       --sum-g8 пыльная роза #B06A6A — приглушённая тёплая розовая, далеко и
           от кирпичного предложения, и от малинового акцента интерфейса.

     ⚠️ ПРИГЛУШЕНИЕ ЖИВЁТ В ТОНЕ, А НЕ В ПРОЗРАЧНОСТИ. В мокапе группы были
     полупрозрачными (0,72). Замер показал, что так нельзя: полупрозрачная
     линия смешивается с холстом, и ни один цвет палитры не удерживает 3:1
     сразу в обеих темах — предел 0,95, а это неотличимо от единицы. Разницу
     в весе несут тон и толщина (1,6 против 3,2), а не прозрачность.
     Это то же правило, что уже записано для заливки календаря активности. */
  --sum-g1: #8B3FE0;    /* фиолетовый  */
  --sum-g2: #0F8C7C;    /* бирюзовый   */
  --sum-g3: #B5791F;    /* янтарный    */
  --sum-g4: #2A8F3E;    /* зелёный     */
  --sum-g5: #B0338C;    /* маджента    */
  --sum-g6: #8A6FB0;    /* сиреневый   */
  --sum-g7: #666C7E;    /* сланцевый   */
  --sum-g8: #B06A6A;    /* пыльная роза*/

  /* Кривые издержек — пять различимых вторичных цветов. */
  --cost-mc:  #119C8A;  --cost-atc: #2F6FED;  --cost-avc: #B5791F;
  --cost-afc: #8B3FE0;  --cost-vc:  #5B6472;
  /* Полные затраты: TC и FC ходят парой с VC, поэтому и цвета из одной
     семьи, но заметно разной светлоты (Б21). */
  --cost-tc:  #2A3140;  --cost-fc:  #96A0AF;

  /* ── Шесть предложенных цветов (П34). Светлая палитра — значения Desmos,
     снятые с их страницы. Тёмная подобрана вручную под наш холст #10141c:
     инверсию всего контейнера, как делает Desmos, копировать нельзя — она
     вывернула бы малиновый акцент. Контраст каждого тёмного к холсту ≥ 6,7:1. */
  --pal-1: #c74440;   /* красный    */
  --pal-2: #2d70b3;   /* синий      */
  --pal-3: #348543;   /* зелёный    */
  --pal-4: #fa7e19;   /* оранжевый  */
  --pal-5: #6042a6;   /* фиолетовый */
  --pal-6: #000000;   /* чёрный     */

  /* ── Структурные цвета холста (зависят от темы) ── */
  --ink:      #2a2f3a;   /* оси, подписи осей, текст у точек  */
  --ink-soft: #5e6675;   /* цифры делений, вторичные подписи  */
  --grid:     #ebebef;   /* линии сетки                       */
  --halo:     #ffffff;   /* обводка-фон под подписями         */
  --canvas:   #ffffff;   /* фон графика                       */

  /* ── Интерфейс: только своё ──
     ⚠️ ОБОЛОЧКА БОЛЬШЕ НЕ СВОЯ, И ЭТО ЗАМЕР, А НЕ ВКУС. Прежний #eceef2 был
     холодным серо-синим и остался от старой палитры: рядом с кремовой
     страницей сайта раздел «Графики» читался как чужой экран. Правило
     «оболочка заметно темнее холста» при этом никуда не делось — и общий
     страничный #f4eed2 его держит ЛУЧШЕ прежнего: к белому холсту 1,166
     против 1,162 у #eceef2 при пороге 1,15. Холст и цвета кривых не тронуты.
     --input-bg остаётся своим: поле ввода белое, как холст.
     Остальное (текст трёх ступеней, поверхности, границы) совпадало с
     платформой байт в байт и приходит из _tokens.html. */
  --bg:          #f4eed2;   /* оболочка за холстом — общая со страницей */
  --input-bg:    #ffffff;
  /* ⚠️ ВТОРОЙ КОПИИ ТОКЕНОВ ПЛАТФОРМЫ ЗДЕСЬ БОЛЬШЕ НЕТ (канон 5.1).
     Малиновый акцент, поверхности, текст, графитовые кнопки, фокус и сигналы
     приходят из `templates/_tokens.html` — он подключён первым в <head>.
     Значения были ТЕ ЖЕ до последнего разряда, то есть акцент и так был общий;
     копия только делала вид, что у калькулятора своя система. Ниже остаётся
     ровно то, что у рисующей поверхности действительно своё. */
  --pick: rgba(var(--accent-rgb), .28);   /* подсветка куска формулы */
  --on-accent:   var(--act-ink-solid);    /* прежнее имя — на общий токен */
  /* Кнопки действий (крупные .btn) — графитовые, НЕ малиновые: --btn-bg
     из _tokens.html. Тёмная тема заодно получила осветлённый графит #46566d
     вместо прежнего #334155 — платформа уже замерила, что на панели #1d2330
     старый давал 1,52:1 и кнопка не читалась как кнопка.

     Радиусы — пять общих значений канона (1.5), тоже из _tokens.html:
     --r-sm 4 / --r 6 / --r-card 10 / --r-panel 12 / --r-pill 999.
     Прежние 6 / 8 / 14 сведены по правилу «скругление уменьшается вглубь»:
     поле и кнопка → --r, карточка сюжета → --r-card, меню и окно → --r-panel.

     ── Тени / слои ──
     --shadow-pop СВЕТЛОЙ темы — значение канона из _tokens.html, своей копии
     больше нет: причин отличаться у всплывающего окна над светлой страницей
     не нашлось. Тёмная тема отличается, и ровно по одной измеренной причине
     (см. ниже). --shadow-float — тень ПЛАВАЮЩЕГО НАД ХОЛСТОМ (кнопки
     масштаба, кнопка расчёта площади, правка названия графика). Раньше он
     звался «panel» и имя лгало: замер показал, что ни на одной панели в покое
     он не висит, а висит ровно на трёх поднятых блоках. Канон 1.7 такую тень
     разрешает: это и есть «то, что физически над страницей». */
  --shadow-float: 0 1px 2px rgba(15,18,28,.06), 0 8px 28px -8px rgba(15,18,28,.18);
  --z-canvas: 1; --z-dock: 20; --z-float: 30; --z-drawer: 40; --z-pop: 50; --z-picker: 60;
  /* --sb и --panel (совместимость со старыми именами) — из _tokens.html */

  --ease: cubic-bezier(.23,1,.32,1);
}
[data-theme="dark"] {
  color-scheme: dark;
  --ink:      #d7dbe3;
  --ink-soft: #98a1b0;
  --grid:     rgba(255,255,255,.075);
  --halo:     #10141c;
  --canvas:   #10141c;

  /* Тёмная тема: своё только у поля ввода — остальное общее.
     ⚠️ ОБОЛОЧКА ПЕРЕЕХАЛА НА СТРАНИЧНЫЙ #1d1a16, И ФИГУРА С ФОНОМ
     ПОМЕНЯЛИСЬ МЕСТАМИ. Прежний #07090e был ТЕМНЕЕ холста #10141c (1,080);
     общий тёплый графит СВЕТЛЕЕ его (1,064). Перепад тот же по величине,
     но теперь холст читается как утопленное поле, а не как подсвеченное.
     Холст не тронут: он остаётся своим у калькулятора, под него
     откалиброваны цвета кривых. */
  --bg:          #1d1a16;
  --input-bg:    #11151d;
  --pick: rgba(var(--accent-rgb), .40);
  --shadow-float: 0 0 0 1px rgba(255,255,255,.05), 0 10px 34px -10px rgba(0,0,0,.6);
  /* ⚠️ ЕДИНСТВЕННОЕ ОТЛИЧИЕ ОТ КАНОННОЙ ТЕНИ, И ОНО ИЗМЕРЕНО.
     У --shadow-pop в _tokens.html нет тёмного варианта: и там, и тут это
     0 6px 20px rgba(0,0,0,.14). Чёрная тень поверх оболочки #07090e даёт
     1,02:1, поверх холста #10141c — 1,03:1, то есть края у всплывающего окна
     в тёмной теме не видно вовсе. Геометрия взята КАНОННАЯ, добавлено только
     светлое кольцо в 1px — им окно и отделяется от фона. */
  --shadow-pop:   0 0 0 1px rgba(255,255,255,.07), 0 6px 20px rgba(0,0,0,.14);
  /* Шесть цветов в тёмной теме — свои значения, а не инверсия светлых. */
  --pal-1: #ff6b66;
  --pal-2: #5aa3e8;
  --pal-3: #4fbf6a;
  --pal-4: #ffa94d;
  --pal-5: #a78bfa;
  --pal-6: #ffffff;
}
/* Оговорка под числом: «эти числа — до вмешательства» (канон 2.13, п. 5).
   Стоит ТАМ ЖЕ, где число, а не сноской внизу: число без оговорки становится
   фактом через секунду после того, как его увидели. */
.scope-note { margin-top: 4px; font-size: 11px; line-height: 1.35; color: var(--text3); }

/* Строка «вернуть исходный вид» (канон 2.8, п. 9). */
.side-reset { display: flex; flex-direction: column; gap: 4px; margin: 0 0 12px; }
.side-reset__why { font-size: 12px; line-height: 1.45; color: var(--text2); }
.side-reset .btn { align-self: flex-start; }

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
  height: 100%; overflow: hidden;
  /* Шрифт интерфейса — общий с сайтом (Montserrat из _tokens.html). Раздел
     «Графики» был единственным экраном на системном шрифте и выпадал из
     оформления. Рисующая поверхность на прежнем шрифте — см. ниже. */
  font-family: var(--font-ui);
  font-size: 14px; color: var(--text);
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
/* ⚠️ ФОН КРАСИТ ТОЛЬКО ТЕЛО, НЕ КОРЕНЬ — ИНАЧЕ ПАТТЕРНА НЕ ВИДНО.
   Слой паттерна из _tokens.html лежит в `body::before/::after` при
   `z-index: -1`, то есть ПОД содержимым тела. Пока фон стоял и на `html`,
   и на `body`, фон тела рисовался поверх слоя и закрывал его собой. Сайт
   красит только тело — здесь теперь так же. */
body { background: var(--bg); }

/* ⚠️ РИСУЮЩАЯ ПОВЕРХНОСТЬ ОСТАЁТСЯ НА ПРЕЖНЕМ ШРИФТЕ, И ЭТО НЕ ЗАБЫТЫЙ ХВОСТ.
   Подписи осей, цифры делений и имена кривых живут внутри #chart и берут
   шрифт по наследству. У Montserrat другая метрика, и смена шрифта сдвинула
   бы разметку графика: поля холста считаются по ИЗМЕРЕННОЙ ширине подписей
   (fitMargins в 20-plane.js).
   Второй селектор — скрытая линейка `measureText`: она лежит рядом с #chart
   отдельным svg и меряет ту же строку. Если шрифт линейки и холста разойдутся,
   поля будут считаться по одному шрифту, а рисоваться другим. */
#chart,
#graph-wrap > svg[data-skip-export] {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}
button { cursor: pointer; font: inherit; color: inherit; }
input, select { font: inherit; }
/* Канон 2.15. Кольцо фокуса ОДНО на весь калькулятор и нейтральное:
   акцентная обводка при каждом клике даёт крупное акцентное пятно, а решение
   «фокус серо-синий» команда уже приняла и записала в токен --focus.
   Тень для этого не годится вовсе (канон 1.7 оставляет её всплывающему),
   да и обводку она рисует без учёта скругления. */
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ── Основной макет: левая панель + область графика ────────────────── */
.app { display: flex; height: calc(100vh - 48px); background: var(--bg); }

/* Левая панель управления (~300px) */
.panel {
  width: 300px; flex-shrink: 0; background: var(--panel);
  border-right: var(--line) solid var(--border);
  overflow-y: auto; padding: 16px;
}
.panel h1 {
  font-size: 15px; font-weight: 600; margin-bottom: 2px;
}
.panel .subtitle {
  font-size: 12px; color: var(--text2); margin-bottom: 16px;
}

/* Секции внутри панели */
.section { margin-bottom: 16px; }
.section-title {
  display: flex; align-items: center;
  font-size: 13px; font-weight: 600; letter-spacing: -.005em;
  color: var(--text); margin-bottom: 8px;
  /* Заголовок бывает смесью слова и набранной формулы («Равновесие D = S»,
     «Оптимум монополии: MR = MC»). Раскладка в ряд съедает пробел на стыке
     текста и span.tex, поэтому зазор задаём явно. */
  column-gap: .28em;
}
/* Вложенная секция: блоки моделей живут внутри одной карточки «Ввод функций»,
   поэтому собственных отступов верхнего уровня у них нет — роль обозначает
   подзаголовок .vsub. */
.section .section { margin-bottom: 0; }
.section .section:last-child { margin-bottom: 0; }

/* ⚠️ ВНУТРИ «ВВОДА ФУНКЦИЙ» ОТСТУП МЕЖДУ БЛОКАМИ ВОЗВРАЩАЕТСЯ.
   Правило выше обнуляет отступ у любой вложенной секции, и это верно, пока
   вложенных блоков один-два. В карточке ввода их до четырёх сразу (кривые,
   монополия, вмешательство, панель сюжета) — без зазора они слипались бы в
   одну простыню. Последний блок отступа не получает: под ним и так край
   карточки. Видимость блоков задаёт setMode инлайновым display, поэтому
   :last-child здесь мало — нужен запас на скрытых соседей снизу. */
#sec-input .section { margin-bottom: 14px; }
#sec-input .section:last-child { margin-bottom: 0; }

/* Элементы управления в панели */
.field { margin-bottom: 8px; }
.field label { display: block; font-size: 12px; color: var(--text2); margin-bottom: 3px; }
.field input[type="text"], .field input[type="number"], .field select {
  width: 100%; padding: 6px 8px; border: 1px solid var(--border);
  border-radius: var(--r); background: var(--input-bg); color: var(--text);
  transition: border-color .12s var(--ease), box-shadow .12s var(--ease);
}
.field input:focus-visible, .field select:focus-visible,
input:focus-visible, select:focus-visible {
  border-color: var(--accent); outline: 2px solid var(--focus); outline-offset: 2px;
}
.row { display: flex; gap: 8px; }
.row .field { flex: 1; }
/* ⚠️ ПОДПИСЬ ГАЛОЧКИ ПЕРЕНОСИТСЯ, А НЕ ОБРЕЗАЕТСЯ (приёмка владельца 31.08).
   «Показывать PS (TR − VC)» обрывалось на «Показывать PS ( TR – V…»: строка
   шире коробки на 40 px при 215 px панели. Сокращать нельзя — «TR − VC» здесь
   и есть объяснение, ради него подпись и написана.
   ⚠️ Причина не в длине текста, а в том, что подпись — ФЛЕКСБОКС. Обозначения
   PS, TR и VC набираются формулой (markNotationsIn), и каждое становится
   отдельным флекс-элементом: их восемь, а перенос стоял `nowrap`, и все
   восемь держались одной строкой. Разрешаем перенос — это и есть починка
   «переносом, а не сокращением». Во всех остальных подписях приложения
   перенос не включится: обзор всех 44 сцен 31.08 нашёл ровно одну обрезанную
   подпись, эту. */
/* ⚠️ ПОДПИСЬ ГАЛОЧКИ — ОБЫЧНАЯ СТРОКА ТЕКСТА, А НЕ ФЛЕКСБОКС.
   Приёмка 31.08: «Показывать PS (TR − VC)» обрывалось на «…( TR – V…».
   Сокращать нельзя — «TR − VC» здесь и есть объяснение.

   Причина была не в длине, а в раскладке. Подпись была флексбоксом, а
   обозначения PS, TR и VC набираются формулой (markNotationsIn) — и подпись
   распадалась на ВОСЕМЬ флекс-элементов в одной строке без переноса: 255 px
   содержимого в коробке 215 px.

   Флексбокс здесь не годится в принципе, и две попытки это показали.
   Разрешить перенос — текст виден, но `gap: 7px` раздвигает все восемь
   элементов подряд, и на экране «PS ( TR − VC )» вместо «PS (TR − VC)».
   Убрать gap — скобки встают вплотную, зато пропадают пробелы между словами:
   флексбокс срезает пробелы по краям каждого элемента, и выходит
   «ПоказыватьPS». Своих пробелов у разметки достаточно, но во флексбоксе их
   не видно.

   Поэтому подпись — обычная строка: галочка и маркер выравниваются по
   середине строки, весь остальной текст течёт как текст, переносится по
   словам и сохраняет свои пробелы. После правки подпись укладывается в ОДНУ
   строку (215 px из 215), а перенос остаётся запасом на узкой панели. */
.chk {
  display: block;
  font-size: 13px; cursor: pointer; margin-bottom: 6px;
}
/* Маркер цвета — блок фиксированного размера. Во флексбоксе размеры ему
   давало то, что он флекс-элемент; в обычной строке это надо сказать явно. */
.chk > .swatch { display: inline-block; vertical-align: middle; }
/* Кусок подписи, который нельзя разрывать переносом. Нужен там, где скобка
   или знак принадлежат соседнему обозначению: «(TR − VC)» обязано ехать на
   вторую строку целиком, а не оставлять на ней одну закрывающую скобку. */
.nowrap { white-space: nowrap; }
/* Б23. Галочка в списке кривых — служебный переключатель, а маркер цвета рядом
   и есть главное: по нему кривую узнают на графике. Малиновая галочка была
   крупнее и заметнее маркера и перетягивала взгляд на себя. Делаем её
   спокойнее (мельче и цветом текста), а маркер — заметнее. */
/* ⚠️ ФЛАЖОК РИСУЕМ САМИ (канон 3.4). Был нативный 13×13, только подкрашенный
   `accent-color`: ни своей рамки, ни скругления, ни галочки, а отмеченное
   состояние серое вместо акцентного. Это прямое нарушение статьи 3.2 — ни
   одного браузерного элемента по умолчанию, иначе в тёмной теме просвечивает
   светлый системный виджет — и вдобавок почти втрое меньшая область касания.
   Значения взяты из канона дословно. */
.chk input[type=checkbox] {
  appearance: none; -webkit-appearance: none;
  /* Отбивка от текста стоит ЗДЕСЬ, а не общим gap строки: gap раздвинул бы и
     куски набранной формулой подписи (см. комментарий у .chk выше). */
  width: 17px; height: 17px; margin: 0 7px 0 0; vertical-align: middle;
  cursor: pointer; position: relative;
  border: 1.5px solid var(--border); border-radius: 4px; background: var(--surface);
  transition: background .12s var(--ease), border-color .12s var(--ease);
}
.chk input[type=checkbox]:hover { border-color: var(--accent); }
.chk input[type=checkbox]:checked { background: var(--accent); border-color: var(--accent); }
.chk input[type=checkbox]:checked::after {
  content: ''; position: absolute; left: 5px; top: 1px; width: 4px; height: 9px;
  /* Чернила на акцентной заливке идут ТОКЕНОМ, а не белым числом: в тёмной
     теме сам акцент светлый, и белая галочка на нём была бы нечитаема — это
     то самое правило части 4, по которому белого на акценте в тёмной теме не
     бывает. Канон 3.4 пишет «белым», имея в виду светлую тему; общий токен
     говорит то же самое и вдобавок верен во второй. */
  border: solid var(--on-accent); border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.chk input[type=checkbox]:disabled {
  border-style: dashed; background: var(--surface-2); opacity: .55; cursor: not-allowed;
}
.chk input[type=checkbox]:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* Заголовок группы кривых: разделяет средние и полные затраты (Б21). */
.curve-group-title { font-size: 11px; font-weight: 600; color: var(--text3);
  text-transform: uppercase; letter-spacing: .03em; margin: 0 0 5px; }
.btn {
  /* Канон 3.1: 13/500, рамка ПРОЗРАЧНАЯ, а не отсутствует — иначе кнопка
     с рамкой и кнопка без неё разной высоты, и ряд подпрыгивает. */
  width: 100%; padding: 8px 10px; border: var(--line) solid transparent;
  border-radius: var(--r);
  background: var(--btn-bg); color: var(--on-btn); font-weight: 500; font-size: 13px;
  transition: background .12s var(--ease), border-color .12s var(--ease),
              color .12s var(--ease), transform .08s var(--ease);
}
.btn:hover { background: var(--btn-bg-hover); }
/* Б17. Второстепенное действие того же семейства: тот же размер и та же
   геометрия, что у главной кнопки, разная только заливка. Прежде рядом
   стояли крупная тёмная во всю ширину и мелкая светлая другой формы. */
/* Рамка нарисована ГРАНИЦЕЙ, а не внутренней тенью: канон 1.7 оставляет тень
   только всплывающему и кольцу фокуса, а внутреннее кольцо это та же рамка,
   записанная другим свойством. */
/* Канон 3.1 знает три веса, и второстепенное действие рядом с главной — это
   «обычная»: поверхность плюс рамка. Прозрачный фон делал её четвёртым видом
   кнопки, которого в каноне нет. */
.btn.btn-quiet { background: var(--surface); color: var(--text);
  border-color: var(--border); font-weight: 500; }
.btn.btn-quiet:hover { background: var(--surface-2); color: var(--text); }
.btn:active { transform: scale(.98); }
.btn-sm {
  padding: 4px 9px; border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface); color: var(--text2); font-size: 12px; font-weight: 500;
  transition: color .12s var(--ease), border-color .12s var(--ease), background .12s var(--ease);
}
.btn-sm:hover { color: var(--accent); border-color: var(--accent); }
.btn-sm:active { transform: scale(.98); }

/* Подсказки, ошибки, код в подсказках */
.hint { font-size: 11px; color: var(--text2); margin-top: 6px; line-height: 1.5; }
/* Инструкция ушла под вопросик (Фаза 5): абзац остаётся в разметке, чтобы
   сцены по-прежнему правили его текстом, но с глаз убран. */
.hint-hidden { display: none !important; }
.help-dot {
  width: 15px; height: 15px; margin-left: 6px; padding: 0; vertical-align: middle;
  display: inline-grid; place-items: center; flex: 0 0 auto;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--surface); color: var(--text3);
  font-size: 10px; font-weight: 700; line-height: 1; cursor: help;
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.help-dot:hover, .help-dot:focus-visible { color: var(--accent); border-color: var(--accent); }
/* Вопросик там, где заголовка рядом нет. Прижимаем его к предыдущему контролу
   вместо отдельной строки: сам по себе он читался как обломок. */
.help-inline { display: inline-flex; align-items: center; vertical-align: middle; margin-left: 2px; }
.help-anchor { margin-top: 4px; line-height: 1; }
.help-anchor > .help-dot { margin-left: 0; }

/* Таблица посчитанных площадей (П44): «Названия» и «Площадь». Столбец
   «Отрезок» убран — он виден рядом с выбором кривой. */
.area-table { margin-top: 10px; }
.area-row {
  display: grid; grid-template-columns: minmax(0,1fr) auto 18px;
  align-items: center; gap: 8px; padding: 6px 0;
  border-bottom: var(--line) solid var(--border);
}
/* Площадь, посчитанная приближением (вершин больше восьми). */
.area-approx { border-bottom: var(--line) dotted var(--text3); }
/* Оговорка к числу: своя строка во всю ширину карточки, под ним. */
.area-note { grid-column: 1 / -1; font-size: 12px; line-height: 1.45; color: var(--text2); }
.area-note--bad { color: var(--error); }
.area-row:last-child { border-bottom: none; }
.area-head { font-size: 11px; font-weight: 600; letter-spacing: .03em;
  color: var(--text3); text-transform: uppercase; padding-bottom: 4px; }
.area-row > span { font-size: 12px; color: var(--text2); min-width: 0; }
.area-row > b { font-size: 13px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums; text-align: right; }
.area-what { display: flex; align-items: center; gap: 6px; }
.area-name {
  flex: 1; min-width: 0; padding: 3px 5px; font-size: 12px; color: var(--text);
  border: 1px solid transparent; border-radius: var(--r); background: none;
}
.area-name:hover { border-color: var(--border); }
.area-name:focus { border-color: var(--accent); background: var(--input-bg); outline: none; }
/* Область рисует сама сцена, её имя не правят: показываем аббревиатуру, полное
   название остаётся подсказкой (Н59). */
.area-name-fixed {
  flex: 1; min-width: 0; padding: 3px 5px; font-size: 12px; color: var(--text);
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Имя складываемой кривой правится прямо в подписи поля (Фаза 13.2). */
.ppfsum-name {
  flex: 1; min-width: 0; margin-left: 6px; padding: 2px 5px;
  font-size: 12px; font-weight: 600; color: var(--text2);
  border: 1px solid transparent; border-radius: var(--r); background: none;
}
.ppfsum-name:hover { border-color: var(--border); }
.ppfsum-name:focus { border-color: var(--accent); background: var(--input-bg); color: var(--text); outline: none; }
#ppfsum-rows .field > label { display: flex; align-items: center; }
/* Единственная плашка подсказки на весь калькулятор (канон 2.11, `.k-tip`).
   ⚠️ `pointer-events: none` — часть правила, а не украшение: плашка всплывает
   там, где рука ведёт указатель, и без этого она перехватывала бы нажатие по
   тому самому элементу, который объясняет. */
#hint-tip {
  position: fixed; z-index: var(--z-pop); display: none; max-width: 260px;
  padding: 8px 11px; border-radius: var(--r);
  background: var(--surface-2); border: var(--line) solid var(--border);
  box-shadow: var(--shadow-pop); pointer-events: none;
  font-size: 12px; line-height: 1.45; color: var(--text2);
}
/* Плашка, внутри которой стоит набранная запись (фигурная скобка кусочной
   функции). KaTeX не переносит строку, и в 260 px запись просто вылезала на
   холст — замер 26.08 дал перебор 165 px. Здесь плашка растёт по содержимому,
   но не шире окна; если и этого мало, запись пересобирается второй формой
   (fitTipMath в 86-workspace.js). */
#hint-tip.tip-math { max-width: min(560px, calc(100vw - 24px)); }
#hint-tip hr { border: none; border-top: var(--line) solid var(--border); margin: 7px 0; }
/* Компактная запись внутри плашки подсказки — та же разметка, что в блоке
   «Итоговая функция» (одно место правды на весь калькулятор), только без
   отбивки сверху: в плашке она стоит не карточкой, а строкой текста. */
#hint-tip .ff-math { margin-top: 0; color: var(--text); }
.hint code { background: var(--surface-2); border: 1px solid var(--border-soft);
  padding: 1px 4px; border-radius: var(--r-sm); font-size: 11px; }

/* ── Подсказка формата формулы (Фаза 1а) ──────────────────────────────
   Кнопка «?» рядом с полем ввода; всплывающая карточка с примерами
   синтаксиса Math.js. Поле НЕ заслоняется — карточка раскрывается под ним.
   Пример кликабелен: подставляется прямо в поле. */
.f-wrap { position: relative; }
.f-row { display: flex; gap: 6px; align-items: stretch; }
.f-row > input { flex: 1; min-width: 0; }
/* Строка с кусочной записью отдаёт полю ВСЮ ширину, а кнопка клавиатуры
   переезжает под него (решение владельца 24.08: «кусочная показывается в поле
   в две строки — поле растёт в высоту»). Тридцать восемь пикселей, которые
   занимала кнопка, — это разница между «запись обрывается на слове если» и
   «запись видна целиком»; остаток добирается подбором кегля (fitFormulaField
   в 82-input.js). Класс ставит тот же подбор, по наличию фигурной скобки. */
.f-row.f-tall { flex-wrap: wrap; justify-content: flex-end; }
.f-row.f-tall > .f-slot { flex: 1 0 100%; }
.f-row.f-tall > .f-help, .f-row.f-tall > .f-kbd { align-self: flex-end; height: 32px; }
/* Ещё шесть пикселей внутрь: у трёх кусков подбор кегля упирался в свой нижний
   предел ровно на этой ширине (замер 25.08 — 190 px содержимого в окне 189). */
.f-row.f-tall math-field { padding-left: 5px; padding-right: 5px; }
/* Набранная формула поверх самого поля: пока в поле не пишут, строка
   показывает формулу так, как её печатают в учебнике. Стоит в потоке рядом с
   input'ом (не поверх него абсолютом), поэтому ничего не съезжает при смене
   темы и масштаба шрифта. Сам input продолжает хранить текст Math.js —
   считается ровно то, что написано. */
.f-slot { position: relative; flex: 1; min-width: 0; }
.f-slot > input { width: 100%; }
.f-typeset {
  position: absolute; inset: 0; display: none; align-items: center;
  padding: 0 8px; overflow-x: auto; overflow-y: hidden; cursor: text;
  background: var(--input-bg); border: 1px solid var(--border);
  border-radius: var(--r); color: var(--text); font-size: 14px; line-height: 1;
}
.f-typeset.show { display: flex; }
.f-typeset:hover { border-color: var(--accent-ring); }
.f-help {
  flex-shrink: 0; width: 30px; border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface); color: var(--text2); font-size: 13px; font-weight: 500; line-height: 1;
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.f-help:hover, .f-help[aria-expanded="true"] { color: var(--accent); border-color: var(--accent); }
.f-pop {
  display: none; margin-top: 8px; padding: 11px 11px 10px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: var(--shadow-pop);
}
.f-pop.open { display: block; }
@media (prefers-reduced-motion: no-preference) {
  .f-pop.open { animation: fpop .16s var(--ease); }
  @keyframes fpop { from { opacity: 0; transform: translateY(-4px); } }
}
.f-pop-title {
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text3); margin-bottom: 9px;
}
/* Примеры — ровная колонка карточек: набранная формула сверху, за что её берут снизу.
   Раньше формула и пояснение стояли в строку и на длинных примерах ломали ряд. */
.f-grid { display: grid; gap: 5px; }
.f-ex {
  display: grid; gap: 3px; width: 100%; text-align: left; cursor: pointer;
  padding: 8px 10px; border: 1px solid transparent; border-radius: var(--r);
  background: var(--surface-2);
  transition: background .12s var(--ease), border-color .12s var(--ease), transform .1s var(--ease);
}
.f-ex:hover { background: var(--accent-tint); border-color: var(--accent-ring); }
.f-ex:active { transform: scale(.99); }
.f-ex:focus-visible { border-color: var(--accent); outline: 2px solid var(--focus); outline-offset: 2px; }
.f-ex-math { font-size: 13px; color: var(--text); line-height: 1.35; overflow-x: auto; }
.f-ex-note { font-size: 11px; color: var(--text2); line-height: 1.35; }
.f-pop-foot {
  font-size: 11px; color: var(--text3); line-height: 1.5;
  margin-top: 9px; padding-top: 8px; border-top: var(--line) solid var(--border);
}
/* Палитра операций: кусочки формулы, которые подставляются по нажатию.
   Плотный ряд плиток, чтобы весь набор помещался в панель шириной 256px. */
.f-pal-lead { font-size: 11px; color: var(--text2); line-height: 1.45; margin: -4px 0 8px; }
.f-pal-group {
  font-size: 11px; font-weight: 600; color: var(--text3);
  margin: 8px 0 4px; letter-spacing: .03em;
}
/* ── Поле формулы на MathLive ─────────────────────────────────────────
   Само поле показывает формулу так, как её печатают в учебнике, и растёт
   в высоту, когда запись многоэтажная (дробь в дроби, кусочная функция).
   Обычный input остаётся рядом скрытым: в нём лежит текст для движка,
   и весь остальной код по-прежнему читает и пишет именно его. */
math-field {
  display: block; width: 100%; box-sizing: border-box;
  padding: 5px 8px; font-size: 15px; line-height: 1.2;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text);
  --caret-color: var(--accent);
  /* Выделение в поле формулы: 10% малинового по белому было не различить,
     поэтому берём отдельный, заметно более плотный тон (Фаза 8). */
  --selection-background-color: var(--pick);
  --contains-highlight-background-color: var(--pick);
  --smart-fence-color: var(--text2);
  --placeholder-color: var(--text3);
}
math-field:focus, math-field:focus-within {
  border-color: var(--accent); outline: 2px solid var(--focus); outline-offset: 2px;
}
/* Свои кнопки уже есть в строке — встроенные не нужны. */
math-field::part(virtual-keyboard-toggle), math-field::part(menu-toggle) { display: none; }
.f-slot > input.mf-hidden { display: none; }

/* Кнопка клавиатуры в правой части строки. */
.f-kbd {
  flex-shrink: 0; width: 32px; display: grid; place-items: center;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface); color: var(--text2);
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.f-kbd svg { width: 17px; height: 17px; }
.f-kbd:hover, .f-kbd[aria-expanded="true"] { color: var(--accent); border-color: var(--accent); }

/* ── Клавиатура ───────────────────────────────────────────────────────
   Три раздела, раскрывается ОДИН за раз на всю ширину панели. */
.mkbd {
  display: none; margin-top: 8px; padding: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: var(--shadow-pop);
}
.mkbd.open { display: block; }
@media (prefers-reduced-motion: no-preference) {
  .mkbd.open { animation: fpop .16s var(--ease); }
}
.mkbd-tabs { display: flex; gap: 4px; margin-bottom: 8px; }
.mkbd-tab {
  flex: 1; padding: 6px 4px; font-size: 12px; font-weight: 600;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface-2); color: var(--text2);
  transition: background .12s var(--ease), color .12s var(--ease), border-color .12s var(--ease);
}
.mkbd-tab:hover { color: var(--text); }
.mkbd-tab.active { background: var(--accent-tint); border-color: var(--accent); color: var(--accent); }
.mkbd-pane { display: none; }
.mkbd-pane.active { display: block; }
.mkbd-row { display: flex; gap: 4px; }
.mkbd-row + .mkbd-row { margin-top: 4px; }
.mkbd-grid { display: flex; flex-wrap: wrap; gap: 4px; }
.mkbd-lab {
  font-size: 11px; font-weight: 600; color: var(--text3);
  margin: 8px 0 4px; letter-spacing: .03em;
}
.mkbd-lab:first-child { margin-top: 0; }
.mk {
  flex: 1 1 auto; min-width: 30px; min-height: 32px; padding: 3px 6px;
  display: grid; place-items: center; font-size: 13px; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r);
  transition: background .1s var(--ease), border-color .1s var(--ease), transform .08s var(--ease);
}
.mk:hover { background: var(--accent-tint); border-color: var(--accent-ring); }
.mk:active { transform: scale(.94); }
.mk.wide { flex: 2 1 auto; }
.mk.op { font-weight: 600; }
.mk.fn { font-size: 12px; }
.mkbd-grid .mk { flex: 0 0 auto; min-width: 32px; }
.mkbd-foot {
  display: flex; gap: 6px; margin-top: 8px; padding-top: 8px;
  border-top: 1px solid var(--border-soft);
}
.mkbd-foot button {
  flex: 1; padding: 6px; font-size: 11px; font-weight: 600; color: var(--text2);
  background: transparent; border: 1px dashed var(--border); border-radius: var(--r);
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.mkbd-foot button:hover { color: var(--accent); border-color: var(--accent); }

.f-pal-row { display: flex; flex-wrap: wrap; gap: 4px; }
.f-pal {
  font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12px;
  padding: 4px 7px; cursor: pointer; color: var(--text);
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r);
  transition: background .12s var(--ease), border-color .12s var(--ease), transform .1s var(--ease);
}
.f-pal:hover { background: var(--accent-tint); border-color: var(--accent-ring); }
.f-pal:active { transform: scale(.96); }
.f-pal:focus-visible { border-color: var(--accent); outline: 2px solid var(--focus); outline-offset: 2px; }
.f-pal.wide { font-family: inherit; font-size: 12px; font-weight: 600; }

/* Конструктор кусочной функции: слева нарисованная фигурная скобка,
   справа строки «формула — на участке до границы». */
/* Строка точки x₀: ползунок и поле точного значения. Поле широкое — в него
   должно целиком влезать число вида -11.78, раньше последний знак обрезался. */
.x0-row { display: flex; gap: 7px; align-items: center; margin-top: 4px; }
.x0-row input[type="range"] { flex: 1; min-width: 0; margin: 0; }
.x0-row input[type="number"] {
  flex: 0 0 88px; width: 88px; padding: 5px 6px; font-size: 13px; text-align: center;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text); font-variant-numeric: tabular-nums;
}

/* Пояснение «как это устроено» прячется под вопросик рядом с полем ввода:
   посреди панели такой абзац отвлекает, а нужен он один раз. */
.hint-row { display: flex; align-items: center; gap: 7px; margin: 8px 0 2px; }
.hint-btn {
  flex: 0 0 auto; width: 20px; height: 20px; display: grid; place-items: center;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--surface); color: var(--text3);
  font-size: 11px; font-weight: 700; line-height: 1; cursor: pointer;
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.hint-btn:hover, .hint-btn[aria-expanded="true"] { color: var(--accent); border-color: var(--accent); }
.hint-lead { font-size: 12px; color: var(--text3); }
.hint-pop {
  display: none; margin: 6px 0 4px; padding: 9px 10px;
  font-size: 12px; line-height: 1.5; color: var(--text2);
  background: var(--surface-2); border: 1px solid var(--border-soft); border-radius: var(--r);
}
.hint-pop.open { display: block; }
.hint-pop p { margin: 0 0 6px; }
.hint-pop p:last-child { margin-bottom: 0; }

/* Разбор в «Аналитике»: как машина пришла к ответу. Стоит под числами и
   читается сплошным текстом, поэтому набран мельче и с воздухом. */
.sb-note {
  margin-top: 10px; padding-top: 9px; border-top: 1px solid var(--border-soft);
  font-size: 12px; line-height: 1.55; color: var(--text2);
}
/* Оригинал врезки остаётся в табло — но невидимым: показывается его копия
   в «Объяснении модели» (см. moveExplanations). Держать оригинал нужно, чтобы
   проход был повторяемым, а не одноразовым. */
.sb-note-src { display: none; }
.sb-note b { display: block; margin-bottom: 5px; color: var(--text); font-size: 11px;
  letter-spacing: .04em; text-transform: uppercase; }
.sb-note p { margin: 0 0 6px; }
.sb-note p:last-child { margin-bottom: 0; }

/* ── ИТОГОВАЯ ФУНКЦИЯ (решение владельца 26.08) ───────────────────────
   Первая карточка «Ключевых значений»: аналитическая запись посчитанной
   кривой. ЖИРНЫМ ЗАПИСЬ НЕ ВЫДЕЛЯЕМ — в табло все числа и так 700, и жирный
   там не выделяет ничего. Выделяют три вещи: левая кромка цветом кривой,
   надзаголовок тем же цветом и кегль 15 px против 13 у чисел рядом.
   ⚠️ Класса `sb-note` внутри быть не должно: он унёс бы блок в «Объяснение
   модели» (см. moveExplanations в 86-workspace.js). */
.ff {
  margin-bottom: 10px; padding: 8px 10px 9px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-left: 3px solid var(--ff-c, var(--border));
  border-radius: var(--r);
}
.ff-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
/* Кнопки блока держатся вместе и при нехватке ширины уезжают своей строкой —
   надзаголовок остаётся в одной строке при любой ширине панели.
   ⚠️ И САМИ КНОПКИ ПЕРЕНОСЯТСЯ, если ширины мало даже им. Замер 31.08 при
   ширине браузера 380 px: панель даёт под содержимое 99 px, а пара кнопок
   «развернуть» и «копировать» просит 135 — без переноса они выталкивали
   карточку блока за её собственные края на 26 px. */
.ff-acts {
  display: flex; align-items: baseline; flex-wrap: wrap; justify-content: flex-end;
  gap: 10px; row-gap: 2px; margin-left: auto; min-width: 0;
}
.ff-eyebrow {
  /* ⚠️ НАДЗАГОЛОВОК СТОИТ В ОДНУ СТРОКУ (указание владельца 31.08).
     «ИТОГОВАЯ ФУНКЦИЯ» ломалось надвое и съедало высоту блока: замер 31.08 —
     две строки и на 1440, и на 1000 px. Текст сокращать нельзя, поэтому
     уменьшены трекинг (.08em → .04em) и кегль (9,5 → 9 px), а перенос
     запрещён явно. Девятка допущена каноном для служебной надписи в кружке,
     и здесь она в том же качестве — метка блока, а не читаемый текст. */
  white-space: nowrap;
  /* ⚠️ УСЕЧЕНИЕ ЗДЕСЬ — ПОСЛЕДНЯЯ МЕРА, А НЕ СПОСОБ СОКРАТИТЬ ТЕКСТ.
     На всех рабочих ширинах панели (215, 191, 147 px) надпись стоит целиком и
     в одну строку — это и требовалось. Но на 99 px, куда панель сжимается при
     ширине браузера 380 px, не помещается ничто: сама надпись просит 111 px.
     Тогда лучше усечь метку блока, чем вытолкнуть карточку за её края. */
  min-width: 0; overflow: hidden; text-overflow: ellipsis;
  /* ⚠️ КЕГЛЬ ОСТАЁТСЯ 9,5, А НЕ 9, И ЭТО НЕ ПРИДИРКА К ПОЛПИКСЕЛЯ.
     Шкала канона 1.2 знает пару «10/700» (она округляет кегль), а «9/700» не
     знает: проверка канона краснела на светлой и тёмной теме сразу. Уложить
     надпись в одну строку хватило одного УМЕНЬШЕННОГО ТРЕКИНГА (.08em → .04em),
     кегль трогать не понадобилось. */
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  /* ⚠️ ЦВЕТ КРИВОЙ ПОДМЕШАН, А НЕ ВЗЯТ ЧИСТЫМ, И ЭТО НЕ ПРИДИРКА К ОТТЕНКУ.
     Цвета кривых подобраны под контраст 3:1 к ХОЛСТУ — этого хватает линии, но
     не мелкому тексту: замер 26.08 дал 3,46 у синего спроса на светлой теме
     при требуемых каноном 4,6. Подмешиваем цвет текста: оттенок кривой виден
     (надзаголовок по-прежнему «того же цвета»), а читаемость доходит до
     канона. Чистый цвет остаётся там, где ему и место, — на левой кромке:
     это графический элемент, и ему довольно 3:1. */
  color: color-mix(in srgb, var(--ff-c, var(--text3)) 55%, var(--text));
}
.ff-copy {
  border: none; background: none; padding: 0; cursor: pointer;
  /* Кегль и вес — из шкалы канона 1.2: 11/400. Десятка допущена каноном
     только для глифа «?» в кружке, а это обычная подпись кнопки. */
  /* --text3 на --surface-2 даёт 4,51 при требуемых 4,6 — это старый долг
     платформы (те же 4,51 у .hint-lead). Новый долг не заводим: берём --text2. */
  font: inherit; font-size: 11px; font-weight: 400; color: var(--text2);
  border-bottom: 1px dotted var(--text2); line-height: 1.2;
}
.ff-copy:hover { color: var(--text); border-bottom-color: var(--text); }
/* «Развернуть» — та же кнопка-ссылка, что и «копировать»: они стоят рядом и
   обязаны читаться как пара, а не как кнопка и ссылка. */
.ff-expand {
  border: none; background: none; padding: 0; cursor: pointer;
  font: inherit; font-size: 11px; font-weight: 400; color: var(--text2);
  border-bottom: 1px dotted var(--text2); line-height: 1.2;
}
.ff-expand:hover { color: var(--text); border-bottom-color: var(--text); }
.ff-name { margin-top: 3px; font-size: 11.5px; color: var(--text2); }
.ff-math { margin-top: 4px; font-size: 15px; color: var(--text); }
/* ⚠️ ЗАПИСЬ НЕ ПЕРЕНОСИТСЯ ПО СЛОВАМ, И ЭТО ТО ЖЕ САМОЕ ЖЁСТКОЕ ПРАВИЛО.
   «Формула участка и её условие ВСЕГДА на одной строке» (владелец, 31.08)
   относится и к однострочной записи — той, у которой скобки нет вовсе.
   KaTeX ставит на свой корень `white-space: normal`, и запись КТВ
   «Y = 200 − 2X, 0 ≤ X ≤ 100» разъезжалась на две строки: замер 31.08 в
   сцене «КТВ. Одна страна». Переноса нет — значит правило ширины видит
   настоящее переполнение и отрабатывает ступенями, как и задумано. */
.ff-math .katex { white-space: nowrap; }

/* ── КОМПАКТНАЯ ЗАПИСЬ: ОДИН УЧАСТОК — ОДНА СТРОКА ───────────────────
   Решение владельца 31.08. Формула слева кеглем 15 и по левому краю, условие
   второй колонкой справа кеглем 11 и серым, без слова «если». Двухстрочной
   формы (условие ПОД формулой) больше нет ни как запасного пути, ни как
   настройки: она и была причиной «запись выглядит кривой».

   ⚠️ КОЛОНКА ФОРМУЛ ОБЪЯВЛЕНА `max-content` НАМЕРЕННО. Формула не усекается
   никогда; не влезает — уменьшается кегль (до 13 px, ниже нельзя), и это
   делает ffFitCases. Ужимается только колонка условий. */
.ff-math.ff-cases {
  display: flex; align-items: center; column-gap: 6px; max-width: 100%;
}
.ff-lhs { flex: 0 0 auto; }
.ff-brace {
  flex: 0 0 9px; align-self: stretch; color: var(--text3); min-height: 34px;
}
.ff-rows {
  flex: 1 1 auto; min-width: 0;
  display: grid; grid-template-columns: max-content minmax(0, max-content);
  column-gap: 10px; row-gap: 1px; align-items: baseline;
}
.ff-f { white-space: nowrap; }
/* Последняя ступень правила ширины у КУСОЧНОЙ записи: уложиться нечем, и
   запись прокручивается. Колонки берут естественную ширину — иначе сетка
   по-прежнему ужимала бы условие в ноль, а формула торчала бы за край. */
.ff-math.ff-cases.ff-scroll { overflow-x: auto; overflow-y: hidden; }
.ff-math.ff-cases.ff-scroll .ff-rows { grid-template-columns: max-content max-content; }
.ff-c {
  font-size: 11px; color: var(--text3);
  white-space: nowrap; overflow: hidden; min-width: 0; position: relative;
}
/* Усечение — последняя мера перед уменьшением кегля формулы. Многоточие
   рисуется накладкой, а не свойством text-overflow: внутри ячейки лежит
   набранный KaTeX (inline-block), и до него text-overflow не достаёт. */
.ff-c.ff-cut::after {
  content: '…'; position: absolute; right: 0; top: 0; bottom: 0;
  display: flex; align-items: center; padding-left: 3px;
  background: var(--surface-2);
}
/* Последняя ступень правила ширины: запись не влезла даже второй формой при
   кегле 13. Прокручиваем её, а не ужимаем дальше — читать нечем. Затухание
   у правого края говорит, что запись продолжается. */
.ff-math.ff-scroll {
  overflow-x: auto; overflow-y: hidden;
  /* ⚠️ Это МАСКА, а не цвет: чёрное здесь означает «показать целиком»,
     прозрачное — «спрятать». Цветовым токеном его подменять нечем, но и
     шестнадцатеричной записи мимо токенов тут быть не должно (канон 1.1),
     поэтому пишем ключевым словом. */
  -webkit-mask-image: linear-gradient(to right, black calc(100% - 18px), transparent 100%);
  mask-image: linear-gradient(to right, black calc(100% - 18px), transparent 100%);
}
.ff-note { margin-top: 5px; font-size: 11px; color: var(--text2); line-height: 1.45; }

/* Строка списка функций в режиме построения графиков: цвет, формула, крестик
   и своё имя под ними. Пустая строка внизу выглядит так же, только без имени
   и крестика — начал печатать, и она стала обычной. */
/* П61. ⚠️ СТРОКА ПЕРЕЖИЛА СВОЮ СЕТКУ, И ЭТО ЛОМАЛО ПОЛЕ ФОРМУЛЫ.
   Колонок объявлено три (образец цвета · формула · крестик), а детей в строке
   стало ПЯТЬ: имя кривой из текстового поля превратилось в редактируемое
   значение (.edval), и лишние элементы перенесло на второй ряд. Ширину
   колонки «auto» задаёт САМЫЙ ШИРОКИЙ элемент колонки по всем рядам, а им
   оказалось имя: образец цвета раздувался до 110 px, и полю формулы из 215 px
   доступных оставалось 75 — плейсхолдер обрезался до «Наприме».
   Лечим не подгонкой ширин, а раскладкой: имя занимает СВОЙ ряд целиком
   (`1 / -1`), и первая колонка снова меряется по образцу цвета. */
.grow {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  gap: 6px; margin-bottom: 8px;
}
.grow .f-slot { min-width: 0; }
.grow > .cpick { justify-self: start; }
/* Отступ по горизонтали ужат с 7 до 4: образец «Например: x^2-4» просит 113 px,
   а поле давало 108, и запись обрезалась. Текст образца владелец оставил, и
   правильнее найти пять пикселей в раскладке, чем урезать то, что он выбрал. */
.grow math-field { font-size: 14px; padding: 4px 4px; }
.grow > .edval, .grow > .grow-name { grid-column: 1 / -1; }
.grow-name {
  width: 100%; min-width: 0; padding: 3px 7px; font-size: 12px;
  border: var(--line) solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text2);
}
.pw-count-field { display: flex; align-items: center; gap: 8px; }
.pw-count-field label { margin: 0; }
.pw-count-field input {
  width: 62px; padding: 5px 7px; font-size: 13px; text-align: center;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text);
}
.pw-brace { display: flex; gap: 10px; margin: 4px 0 12px; align-items: stretch; }
.pw-brace-mark { flex: 0 0 12px; align-self: stretch; color: var(--text3); min-height: 56px; }
.pw-rows { flex: 1; display: grid; gap: 7px; }

/* ── СТРОКА КУСКА: ПЯТЬ КОЛОНОК ПЛЮС МЕСТО ПОД КЛАВИАТУРУ ─────────────
   Указание владельца 25.08: «расширить окно конструктора и уменьшить поля
   ввода формул», потому что колонки «От» и «До» оказывались у самого края.

   Была гибкая строка (flex), и у неё две беды.

   1. Колонки «От» и «До» были ЖЁСТКИЕ по 52 px и стояли последними, а поле
      формулы забирало всю остальную ширину. В окне 380 px на границу участка
      оставалось меньше, чем нужно даже трёхзначному числу, и «До» упиралось
      в правое поле карточки: замер 25.08 — правый край последней границы
      809 px при правом крае окна 830 px, то есть весь запас 21 px.

   2. Кнопка клавиатуры ОДНА на весь редактор и переезжает в ту строку, где
      стоит курсор (см. pwAttachKeyboard). В гибкой строке она отнимала свои
      32 px у поля формулы, и колонки ездили от того, куда поставили каретку:
      замер 25.08 — в строке с курсором поле формулы 118 px, во всех
      остальных 156 px, разница 38 px на ровном месте.

   Стало: сетка с ПОСТОЯННЫМИ колонками. Место под кнопку клавиатуры
   зарезервировано в КАЖДОЙ строке, поэтому переезд каретки колонок больше не
   двигает. Границы участка получили свою ширину и сжаться не могут в
   принципе — поле формулы делит с ними ряд по правилу сетки, а не отбирает
   у них остаток. */
:root { --pw-bound: 96px; }
.pw-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) max-content var(--pw-bound) max-content var(--pw-bound) 32px;
  align-items: center; gap: 6px;
}
.pw-row .f-slot { min-width: 0; }
.pw-row input[type=text] {
  width: 100%; min-width: 0; padding: 5px 7px; font-size: 12px;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text);
}
/* Поле формулы внутри строки — то же самое, что и в панели: набранная запись.
   Ужимаем его по высоте, чтобы весь редактор помещался в один экран. */
.pw-row math-field { font-size: 13px; padding: 3px 6px; }
/* Границы участка: своя колонка сетки, ужать её нечем. */
.pw-row input.pw-bound { width: 100%; text-align: center; padding: 5px 3px; }
.pw-when { font-size: 12px; color: var(--text2); white-space: nowrap; }
/* Кнопка клавиатуры стоит в зарезервированной шестой колонке. */
.pw-row > .f-kbd { grid-column: 6; }
/* Живой предпросмотр формулы под полем (Фаза 4б): в поле остаётся синтаксис
   Math.js, а тут видно, как формула выглядит набранной. */
.f-preview { margin-top: 6px; font-size: 14px; color: var(--text); overflow-x: auto; }
.f-preview:not(.has) { display: none; }
.f-preview.has {
  padding: 7px 9px; background: var(--surface-2);
  border: 1px solid var(--border-soft); border-radius: var(--r);
}
.f-preview .katex { font-size: 1.04em; }

/* Бейдж формы ввода кривой (Q(P) — введена как объём от цены). */
.form-badge {
  flex-shrink: 0; font-size: 11px; font-weight: 600; letter-spacing: .02em;
  color: var(--accent); background: var(--accent-tint);
  border-radius: var(--r-sm); padding: 1px 4px;
}
.muted { font-size: 12px; color: var(--text2); }
.error { font-size: 12px; color: var(--c-bad); margin-top: 8px; display: none; line-height: 1.4; }

/* Список кривых (две строки: формула + роль) */
.curve-row {
  display: flex; flex-direction: column;
  padding: 7px 0; border-bottom: var(--line) solid var(--border);
}
.curve-row:last-child { border-bottom: none; }
.crow-top { display: flex; align-items: center; gap: 7px; }
/* Второй ряд строки кривой: имя на графике и роль. Открывается шевроном —
   нужны они заметно реже формулы (Фаза 10). */
.crow-more { display: none; gap: 6px; margin-top: 6px; padding-left: 22px; }
.crow-more.open { display: flex; }
.crow-more > * { flex: 1 1 0; min-width: 0; width: auto; }
.crow-gear { color: var(--text3); }
.crow-gear svg { width: 13px; height: 13px; display: block; transition: transform .18s var(--ease); }
.crow-gear[aria-expanded="true"] svg { transform: rotate(180deg); }
.crow-gear:hover { color: var(--accent); }
.crow-top > .curve-expr-inp { flex: 1 1 auto; min-width: 0; }
/* А66. Формула кривой правится тем же полем, что и новая (набранная запись,
   клавиатура, примеры). Оснащение не нашло обёртки .f-wrap и пометило ею саму
   строку, а поле убрало внутрь .f-row. Гибкость, которая была у поля, нужна
   теперь этой строке-обёртке, иначе формула сжимается в узкий столбик. */
/* Строка формулы кривой: отдельная линия во всю ширину под именем. */
.crow-formula { margin-top: 5px; }
.crow-formula .f-row { width: 100%; }

/* Обозначение кривой в строке списка (Фаза 3, сцена сложения). Стоит перед
   именем и повторяет подпись на графике — этим связь списка с холстом и
   держится. Цвет ставит скрипт: он равен цвету самой линии. */
.crow-tag {
  flex: 0 0 auto; font-size: 12px; line-height: 1; min-width: 15px;
}
.crow-tag .katex { font-size: 1em; }

/* Место поля формулы у суммарной кривой. Поля у неё нет и быть не может:
   запись считается из формул групп на каждой перерисовке. Пустое место
   читалось как недоделанная строка, поэтому вместо него стоит прямая
   надпись — той же высоты, что поле, но явно неактивная. */
.crow-auto {
  margin-top: 5px; padding: 6px 8px;
  border: 1px dashed var(--border); border-radius: var(--r);
  background: transparent; color: var(--text2);
  font-size: 11px; line-height: 1.35;
}
/* ⚠️ ЧЕРНИЛА ЗДЕСЬ --text2, А НЕ --text3, И ЭТО ЗАМЕР.
   Третья ступень текста даёт на поверхности панели 4,51:1 при норме канона
   4,6 — проверка контраста краснела бы на двух новых строках (замер 25.08,
   canon_checks по сцене sdsum: 3 нарушения до правки, 5 после). Строка и так
   читается как неактивная: пунктирная рамка, мелкий кегль и прямая надпись. */
.role-sel {
  width: 100%; padding: 4px 6px; border: 1px solid var(--border);
  border-radius: var(--r); background: var(--input-bg); font-size: 12px; color: var(--text2);
}
/* Б23: маркер цвета крупнее галочки — по нему кривую узнают на графике. */
.swatch { width: 16px; height: 16px; border-radius: var(--r-sm); flex-shrink: 0; }
/* Кликабельный образец = нативный input[type=color] (Фаза 2). */
.swatch-pick {
  -webkit-appearance: none; appearance: none;
  width: 17px; height: 17px; padding: 0; cursor: pointer;
  border: 1px solid var(--border-soft); border-radius: var(--r-sm); background: none;
  transition: border-color .12s var(--ease), transform .12s var(--ease);
}
.swatch-pick::-webkit-color-swatch-wrapper { padding: 0; }
.swatch-pick::-webkit-color-swatch { border: none; border-radius: var(--r-sm); }
.swatch-pick::-moz-color-swatch { border: none; border-radius: var(--r-sm); }
.swatch-pick:hover { border-color: var(--accent); transform: scale(1.12); }

/* ── Выбор цвета: шесть образцов + свой (П34) ──────────────────────────
   Обёртка держит место в строке, а само меню уезжает в <body> с fixed —
   иначе панель с прокруткой обрезала бы его по своему краю. */
.cpick { display: inline-flex; align-items: center; flex-shrink: 0; vertical-align: middle; }

/* ── Свой раскрывающийся список (А64 · А32) ────────────────────────────────
   Нативный select не переносит текст, а режет его: «Что добавляем» имел 179px
   при нужных 208. Кнопка ниже показывает выбранный вариант целиком, а меню
   уезжает в body с position:fixed, чтобы прокрутка панели его не обрезала. */
.sel-native-hidden { display: none !important; }
.sel-btn {
  display: flex; align-items: center; gap: 6px; width: 100%; min-width: 0;
  padding: 4px 8px; text-align: left; cursor: pointer;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text); font: inherit; font-size: 12px; font-weight: 500;
  transition: border-color .12s var(--ease);
}
.sel-btn:hover { border-color: var(--accent); }
.sel-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.sel-btn.sel-empty .sel-text { color: var(--text3); }
.sel-text { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sel-caret { flex: 0 0 auto; color: var(--text3); line-height: 1; }
.sel-menu {
  position: fixed; z-index: var(--z-picker); max-height: 60vh; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r); box-shadow: var(--shadow-pop); padding: 4px;
}
.sel-item {
  display: block; width: 100%; text-align: left; padding: 5px 8px; cursor: pointer;
  border: none; border-radius: var(--r); background: transparent;
  color: var(--text); font: inherit; font-size: 12px; white-space: normal;
}
.sel-item:hover { background: var(--surface-2); }
.sel-item.active { color: var(--accent); font-weight: 600; }
/* Имя, набранное прямо в строке (А6): то же правило, что у чисел. */
.edval-text { display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; vertical-align: bottom; }
/* Само поле остаётся в разметке (в нём значение и его слушатели), но не видно. */
.text-up-hidden { display: none !important; }
.cpick-btn {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px; padding: 0; cursor: pointer;
  border: 1px solid var(--border-soft); border-radius: var(--r-sm);
  transition: border-color .12s var(--ease), transform .12s var(--ease);
}
.cpick-btn:hover { border-color: var(--accent); transform: scale(1.12); }
.cpick-menu {
  position: fixed; z-index: var(--z-picker);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r); box-shadow: var(--shadow-pop);
  padding: 8px; width: 148px;
}
.cpick-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.cpick-sw {
  position: relative; height: 30px; border-radius: var(--r);
  border: 1px solid var(--border-soft); cursor: pointer; padding: 0;
  transition: transform .12s var(--ease);
}
.cpick-sw:hover { transform: scale(1.06); }
/* Галочка на текущем цвете — как у Desmos. Рисуем двумя полосками, чтобы
   не тащить иконку и чтобы она читалась на любом фоне образца. */
.cpick-sw[aria-checked="true"]::after {
  content: ''; position: absolute; left: 50%; top: 47%;
  width: 6px; height: 11px; margin: -7px 0 0 -3px;
  border: solid var(--sw-check); border-width: 0 2.2px 2.2px 0;
  transform: rotate(42deg);
  filter: drop-shadow(0 0 1.5px rgba(0,0,0,.55));
}
.cpick-own {
  display: flex; align-items: center; gap: 7px; width: 100%;
  margin-top: 8px; padding: 6px 7px; font-size: 12px; color: var(--text2);
  background: var(--surface-2); border: 1px solid var(--border-soft);
  border-radius: var(--r); cursor: pointer; text-align: left;
}
.cpick-own:hover { border-color: var(--accent); color: var(--text); }
.cpick-own input[type="color"] {
  -webkit-appearance: none; appearance: none;
  width: 15px; height: 15px; padding: 0; border: none; border-radius: var(--r-sm);
  background: none; cursor: pointer; flex-shrink: 0;
}
.cpick-own input::-webkit-color-swatch-wrapper { padding: 0; }
.cpick-own input::-webkit-color-swatch { border: 1px solid var(--border); border-radius: var(--r-sm); }
.cpick-own input::-moz-color-swatch { border: 1px solid var(--border); border-radius: var(--r-sm); }
/* Поле «имя кривой на графике» в списке кривых (Фаза 1). */
.curve-label-inp {
  width: 100%; padding: 4px 6px; border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text); font-size: 12px;
}
/* Формула кривой правится на месте. Моноширинный шрифт как у подписи кривой,
   красная рамка = формулу пока не поняли и старая кривая осталась на графике. */
.curve-expr-inp {
  width: 100%; padding: 4px 6px; border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text); font-size: 12px;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
}
.curve-expr-inp.bad { border-color: var(--c-bad); }
.curve-name {
  flex: 1; font-size: 12px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; font-family: ui-monospace, 'SF Mono', Menlo, monospace; color: var(--text);
}
.btn-icon {
  border: none; background: none; color: var(--text2);
  font-size: 14px; padding: 2px 4px; line-height: 1; flex-shrink: 0;
  transition: color .12s var(--ease);
}
.btn-icon:hover { color: var(--c-bad); }

/* Подзаголовок внутри секции («Свои точки» в «Оформлении»). */
/* Подзаголовок ВНУТРИ секции: ступень ниже её заголовка, поэтому мельче и
   приглушён. Капитель здесь читалась бы как уровень выше, а не ниже. */
.vsub {
  display: flex; align-items: center;
  font-size: 12px; font-weight: 600; color: var(--text2);
  margin: 12px 0 7px; padding-top: 10px; border-top: var(--line) solid var(--border);
}
/* Кнопка «Поставить точку» во взведённом состоянии — ждёт щелчка по графику. */
.btn-sm.armed { color: var(--accent); border-color: var(--accent); background: var(--accent-tint); }
/* Строка своей точки: координаты, подпись, что показывать, удаление. */
.mark-row { padding: 8px 0; border-bottom: var(--line) solid var(--border); }
.mark-row:last-child { border-bottom: none; }
/* Пайплайн добавления точки (П28): тумблер способа ввода, подсказка и
   кнопка «Добавить точку», которая всегда стоит под уже созданными точками. */
.mark-mode { margin: 0 0 7px; }
.mark-mode .seg-btn { font-size: 11px; padding: 5px 6px; }
.mark-mode .seg-btn:disabled { cursor: default; opacity: .75; }
/* П59. Заливки сигнальным оттенком у блока ввода больше нет: розовая плашка
   читалась как ошибка (канон 1.1.2 — сигнальный цвет не для украшения).
   Что блок временный, говорит рецессивная поверхность и рамка. */
.mark-draft { background: var(--surface-2); border: var(--line) solid var(--border);
  border-radius: var(--r); padding: 8px; }
.mark-hint { font-size: 12px; font-weight: 600; color: var(--accent); padding: 4px 0 2px; }
.btn-mark-add { margin-top: 8px; }
.mark-top { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.mark-co {
  flex: 1; font-size: 12px; color: var(--text2); white-space: nowrap;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
}
.mark-row input[type="text"] {
  width: 100%; padding: 5px 7px; border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text); font-size: 12px;
}
.mark-row .chk { font-size: 12px; margin: 6px 0 0; }

/* Табло результатов (равновесие, площади, налог) */
.stat { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; padding: 4px 0; font-size: 13px; }
.stat span { color: var(--text2); }
.stat b { font-variant-numeric: tabular-nums; color: var(--text); }
/* В табло результатов число — главное, что человек ищет глазами, поэтому оно
   крупнее и тяжелее подписи. В «Инструментах» те же .stat остаются мелкими:
   там они справочные, а не отсчёт прибора. */
.sb-body .stat { padding: 5px 0; }
.sb-body .stat span { font-size: 12px; font-weight: 600; }
.sb-body .stat b { font-size: 13px; font-weight: 700; letter-spacing: -.01em; }
.warn { font-size: 12px; color: var(--c-warn); line-height: 1.4; }

/* Таблица «До / После / Δ» для налога */
.tx-table { width: 100%; border-collapse: collapse; font-size: 12px; margin-top: 4px; }
.tx-table th, .tx-table td { padding: 3px 4px; text-align: right; border-bottom: var(--line) solid var(--border); }
.tx-table th:first-child, .tx-table td:first-child { text-align: left; color: var(--text2); }
.tx-table th { color: var(--text2); font-weight: 600; }
.tx-table td { font-variant-numeric: tabular-nums; }

/* Ползунок */
input[type="range"] { width: 100%; accent-color: var(--accent); margin-top: 2px; }

/* Переключатель «Налог / Субсидия» */
/* П55. Сегмент по канону 3.9: обёртка-пилюля с внутренним полем, кнопки
   с настоящими горизонтальными отступами и переносом на новый ряд. Прежде у
   кнопок был нулевой отступ и запрет переноса — «Равновесие» и «Эластичность»
   стояли вплотную, текст упирался в границы, а пять вариантов ложились в два
   ряда РАЗНОЙ высоты (28 и 42 px). */
.seg {
  display: flex; flex-wrap: wrap; gap: 2px; padding: 3px;
  background: var(--surface-2);
  border: var(--line) solid var(--border); border-radius: var(--r-pill);
}
.seg-btn { flex: 1 1 auto; min-width: 0; padding: 6px 14px; border: none;
  border-radius: var(--r-pill); background: transparent; color: var(--text2);
  font-size: 12px; font-weight: 600;
  transition: background .12s var(--ease), color .12s var(--ease); }
.seg-btn:hover { color: var(--text); }
.seg-btn.active { background: var(--accent); color: var(--on-accent); }
.seg-btn.active:hover { color: var(--on-accent); }
/* Разделителя между сегментами нет: их разводит зазор обёртки. */

/* Область графика справа */
.graph-wrap {
  /* Предел ширины графика (А57): ниже него сжимаются панели, а не он. */
  flex: 1 1 auto; min-width: 320px;
  position: relative; background: var(--canvas); overflow: hidden;
}
#chart { display: block; width: 100%; height: 100%; }

/* Окошко с координатами у курсора, пока катят точку по кривой (Фаза 3).
   Обычный div поверх холста: SVG на каждом движении мыши не трогаем. */
#roll-tip {
  position: absolute; z-index: 30; display: none; pointer-events: none;
  padding: 4px 8px; border-radius: var(--r);
  background: var(--surface); border: var(--line) solid var(--border);
  box-shadow: 0 2px 10px rgba(0,0,0,.14);
  font-size: 12px; font-weight: 600; color: var(--text);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Заглушка-подсказка по центру пустого графика (шаг 1) */
.empty-hint {
  position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center;
  color: var(--text2); font-size: 13px; pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
}

/* ═══════════════════════════════════════════════════════════════════
   ЭКРАН 1 — ОКНО ВЫБОРА СЦЕНАРИЯ
   Полноэкранный слой поверх рабочего места. Каждая карточка — мини-график
   в визуальном языке движка (тот же набор цветов кривых).
   ═══════════════════════════════════════════════════════════════════ */
.picker {
  position: fixed; inset: 48px 0 0 0; z-index: var(--z-picker);
  background: var(--bg); overflow-y: auto;
  opacity: 1; transition: opacity .26s var(--ease), visibility .26s var(--ease);
}
/* visibility:hidden убирает скрытое окно из дерева доступности и из таб-порядка
   (одного opacity:0 для этого недостаточно). */
.picker.hidden { opacity: 0; visibility: hidden; pointer-events: none; }
.picker-inner { max-width: 1100px; margin: 0 auto; padding: 44px 28px 64px; }
.picker-head { margin-bottom: 28px; }
/* А41 · А68. На экране ОТКРЫТОГО блока шапки первого экрана нет. Кикер,
   заголовок «С чего начнём?» и инструкция «Откройте раздел и выберите модель»
   вместе с кнопкой возврата занимали 332 пикселя над первой карточкой: при
   высоте окна 392 карточка была видна на пятую часть, а инструкция про выбор
   раздела показывалась уже ПОСЛЕ того, как раздел открыт. */
#scene-picker.block-open .picker-head { display: none; }
#scene-picker.block-open .picker-inner { padding-top: 20px; }
.picker-eyebrow {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 12px;
}
.picker-eyebrow::before {
  content: ""; width: 18px; height: 2px; border-radius: var(--r-pill); background: var(--accent);
}
.picker-title { font-size: 22px; font-weight: 600; letter-spacing: -.02em; color: var(--text); }
/* Н3: подзаголовок укладывается ровно в две строки. При 56ch «экономике!»
   уезжало одно на третью; 68ch хватает, чтобы фраза легла надвое. */
.picker-sub { font-size: 15px; color: var(--text2); margin-top: 6px; max-width: 68ch; line-height: 1.5; }

/* ── П2. Главный экран: десять больших карточек блоков по две в ряд ────
   Щелчок по карточке убирает остальные и на их месте показывает модели этого
   блока. Второго экрана нет — всё на одной странице. */
.picker-blocks {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px; padding-bottom: 24px;
}
.picker-blocks.hidden { display: none; }
/* А42. Карточка блока была во всю ширину, а занята была только левая
   половина: картинка, название и счётчик жались друг к другу, а справа
   оставалось 555 пустых пикселей. Карточки стали уже и идут по две в ряд —
   на экране их видно вдвое больше, и пустоты не остаётся.
   Третья строка сетки отдана перечню моделей блока: он и заполняет карточку
   содержанием, а не воздухом. */
/* ⚠️ РАЗНОБОЙ ВЫСОТ КАРТОЧЕК УХОДИТ ВМЕСТЕ С ПЕРЕЧНЕМ МОДЕЛЕЙ — ЭТО ОДНА
   ПРАВКА, А НЕ ДВЕ. Замер десяти карточек: 162, 162, 194, 194, 178, 178, 210,
   210, 178, 178 — пять разных значений. Перечень занимал от 32 до 96 px, а
   карточки в ряду тянутся по самой высокой.
   Осталось ровно две строки текста: название и счётчик. Компоновка прежняя,
   горизонтальная — картинка слева, текст справа; размеры из согласованного
   макета (картинка 132×82, название 18 px), высота у всех блоков одна. */
.bcard {
  display: grid; grid-template-columns: 132px minmax(0, 1fr);
  grid-template-rows: auto auto; align-content: center;
  gap: 2px 16px; text-align: left; cursor: pointer; color: inherit;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-card); padding: 14px;
  transition: transform .16s var(--ease), border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.bcard-spec {
  grid-row: 1 / 3; align-self: center;
  width: 132px; height: 82px;
  border-radius: var(--r); background: var(--canvas);
  border: 1px solid var(--border-soft); overflow: hidden;
}
.bcard-spec svg { display: block; width: 100%; height: 100%; }
.bcard-name {
  align-self: end; font-size: 18px; font-weight: 600; letter-spacing: -.02em; color: var(--text);
}
.bcard-count { align-self: start; font-size: 13px; font-weight: 400; color: var(--text2); }
.bcard:hover { transform: translateY(-3px); border-color: var(--accent); box-shadow: var(--shadow-pop); }
.bcard:focus-visible { border-color: var(--accent); outline: 2px solid var(--focus); outline-offset: 2px; }
.bcard:active { transform: translateY(-1px) scale(.995); }
/* Первую карточку окно фокусирует программно при открытии (доступность —
   Tab/Enter срабатывают сразу), но браузер не отличает такой фокус от
   настоящего Tab и рисует кольцо на пустом месте, ещё до первого касания
   мышью или клавиатурой. Кольцо гасим только на время этого «служебного»
   фокуса — см. openPicker() в 84-picker.js, класс снимается на первое же
   действие человека, и дальше :focus-visible снова решает сам браузер. */
.bcard.no-init-ring:focus-visible { border-color: var(--border); outline: none; }
/* ⚠️ И ОТДЕЛЬНО ПРО НАВЕДЕНИЕ — ИНАЧЕ ПЕРВАЯ КАРТОЧКА НЕ ПОДСВЕЧИВАЕТСЯ.
   Правило выше весит два класса плюс псевдокласс и бьёт `.bcard:hover` по
   весу: под курсором первая карточка оставалась с обычной серой рамкой, пока
   человек не нажмёт клавишу или кнопку мыши (тогда класс снимается). Замер
   25.08: border первой карточки при наведении rgba(22,26,38,.12), второй —
   rgb(190,24,93). Гасить надо КОЛЬЦО, а не подсветку, поэтому наведению
   возвращаем его цвет рамки, добавив тот же :hover и сюда. */
.bcard.no-init-ring:focus-visible:hover { border-color: var(--accent); }

/* Возврат ко всем блокам. Показывается только внутри открытого блока. */
.picker-back {
  display: none; align-items: center; gap: 6px; margin-bottom: 14px;
  padding: 7px 12px 7px 8px; background: transparent;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  font-size: 13px; font-weight: 600; color: var(--text2); cursor: pointer;
}
.picker-back.shown { display: inline-flex; }
.picker-back:hover { color: var(--accent); border-color: var(--accent); }
.picker-back svg { width: 15px; height: 15px; }
.picker-group-open-name {
  font-size: 22px; font-weight: 600; letter-spacing: -.02em;
  color: var(--text); margin-bottom: 14px;
}

/* Блок показывается целиком или не показывается вовсе. */
.picker-group { display: none; }
.picker-group.open { display: block; }
.picker-grid {
  display: grid; gap: 14px; padding-bottom: 18px;
  grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
}

/* Плавное появление: и карточки блоков, и модели внутри блока въезжают
   одинаково. Уважает системную настройку «меньше движения». */
@media (prefers-reduced-motion: no-preference) {
  .picker-blocks:not(.hidden) > .bcard,
  .picker-group.open > .picker-grid > .scard {
    animation: pickerRise .26s var(--ease) both;
  }
  .picker-group.open > .picker-group-open-name { animation: pickerRise .2s var(--ease) both; }
  @keyframes pickerRise { from { opacity: 0; transform: translateY(8px); } }
  .picker-blocks > .bcard:nth-child(2n)   { animation-delay: .03s; }
  .picker-blocks > .bcard:nth-child(n+3)  { animation-delay: .05s; }
  .picker-blocks > .bcard:nth-child(n+7)  { animation-delay: .08s; }
}

.scard {
  display: flex; flex-direction: column; text-align: left;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card);
  padding: 14px; gap: 12px; cursor: pointer; color: inherit;
  transition: transform .16s var(--ease), border-color .16s var(--ease), box-shadow .16s var(--ease);
}
.scard:hover { transform: translateY(-3px); border-color: var(--accent); box-shadow: var(--shadow-pop); }
.scard:focus-visible { border-color: var(--accent); outline: 2px solid var(--focus); outline-offset: 2px; transform: translateY(-3px); }
.scard:active { transform: translateY(-1px) scale(.995); }
.scard-spec {
  border-radius: var(--r); background: var(--canvas);
  border: 1px solid var(--border-soft); overflow: hidden; aspect-ratio: 16 / 9;
}
.scard-spec svg { display: block; width: 100%; height: 100%; }
.scard-name { font-size: 15px; font-weight: 600; color: var(--text); letter-spacing: -.01em; }
.scard-desc { font-size: 13px; color: var(--text2); line-height: 1.45; margin-top: -4px; }


/* Карточка сюжета, который ещё готовится. Видна, чтобы карта блоков была
   полной, но не нажимается: disabled + приглушение + бейдж «скоро». */
/* Переключатель, который карточка блока уже зафиксировала. Класс, а не
   инлайновый display: обычная логика видимости продолжает распоряжаться
   style.display, а этот класс просто перекрывает её сверху. */
.scoped-off { display: none !important; }

/* А38. Карточка «Скоро» должна читаться как нерабочая С ПЕРВОГО ВЗГЛЯДА.
   Раньше она была лишь слегка приглушена, но имела полноценную картинку и
   описание, и человек тыкал в неё, не понимая, почему ничего не происходит.
   Это тем важнее, что счётчик блока по решению штаба считает и запланированные
   модели: «9 моделей» при двух рабочих обязано быть видно на самих карточках.

   Картинка сведена к силуэту, текст приглушён, курсор обычный, наведение и
   нажатие ничего не делают. Плашка «Скоро», наоборот, контрастная. */
/* п. 65. Недоступная модель показана ЦВЕТОМ и пунктиром рамки, а не общей
   прозрачностью: `opacity` гасит и без того бледную рамку, а на карточке с
   именем в две строки заодно съедала контраст бейджа. Правило то же, что в
   платформе: приглушение прозрачностью не применяется. */
.scard.soon { cursor: default; border-style: dashed; background: transparent; }
.scard.soon:hover, .scard.soon:active { transform: none; border-color: var(--border); box-shadow: none; }
.scard.soon .scard-spec { filter: grayscale(1); }
.scard.soon .scard-name, .scard.soon .scard-desc { color: var(--text3); }
.scard.soon .scard-soon {
  color: var(--on-accent, #fff); background: var(--text3);
  border-color: var(--text3); opacity: 1;
}
/* п. 65. Бейдж прижат к ВЕРХУ строки: при имени в две строки середина
   уезжает вниз, и подпись «СКОРО» вставала напротив второй строки, разрывая
   шапку карточки. */
.scard-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.scard-soon {
  flex-shrink: 0; font-size: 11px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--text3);
  border: 1px solid var(--border); border-radius: var(--r-pill); padding: 2px 7px;
}

@media (max-width: 640px) {
  .picker-inner { padding: 28px 16px 48px; }
  .picker-title { font-size: 22px; }
  .picker-grid { grid-template-columns: 1fr; }
}

/* ═══════════════════════════════════════════════════════════════════
   ЭКРАН 2 — РАБОЧЕЕ МЕСТО «СЦЕНА»
   Четыре колонки в ряд:
     [док 56] [Ввод функций + Аналитика 316] [график] [Основные параметры 268]
   Панели больше не висят НАД холстом, они его соседи. Свернул панель,
   график стал шире, и ни одна кривая не осталась под стеклом.
   ═══════════════════════════════════════════════════════════════════ */
.app { position: relative; display: flex; align-items: stretch; height: calc(100vh - 48px); overflow: hidden; }
/* Выделение текста на холсте выключено: при панорамировании трекпадом иначе
   подсвечиваются числа на осях и подписи кривых. Поля ввода поверх графика
   (переименование точки, меню плоскости) выделение возвращают себе сами. */
.graph-wrap {
  position: relative; flex: 1 1 auto; min-width: 0; background: var(--canvas);
  user-select: none; -webkit-user-select: none;
}
.graph-wrap input, .graph-wrap textarea, .graph-wrap select { user-select: text; -webkit-user-select: text; }
#chart { display: block; width: 100%; height: 100%; }
#chart text { user-select: none; -webkit-user-select: none; }

/* ── Левый док: постоянная полоса иконок ── */
/* Полоса иконок лежит ВЫШЕ боковых панелей: её подсказки вылетают вправо и
   раньше уходили под свёрнутую панель ввода. z-index у подсказки помочь не мог —
   она внутри своего слоя, и весь слой был ниже панели. */
/* Н13. Три стрелки лежат на одной линии: «назад к сценариям» в доке и две
   сворачивалки панелей. Шапка панели это полоса ровно 46px, её стрелка стоит по
   центру полосы (23px от верха). Кнопка дока высотой 40px попадает туда же при
   отступе 3px, поэтому верхний отступ здесь не «ритм по 4px», а привязка к той
   же полосе; нижний отступ остаётся прежним. */
.dock {
  position: relative; flex: 0 0 56px; z-index: var(--z-drawer);
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 3px 0 12px; background: var(--surface-2);
  border-right: 1px solid var(--border);
}
.dock-sep { width: 24px; height: 1px; background: var(--border); margin: 6px 0; }
.dock-spacer { flex: 1; }
.dock-btn {
  width: 40px; height: 40px; border: 1px solid transparent; border-radius: var(--r);
  background: transparent; color: var(--text2); display: grid; place-items: center;
  position: relative; transition: background .12s var(--ease), color .12s var(--ease);
}
.dock-btn svg { width: 20px; height: 20px; }
.dock-btn:hover { background: var(--surface); color: var(--text); }
.dock-btn.active { background: var(--accent-tint); color: var(--accent); }
.dock-btn:disabled { opacity: .4; cursor: not-allowed; }
/* п. 78. Своей подписи у кнопки полосы больше нет: подсказку показывает одна
   общая плашка (#hint-tip), та же, что у знаков «?». Правило `::after`,
   привязанное к :focus-visible, держало подпись и ПОСЛЕ нажатия. */

/* ── Боковые панели ──────────────────────────────────────────────────
   Обе устроены одинаково и различаются только стороной. Свёрнутая панель
   ужимается до полосы 26px, на которой остаётся одна стрелка, поэтому
   развернуть её можно всегда, не гадая, куда она делась. */
.side {
  position: relative; flex: 0 0 auto; z-index: var(--z-float);
  display: flex; flex-direction: column; background: var(--surface);
  transition: width .22s var(--ease);
}
/* А57. Сжимался ТОЛЬКО график. У панелей стояло flex-grow: 0 и flex-shrink: 0
   при жёсткой ширине, поэтому при окне 782 пикселя графику доставалось 262 из
   них: рейка 56, панели по 232, а остаток графику. При окне уже 520 график
   исчез бы совсем.

   Теперь панели тоже умеют сжиматься, но не ниже своего предела, а у графика
   есть собственный предел, ниже которого его не отнимают. */
.side-left  { width: 268px; border-right: 1px solid var(--border); }
.side-right { width: 268px; border-left:  1px solid var(--border); }
.side-left, .side-right { flex: 0 1 auto; min-width: 200px; }
.side.collapsed { width: 26px; min-width: 26px; flex: 0 0 auto; }
/* п. 77. Имя свёрнутой панели читается вдоль её корешка. Видно только в
   свёрнутом виде: у раскрытой панели имя и так стоит в шапке. */
.side-spine {
  display: none; position: absolute; top: 52px; left: 0; width: 26px;
  writing-mode: vertical-rl; text-orientation: mixed;
  font-size: 11px; font-weight: 600; letter-spacing: .02em; color: var(--text3);
  white-space: nowrap; pointer-events: none;
  text-align: left;
}
.side.collapsed .side-spine { display: block; }
.side-right .side-spine { transform: rotate(180deg); transform-origin: center; top: auto; bottom: 52px; }
.side-inner { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.side.collapsed .side-inner { display: none; }

.side-arrow {
  /* П7. Стрелка стоит по ЦЕНТРУ шапки, а не на отмеренных сверху 19 пикселях:
     высота шапки зависела от содержимого, и стрелка съезжала. Шапка теперь
     фиксированной высоты (.side-head), поэтому центр считается от неё. */
  position: absolute; top: 0; height: 46px; width: 22px; z-index: 2;
  display: grid; place-items: center; border: none; border-radius: var(--r);
  background: transparent; color: var(--text3);
  transition: background .12s var(--ease), color .12s var(--ease);
}
.side-left  .side-arrow { right: 2px; }
.side-right .side-arrow { left: 2px; }
.side-arrow:hover { background: var(--surface-2); color: var(--text); }
/* п. 78. Подпись стрелки показывает та же общая плашка, что и у всех
   остальных подсказок калькулятора. */

.side-arrow svg { width: 15px; height: 15px; transition: transform .22s var(--ease); }
.side.collapsed .side-arrow svg { transform: rotate(180deg); }

/* Заголовок панели уступает место стрелке сворачивания: у левой панели стрелка
   справа, у правой — слева, поэтому и отступы зеркальные. */
/* П6. Обе шапки — одной высоты, одного кегля и с линией на одном уровне.
   Слева заголовок это название модели, справа «Аналитика»; раньше слева было
   две строки в колонку, справа одна, и подписи стояли на разной высоте.
   Высота задана явно (не «сколько получится»), чтобы линии сошлись пиксель
   в пиксель при любой длине названия. */
.side-head {
  display: flex; align-items: center; gap: 8px; flex-shrink: 0;
  /* Отступы симметричные: стрелка лежит поверх шапки только с одной стороны, но
     при 34px и 14px заголовок вставал на десяток пикселей мимо центра (Н4). */
  /* А3 · А54. Место под заголовок было 163px при узком окне и 199 при широком,
     а самое длинное название требовало 321px: многоточием обрезались 23
     названия из 41. Решение штаба от 13.08 — переносить на две строки, не
     сокращая названий и не уменьшая кегль.

     Высота шапки ПОСТОЯННА и задана с запасом: при плавающей высоте
     содержимое ниже прыгало бы при переходе между сценами с коротким и
     длинным названием. Отступы по бокам уменьшены с 34px до 24px — стрелке
     хватает, а заголовку достаётся двадцать лишних пикселей на строку.
     ⚠️ Самое длинное название в комментарии не цитируем: правки имён моделей
     этот текст не переживает, а проверки читают файл целиком. */
  height: 74px; padding: 6px 24px;
  border-bottom: 1px solid var(--border-soft);
}
/* Н4. Название модели слева и «Аналитика» справа — одним кеглем и по центру
   своей панели. Стрелка-сворачивалка лежит поверх шапки в отступе 34px, поэтому
   ширину под заголовок занимает вся строка, а flex:1 с центрированием ставит
   его ровно посередине панели независимо от длины названия. */
.side-head h2, .side-scene {
  flex: 1; min-width: 0; text-align: center;
  font-size: 15px; font-weight: 700; letter-spacing: -.015em; color: var(--text);
  /* Переносим по словам; многоточия быть не должно. Три строки разрешены для
     самых длинных названий, дальше слово переносится по буквам, лишь бы текст
     не пропал. */
  white-space: normal; overflow-wrap: break-word; word-break: break-word;
  line-height: 1.22; max-height: calc(1.22em * 3);
}
.side-scroll { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }

/* Две части левой панели: ввод сверху, аналитика снизу. */
.side-part { border-top: 1px solid var(--border-soft); }
.side-part:first-child { border-top: none; }
.part-title {
  position: sticky; top: 0; z-index: 1; background: var(--surface);
  padding: 12px 14px 6px;
  font-size: 11px; font-weight: 600; letter-spacing: .07em; text-transform: uppercase;
  color: var(--text3);
}
.tools-body { padding: 4px 14px 14px; }
.tools .panel-only { display: none; }   /* h1/subtitle старой панели прячем */

/* Аналитика читается как приборная панель: крупное число, тихая подпись. */
#scoreboard.hidden, #explain.hidden { display: none; }
/* Панели, в которой нечего показать, на экране нет вовсе: пустая колонка
   только отнимала бы место у графика. */
.side.empty { display: none; }
/* Складные блоки правой панели: заголовок с отступами панели, тело без
   лишнего верхнего зазора — .sb-body свой отступ уже несёт. */
/* П14. «Ключевые значения» и «Объяснение модели» обрамлены так же, как блоки
   левой панели: рамка, скругление, свой фон, тот же кегль заголовка, раскрытая
   карточка меняет фон. Эталон — блок «КПВ и торговля» в панели ввода. Раньше
   справа не было ни рамки, ни фона, только верхняя линия, и два столбца
   читались как разные интерфейсы. */
#params-panel .side-part {
  border: 1px solid var(--border-soft); border-radius: var(--r);
  background: var(--surface); margin: 0 14px 7px; overflow: hidden;
}
#params-panel .side-part:first-child { margin-top: 10px; }
#params-panel .side-part > .fold-btn {
  padding: 10px 11px; font-size: 13px; font-weight: 600; letter-spacing: -.01em;
}
#params-panel .side-part.open-card { background: var(--surface-2); border-color: var(--border); }
#params-panel .fold-body { padding: 0 11px 11px; }
#params-panel .sb-body { padding: 4px 0 6px; }
.sb-body { padding: 4px 14px 16px; }
.sb-body .section { margin-bottom: 12px; }
.sb-body .section:last-child { margin-bottom: 0; }
.sb-body .section-title { margin-top: 8px; }
.sb-body .stat { padding: 5px 0; align-items: baseline; gap: 10px; }
.sb-body .stat span { font-size: 12px; }
.sb-body .stat b { font-size: 13px; font-weight: 700; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
.sb-empty { font-size: 13px; color: var(--text2); line-height: 1.5; padding: 6px 0; }
/* Пустое состояние по канону 3.13: заголовок 13/600, текст 13/400, по центру.
   Говорит две вещи — чего здесь нет и что здесь появится. */
.k-empty { text-align: center; padding: 18px 12px 14px; color: var(--text2); }
/* П16. Строка требования под полем формулы. Канон 2.4: место занято всегда,
   поэтому появление фразы НЕ двигает вниз то, что под ней. Пустая строка той
   же высоты — та же мысль, что у скрытой кнопки возврата масштаба. */
.f-why { min-height: 16px; margin-top: 2px; font-size: 12px; line-height: 1.3;
  color: var(--error); visibility: hidden; }
.f-why.is-bad { visibility: visible; }
/* Линия — второй носитель ошибки рядом с цветом и фразой. */
.f-slot.is-bad math-field, .f-slot.is-bad input { border-color: var(--error); }
.k-empty > b { display: block; font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
.k-empty > p { margin: 0; font-size: 13px; font-weight: 400; line-height: 1.5; }
.sb-body > div:not(:empty) { margin-bottom: 12px; }
.sb-body > div:empty { margin: 0; }

/* ── Правая панель «Основные параметры» ──────────────────────────────
   Здесь живут живые регуляторы активной сцены. Каждый в две строки:
   сверху имя и значение, снизу ползунок во всю ширину, так число видно
   не щурясь, а тянуть удобно даже в узкой колонке. */
#params-body { padding: 6px 14px 18px; display: flex; flex-direction: column; gap: 13px; }
#params-empty { font-size: 13px; color: var(--text2); line-height: 1.5; }
#params-curves, #params-extra { display: contents; }
.pchip { display: flex; flex-direction: column; gap: 5px; }
.pchip-top { display: flex; align-items: center; gap: 7px; }
.pchip-dot { width: 9px; height: 9px; border-radius: var(--r-pill); flex-shrink: 0; }
.pchip-label {
  font-size: 12px; font-weight: 600; color: var(--text2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pchip-val {
  margin-left: auto; font-size: 13px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums; flex-shrink: 0;
}
.pchip input[type="range"] { width: 100%; margin: 0; }
/* Перенесённые сюда поля сцены (ставка, цена, зарплата, мировая цена, альфа)
   уже свёрстаны в две строки, им хватает обнуления внешнего отступа. */
/* Регулятор сцены выглядит ровно как ползунок буквы-параметра: имя слева,
   значение справа крупным, дорожка во всю ширину под ними. Раньше это были две
   разные на вид штуки в одной колонке (Фаза 10). */
#params-body .field { margin: 0; display: flex; flex-direction: column; gap: 5px; }
/* Подпись регулятора читается одной фразой («Ставка t = 20»), поэтому она
   обычная строка, а не колонки: разгонять её по краям было бы враньём про
   структуру — там не «имя и значение», там предложение. */
#params-body .field > label {
  font-size: 12px; font-weight: 600; color: var(--text2); margin: 0;
}
#params-body .field > label b {
  font-size: 13px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums;
}
/* Поле точного значения: спокойное, пока в него не целятся. */
#params-body .field input[type="number"] {
  width: 56px; padding: 3px 5px; font-size: 12px; text-align: right;
  border: 1px solid transparent; background: none; color: var(--text);
  font-variant-numeric: tabular-nums; border-radius: var(--r);
}
#params-body .field input[type="number"]:hover { border-color: var(--border); }
#params-body .field input[type="number"]:focus { border-color: var(--accent); background: var(--input-bg); outline: none; }
#params-body .hint { font-size: 12px; margin-top: 5px; }
#params-body .seg { margin: 0; }

/* Сворачиваемый блок в панели (расчёт площадей). */
.fold-btn {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  padding: 7px 0; background: transparent; border: none;
  font-size: 13px; font-weight: 600; letter-spacing: -.005em;
  color: var(--text);
}
.fold-btn:hover { color: var(--accent); }
.fold-btn > span { display: flex; align-items: center; gap: 8px; min-width: 0; }
.fold-btn > span > b { font: inherit; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* П9: иконка блока — та же геометрия, что у превью моделей в окне сценариев. */
.sec-ico { width: 17px; height: 17px; flex-shrink: 0; color: var(--text3); }
.section.open-card > .fold-btn .sec-ico,
.section.first-card > .fold-btn .sec-ico { color: currentColor; }
/* Н67: переворачивается ТОЛЬКО шеврон, у него поворот и означает «раскрыто».
   Смысловая иконка блока остаётся на месте: перевёрнутая площадь или точка
   ничего не сообщают, а читать их вверх ногами мешают. Шеврон лежит прямым
   потомком кнопки, иконка внутри <span>, поэтому хватает дочернего селектора. */
.fold-btn > svg { width: 13px; height: 13px; transition: transform .18s var(--ease); }
.fold-btn[aria-expanded="true"] > svg { transform: rotate(180deg); }
.fold-body { display: none; padding-top: 4px; }
.fold-body.open { display: block; }

/* ── Карточки панели ввода (Фаза 5) ──────────────────────────────────
   Каждая часть — отдельный блок с ярким заголовком. Закрытая карточка
   спокойная, раскрытая меняет фон: видно, что это своя карточка, а не
   продолжение ленты. Первая видимая — ярче остальных. */
.tools-body > .section.card {
  border: 1px solid var(--border-soft); border-radius: var(--r);
  background: var(--surface); margin-bottom: 7px; overflow: hidden;
}
.tools-body > .section.card:last-child { margin-bottom: 0; }
.tools-body > .section.card > .fold-btn {
  padding: 10px 11px; font-size: 13px; font-weight: 600; letter-spacing: -.01em;
}
.tools-body > .section.card > .fold-body { padding: 0 11px 11px; }
.tools-body > .section.card.open-card { background: var(--surface-2); border-color: var(--border); }
/* Н2. Первая карточка выделена РАЗМЕРОМ, а не цветом. Прежде у неё были и
   акцентная рамка, и акцентный текст, и карточка читалась как нажатая, хотя
   ничего не выбрано. Акцент в этой системе — для мелких точек управления, а не
   для заливки целого блока. */
.tools-body > .section.card.first-card > .fold-btn { font-size: 13px; color: var(--text); }
.tools-body > .section.card.first-card { border-color: var(--border); }
/* Н35. Первой кнопке нужен воздух от линии-разделителя под заголовком панели:
   она стояла к ней вплотную и читалась как её продолжение. */
.tools-body > .section.card:first-child { margin-top: 8px; }

/* Стрелки-крутилки у числовых полей внутри калькулятора не нужны: значения
   здесь набирают и тянут ползунком, а не щёлкают по микроскопическим стрелкам.
   Только внутри .app: на остальных страницах сайта они на месте. */
.app input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.app input[type="number"]::-webkit-outer-spin-button,
.app input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* Полосы прокрутки внутри калькулятора не показываем (П35, П48). Сама
   прокрутка остаётся: и колесом, и клавишами, и тачпадом. Правило только
   внутри .app и у меню, которые уезжают в <body>, — остальные страницы
   сайта со своими полосами не трогаем. */
.app *, .cpick-menu, .modal-card {
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.app *::-webkit-scrollbar,
.cpick-menu::-webkit-scrollbar,
.modal-card::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* Набранные вершины будущей площади. */
.vert-btns { display: flex; gap: 6px; margin: 8px 0; }
/* Список вершин (П42): крупнее прежнего, у каждой строки крестик,
   по двойному щелчку координата правится прямо в строке. */
.vert-row {
  display: flex; gap: 8px; align-items: center; padding: 5px 0;
  font-size: 13px; color: var(--text2); border-bottom: var(--line) solid var(--border-soft);
}
.vert-row:last-child { border-bottom: none; }
.vert-row b { color: var(--text); font-variant-numeric: tabular-nums; }
.vert-co { flex: 1; min-width: 0; cursor: text; font-variant-numeric: tabular-nums; }
.vert-co:hover { color: var(--text); }
.vert-edit { flex: 1; display: flex; align-items: center; gap: 4px; min-width: 0; }
.vert-edit input {
  width: 100%; min-width: 0; flex: 1; padding: 3px 5px; font-size: 12px; text-align: center;
  border: 1px solid var(--accent); border-radius: var(--r);
  background: var(--input-bg); color: var(--text); font-variant-numeric: tabular-nums;
}
/* Пока не выбрана ни одна вершина — вместо списка одна надпись. */
.ac-empty { font-size: 13px; color: var(--text2); padding: 6px 0; }
/* Выбор кривой узкий, справа от него отрезок в скобках (П41). */
.ac-pick-row { display: flex; align-items: center; gap: 8px; }
.ac-pick-row select {
  flex: 1 1 auto; min-width: 0; padding: 5px 6px; font-size: 13px;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text);
}
/* Б16: отрезок — своя строка во всю ширину, а не остаток места справа от
   списка. Пока кривая не выбрана, строки нет вовсе (А65). */
.ac-range { display: block; margin-top: 6px; font-size: 12px; color: var(--text2);
  font-variant-numeric: tabular-nums; line-height: 1.5; }
.ac-range:empty { display: none; }
/* Кнопка расчёта не горит, пока считать нечего. */
#ac-calc:disabled { opacity: .45; cursor: not-allowed; }

.chk.dim { opacity: .72; }

/* Пояснение под полем в меню плоскости: что ещё можно сделать с этим же
   на самом графике. Обычный текст, не сигнал. */
.wrench-note { margin-top: 6px; font-size: 12px; line-height: 1.45; color: var(--text2); }

/* п. 25. Полоса режима холста. Поверхность и рамка, без сигнальной заливки:
   это не предупреждение, а название текущего состояния. Стоит вверху слева,
   где холст всегда пуст, и не перекрывает кнопок масштаба справа. */
.cv-mode {
  position: absolute; left: 12px; top: 10px; z-index: var(--z-float);
  display: flex; align-items: center; gap: 10px;
  padding: 6px 8px 6px 12px; border-radius: var(--r-pill);
  background: var(--surface); border: var(--line) solid var(--border);
  box-shadow: var(--shadow-float);
  font-size: 12px; line-height: 1.4; color: var(--text);
}
.cv-mode[hidden] { display: none; }
.cv-mode__what b { font-weight: 600; }
.cv-mode__what span { color: var(--text2); }
.cv-mode__stop {
  padding: 5px 11px; min-height: 28px; font-size: 12px; font-weight: 600;
  color: var(--on-btn); background: var(--btn-bg);
  border: none; border-radius: var(--r-pill); cursor: pointer;
}
.cv-mode__stop:hover { background: var(--btn-bg-hover); }


/* Поля координат своей точки. Приставка «x = »/«y = » — статичный узел рядом
   с редактируемым значением (не .edval): в правку не входит и пунктира под
   ней нет, чтобы глаз не путал подпись с тем, что можно щёлкнуть. */
.mark-xy { display: flex; align-items: baseline; gap: 6px; margin-top: 5px; flex-wrap: wrap; }
.mark-xy label { font-size: 11px; color: var(--text3); }
.mark-num-prefix { color: var(--text); font-variant-numeric: tabular-nums; margin-right: -4px; }
.mark-xy .edval { margin-left: 2px; }
.mark-xy input[type="number"] {
  width: 100%; min-width: 0; padding: 4px 6px; font-size: 12px;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text); font-variant-numeric: tabular-nums;
}

/* Ползунок параметра: под ним строка настройки границ и шага. */
.pchip-param .param-row { display: flex; gap: 4px; margin-top: 4px; }
.pchip-param .param-row input {
  min-width: 0; padding: 3px 4px; font-size: 11px; text-align: center;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text2); font-variant-numeric: tabular-nums;
}
/* Ползунок параметра: границы написаны по краям дорожки и открывают редактор. */
.param-track { display: flex; align-items: center; gap: 6px; }
.param-track input[type="range"] { flex: 1; min-width: 0; }
/* А35. Границы ползунка задуманы как правка на месте, но выглядели как
   служебная мелочь: кегль 10.5 и цвет --text3 почти сливались с фоном, и
   человек просто не замечал, что по ним можно щёлкнуть. Оформляем их так же,
   как и любое редактируемое значение (.edval): читаемый цвет, заметный
   пунктир снизу, текстовый курсор. */
/* ⚠️ ШИРИНА ГРАНИЦЫ НЕ ЗАВИСИТ ОТ РАЗРЯДНОСТИ ЧИСЛА (решение владельца 22.08).
   Было `flex: 0 0 auto` без минимума, то есть ширина ровно по содержимому:
   трёхзначная граница «100» шире однозначной «0» на семь пикселей, и дорожки
   соседних ползунков начинались в разных местах — панель из четырёх ползунков
   выглядела как лесенка. Минимальная ширина в 3,4em (≈37px при кегле 11)
   вмещает и «−100», и «1 250», а числа стоят по центру своего места. */
.param-bound {
  flex: 0 0 auto; min-width: 3.4em; text-align: center;
  padding: 1px 3px; border: none; background: transparent;
  font-size: 11px; color: var(--text2); font-variant-numeric: tabular-nums;
  border-bottom: 1px dashed var(--text3); border-radius: 0; cursor: text;
  transition: color .12s var(--ease), border-bottom-color .12s var(--ease);
}
.param-bound:hover,
.param-bound:focus-visible { color: var(--accent); border-bottom-color: var(--accent); }
.param-editor { display: none; }
.param-editor.open { display: block; margin-top: 6px; }
.param-ed-line { display: flex; align-items: center; gap: 5px; margin-bottom: 5px; font-size: 11px; color: var(--text2); }
.param-ed-line input {
  width: 100%; min-width: 0; flex: 1; padding: 3px 5px; font-size: 11px; text-align: center;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--input-bg); color: var(--text); font-variant-numeric: tabular-nums;
}
/* Поля интервала — те же тонкие числа, что по краям дорожки, а не текстовые
   прямоугольники: границу несёт подчёркивание (Н9, Н60). */
.param-ed-line input.param-ed-num {
  flex: 1 1 0; min-width: 0; width: 100%; padding: 2px 4px; text-align: center;
  font-size: 12px; color: var(--text); font-variant-numeric: tabular-nums;
  background: transparent; border: none; border-bottom: 1px solid var(--border);
  border-radius: 0;
}
.param-ed-line input.param-ed-num:focus { outline: none; border-bottom-color: var(--accent); }
/* Неполный ввод («−», «1e») подсвечивается самим подчёркиванием: всплывающих
   сообщений в этом меню нет, значение просто ещё не применилось. */
.param-ed-line input.param-ed-num.bad { border-bottom-color: var(--error, #d14343); }
/* Точное значение регулятора вводится щелчком по строке «t = 20», поэтому
   собственное числовое поле сцены рядом с ползунком лишнее (Н8, Н60). */
.reg-num-hidden { display: none !important; }
.pchip-editable { cursor: text; border-bottom: 1px dashed var(--border); }
.pchip-editable:hover { border-bottom-color: var(--accent); }
/* ── Ползунок параметра в духе Desmos (П23) ──────────────────────────
   Строка «a = 1» набрана формулой, справа серый крестик, под ними дорожка
   с серыми границами по краям. Раскрытый редактор границ печатает настоящие
   знаки неравенства. */
/* Н11: строка значения чуть крупнее прочих подписей панели — это главное, что
   читают в регуляторе, но не заголовок, поэтому прибавка ровно в пункт. */
.param-eq { font-size: 15px; color: var(--text); display: inline-flex; align-items: baseline; gap: 3px; }
.param-eq .katex { font-size: 1.02em; }
/* Н12: правка значения идёт ВНУТРИ формулы — «a =» остаётся набранным, меняется
   только число. Поле подхватывает тот же кегль, поэтому строка не дёргается. */
.param-eq-head { flex: 0 0 auto; }
.param-eq-input {
  flex: 1 1 0; min-width: 0; width: 100%; padding: 0 2px;
  font-size: 15px; font-family: 'KaTeX_Main', var(--font, inherit);
  color: var(--text); background: transparent;
  border: none; border-bottom: 1px solid var(--accent); border-radius: 0;
  font-variant-numeric: tabular-nums;
}
.param-eq-input:focus { outline: none; }
/* Регулятор сцены носит ту же строку «имя = значение», поэтому собственная
   подпись поля больше не нужна: имя стояло бы дважды (Н8). */
.reg-label-hidden { display: none !important; }
.reg-eq { display: inline-flex; align-items: baseline; gap: 3px; font-size: 15px; color: var(--text); }

/* Строка «имя = значение» у регулятора сцены стоит по содержимому. Колонка
   параметров — flex-столбец, и без этого она растягивала строку на всю
   ширину панели, а вместе со строкой — и пунктир под ней (жалоба владельца
   21.08: полоса во всю ширину видна даже без правки). */
.field > .reg-eq { align-self: flex-start; max-width: 100%; }

/* Поле правки значения НЕ подчиняется общему виду числовых полей панели:
   ни рамки, ни фона — только подчёркивание под самим числом, как у .edval.
   Селектор намеренно специфичнее, чем «#params-body .field
   input[type="number"]:focus», иначе тот перебивает задуманный вид. */
#params-body .field input.param-eq-input[type="number"],
#params-body .field input.param-eq-input[type="number"]:hover,
#params-body .field input.param-eq-input[type="number"]:focus {
  flex: 0 0 auto; width: auto; min-width: 1.6em; padding: 0 2px;
  text-align: left; background: transparent; outline: none;
  border: none; border-bottom: 1px solid var(--accent); border-radius: 0;
  font-size: 15px; font-family: 'KaTeX_Main', var(--font, inherit);
  color: var(--text); font-variant-numeric: tabular-nums;
}
.pchip-param .pchip-top { gap: 6px; }
.param-kill {
  margin-left: auto; flex: 0 0 auto; width: 18px; height: 18px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; line-height: 1; color: var(--text3);
  background: transparent; border: none; border-radius: var(--r);
}
.param-kill:hover { color: var(--accent); background: var(--accent-tint); }
/* Свёрнутый параметр: остаётся только строка «a = 1». */
.pchip-param.folded .param-track,
.pchip-param.folded .param-editor,
.pchip-param.folded .pchip-val { display: none; }
.pchip-param.folded .param-eq { cursor: pointer; opacity: .72; }
.param-ed-tex { flex: 0 0 auto; color: var(--text); }

/* Две группы в правой панели: регуляторы модели и буквы из формул. */
.params-group-title {
  font-size: 11px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text3); margin: 12px 0 6px;
}
.params-group-title:first-child { margin-top: 0; }
.pchip-param .param-row input:nth-child(2) { color: var(--text); font-weight: 600; }

/* ── Кнопки над графиком: гаечный ключ и возврат масштаба ── */
/* Н33. Кнопки над графиком лежат ВЫШЕ своего меню. Раньше здесь стоял
   --z-float, то есть ниже --z-pop у меню плоскости, и, поскольку слой создаёт
   собственный контекст наложения, подсказка внутри него не могла подняться над
   меню никаким z-index: она начинается на 7px ниже кнопки и попадала ровно под
   раскрытую панель. Сами кнопки на меню не налезают (оно открывается ниже их),
   поэтому поднять слой безопасно. */
.graph-tools {
  position: absolute; top: 12px; right: 12px; z-index: calc(var(--z-pop) + 1);
  /* А33. Кнопки стоят ВЕРТИКАЛЬНОЙ СТОПКОЙ: ключ, под ним «плюс», под ним
     «минус» — как у Desmos и как в первом калькуляторе. В коде это уже было
     задумано (см. комментарий в разметке), но раскладка оставалась строкой,
     и все четыре кнопки шли в ряд. */
  display: flex; flex-direction: column; align-items: center; gap: 6px;
}
/* Приблизить и отдалить читаются как одна пара, поэтому стоят вплотную. */
#btn-zoomin { margin-top: 2px; }
#btn-zoomout { margin-top: -3px; }
.gt-btn {
  width: 34px; height: 34px; display: grid; place-items: center; position: relative;
  border: 1px solid var(--border); border-radius: var(--r);
  background: var(--surface); color: var(--text2); box-shadow: var(--shadow-float);
  transition: color .12s var(--ease), border-color .12s var(--ease), transform .08s var(--ease);
}
.gt-btn svg { width: 17px; height: 17px; }
.gt-btn:hover { color: var(--accent); border-color: var(--accent); }
.gt-btn:active { transform: scale(.94); }
.gt-btn.on { color: var(--accent); border-color: var(--accent); background: var(--accent-tint); }
.gt-btn[hidden] { display: none; }
/* п. 78. Кнопки над графиком тоже показывают общую плашку. */
@media (prefers-reduced-motion: no-preference) {
  .gt-btn.appear { animation: gtIn .18s var(--ease); }
  @keyframes gtIn { from { opacity: 0; transform: scale(.9); } }
}

/* ── Меню настроек координатной плоскости (гаечный ключ) ── */
.wrench {
  position: absolute; top: 52px; right: 12px; z-index: var(--z-pop);
  width: 268px; max-height: calc(100% - 72px); overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-panel);
  box-shadow: var(--shadow-pop); padding: 4px 14px 14px;
  transform-origin: top right;
  display: none;
}
.wrench.open { display: block; }
@media (prefers-reduced-motion: no-preference) {
  .wrench.open { animation: wrenchIn .16s var(--ease); }
  @keyframes wrenchIn { from { opacity: 0; transform: scale(.96) translateY(-4px); } }
}
.wrench-group { padding: 10px 0; border-top: 1px solid var(--border-soft); }
.wrench-group:first-of-type { padding-top: 10px; border-top: none; }
.wrench-title {
  font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text3); margin-bottom: 7px;
}
.wrench-row { display: flex; align-items: center; gap: 7px; }
.wrench-row + .wrench-row { margin-top: 7px; }
.wrench-row > label { font-size: 12px; color: var(--text2); flex-shrink: 0; }
/* Н75, Н60. Границы окна, шаг делений, названия осей и заголовок графика
   правятся прямо в строке: рамки нет, фона нет, намёк несёт пунктир снизу —
   ровно как у компонента .edval. Сами поля остались нативными: к ним привязана
   обвязка сцен по id, и подменять их на contenteditable значило бы переучивать
   десяток обработчиков ради одного и того же вида. */
.wrench-row input[type="number"], .wrench-row input[type="text"] {
  flex: 1; min-width: 0; padding: 4px 2px; font-size: 13px;
  border: none; border-bottom: var(--line) dashed var(--num-line); border-radius: 0;
  background: transparent; color: var(--text); font-variant-numeric: tabular-nums;
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.wrench-row input[type="number"]:hover, .wrench-row input[type="text"]:hover {
  color: var(--accent); border-bottom-color: var(--accent);
}
.wrench-row input[type="number"]:focus, .wrench-row input[type="text"]:focus {
  outline: none; border-bottom-color: var(--accent);
}
.wrench .seg { margin: 0; }
.wrench .seg-btn { padding: 6px 8px; font-size: 12px; }
.wrench .chk { margin-top: 0; }
.wrench .hint { margin-top: 5px; }

/* Ступени ширины панелей (А57). Правило одно: графику остаётся не меньше
   половины окна. При 1024 две панели по 268 отдавали ему 43 процента, поэтому
   на ноутбучных ширинах панели ужимаются. */
@media (max-width: 1200px) {
  .side-left  { width: 224px; }
  .side-right { width: 224px; }
  .side-left, .side-right { min-width: 190px; }
}
@media (max-width: 900px) {
  .side-left  { width: 200px; }
  .side-right { width: 200px; }
  .side-left, .side-right { min-width: 176px; }
}
/* На совсем узком экране правая панель сворачивается САМА: две панели плюс
   рейка не оставляют графику и половины окна. Развернуть её можно вручную,
   стрелка на месте. */
@media (max-width: 820px) {
  .side-right:not(.user-open) { width: 26px; min-width: 26px; flex: 0 0 auto; }
  .side-right:not(.user-open) .side-inner { display: none; }
  /* п. 77. Панель, свёрнутая самой раскладкой, называет себя так же, как
     свёрнутая рукой: класса `collapsed` на ней нет, поэтому правило своё. */
  .side-right:not(.user-open) .side-spine { display: block; }
}

/* ── ПЕЧАТЬ (А58) ─────────────────────────────────────────────────────────
   Правил печати не было ни одного: Ctrl+P выводил страницу как есть, вместе
   с рейкой значков, обеими панелями и тёмным фоном. Учителю нужен график в
   раздатку, поэтому на листе остаётся только он, название модели сверху и
   ключевые значения снизу. */
@media print {
  :root { color-scheme: light; }
  html, body { background: #fff !important; color: #000 !important; height: auto !important; }
  /* П71. Шапка сайта в список не входила, и на бумаге печаталась тёмная
     полоса с меню и кнопкой «Выйти». Прячем по тем же признакам, по каким её
     находят проверки: свой тег и общие имена шапки платформы. */
  nav, header, .site-header, #site-nav,
  .dock, .side, .graph-tools, .wrench, .picker, .modal, .toast,
  #pult, .cv-mode, .side-arrow, .kbd, .mkbd { display: none !important; }
  .app { display: block !important; height: auto !important; }
  .stage, .graph-wrap {
    display: block !important; width: 100% !important; height: auto !important;
    overflow: visible !important; background: #fff !important; border: none !important;
  }
  #chart { width: 100% !important; height: 15cm !important; }
  /* Название модели и ключевые значения печатаем: без них лист безымянный. */
  .print-only { display: block !important; }
  #print-title {
    font-size: 16pt; font-weight: 700; margin: 0 0 8px; color: #000;
  }
  #print-stats { margin-top: 10px; font-size: 10pt; color: #000; }
  /* П73. Таблица чисел не растягивается на всю ширину листа: строка «подпись
     ... значение» через весь лист читается по диагонали, а глаз теряет пару.
     Ширина — по содержанию, потолок — половина полосы. */
  #print-stats { max-width: 9cm; }
  #print-stats .stat { display: flex; justify-content: space-between; gap: 12px;
    padding: 2px 0; border-bottom: 1px solid #ddd; }
  @page { margin: 1.5cm; }
}
.print-only { display: none; }

/* Интервал между блоками результатов; пустые блоки (чужой режим) не занимают места. */
.sb-body > div:not(:empty) { margin-bottom: 12px; }
.sb-body > div:empty { margin: 0; }

/* Тост-уведомление (заглушки «сохранить/экспорт») */
.toast {
  position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 12px); z-index: var(--z-pop);
  background: var(--text); color: var(--surface); font-size: 13px; font-weight: 600;
  padding: 9px 16px; border-radius: var(--r-pill); box-shadow: var(--shadow-pop);
  opacity: 0; pointer-events: none;
  transition: opacity .2s var(--ease), transform .2s var(--ease);
}
.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ── Модальное окно (экспорт, Фаза 5) ────────────────────────────────── */
.modal {
  position: fixed; inset: 0; z-index: var(--z-picker);
  display: none; align-items: center; justify-content: center; padding: 20px;
  background: rgba(15, 18, 26, .42); backdrop-filter: blur(2px);
}
.modal.open { display: flex; }
.modal-card {
  width: min(380px, 100%); max-height: 90vh; overflow-y: auto;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-panel); box-shadow: var(--shadow-pop); padding: 20px;
}
/* ── ОКНО КОНСТРУКТОРА КУСОЧНОЙ ШИРЕ ОСТАЛЬНЫХ ────────────────────────
   Указание владельца 25.08. Общие 380 px рассчитаны на окно с двумя-тремя
   строками текста и парой кнопок; у конструктора в строке ПЯТЬ колонок, и
   при пяти кусках это пять таких строк плюс предпросмотр всей записи.
   92 % ширины браузера — верхняя граница: на 1280 px окно выходит 720 px
   (56 %), на 1440 px те же 720 px (50 %), и в упор оно не встаёт нигде. */
#pw-modal .modal-card { width: min(720px, 92vw); }

/* ── ОКНО РАЗВОРОТА ИТОГОВОЙ ФУНКЦИИ ──────────────────────────────────
   Решение владельца 31.08. Запись во всю ширину окна кеглем 20 px, СО словом
   «если», по левому краю. Окно шире общих 380 px по той же причине, что и у
   конструктора кусочной: в записи может быть пять участков, и каждый — целая
   строка «формула + условие». */
#ff-modal .modal-card { width: min(720px, 92vw); position: relative; }
.ff-modal-math {
  font-size: 20px; color: var(--text); line-height: 1.5;
  /* ⚠️ ПРОКРУТКА ЗДЕСЬ СВОЯ И НЕ ДОЛЖНА УХОДИТЬ НА ХОЛСТ.
     Колесо над окном обязано прокручивать запись, а не менять масштаб
     графика; сам обработчик масштаба висит на #graph-wrap, поэтому события
     туда не доходят, но `overscroll-behavior` закрывает и цепочку прокрутки
     родителей, когда запись домотана до края. */
  overflow: auto; overscroll-behavior: contain;
  margin: 4px 0 4px; padding: 2px 0;
}
.ff-modal-x {
  position: absolute; top: 10px; right: 12px;
  width: 28px; height: 28px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface-2);
  color: var(--text2); font-size: 16px; line-height: 1; cursor: pointer;
}
.ff-modal-x:hover { color: var(--text); border-color: var(--text3); }

.modal-title { font-size: 15px; font-weight: 700; color: var(--text); margin: 0 0 4px; }
.modal-sub { font-size: 12px; color: var(--text2); margin: 0 0 16px; line-height: 1.5; }
.modal-opts { display: grid; gap: 8px; margin-top: 16px; }

/* Предпросмотр выгрузки (А50): что именно уйдёт в файл. Раньше человек жал
   «Скачать» вслепую и узнавал содержимое файла уже в редакторе. */
.exp-preview { border: 1px solid var(--line); border-radius: var(--r); padding: 8px 10px;
  background: var(--surface-2); font-size: 12px; }
.exp-prow { display: flex; justify-content: space-between; gap: 12px; padding: 2px 0; }
.exp-pk { color: var(--text2); }
.exp-pv { color: var(--text); font-variant-numeric: tabular-nums; }

.modal-opts .hint { margin-top: 2px; }
.modal-foot { margin-top: 16px; display: flex; justify-content: flex-end; gap: 10px; align-items: center; }
.btn-ghost {
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); font-weight: 600;
}
.btn-ghost:hover:not(:disabled) { background: var(--surface-2); border-color: var(--accent); color: var(--accent); }
.btn:disabled, .btn-ghost:disabled { opacity: .45; cursor: not-allowed; }
@media (prefers-reduced-motion: no-preference) {
  .modal.open .modal-card { animation: modalIn .18s var(--ease); }
  @keyframes modalIn { from { opacity: 0; transform: translateY(10px) scale(.985); } }
}

/* ═══════════════════════════════════════════════════════════════════
   ДВИЖЕНИЕ — целенаправленное, ease-out, <300мс; уважает reduced-motion.
   ═══════════════════════════════════════════════════════════════════ */
/* Окно выбора: мягкое появление содержимого (один раз при загрузке).
   Без fill-mode — если анимация не сыграет, карточки уже видимы (не пусто). */
@keyframes pickerIn {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
.picker-inner { animation: pickerIn .42s var(--ease); }
/* Закрытие окна — лёгкий «отъезд назад», открывая готовую сцену. */
.picker.hidden { transform: scale(.99); }

/* Тактильная отдача нажатия на кнопки дока. */
.dock-btn:not(:disabled):active { transform: scale(.92); }

/* Кнопки над графиком мягко въезжают один раз при открытии сцены. */
@keyframes floatIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
/* п. 31. Кнопка возврата масштаба то есть, то нет, а стопка не должна
   разъезжаться: место за ней остаётся, меняется только видимость. */
.gt-btn.is-off { visibility: hidden; pointer-events: none; }

.graph-tools { animation: floatIn .34s var(--ease) .04s; }
@media (prefers-reduced-motion: reduce) {
  .side, .side-arrow svg { transition: none; }
  .graph-tools, .picker-inner { animation: none; }
}

/* ── Н75, Н60, Н72. Редактируемое значение ────────────────────────────────
   Ни в одном состоянии не появляется прямоугольная рамка поля ввода: значение
   живёт прямо в тексте, а править его можно щелчком. Намёк на это несёт только
   пунктир снизу. */
.edval {
  display: inline-block; min-width: 1.6em; padding: 0 1px;
  border-bottom: 1px dashed var(--border);
  color: var(--text); font-variant-numeric: tabular-nums;
  cursor: text; outline: none; white-space: nowrap;
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
.edval:hover, .edval:focus { color: var(--accent); border-bottom-color: var(--accent); }
/* В правке остаётся тот же кегль и тот же пунктир: рамка не появляется, и
   строка не дёргается по высоте. */
.edval.editing { border-bottom-color: var(--accent); color: var(--text); }
.edval.editing .katex { display: none; }
/* Неполный ввод («−», «1e») подсвечивается самим подчёркиванием, без всплывашек. */
.edval.bad { border-bottom-color: var(--error, #d14343); }
.edval-row { display: flex; align-items: baseline; gap: 4px; flex-wrap: nowrap; }

/* Имя кривой на графике: в покое пунктира нет, он появляется под курсором и
   при правке. Причина узкая: строка ввода в этом блоке и так подписана, а
   пунктир под каждым именем превращал панель в решётку из подчёркиваний.
   Правило намеренно узкое — только имя в строке списка. Пунктир под
   КООРДИНАТАМИ точек не трогаем: там он показывает, что число правится, и
   другого признака у него нет. */
.grow > .edval-text { border-bottom-color: transparent; }
.grow > .edval-text:hover, .grow > .edval-text:focus,
.grow > .edval-text.editing { border-bottom-color: var(--accent); }

/* Н60, Н72, Н75. Общее правило на всю панель: короткое значение правится прямо
   в строке, без прямоугольной рамки и фона — намёк несёт пунктир снизу, как у
   компонента .edval. Поля ФОРМУЛ сюда не попадают: они живут внутри .f-wrap и
   работают через MathLive, у них своя оснастка. */
#tools-panel input[type="number"], #tools-panel input[type="text"],
#params-body input[type="number"], #params-body input[type="text"] {
  padding: 3px 2px;
  border: none; border-bottom: var(--line) dashed var(--num-line); border-radius: 0;
  background: transparent; color: var(--text); font-variant-numeric: tabular-nums;
  transition: color .12s var(--ease), border-color .12s var(--ease);
}
#tools-panel .f-wrap input, #params-body .f-wrap input,
#tools-panel input.curve-expr-inp {
  /* формульные поля оставляем как есть */
  padding: revert; border: revert; border-radius: revert; background: revert;
}
#tools-panel input[type="number"]:hover, #tools-panel input[type="text"]:hover,
#params-body input[type="number"]:hover, #params-body input[type="text"]:hover {
  color: var(--accent); border-bottom-color: var(--accent);
}
#tools-panel input[type="number"]:focus, #tools-panel input[type="text"]:focus,
#params-body input[type="number"]:focus, #params-body input[type="text"]:focus {
  outline: none; border-bottom-color: var(--accent);
}

/* ── Н15, Н50, Н73. Тумблер ────────────────────────────────────────────────
   Пилюля с ездящим кружком. Активная сторона подписана акцентом, неактивная
   приглушена: видно и положение кружка, и что именно выбрано. */
.tgl { display: flex; align-items: center; gap: 8px; margin: 2px 0 6px; }
.tgl-lab {
  font-size: 12px; font-weight: 600; color: var(--text3); cursor: pointer;
  transition: color .14s var(--ease);
}
.tgl-lab.on { color: var(--accent); }
.tgl-sw {
  flex: 0 0 auto; width: 34px; height: 18px; padding: 0; position: relative;
  border: 1px solid var(--border); border-radius: var(--r-pill);
  background: var(--surface-2); cursor: pointer;
  transition: background .16s var(--ease), border-color .16s var(--ease);
}
.tgl-sw:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.tgl-knob {
  position: absolute; top: 1px; left: 1px; width: 14px; height: 14px;
  border-radius: var(--r-pill); background: var(--text3);
  transition: transform .16s var(--ease), background .16s var(--ease);
}
.tgl.is-right .tgl-sw { background: var(--accent-tint); border-color: var(--accent); }
.tgl.is-right .tgl-knob { transform: translateX(16px); background: var(--accent); }
@media (prefers-reduced-motion: reduce) {
  .tgl-knob, .tgl-sw, .tgl-lab { transition: none; }
}

/* Н46, Н48. Строка точки: цвет и имя сверху, ниже координаты столбиком и
   галочки правее от них. Прежде тут были подпись-заглушка «Своя точка»,
   текстовое поле подписи и четыре строки подряд. */
.mark-top { display: flex; align-items: center; gap: 7px; }
.mark-name { flex: 1; min-width: 0; font-size: 12px; font-weight: 600; }
.mark-body { display: flex; align-items: flex-start; gap: 10px; margin-top: 4px; }
.mark-xy-col { display: flex; flex-direction: column; gap: 3px; flex: 0 0 auto; }
.mark-chks { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.mark-chks .chk { font-size: 11px; }
.mark-ok { margin-left: 2px; color: var(--accent); font-size: 13px; }

/* Н6. «Ключевые значения» набраны как в учебнике: подпись, знак равенства,
   число. Знаки стоят в одной колонке, поэтому числа не пляшут лесенкой, а
   читаются столбиком. Прежде подпись и число разносило по краям строки
   (justify-content: space-between), и у каждой строки был свой отступ. */
/* Колонка числа фиксированной ширины: сетка у каждой строки своя, и без этого
   знаки равенства вставали каждый на своём месте. С постоянной третьей
   колонкой их левый край одинаков во всех строках, то есть колонка знаков
   стоит ровно. */
.sb-body .stat.stat-eq {
  /* Колонка числа не уже 78px, но растёт под длинное значение: с жёсткой
     шириной семизначное число вылезало за дорожку. */
  /* П49. Колонка числа не съедает строку. `max-content` без потолка отдавал
     длинному значению всю ширину, подпись оставалась шириной в букву и
     рассыпалась по вертикали. `fit-content(46%)` держит прежнее поведение у
     обычных чисел и не пускает колонку дальше середины строки. */
  /* ⚠️ ЗДЕСЬ БЫЛА НЕДЕЙСТВУЮЩАЯ ЗАПИСЬ, И ИМЕННО ОНА ДАВАЛА ТРИ СТРОКИ.

     Стояло `grid-template-columns: minmax(0, 1fr) auto minmax(78px,
     fit-content(46%))`. По правилам CSS `fit-content()` НЕ РАЗРЕШЕН внутри
     `minmax()`, поэтому браузер выбрасывал объявление целиком — и сетка
     оставалась с одной колонкой. Подпись, знак и число вставали тремя
     отдельными строками: пять значений занимали пятнадцать строк. Замер:
     `grid-template-columns` считался как одно значение вроде «108px», высота
     строки 76–78 px вместо одной строки.

     Комментарий рядом при этом подробно объяснял поведение трёх колонок,
     которого никогда не было. Это второй урок: описание в комментарии не
     доказывает, что правило работает.

     Раскладка переложена на flex с переносом — так же, как в согласованном
     макете. Значение стоит СРАЗУ ЗА ЗНАКОМ РАВЕНСТВА и к правому краю не
     прижимается (решение владельца). Длинная подпись переносится сама внутри
     себя, знак и число остаются при ней. Панель НЕ расширяется. */
  display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: flex-start; column-gap: 5px;
  /* ⚠️ ПЕРЕНЕСЁННАЯ СТРОКА ПРОСИТ ЗАЗОР, ХОТЯ ТЕКСТ ЕГО НЕ ПРОСИЛ.
     С нулевым зазором значение, набранное формулой, налезало на подпись
     сверху на 2,4 px: у формулы с индексами и скобками чернила выходят ВЫШЕ
     базовой линии, а выравнивание идёт по ней. Обычный текст такого не даёт,
     поэтому в макете зазора и нет. Замер поймал два случая в «Оптимизации». */
  row-gap: 3px;
}
.sb-body .stat.stat-eq > span { text-align: left; flex: 0 1 auto; min-width: 0; }
.stat-sign { font-style: normal; color: var(--text3); font-size: 12px; flex: 0 0 auto; }
.sb-body .stat.stat-eq > b { text-align: left; font-weight: 700; flex: 0 1 auto; }
/* Б39. Страховка от наложения. Колонка подписи может сжаться до нуля
   (minmax(0, 1fr)), но САМ ТЕКСТ подписи сжиматься не умеет и при длинном
   значении вылезал за свою колонку прямо на число. Разрешаем ему переноситься
   и не даём числу занять всю строку. */
/* П49. ПЕРЕНОС ИДЁТ ПО ПРОБЕЛУ, А НЕ ПО БУКВЕ.
   Было `hyphens: auto` вместе с `overflow-wrap: anywhere`: браузер резал слово
   там, где ему удобно, и в панели читалось «(произво-дитель)», «Прибыль (P −» /
   «ATC)·Q», «SW = CS +» / «PS». Обрубленное слово не экономит место, а отнимает
   смысл — то же правило, по которому отменён обрубок подписи легенды (Р3b.3).
   `break-word` оставлен последним средством: он режет ТОЛЬКО такое слово,
   которое не помещается в строку целиком ни при какой разбивке. */
.sb-body .stat.stat-eq > span { overflow-wrap: break-word; hyphens: none; }
.sb-body .stat.stat-eq > b { max-width: 100%; overflow-wrap: break-word; hyphens: none; }
/* Колонок в сетке три (подпись · знак равенства · число), но знак ставится не
   всегда: у значения со своим равенством внутри его не удваивают. Пустая
   третья колонка при этом продолжала держать свои 78 px и додавливала подпись
   до ширины одной буквы — та переносилась по букве в строку. Строке без знака
   отдаём две колонки. */
/* Строке без знака отдельной раскладки больше не нужно: при переносе по
   строке пустой колонки не остаётся вовсе. */

/* Б40. Составное значение — это список, а не число, и в ячейку для числа оно
   не помещается: подпись сверху, величины снизу, каждая своей строкой. */
/* Прежде широкое значение уходило на свою строку блоком, и в одном списке
   уживались два разных формата. Формат теперь один: тот же перенос по строке,
   просто значение переезжает целиком, когда не помещается рядом. */
.sb-body .stat.stat-stack { display: flex; flex-wrap: wrap; align-items: baseline;
  justify-content: flex-start; column-gap: 5px; row-gap: 3px; padding: 6px 0; }
.sb-body .stat.stat-stack > span { text-align: left; flex: 0 1 auto; min-width: 0; }
.sb-body .stat.stat-stack > b { text-align: left; flex: 0 1 auto; }
.sb-body .stat.stat-stack .stat-line { display: block; line-height: 1.45; }
/* Качественная характеристика набирается обычным начертанием: это не отсчёт. */
.sb-body .stat.stat-phrase > b { font-size: 13px; font-weight: 500; color: var(--text); }
/* Число, набранное KaTeX: кегль табло, но без жирности самого шрифта формул. */
.sb-body .stat b.stat-tex .katex { font-size: 1em; }

/* Н22. Правка названия графика прямо на холсте: поле стоит там же, где сама
   подпись, тем же кеглем, без рамки — как и все остальные значения. */
.title-edit {
  position: absolute; z-index: var(--z-float);
  transform: translateX(-50%);
  display: flex; align-items: center; gap: 6px;
  padding: 3px 6px; border-radius: var(--r);
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: var(--shadow-float);
}
.title-edit input[type="text"] {
  min-width: 160px; padding: 2px 4px; font-size: 15px; font-weight: 600;
  color: var(--text); background: transparent;
  border: none; border-bottom: 1px dashed var(--border); border-radius: 0;
}
.title-edit input[type="text"]:focus { outline: none; border-bottom-color: var(--accent); }
