@font-face {
  font-family: "Inter Variable";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/inter-variable.woff2") format("woff2-variations"), url("../fonts/inter-variable.woff2") format("woff2");
}

:root {
  /* Типографическая шкала (пункт №8). Весь текст интерфейса берёт размер
     отсюда. Вне шкалы намеренно остаются: глифы иконок и шахматных фигур
     (пункт №22) и fluid-заголовки на clamp(), привязанные к вьюпорту и к
     контейнеру доски. */
  --fs-caption: 0.75rem;   /* 12px — подписи, ярлыки, вторичные метки */
  --fs-body-sm: 0.875rem;  /* 14px — плотный текст интерфейса */
  --fs-body: 1rem;         /* 16px — основной текст */
  --fs-h4: 1.125rem;       /* 18px — подзаголовок карточки */
  --fs-h3: 1.25rem;        /* 20px — заголовок блока */
  --fs-h2: 1.5rem;         /* 24px — заголовок секции */
  --fs-h1: 2rem;           /* 32px — заголовок страницы */

  /* Размеры глифов-иконок (07.10.2026, пункт №8 аудита). Вынесены из
     текстовой шкалы намеренно: у ⏮ ‹ › ⇆ ☾ ⓘ font-size задаёт размер
     значка, а не читаемость текста, и смешивание двух сущностей в одном
     наборе и давало «14 разных размеров в диапазоне 0.68–1rem». Текст
     ниже 12px недопустим, значок — нормально. */
  --icon-sm: 0.8125rem;    /* 13px — ⓘ и прочие мелкие пометки */
  --icon-md: 0.9375rem;    /* 15px — кнопки в плотных рядах */
  --icon-lg: 1.25rem;      /* 20px — основные кнопки-иконки */
  --icon-xl: 1.625rem;     /* 26px — навигация под доской */

  /* 31.10.2026 (запрос пользователя): страница — --page без бликов;
     карточки, меню, кнопки и поля — белый --bg. До этого карточка
     была того же цвета, что страница, и выделялась только тенью.
     08.11.2026 (запрос пользователя): сетка 34px вернулась, как до 31.10;
     блики и диагональный градиент не возвращались. */
  --page: #F7F9FC;
  --page-grid: rgba(37, 99, 235, 0.06);
  --page-pattern:
    linear-gradient(var(--page-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--page-grid) 1px, transparent 1px);
  --bg: #ffffff;
  --surface: #ffffff;
  --surface-soft: #F7F9FC;
  --surface-strong: #eef1f6;
  --ink: #1f2937;
  --muted: #6b7280;
  /* было #9ca3af — 2.54:1 на белом, ниже WCAG AA. */
  --muted-faint: #6b7280;
  --line: #e6eaf2;
  --line-strong: #c9d1de;

  /* Основной акцент #4F6BFF (31.10.2026). Белый текст на нём — 4.30:1,
     поэтому мелкий текст и ссылки берут --blue-dark того же тона (5.34:1). */
  --blue: #4F6BFF;
  --blue-dark: #3D5AF1;
  /* Доска и подсветка ходов остаются прежними (решение пользователя
     31.10.2026: «шахматная доска — цвета остаются такими же») — свой
     закреплённый синий, не зависящий от акцента сайта. */
  --board-accent: #2563eb;
  --board-eval-accent: #3e48de;
  --performance: #4a7dff;
  /* UI-акцент по визуальному референсу (Design Specification, 03.08.2026):
     indigo, отдельно от --blue. Применяется к .primary-button, активному
     табу «Обзора» и синей стороне очков — остальные места на --blue пока
     не трогать (не эта итерация). Двухслойно, как --green/--green-text
     (U-5): --accent — заливка (кнопка, подчёркивание таба), --accent-text —
     тот же цвет как текст, безопасный по контрасту на --surface. */
  --accent: #4F6BFF;
  --accent-text: #3D5AF1;
  /* Идентичность чёрной стороны в очках партии (Перформанс) — измерено на
     референсе, отдельно от декоративного --violet. Тот же принцип слоёв. */
  --player-black: #8f18e6;
  --player-black-text: #8f18e6;
  /* Заливка карточек очков — измерено на референсе: #F6F6F6, нейтральный
     серый (не голубоватый --surface-soft). */
  --score-card-bg: color-mix(in srgb, black 4%, var(--surface));
  /* Подложка чёрной стороны шапки (запрос пользователя, не из референса) —
     заметнее, чем --score-card-bg, но всё ещё очень светлая. Чуть темнее
     по повторному запросу пользователя 05.08.2026 (было 7%). */
  --side-black-bg: color-mix(in srgb, black 11%, var(--surface));
  --board-light: #f2dcc0;
  --board-dark: #b89068;
  --piece-light: #ffffff;
  --piece-dark: #031026;
  --piece-light-stroke: #071b45;
  --piece-dark-stroke: rgba(255, 255, 255, 0.72);
  --green: #34c759;
  --green-soft: color-mix(in srgb, var(--green) 16%, var(--surface));
  --amber: #f59e0b;
  --amber-soft: color-mix(in srgb, var(--amber) 16%, var(--surface));
  --red: #b91c1c;
  --red-soft: color-mix(in srgb, var(--red) 12%, var(--surface));
  --mistake: #ef4444;
  --mistake-soft: color-mix(in srgb, var(--mistake) 14%, var(--surface));
  --violet: #6366f1;
  --teal: #0c99a0;
  /* Текстовый слой палитры: те же смыслы, но затемнённые до WCAG AA.
     Токены выше остаются для заливок и полос, где нужна яркость. */
  --green-text: #15803d;
  --red-text: #b91c1c;
  /* Затемнён сильнее ожидаемого: должен проходить AA и на белом (5.50),
     и на собственной плашке --amber-soft (4.85). */
  --amber-text: #a8500a;
  --mistake-text: #b91c1c;
  --teal-text: #0f766e;
  --violet-text: #4f46e5;
  /* Бронза для эмблем уровня квестов (запрос пользователя 12.08.2026,
     редизайн «Квесты») — своего токена под медный/бронзовый цвет раньше
     не было. */
  --bronze: #a15c3e;
  --bronze-text: #8a4a2f;
  /* Неоморфизм (редизайн 27.08.2026, ТЗ пользователя): выпуклый рельеф —
     пара теней от воображаемого источника света сверху-слева. Тёмная тень
     снизу-справа, светлая — сверху-слева от того же базового тона --bg,
     на который кладутся все переоформленные поверхности (принцип
     «монохромная база» — карточка не светлее/темнее страницы, объём даёт
     только тень). Старые имена --shadow/--shadow-soft сохранены (их уже
     использует много селекторов), поменялись только значения. */
  /* 31.10.2026: одна мягкая нейтральная тень вместо пары «тёмная снизу +
     белый блик сверху» — блик не виден на белой карточке, а белая карточка
     на #F7F9FC без тени сливается (контраст 1.05). */
  --shadow: 0 1px 2px rgba(16, 24, 40, 0.05), 0 6px 16px rgba(16, 24, 40, 0.06);
  --shadow-soft: 0 1px 2px rgba(16, 24, 40, 0.05), 0 3px 10px rgba(16, 24, 40, 0.05);
  /* Вдавленные состояния (поля ввода, неактивные пункты сегментов, нажатые
     кнопки) — та же пара теней, но inset. */
  --shadow-inset: inset 0 1px 3px rgba(16, 24, 40, 0.08), inset 0 0 0 1px #E3E8F0;
  --shadow-inset-soft: inset 0 1px 2px rgba(16, 24, 40, 0.06), inset 0 0 0 1px #E3E8F0;
  /* Светлая половина пары теней отдельным токеном — переиспользуется в
     цветных выпуклых бейджах качества хода (принцип 6: свой цвет сохраняется,
     объём добавляется), где тёмная половина берётся из --quality-*. */
  --neu-highlight: rgba(255, 255, 255, 0.8);
  /* 31.10.2026 (запрос пользователя: «не хочу слишком скруглённые,
     как вытянутые пилюли»): карточки — 14px (как контейнер
     переключателя «Партия / Обзор / …»), кнопки, поля, метки и
     переключатели — --radius-control 10px, как пункт левого меню.
     Круглыми остаются аватарки, точки и значки на доске. */
  --radius: 14px;
  --radius-control: 10px;
  /* Крупные контейнеры (карточка задачи «Квестов» и т.п.) — свой, больший
     радиус, не общий --radius. */
  --radius-lg: 14px;

  /* Пять уровней качества хода — измерено на референсе («Статистика ходов»,
     «Переломные моменты»): отдельная шкала от --green/--amber/--mistake/
     --red, у которых другие роли (game-card, quest-tier и т.д.) и другие
     измеренные значения. -text — затемнено до WCAG AA (>=4.5:1 на белом). */
  --quality-excellent: #38883f;
  --quality-excellent-text: #36843d;
  --quality-good: #90b343;
  --quality-good-text: #657d2f;
  /* "Неплохой" — новая категория (запрос пользователя): ход, который не
     улучшает позицию, но и не ухудшает — между --quality-good и
     --quality-inaccuracy, оттенок посередине между ними же (не бирюзовый —
     тот уже занят --quality-brilliant). -text посчитан на контраст (5.83:1
     на белом), не на глаз. */
  --quality-ok: #b7ad26;
  --quality-ok-text: #6d6716;
  /* "Теоретический ход" — запрос пользователя 04-05.09.2026, реальная база
     дебютной теории (см. analyzer.py ECO_OPENINGS_DIR), не выдуманная
     заново, а переиспользованная категория тона: --blue уже используется на
     сайте как нейтральный/информационный акцент (график «Качество ходов»),
     не оценочный (не хороший/плохой) — ровно то, чем является книжный ход.
     Контраст уже посчитан для --blue в прошлой сессии (5.17:1 на белом,
     переиспользуем -dark вариант тем же приёмом, что и другие -text). */
  --quality-book-text: #1d4ed8;
  /* "Упущенная возможность" — запрос пользователя 05.09.2026, по мотивам
     категории "Miss" у chess.com (идея, не их иконка/цвет) — приглушённый
     оранжевый между --quality-ok (жёлто-оливковый) и --quality-inaccuracy
     (золотой), не совпадает ни с одним из них. -text посчитан на контраст
     (6.34:1 на белом), не на глаз. */
  --quality-missed: #e98a14;
  --quality-missed-text: #8b520c;
  --quality-inaccuracy: #dfa809;
  --quality-inaccuracy-text: #936f06;
  --quality-mistake: #f46c1f;
  --quality-mistake-text: #be5418;
  --quality-blunder: #e23e28;
  --quality-blunder-text: #d73b26;
  /* Бриллиантовый — отдельная строка «Статистики ходов» (запрос
     пользователя 06.08.2026), выделена из общей «Отличные» бирюзовым, по
     принятой шахматной конвенции (chess.com и т.п.) для лучшего хода. */
  --quality-brilliant: #14b8a6;
  --quality-brilliant-text: #0f766e;
  --quality-excellent-soft: color-mix(in srgb, var(--quality-excellent) 14%, var(--surface));
  --quality-good-soft: color-mix(in srgb, var(--quality-good) 14%, var(--surface));
  --quality-ok-soft: color-mix(in srgb, var(--quality-ok) 14%, var(--surface));
  --quality-missed-soft: color-mix(in srgb, var(--quality-missed) 14%, var(--surface));
  --quality-inaccuracy-soft: color-mix(in srgb, var(--quality-inaccuracy) 14%, var(--surface));
  --quality-mistake-soft: color-mix(in srgb, var(--quality-mistake) 14%, var(--surface));
  --quality-blunder-soft: color-mix(in srgb, var(--quality-blunder) 14%, var(--surface));
  --quality-brilliant-soft: color-mix(in srgb, var(--quality-brilliant) 14%, var(--surface));
}

html[data-theme="dark"] {
  --page: #0f172a;
  --page-grid: rgba(255, 255, 255, 0.045);
  --bg: #1e293b;
  --surface: #1e293b;
  --surface-soft: #253248;
  --surface-strong: #2c3b54;
  --ink: #f8fafc;
  --muted: #cbd5e1;
  --muted-faint: #94a3b8;
  --line: #334155;
  --line-strong: #475569;

  /* Акцент того же тона, светлее — читается на тёмном (5.03:1 на карточке). */
  --blue: #7B8FFF;
  --blue-dark: #A9B6FF;
  --board-accent: #60a5fa;
  --quality-book-text: #93c5fd;
  --performance: #60a5fa;
  /* --accent/--player-black (заливки) не меняются — их контраст считается
     против белого текста кнопки, а не против фона страницы. Только
     текстовые варианты светлее: сами значения на --surface тёмной темы
     дают 2.2:1 и 2.4:1 — ниже AA даже для крупного текста (3:1). */
  --accent-text: #A9B6FF;
  --player-black-text: #c4b5fd;
  /* Тёмной темы в референсе нет — переиспользую существующее отношение
     поверхностей (--surface-strong уже служит «на ступень темнее -surface»
     в тёмной теме), не изобретаю новое значение. */
  --score-card-bg: var(--surface-strong);
  /* Подложка чёрной стороны — на ступень темнее --surface-strong. Чуть
     темнее по повторному запросу пользователя 05.08.2026 (было 12%). */
  --side-black-bg: color-mix(in srgb, black 17%, var(--surface-strong));
  /* Тёмной темы в референсе нет — тон подобран как приглушённый вариант
     измеренного дерева (#f2dcc0/#b89068), референс задаёт только светлую. */
  --board-light: #d9c2a0;
  --board-dark: #8b6a45;
  --piece-light: #fbfdff;
  --piece-dark: #050b18;
  --piece-light-stroke: #071b45;
  --piece-dark-stroke: rgba(255, 255, 255, 0.78);
  --green: #34c759;
  --green-soft: color-mix(in srgb, var(--green) 16%, var(--surface));
  --amber: #f0b85a;
  --amber-soft: color-mix(in srgb, var(--amber) 16%, var(--surface));
  --red: #f87171;
  --red-soft: color-mix(in srgb, var(--red) 14%, var(--surface));
  --mistake: #fca5a5;
  --mistake-soft: color-mix(in srgb, var(--mistake) 14%, var(--surface));
  --violet: #a5b4fc;
  --teal: #5bcdd2;
  /* В тёмной теме контраст уже проходит AA — текстовый слой равен основному. */
  --green-text: var(--green);
  /* --red на --red-soft в тёмной теме даёт 4.33:1 — осветляем до 6.31:1. */
  --red-text: #fca5a5;
  --amber-text: var(--amber);
  --mistake-text: var(--mistake);
  --teal-text: var(--teal);
  --violet-text: var(--violet);
  --bronze: #c47a52;
  --bronze-text: var(--bronze);
  /* Тёмная тема неоморфизма: "светлая" сторона пары — не белый, а --bg,
     чуть осветлённый (иначе на тёмно-синем фоне блик выглядел бы серым
     пятном, а не бликом того же материала). */
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 6px 16px rgba(0, 0, 0, 0.3);
  --shadow-soft: 0 1px 2px rgba(0, 0, 0, 0.35), 0 3px 10px rgba(0, 0, 0, 0.25);
  --shadow-inset: inset 0 1px 3px rgba(0, 0, 0, 0.45), inset 0 0 0 1px rgba(255, 255, 255, 0.06);
  --shadow-inset-soft: inset 0 1px 2px rgba(0, 0, 0, 0.4), inset 0 0 0 1px rgba(255, 255, 255, 0.06);
  --neu-highlight: rgba(44, 58, 82, 0.55);

  /* Тёмной темы в референсе нет — те же 5 значений, осветлённые на 22%
     (тот же приём, что уже применён к --board-light/--board-dark). Текст
     тёмной темы уже проходит AA против --surface без дополнительного
     затемнения (проверено расчётом), поэтому -text равен базовому. */
  --quality-excellent: #64a269;
  --quality-excellent-text: var(--quality-excellent);
  --quality-good: #a8c46c;
  --quality-good-text: var(--quality-good);
  --quality-ok: #dcd02e;
  --quality-ok-text: var(--quality-ok);
  --quality-missed: #ffa618;
  --quality-missed-text: var(--quality-missed);
  --quality-inaccuracy: #e6bb3f;
  --quality-inaccuracy-text: var(--quality-inaccuracy);
  --quality-mistake: #f68c50;
  --quality-mistake-text: var(--quality-mistake);
  --quality-blunder: #e86857;
  --quality-blunder-text: var(--quality-blunder);
  --quality-brilliant: #2dd4bf;
  --quality-brilliant-text: var(--quality-brilliant);
}

* {
  box-sizing: border-box;
}

html {
  min-width: 320px;
  /* Фон без бликов (31.10.2026), сетка вернулась 08.11.2026 — запросы пользователя. */
  background: var(--page-pattern), var(--page);
  background-size: 34px 34px, 34px 34px, auto;
  color: var(--ink);
  font-family: "Inter Variable", Inter, "Segoe UI", Arial, sans-serif;
  letter-spacing: 0;
}

body {
  margin: 0;
  overflow-x: hidden;
  /* Длинное слово (немецкий) переносится, только если не помещается. */
  overflow-wrap: break-word;
  font-size: var(--fs-body);
  font-weight: 400;
}

body.is-skin-modal-open {
  overflow: hidden;
}

/* Доступно скринридеру, невидимо на экране. */
/* Ссылка-пропуск (пункт №13 аудита, 07.10.2026). Спрятана за верхний край
   экрана и выезжает только при фокусе с клавиатуры — мышиному пользователю
   она не видна, клавиатурному даёт первый же Tab на странице. */
/* Плашка отмены после «Очистить» (пункт №16 аудита, 07.10.2026).
   Висит над содержимым, уходит сама через 8 секунд. */
.undo-toast {
  position: fixed;
  z-index: 90;
  right: 20px;
  bottom: 20px;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border-radius: var(--radius);
  background: var(--ink);
  color: #ffffff;
  font-size: var(--fs-body-sm);
  box-shadow: 0 10px 28px rgba(2, 8, 23, 0.28);
}

.undo-toast-button {
  padding: 4px 10px;
  border: 1px solid rgba(255, 255, 255, 0.45);
  border-radius: var(--radius-control);
  background: transparent;
  color: #ffffff;
  font-size: var(--fs-body-sm);
  font-weight: 600;
  cursor: pointer;
}

.undo-toast-button:hover {
  background: rgba(255, 255, 255, 0.14);
}

.undo-toast-button:focus-visible {
  outline: 2px solid #ffffff;
  outline-offset: 2px;
}

/* В тёмной теме --ink светлый, и белый текст плашки на нём не читался
   (найдено 02.11.2026: фон rgb(248,250,252), текст белый). Там плашка
   светлая с тёмным текстом. */
html[data-theme="dark"] .undo-toast {
  background: #f8fafc;
  color: #0f172a;
}

html[data-theme="dark"] .undo-toast-button {
  border-color: rgba(15, 23, 42, 0.4);
  color: #0f172a;
}

html[data-theme="dark"] .undo-toast-button:hover {
  background: rgba(15, 23, 42, 0.08);
}

html[data-theme="dark"] .undo-toast-button:focus-visible {
  outline-color: #0f172a;
}

@media (max-width: 620px) {
  .undo-toast {
    right: 12px;
    bottom: 12px;
    left: 12px;
  }
}

.skip-link {
  position: fixed;
  z-index: 100;
  top: -100px;
  left: 16px;
  padding: 10px 18px;
  border-radius: 0 0 var(--radius) var(--radius);
  background: var(--accent);
  color: #ffffff;
  font-size: var(--fs-body-sm);
  font-weight: 600;
  text-decoration: none;
  transition: top 140ms ease;
}

.skip-link:focus-visible {
  top: 0;
  outline: 3px solid #ffffff;
  outline-offset: -3px;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

button,
input,
select,
textarea {
  font: inherit;
  letter-spacing: 0;
}

button {
  cursor: pointer;
  transition: background 160ms ease, border-color 160ms ease, box-shadow 160ms ease, color 160ms ease, transform 160ms ease;
}

button:hover {
  transform: translateY(-1px);
}

button:disabled {
  cursor: not-allowed;
  opacity: 0.55;
  transform: none;
}

button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
a:focus-visible {
  /* Раздел 7/6 ТЗ 27.08.2026: акцентный индикатор фокуса нужен отдельно от
     неоморфной тени — она сама по себе (--shadow-soft и т.п.) не меняется
     на фокусе и не служит сигналом. Добавлен `a` — раньше в правиле не
     было: mailto-плашка на «Связаться с нами» и ссылка «Получить
     Секунданта» получали пустой outline (style: none) и не имели вообще
     никакого видимого фокуса. */
  outline: 3px solid color-mix(in srgb, var(--blue) 34%, transparent);
  outline-offset: 2px;
}

/* --topbar-space — сколько места занимает зафиксированная шапка сверху:
   её отступ (12px) плюс НАСТОЯЩАЯ высота. Высота не постоянна: шапка
   переносит содержимое на вторую строку на узких окнах (замерено: 52px
   на 1440px, 119px на 1024px), поэтому прежняя жёсткая константа 86px
   на 1024px не спасала — содержимое всё равно уходило под шапку.
   Значение обновляет syncTopbarSpace() в app.js через ResizeObserver;
   64px здесь — разумный запас до первого замера (12 + min-height 52),
   чтобы в первый кадр ничего не пряталось. */
:root {
  --topbar-space: 64px;
}

.app-shell {
  width: min(1720px, 100%);
  margin: 0 auto;
  padding: calc(var(--topbar-space) + 22px) 28px 28px;
}

/* Прокрутка к элементу (scrollIntoView, переходы по якорям) не знает про
   зафиксированную шапку и ставит цель вплотную к верхней кромке окна —
   под шапку. scroll-padding-top сдвигает точку остановки ниже неё. */
html {
  scroll-padding-top: calc(var(--topbar-space) + 12px);
}

/* .header-hover-trigger удалён 26.10.2026 вместе с выезжающей шапкой:
   это была невидимая полоса в 10px у верхней кромки, ловившая наведение. */

.topbar {
  position: fixed;
  top: 12px;
  left: 50%;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: min(1720px, calc(100% - 56px));
  min-height: 52px;
  padding: 6px 12px;
  border-radius: var(--radius);
  /* Неоморфизм: полоса того же тона, что страница — обводка убрана,
     объём даёт только тень (принцип «монохромная база», ТЗ 27.08.2026). */
  background: var(--bg);
  box-shadow: var(--shadow);
  /* backdrop-filter убран 07.10.2026: фон шторки непрозрачный
     (rgb(248,250,253)), размывать за ним нечего — эффекта ноль, а GPU
     пересчитывал размытие на каждом кадре выезда. Отсюда и ощущение,
     что шторка «подтормаживает». */
  /* Шапка зафиксирована и видна всегда (запрос пользователя 26.10.2026).
     До этого она пряталась наверх и выезжала по наведению на верхнюю
     кромку — см. решение U-26 и правки 07.10.2026 про скорость выезда.
     Это поведение отменено целиком: прятать навигацию, чтобы сэкономить
     52 пикселя, оказалось дороже, чем они стоят. Вместе с ним потеряли
     смысл .header-hover-trigger, класс .is-peek и расчёт зоны наведения
     в app.js — всё удалено, а не оставлено висеть. */
  transform: translateX(-50%);
  pointer-events: auto;
}

/* Устройства без hover (тач-планшеты, телефоны): шапка всегда видима.
   Привязка к возможностям ввода, а не к ширине экрана. */
@media (hover: none), (pointer: coarse) {
  /* Здесь шапка в обычном потоке (sticky), место под себя занимает сама —
     резервировать под неё нечего. Ноль выставлен ДО первого замера из JS;
     syncTopbarSpace() приходит к тому же значению, читая position шапки. */
  :root {
    --topbar-space: 0px;
  }

  .topbar {
    position: sticky;
    top: 0;
    left: auto;
    width: 100%;
    transform: none;
    pointer-events: auto;
  }
}

body.theme-dark .topbar {
  border: 0 !important;
  background: var(--bg) !important;
}

.brand,
.top-actions,
.language-switch {
  display: flex;
  align-items: center;
}

.brand {
  flex: 0 1 300px;
  min-width: 0;
  gap: 12px;
  /* Логотип — ссылка на главную (08.11.2026). */
  color: inherit;
  text-decoration: none;
}

.brand-mark {
  display: grid;
  flex: 0 0 auto;
  width: 56px;
  height: 49px;
  place-items: center;
  overflow: visible;
  border: 0;
  border-radius: 0;
  background: transparent;
}

.brand-mark img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 8px 14px rgba(12, 35, 80, 0.12));
}

html[data-theme="dark"] .brand-mark {
  border: 1px solid rgba(126, 164, 224, 0.24);
  border-radius: var(--radius);
  background: rgba(239, 246, 255, 0.08);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

html[data-theme="dark"] .brand-mark img {
  filter:
    brightness(1.55)
    contrast(1.08)
    saturate(1.24)
    drop-shadow(0 8px 16px rgba(121, 167, 255, 0.32));
}

.brand-wordmark {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.brand-wordmark h1 {
  color: #07142e;
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: 0;
  line-height: 1;
  white-space: nowrap;
}

.brand-wordmark h1 span {
  color: var(--blue);
}

.brand-wordmark small {
  color: var(--blue);
  font-size: var(--fs-caption);
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1;
  white-space: nowrap;
}

html[data-theme="dark"] .brand-wordmark h1 {
  color: var(--ink);
}

body.theme-dark .brand-wordmark h1 {
  color: #eef3f8 !important;
}

.top-actions {
  flex: 0 0 auto;
  justify-content: flex-end;
  gap: 8px;
}

.top-actions .icon-button {
  width: 36px;
  height: 36px;
  font-size: var(--icon-md);
}

.top-nav {
  display: flex;
  flex: 1 1 auto;
  justify-content: center;
  gap: 4px;
  min-width: 280px;
  min-height: 36px;
}

.nav-tab {
  min-height: 38px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  white-space: nowrap;
  transition:
    background 180ms ease,
    box-shadow 180ms ease,
    color 180ms ease,
    transform 180ms ease;
}

.nav-tab:hover {
  background: var(--bg);
  box-shadow: var(--shadow-soft);
  color: var(--ink);
  transform: translateY(-1px);
}

.nav-tab.is-active {
  /* Активный пункт — вдавлен в полосу, а не подсвечен заливкой (принцип
     «вдавленные состояния», ТЗ 27.08.2026). Цвет остаётся акцентным. */
  background: var(--bg);
  box-shadow: var(--shadow-inset-soft);
  color: var(--blue-dark);
}

.language-switch {
  /* overflow:hidden убран (раздел 7 ТЗ 27.08.2026) — той же природы
     находка, что у .segmented: .language-button уже имеет свой margin
     (3px от края) и border-radius, обрезать больше нечего, а сам
     overflow:hidden срезал верх/низ focus-outline у кнопок RU/EN при
     табуляции с клавиатуры. */
  border: 0;
  border-radius: var(--radius-control);
  /* Сам переключатель — выпуклый компактный элемент (ТЗ 5.1), активный
     пункт внутри него «вдавлен», как тумблер в нажатом положении. */
  background: var(--bg);
  box-shadow: var(--shadow-soft);
}

.language-button {
  min-width: 38px;
  min-height: 34px;
  margin: 3px;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  border-radius: calc(var(--radius) - 6px);
  transition: background 180ms ease, box-shadow 180ms ease, color 180ms ease;
}

.language-button.is-active {
  background: var(--bg);
  color: var(--blue-dark);
  box-shadow: var(--shadow-inset-soft);
}

.eyebrow {
  margin: 0 0 4px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: uppercase;
}

h1,
h2,
h3,
p {
  margin-top: 0;
}

h1 {
  margin-bottom: 0;
  overflow-wrap: anywhere;
  font-size: var(--fs-h1);
  font-weight: 700;
  line-height: 1.15;
}

h2 {
  margin-bottom: 0;
  font-size: var(--fs-h2);
  font-weight: 600;
  line-height: 1.25;
}

h3 {
  margin-bottom: 14px;
  font-size: var(--fs-h3);
  font-weight: 600;
}

h4 {
  margin-bottom: 10px;
  font-size: var(--fs-h4);
  font-weight: 600;
}

.small-badge {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface-soft);
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

/* Живёт в правой колонке экрана анализа, поэтому доску не двигает и в другие
   разделы за человеком не едет (05.11.2026, UX-исследование №10). */
.analysis-loader {
  flex: none;
  display: none;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid color-mix(in srgb, var(--blue) 28%, var(--line));
  border-radius: var(--radius);
  background: var(--surface);
}

body.is-loading-analysis .analysis-loader {
  display: grid;
  animation: rise-in 260ms ease both;
}

.loader-head,
.loader-steps {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.loader-head span,
.loader-head strong,
.loader-steps span {
  font-weight: 700;
}

.loader-head span {
  color: var(--ink);
}

.loader-head strong,
.loader-steps span {
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.loader-steps span {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.loader-steps span::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: currentColor;
  opacity: 0.42;
}

.loader-steps span.is-active {
  color: var(--blue-dark);
}

.loader-steps span.is-active::before {
  opacity: 1;
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--blue) 12%, transparent);
}

.loader-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.loader-detail {
  min-width: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 500;
  line-height: 1.35;
}

.loader-foot button {
  flex: none;
}

.loader-foot button[hidden] {
  display: none;
}

.loader-track {
  height: 10px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--surface-strong);
}

.loader-fill {
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--blue);
  transition: width 260ms ease;
}

body:not(.page-analysis) .workspace,
body:not(.page-analysis) .tables-section {
  display: none;
}

/* Как и остальные разделы, а не нулевой высотой и прозрачностью: так кнопки
   скрытой главной оставались в порядке Tab, фокус уходил на невидимое
   (05.11.2026, UX-исследование №6). */
body:not(.page-home) .welcome-screen,
body:not(.page-quests) .quests-section,
body:not(.page-play) .play-section,
body:not(.page-profile) .profile-section,
body:not(.page-contact) .contact-section {
  display: none;
}

body.page-analysis:not(.has-analysis) .tables-section {
  display: none;
}

.welcome-screen,
.info-section,
.quests-page,
.workspace,
.board-zone,
.quest-board {
  scroll-margin-top: 112px;
}

.welcome-screen {
  position: relative;
  display: grid;
  /* 28.10.2026: доля текста 0.68 -> 0.55 и промежуток уже. Заголовок
     накануне уменьшили (U-47), и колонка текста столько места больше не
     требует — отдано картинке. На 1440 её колонка 791 -> 867, на 1920
     1076 -> 1174. */
  grid-template-columns: minmax(400px, 0.55fr) minmax(480px, 1fr);
  gap: clamp(20px, 2.8vw, 44px);
  /* Промежуток между колонками и промежуток до карточек возможностей —
     разные вещи. Горизонтальный держит воздух между текстом и картинкой,
     вертикальный только отодвигает карточки вниз, за край экрана. */
  row-gap: 28px;
  align-items: center;
  /* 28.10.2026, запрос пользователя: карточки возможностей не помещались на
     первый экран, приходилось крутить колесом. Высота считается от реального
     низа шторки, а не от «150px на глаз»: 50 = 22px верхнего отступа
     .app-shell сверх шторки + 28px его же нижнего. Сумма с ними даёт ровно
     100vh, и полосы прокрутки на первом экране не возникает. */
  min-height: calc(100vh - var(--topbar-space) - 50px);
  margin-top: 0;
  transition: opacity 420ms ease, transform 420ms ease, filter 420ms ease, max-height 520ms ease, padding 420ms ease;
}

.info-section {
  /* .info-section (обёртка «Профиля» и «Связаться с нами» целиком) больше
     нигде не описана — тоже держала рамку поверх уже неоморфной тени. */
  border: 0;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  box-shadow: var(--shadow);
  backdrop-filter: blur(18px);
}

.welcome-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* min-height: 520px снят 28.10.2026. На широком экране высоту строки
     держала картинка и эти 520 ничего не решали, а на 1024x800 и 1280x720
     решали именно они: текст занимает ~370px, но колонка всё равно
     распирала строку до 520, и карточки возможностей уезжали за первый
     экран. Виновника видно только замером обеих колонок по отдельности —
     при align-items: center обе показывают одинаковую высоту строки. */
  /* Вертикальные отступы ужаты 28.10.2026 (было clamp(18px, 4vw, 58px)):
     на 1280x720 они давали 102px, колонка текста выходила 415px против 409
     у картинки и одна упиралась в первый экран. Горизонтальный не тронут —
     он отвечает за отбивку от края страницы, а не за высоту. */
  padding: clamp(12px, 2.4vw, 40px) 0 clamp(12px, 2.4vw, 40px) clamp(10px, 2vw, 42px);
}

.welcome-copy h2 {
  max-width: 680px;
  margin-bottom: 18px;
  color: #07142e;
  /* 28.10.2026, запрос пользователя: заголовок был clamp(2.55rem, 4.8vw,
     5.2rem) — на 1440 это 69px, на 1920 упиралось в потолок 83px, и четыре
     строки занимали почти весь первый экран. Снижено примерно на пятую
     часть по всей шкале: 1440 — 69 -> 55, 1920 — 83 -> 64. Ниже 620px
     работает отдельное правило (var(--fs-h1), 32px), его не трогали. */
  font-size: clamp(2.1rem, 3.8vw, 4rem);
  line-height: 1.04;
}

html[data-theme="dark"] .welcome-copy h2 {
  color: var(--ink);
}

body.theme-dark .welcome-copy h2 {
  color: #eef3f8 !important;
}

.welcome-copy p:not(.eyebrow) {
  max-width: 560px;
  margin-bottom: 0;
  color: var(--muted);
  font-size: var(--fs-h4);
  font-weight: 700;
  line-height: 1.55;
}

/* Две кнопки друг под другом (запрос 28.10.2026): «Играть» встала под
   разбор. Колонка, а не перенос через flex-basis: при переносе кнопка
   растягивалась на всю ширину колонки текста и переставала читаться как
   второстепенная. align-items: flex-start оставляет обеим ширину по
   содержимому. */
.welcome-actions {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  margin-top: 28px;
}

/* .welcome-player-stat удалён 19.08.2026 (запрос пользователя) — карточка
   "Максимальный перформанс игрока" всегда была пустой при первом визите
   (93px чистого ожидания под заголовком, без пользы до первого анализа
   Chess.com-партии). JS (#homeMaxPerformance/#homeMaxPerformanceNote в
   app.js) не трогали — там уже была защита `if (!els.xxx) return`, без
   элементов в DOM просто не выполняется, без ошибок. */

.hero-action {
  min-height: 54px;
  padding: 0 18px;
}

/* .welcome-note удалён 28.10.2026 (запрос пользователя): подпись
   "Работает локально на этом ПК." рядом с кнопкой разбора снята целиком —
   вместе с разметкой и ключами welcome.note в обеих локалях. */

.welcome-visual {
  position: relative;
  min-width: 0;
  isolation: isolate;
  align-self: center;
  /* Было padding-block: 10px — 20px по вертикали ни за что: края кадра и так
     растворяются маской, прижиматься к соседям ему нечем. Снято 28.10.2026
     вместе с разбором высоты первого экрана. */
  /* 14.10.2026. Псевдоэлементы свечения ниже намеренно вылезают за правый
     край (inset: 4% -4%) и ещё растекаются blur(34px) — из-за этого на
     главной была горизонтальная прокрутка 20.8px. Дефект давний, нашёлся
     при замене картинки на вектор: проверено — убрать саму доску прокрутку
     не убирает, а отключить эти два псевдоэлемента убирает.
     `clip`, а не `hidden`: hidden создал бы контейнер прокрутки. */
  overflow: clip;
}

.welcome-visual::before,
.welcome-visual::after {
  position: absolute;
  z-index: -1;
  border-radius: 999px;
  content: "";
  pointer-events: none;
}

.welcome-visual::before {
  inset: 4% -4% -2% 2%;
  /* Блик за иллюстрацией снят 31.10.2026 — сайт без градиентов. */
  background: none;
}

.welcome-visual::after {
  right: 6%;
  bottom: 4%;
  width: 78%;
  height: 20%;
  background: none;
}

/* Титульное изображение (27.10.2026). Сперва кадр стоял в скруглённой
   плашке с тенью; по просьбе пользователя рамка убрана совсем — края
   растворяются в фоне страницы маской, как это делал и прежний растр до
   14.10.2026. Тени нет сознательно: box-shadow рисуется по рамке
   элемента, а не по маске, и вернул бы прямоугольник обратно.
   Токенов --hero-* больше нет: красить в картинке нечего. */
.hero-figure {
  position: relative;
  z-index: 1;
  display: block;
  /* 1050 при кадре 1305px шириной — сам снимок выходит 959px: поле справа
     (113px, см. prepare_hero_image.py) в счёт не идёт, оно прозрачное.

     Третий предел — по высоте окна, иначе на невысоком экране картинка
     выдавливает карточки возможностей за первый экран. Ширина считается
     через пропорцию кадра (1305/896 = 1.4565), потому что высоту задавать
     нельзя: слой с цифрами привязан к размеру ОБЁРТКИ, и разъедься они —
     цифры уедут с карточек. 180 = 22 (верхний отступ .app-shell сверх
     шторки) + 28 (его нижний) + 28 (ряд до карточек) + 96 (сами карточки
     в одну строку) + 6 запаса: на 1280 подпись «Секундант — разбор глубже»
     переносится и строка карточек вырастает до 101px. */
  width: min(100%, 1050px, calc((100vh - var(--topbar-space) - 180px) * 1.4565));
  margin-inline: auto;
  /* Ширина растушёвки у каждого края своя, и это не вкус, а замер: она
     доходит ровно до того места, где в кадре начинается содержимое.
     Слева 13% — там размытый фон, горшок и книги. Сверху 5.5%: карточки с
     цифрами начинаются на 7.25%. Снизу 6% — доска кончается на 94.3%, ниже
     только стол. Справа 11%: правая карточка кончается на 86.6%, дальше
     идёт то самое приписанное поле, растушёвывать там нечего кроме него.

     Каждый градиент идёт не линейно, а по smoothstep (3t²-2t³) — шесть
     остановок вместо двух. Линейная рампа упирается в полную непрозрачность
     углом, и глаз этот угол видит как границу; у smoothstep обоих углов
     нет, поэтому переход и читается плавным. */
  mask-image:
    linear-gradient(to right,
      rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.104) 2.6%, rgba(0, 0, 0, 0.352) 5.2%,
      rgba(0, 0, 0, 0.5) 6.5%, rgba(0, 0, 0, 0.648) 7.8%, rgba(0, 0, 0, 0.896) 10.4%,
      #000 13%),
    linear-gradient(to left,
      rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.104) 2.2%, rgba(0, 0, 0, 0.352) 4.4%,
      rgba(0, 0, 0, 0.5) 5.5%, rgba(0, 0, 0, 0.648) 6.6%, rgba(0, 0, 0, 0.896) 8.8%,
      #000 11%),
    linear-gradient(to bottom,
      rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.104) 1.1%, rgba(0, 0, 0, 0.352) 2.2%,
      rgba(0, 0, 0, 0.5) 2.75%, rgba(0, 0, 0, 0.648) 3.3%, rgba(0, 0, 0, 0.896) 4.4%,
      #000 5.5%),
    linear-gradient(to top,
      rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.104) 1.2%, rgba(0, 0, 0, 0.352) 2.4%,
      rgba(0, 0, 0, 0.5) 3%, rgba(0, 0, 0, 0.648) 3.6%, rgba(0, 0, 0, 0.896) 4.8%,
      #000 6%);
  mask-composite: intersect;
}

.hero-shot {
  display: block;
  width: 100%;
  height: auto;
  border-radius: inherit;
}

/* Слой живых цифр поверх кадра. viewBox 1195x896 = пиксели исходника,
   поэтому координаты в разметке — прямо замеренные по картинке, и слой
   тянется вместе с ней на любой ширине. pointer-events выключены: слой
   перекрывает картинку целиком и иначе съедал бы выделение и клики. */
.hero-live {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* Цвета намеренно не темозависимые: текст лежит НА светлой карточке внутри
   кадра, а не на фоне страницы, и в тёмной теме обязан остаться тёмным.
   Размеры — замер высоты цифр в исходном рендере (41px и 38.5px при
   cap-height Inter 0.727em), отрицательный трекинг — потому что шрифт в
   кадре у́же Inter. */
.hero-live-big {
  fill: #0d182e;
  font-family: "Inter Variable", Inter, "Segoe UI", Arial, sans-serif;
  font-weight: 700;
  letter-spacing: -1.5px;
}

#heroAccuracyValue {
  font-size: 56px;
}

#heroPerformanceValue {
  font-size: 53px;
}

/* Подписи карточек: в кадре они были вшиты, и «Max Performance» стоял
   по-английски на русской странице (05.11.2026). Размер — по ширине
   стёртых надписей. */
.hero-live-label {
  fill: #23344f;
  font-family: "Inter Variable", Inter, "Segoe UI", Arial, sans-serif;
  font-size: 24px;
  font-weight: 500;
  letter-spacing: -0.2px;
}

.hero-live-player {
  fill: #1a66e4;
  font-family: "Inter Variable", Inter, "Segoe UI", Arial, sans-serif;
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.2px;
}

.hero-live-mini {
  fill: #1f3157;
  font-family: "Inter Variable", Inter, "Segoe UI", Arial, sans-serif;
  font-size: 27px;
  font-weight: 600;
  letter-spacing: -0.5px;
}

html[data-theme="dark"] .welcome-visual::after {
  opacity: 0.68;
}

.welcome-features {
  grid-column: 1 / -1;
  display: grid;
  /* Было repeat(5, 1fr) — пять карточек держались в строке на ЛЮБОЙ ширине,
     и около 1000px подпись «Графики и динамика по фазам» ломалась так, что
     предлог «и» висел отдельной строкой. auto-fit переносит карточки сами,
     как только на каждую остаётся меньше 190px. 14.10.2026, вместе с
     сокращением самих подписей. */
  /* 168px, а не 190: при 190 на 1009px помещалось только 4 колонки и
     «Секундант» оставался один во второй строке. С сокращёнными
     подписями 168 хватает — пять карточек держатся в строке до ~950px,
     ниже переносятся сами. */
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: clamp(12px, 2vw, 24px);
  padding: 0 clamp(4px, 1vw, 14px);
}

.welcome-features > :is(article, a) {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  gap: 14px;
  align-items: center;
  min-height: 96px;
  padding: 18px;
  border: 0;
  border-radius: var(--radius);
  /* Монохромная база: карточка того же тона, что страница — объём только
     от тени (ТЗ 27.08.2026, принцип 1). */
  background: var(--bg);
  box-shadow: var(--shadow);
}

.welcome-features span {
  display: grid;
  width: 48px;
  height: 48px;
  place-items: center;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--blue) 10%, var(--bg));
  color: var(--blue);
  font-size: var(--fs-h3);
  font-weight: 700;
  box-shadow: var(--shadow-soft);
}

.welcome-features strong {
  color: var(--ink);
  line-height: 1.35;
}

/* Карточка-ссылка «Секундант» (14.10.2026). Выглядит как остальные четыре,
   но кликается и ведёт на /pricing. Фокус видимый — карточка попадает в
   обход с клавиатуры наравне с кнопками (WCAG 2.4.7). */
.welcome-features > a {
  color: inherit;
  text-decoration: none;
  transition: transform 160ms ease, box-shadow 160ms ease;
}

.welcome-features > a:not(.is-premium):hover {
  transform: translateY(-2px);
}

.welcome-features > a:not(.is-premium):focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

.welcome-features > a.is-premium:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow), 0 0 32px color-mix(in srgb, var(--amber) 32%, transparent);
}

.welcome-features > a.is-premium:focus-visible {
  /* Сплошной --blue, а не color-mix(...34%), как у некоторых соседей:
     здесь карточка на амбровом фоне, полупрозрачная обводка на нём теряется.
     Токена --focus в проекте нет — не ссылаемся на несуществующий. */
  outline: 3px solid var(--blue);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .welcome-features > a.is-premium {
    transition: none;
  }

  .welcome-features > a.is-premium:hover {
    transform: none;
  }
}

/* Карточка "Секундант" (запрос пользователя 19.08.2026) — единственная
   платная из пяти. Обводку-выделение (было amber border) заменяет тёплое
   свечение поверх обычной neumorphic-тени — семантика цвета сохранена
   (принцип 6), но обводки больше нет нигде на карточках (принцип 5). */
.welcome-features > .is-premium {
  background: color-mix(in srgb, var(--amber) 5%, var(--bg));
  box-shadow: var(--shadow), 0 0 26px color-mix(in srgb, var(--amber) 22%, transparent);
}

.welcome-features > .is-premium span {
  background: color-mix(in srgb, var(--amber) 18%, var(--bg));
  color: color-mix(in srgb, var(--amber) 70%, var(--ink));
}

.glass-stat {
  display: grid;
  gap: 5px;
  padding: 15px;
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
  animation: rise-in 380ms ease both;
}

.glass-stat:nth-child(2) {
  animation-delay: 80ms;
}

.glass-stat:nth-child(3) {
  animation-delay: 160ms;
}

.glass-stat strong {
  font-size: var(--fs-h3);
}

.glass-stat span {
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

.mini-board {
  display: grid;
  aspect-ratio: 1;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: repeat(4, 1fr);
  overflow: hidden;
  border: 2px solid var(--ink);
  border-radius: 20px;
  box-shadow: var(--shadow-soft);
}

.mini-board span:nth-child(odd) {
  background: #eed8b8;
}

.mini-board span:nth-child(even) {
  background: #4f7862;
}

.info-section {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 0.8fr);
  gap: 18px;
  margin: 14px 0;
  padding: clamp(18px, 3vw, 30px);
}

.info-section h2 {
  margin-bottom: 10px;
  font-size: clamp(1.5rem, 3vw, 2.6rem);
  line-height: 1.03;
}

.info-section p:not(.eyebrow) {
  margin: 0;
  color: var(--muted);
  font-weight: 600;
  line-height: 1.55;
}

.quests-page {
  display: grid;
  gap: 16px;
  margin: 14px 0;
}

/* Шапка режима несёт только идентификацию страницы. Описание, счётчики и
   уровни сложности убраны в раскрывающийся блок, иначе активная задача
   уезжает за первый экран. */
.quest-detail {
  /* Рамка убрана (ТЗ 27.08.2026, принцип 5) — box-shadow: var(--shadow)
     уже несёт неоморфный рельеф сама по себе (значение токена поменялось
     в этой же правке), рамка рядом с ней просто дублировала функцию.
     Полупрозрачный фон + blur — фирменная "стеклянная" подложка Квестов
     (поверх .bg-grid), сохраняю как есть, не свожу к сплошному --bg. */
  border: 0;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 90%, transparent);
  box-shadow: var(--shadow);
  backdrop-filter: blur(18px);
}

/* Переключатель «Сценарии»/«Задачи» — новый раздел Квестов (запрос
   пользователя 07.09.2026). Свои классы (.quest-mode-*), не переиспользует
   .segment/.segmented Анализа (Н-5 — общих селекторов между подсистемами
   быть не должно), но использует те же токены палитры. */
.quest-mode-switch {
  display: flex;
  gap: 6px;
  padding: 0 clamp(16px, 2.3vw, 26px) 12px;
}

.quest-mode-tab {
  padding: 8px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--muted);
  font-weight: 600;
  font-size: var(--fs-body-sm);
  cursor: pointer;
}

.quest-mode-tab.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

#matePuzzleProgress {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

/* Обёртка "идёт решение" внутри .quest-tasks-board-panel (запрос
   пользователя 08.09.2026 — полноценный экран "Раздел пройден" вместо
   строки текста, нужно скрывать доску/инструменты/фидбек целиком). Свой
   flex повторяет родителя — иначе доска/кнопки/фидбек внутри потеряли бы
   gap, положенный им как прямым flex-детям .quest-tasks-board-panel.
   [hidden]-переопределение — тот же приём, что у .secondant-locked[hidden]
   (см. выше в файле): ID-селектор специфичнее атрибута [hidden] сам по
   себе, без явного override отображался бы поверх скрытого состояния. */
#matePuzzleSolvingView {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

#matePuzzleSolvingView[hidden] {
  display: none;
}

.quest-tasks-complete {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 32px 16px;
  text-align: center;
}

.quest-tasks-complete[hidden] {
  display: none;
}

.quest-tasks-complete strong {
  font-size: var(--fs-h3);
  color: var(--ink);
}

.quest-tasks-complete p {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.quest-tasks-feedback {
  min-height: 1.4em;
  margin: 0;
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

.quest-tasks-feedback.is-success {
  color: var(--green);
}

.quest-tasks-feedback.is-error {
  color: var(--red);
}

.quest-disclosure-body {
  display: grid;
  gap: 12px;
  padding: 0 clamp(16px, 2.3vw, 26px) 14px;
}

.quest-brief span,
.contact-actions span {
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  line-height: 1.4;
}

/* Эмблема — обрезанный кроп готового листа иконок (не нарисована заново),
   короткая подпись рядом — по ТЗ раздел 4: «не нужно повторять крупную
   текстовую плашку, уровень воспринимается через эмблему + подпись».
   Раньше эмблема+подпись повторялись на каждой карточке квеста; теперь
   показаны один раз в .quest-level-head (запрос пользователя 13.08.2026). */
.quest-tier-emblem {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  object-fit: contain;
}

.quest-detail {
  display: grid;
  gap: 14px;
  padding: clamp(16px, 2.3vw, 26px);
}

/* Заголовок + компактная «Об этой задаче» в одну строку (запрос
   пользователя 12.08.2026, редизайн страницы активного квеста) —
   «Активный сценарий» и 3 шага-инструкции убраны совсем, ничем не
   заменены (пользователь понимает механику через саму доску). */
.quest-detail-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.quest-detail-head h3 {
  flex: 1 1 auto;
  min-width: 0;
}

/* «Об этой задаче» — настоящий оверлей поверх интерфейса (запрос
   пользователя 13.08.2026), не раскрывающийся в потоке: <dialog> рендерится
   в top layer браузера и в закрытом состоянии не занимает места (display:none
   из UA-стиля), поэтому открытие/закрытие никогда не двигает доску и правую
   панель. Кнопка-переключатель — маленькая, вплотную к заголовку. */
.quest-about-toggle {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  border: 0;
  border-radius: var(--radius-control);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
  color: var(--ink);
  font-size: var(--fs-caption);
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: box-shadow 150ms ease;
}

.quest-about-toggle:hover {
  box-shadow: var(--shadow);
}

.quest-about-toggle:active {
  box-shadow: var(--shadow-inset-soft);
}

.quest-about-dialog {
  width: min(440px, 90vw);
  max-height: min(80vh, 640px);
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow);
}

.quest-about-dialog::backdrop {
  background: color-mix(in srgb, black 45%, transparent);
}

.quest-about-dialog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}

.quest-about-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--radius-control);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
  color: var(--muted);
  font-size: var(--fs-caption);
  line-height: 1;
  cursor: pointer;
  transition: box-shadow 150ms ease;
}

.quest-about-close:hover {
  color: var(--ink);
  box-shadow: var(--shadow);
}

.quest-about-close:active {
  box-shadow: var(--shadow-inset-soft);
}

.quest-about-dialog .quest-disclosure-body {
  padding: 14px 16px 16px;
  max-height: calc(min(80vh, 640px) - 56px);
  overflow-y: auto;
}

.quest-detail h3 {
  margin-bottom: 8px;
  font-size: clamp(1.35rem, 2.45vw, 2.25rem);
  line-height: 1.04;
}

.quest-detail p:not(.eyebrow) {
  max-width: 850px;
  margin: 0;
  color: var(--muted);
  font-weight: 600;
  line-height: 1.55;
}

/* 18.08.2026 (запрос пользователя): пропорции доска/панель приведены к тем
   же, что на Анализе — доска доминирует (гибкая колонка), панель узкая и
   фиксированная (тот же clamp, что у .board-with-side на Анализе). Раньше
   было наоборот: доска 520px, панель — весь остаток (на 1280px получалось
   520 vs 619px, панель шире доски). Это не расшаривание с Анализом — тот же
   числовой clamp продублирован здесь отдельно, см. Н-5 в docs/DECISIONS.md. */
.quest-detail-grid {
  display: grid;
  grid-template-columns: minmax(360px, 1fr) clamp(320px, 30vw, 620px);
  gap: 16px;
  align-items: start;
}

.quest-brief {
  display: grid;
  gap: 10px;
}

.quest-board-panel {
  display: grid;
  gap: 10px;
}

/* Правая колонка рядом с доской (запрос пользователя 12.08.2026) —
   статус/сообщение/попытки/линия/результат/награды, раньше стоявшие под
   доской, теперь здесь. Сами блоки (.quest-status-grid/.quest-feedback/
   .quest-attempts/.quest-line/.quest-result/.quest-rewards) не менялись,
   только их место.
   18.08.2026: высота панели теперь = высоте .quest-board-panel (JS,
   syncQuestPanelHeight), внутренний скролл — .quest-info-scroll ниже.
   По той же причине, что у .board-side на Анализе: чистый grid без
   ограничения по высоте позволял панели растягивать страницу вслед за
   контентом вместо того, чтобы стоять вровень с доской. */
.quest-info-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.quest-board-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.quest-board-actions {
  flex-wrap: wrap;
  order: 3;
}

.quest-board-actions .compact {
  flex: 1 1 130px;
  justify-content: center;
}

/* Статистика стоит ПОСЛЕ обратной связи: сначала результат хода, потом цифры.
   18.08.2026 (запрос пользователя, редизайн Квестов): было 4 отдельные
   рамки-бокса подряд — из-за этого и соседних блоков страница читалась
   "стеной одинаковых карточек". Сжато в одну тонкую строку с разделителями
   между значениями вместо отдельных рамок на каждое; сама разметка
   (4 div внутри) и id/логика в JS не менялись, поменялся только внешний вид. */
.quest-status-grid {
  display: flex;
  flex-wrap: wrap;
  row-gap: 6px;
  padding: 9px 12px;
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
}

.quest-status-grid div {
  display: flex;
  align-items: baseline;
  gap: 5px;
  min-height: auto;
  padding: 0;
  border: 0;
  background: none;
}

.quest-status-grid div:not(:first-child)::before {
  margin: 0 14px 0 0;
  color: var(--line-strong);
  content: "·";
}

.quest-status-grid div:not(:first-child) {
  margin-left: 14px;
}

.quest-status-grid span {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
  text-transform: uppercase;
  white-space: nowrap;
}

.quest-status-grid strong {
  overflow-wrap: anywhere;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  line-height: 1.15;
}

/* Обёртка доски (запрос пользователя 18.08.2026) — несёт размер/центрирование
   (раньше было на .quest-board напрямую) и служит точкой отсчёта для
   координат-меток и стартового оверлея, которые позиционируются относительно
   неё. Тот же приём, что .chess-board-frame/.chess-board на Анализе, но
   отдельные классы — без общих селекторов между подсистемами (Н-5). */
/* Обёртка доски + шкалы оценки (запрос пользователя 19.08.2026) — та же
   роль, что .board-live на Анализе, но свой класс. Раньше .quest-board-frame
   был прямым ребёнком .quest-board-panel с order:2 — order переехал сюда,
   т.к. теперь этот grid-слот занимает именно .quest-board-live. */
.quest-board-live {
  display: flex;
  order: 2;
  align-items: stretch;
  justify-content: center;
  gap: 8px;
}

.quest-board-frame {
  position: relative;
  justify-self: center;
  width: min(100%, clamp(360px, 46vw, 620px));
  aspect-ratio: 1;
  margin: 4px 4px 28px 12px;
  border-radius: 20px;
  /* Внешняя тень на контейнере (ТЗ 27.08.2026, 5.8) — своя копия того же
     приёма, что у .chess-board-frame на Анализе, свой класс — Н-5. */
  box-shadow: var(--shadow);
}

.quest-board {
  display: grid;
  container-type: inline-size;
  width: 100%;
  height: 100%;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);
  overflow: hidden;
  /* Толстая цветная рамка — намеренный акцент Квестов (не декоративная
     var(--line), другой источник), не трогаю. Радиус — отдельно от общего
     --radius (сейчас 20px), тем же приёмом, что и .chess-board на Анализе:
     на угловых клетках такое скругление слишком сильно "срезало" бы
     фигуру/рамку. */
  border: 3px solid color-mix(in srgb, var(--piece-dark) 86%, var(--board-accent));
  border-radius: 10px;
  background: var(--board-light);
  /* Тень перенесена на .quest-board-frame (см. выше) — раньше стояла и
     здесь тоже (--shadow-soft уже неоморфная), вдвоём с новой тенью рамки
     давало заметное задвоение рельефа по одному и тому же контуру. */
  box-shadow: none;
}

.quest-board-coordinates {
  position: absolute;
  z-index: 2;
  inset: 0;
  pointer-events: none;
}

.quest-board-coordinate {
  position: absolute;
  color: #000000;
  font-size: clamp(0.7rem, 1.8cqw, 0.85rem);
  font-weight: 700;
}

html[data-theme="dark"] .quest-board-coordinate {
  color: var(--ink);
}

.quest-board-coordinate.is-file {
  bottom: -22px;
  transform: translateX(-50%);
}

.quest-board-coordinate.is-rank {
  left: -16px;
  transform: translateY(-50%);
}

/* Шкала оценки (запрос пользователя 19.08.2026, второй заход — первая
   версия была статичными числами из данных квеста, пряталась до решения;
   нужна была живая, привязанная к движку) — тот же визуальный язык, что
   .eval-bar-wrap/.eval-bar/.eval-bar-fill/.eval-bar-score на Анализе (чёрный
   фон, непрозрачно-белая заливка снизу вверх, буквальные #fff/#000, не
   токены темы — иначе белая часть терялась бы в тёмной теме), но отдельные
   классы/JS-функции — без общих селекторов с Анализом (Н-5). */
.quest-eval-bar-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
  gap: 6px;
}

.quest-eval-bar {
  position: relative;
  display: flex;
  flex-direction: column-reverse;
  width: 16px;
  flex: 1;
  min-height: 100px;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: #000000;
  box-shadow: var(--shadow-soft);
}

.quest-eval-bar-fill {
  width: 100%;
  height: 50%;
  background: #ffffff;
  border-top: 3px solid var(--board-eval-accent);
  transition: height 420ms cubic-bezier(0.4, 0, 0.2, 1);
}

.quest-eval-bar-score {
  min-width: 30px;
  color: var(--ink);
  font-size: var(--fs-caption);
  font-weight: 700;
  text-align: center;
}

.quest-eval-bar-wrap.is-pending .quest-eval-bar {
  animation: questEvalBarPulse 1.1s ease-in-out infinite;
}

@keyframes questEvalBarPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/* Стартовый оверлей (запрос пользователя 18.08.2026): таймер и попытки не
   должны начинаться, пока пользователь не прочитал задачу и не нажал явно
   "Начать решение" — раньше startQuestClock() запускался автоматически при
   каждом выборе квеста. Оверлей перекрывает доску кликабельным слоем;
   JS дополнительно блокирует обработчик хода (questState.started), это не
   единственная защита. */
.quest-start-gate {
  position: absolute;
  z-index: 5;
  inset: 0;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 82%, transparent);
  backdrop-filter: blur(2px);
}

.quest-start-gate.is-hidden {
  display: none;
}

.quest-square {
  position: relative;
  display: grid;
  min-width: 0;
  min-height: 0;
  place-items: center;
  border: 0;
  border-radius: 0;
  color: var(--ink);
  font-family: "Segoe UI Symbol", "Noto Sans Symbols 2", "DejaVu Sans", Georgia, serif;
  font-size: clamp(2.25rem, 9cqw, 5rem);
  line-height: 1;
  transition: background 140ms ease, filter 140ms ease;
}

.quest-square:hover {
  filter: brightness(1.04);
}

.quest-square span,
.square span {
  position: relative;
  z-index: 1;
  display: inline-grid;
  width: 82%;
  height: 82%;
  place-items: center;
  border-radius: 50%;
  will-change: transform;
}

.quest-square span::before,
.square span::before {
  position: absolute;
  z-index: -1;
  inset: 9%;
  border-radius: 50%;
  content: "";
  opacity: 0.5;
  filter: blur(1px);
}

.quest-square .white-piece::before,
.square .white-piece::before {
  background: radial-gradient(circle, rgba(7, 27, 69, 0.26), rgba(7, 27, 69, 0.08) 48%, transparent 72%);
}

.quest-square .black-piece::before,
.square .black-piece::before {
  background: radial-gradient(circle, rgba(255, 255, 255, 0.36), rgba(255, 255, 255, 0.10) 48%, transparent 72%);
}

.quest-square .black-piece {
  color: var(--piece-dark);
  -webkit-text-stroke: 1.15px var(--piece-dark-stroke);
  text-shadow:
    0 1px 0 rgba(255, 255, 255, 0.42),
    0 8px 16px rgba(0, 0, 0, 0.24);
  filter:
    drop-shadow(0 2px 1px rgba(0, 0, 0, 0.18))
    drop-shadow(0 8px 10px rgba(11, 29, 70, 0.18));
}

.quest-square .white-piece {
  color: var(--piece-light);
  -webkit-text-stroke: 1.55px var(--piece-light-stroke);
  filter:
    drop-shadow(0 2px 1px rgba(0, 0, 0, 0.18))
    drop-shadow(0 8px 10px rgba(11, 29, 70, 0.18));
  text-shadow:
    0 1px 0 rgba(255, 255, 255, 0.88),
    0 0 3px rgba(7, 20, 46, 0.96),
    0 6px 16px rgba(7, 20, 46, 0.42);
}

.quest-square.light {
  background: var(--board-light);
}

.quest-square.dark {
  background: var(--board-dark);
}

.quest-square.is-selected::after,
.quest-square.is-last-from::after,
.quest-square.is-last-to::after {
  position: absolute;
  inset: 8%;
  border-radius: 6px;
  content: "";
  pointer-events: none;
}

.quest-square.is-selected::after {
  border: 2px solid color-mix(in srgb, var(--amber) 86%, #fff);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--amber) 20%, transparent);
}

.quest-square.is-last-from::after {
  border: 2px solid color-mix(in srgb, var(--board-accent) 70%, #fff);
}

.quest-square.is-last-to::after {
  background: color-mix(in srgb, var(--board-accent) 26%, transparent);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--board-accent) 80%, #fff);
}

.quest-square.is-wrong.is-last-from::after {
  border-color: color-mix(in srgb, var(--amber) 86%, #fff);
}

.quest-square.is-wrong.is-last-to::after {
  background: color-mix(in srgb, var(--amber) 30%, transparent);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--amber) 82%, #fff);
}

/* Доска задач «оживлена» 06.10.2026 по запросу пользователя: раньше при
   выборе фигуры на доске не появлялось ничего, кроме рамки на самой клетке,
   и было не видно, куда фигура может пойти. Своя группа селекторов —
   .quest-square.*, доски «Анализа» (.square.*) не касается: Н-5 запрещает
   общие селекторы между подсистемами. Форма повторяет доску «Анализа»
   намеренно, чтобы жест читался одинаково в обоих местах. */
.quest-square.is-legal-target::before {
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  width: 28%;
  height: 28%;
  border-radius: 50%;
  content: "";
  transform: translate(-50%, -50%);
  background: color-mix(in srgb, var(--board-accent) 55%, var(--ink));
  opacity: 0.55;
  pointer-events: none;
}

/* Взятие — кольцо по краю клетки, а не точка: точку закрыла бы фигура. */
.quest-square.is-legal-capture::before {
  position: absolute;
  z-index: 2;
  inset: 4%;
  border-radius: 50%;
  content: "";
  border: 4px solid color-mix(in srgb, var(--board-accent) 55%, var(--ink));
  opacity: 0.5;
  pointer-events: none;
}

.quest-square.is-check::before {
  position: absolute;
  inset: 0;
  z-index: 1;
  content: "";
  background: radial-gradient(circle, color-mix(in srgb, var(--red) 68%, transparent) 0%, transparent 72%);
  pointer-events: none;
}

/* Перетаскивание фигур на доске задач (06.10.2026). Свои классы, а не
   .piece-drag-ghost / .is-drag-source доски «Анализа»: Н-5 запрещает общие
   селекторы между подсистемами. Вид намеренно совпадает — жест должен
   читаться одинаково в обоих местах. */
.quest-square .chess-piece.is-quest-drag-source {
  opacity: 0.28;
}

.quest-drag-ghost {
  position: fixed;
  z-index: 60;
  transform: translate(-50%, -50%) scale(1.08);
  filter: drop-shadow(0 12px 20px rgba(2, 8, 23, 0.4));
  pointer-events: none;
}

/* Клетка под курсором во время переноса. */
.quest-square.is-drop-hover::after {
  position: absolute;
  inset: 0;
  z-index: 3;
  content: "";
  border-radius: 4px;
  box-shadow: inset 0 0 0 4px color-mix(in srgb, var(--board-accent) 70%, #fff);
  pointer-events: none;
}

/* Жест переноса не должен превращаться в выделение текста или в
   «перетаскивание картинки» браузером. */
.quest-square {
  -webkit-user-select: none;
  user-select: none;
}

.quest-square .chess-piece,
.quest-square img {
  -webkit-user-drag: none;
  user-drag: none;
}

/* Видимый фокус. До этой правки у клеток задач не было ни одного правила
   фокуса — клавиатурный пользователь не видел, где находится (WCAG 2.4.7). */
.quest-square:focus-visible {
  z-index: 4;
  outline: 3px solid #ffffff;
  outline-offset: -3px;
  box-shadow: inset 0 0 0 6px var(--board-accent);
}

.quest-square.is-hint-source::after {
  position: absolute;
  inset: 8%;
  border-radius: 6px;
  content: "";
  pointer-events: none;
  border: 2px solid color-mix(in srgb, var(--teal) 82%, #fff);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--teal) 22%, transparent);
  animation: quest-hint-pulse 1.1s ease-in-out infinite;
}

@keyframes quest-hint-pulse {
  0%, 100% { opacity: 0.65; }
  50% { opacity: 1; }
}

.quest-square.is-correct.is-last-to::after,
.quest-square.is-line.is-last-to::after {
  background: color-mix(in srgb, var(--green) 26%, transparent);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--green) 74%, #fff);
}

/* 18.08.2026 (запрос пользователя, редизайн Квестов): раньше это была
   отдельная рамка-бокс с min-height 64px, стоящая в ряду ещё двух таких же
   рамок (Попытки, Историческая линия) — вместе они и создавали "стену
   одинаковых карточек". Рамка убрана, остаётся только лёгкий фон —
   строка подсказки, а не карточка. */
/* 18.08.2026, доп. правка того же дня (запрос пользователя): полностью
   безрамочный вид оказался ПЕРЕборщенным — подсказка сливалась с соседним
   служебным текстом (Попытки/Историческая линия), не читалась как самое
   важное сообщение на панели. Возвращена явная визуальная роль — цветная
   полоса слева + обычный (не приглушённый) текст, но без полной рамки-бокса,
   чтобы не вернуть "стену одинаковых карточек". */
.quest-feedback {
  min-height: auto;
  margin: 0;
  padding: 8px 10px 8px 12px;
  border: 0;
  border-left: 3px solid color-mix(in srgb, var(--blue) 55%, var(--line-strong));
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--surface-soft);
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  line-height: 1.45;
}

/* `.quest-detail p:not(.eyebrow)` (выше по файлу) — (0,2,1) по специфичности
   против (0,1,0) у одного .quest-feedback, поэтому color молча проигрывал
   тому старому правилу, а border-left применялся (там конфликта не было).
   ID здесь просто выигрывает специфичность, не ради переиспользования. */
#questFeedback {
  color: var(--ink);
}

/* Рамка/фон/минимальная высота — только когда внутри реальные попытки
   (.quest-attempt-list). В состоянии "попыток ещё нет" блок — тонкая
   строка без рамки, а не пустая карточка (запрос пользователя 18.08.2026). */
.quest-attempts {
  display: grid;
  gap: 8px;
}

.quest-attempts:has(.quest-attempt-list) {
  min-height: 58px;
  padding: 10px;
  border: 1px solid color-mix(in srgb, var(--line) 84%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 74%, transparent);
}

.quest-attempts > strong {
  font-size: var(--fs-body-sm);
}

.quest-attempts > span {
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  line-height: 1.35;
}

.quest-attempt-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.quest-attempt {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 32px;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

.quest-attempt span {
  color: var(--muted);
  font-size: var(--fs-caption);
  text-transform: uppercase;
}

.quest-attempt.is-correct {
  border-color: color-mix(in srgb, var(--green) 36%, var(--line));
  background: color-mix(in srgb, var(--green) 10%, var(--surface));
}

.quest-attempt.is-wrong {
  border-color: color-mix(in srgb, var(--amber) 36%, var(--line));
  background: color-mix(in srgb, var(--amber) 10%, var(--surface));
}

/* Та же логика, что у .quest-attempts выше: рамка/фон — только когда в
   линии есть реальные ходы (.quest-line-step). В состоянии ожидания первого
   хода — тонкая строка без рамки (запрос пользователя 18.08.2026). Полностью
   пустой блок (партий без линии) — без padding/рамки просто не занимает
   места.
   19.08.2026 (запрос пользователя, п.3+4): зелёная полоса прогресса и
   баннер-подсказка убраны совсем (см. renderQuestLine() в app.js) — селектор
   ниже раньше проверял именно полосу (.quest-line-progress), теперь
   проверяет сами ходы. Список ходов вынесен в свой прокручиваемый блок
   .quest-line-steps — своя рамка не даёт ему сливаться с карточкой "Миссия
   решена" ниже (та же путаница, что жаловался пользователь на скриншоте).
   flex:1 — линия дотягивается до нижнего края доски и сама уступает место
   карточке "Миссия решена", когда та появляется (обе тянутся к одной и той
   же нижней границе внутри .quest-info-scroll, см. правило там). */
.quest-line {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
}

.quest-line:has(.quest-line-step) {
  padding: 12px;
  border: 0;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface-soft) 88%, transparent);
  box-shadow: var(--shadow-soft);
}

.quest-line > strong {
  flex: 0 0 auto;
  font-size: var(--fs-body-sm);
}

.quest-line > span,
.quest-line-step span,
.quest-line-complete {
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  line-height: 1.38;
}

.quest-line-steps {
  display: grid;
  align-content: start;
  gap: 8px;
  flex: 1 1 auto;
  min-height: 64px;
  overflow-y: auto;
  padding-right: 4px;
}

.quest-line-step {
  display: grid;
  gap: 3px;
  padding: 9px;
  border: 0;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-soft);
}

/* Цвет по стороне хода (запрос пользователя 19.08.2026, п.2) — тот же
   приём, что у карточки игрока на Анализе: белые — обычный светлый фон
   (по умолчанию выше, отдельного класса не требует), чёрные — светлый
   графит (см. .player-side.is-black, тот же #e4e7ec/#2b3440). Текущий шаг
   (.is-current) теперь отмечается только цветом рамки, не заливкой — иначе
   спорил бы с цветом стороны. */
.quest-line-step.is-black-move {
  background: #e4e7ec;
}

.quest-line-step.is-black-move b,
.quest-line-step.is-black-move span {
  color: #2b3440;
}

.quest-line-step.is-current {
  /* border-color больше не на чём отрисовываться (базовый border:0) —
     сигнал "текущий шаг" остаётся зелёным кольцом, добавленным поверх
     обычного выпуклого рельефа шага, а не взамен него. */
  box-shadow: var(--shadow-soft), inset 0 0 0 1px color-mix(in srgb, var(--green) 46%, transparent);
}

.quest-line-step b {
  color: var(--ink);
  font-size: var(--fs-body);
}

.quest-line-complete {
  flex: 0 0 auto;
  padding: 8px 9px;
  border-radius: var(--radius);
  background: var(--green-soft);
  color: color-mix(in srgb, var(--green-text) 58%, var(--ink));
}

/* 19.08.2026 (баг, найден пользователем): renderQuestResult() пишет "" в
   этот блок, пока новая попытка уже решённого квеста ещё не завершена
   (см. п.1 из 18.08.2026) — но рамка/фон/padding раньше применялись
   БЕЗУСЛОВНО, поэтому пустой блок всё равно рисовал зелёную полосу без
   текста. Стили — только когда внутри есть контент. */
.quest-result {
  display: grid;
  gap: 8px;
  min-height: auto;
}

.quest-result:not(:empty) {
  padding: 12px;
  border: 1px solid color-mix(in srgb, var(--green) 24%, var(--line));
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--green) 6%, var(--surface));
}

.quest-result > strong {
  font-size: var(--fs-body);
}

.quest-result p {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  line-height: 1.42;
}

/* 18.08.2026 (запрос пользователя, редизайн Квестов): решётка из 5
   подписанных боксов + отдельная строка наград ниже — заменена на
   заголовок с иконкой + одну строку с цифрами через запятую. Награды
   показываются здесь один раз (превью выше прячется, см. .is-hidden-duplicate). */
.quest-result-solved {
  display: flex;
  align-items: center;
  gap: 8px;
}

.quest-result-icon {
  display: inline-grid;
  flex: 0 0 auto;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--green-soft);
  color: color-mix(in srgb, var(--green-text) 58%, var(--ink));
  font-size: var(--fs-caption);
  font-weight: 700;
}

.quest-result-solved strong {
  font-size: var(--fs-body);
}

.quest-result-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

.quest-result-line b {
  color: var(--ink);
  font-weight: 700;
}

.quest-brief div {
  display: grid;
  gap: 5px;
  padding: 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
}

.quest-brief strong {
  line-height: 1.36;
}

.quest-brief .quest-insight {
  border-color: color-mix(in srgb, var(--green) 34%, var(--line));
  background: color-mix(in srgb, var(--green) 8%, var(--surface));
}

.quest-brief .quest-insight strong {
  color: color-mix(in srgb, var(--green-text) 48%, var(--ink));
}

/* Оверлей-поздравление при завершении линии своим ходом (запрос
   пользователя 18.08.2026, п.7). 19.08.2026 (запрос пользователя): раньше
   перекрывал только правую панель — теперь весь экран (position:fixed,
   а не absolute внутри .quest-info-panel), т.к. просили "весь экран
   заблюрится". z-index 200 — выше любого другого элемента на странице
   (максимум в файле был 90). .quest-start-gate (закрывает доску ДО начала)
   остаётся отдельным, локальным оверлеем — не переиспользуют друг друга. */
.quest-celebration {
  position: fixed;
  z-index: 200;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 16px;
  background: color-mix(in srgb, var(--surface) 78%, transparent);
  backdrop-filter: blur(6px);
  cursor: pointer;
}

.quest-celebration[hidden] {
  display: none;
}

.quest-celebration-card {
  display: grid;
  gap: 14px;
  max-width: 320px;
  padding: 20px;
  border: 1px solid color-mix(in srgb, var(--green) 30%, var(--line));
  border-radius: var(--radius);
  background: var(--surface);
  text-align: center;
  cursor: default;
  box-shadow: var(--shadow-soft);
}

.quest-celebration-card.is-hinted {
  border-color: color-mix(in srgb, var(--amber) 34%, var(--line));
}

.quest-celebration-headline {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: 1.4;
}

.quest-celebration-badges {
  display: flex;
  justify-content: center;
  gap: 14px;
}

.quest-celebration-badges figure {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 6px;
  margin: 0;
}

/* "?" на флажке — объяснение по клику (запрос пользователя 19.08.2026, п.2):
   вместо того чтобы пытаться вплести название награды в одну фразу (у
   разных квестов разные названия — Историк/Романтик/Профилактик/...),
   у каждого флажка своя раскрывающаяся подсказка. */
.quest-badge-info {
  position: absolute;
  top: -4px;
  right: 8px;
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  border: 0;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: var(--shadow-soft);
  color: var(--muted);
  font-size: 10px;
  font-weight: 700;
  cursor: pointer;
  transition: box-shadow 150ms ease, color 150ms ease;
}

.quest-badge-info:hover,
.quest-badge-info:focus-visible {
  box-shadow: var(--shadow);
  color: var(--blue-dark);
}

/* Раздел 7 ТЗ 27.08.2026: раньше здесь стоял border-color на фокус (когда
   была рамка) — после перехода на рельеф общий `outline: none;` остался
   висеть и глушил единственный оставшийся сигнал фокуса. box-shadow выше
   у hover/focus-visible одинаковый — не отличить наведение от фокуса
   клавиатурой. Явно возвращаю акцентное кольцо для фокуса отдельно. */
.quest-badge-info:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--blue) 34%, transparent);
  outline-offset: 2px;
}

.quest-badge-tooltip {
  position: absolute;
  top: 100%;
  left: 50%;
  z-index: 1;
  width: 150px;
  margin-top: 6px;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-caption);
  font-weight: 600;
  line-height: 1.4;
  text-align: left;
  box-shadow: var(--shadow-soft);
  transform: translateX(-50%);
  cursor: default;
}

.quest-badge-tooltip[hidden] {
  display: none;
}

/* Плоская заливка → небольшой выпуклый рельеф (принцип 6, тот же приём,
   что у .critical-item-badge на Анализе) — цвет-смысл (зелёный/янтарный
   для "подсказка использована") не меняется. */
.quest-celebration-badges .quest-badge-icon {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--green-soft);
  color: color-mix(in srgb, var(--green-text) 55%, var(--ink));
  box-shadow: 3px 3px 7px color-mix(in srgb, var(--green) 40%, transparent), -2px -2px 6px var(--neu-highlight);
}

.quest-celebration-card.is-hinted .quest-badge-icon {
  background: color-mix(in srgb, var(--amber) 18%, var(--surface-soft));
  color: color-mix(in srgb, var(--amber) 60%, var(--ink));
  box-shadow: 3px 3px 7px color-mix(in srgb, var(--amber) 40%, transparent), -2px -2px 6px var(--neu-highlight);
}

.quest-celebration-badges .quest-badge-icon svg {
  width: 22px;
  height: 22px;
}

.quest-celebration-badges figcaption {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
}

.quest-celebration-dismiss {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Постоянный (не оверлейный) ряд наград в самой карточке результата —
   заменяет старые плоские текстовые плашки .quest-result-rewards span,
   те же иконки-заглушки, что и в оверлее выше, только меньше. */
.quest-result-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.quest-result-badges .quest-badge-icon {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--green-soft);
  color: color-mix(in srgb, var(--green-text) 55%, var(--ink));
  box-shadow: 2px 2px 5px color-mix(in srgb, var(--green) 40%, transparent), -1px -1px 4px var(--neu-highlight);
}

.quest-result-badges .quest-badge-icon svg {
  width: 15px;
  height: 15px;
}

.quest-result-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
}

/* Компактная запись истории решений (запрос пользователя 18.08.2026, п.1) —
   одна строка на каждое прохождение, а не отдельная карточка. */
.quest-result-history {
  display: grid;
  gap: 3px;
}

.quest-result-history-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

.quest-result-history-row b {
  color: var(--ink);
  font-weight: 700;
}

.contact-actions {
  display: grid;
  align-content: center;
  gap: 10px;
}

.contact-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 0 12px;
  color: var(--ink);
  text-decoration: none;
}

.workspace {
  display: grid;
  /* Одна колонка с 29.10.2026: выбор партии переехал во вкладку «Партия»
     на экране анализа, отдельной панели слева больше нет. */
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.analysis-stage,
.coach-panel,
.endgame-wrap {
  /* Рамка убрана (ТЗ 27.08.2026) — этот общий класс держал ОДНОВРЕМЕННО
     рамку и уже неоморфную box-shadow: var(--shadow) (значение токена
     поменялось раньше в этой же задаче), из-за чего у всей левой панели
     «Источник партии» (.input-panel) и всего правого блока результата
     (.analysis-stage) сохранялась плоская обводка поверх объёма —
     пропущено в прошлом заходе по «Анализу», досмотрел только вложенные
     карточки, не сами внешние обёртки. */
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
}

/* Вкладка «Партия» (29.10.2026): бывшая панель «Анализ PGN / FEN»
   внутри правой колонки экрана анализа. Скрытие по [hidden] даёт
   .tab-panel[hidden] — он специфичнее этого правила. */
.game-panel {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 14px;
  padding: 4px 2px 8px;
}

.game-panel .field textarea {
  min-height: 180px;
}

/* Выбор PGN/FEN · Chess.com · Lichess — в стиле левого меню, как и
   остальные горизонтальные переключатели (31.10.2026): дорожка без рамки,
   пункты без рамок, выбранный — светло-голубой с синим текстом.
   Специфичность выше общих .segment / .segment.is-active, поэтому
   выбранный пункт описан здесь же. */
.game-panel > .segmented {
  gap: 2px;
  padding: var(--switch-pad);
  border-radius: calc(var(--switch-radius) + var(--switch-pad));
  background: var(--switch-track);
  box-shadow: none;
}

.game-panel > .segmented .segment {
  /* 38 + дорожка 2×3 = прежние 44px. */
  min-height: 38px;
  border: 0;
  border-radius: var(--switch-radius);
  background: transparent;
  color: var(--muted);
  transition: background-color 150ms ease, color 150ms ease;
}

.game-panel > .segmented .segment:not(.is-active):hover {
  color: var(--ink);
  background: var(--surface-strong);
}

.game-panel > .segmented .segment.is-active {
  background: var(--switch-active-bg);
  color: var(--an-accent);
  box-shadow: none;
}

.game-panel > .segmented .segment:focus-visible {
  outline: 2px solid var(--an-accent);
  outline-offset: 1px;
}

.panel-heading,
.section-heading,
.board-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.chart-controls {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  min-width: max-content;
}

/* 96px было рассчитано на подпись вроде «1x» в широкой панели; в узкой
   (328px) из-за них вся строка кнопок не влезала рядом с заголовком и
   переносилась вниз — это +26px к каждому из двух графиков
   (29.10.2026). 56px хватает самой длинной подписи масштаба. */
.chart-zoom-label {
  display: grid;
  min-width: 56px;
  height: 34px;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface-soft);
  color: var(--blue-dark);
  font-size: var(--fs-caption);
  font-weight: 700;
}

.panel-heading.compact {
  margin-bottom: 8px;
}

/* Заголовок графика вместо eyebrow-строки, по референсу примеры
   графиков.png (запрос пользователя 06.08.2026). Значок «ⓘ» рядом с
   заголовком убран 31.10.2026 по просьбе пользователя. */
.panel-heading-title {
  display: flex;
  align-items: center;
  gap: 8px;
}

.icon-button {
  display: grid;
  width: 42px;
  height: 42px;
  flex: 0 0 auto;
  place-items: center;
  border: 0;
  border-radius: var(--radius-control);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
  color: var(--ink);
  font-size: var(--icon-lg);
  font-weight: 700;
  transition: box-shadow 150ms ease;
}

.icon-button:hover {
  box-shadow: var(--shadow);
}

.icon-button:active {
  box-shadow: var(--shadow-inset-soft);
}

.field {
  display: grid;
  gap: 7px;
}

/* История никнеймов (26.10.2026). Список всплывает под полем, поэтому
   .nickname-field — точка отсчёта для абсолютного позиционирования, а
   z-index поднимает его над соседними карточками. */
.nickname-field {
  position: relative;
}

.nickname-history {
  position: absolute;
  top: 100%;
  right: 0;
  left: 0;
  z-index: 30;
  max-height: 232px;
  margin: 6px 0 0;
  padding: 6px;
  overflow-y: auto;
  list-style: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.nickname-history-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 32px;
  align-items: center;
  gap: 4px;
  border-radius: 8px;
}

.nickname-history-item:hover {
  background: var(--surface-strong);
}

.nickname-history-pick {
  padding: 9px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nickname-history-drop {
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-body);
  line-height: 1;
  cursor: pointer;
}

.nickname-history-drop:hover {
  background: color-mix(in srgb, var(--red) 14%, transparent);
  color: var(--red);
}

.nickname-history-pick:focus-visible,
.nickname-history-drop:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: -2px;
}

.field span {
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

.field input,
.field select,
.field textarea {
  width: 100%;
  border: 0;
  border-radius: var(--radius-control);
  outline: 0;
  /* Вдавленное состояние (ТЗ 27.08.2026, принцип 3 и 5.4) — поле выглядит
     утопленным в поверхность вместо обводки. Тот же стиль подходит и
     будущей форме на «Связаться с нами» (см. раздел 5.4 ТЗ). */
  background: var(--bg);
  box-shadow: var(--shadow-inset-soft);
  color: var(--ink);
}

.field input,
.field select {
  min-height: 44px;
  padding: 10px 12px;
}

.field textarea {
  min-height: 300px;
  resize: vertical;
  padding: 12px;
  line-height: 1.45;
}

.field-grow {
  flex: 1;
}

/* Единственный переключатель профиля анализа, оставшийся в интерфейсе
   (запрос пользователя 03.09.2026) — не полноценный селектор режимов
   (тот убран 15.08.2026), просто опция "подождать дольше ради точности"
   поверх быстрого профиля по умолчанию. */
.deep-analysis-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 400;
  cursor: pointer;
}

/* Переключатель вместо галочки (31.10.2026, запрос пользователя): общий для
   «Глубокого анализа» и звука ходов в настройках доски. Селектор с
   [type] — сильнее правил размеров у .board-settings-row input и здесь. */
input.switch-toggle[type="checkbox"] {
  position: relative;
  flex: none;
  width: 34px;
  height: 20px;
  margin: 0;
  border-radius: 999px;
  background: var(--line-strong);
  appearance: none;
  cursor: pointer;
  transition: background-color 150ms ease;
}

input.switch-toggle[type="checkbox"]::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.25);
  transition: transform 150ms ease;
}

input.switch-toggle[type="checkbox"]:checked {
  background: var(--an-accent);
}

input.switch-toggle[type="checkbox"]:checked::before {
  transform: translateX(14px);
}

input.switch-toggle[type="checkbox"]:focus-visible {
  outline: 2px solid var(--an-accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  input.switch-toggle[type="checkbox"],
  input.switch-toggle[type="checkbox"]::before {
    transition: none;
  }
}

.deep-analysis-toggle input {
  width: 16px;
  height: 16px;
  accent-color: var(--accent);
  cursor: pointer;
}

.profile-engine-info {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 2px;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

.profile-engine-info span:first-child {
  overflow: hidden;
  color: var(--ink);
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.segmented {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* overflow:hidden убран (раздел 7 ТЗ 27.08.2026, найдено при проверке
     фокуса): раньше он подрезал квадратные углы дочерних .segment в
     скругление капсулы — теперь у каждого .segment свой border-radius и
     4px padding/gap от края, задача overflow:hidden больше не нужна, а
     он попутно подрезал верх/низ 3px focus-outline у крайних кнопок при
     табуляции с клавиатуры (замер: 0.8px кольца срезано). */
  border: 0;
  border-radius: var(--radius);
  /* Вдавленная капсула (ТЗ 5.5) — объём переносится на активный пункт. */
  background: var(--bg);
  box-shadow: var(--shadow-inset-soft);
  padding: 4px;
  gap: 4px;
}

.segment {
  min-height: 40px;
  min-width: 0;
  border: 0;
  /* Разделительные линии убраны (ТЗ 5.5) — разделение теперь только за
     счёт объёма активного пункта. */
  background: transparent;
  border-radius: calc(var(--radius) - 6px);
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  transition: box-shadow 150ms ease, background 150ms ease, color 150ms ease;
}

.segment.is-active {
  /* Выпуклый «остров» внутри вдавленной капсулы, заливка остаётся
     акцентной (ТЗ 5.5 — заливку сохранить, поменять только объём). */
  background: var(--blue);
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.12);
  /* var(--surface), а не #fff: в тёмной теме --blue светлый, и белый текст
     на нём давал 2.54:1. Тёмная подложка даёт 5.75:1, светлая — прежние 5.17:1. */
  color: var(--surface);
}

.actions {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 10px;
}

.file-actions {
  display: grid;
}

.primary-button,
.ghost-button {
  min-height: 46px;
  border-radius: var(--radius-control);
  font-weight: 700;
}

.primary-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 0;
  background: var(--accent);
  color: #fff;
  /* Точечный акцент (принцип 6, ТЗ 27.08.2026): заливка остаётся акцентной,
     но плоскость сменяется на выпуклый рельеф её же цвета — не серый. */
  box-shadow: 0 1px 2px rgba(16, 24, 40, 0.12);
  transition: background-color 150ms ease, transform 150ms ease;
}

.primary-button:hover {
  background: color-mix(in srgb, var(--accent) 88%, #000);
}

.primary-button:active {
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
  transform: translateY(1px);
}

.ghost-button {
  border: 0;
  background: var(--bg);
  box-shadow: var(--shadow-soft);
  color: var(--ink);
  transition: box-shadow 150ms ease, transform 150ms ease;
}

.ghost-button:hover {
  box-shadow: var(--shadow);
}

.ghost-button:active {
  box-shadow: var(--shadow-inset-soft);
  transform: translateY(1px);
}

.ghost-button.wide {
  width: 100%;
}

.ghost-button.compact {
  min-height: 36px;
  padding: 6px 10px;
  font-size: var(--fs-body-sm);
}

.recent-games-panel {
  display: grid;
  gap: 10px;
  padding: 12px;
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
}

/* Поле вставки PGN/FEN прячется, когда источник — Chess.com или Lichess
   (26.10.2026). Отдельное правило, потому что .is-hidden в этом проекте
   не глобальный класс, а объявляется у каждого компонента своим. */
.field.is-hidden {
  display: none;
}

/* Кнопки «Анализировать / Очистить / Открыть PGN-файл» — только для
   PGN/FEN (31.10.2026), см. updateSourceMode() в app.js. */
.actions.is-hidden,
.file-actions.is-hidden {
  display: none;
}

.recent-games-panel.is-hidden {
  display: none;
}

.recent-games-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.recent-games-head h3 {
  margin-bottom: 0;
}

.cache-note {
  min-height: 28px;
  padding: 7px 8px;
  border-radius: 6px;
  background: var(--surface-strong);
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 400;
}

/* Подсказка на месте списка, пока партий нет (31.10.2026): что ввести и
   что будет дальше. Пунктир — «здесь появятся партии». */
.recent-empty {
  margin: 0;
  padding: 16px 14px;
  border: 1px dashed var(--line);
  border-radius: var(--radius-control);
  color: var(--muted);
  font-size: var(--fs-body-sm);
  line-height: 1.45;
  text-align: center;
}

.recent-games-list {
  display: grid;
  gap: 8px;
  /* Карточка с доской выше прежней — 520px вмещают ~3 партии. */
  max-height: 520px;
  overflow: auto;
  padding-right: 2px;
}

.game-card {
  display: grid;
  gap: 8px;
  width: 100%;
  padding: 10px;
  border: 0;
  /* border-left-color — индикатор результата (Н-3, docs/DECISIONS.md),
     не трогать. Сама рамка вокруг карточки убрана, роль обводки берёт тень. */
  border-left: 5px solid var(--muted);
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
  color: var(--ink);
  text-align: left;
}

.game-card-main {
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr);
  gap: 9px;
  align-items: center;
}

.game-avatar,
.player-card img {
  display: block;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-strong);
  object-fit: cover;
}

.game-avatar {
  width: 38px;
  height: 38px;
}

.game-card.win {
  border-left-color: var(--green);
}

.game-card.loss {
  border-left-color: var(--red);
}

.game-card.draw {
  /* Серый по просьбе пользователя 25.10.2026 (было var(--amber)). */
  border-left-color: #9ca3af;
}

.game-card.is-active {
  border-color: var(--blue);
  border-left-color: var(--blue);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 18%, transparent);
}

.game-card-top,
.game-card-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.game-card strong {
  min-width: 0;
  overflow: hidden;
  font-size: var(--fs-body-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.result-pill {
  flex: 0 0 auto;
  padding: 4px 7px;
  border-radius: var(--radius-control);
  background: var(--surface-strong);
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
}

.game-card.win .result-pill {
  background: var(--green-soft);
  color: var(--green-text);
}

.game-card.loss .result-pill {
  background: var(--red-soft);
  color: var(--red);
}

.game-card.draw .result-pill {
  background: var(--amber-soft);
  color: var(--amber-text);
}

.game-card-meta {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
}

/* Карточки очков — под своим игроком, а не отдельной колонкой справа
   (запрос пользователя 06.08.2026). Три колонки: белая сторона / счёт /
   чёрная сторона, каждая сторона — .player-side (игрок сверху, его же
   Точность+Перформанс снизу). */
.player-strip {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 8px 20px;
  align-items: center;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 14px;
}

.player-strip.is-hidden {
  display: none;
}

/* Карточка игрока (рамка + текст) уменьшена на ~15% (запрос пользователя
   08.08.2026) — было gap/padding 10px, ниже везде отдельные явные px
   вместо var(--fs-*) там, где меняется размер шрифта: трогать сами
   токены --fs-h2/h3/caption/body-sm нельзя, они общие на весь сайт. */
.player-side {
  display: flex;
  flex-direction: column;
  gap: 9px;
  min-width: 0;
  padding: 9px;
  border-radius: var(--radius);
}

/* Подсветка блока игрока по стороне (запрос пользователя 07.08.2026,
   уточнено в тот же день): белая сторона — белое свечение, чёрная —
   чёрное. У чёрной стороны первая правка использовала полупрозрачный
   чёрный (color-mix с прозрачностью) — на светлом фоне это читалось как
   серый, а не чёрный, поэтому у неё теперь ещё и сплошное непрозрачное
   чёрное кольцо (0 0 0 2px #000), а не только мягкое свечение. Тонкая
   обводка (--line-strong) у белой стороны держит границу видимой, даже
   если свечение слабо контрастирует с фоном страницы. */
.player-side.is-white {
  box-shadow:
    0 0 0 1px var(--line-strong),
    0 0 16px 3px color-mix(in srgb, white 55%, transparent);
}

.player-side.is-black {
  /* 26.08.2026 (запрос пользователя, «среднее» между старым светлым
     графитом и прежним тёмным HUD-фоном от 08.08.2026): #e4e7ec от
     18.08.2026 читался как обычный серый, попросили сделать темнее,
     ближе к чёрному, но не до конца чёрным. #4b5563 — средний
     тёмно-графитовый, WCAG-контраст с белым текстом посчитан заранее
     (не на глаз): 6.5–7.6 в зависимости от оттенка текста, с запасом
     выше порога 4.5:1 для обычного текста. Цвет литеральный, не токен
     темы (та же логика, что и раньше — карточка чёрной стороны не
     должна зависеть от переключателя темы сайта). */
  background: #4b5563;
  box-shadow:
    0 0 0 1px #9aa3b0,
    0 0 12px 2px color-mix(in srgb, #6b7684 40%, transparent);
}

.player-card {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  gap: 10px;
  align-items: center;
  min-width: 0;
}

/* Явное разделение сторон в шапке (запрос пользователя 03.08.2026, не из
   референса): чёрная сторона — на лёгкой серой подложке, чтобы читалась
   как отдельный блок. Распространяется и на карточки очков ниже.
   08.08.2026: серую подложку заменили на тёмно-чёрную, текст — на синий/
   голубой (литеральные цвета, не токены темы).
   18.08.2026 (запрос пользователя): чёрный HUD-фон + неоновый синий текст
   выглядели слишком «игровым меню» и отпугивали, а не привлекали. Заменено
   на пару «слоновая кость / графит» — оба светлые, разница только в
   оттенке (тёплый у белых, холодный у чёрных), текст — обычный тёмный.
   Цвета по-прежнему литеральные, не токены темы (та же логика фиксации,
   что и раньше). */
.player-card.is-black {
  grid-template-columns: minmax(0, 1fr) 48px;
  padding: 7px 10px;
  border-radius: var(--radius);
  /* #4b5563 — средний тёмно-графитовый, см. .player-side.is-black выше. */
  background: #4b5563;
  text-align: right;
}

.player-card.is-black img {
  order: 2;
}

.player-card.is-black span,
.player-card.is-black strong,
.player-card.is-black small {
  /* Светлый текст (было тёмное #2b3440) — фон снова тёмный (26.08.2026),
     тёмный текст на тёмном фоне был бы нечитаем. Контраст посчитан
     заранее против нового фона #4b5563, см. .player-side.is-black. */
  color: #eef0f3;
}

.player-card.is-focus strong {
  color: var(--blue-dark);
}

/* Имя фокус-игрока на чёрной карточке — светло-голубой, не обычный
   var(--blue-dark) (запрос пользователя 26.08.2026): в светлой теме
   сайта это насыщенный тёмно-синий #1d4ed8, плохо читался на новом
   тёмном фоне карточки #4b5563 (контраст ниже, чем хотелось бы — не
   критично нечитаем, но пользователь попросила светлее и голубее).
   #93c5fd — тот же оттенок, что уже используется как --blue-dark в
   тёмной теме сайта (переиспользован, не выдуман заново), контраст
   против #4b5563 посчитан заранее: 4.19 — выше порога 3:1 для крупного
   текста (имя — 20px). Специфичнее правила выше на один класс
   (.is-black) — переопределяет его именно на чёрной карточке, белая
   сторона не затронута. */
.player-card.is-black.is-focus strong {
  color: #93c5fd;
}

.player-card img {
  width: 48px;
  height: 48px;
  border-radius: 9px;
}

.player-card span {
  display: block;
  color: var(--muted);
  /* Было 10px — ниже минимума 12px, при котором текст остаётся читаемым
     (пункт №8 аудита). Переведено на токен шкалы 07.10.2026. */
  font-size: var(--fs-caption);
  font-weight: 600;
}

.player-card strong {
  display: block;
  min-width: 0;
  overflow: hidden;
  font-size: var(--fs-h3);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.player-card small {
  display: block;
  color: var(--muted-faint);
  font-size: var(--fs-caption);
  /* Подпись подняли с 9.9px до 12px, и «Chess.com» перестал влезать в
     узкую карточку. Это вторичная строка — обрезаем многоточием, а не
     рвём слово посередине и не возвращаем нечитаемый размер. */
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-weight: 500;
}

.versus-block {
  display: grid;
  min-width: 92px;
  place-items: center;
  gap: 4px;
  text-align: center;
}

.versus-block strong {
  padding: 4px 16px;
  border-radius: var(--radius-control);
  background: var(--surface-strong);
  font-size: var(--fs-h2);
  font-weight: 700;
  line-height: 1.4;
}

.versus-block span {
  display: block;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 400;
}

.versus-block #matchOutcome {
  color: var(--secondary-text, var(--ink));
  font-weight: 600;
}

.versus-block #matchSource:empty {
  display: none;
}

.status-line {
  min-height: 22px;
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  line-height: 1.35;
}

/* Уровни важности статуса. Текст берёт затемнённый слой палитры (U-5):
   яркие --mistake / --amber как текст не проходят контраст. */
.status-line.is-error {
  color: var(--mistake-text);
  font-weight: 600;
}

.status-line.is-warning {
  color: var(--amber-text);
  font-weight: 600;
}

/* С 29.10.2026 (запрос пользователя) нейтральные подсказки вроде «Готов к
   локальному анализу» на экране не показываются — их слышит только
   читалка (role="status"). Видно лишь предупреждение: «Введите ник»,
   «Выберите партию» — иначе кнопка молча ничего бы не делала. Ошибки и так
   выходят баннером над экраном анализа. */
.status-line:not(.is-warning) {
  position: absolute;
  width: 1px;
  height: 1px;
  min-height: 0;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Пункт №7: строка статуса живёт в панели ввода (с 29.10.2026 — вкладка
   «Партия»), а та после анализа не видна — сообщение об ошибке терялось.
   Баннер поэтому на уровне .workspace, над экраном анализа. */
.analysis-error {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid color-mix(in srgb, var(--mistake) 42%, var(--line));
  border-left: 3px solid var(--mistake);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--mistake) 8%, var(--surface));
}

.analysis-error[hidden] {
  display: none;
}

.analysis-error span {
  min-width: 0;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 400;
  line-height: 1.4;
}

.analysis-stage {
  scroll-margin-top: 24px;
  min-width: 0;
  padding: 16px;
}

.tab-panel > .move-table-wrap,
.tab-panel > .analysis-review-grid,
.tab-panel > .tables-section,
.tab-panel > .analysis-facts-details {
  margin-top: 18px;
}

/* Вкладки отчёта. Скрытая панель убрана через [hidden] — она не попадает
   ни в таб-порядок, ни в дерево доступности. */
.tab-panel[hidden] {
  display: none;
}

.tab-panel:focus-visible {
  outline: 3px solid color-mix(in srgb, var(--blue) 34%, transparent);
  outline-offset: 4px;
}

/* Вкладки — первый элемент .board-side (запрос пользователя 16.08.2026,
   ТЗ «реструктуризация layout») — раньше жили в отдельном .analysis-tabs-align
   full-width гриде над доской+панелью специально подогнанном под ширину
   .board-with-side; теперь просто наследуют ширину .board-side, тот приём
   больше не нужен. */

.analysis-tabs {
  /* flex-wrap, не grid с фиксированными 3 колонками — ТЗ прямо разрешает
     третьей вкладке переноситься на вторую строку, если не помещается
     (п.3), а .board-side теперь узкая колонка, не полная ширина. */
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  overflow: visible;
  border: 0;
  border-radius: 0;
  background: transparent;
  /* .analysis-tabs несёт класс .segmented (см. комментарий у .segment выше)
     — вместе с ним по наследству идёт .segmented { box-shadow:
     var(--shadow-inset-soft) }, которую этот блок не отменял. Итог —
     видимая "общая рамка" вокруг всех трёх вкладок (запрос пользователя
     27.08.2026). Каждая вкладка уже сама себе даёт нужную тень. */
  box-shadow: none;
}

/* Каждая вкладка — своя отдельная рамка (запрос пользователя 08.08.2026)
   — раньше был просто текст с подчёркиванием у активной, общей нижней
   границы .analysis-tabs теперь не нужно, роль разделителя выполняют
   сами рамки. Компактнее прежнего (запрос пользователя 16.08.2026) —
   min-height 46→34px, padding сужен, но typography/цвет/border не тронуты. */
.analysis-tabs .segment {
  flex: 1 1 auto;
  min-height: 34px;
  padding: 5px 10px;
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  text-align: left;
  transition: box-shadow 150ms ease, color 150ms ease;
}

.analysis-tabs .segment.is-active {
  /* Вдавленное состояние, как у вкладок шапки (5.1) — та же логика для
     той же роли компонента (активная вкладка). */
  box-shadow: var(--shadow-inset-soft);
  background: var(--bg);
  color: var(--accent-text);
}

.analysis-review-grid,
.tables-section {
  padding-top: 18px;
  border-top: 1px solid var(--line);
}

.chart-zone,
.segments-panel {
  min-width: 0;
  padding: 0;
}

/* .tables-section — теперь <details> (запрос пользователя 16.08.2026,
   ТЗ «реструктуризация layout», п.11): «Время на ход» свёрнут по
   умолчанию, «Качество ходов» — нет. Тот же чекврон-паттерн, что уже
   есть у .analysis-facts-details, typography/цвет заголовка не тронуты
   (наследуются от .panel-heading-title/h2). */
.tables-section > summary {
  cursor: pointer;
  list-style: none;
}

.tables-section > summary::-webkit-details-marker {
  display: none;
}

/* В строке заголовка «Времени на ход» теперь три элемента: название,
   кнопки масштаба и шеврон (29.10.2026). space-between от .panel-heading
   растащил бы кнопки на середину строки — прижимаем их вправо, к
   шеврону, как у «Качества ходов», где элементов всего два. */
.tables-section > summary > .chart-controls {
  margin-left: auto;
}

.tables-section > summary::after {
  content: "▾";
  flex: 0 0 auto;
  color: var(--muted);
  font-size: var(--icon-sm);
  transition: transform 160ms ease;
}

.tables-section[open] > summary::after {
  transform: rotate(180deg);
}

.tables-section:not([open]) > summary {
  margin-bottom: 0;
}

/* Референс не подписывает очки отдельным «Белые»/«Чёрные» — сторона уже
   названа в .player-card выше; ряд из четырёх плоских карточек в той же
   строке, что игроки и счёт (4-я колонка .player-strip). */
.score-card-row {
  display: flex;
  flex-wrap: wrap;
  gap: 9px;
}

.score-card-row .score-card {
  flex: 1 1 calc(50% - 5px);
  min-width: 0;
}

/* Карточка очков партии по референсу: плоская заливка, без прогресс-бара
   (заменяет прежнее решение U-12 — референс имеет приоритет, см.
   docs/PROJECT_CONTEXT.md). Точность окрашена по сравнению сторон
   (is-lead/is-behind ставит JS), Перформанс — по идентичности стороны. */
.score-card {
  display: flex;
  align-items: center;
  gap: 9px;
  min-height: 53px;
  padding: 9px 10px;
  border: 0;
  border-radius: var(--radius);
  background: var(--score-card-bg);
}

.score-card-icon {
  display: grid;
  flex: 0 0 auto;
  width: 17px;
  height: 17px;
  place-items: center;
  color: var(--muted);
}

.score-card-icon svg {
  width: 100%;
  height: 100%;
}

.score-card-body {
  display: grid;
  gap: 2px;
  text-align: left;
}

.score-card-body span {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

.score-card-body strong {
  /* 17px не принадлежало шкале; ближайшая ступень — 18px, и она
     сохраняет иерархию под именем игрока (20px). */
  font-size: var(--fs-h4);
  line-height: 1;
  color: var(--ink);
}

/* Явно белая заливка у карточек белой стороны (запрос пользователя
   05.08.2026) — чтобы визуально отличаться от более тёмных карточек
   чёрной стороны сильнее, чем нейтральный --score-card-bg. Своя рамка на
   каждой карточке (запрос 07.08.2026, второй заход) — та же логика, что
   у .player-side выше: белая — мягкое свечение, чёрная — сплошное
   непрозрачное чёрное кольцо, чтобы не читалась как серая. */
.score-card.is-white {
  background: var(--surface);
  box-shadow:
    0 0 0 1px var(--line-strong),
    0 0 10px 2px color-mix(in srgb, white 55%, transparent);
}

/* Средний тёмно-графитовый фон (26.08.2026, было светлое #e4e7ec от
   18.08.2026) — см. пояснение у .player-side.is-black выше. «Точность»
   (span+strong.is-lead/is-behind) по-прежнему сознательно зелёная/
   красная (просьба пользователя от 18.08.2026 не менялась), но раз фон
   снова тёмный — обычные light/dark-theme варианты этих цветов (тёмно-
   зелёный/тёмно-красный на светлой теме сайта) на нём почти не видны;
   ниже для is-black отдельно зафиксированы более светлые оттенки той же
   идеи, не зависящие от темы сайта (та же логика фиксации, что и у
   фона/текста). */
.score-card.is-black {
  /* #4b5563 — средний тёмно-графитовый, см. .player-side.is-black. */
  background: #4b5563;
  box-shadow:
    0 0 0 1px #9aa3b0,
    0 0 10px 2px color-mix(in srgb, #6b7684 40%, transparent);
}

.score-card.is-performance.is-white .score-card-body strong {
  color: var(--accent-text);
}

/* Подпись («Точность»/«Перформанс») — светлая на обеих карточках чёрной
   стороны (было узко scoped только на is-performance, до 26.08.2026 у
   is-accuracy подпись бралась из общего var(--muted), тёмного на светлой
   теме сайта — на новом тёмном фоне карточки это было бы нечитаемо). */
.score-card.is-black .score-card-body span {
  color: #eef0f3;
}

.score-card.is-performance.is-black .score-card-body strong {
  /* Светлый текст (было тёмное #2b3440) — см. .player-card.is-black. */
  color: #eef0f3;
}

/* Точность — зелёный/красный, но литеральные, светлые для тёмного фона
   (не var(--green-text)/var(--mistake-text): их светло-темный вариант
   заточен под светлую тему сайта и даёт контраст ~1.2–1.5 против нового
   #4b5563 — измерено, не видно вообще). Специфичнее общего правила ниже
   (.score-card.is-accuracy .score-card-body strong.is-lead/is-behind) за
   счёт дополнительного класса .is-black — переопределяет его именно
   здесь, само общее правило и белую сторону не трогает. */
.score-card.is-accuracy.is-black .score-card-body strong.is-lead {
  color: #4ade80;
}

.score-card.is-accuracy.is-black .score-card-body strong.is-behind {
  color: #fecaca;
}

.score-card.is-accuracy .score-card-body strong.is-lead {
  color: var(--green-text);
}

.score-card.is-accuracy .score-card-body strong.is-behind {
  color: var(--mistake-text);
}

.analysis-facts-details {
  margin: 0 0 10px;
}

.analysis-facts-details summary {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

.analysis-facts-details summary::-webkit-details-marker {
  display: none;
}

.analysis-facts-details summary::after {
  content: "▾";
  font-size: var(--icon-sm);
  transition: transform 160ms ease;
}

.analysis-facts-details[open] summary::after {
  transform: rotate(180deg);
}

.analysis-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0 2px;
}

.analysis-facts span {
  min-height: 24px;
  padding: 5px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface-soft);
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 400;
}

.board-zone,
.chart-zone {
  min-width: 0;
}

/* Референс не показывает заголовок «Позиция»/«Финальная доска» и бейдж
   хода над доской — визуально скрываем (не display:none), их текст
   по-прежнему используется announceBoard() для озвучки хода. */
.board-zone .board-toolbar,
.board-zone .board-badge {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Пункт №6, вариант Б. Раньше правая колонка забирала свой максимум (320px),
   а доске доставался остаток — на 1024 это зажимало её до 417px при бюджете
   по высоте 553px. Теперь наоборот: колонка доски получает ровно свою
   потребность (сторона доски + 78px на eval-бар, зазор и поля рамки),
   а инспектор занимает всё оставшееся место. */
.board-with-side {
  display: grid;
  /* Правая колонка — clamp(), не minmax(): у minmax() без fr нет
     собственного «предпочтительного» размера — сетка сначала выдаёт ей
     потолок, и только остаток достаётся гибкому левому треку. clamp()
     сам сужается вместе с вьюпортом ДО расчёта сетки, поэтому левый (1fr)
     трек с доской получает остаток корректно на каждой ширине. Потолок
     поднят 240→470px 05.08.2026, затем 470→620px 16.08.2026 (запрос
     пользователя — панель "слишком зажата": на широком экране график
     "Качество ходов" внутри неё физически не помещал подписи оси X без
     наложения текста друг на друга), затем весь clamp +20%
     (320/30vw/620 → 384/36vw/744) 22.08.2026 — на графике «Качество
     ходов» иконки заметных ходов стали налезать друг на друга и на
     подписи cp, ширины не хватало. Ещё +10% тем же вечером (384/36vw/744
     → 422/40vw/818) — «немного растянуть в длину», после появления шкалы
     оценки слева. Доску это не сужает ниже её собственного предела —
     .chess-board-frame всё так же капается по vh, у левого (1fr) трека и
     так был запас. */
  grid-template-columns: minmax(0, 1fr) clamp(422px, 40vw, 818px);
  /* gap уменьшен 32→22px тем же сообщением ("приблизить чуть-чуть к
     доске") — панель графиков и доска ближе друг к другу по горизонтали. */
  gap: 22px;
  /* align-items:stretch тут не годится: grid растягивает .board-side до
     высоты СТРОКИ, но auto-высота строки сама считается по max-content
     детей — с min-height:0 в теории не должна учитывать переполнение
     .board-side-scroll, но на практике (проверено 16.08.2026) высота
     .board-zone всё равно "тянется" за контентом Секунданта, а не
     наоборот. Синхронизация высоты — через JS (syncBoardSideHeight() в
     app.js, ResizeObserver на .board-zone, пишет max-height на
     .board-side инлайн-стилем) — надёжнее, чем воевать с auto-track
     sizing по вложенным grid/flex. */
  align-items: start;
}

.board-zone {
  display: grid;
  scroll-margin-top: 24px;
  gap: 10px;
}

.board-controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
}

.board-controls-below {
  justify-content: center;
  gap: 12px;
  margin-top: 4px;
}

.board-controls-below .icon-button {
  width: 64px;
  height: 64px;
  font-size: var(--icon-xl);
}

.icon-button.small {
  width: 34px;
  height: 34px;
  font-size: var(--icon-md);
}

.board-badge {
  justify-self: start;
}

.board-live {
  display: flex;
  align-items: stretch;
  justify-content: center;
  gap: 10px;
}

.eval-bar-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  flex-shrink: 0;
  gap: 6px;
}

/* Строго чёрно-белая ДИНАМИЧЕСКАЯ заливка (запрос пользователя
   07.08.2026, второй заход — первая версия была статичным разделением
   50/50, что и вызвало правку): .eval-bar-fill теперь непрозрачно-белый
   и растёт снизу вверх на процент, который уже считает JS
   (winProbabilityPercent от оценки белых) — тот же процент, что раньше
   двигал только тонкую линию-маркер. Остаток бара (эта чёрная заливка)
   визуально «съедается» белой сверху вниз по мере роста преимущества
   белых, и наоборот. Цвета буквальные (#fff/#000), не токены темы —
   иначе в тёмной теме белая часть потерялась бы на фоне страницы. */
.eval-bar {
  position: relative;
  display: flex;
  flex-direction: column-reverse;
  width: 18px;
  flex: 1;
  min-height: 120px;
  overflow: hidden;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: #000000;
  box-shadow: var(--shadow-soft);
}

/* Сама заливка — непрозрачно-белая (была transparent, когда бар был
   просто статичным фоном с бегающей линией-маркером). Из-за
   column-reverse на родителе .eval-bar этот блок стоит снизу и растёт
   вверх на свою height% — то есть реально «заполняет» долю белых.
   Верхнюю линию (border-top) красим акцентным цветом, не --ink: --ink на
   белой заливке снизу виден, но у чёрного остатка сверху слился бы с
   ним. */
.eval-bar-fill {
  width: 100%;
  height: 50%;
  background: #ffffff;
  border-top: 3px solid var(--board-eval-accent);
  transition: height 420ms cubic-bezier(0.4, 0, 0.2, 1);
}

.eval-bar-score {
  min-width: 34px;
  color: var(--ink);
  font-size: var(--fs-caption);
  font-weight: 700;
  text-align: center;
}

.eval-bar-wrap.is-pending .eval-bar {
  animation: evalBarPulse 1.1s ease-in-out infinite;
}

@keyframes evalBarPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

.board-branch-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 12px;
  border: 1px solid color-mix(in srgb, var(--amber) 55%, var(--line));
  border-radius: var(--radius);
  background: var(--amber-soft);
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 400;
}

.board-branch-banner.is-hidden {
  display: none;
}

/* Кнопка — .icon-button.small, у которого ширина и высота 34px. Без
   более сильного правила (.icon-button в селекторе) подпись «Вернуться к
   партии» переносилась на две строки и вылезала за плашку (01.11.2026). */
.board-branch-banner button,
.board-branch-banner .icon-button {
  flex-shrink: 0;
  width: auto;
  height: auto;
  min-height: 32px;
  padding: 0 12px;
  white-space: nowrap;
}

.board-live-line {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 400;
}

.board-live-line.is-hidden {
  display: none;
}

/* Пункт №6: размер доски привязан к высоте вьюпорта, а не к ширине.
   Прежний clamp(420px, 40vw, 560px) держал жёсткий потолок 560px — на
   мониторе 1920 доска оставалась 560px при 1032px пустоты справа.
   Член 100% оставлен: на узких экранах он связывает раньше остальных
   и защищает мобильную раскладку от роста доски. */
.chess-board-frame {
  position: relative;
  /* Ещё раз уменьшено на 10% (было min(72vh,783px), до этого —
     min(80vh,870px) 20.08.2026) — запрос пользователя 22.08.2026, доска
     всё ещё казалась крупной. Quest Board использует свой отдельный
     .quest-board-frame (Н-5) — этот класс его не задевает. */
  width: min(100%, min(65vh, 705px));
  aspect-ratio: 1;
  /* Левый отступ уменьшен (был 30px) — запрос пользователя 05.08.2026,
     слишком много воздуха между термометром оценки и доской. */
  margin: 4px 4px 28px 12px;
  border-radius: 20px;
  /* Мягкая внешняя тень на контейнере (ТЗ 27.08.2026, 5.8) — доска
     "встраивается" в общий стиль, сами клетки/фигуры не тронуты. */
  box-shadow: var(--shadow);
}

.chess-board {
  position: relative;
  display: grid;
  container-type: inline-size;
  width: 100%;
  height: 100%;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);
  overflow: hidden;
  border: 0;
  /* Радиус доски держим отдельно от общего --radius (сейчас 20px) — на
     угловых клетках такое скругление слишком сильно "срезало" бы фигуру.
     Объём даёт внешняя тень на .chess-board-frame, сама доска — без
     изменений (ТЗ 27.08.2026, 5.8). */
  border-radius: 10px;
  background: var(--board-light);
}

.square {
  position: relative;
  display: grid;
  min-width: 0;
  min-height: 0;
  place-items: center;
  color: var(--ink);
  font-family: "Segoe UI Symbol", "Noto Sans Symbols 2", "DejaVu Sans", Georgia, serif;
  font-size: clamp(2.2rem, 8.8cqw, 4.9rem);
  line-height: 1;
  /* Клетка — <button>: снимаем обвязку браузера, чтобы вид и размер фигуры
     остались ровно такими же, как были у <div>. */
  padding: 0;
  border: 0;
  border-radius: 0;
  appearance: none;
  -webkit-appearance: none;
  text-align: center;
}

/* Глобальное button:hover { transform: translateY(-1px) } иначе поднимало бы клетки. */
.square:hover {
  transform: none;
}

.square:focus-visible {
  z-index: 4;
  outline: 3px solid #ffffff;
  outline-offset: -3px;
  box-shadow: inset 0 0 0 6px var(--board-accent);
}

.square::after {
  position: absolute;
  inset: 7%;
  border-radius: 6px;
  content: "";
  opacity: 0;
  pointer-events: none;
  transition: opacity 160ms ease, transform 160ms ease;
}

.square.is-move-from::after {
  border: 2px solid color-mix(in srgb, var(--amber) 88%, #fff);
  opacity: 0.9;
}

.square.is-move-to::after {
  background: color-mix(in srgb, var(--board-accent) 30%, transparent);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--board-accent) 84%, #fff);
  opacity: 1;
  transform: scale(0.94);
}

/* Клетки хода цветом его оценки (запрос пользователя 29.10.2026, по
   образцу присланных снимков). Цвет у каждой категории — основной цвет её
   же иконки (снят с PNG в img/move-quality/), чтобы значок и клетка были
   одной краски. Заливка лежит под фигурой (у фигуры z-index: 1), рамка —
   по самому краю клетки, без скругления и отступа. Клетка, откуда ушла
   фигура, — тот же цвет бледнее и без рамки. Без оценки (ход в ветке)
   остаётся прежняя синяя подсветка выше. */
.square[data-move-quality="brilliant"] { --mq: #45cbef; }
.square[data-move-quality="great"],
.square[data-move-quality="mate"] { --mq: #2f7bf4; }
.square[data-move-quality="best"] { --mq: #5ab652; }
.square[data-move-quality="good"] { --mq: #90cc4c; }
.square[data-move-quality="ok"] { --mq: #bdb23e; }
.square[data-move-quality="book"] { --mq: #3581f4; }
.square[data-move-quality="missed"] { --mq: #f0a046; }
.square[data-move-quality="inaccuracy"] { --mq: #fa9f2d; }
.square[data-move-quality="mistake"] { --mq: #f23b40; }
.square[data-move-quality="blunder"] { --mq: #ea3b3e; }

.square.is-move-to[data-move-quality]::after,
.square.is-move-from[data-move-quality]::after {
  inset: 0;
  border: 0;
  border-radius: 0;
  opacity: 1;
  transform: none;
}

.square.is-move-to[data-move-quality]::after {
  background: color-mix(in srgb, var(--mq) 50%, transparent);
  /* Толщина рамки — ~4% клетки (клетка = 12.5cqw доски). */
  box-shadow: inset 0 0 0 clamp(2px, 0.5cqw, 5px) var(--mq);
}

.square.is-move-from[data-move-quality]::after {
  background: color-mix(in srgb, var(--mq) 30%, transparent);
  box-shadow: none;
}

.square.is-best-from::after {
  border: 1.5px dashed color-mix(in srgb, var(--board-accent) 68%, #fff);
  background: color-mix(in srgb, var(--board-accent) 8%, transparent);
  opacity: 0.72;
}

.square.is-best-to::after {
  border: 1.5px solid color-mix(in srgb, var(--green) 62%, var(--board-accent));
  background: radial-gradient(circle, color-mix(in srgb, var(--green) 15%, transparent), transparent 68%);
  opacity: 0.82;
  transform: scale(0.84);
}

/* Значок качества хода на клетке, куда пришла фигура. Была маленькая
   цветная точка в углу — запрос пользователя 22.08.2026: иконка вместо
   точки, те же файлы, что в «Статистике ходов» (styles.css
   .quality-table-row.is-*) и на графике «Качество ходов» (app.js
   moveQualityIcon) — держим все три места синхронно. С 28.10.2026 имя
   файла равно ключу качества, отдельного мэппинга нет. Исключение одно:
   «Матовая атака» своей иконки в наборе не имеет и берёт great — ровно
   так же, как в таблице она складывается в строку «Отличные»
   (QUALITY_GREAT_ALIAS_KEYS в app.js).
   Селектор ".square .square-quality-badge" — не просто ".square-quality-badge":
   общее правило ".square span" (для фигур, ниже по файлу не помогло бы —
   оно специфичнее одного класса) иначе перебивает width/height/position. */
.square .square-quality-badge {
  position: absolute;
  top: 4%;
  right: 4%;
  z-index: 5;
  display: block;
  width: 20%;
  height: 20%;
  min-width: 14px;
  min-height: 14px;
  max-width: 22px;
  max-height: 22px;
  border-radius: 999px;
  background: var(--surface);
  background-image: url("/static/img/move-quality/blunder.png");
  background-size: 82%;
  background-repeat: no-repeat;
  background-position: center;
  box-shadow: 0 0 0 2px var(--surface);
  pointer-events: none;
}

.square .square-quality-badge.severity-mistake {
  background-image: url("/static/img/move-quality/mistake.png");
}

.square .square-quality-badge.severity-inaccuracy {
  background-image: url("/static/img/move-quality/inaccuracy.png");
}

.square .square-quality-badge.severity-good {
  background-image: url("/static/img/move-quality/good.png");
}

.square .square-quality-badge.severity-missed {
  background-image: url("/static/img/move-quality/missed.png");
}

.square .square-quality-badge.severity-ok {
  background-image: url("/static/img/move-quality/ok.png");
}

.square .square-quality-badge.severity-book {
  background-image: url("/static/img/move-quality/book.png");
}

.square .square-quality-badge.severity-best {
  background-image: url("/static/img/move-quality/best.png");
}

.square .square-quality-badge.severity-mate,
.square .square-quality-badge.severity-great {
  background-image: url("/static/img/move-quality/great.png");
}

.square .square-quality-badge.severity-brilliant {
  background-image: url("/static/img/move-quality/brilliant.png");
}

/* Оптическое выравнивание значков (запрос пользователя 29.10.2026: «Лучший»
   рядом с «Ошибкой» выглядел мельче). Рамки у всех PNG одинаковые, но
   рисунки разной плотности: крест закрашивает 57% своего квадрата, прицел
   «Лучшего» — 35% (тонкое кольцо, вес в маленьком кружке по центру),
   «Упущенная» — 29%. Широкие («Неплохой», «Бриллиантовый») при размере по
   ширине выходили ниже остальных. Размер подобран так, чтобы цветная
   площадь значка была близка к кресту; сами файлы не тронуты. Предел —
   круг значка: кольцевые можно почти до края, у широких углы упираются в
   окружность раньше. «Теоретический» не трогаем — он и так самый плотный. */
.square .square-quality-badge.severity-best {
  background-size: 96%;
}

.square .square-quality-badge.severity-missed {
  background-size: 98%;
}

.square .square-quality-badge.severity-brilliant {
  background-size: 88%;
}

.square .square-quality-badge.severity-ok {
  background-size: 86%;
}

.best-move-arrow {
  position: absolute;
  z-index: 3;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
  mix-blend-mode: multiply;
}

.best-move-trace-shadow,
.best-move-trace {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.best-move-trace-shadow {
  stroke: rgba(255, 255, 255, 0.86);
  stroke-width: 4.2;
  opacity: 0.42;
}

.best-move-trace {
  stroke: color-mix(in srgb, var(--board-accent) 88%, #fff);
  stroke-width: 2.4;
  opacity: 0.9;
  filter: drop-shadow(0 2px 5px rgba(17, 86, 224, 0.22));
}

.best-move-head {
  fill: color-mix(in srgb, var(--board-accent) 88%, #fff);
  opacity: 0.9;
}

.best-move-arrow.is-match {
  opacity: 0.78;
}

.best-move-arrow.is-match .best-move-trace {
  stroke: color-mix(in srgb, var(--green) 76%, var(--board-accent));
}

.best-move-arrow.is-match .best-move-head {
  fill: color-mix(in srgb, var(--green) 76%, var(--board-accent));
}

html[data-theme="dark"] .best-move-arrow {
  mix-blend-mode: screen;
}

html[data-theme="dark"] .best-move-trace-shadow {
  stroke: rgba(2, 8, 23, 0.72);
  opacity: 0.5;
}

/* Стрелка-подсказка на площадке задач (запрос пользователя 08.09.2026) —
   тот же геометрический приём, что .best-move-arrow на Анализе (SVG
   viewBox 0 0 100 100 поверх доски), но своя копия классов/цвета (teal,
   не blue — на площадке задач синий ничего не значит, тогда как на
   Анализе он уже занят "лучшим ходом движка"). Общих селекторов с
   Анализом нет (Н-5). */
.puzzle-hint-arrow {
  position: absolute;
  z-index: 3;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
  mix-blend-mode: multiply;
}

.puzzle-hint-trace-shadow,
.puzzle-hint-trace {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.puzzle-hint-trace-shadow {
  stroke: rgba(255, 255, 255, 0.86);
  stroke-width: 4.2;
  opacity: 0.42;
}

.puzzle-hint-trace {
  stroke: color-mix(in srgb, var(--teal) 88%, #fff);
  stroke-width: 2.4;
  opacity: 0.9;
  filter: drop-shadow(0 2px 5px rgba(12, 153, 160, 0.28));
}

.puzzle-hint-head {
  fill: color-mix(in srgb, var(--teal) 88%, #fff);
  opacity: 0.9;
}

html[data-theme="dark"] .puzzle-hint-arrow {
  mix-blend-mode: screen;
}

html[data-theme="dark"] .puzzle-hint-trace-shadow {
  stroke: rgba(2, 8, 23, 0.72);
  opacity: 0.5;
}

.square.light {
  background: var(--board-light);
}

.square.dark {
  background: var(--board-dark);
}

.board-coordinates {
  position: absolute;
  z-index: 2;
  inset: 0;
  pointer-events: none;
}

.board-coordinate {
  position: absolute;
  color: #000000;
  font-size: clamp(0.85rem, 2.1cqw, 1.05rem);
  font-weight: 700;
}

html[data-theme="dark"] .board-coordinate {
  color: var(--ink);
}

.board-coordinate.is-file {
  bottom: -24px;
  transform: translateX(-50%);
}

.board-coordinate.is-rank {
  left: -20px;
  transform: translateY(-50%);
}

.square.is-selected::before {
  position: absolute;
  inset: 0;
  z-index: 1;
  content: "";
  background: color-mix(in srgb, var(--board-accent) 26%, transparent);
  pointer-events: none;
}

.square.is-check::before {
  position: absolute;
  inset: 0;
  z-index: 1;
  content: "";
  background: radial-gradient(circle, color-mix(in srgb, var(--red) 68%, transparent) 0%, transparent 72%);
  pointer-events: none;
}

.square.is-legal-target .legal-move-dot {
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  width: 28%;
  height: 28%;
  border-radius: 50%;
  background: color-mix(in srgb, var(--board-accent) 55%, var(--ink));
  opacity: 0.55;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

.square.is-legal-capture::after {
  z-index: 2;
  border: 3px solid color-mix(in srgb, var(--board-accent) 60%, var(--ink));
  opacity: 0.68;
}

.square.is-drop-hover::before {
  position: absolute;
  inset: 4%;
  z-index: 1;
  content: "";
  border-radius: 6px;
  background: color-mix(in srgb, var(--green) 30%, transparent);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--green) 70%, #fff);
  pointer-events: none;
}

.square.is-illegal-flash::before {
  position: absolute;
  inset: 0;
  z-index: 1;
  content: "";
  background: color-mix(in srgb, var(--red) 38%, transparent);
  animation: illegalFlash 320ms ease-out;
  pointer-events: none;
}

@keyframes illegalFlash {
  0% { opacity: 1; }
  100% { opacity: 0; }
}

.piece-svg {
  filter: drop-shadow(0 3px 3px rgba(2, 8, 23, 0.28));
}

.chess-piece {
  cursor: grab;
  touch-action: none;
  user-select: none;
  -webkit-user-drag: none;
}

.chess-piece .piece-svg {
  pointer-events: none;
}

.chess-piece.is-drag-source {
  opacity: 0.28;
}

.piece-drag-ghost {
  position: fixed;
  z-index: 60;
  transform: translate(-50%, -50%) scale(1.08);
  filter: drop-shadow(0 12px 20px rgba(2, 8, 23, 0.4));
  pointer-events: none;
}

/* Справка по горячим клавишам (?) — IMPROVEMENT_PROPOSALS.md, High,
   «Клавиатурные шорткаты доски «Анализ»». Тот же визуальный паттерн, что у
   .promotion-overlay, но на весь экран (не привязана к доске). */
.shortcuts-overlay {
  position: fixed;
  z-index: 90;
  inset: 0;
  display: grid;
  place-items: center;
  background: color-mix(in srgb, var(--ink) 40%, transparent);
  padding: 16px;
}

.shortcuts-panel {
  display: grid;
  gap: 14px;
  width: min(360px, 100%);
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.shortcuts-panel h2 {
  margin: 0;
  font-size: var(--fs-h3);
}

.shortcuts-panel dl {
  display: grid;
  gap: 10px;
  margin: 0;
}

.shortcuts-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.shortcuts-row dt {
  flex-shrink: 0;
}

.shortcuts-row dd {
  margin: 0;
  color: var(--muted);
  text-align: right;
}

.shortcuts-row kbd {
  display: inline-block;
  min-width: 28px;
  padding: 3px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: var(--surface-soft);
  color: var(--ink);
  font-family: inherit;
  font-size: var(--fs-caption);
  font-weight: 700;
  text-align: center;
}

.shortcuts-panel .ghost-button {
  justify-self: end;
}

.promotion-overlay {
  position: absolute;
  z-index: 30;
  inset: 0;
  display: grid;
  place-items: center;
  background: color-mix(in srgb, var(--ink) 40%, transparent);
  backdrop-filter: blur(3px);
}

.promotion-panel {
  display: flex;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.promotion-option {
  display: grid;
  width: clamp(42px, 9cqw, 62px);
  height: clamp(42px, 9cqw, 62px);
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface-soft);
  cursor: pointer;
  transition: transform 120ms ease, border-color 120ms ease;
}

.promotion-option:hover {
  border-color: var(--blue);
  transform: translateY(-2px);
}

.promotion-option .chess-piece {
  display: block;
  width: 78%;
  height: 78%;
  cursor: pointer;
}

.quest-square .is-moving-piece,
.square .is-moving-piece,
.play-square .is-moving-piece {
  animation: piece-slide 260ms cubic-bezier(0.2, 0.82, 0.22, 1) both;
}

/* Правая колонка «Обзора» по утверждённому макету: карточка инспектора с
   навигацией по ходам, под ней лента ходов в две колонки. */
/* Плотнее по прямой правке (итерация 3): правая колонка должна читаться
   как единый стек, не как отдельные блоки. */
.board-side {
  /* flex-колонка: вкладки — фиксированный первый элемент,
     .board-side-scroll — второй, занимает остаток. Фактическая высота
     колонки (запрос пользователя 16.08.2026) задаётся не CSS, а JS —
     syncBoardSideHeight() в app.js пишет max-height инлайн-стилем равным
     высоте .board-zone. min-height:0 всё равно нужен: без него flex-max
     -height обрезал бы контейнер, но внутренний .board-side-scroll не
     сжался бы меньше своего контента и просто вылез бы за max-height. */
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
}

.board-side-scroll {
  display: grid;
  align-content: start;
  gap: 6px;
  min-height: 0;
  overflow-y: auto;
  /* Небольшой правый отступ, чтобы полоса прокрутки не наезжала на
     контент карточек вплотную. */
  padding-right: 4px;
}

/* Общая оболочка карточки «Обзора» по референсу: белый фон, тонкая рамка,
   скруглённый угол, заголовок сверху. Общий класс не привязан к тому, в
   какой колонке карточка стоит — используется и в правой рейке, и в
   полноширинных блоках снизу. */
.overview-card {
  /* Рамка убрана (запрос пользователя 27.08.2026) — кроме «Ленты ходов»,
     см. .board-moves-wrap ниже, которая явно восстанавливает исходный вид. */
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
  /* 16px у всех карточек «Обзора» (31.10.2026): было 12×14, а у «Ленты
     ходов» 16 и рамка — заголовки стояли на разных отступах. Рамку
     «Ленты ходов» (решение 27.08.2026) пользователь попросила убрать:
     теперь она такая же карточка, как остальные, а границы ходов видны
     в самой таблице. */
  padding: 16px;
}

.side-card-title {
  margin: 0 0 12px;
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.2;
}

canvas {
  display: block;
  width: 100%;
  max-width: 100%;
  min-height: 224px;
  border: 0;
  border-radius: var(--radius);
  /* Карточка-обёртка графика (ТЗ 27.08.2026, 5.7) — рельеф только здесь,
     сама отрисовка (линии/точки/дуги в JS) не тронута. */
  background: var(--bg);
  box-shadow: var(--shadow);
}

.chart-zone,
.segments-panel {
  position: relative;
}

#timelineChart,
#segmentsChart {
  cursor: crosshair;
}

.chart-tooltip {
  position: fixed;
  z-index: 90;
  max-width: min(340px, calc(100vw - 28px));
  padding: 10px 12px;
  border: 1px solid color-mix(in srgb, var(--blue) 28%, var(--line));
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--surface) 96%, transparent);
  box-shadow: var(--shadow-soft);
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 400;
  line-height: 1.4;
  pointer-events: none;
  transform: translate(12px, 12px);
  backdrop-filter: blur(12px);
}

.chart-tooltip strong {
  display: block;
  margin-bottom: 4px;
  color: var(--blue-dark);
  font-size: var(--fs-body-sm);
}

.chart-tooltip span {
  display: block;
  color: var(--muted);
}

.coach-panel {
  padding: 15px;
  border-top: 5px solid var(--violet);
}

.coach-panel p:last-child {
  margin-bottom: 0;
  color: var(--muted);
  line-height: 1.5;
}

/* Переключатель персонажа Секунданта — тот же визуальный рецепт, что у
   .language-switch/.language-button (переключатель RU/EN), только auto-
   ширина вместо фиксированных 38px: подписи персонажей длиннее кода языка. */
.persona-switch {
  display: inline-flex;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface-soft);
  margin-bottom: 16px;
}

.persona-button {
  min-height: 34px;
  padding: 0 14px;
  border: 0;
  border-right: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  cursor: pointer;
}

.persona-button:last-child {
  border-right: 0;
}

.persona-button.is-active {
  background: var(--ink);
  color: var(--surface);
}

/* «Цифровой секундант» — вкладка «Секундант» по референсу вкладка
   секундант.png: 5 карточек (3+2), трекинг-капс заголовки, цветной
   левый бордер по смыслу карточки (зелёный/красный/бирюзовый,
   «Почему»/«Насколько типично» — нейтральные). */
.coach-summary {
  display: flex;
  align-items: flex-start;
  gap: 24px;
}

/* Персонаж-секундант слева (запрос пользователя 06.08.2026, не из
   референса — референс этот элемент не содержит). Плоская SVG-иллюстрация
   в токенах сайта, а не фотореалистичный маскот, как у Chess.com — своего
   инструмента генерации изображений в этой сессии нет. */
.coach-avatar {
  flex: 0 0 auto;
  width: 96px;
}

.coach-avatar svg {
  display: block;
  width: 100%;
  height: auto;
}

.coach-summary-body {
  min-width: 0;
  flex: 1 1 auto;
}

.coach-summary-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 14px;
  color: var(--ink);
  font-size: var(--fs-caption);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Живой комментарий Секунданта к текущему ходу (19.10.2026). Главный
   элемент вкладки: крутишь ходы — он говорит о значимых и молчит на
   проходных. Пустое состояние прячется через hidden в
   renderCoachMoveNote(), чтобы не оставался отступ от пустого блока. */
.coach-move-note {
  /* Окно стоит ВСЕГДА (запрос пользователя 19.10.2026) — меняется только
     содержимое. Раньше на молчаливом ходе блок убирался целиком, и панель
     подпрыгивала на каждое нажатие стрелки. min-height держит высоту трёх
     строк: столько занимает типичная реплика, так что текст появляется и
     исчезает, не двигая всё, что ниже. */
  display: grid;
  align-items: center;
  min-height: 92px;
  margin: 0 0 16px;
  padding: 14px 16px;
  /* Своя заливка с оттенком, а не оттенок серого. В светлой теме
     --surface-soft и --bg — один и тот же #f8fafd: на нём окно было
     невидимо совсем (контраст 1.00), а чистый белый дал всего 1.05 —
     тоже почти неразличимо. Глаз ловит смену тона лучше, чем смену
     светлоты, поэтому окно подкрашено синим из палитры продукта.
     Замечено пользователем 19.10.2026. */
  border: 1px solid var(--line-strong);
  border-left: 4px solid var(--blue);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--blue) 9%, var(--surface));
  box-shadow: var(--shadow-soft);
  transition: border-color 200ms ease, background-color 200ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .coach-move-note {
    transition: none;
  }
}

.coach-move-note p {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: 1.5;
}

/* Мини-иконки фигур рядом с человеко-читаемым описанием хода
   ("слон бьёт пешку") — переиспользуют существующий набор Cburnett,
   те же файлы, что и доска. */
.coach-piece-icon {
  width: 18px;
  height: 18px;
  vertical-align: -3px;
  margin-right: 3px;
}

/* "Вау"-блок стиля наверху вкладки — убран 14.09.2026 (запрос
   пользователя: "похож на N%" на одной партии — шумное, не заслуживающее
   доверия совпадение; устойчивая версия без процента теперь только в
   Профиле, после накопления нескольких анализов). */

/* Секундант — платная функция. #secondantUnlocked/#secondantLocked/
   #secondantLoading переключаются в app.js (applySecondantAccessUI) по
   /api/subscription; сам render() существующих карточек выше не менялся —
   эти три класса только про то, что из панели видно. */
.secondant-loading {
  margin: 0;
  padding: 48px 16px;
  text-align: center;
  color: var(--muted);
  font-size: var(--fs-body);
}

.tab-lock-icon {
  margin-left: 2px;
  font-size: 0.85em;
}

.secondant-locked {
  max-width: 560px;
  margin: 0 auto;
  padding: 40px 24px 48px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}

/* Явный display:flex выше по специфичности равен встроенному правилу UA
   [hidden]{display:none} — при равенстве побеждает то, что позже в
   каскаде, а это мы. Без этой строки applySecondantAccessUI() выставляет
   атрибут hidden, а блок всё равно виден. */
.secondant-locked[hidden] {
  display: none;
}

.secondant-locked h2 {
  font-size: var(--fs-h2);
  color: var(--ink);
  margin: 0 0 8px;
}

.secondant-locked-subtitle {
  color: var(--muted);
  font-size: var(--fs-body);
  margin: 0 0 20px;
  max-width: 42ch;
}

/* Единый список вместо двух таблиц бок о бок (запрос пользователя
   04.08.2026 — split-таблица с "Ошибки"/"Зевки" без своей шапки читалась
   как сломанная в сравнении с одноколоночным списком chess.com). */
.quality-table {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 40px 40px;
  gap: 6px 8px;
  align-items: center;
  margin-top: 2px;
  align-content: start;
}

.quality-table-row {
  display: contents;
}

/* Референс не подчёркивает строки — только вертикальный ритм за счёт
   .quality-table gap (запрос пользователя 05.08.2026, было по линии
   под каждой строкой). */
.quality-table-row > span,
.quality-table-row > strong {
  padding: 5px 4px;
}

.quality-table-row > span {
  display: flex;
  align-items: center;
  gap: 7px;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 400;
}

/* Иконка вместо цветного кружка — только у строк с категориями качества;
   в шапке («Белые»/«Чёрные») она не нужна (запрос пользователя 06.08.2026).

   28.10.2026 пользователь прислал набор на ВСЕ десять категорий
   (assets/source/move-quality-sheet.png, нарезка —
   scripts/cut_move_quality_icons.py). Поэтому имя файла теперь просто
   совпадает с ключом качества, и ушли две подпорки прежнего набора из
   шести: переназначение по чужим подписям (mistake→miss и т.п.) и
   самодельные фигурки на CSS для best/ok/book/missed, которым иконки не
   досталось. Цвет с иконки по-прежнему не снимается — она цветная в своём
   файле, никакой перекраски через CSS. */
.quality-table-row:not(.quality-table-head) > span::before {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  border-radius: 0;
  background: none;
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  content: "";
}

.quality-table-row.is-brilliant > span::before {
  background-image: url("/static/img/move-quality/brilliant.png");
}

.quality-table-row.is-great > span::before {
  background-image: url("/static/img/move-quality/great.png");
}

.quality-table-row.is-best > span::before {
  background-image: url("/static/img/move-quality/best.png");
}

.quality-table-row.is-good > span::before {
  background-image: url("/static/img/move-quality/good.png");
}

.quality-table-row.is-ok > span::before {
  background-image: url("/static/img/move-quality/ok.png");
}

.quality-table-row.is-book > span::before {
  background-image: url("/static/img/move-quality/book.png");
}

.quality-table-row.is-missed > span::before {
  background-image: url("/static/img/move-quality/missed.png");
}

.quality-table-row.is-inaccuracy > span::before {
  background-image: url("/static/img/move-quality/inaccuracy.png");
}

.quality-table-row.is-mistake > span::before {
  background-image: url("/static/img/move-quality/mistake.png");
}

.quality-table-row.is-blunder > span::before {
  background-image: url("/static/img/move-quality/blunder.png");
}

.quality-table-row > strong {
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  text-align: center;
}

.quality-table-head > span {
  padding: 0 4px 4px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
  text-align: center;
}

/* Карточки в ряд по референсу: 3 в ряд на широком экране, меньше — на
   узком. Порог 320px подобран по ширине карточки, при которой строка
   описания остаётся в читаемом диапазоне. */
.critical-list {
  display: grid;
  /* Один столбец, не auto-fit (запрос пользователя 16.08.2026) — теперь
     это accordion (развёрнут максимум один момент), два по горизонтали
     не сочетались бы с разной высотой развёрнутого/свёрнутых элементов.
     .board-side и так узкая колонка — 2 колонки всё равно почти не
     случались, но на верхней границе clamp (470px) технически влезали. */
  grid-template-columns: 1fr;
  gap: 6px;
  margin-top: 4px;
}

.critical-item.is-hidden {
  display: none;
}

/* «Показать ещё» — текстовая кнопка под строками, а не отдельная карточка
   во всю ширину (31.10.2026). Та же кнопка — «Загрузить ещё 5» под
   списком партий. */
.critical-more,
.recent-more {
  justify-self: start;
  width: auto;
  min-height: 32px;
  margin-top: 8px;
  padding: 0 10px;
  background: none;
  box-shadow: none;
  color: var(--an-accent);
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

.critical-more:hover,
.recent-more:hover:not(:disabled) {
  background: var(--surface-strong);
}

.recent-more[hidden] {
  display: none;
}

/* Фильтр партий по контролю времени (31.10.2026): дорожка и пункты — как
   у остальных переключателей (U-54); подписи короткие, ряд может
   переноситься на узкой колонке. */
.recent-filter {
  flex-wrap: wrap;
  align-self: flex-start;
  margin-bottom: 8px;
}

.recent-filter[hidden] {
  display: none;
}

.recent-filter .an-pill {
  min-height: 30px;
  padding: 0 12px;
}

.game-card[hidden] {
  display: none;
}

.critical-more.is-hidden {
  display: none;
}

/* Все переломные моменты — строки одного формата (запрос пользователя
   31.10.2026: «первая строка значительно выше остальных, переход
   выглядит резко»). До этого был аккордеон 16.08.2026: первая строка
   74px с мини-доской, остальные 33px без неё. Теперь у каждой мини-доска
   36px, ход и его оценка под ним, потеря справа; выбранная строка
   подсвечена так же, как активный пункт меню, раскладка не меняется. */
.critical-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 7px 12px 7px 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface);
  text-align: left;
}

.critical-item:hover {
  background: var(--surface-strong);
}

.critical-item.is-expanded {
  border-color: color-mix(in srgb, var(--an-accent) 35%, var(--line));
  background: var(--switch-active-bg);
}

.critical-item-chevron {
  display: none;
}

.critical-item-board {
  position: relative;
  display: grid;
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  grid-template-columns: repeat(8, 1fr);
  grid-template-rows: repeat(8, 1fr);
  overflow: hidden;
  border-radius: 5px;
}

.critical-item-square {
  position: relative;
  display: grid;
  place-items: center;
}

.critical-item-square.is-light {
  background: var(--board-light);
}

.critical-item-square.is-dark {
  background: var(--board-dark);
}

.critical-item-square .chess-piece {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
}

.critical-item-badge {
  position: absolute;
  right: -5px;
  bottom: -5px;
  display: inline-grid;
  min-width: 16px;
  min-height: 16px;
  place-items: center;
  border: 2px solid var(--surface);
  border-radius: 50%;
  background: var(--quality-blunder-soft);
  color: var(--quality-blunder-text);
  font-size: var(--fs-caption);
  font-weight: 700;
  /* Небольшой выпуклый рельеф уровня 1 (ТЗ 27.08.2026, 5.6/принцип 6) —
     смысловой цвет остаётся, плоская заливка получает объём своего же
     оттенка. Кольцо var(--surface) выше не трогаем — оно отделяет бейдж от
     клетки миниатюры, это не декоративная обводка из принципа 5. */
  box-shadow: 2px 2px 5px color-mix(in srgb, var(--quality-blunder) 45%, transparent), -1px -1px 4px var(--neu-highlight);
}

.critical-item.severity-mistake .critical-item-badge {
  background: var(--quality-mistake-soft);
  color: var(--quality-mistake-text);
  box-shadow: 2px 2px 5px color-mix(in srgb, var(--quality-mistake) 45%, transparent), -1px -1px 4px var(--neu-highlight);
}

.critical-item.severity-inaccuracy .critical-item-badge {
  background: var(--quality-inaccuracy-soft);
  color: var(--quality-inaccuracy-text);
  box-shadow: 2px 2px 5px color-mix(in srgb, var(--quality-inaccuracy) 45%, transparent), -1px -1px 4px var(--neu-highlight);
}

/* Ход и оценка — слева в две строки, потеря — справа по центру строки. */
/* Что значат числа на графике: на телефоне всплывающих подсказок нет
   (UX-исследование №21). */
.chart-note {
  margin: 0 0 8px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  line-height: 1.4;
}

.critical-item-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas: "move loss" "label loss";
  align-items: center;
  column-gap: 12px;
  row-gap: 1px;
  min-width: 0;
}

.critical-item-move {
  grid-area: move;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.critical-item-label {
  grid-area: label;
}

.critical-item-loss {
  grid-area: loss;
}

.critical-item-move {
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

.critical-item-label {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 400;
}

.critical-item-loss {
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

.tables-section,
.analysis-review-grid {
  margin-top: 16px;
}

.segments-panel {
  grid-area: auto;
  min-width: 0;
}

.roadmap-focus-card {
  border-color: color-mix(in srgb, var(--blue) 34%, var(--line));
  background: color-mix(in srgb, var(--blue) 7%, var(--surface));
}

.coach-roadmap-card {
  border-color: color-mix(in srgb, var(--green) 30%, var(--line));
  background: color-mix(in srgb, var(--green) 7%, var(--surface));
}

.coach-roadmap-card p:not(.eyebrow) {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 400;
  line-height: 1.52;
}

.coach-roadmap-hint {
  align-self: end;
  padding: 10px;
  border: 1px solid color-mix(in srgb, var(--green) 32%, var(--line));
  border-radius: var(--radius);
  background: var(--green-soft);
  color: color-mix(in srgb, var(--green-text) 55%, var(--ink));
  font-size: var(--fs-body-sm);
  font-weight: 400;
  line-height: 1.42;
}

/* Правила .coach-conclusion / .coach-conclusion-head / .coach-panel-profile
   и упоминания .analysis-conclusion в сгруппированных селекторах удалены
   09.10.2026 вместе с самой секцией «Решения цифрового секунданта» —
   стилей без разметки не держим. */

.roadmap-focus-card h3 {
  font-size: clamp(1.12rem, 1.45vw, 1.45rem);
  line-height: 1.12;
}

.roadmap-focus-card p {
  margin: 0;
  color: var(--muted);
  font-weight: 600;
  line-height: 1.48;
}

.roadmap-meta {
  align-self: end;
  justify-self: start;
  min-height: 32px;
  padding: 7px 9px;
  border: 1px solid color-mix(in srgb, var(--blue) 26%, var(--line));
  border-radius: var(--radius);
  background: var(--surface-soft);
  color: color-mix(in srgb, var(--blue-dark) 70%, var(--ink));
  font-size: var(--fs-caption);
  font-weight: 700;
}

.readiness-list {
  display: grid;
  gap: 9px;
}

.readiness-item,
.roadmap-moment {
  display: grid;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
}

.readiness-item > span,
.roadmap-moment > span {
  display: grid;
  min-width: 28px;
  height: 28px;
  place-items: center;
  border-radius: 7px;
  background: color-mix(in srgb, var(--blue) 14%, var(--surface));
  color: var(--blue-dark);
  font-weight: 700;
}

.readiness-item strong,
.roadmap-moment strong {
  min-width: 0;
  font-size: var(--fs-body-sm);
  line-height: 1.34;
}

.roadmap-empty {
  border-color: color-mix(in srgb, var(--green) 28%, var(--line));
  background: var(--green-soft);
}

.roadmap-moment {
  grid-template-columns: 44px minmax(0, 1fr);
  gap: 8px 10px;
  width: 100%;
  padding: 10px;
  color: var(--ink);
  text-align: left;
}

.roadmap-moment:hover {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 16%, transparent);
}

.roadmap-moment small {
  grid-column: 2;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 400;
  line-height: 1.3;
}

.readiness-item {
  grid-template-columns: 30px minmax(0, 1fr);
  gap: 9px;
  align-items: center;
  padding: 9px;
}

.readiness-item.done > span {
  background: var(--green-soft);
  color: var(--green-text);
}

.readiness-item.next > span {
  background: var(--amber-soft);
  color: var(--amber-text);
}

.report-actions {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 2px;
}

.report-actions .compact {
  min-height: 38px;
}

.bar {
  height: 12px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--surface-strong);
}

.bar-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--green);
}

/* .style-match убран 14.09.2026 — см. коммент у .coach-style-hero выше. */

.analysis-review-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 16px;
  align-items: stretch;
}

/* Было padding: 16px — графики выпадали из общей левой границы контента
   на 16 px. Правило выше (.chart-zone, .segments-panel { padding: 0 })
   перебивалось этим и было мёртвым. */
.chart-zone,
.segments-panel {
  min-width: 0;
  padding: 0;
}

/* Оба графика («Качество ходов» и «Время на ход») одного размера
   (запрос пользователя 08.08.2026, второй заход — размер «Качества
   ходов» ошибочно применили ко второму графику, надо было наоборот:
   у обоих теперь размер, который раньше был только у #segmentsChart).
   Высота была в vw (доле ширины ВЬЮПОРТА) — росла вместе с окном браузера,
   а сама панель графиков справа от доски капается отдельным clamp() по
   ширине (320–620px) независимо от вьюпорта. На широких экранах высота
   росла быстрее ширины панели — график выглядел неоправданно высоким и
   узким (запрос пользователя 22.08.2026). aspect-ratio вместо vw — высота
   теперь считается от РЕАЛЬНОЙ ширины самого графика, не от вьюпорта,
   пропорция остаётся одной и той же на любом экране. */
.chart-zone canvas,
#segmentsChart {
  width: 100%;
  /* 2.2/1 было слишком плоско для «Качества ходов» после возврата к
     кривой оценки (22.08.2026) — заголовок (2 строки, белые) + подвал
     (1 строка, чёрные) вместе съедали ~104px из типичных ~150-166px
     общей высоты, самой кривой оставалось всего ~46px — на такой узкой
     полосе не видно "плавных" колебаний вверх-вниз, которые просила
     пользователь (по референсу Lichess), кривая выглядела прижатой к
     верху. 1.7/1 — заметно выше при той же ширине панели, даёт кривой
     реальный запас по вертикали. */
  aspect-ratio: 1.7 / 1;
  height: auto;
  min-height: 220px;
  /* 420 -> 320 (29.10.2026): на широкой панели (877px) пропорция 1.7/1
     давала 516px и упиралась в потолок 420 — вдвоём с «Временем на ход»
     графики переставали помещаться в экран. Служебной обвязки внутри
     холста 104px, так что самой кривой остаётся 216px вместо 316 —
     запас, ради которого потолок поднимали 22.08.2026, сохраняется. */
  max-height: 320px;
}

/* «Время на ход» — ниже «Качества ходов» (29.10.2026, просьба уместить
   оба графика в экран). Пропорция 1.7/1 была общей у двух графиков с
   08.08.2026, когда оба печатали по две полосы точности/перформанса и
   им нужна была одинаковая высота под обвязку. Теперь эти полосы здесь
   не печатаются (см. drawSegments в app.js), обвязка внутри холста —
   68px вместо 104, и прежняя высота уходила в пустое поле. Самой сетке
   столбцов места не убавилось: 180 - 68 = 112 против прежних
   220 - 104 = 116. */
/* 08.11.2026: точность/перформанс отрезков снова печатаются (запрос
   пользователя) — +40px под две полосы, столбцам остаётся прежнее место:
   220 - 108 = 112. */
#segmentsChart {
  aspect-ratio: 2.6 / 1;
  min-height: 220px;
  max-height: 340px;
}

/* Первый блок вкладки «Графики» (29.10.2026): разделительная линия с
   отступом в 18px нужна МЕЖДУ блоками, а над самым первым она отделяет
   график от пустого места. Разделитель между «Качеством ходов» и
   «Временем на ход» остаётся — правило адресует только первый блок. */
#panelCharts > .analysis-review-grid:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

/* Раньше здесь рядом с «Временем на ход» стояла таблица «Статистика
   ходов» (.quality-panel) во второй колонке (minmax(240px, 300px)) — её
   с тех пор перенесли на вкладку «Обзор» (она живёт там отдельной
   карточкой, .quality-panel в этой секции больше нет), а вторую колонку
   сетки никто не убрал: она оставалась зарезервированной и пустой,
   из-за чего график сжимался в первую колонку и был у́же «Качества
   ходов» — надписи отрезков слипались (запрос пользователя 08.08.2026).
   Теперь колонка одна, на всю ширину, как у .analysis-review-grid. */
.tables-section {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 16px;
}

.quality-panel {
  min-width: 0;
}

.analysis-stage .tables-section {
  margin-top: 16px;
}

.move-table-wrap,
/* Профиль → блок подписки «Секундант» (ТЗ §19-23). Карточку рисует
   .pf-card (U-67), здесь только содержимое. */
.profile-secondant-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}

.profile-secondant-loading {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

/* «.info-section p» в начале файла красит все абзацы раздела серым и
   перебивал этот статус: «Активна» не была зелёной (найдено 02.11.2026).
   Отсюда селекторы с «.info-section p». */
.info-section p.profile-secondant-status {
  margin: 0;
  font-size: var(--fs-h4);
  font-weight: 700;
  color: var(--ink);
}

.info-section p.profile-secondant-status.is-active {
  color: var(--green-text);
}

.info-section p.profile-secondant-status.is-locked {
  color: var(--muted);
}

.profile-secondant-detail {
  margin: 0;
  font-size: var(--fs-body-sm);
  color: var(--muted);
}

.profile-secondant-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

.profile-secondant-manage,
.profile-secondant-confirm {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}

.profile-secondant-manage h3,
.profile-secondant-confirm h3 {
  margin: 0;
  font-size: var(--fs-body);
  color: var(--ink);
}

.profile-secondant-manage dl {
  margin: 0;
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 12px;
  font-size: var(--fs-body-sm);
}

.profile-secondant-manage dt {
  color: var(--muted);
}

.profile-secondant-manage dd {
  margin: 0;
  color: var(--ink);
  font-weight: 600;
}

.profile-secondant-history {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.profile-secondant-history-title {
  margin: 0;
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.profile-secondant-history-item {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: var(--fs-body-sm);
  color: var(--ink);
}

.profile-secondant-history-status {
  color: var(--muted);
}

.profile-secondant-history-status.is-paid {
  color: var(--green-text);
}

.profile-secondant-history-status.is-failed {
  color: var(--mistake-text);
}

.profile-history-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 4px;
}

.profile-history-loading,
.profile-history-locked,
.profile-history-empty {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.profile-history-summary {
  margin: 0;
  font-size: var(--fs-body-sm);
  color: var(--ink);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.profile-history-since {
  color: var(--muted);
  font-size: var(--fs-caption);
}

/* Ваши ники и память Секунданта (этап 2, 01.11.2026). Под каждым
   полем — рейтинги с площадки (U-67), поэтому поля выровнены по верху. */
.profile-nicks {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px 10px;
  align-items: start;
}

.profile-nicks[hidden] {
  display: none;
}

.profile-nick-field {
  display: grid;
  gap: 4px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

.profile-nick-field input {
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-body-sm);
}

.profile-nicks .primary-button {
  justify-self: start;
}

.profile-nicks-status {
  margin: 0;
  align-self: center;
  color: var(--green-text);
  font-size: var(--fs-caption);
}

.profile-nicks-status.is-error {
  color: var(--quality-blunder);
}

/* Карточка — flex-колонка, поэтому align-self, а не justify-self. */
.profile-memory-forget {
  align-self: flex-start;
  color: var(--quality-blunder);
}

.board-moves-wrap {
  align-self: stretch;
}

.section-note {
  max-width: 620px;
  margin: 6px 0 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 400;
  line-height: 1.4;
}

/* Лента ходов — таблица (31.10.2026, запрос пользователя: «сделай как
   таблицу, чтобы границы каждого хода были видны, а то сливаются»):
   номер | белые | чёрные, каждая ячейка отделена линией. Линии — это
   1px-зазоры сетки поверх фона --line, поэтому они одной толщины везде,
   без двойных краёв. Шапка «Белые / Чёрные» — первая строка той же сетки
   (прилипает при прокрутке): колонки и подписи совпадают до пикселя, а
   полоса прокрутки идёт справа от всей таблицы, не внутри колонки чёрных.
   Все строки одной высоты — раньше ход с фигурой (♘f3) был 31px против
   28px у хода пешкой, и подсветки в колонках расходились.
   Колонка чёрных подкрашена тем же графитом #4b5563 и той же силы, что и
   раньше (решение 26.08.2026: 18% в светлой теме, 55% в тёмной), только
   теперь по ячейкам, а не одной полосой на весь блок. */
.move-table {
  --move-black-cell: color-mix(in srgb, #4b5563 18%, var(--surface));
  display: grid;
  grid-template-columns: 3.4em minmax(0, 1fr) minmax(0, 1fr);
  grid-auto-rows: 32px;
  gap: 1px;
  margin-top: 4px;
  /* Шапка и три хода, как и раньше три строки (решение 26.10.2026 «размер
     ленты ходов сделай меньше»): 4×32 + 3 линии + рамка 2. */
  max-height: 133px;
  overflow-x: hidden;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--line);
  scroll-behavior: smooth;
  scrollbar-width: thin;
}

html[data-theme="dark"] .move-table {
  --move-black-cell: color-mix(in srgb, #4b5563 55%, var(--surface));
}

.move-table-th,
.move-table-num,
.move-table-empty {
  display: flex;
  align-items: center;
  min-width: 0;
  padding: 0 10px;
  background: var(--surface);
}

.move-table-th {
  position: sticky;
  top: 0;
  z-index: 1;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* Номера ходов — отдельная колонка с ровными цифрами: «9.» и «10.» больше
   не сдвигают ходы. */
.move-table-num,
.move-table-th.is-num {
  justify-content: flex-end;
  padding: 0 8px;
  background: var(--surface-soft);
}

.move-table-num {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-variant-numeric: tabular-nums;
}

.move-table-th.is-black,
.move-table-empty.is-black,
.move-token.is-black {
  background: var(--move-black-cell);
}

/* Кандидаты для позиции FEN — один ход на строку, на обе колонки. */
.move-token.is-wide {
  grid-column: 2 / -1;
}

.move-token {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: flex-start;
  gap: 6px;
  width: 100%;
  min-width: 0;
  padding: 0 10px;
  border: 0;
  border-radius: 0;
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  line-height: 1.2;
}

.move-token:hover {
  background: var(--surface-soft);
}

.move-token.is-active {
  background: color-mix(in srgb, var(--blue) 14%, var(--surface));
}

/* Раньше здесь было отдельное правило .move-token.is-active .move-token-num
   { color: var(--ink) } — на синеватой подложке активного хода --muted
   падал до 3.97:1, номер темнили отдельно. Теперь .move-token-num всегда
   наследует currentColor (см. ниже), а currentColor для реального хода —
   это уже подобранный «-text»-токен категории (mistake/blunder/green-
   text/blue-dark/inaccuracy-text), не блёклый --muted, так что отдельная
   починка контраста для активного состояния больше не нужна. */

/* Номер хода всегда наследует цвет родителя (currentColor) — у реального
   хода .move-token всегда есть категория качества (см. ниже), поэтому
   отдельного «нейтрального» состояния для него на практике не бывает. */
.move-token-num {
  color: currentColor;
  opacity: 0.8;
  font-size: var(--fs-caption);
  font-weight: 400;
}

/* Раньше (по референсу) «Лента ходов» красила фоном+текстом только
   ошибки/зевки (совпадало с «Ошибки»/«Зевки» из «Статистики ходов»), а
   остальные категории оставались нейтральными. Запрос пользователя
   07.08.2026 (второй заход) — красить ВЕСЬ ход целиком на всех
   категориях, не только фигуру и не только mistake/blunder: номер, фигура
   и клетка одним цветом, палитра та же — токены --quality-*-text.
   Фон-подложку (var(--quality-*-soft)) осознанно оставляем только у
   mistake/blunder — это отдельное более раннее решение, его не трогаем,
   плашка .is-active по-прежнему перекрывается им, если критичный ход ещё
   и текущий. */
.move-token.mistake {
  background: var(--quality-mistake-soft);
  color: var(--quality-mistake-text);
}

.move-token.blunder {
  background: var(--quality-blunder-soft);
  color: var(--quality-blunder-text);
}

.move-token.inaccuracy {
  color: var(--quality-inaccuracy-text);
}

/* Запрос пользователя 27.08.2026: цвет на ленте должен считаться так же,
   как строки «Статистики ходов» (renderQualityTable() в app.js), а не по
   старой 2-цветной схеме (зелёный на 4 категории, синий на 1) — иначе
   подсчёт ходов по цвету на ленте не совпадал с числом в таблице. Токены —
   та же палитра --quality-*-text, что уже используют «Статистика ходов»
   (иконки) и «Переломные моменты» (бейджи), отдельная покраска не
   изобретается заново. mate/best/great по-прежнему делят один и тот же
   цвет (--quality-excellent-text) даже после разделения строк "Отличные"/
   "Лучшие" 07.09.2026 (QUALITY_GREAT_ALIAS_KEYS в app.js) — разделение
   касалось только счётчика в таблице, не цвета ленты, отдельного запроса
   на это не было. */
.move-token.brilliant {
  color: var(--quality-brilliant-text);
}

.move-token.mate,
.move-token.best,
.move-token.great {
  color: var(--quality-excellent-text);
}

.move-token.good {
  color: var(--quality-good-text);
}

.move-token.ok {
  color: var(--quality-ok-text);
}

.move-token.book {
  color: var(--quality-book-text);
}

.move-token.missed {
  color: var(--quality-missed-text);
}

.move-token-icon {
  font-size: 1.1em;
}

.endgame-card {
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 13px;
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
}

.endgame-piece {
  display: grid;
  width: 46px;
  height: 46px;
  place-items: center;
  border-radius: var(--radius-control);
  background: var(--surface-strong);
  color: var(--piece-light);
  font-size: var(--icon-xl);
  -webkit-text-stroke: 1px var(--piece-light-stroke);
  text-shadow:
    0 1px 0 rgba(255, 255, 255, 0.8),
    0 0 2px rgba(7, 20, 46, 0.7);
  /* Маленькая выпуклая "капсула" под фигуру (принцип 6) — цвет
     (--surface-strong) не меняется, добавлен только рельеф. */
  box-shadow: 2px 2px 5px color-mix(in srgb, black 12%, transparent), -2px -2px 5px var(--neu-highlight);
}

.endgame-card strong {
  display: block;
  margin-bottom: 4px;
}

.endgame-card span {
  display: block;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

@media (max-width: 1320px) {
  .topbar {
    flex-wrap: wrap;
  }

  .brand {
    flex: 1 1 280px;
  }

  .top-actions {
    margin-left: auto;
  }

  .top-nav {
    order: 3;
    justify-content: center;
    width: 100%;
    min-width: 0;
    overflow-x: auto;
    padding-bottom: 2px;
  }

  .nav-tab {
    min-height: 40px;
    padding-inline: 14px;
  }

      .roadmap-focus-card {
    grid-column: 1 / -1;
  }

  .segments-panel {
    grid-column: auto;
  }
}

@media (max-width: 1180px) {
  .hero-figure {
    width: min(100%, 780px);
  }

  /* Тут ширину картинки держит не потолок, а колонка грида, и после того как
     8.7% кадра стали прозрачным полем, на 1024 сам снимок вышел бы МЕНЬШЕ
     прежнего (507 -> 479). Доля текста на этом диапазоне ужата ещё раз —
     снимок снова крупнее, чем был. */
  .welcome-screen {
    grid-template-columns: minmax(360px, 0.5fr) minmax(460px, 1fr);
  }

  /* Раньше здесь переопределялся только .chart-zone canvas (min(400px,
     54vw)) — #segmentsChart в этот брейкпоинт не входил и просто
     наследовал базовый clamp(270px, 24vw, 360px) выше. Правки размера
     08.08.2026 (второй заход) убрали этот override вовсе: у обоих
     графиков на этой ширине действует тот же базовый clamp. */
}

@media (max-width: 980px) {
  .board-with-side {
    grid-template-columns: 1fr;
  }

  /* Доска и .board-side складываются друг под другом на этой ширине —
     они больше не делят одну строку грида, поэтому stretch-высота и
     внутренний скролл (запрос пользователя 16.08.2026, десктопная
     задача) здесь не нужны и без этой правки схлопнули бы контент почти
     до нуля (min-height:0 без родительского stretch-контекста). На
     мобильном — обычный скролл всей страницы, как и было. */
  .board-side {
    min-height: auto;
  }

  .board-side-scroll {
    min-height: auto;
    overflow-y: visible;
    padding-right: 0;
  }

  .top-nav {
    justify-content: flex-start;
  }

  .info-section {
    grid-template-columns: 1fr;
  }

  .welcome-screen {
    grid-template-columns: 1fr;
  }

  .welcome-copy {
    min-height: auto;
    padding-inline: 0;
  }

  .hero-figure {
    width: min(100%, 660px);
  }

  .welcome-features {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .quest-detail-grid {
    grid-template-columns: 1fr;
  }

  .welcome-actions {
    align-items: flex-start;
    flex-direction: column;
  }

  .workspace,
  .analysis-review-grid,
  .tables-section {
    grid-template-columns: 1fr;
  }

  .coach-summary {
    flex-direction: column;
    align-items: center;
  }

  .coach-avatar {
    width: 72px;
  }

  .roadmap-focus-card {
    grid-column: auto;
  }

  .score-card-row {
    width: 100%;
  }

  .player-strip {
    grid-template-columns: 1fr;
  }

  .versus-block {
    min-width: 0;
  }

  .chess-board {
    width: min(100%, 620px);
  }

  .quest-board-frame {
    width: min(100%, 560px);
  }
}

@media (max-width: 620px) {
  /* На узком экране карточки «Точность» и «Перформанс» стояли по две в
     ряд, и на подпись оставалось 15px ширины: текст ломался по 1-2 буквы
     в строку («То/чн/ос/ть»). Раньше это маскировал шрифт 9.6px, но он
     ниже минимума читаемости (пункт №8 аудита). Ставим карточки в один
     столбец — подпись получает всю ширину карточки игрока. */
  .score-card-row .score-card {
    flex: 1 1 100%;
  }

  .app-shell {
    padding: 7px;
    /* На телефоне шапка sticky и --topbar-space равен нулю — остаётся 7px,
       как и было. Узкое окно браузера на десктопе (мышь, ширина < 620) —
       шапка зафиксирована, и место под неё нужно так же, как на широком. */
    padding-top: calc(var(--topbar-space) + 7px);
  }

  /* Столько же строк, сколько на десктопе (запрос пользователя 06.08.2026). */
  .move-table {
    max-height: 133px;
  }

  .topbar {
    position: sticky;
    top: 0;
    left: auto;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 6px;
    align-items: center;
    width: 100%;
    padding: 6px;
    transform: none;
    pointer-events: auto;
  }

  .topbar > * {
    min-width: 0;
  }

  .brand {
    width: auto;
    max-width: 100%;
    gap: 8px;
  }

  .top-actions {
    width: auto;
    max-width: 100%;
    justify-content: end;
    gap: 4px;
  }

  .brand > div:last-child {
    min-width: 0;
  }

  .top-nav {
    display: flex;
    grid-column: 1 / -1;
    gap: 6px;
    overflow-x: auto;
    scrollbar-width: thin;
    padding-bottom: 0;
  }

  .nav-tab {
    width: auto;
    min-height: 36px;
    flex: 0 0 auto;
    padding-inline: 8px;
    white-space: nowrap;
  }

  /* Фиксированная height:310px/min-height:280px здесь раньше перебивала
     базовый aspect-ratio (22.08.2026) — на мобильной ширине график либо
     не рос вместе с сужением экрана, либо становился непропорционально
     высоким. Убрано — базовое правило .chart-zone canvas теперь одно на
     все ширины, aspect-ratio сам держит пропорцию, min-height:200px там
     же не даёт графику стать нечитаемо низким. */

  .chart-zone .panel-heading,
  .segments-panel .section-heading {
    align-items: flex-start;
    flex-direction: column;
  }

  .chart-controls {
    justify-content: flex-start;
    width: 100%;
  }

  .icon-button {
    width: 38px;
    height: 38px;
  }

  .language-button {
    min-width: 38px;
    height: 38px;
  }

  h1 {
    font-size: var(--fs-h3);
  }

  .brand-wordmark h1 {
    font-size: var(--fs-body);
  }

  .brand-wordmark small {
    font-size: var(--fs-caption);
  }

  .brand-mark {
    width: 46px;
    height: 42px;
  }

  .welcome-copy {
    min-height: auto;
  }

  .analysis-stage,
  .move-table-wrap,
  .endgame-wrap,
  .quest-detail {
    padding: 10px;
  }

  .quest-board-panel,
  .board-zone {
    gap: 8px;
  }

  .welcome-features {
    grid-template-columns: 1fr;
  }

  .welcome-copy h2 {
    font-size: var(--fs-h1);
  }

  .square {
    font-size: clamp(2.2rem, 9.2cqw, 4.8rem);
  }

  .quest-square {
    font-size: clamp(2.2rem, 9.3cqw, 4.9rem);
  }

  /* Analysis Board и Quest Board — независимые подсистемы (Н-5), общих
     селекторов между ними быть не должно. Значение одно и то же, но правила
     раздельные: менять размер одной доски нельзя через другую. */
  .chess-board {
    width: min(100%, calc(100vw - 34px));
  }

  .quest-board-frame {
    width: min(100%, calc(100vw - 34px));
  }

  .actions,
  .segmented {
    grid-template-columns: 1fr;
  }

  /* .analysis-tabs несёт класс .segmented для переиспользования базовой
     раскладки, но это вкладки-подчёркивания, а не переключатель платформы:
     на мобильном они остаются в один ряд, а не складываются в столбец. */
  .analysis-tabs.segmented {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .segment {
    border-right: 0;
    border-bottom: 0;
  }

  /* .analysis-tabs .segment теперь всегда полная рамка (запрос
     пользователя 08.08.2026) — раньше здесь стирали border до
     подчёркивания на мобильном, это больше не нужно. */

  .segment:last-child {
    border-bottom: 0;
  }

  /* Пункт 2 (запрос пользователя 06.08.2026): выше в файле есть глобальное
     desktop-правило .topbar:hover/:focus-within { transform: translate(-50%,0) }
     — оно рассчитано на left:50% из десктопной .topbar и на мобильной
     ширине уезжает шапку на половину её собственной ширины влево при
     наведении (даже при position:sticky и left:auto). Явно гасим
     transform на этой ширине — шапка остаётся неподвижной. */
  .topbar,
  .topbar:hover,
  .topbar:focus-within {
    /* На этой ширине шапка в потоке (sticky) и центрируется сама —
       десктопный translateX(-50%) увёл бы её на половину ширины влево. */
    transform: none;
  }

  /* Пункт 1: имена игроков на одном уровне (было — .player-strip
     схлопнут в 1fr общим правилом для ≤980px, чёрная сторона уезжала
     под карточки очков белой). Возвращаем три колонки и уменьшаем
     аватар/шрифты, чтобы обе стороны уместились рядом на телефоне. */
  .player-strip {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    gap: 6px 8px;
    align-items: start;
  }

  .player-card {
    grid-template-columns: 34px minmax(0, 1fr);
    gap: 6px;
  }

  .player-card.is-black {
    grid-template-columns: minmax(0, 1fr) 34px;
    padding: 6px 8px;
  }

  .player-card img {
    width: 34px;
    height: 34px;
    border-radius: 8px;
  }

  .player-card strong {
    font-size: var(--fs-body);
  }

  .versus-block {
    min-width: 0;
    gap: 2px;
  }

  .versus-block strong {
    padding: 3px 10px;
    font-size: var(--fs-body);
  }

  .score-card {
    min-height: 44px;
    gap: 6px;
    padding: 6px 8px;
  }

  .score-card-icon {
    width: 14px;
    height: 14px;
  }

  .score-card-body {
    min-width: 0;
  }

  /* min-width:0 нужен и самим ячейкам грида (не только контейнеру) —
     иначе их собственный min-width:auto по содержимому не даёт им сжаться
     внутри узкой колонки телефона, и слово «Перформанс» вылезает наружу. */
  .score-card-body span,
  .score-card-body strong {
    min-width: 0;
  }

  .score-card-body span {
    overflow-wrap: break-word;
  }

  .score-card-body strong {
    font-size: var(--fs-body-sm);
  }
}

/* Компактная геометрия телефона (пункт №5). Ничего не скрывается и не
   переносится — уменьшены только зазоры и отступы.
   Зазор 8px возвращает пять кнопок доски в один ряд: 5×64 + 4×8 = 352 px
   при доступных 354.4; с зазором 12 требовалось 368 и пятая кнопка
   уезжала на вторую строку, стоившую 76 px.
   Граница 420, а не 400: естественной ширины кнопкам хватает лишь
   примерно с 404 px вьюпорта, и на 401–403 они переносились снова. */
@media (max-width: 420px) {
  .board-controls-below {
    gap: 8px;
  }

  .board-with-side {
    gap: 16px;
  }

  .tab-panel > .move-table-wrap {
    margin-top: 12px;
  }

  .move-table-wrap {
    padding: 6px 10px;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

@media print {
  @page {
    margin: 12mm;
  }

  html,
  body {
    background: #fff !important;
    color: #111827 !important;
  }

  .app-shell {
    max-width: none;
    padding: 0;
  }

  .topbar,
  .analysis-loader,
  .welcome-screen,
  .info-section,
  .quests-section,
  .game-panel,
  .board-controls,
  .report-actions {
    display: none !important;
  }

  .workspace,
  .analysis-review-grid,
  .tables-section {
    display: block !important;
  }

  .analysis-stage,
  .chart-zone,
  .segments-panel,
  .move-table-wrap,
  .endgame-wrap,
  .coach-panel {
    break-inside: avoid;
    box-shadow: none !important;
    border-color: #d1d5db !important;
    background: #fff !important;
  }

  .analysis-stage {
    margin: 0 0 12px;
    padding: 10px;
  }

  .chess-board {
    width: 120mm;
    max-width: 100%;
    box-shadow: none;
  }

  canvas {
    min-height: 70mm;
  }

  .analysis-review-grid,
  .tables-section {
    gap: 8px;
  }

  .move-table {
    max-height: none;
    overflow: visible;
  }
}

@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(5px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes piece-slide {
  from {
    transform:
      translate(var(--move-x, 0), var(--move-y, 0))
      scale(0.94);
  }

  70% {
    transform: translate(0, 0) scale(1.04);
  }

  to {
    transform: translate(0, 0) scale(1);
  }
}

/* Карточка партии с миниатюрой (25.10.2026): белые | доска | чёрные,
   под ними время, дата, дебют и точность. */
.game-card-players {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 84px minmax(0, 1fr);
  align-items: center;
  gap: 8px;
}

.game-card-player {
  display: grid;
  justify-items: center;
  gap: 3px;
  min-width: 0;
  text-align: center;
}

.game-card-player .game-avatar {
  width: 30px;
  height: 30px;
}

.game-card-name {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

.game-card-player.is-focus .game-card-name {
  color: var(--blue-dark, var(--ink));
  font-weight: 800;
}

.game-card-rating {
  color: var(--muted);
  font-size: var(--fs-caption);
}

/* Точность под рейтингом каждого игрока (26.10.2026). Раньше оба числа
   стояли одной строкой внизу карточки, и чьё из них чьё — приходилось
   угадывать по порядку. */
.game-card-accuracy {
  color: var(--accent-text);
  font-size: var(--fs-caption);
  font-weight: 700;
}

/* Chess.com отдаёт точность только для разобранных партий. У остальных
   ставим прочерк, а не прячем строку: иначе карточки будут разной высоты. */
.game-card-accuracy.is-unknown {
  color: var(--muted-faint);
  font-weight: 400;
}

.game-thumb-wrap {
  display: grid;
  gap: 5px;
  width: 84px;
}

/* Кружки под доской: белый слева (белые игрок слева), чёрный справа. */
.game-thumb-sides {
  display: flex;
  justify-content: space-between;
  padding: 0 6px;
}

.side-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid #6b7280;
}

.side-dot.is-white {
  background: #ffffff;
}

.side-dot.is-black {
  background: #111827;
}

html[data-theme="dark"] .side-dot.is-black {
  border-color: #cbd5e1;
}

.game-thumb {
  width: 84px;
  aspect-ratio: 1;
  border-radius: 4px;
  overflow: hidden;
  background: var(--surface-strong);
}

.game-thumb-board {
  display: block;
  width: 100%;
  height: 100%;
}

.game-thumb-light {
  fill: var(--board-light);
}

.game-thumb-dark {
  fill: var(--board-dark);
}

@media (max-width: 380px) {
  .game-card-players {
    grid-template-columns: minmax(0, 1fr) 68px minmax(0, 1fr);
  }

  .game-thumb,
  .game-thumb-wrap {
    width: 68px;
  }
}

/* Во время анализа карточки партий неактивны (25.10.2026). */
.recent-games-list.is-busy .game-card:not(.is-active) {
  opacity: 0.55;
  cursor: progress;
}

/* ===== Экран анализа по макету (25.10.2026) =====================
   Левое меню, «← Анализ партии», чёрные — тёмная плашка справа вверху,
   компактная строка «белые · точность/перформанс · счёт», карточка доски
   со шкалой справа, лента миниатюр, справа вкладки
   «Анализ / Движения / Статистика / Секундант». */

:root {
  --an-navy: #1e293b;
  --an-accent: var(--blue);
  --an-good: #15803d;
  --an-good-soft: #e9f7ee;
  --an-bad: #b91c1c;
  --an-bad-soft: #fdecec;
  --an-card-border: #e6eaf2;
  /* Горизонтальные переключатели в стиле левого меню (31.10.2026, запрос
     пользователя: «вертикальное меню — эталон»). Радиус — как у
     .app-rail-item; активная подложка — та же светло-голубая, что у
     активного пункта меню (синий 10% поверх фона меню). Фон дорожки —
     тот же, что у самого меню, --surface (белый / #1e293b): на сером
     фоне наведение (--surface-strong) сливалось с дорожкой, а голубой
     лежал на сером — замечено пользователем 31.10.2026. */
  --switch-radius: var(--radius-control);
  --switch-pad: 3px;
  --switch-track: var(--surface);
  --switch-active-bg: color-mix(in srgb, var(--an-accent) 10%, var(--surface));
}

html[data-theme="dark"] {
  --an-navy: #334155;
  --an-accent: var(--blue);
  --an-good: #4ade80;
  --an-good-soft: rgba(74, 222, 128, 0.12);
  --an-bad: #f87171;
  --an-bad-soft: rgba(248, 113, 113, 0.12);
  --an-card-border: #2b3a52;
}

/* --- Левое меню --- */
.app-rail {
  display: none;
}

/* С 29.10.2026 меню — единственная навигация на десктопе: оно на всех
   страницах, а верхняя шторка скрыта (запрос пользователя — навигация уже
   слева, дублировать её сверху незачем). Место под шторку больше не
   резервируется: --topbar-space = 0 ещё до первого замера из JS. */
@media (min-width: 981px) {
  :root {
    --topbar-space: 0px;
  }

  .topbar {
    display: none;
  }

  .app-rail {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    z-index: 30;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    width: 84px;
    padding: 16px 0;
    overflow-y: auto;
    border-right: 1px solid var(--an-card-border);
    background: var(--surface);
  }

  .app-shell {
    padding-left: 108px;
  }
}

/* Низ меню (08.11.2026): Контакты и Тема значками в строку, язык и
   «Войти» / «Профиль» — прижаты к низу, отдельно от разделов. */
.app-rail-bottom {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  margin-top: auto;
  padding-top: 10px;
  border-top: 1px solid var(--an-card-border);
}

.app-rail-tools {
  display: flex;
  gap: 4px;
}

.app-rail-tool {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.app-rail-tool svg {
  width: 20px;
  height: 20px;
}

.app-rail-tool:hover {
  color: var(--ink);
  background: var(--surface-strong);
}

.app-rail-tool.is-active {
  color: var(--an-accent);
  background: color-mix(in srgb, var(--an-accent) 10%, transparent);
}

.app-rail-tool:focus-visible,
.app-rail-logo:focus-visible,
.app-rail-menu-item:focus-visible {
  outline: 2px solid var(--an-accent);
  outline-offset: 2px;
}

.app-rail-lang {
  flex: none;
}

.app-rail-account {
  position: relative;
}

/* Подменю «Профиль» выезжает вправо от меню, низом по кнопке. */
.app-rail-menu {
  position: fixed;
  bottom: 16px;
  left: 92px;
  z-index: 60;
  display: grid;
  min-width: 220px;
  padding: 6px;
  border: 1px solid var(--an-card-border);
  border-radius: 12px;
  background: var(--surface);
  box-shadow: 0 12px 32px rgba(15, 23, 42, 0.16);
}

.app-rail-menu[hidden] {
  display: none;
}

.app-rail-menu-item {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.app-rail-menu-item:hover {
  background: var(--surface-strong);
}

.app-rail-menu-item.is-danger {
  color: var(--an-bad);
}

.app-rail-lang .language-button {
  min-width: 30px;
  min-height: 30px;
  margin: 2px;
  font-size: 12px;
}

.app-rail-logo {
  display: grid;
  flex: none;
  place-items: center;
  width: 46px;
  height: 46px;
  margin-bottom: 14px;
  border-radius: 12px;
  background: var(--an-navy);
  cursor: pointer;
}

.app-rail-logo img {
  width: 30px;
  height: 30px;
  object-fit: contain;
  filter: brightness(0) invert(1);
}

.app-rail-item {
  position: relative;
  display: grid;
  justify-items: center;
  gap: 4px;
  width: 76px;
  min-height: 56px;
  padding: 8px 2px;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  /* Пункты разделов — ссылки (02.11.2026), «Тема» — кнопка. */
  text-decoration: none;
  cursor: pointer;
}

.app-rail-item svg {
  width: 22px;
  height: 22px;
}

.app-rail-item:hover {
  color: var(--ink);
  background: var(--surface-strong);
}

.app-rail-item.is-active {
  color: var(--an-accent);
  background: color-mix(in srgb, var(--an-accent) 10%, transparent);
}

.app-rail-item.is-active::before {
  content: "";
  position: absolute;
  top: 10px;
  bottom: 10px;
  left: -4px;
  width: 3px;
  border-radius: 3px;
  background: var(--an-accent);
}

.app-rail-item:focus-visible {
  outline: 2px solid var(--an-accent);
  outline-offset: 2px;
}

/* --- Строка игроков над доской: обе стороны рядом ---
   Запрос пользователя 26.10.2026: «карточки игроков сейчас стоят вразброс
   в две строчки, сделай их рядом поверху доски». Редизайн 25.10 уносил
   чёрных отдельной тёмной плашкой в заголовок, а белые оставлял строкой
   ниже — стороны оказывались на разных уровнях и в разном оформлении.
   Теперь обе в одной строке, счёт по центру между ними:

      белые · точность+перформанс · СЧЁТ · точность+перформанс · чёрные

   Строка живёт в колонке доски, а не во всю ширину экрана, поэтому
   размеры ужаты против прежних (аватар 44->36, имя 17->15, число 20->17,
   отступы 12->9): пять ячеек в ~710px иначе не умещаются, а правая панель
   из-за переноса строки не должна опускаться ниже. Пользователь на
   уменьшение согласилась явно. */
.analysis-stage .player-strip {
  grid-template-columns:
    minmax(0, 1.1fr) minmax(0, 1.16fr) auto minmax(0, 1.16fr) minmax(0, 1.1fr);
  gap: 6px;
  margin: 0;
  padding: 0;
  border-bottom: 0;
  /* stretch, а не center: пять плиток выстраиваются в одну линию по
     верхнему и нижнему краю, иначе каждая встаёт по своей высоте и строка
     снова выглядит «вразброс» — ровно то, на что жаловался пользователь. */
  align-items: stretch;
}

/* Зеркало: у чёрных сначала очки, потом карточка, чтобы счёт оказался
   ровно посередине, а обе карточки — по краям строки. В разметке у
   чёрных карточка идёт первой (аватар там справа), поэтому порядок
   задаётся явно номерами колонок, а не перестановкой HTML. */
.analysis-stage .player-strip .score-card-row.is-black {
  grid-column: 4;
  grid-row: 1;
}

.analysis-stage .player-strip .player-card.is-black {
  grid-column: 5;
  grid-row: 1;
}

/* Обе стороны раскрываются в ячейки общей сетки — иначе карточка и плитки
   очков остались бы склеенными в одну колонку. */
.analysis-stage .player-strip .player-side.is-white,
.analysis-stage .player-strip .player-side.is-black {
  display: contents;
}

.analysis-stage .player-strip .player-card:not(.is-black),
.analysis-stage .player-strip .score-card-row:not(.is-black),
.analysis-stage .player-strip .versus-block {
  border: 1px solid var(--an-card-border);
  border-radius: 14px;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}

.analysis-stage .player-strip .player-card {
  grid-template-columns: 34px minmax(0, 1fr);
  gap: 8px;
  padding: 4px 10px;
}

/* Чёрные зеркальны: аватар справа, текст слева (так и в разметке). */
.analysis-stage .player-strip .player-card.is-black {
  grid-template-columns: minmax(0, 1fr) 34px;
}

.analysis-stage .player-strip .player-card img {
  width: 34px;
  height: 34px;
  border-radius: 9px;
}

.analysis-stage .player-strip .player-card strong {
  font-size: 13px;
}

/* Ник длиннее колонки обрезаем многоточием, а не в ноль: видно начало
   имени. «tobiasbourne» — 12 знаков, помещается; бывают и длиннее. */
.analysis-stage .player-strip .player-card strong,
.analysis-stage .player-strip .player-card small {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.analysis-stage .player-strip .score-card-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  padding: 5px 3px;
}

/* Значки у «Точности»/«Перформанса» убраны на всём десктопе: подпись
   рядом говорит то же самое, а пять плиток в колонке доски (~670px при
   1440) иначе не помещаются — имена обрезались (замерено: под
   «tobiasbourne» нужно 104px, оставалось 40). */
.analysis-stage .player-strip .score-card-icon {
  display: none;
}

.analysis-stage .player-strip .score-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  min-width: 0;
  /* 5px, а не 8: при 8 на подпись оставалось 43px, а «Точность» требует 47
     и вылезала за плитку (замерено 26.10.2026). */
  padding: 0 3px;
  /* Общее правило .score-card держит min-height: 53px — размер под палец,
     заданный для прежних крупных карточек. Именно он задавал высоту всей
     строки над доской: содержимого в плитке всего 33px. Запрос
     пользователя 27.10.2026 — укоротить карточки примерно на 15%. */
  min-height: 40px;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.analysis-stage .player-strip .score-card + .score-card {
  border-left: 1px solid var(--an-card-border);
}

/* Чёрная сторона — тёмные плитки той же формы и размера, что светлые у
   белых. Это и прежний принцип оформления чёрной стороны (графит), и то,
   что пользователь уже видит на экране: до переноса чёрные стояли такой
   же тёмной плашкой в заголовке. Светлые правила выше намеренно обходят
   .is-black через :not() — иначе тёмный текст базовых правил остался бы
   на белом фоне и имя игрока пропадало. */
.analysis-stage .player-strip .player-card.is-black,
.analysis-stage .player-strip .score-card-row.is-black {
  border: 1px solid transparent;
  border-radius: 14px;
  background: var(--an-navy);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}

.analysis-stage .player-strip .player-card.is-black strong {
  color: #ffffff;
}

.analysis-stage .player-strip .player-card.is-black span,
.analysis-stage .player-strip .player-card.is-black small {
  color: #cbd5e1;
}

.analysis-stage .player-strip .score-card.is-black + .score-card.is-black {
  border-left-color: rgba(255, 255, 255, 0.16);
}

.analysis-stage .player-strip .score-card-icon {
  width: 24px;
  height: 24px;
  color: var(--muted);
}

.analysis-stage .player-strip .score-card-body span {
  color: var(--muted);
  font-size: 10px;
}

.analysis-stage .player-strip .score-card.is-black .score-card-body span {
  color: #cbd5e1;
}

.analysis-stage .player-strip .score-card.is-black .score-card-body strong {
  color: #ffffff;
}

/* Зелёный/красный на «Точности» — у кого из двоих выше. Сигнал нужен на
   обеих сторонах; правило выше (белый текст) по специфичности перебило бы
   базовые .is-lead/.is-behind, поэтому повторяем их здесь с теми же
   светлыми литералами, что уже подобраны под тёмный фон. */
.analysis-stage .player-strip .score-card.is-accuracy.is-black .score-card-body strong.is-lead {
  color: #4ade80;
}

.analysis-stage .player-strip .score-card.is-accuracy.is-black .score-card-body strong.is-behind {
  color: #fecaca;
}

.analysis-stage .player-strip .score-card.is-black .score-card-icon {
  color: #cbd5e1;
}

.analysis-stage .player-strip .score-card-body strong {
  font-size: 16px;
}

.analysis-stage .player-strip .score-card.is-performance:not(.is-black) strong {
  color: var(--an-accent);
}

.analysis-stage .player-strip .versus-block {
  /* 120, а не 88: при 88 подпись «Победа чёрных» переносилась на две
     строки, и вся строка игроков вырастала с ~61 до 77px — это чистая
     потеря высоты доски, потому что её потолок считается от того, что
     осталось. Место есть: колонка доски теперь 761px при 1440. */
  min-width: 0;
  max-width: 104px;
  padding: 4px 7px;
}

.analysis-stage .player-strip .versus-block strong {
  padding: 2px 10px;
  font-size: 18px;
}

.analysis-stage .player-strip .versus-block #matchSource {
  padding: 1px 6px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--an-accent) 14%, transparent);
  color: var(--an-accent);
  font-weight: 600;
}

/* Ниже 981px пятиколоночная строка не живёт ни в каком виде: на 375px
   на неё приходится 341px. Поэтому каждая сторона снова собирается в один блок
   (карточка, под ней очки), а строка делится на три колонки
   «белые · счёт · чёрные». Так было и до редизайна 25.10.

   Правила десктопа (display: contents и явные номера колонок у чёрных)
   здесь снимаются — иначе чёрные уезжают в несуществующие колонки: замер
   на 375px без этого сброса дал две строки, колонки
   «0px 127px 0px 108px 74px» и обрезанный в ноль текст во всех плитках. */
@media (max-width: 980px) {
  .analysis-stage .player-strip {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: start;
  }

  .analysis-stage .player-strip .player-side.is-white,
  .analysis-stage .player-strip .player-side.is-black {
    display: flex;
  }

  .analysis-stage .player-strip .score-card-row.is-black,
  .analysis-stage .player-strip .player-card.is-black {
    grid-column: auto;
    grid-row: auto;
  }

  /* Счёт посередине — самая узкая колонка. Без этого «Партия
     продолжается» растягивало её до 143px из 341, и сторонам оставалось
     по 91px: аватар съедал всё, на имя приходилось 2px. */
  .analysis-stage .player-strip .versus-block {
    min-width: 0;
    max-width: 96px;
    padding: 6px;
  }

  .analysis-stage .player-strip .versus-block span {
    font-size: 10px;
  }
}

/* Телефон и планшет: тут и сами размеры нужно ужать, и плитки очков
   поставить друг под другом — рядом они в колонку 115px не влезают. */
@media (max-width: 980px) {
  /* Размеры карточки задаются здесь заново: десктопные правила строки
     (0,3,0) специфичнее общих мобильных (.player-card, 0,1,0) и иначе
     оставляли бы на имя 25px из 115px колонки. */
  .analysis-stage .player-strip .player-card {
    grid-template-columns: 30px minmax(0, 1fr);
    gap: 6px;
    padding: 6px 8px;
  }

  .analysis-stage .player-strip .player-card.is-black {
    grid-template-columns: minmax(0, 1fr) 30px;
  }

  .analysis-stage .player-strip .player-card img {
    width: 30px;
    height: 30px;
    border-radius: 8px;
  }

  .analysis-stage .player-strip .player-card strong {
    font-size: 13px;
  }

  /* Две плитки рядом в колонке 115px не живут: замер показал 0px под
     само число. Ставим их друг под другом и убираем значки — подпись
     рядом говорит то же самое. Разделитель переезжает с левой кромки на
     верхнюю, раз плитки теперь одна под другой. */
  .analysis-stage .player-strip .score-card-row {
    grid-template-columns: minmax(0, 1fr);
    padding: 6px 4px;
  }

  .analysis-stage .player-strip .score-card {
    grid-template-columns: minmax(0, 1fr);
    min-height: 0;
    padding: 4px 8px;
  }

  .analysis-stage .player-strip .score-card-icon {
    display: none;
  }

  .analysis-stage .player-strip .score-card + .score-card {
    border-left: 0;
    border-top: 1px solid var(--an-card-border);
  }

  .analysis-stage .player-strip .score-card.is-black + .score-card.is-black {
    border-top-color: rgba(255, 255, 255, 0.16);
  }

  .analysis-stage .player-strip .score-card-body strong {
    font-size: 15px;
  }

  /* Длинный ник на телефоне всё равно шире колонки — обрезаем многоточием,
     а не в ноль: видно начало имени и понятно, что оно длиннее. */
  .analysis-stage .player-strip .player-card strong,
  .analysis-stage .player-strip .player-card small {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* --- Карточка доски --- */
.analysis-stage .board-zone {
  gap: 12px;
  padding: 16px;
  border: 1px solid var(--an-card-border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
}

.analysis-stage #turnBadge {
  display: none;
}

.recent-filter {
  display: inline-flex;
  gap: 2px;
  padding: var(--switch-pad);
  border-radius: calc(var(--switch-radius) + var(--switch-pad));
  background: var(--switch-track);
}

.an-pill {
  min-height: 32px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--switch-radius);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease;
}

.an-pill:not(.is-active):hover {
  color: var(--ink);
  background: var(--surface-strong);
}

/* Активный — как активный пункт левого меню (обе группы одинаково). */
.recent-filter .an-pill.is-active {
  background: var(--switch-active-bg);
  color: var(--an-accent);
}

.an-pill:focus-visible {
  outline: 2px solid var(--an-accent);
  outline-offset: 1px;
}

/* Шкала оценки — СЛЕВА от доски (запрос пользователя 26.10.2026: вернуть
   как было). Редизайн 25.10 переносил её направо через `order: 2`; в
   разметке она и так идёт перед доской, поэтому достаточно снять order.
   Тонкая полоса и число остаются от редизайна — о них речи не было. */
.analysis-stage .board-live .eval-bar-wrap {
  flex-direction: column-reverse;
}

.analysis-stage .eval-bar {
  width: 12px;
  border-radius: 6px;
}

.analysis-stage .eval-bar-score {
  font-size: 13px;
  font-weight: 700;
}

/* Кнопки под доской. */
.analysis-stage .board-controls-below {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 0;
}

.an-nav,
.an-nav-right {
  display: flex;
  align-items: center;
  gap: 6px;
}

.analysis-stage .board-controls-below .icon-button {
  width: 38px;
  height: 38px;
  min-height: 38px;
  padding: 0;
  border: 1px solid var(--an-card-border);
  border-radius: 10px;
  background: var(--surface);
  box-shadow: none;
  font-size: 16px;
}

.analysis-stage .board-controls-below .an-play {
  background: var(--an-navy);
  border-color: var(--an-navy);
  color: #ffffff;
}

/* Без партии листать нечего (UX-исследование №19). */
.analysis-stage .board-controls-below .icon-button:disabled {
  opacity: 0.4;
  cursor: default;
}

.an-ply-select[hidden] {
  display: none;
}

.an-ply-select {
  height: 38px;
  min-width: 120px;
  padding: 0 10px;
  border: 1px solid var(--an-card-border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
}

/* Лента миниатюр позиций. */
.segment {
  display: inline-flex;
  flex: 1 1 0;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 10px;
  border: 1px solid var(--an-card-border);
  border-radius: var(--radius-control);
  background: var(--surface-soft);
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  box-shadow: none;
}

/* С вкладкой «Партия» их пять (29.10.2026). Ширина — по подписи, а не
   поровну: «Секундант 🔒» длиннее остальных и при равных долях обрезался.
   Если в ряд не помещаются (колонка уже ~460px), переносятся на вторую
   строку, а не режут текст. */
.analysis-stage .analysis-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 2px;
  padding: var(--switch-pad);
  border: 0;
  border-radius: calc(var(--switch-radius) + var(--switch-pad));
  background: var(--switch-track);
  box-shadow: none;
}

.analysis-stage .analysis-tabs .segment {
  display: inline-flex;
  flex: 1 1 auto;
  align-items: center;
  justify-content: center;
  gap: 6px;
  /* 38 + дорожка 2×3 = прежние 44px высоты строки вкладок. */
  min-height: 38px;
  padding: 0 8px;
  border: 0;
  border-radius: var(--switch-radius);
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  font-weight: 600;
  box-shadow: none;
  transition: background-color 150ms ease, color 150ms ease;
}

.analysis-stage .analysis-tabs .segment.is-active {
  background: var(--switch-active-bg);
  color: var(--an-accent);
}

.analysis-stage .analysis-tabs .segment:not(.is-active):not(:disabled):hover {
  color: var(--ink);
  background: var(--surface-strong);
}

.analysis-stage .analysis-tabs .segment:focus-visible {
  outline: 2px solid var(--an-accent);
  outline-offset: 1px;
}

/* Вкладки отчёта до анализа (29.10.2026): видны, чтобы было понятно, что
   появится, но не нажимаются. */
.analysis-stage .analysis-tabs .segment:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Стрелки альтернатив («Варианты» над доской). */
.best-move-arrow.is-alt {
  opacity: 0.55;
  filter: grayscale(0.6);
}

/* --- Сетка экрана на десктопе --- */
@media (min-width: 981px) {
  body.page-analysis .analysis-stage {
    display: grid;
    grid-template-columns: minmax(0, 1.5fr) minmax(330px, 1fr);
    grid-template-areas:
      "strip strip"
      "board side";
    grid-template-rows: auto 1fr;
    gap: 14px 20px;
    padding: 0;
    background: transparent;
    box-shadow: none;
  }

  body.page-analysis .analysis-stage .player-strip {
    grid-area: strip;
  }

  body.page-analysis .analysis-stage .board-with-side {
    display: contents;
  }

  body.page-analysis .analysis-stage .board-zone {
    grid-area: board;
    align-self: start;
  }

  body.page-analysis .analysis-stage .board-side {
    grid-area: side;
    align-self: start;
  }
}

@media (max-width: 620px) {
  .analysis-stage .analysis-tabs .segment {
    min-height: 38px;
    padding: 0 6px;
    font-size: 12px;
  }

}

/* Плотность как в макете: экран анализа помещается в 1280×853. */
@media (min-width: 981px) {
  /* Плотный зазор сверху (10px вместо 22px) — ради высоты доски. Но
     само место под зафиксированной шапкой резервировать всё равно надо:
     без этого (дефект найден пользователем 26.10.2026) под неё уходили
     и заголовок «Анализ партии» с кнопкой возврата, и карточка чёрных,
     и верх полосы вкладок — замер при ширине 1024: шапка занимала
     12-131px, а содержимое начиналось с 10px. */
  body.page-analysis .app-shell {
    padding-top: calc(var(--topbar-space) + 10px);
    padding-bottom: 16px;
  }

  /* Вертикаль экрана анализа собрана здесь, в одном месте: раньше эти же
     значения задавались и выше по файлу, и тут — правка наверху молча
     перебивалась (замерено 26.10.2026: карточка игрока получала 9px 12px
     вместо выставленных 6px 10px). Всё, что ужато ниже, идёт в высоту
     доски — её потолок считается от этого бюджета. */
  body.page-analysis .analysis-stage {
    gap: 8px 20px;
  }

  .analysis-stage .player-strip .player-card {
    padding: 4px 10px;
  }

  .analysis-stage .player-strip .score-card-row {
    padding: 4px 6px;
  }

  .analysis-stage .board-zone {
    gap: 5px;
    padding: 8px 12px;
  }

  .an-pill {
    min-height: 28px;
  }

  /* Доска не выше, чем позволяет окно: остальное в карточке ~360px. */
  .analysis-stage .chess-board-frame {
    width: min(100%, 65vh, 820px, calc(100vh - 360px));
  }
}

@media (min-width: 981px) {
  /* Потолок пересчитан 26.10.2026: из колонки доски ушла полоса миниатюр,
     строка игроков стала компактнее (77 -> 67px), обвязка ужата. Бюджет
     при окне 900px — замерен, не прикинут: шапка 73 + отступ страницы 10
     + заголовок 32 + зазор 8 + строка игроков 67 + зазор 8 + обвязка
     карточки доски 133 + нижний отступ 16 = 264 плюс сама шапка
     (273 -> 264 после того, как строка игроков ужалась с 63 до 54px —
     запрос пользователя 27.10.2026, освободившиеся 9px ушли доске).

     Шапку вычитаем переменной, а не числом: её высота зависит от ширины
     окна — она переносит содержимое на вторую строку (73px при 1440,
     131px при 1024, см. --topbar-space). С зашитой константой 73 экран
     на 1280x800 вылезал вниз на 55px — ровно на разницу высот шапки
     (замерено 27.10.2026). 273 — всё остальное, с запасом в несколько
     пикселей, чтобы не прокручивалось от мелких расхождений шрифтов.
     65vh подняты до 70vh — на 1080px ограничителем были именно они
     (702 против 730 по формуле), а не высота окна. */
  .analysis-stage .chess-board-frame {
    /* 05.11.2026: ярлыки над доской убраны (UX-исследование №17), освободившиеся
       40 px ушли доске: 264 -> 224, 70vh -> 74vh. */
    width: min(100%, 74vh, 820px, calc(100vh - var(--topbar-space) - 224px));
  }

  .analysis-stage .player-strip .player-card span,
  .analysis-stage .player-strip .player-card strong,
  .analysis-stage .player-strip .player-card small {
    line-height: 1.2;
  }

  /* Блок счёта был самым высоким в строке — 60.8px против 54 у карточек
     игроков, то есть высоту всей строки задавал он (замерено 27.10.2026
     со снятым align-items: stretch). Ужат, чтобы строка стала примерно на
     15% ниже, как просил пользователь. */
  .analysis-stage .player-strip .versus-block {
    gap: 1px;
    padding: 3px 10px;
  }

  .analysis-stage .player-strip .versus-block strong {
    padding: 1px 10px;
    font-size: 17px;
  }

  /* Подпись исхода — в одну строку. Блок счёта сузился до 104px ради
     полного «Перформанс» в соседних плитках, и «Победа чёрных» прежним
     размером переносилась на две строки: вся строка игроков вырастала с
     54 до 66px (замерено 27.10.2026). */
  .analysis-stage .player-strip .versus-block span {
    font-size: 10px;
  }
}

/* Строка игроков встаёт НАД ДОСКОЙ (в её колонке), а не во всю ширину —
   запрос пользователя 26.10.2026. Только от 1400px: колонка доски это
   примерно половина окна, и пяти плиткам нужно ~670px — замерено 670 при
   1440 (ровно хватает) против 586 при 1280. Ниже 1400 строка остаётся во
   всю ширину: так она одной высоты (67px), и доске достаётся больше
   вертикали, чем если собирать стороны в блоки (замер при 1280x800:
   450px доски против 365). */
@media (min-width: 1400px) {
  body.page-analysis .analysis-stage {
    grid-template-areas:
      "strip side"
      "board side";
  }
}

/* Шкала оценки как в макете: светлая дорожка, зелёная заливка снизу —
   доля перевеса белых (та же winProbabilityPercent, смысл прежний). */
/* Шкала оценки строго чёрно-белая (запрос пользователя 26.10.2026).
   Редизайн 25.10 перекрашивал её в зелёное по светлой дорожке; базовые
   правила выше (.eval-bar / .eval-bar-fill) и так чёрно-белые — здесь
   остаётся только снять акцентную линию на границе заливки, чтобы цвета
   не осталось совсем. Граница между чёрным и белым читается и без неё. */
.analysis-stage .eval-bar-fill {
  border-top: 0;
}

/* ===================== Живая игра =====================================
   docs/LIVE_PLAY_DESIGN.md, этап 3. Доска здесь своя (.play-square), а не
   общая с анализом и квестами: по правилу Н-5 это независимые подсистемы,
   общих селекторов между ними быть не должно. Фигуры — тот же набор
   cburnett, что и везде, новых картинок не заводим. */

.play-page {
  display: grid;
  gap: 20px;
  justify-items: center;
  padding-top: calc(var(--topbar-space) + 10px);
}

.play-topbar {
  justify-self: start;
}

/* `display: grid` у блоков ниже перебивает атрибут `hidden` из таблицы
   браузера, и скрытые блоки остаются видимыми. Экран переключает их именно
   атрибутом, поэтому возвращаем ему силу — иначе гость видит и лобби, и
   пустую доску разом (поймано на первом же показе экрана). */
.play-page [hidden] {
  display: none;
}

.play-guest {
  display: grid;
  justify-items: center;
  gap: 14px;
  width: min(100%, 720px);
  padding: 24px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  box-shadow: var(--shadow);
}

/* Лобби — две равные карточки «С человеком» и «С компьютером»
   (31.10.2026). Ниже 860px — одна под другой. */
.play-lobby {
  display: grid;
  gap: 16px;
  width: min(100%, 1040px);
}

/* Карточки разной высоты и не растягиваются: прижатые к низу кнопки
   оставляли посреди карточки «С человеком» пустое поле. */
.play-modes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 16px;
}

.play-mode {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-width: 0;
  padding: 22px;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
}

.play-mode-head h3 {
  margin: 0 0 4px;
  font-size: var(--fs-h3);
}

.play-mode-head p {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.play-mode .play-lobby-actions > button {
  flex: 1 1 0;
}

.play-promise {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  text-align: center;
}

/* Приглашение войти — ссылка, а не кнопка, а горизонтальных отступов у
   .primary-button своих нет: обычно их даёт поле <button> от браузера.
   У <a> его нет, и текст вылезал за края. Чиним здесь, а не в общем
   правиле: оно действует на каждую кнопку сайта. */
.play-guest .primary-button {
  padding: 0 22px;
  text-decoration: none;
}

/* Гость (08.11.2026): не стена, а строка-подсказка над лобби. */
.play-guest.is-soft {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  width: 100%;
  padding: 12px 16px;
  box-shadow: none;
  border: 1px solid var(--an-card-border);
}

.play-guest.is-soft[hidden] {
  display: none;
}

.play-guest.is-soft p {
  margin: 0;
  color: var(--muted);
  font-size: 14px;
}

.play-guest.is-soft .ghost-button {
  text-decoration: none;
}

.play-controls {
  display: grid;
  gap: 10px;
  width: 100%;
}

.play-control-group {
  display: grid;
  gap: 6px;
}

.play-control-label {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
}

.play-control-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

.play-control {
  display: grid;
  align-content: center;
  justify-items: center;
  gap: 2px;
  min-height: 52px;
  padding: 8px 6px;
  border: 2px solid transparent;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--blue) 8%, var(--surface));
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

.play-control.is-active {
  border-color: var(--blue);
}

.play-control strong {
  font-size: var(--fs-h4);
  font-weight: 800;
}

/* Сколько народу ждёт на этом контроле — только когда кто-то ждёт
   (31.10.2026): на пустом контроле подпись «никто не ждёт» была шумом. */
.play-control-waiting {
  padding: 0 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--green) 18%, var(--surface));
  color: var(--green-text);
  font-size: var(--fs-caption);
  font-weight: 700;
}

.play-lobby-actions,
.play-game-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}

.play-lobby-status {
  min-height: 24px;
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.play-lobby-waiting {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  min-height: 24px;
}

.play-invite-link {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
}

/* Крестик — последний в ряду и не сжимается: при узком окне поле ссылки
   ужимается само, а кнопка отмены должна остаться нажимаемой. */
.play-invite-close {
  flex-shrink: 0;
  width: 40px;
  min-height: 40px;
  font-size: 20px;
  line-height: 1;
}

.play-invite-link input {
  flex: 1;
  min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
}

.play-bot-levels {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  width: 100%;
}

.play-bot-level {
  display: grid;
  gap: 2px;
  padding: 9px 6px;
  border: 2px solid transparent;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--blue) 8%, var(--surface));
  color: var(--ink);
  font: inherit;
  cursor: pointer;
}

.play-bot-level.is-active {
  border-color: var(--blue);
}

.play-bot-level strong {
  font-size: var(--fs-body-sm);
  font-weight: 800;
}

/* Сила — по шкале самого движка, не по ФИДЕ, поэтому приглушена и со
   значком «примерно»: выдавать её за настоящий рейтинг нельзя. */
.play-bot-level span {
  color: var(--muted);
  font-size: var(--fs-caption);
}

.play-bot-side {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.play-side-pick {
  padding: 7px 14px;
  border: 2px solid transparent;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--ink) 6%, var(--surface));
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.play-side-pick.is-active {
  border-color: var(--blue);
}

.play-game {
  display: grid;
  grid-template-columns: minmax(0, 560px) minmax(260px, 300px);
  justify-content: center;
  gap: 24px;
  width: 100%;
}

.play-board-col {
  display: grid;
  align-content: start;
  gap: 10px;
  min-width: 0;
}

.play-side {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
}

.play-player {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  flex-wrap: wrap;
}

/* Чей цвет — кружком: подпись «Белые / Чёрные» заменило имя. */
.play-player-dot {
  flex: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
}

.play-player-dot.is-white {
  border: 1px solid #9ca3af;
  background: #fff;
}

/* Светлая обводка — чтобы чёрный кружок был виден и на тёмной теме. */
.play-player-dot.is-black {
  border: 1px solid #6b7280;
  background: #1f2937;
}

.play-side-name {
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

.play-side-name small {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

/* Взятые фигуры внахлёст и перевес «+2». */
.play-captured {
  display: inline-flex;
  align-items: center;
}

.play-captured img {
  width: 18px;
  height: 18px;
}

.play-captured img + img {
  margin-left: -7px;
}

.play-captured-lead {
  margin-left: 6px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
}

/* Часы — моноширинные цифры, иначе строка дёргается на каждой секунде:
   в пропорциональном шрифте «1» у́же остальных цифр. */
.play-clock {
  padding: 4px 14px;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--ink) 8%, var(--surface));
  color: var(--ink);
  font-size: var(--fs-h4);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.play-side.is-turn .play-clock {
  background: var(--blue);
  color: #fff;
}

/* Последние 20 секунд. Цветом, а не морганием: мигание на часах соперника
   отвлекает от доски, а здесь нужно заметить своё время, не теряя позицию
   из виду.

   Селектор перечислен дважды нарочно. Правило «идут часы этой стороны»
   выше весит больше (три класса против двух), и при своём ходе синяя
   заливка перебивала красную — то есть нехватка времени пряталась ровно
   тогда, когда она важна. */
.play-side.is-turn .play-clock.is-low,
.play-clock.is-low {
  background: var(--quality-blunder);
  color: #fff;
}

/* Ряды тоже заданы явно (31.10.2026): без grid-template-rows высота ряда
   бралась по содержимому, и пустые ряды схлопывались в узкие полоски —
   в начальной позиции середина доски была вдвое ниже краёв. */
.play-board {
  display: grid;
  /* minmax(0, 1fr), а не 1fr: у 1fr нижняя граница — размер содержимого,
     и клетка с лишним содержимым растягивала свой ряд (снимок
     пользователя 31.10.2026, второй ряд вдвое выше). Так же у доски анализа. */
  grid-template-columns: repeat(8, minmax(0, 1fr));
  grid-template-rows: repeat(8, minmax(0, 1fr));
  container-type: inline-size;
  width: 100%;
  aspect-ratio: 1;
  border-radius: 10px;
  /* Перетаскивание фигуры пальцем не должно прокручивать страницу. */
  touch-action: none;
  overflow: hidden;
  box-shadow: var(--shadow);
}

.play-square {
  position: relative;
  display: grid;
  place-items: center;
  min-width: 0;
  min-height: 0;
  padding: 0;
  border: 0;
  background: var(--board-light, #eef1f6);
  cursor: pointer;
}

.play-square.is-dark {
  background: var(--board-dark, #b7c0d0);
}

.play-square img {
  position: relative;
  z-index: 1;
  width: 86%;
  height: 86%;
  pointer-events: none;
}

/* Подсветка — как у доски анализа (31.10.2026): откуда пошли — рамка,
   куда пришли — заливка с рамкой, под фигурой (у картинки z-index: 1). */
.play-square::before {
  position: absolute;
  inset: 7%;
  border-radius: 6px;
  content: "";
  opacity: 0;
  pointer-events: none;
}

.play-square.is-from::before {
  border: 2px solid color-mix(in srgb, var(--amber) 88%, #fff);
  opacity: 0.9;
}

.play-square.is-to::before {
  background: color-mix(in srgb, var(--board-accent) 30%, transparent);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--board-accent) 84%, #fff);
  opacity: 1;
  transform: scale(0.94);
}

.play-square.is-selected {
  box-shadow: inset 0 0 0 100px color-mix(in srgb, var(--board-accent) 26%, transparent);
}

/* Король под шахом — красное свечение, как на доске анализа. */
.play-square.is-check {
  background-image: radial-gradient(circle, color-mix(in srgb, var(--red) 68%, transparent) 0%, transparent 72%);
}

/* Отметки ходов наложены поверх клетки, а не стоят в её сетке: раньше
   кольцо «можно бить» вставало второй строкой под фигурой и растягивало
   ряд (31.10.2026). Цвета — как у доски анализа. */
.play-square.is-target::after {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  box-sizing: border-box;
  width: 28%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: color-mix(in srgb, var(--board-accent) 55%, var(--ink));
  content: "";
  opacity: 0.55;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

.play-square.is-target:has(img)::after {
  width: 86%;
  border: 3px solid color-mix(in srgb, var(--board-accent) 60%, var(--ink));
  background: transparent;
  opacity: 0.68;
}

/* Координаты в углах крайних клеток, цветом соседней клетки — так их
   видно при любом цвете доски. */
.play-coord {
  position: absolute;
  z-index: 1;
  color: var(--board-dark, #b7c0d0);
  font-size: clamp(0.6rem, 2.1cqw, 0.85rem);
  font-weight: 700;
  line-height: 1;
  pointer-events: none;
}

.play-square.is-dark .play-coord {
  color: var(--board-light, #eef1f6);
}

.play-coord.is-file {
  right: 5%;
  bottom: 4%;
}

.play-coord.is-rank {
  top: 4%;
  left: 5%;
}

/* Перетаскивание: фигура под пальцем, на клетке остаётся её тень. */
.play-square img.is-dragging {
  opacity: 0.3;
}

.play-drag-ghost {
  position: fixed;
  z-index: 1000;
  pointer-events: none;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 6px 10px rgba(15, 23, 42, 0.35));
}

.play-board-wrap {
  position: relative;
}

/* Выбор фигуры при превращении — поверх доски. */
.play-promotion {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: grid;
  place-items: center;
  border-radius: 10px;
  background: rgba(15, 23, 42, 0.45);
}

.play-promotion-card {
  display: grid;
  gap: 10px;
  padding: 14px;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
}

.play-promotion-title {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  text-align: center;
}

.play-promotion-pieces {
  display: flex;
  gap: 8px;
}

.play-promotion-piece {
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  padding: 4px;
  border: 2px solid transparent;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--blue) 8%, var(--surface));
  cursor: pointer;
}

.play-promotion-piece:hover,
.play-promotion-piece:focus-visible {
  border-color: var(--blue);
}

.play-promotion-piece img {
  width: 100%;
  height: 100%;
}

.play-panel-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

/* Шестерёнка «Играть» стоит наверху панели — её окно открывается вниз,
   а не вверх, как у доски анализа. */
.board-settings-panel.is-down {
  top: calc(100% + 8px);
  bottom: auto;
}

.play-status {
  min-height: 22px;
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  text-align: center;
}

/* Панель партии. Высота — по колонке доски: список ходов растёт в
   оставшееся место и прокручивается сам (height: 0 + flex: 1 не даёт
   длинной партии растянуть панель выше доски). */
.play-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
  padding: 16px;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
}

.play-panel-head {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: 800;
}

.play-moves {
  flex: 1 1 auto;
  height: 0;
  min-height: 160px;
  overflow-y: auto;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--ink) 3%, var(--surface));
  font-variant-numeric: tabular-nums;
}

.play-move-row {
  display: grid;
  grid-template-columns: 40px 1fr 1fr;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  font-size: var(--fs-body-sm);
}

.play-move-row:nth-child(even) {
  background: color-mix(in srgb, var(--ink) 4%, transparent);
}

.play-move-num {
  color: var(--muted);
}

.play-move {
  padding: 2px 6px;
  border-radius: 6px;
  color: var(--ink);
  font-weight: 600;
}

.play-move.is-last {
  background: color-mix(in srgb, var(--blue) 16%, transparent);
  font-weight: 800;
}

.play-moves-empty {
  margin: 0;
  padding: 24px 12px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  text-align: center;
}

/* :not([hidden]) — иначе это правило перебивает .play-page [hidden]
   (у них одинаковый вес), и после конца партии кнопки оставались. В столбик:
   «Предложить ничью» в половину панели не помещается в строку. */
/* Колонка задана явно: общее правило с лобби даёт justify-content: center,
   и без этого колонка сжималась до ширины подписи. */
.play-panel .play-game-actions:not([hidden]) {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
}

/* Соперник предложил ничью — та же кнопка её принимает. */
.play-panel .ghost-button.is-offer {
  box-shadow: inset 0 0 0 2px var(--blue);
  color: var(--blue);
}

/* Итог партии: крупно результат, ниже — что делать дальше. */
.play-result {
  display: grid;
  gap: 4px;
  padding: 14px;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--ink) 5%, var(--surface));
  text-align: center;
}

.play-result[data-outcome="win"] {
  background: color-mix(in srgb, var(--green) 14%, var(--surface));
}

.play-result[data-outcome="loss"] {
  background: color-mix(in srgb, var(--quality-blunder) 12%, var(--surface));
}

.play-result-title {
  color: var(--ink);
  font-size: var(--fs-h3);
  font-weight: 800;
}

.play-result-reason {
  display: block;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.play-result-reason::first-letter {
  text-transform: uppercase;
}

.play-result-actions {
  display: grid;
  gap: 8px;
  margin-top: 10px;
}

@media (max-width: 980px) {
  .play-game {
    grid-template-columns: minmax(0, 560px);
  }

  .play-moves {
    flex: none;
    height: auto;
    max-height: 220px;
  }
}

@media (max-width: 860px) {
  .play-modes {
    grid-template-columns: minmax(0, 1fr);
  }
}

.ghost-button.is-danger {
  color: var(--quality-blunder);
}

@media (max-width: 620px) {
  .play-guest,
  .play-mode {
    padding: 16px;
  }
}

/* ===== ИИ-помощник (28.10.2026): «Объяснить ход» и ИИ-чат ============== */

.an-ai-card {
  margin-bottom: 12px;
  border-left: 4px solid var(--blue);
  background: color-mix(in srgb, var(--blue) 6%, var(--surface));
}

.an-ai-card[hidden] {
  display: none;
}

.ai-card-title {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ai-card-title svg {
  width: 18px;
  height: 18px;
  color: var(--blue);
}

.ai-hint,
.ai-note {
  margin: 0 0 12px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.ai-note {
  margin: 10px 0 0;
  font-size: var(--fs-caption);
}

.ai-answer p {
  margin: 0 0 8px;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  line-height: 1.55;
}

.ai-answer p:last-child {
  margin-bottom: 0;
}

.ai-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
}

.primary-button.compact {
  min-height: 36px;
  padding: 6px 14px;
  border-radius: var(--radius-control);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  cursor: pointer;
}

.primary-button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
  box-shadow: none;
}

.ai-quota {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-caption);
}

.ai-quota a,
.ai-link {
  color: var(--blue);
  font-weight: 600;
}

.ai-error {
  margin: 0 0 12px;
  padding: 8px 12px;
  border-radius: 12px;
  background: var(--an-bad-soft);
  color: var(--an-bad);
  font-size: var(--fs-body-sm);
}

.ai-thinking {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.ai-dots {
  display: inline-flex;
  gap: 4px;
}

.ai-dots i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--blue);
  animation: ai-dot 1.2s infinite ease-in-out;
}

.ai-dots i:nth-child(2) { animation-delay: 0.15s; }
.ai-dots i:nth-child(3) { animation-delay: 0.3s; }

@keyframes ai-dot {
  0%, 80%, 100% { opacity: 0.25; transform: scale(0.8); }
  40% { opacity: 1; transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .ai-dots i { animation: none; opacity: 0.7; }
}

.persona-switch[hidden] {
  display: none;
}

/* Окно Секунданта говорит голосом ИИ (30.10.2026, запрос пользователя):
   итог партии из отчёта, ошибки на ходах, ответы на вопросы. Действия в
   окне — ссылки, а не полновесные кнопки: окно — реплика, а не панель. */
.coach-move-note {
  row-gap: 8px;
}

/* «Фазы партии» и «Стиль игры» — две карточки как на «Обзоре»
   (.overview-card + .side-card-title), 01.11.2026. */
.secondant-stats {
  display: grid;
  gap: 12px;
  margin-top: 12px;
}

.phase-cells {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.phase-cell {
  display: grid;
  align-content: start;
  gap: 2px;
  min-width: 0;
  padding: 10px;
  border-radius: var(--radius-control);
  background: color-mix(in srgb, var(--ink) 4%, var(--surface));
}

.phase-cell-label {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

.phase-cell-value {
  color: var(--ink);
  font-size: var(--fs-h3);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.phase-cell.is-empty .phase-cell-value {
  color: var(--muted);
}

.phase-cell-meta {
  color: var(--muted);
  font-size: var(--fs-caption);
  line-height: 1.35;
}

/* Слабая фаза — янтарная обводка, а не красная: красный на сайте значит
   ошибку хода, а слабая фаза — подсказка, где тренироваться. */
.phase-cell.is-weakest {
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--amber) 70%, transparent);
}

.phase-cell-badge {
  justify-self: start;
  margin-top: 4px;
  padding: 1px 7px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--amber) 22%, var(--surface));
  color: color-mix(in srgb, var(--amber) 45%, var(--ink));
  font-size: var(--fs-caption);
  font-weight: 700;
}

.style-note {
  margin: -4px 0 12px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.style-note[hidden] {
  display: none;
}

.style-bars {
  display: grid;
  gap: 10px;
}

.style-bar-head {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 4px;
  color: var(--ink);
  font-size: var(--fs-body-sm);
}

.style-bar-head span:last-child {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Одним цветом: стиль — описание партии, а не оценка, светофор тут
   читался бы как «хорошо/плохо». */
.style-bar {
  height: 6px;
  overflow: hidden;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 8%, var(--surface));
}

.style-bar-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--an-accent, var(--blue));
}

.ai-bubble-action {
  padding: 0;
  border: 0;
  background: none;
  color: var(--blue-dark);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.ai-bubble-action {
  justify-self: start;
  font-size: var(--fs-body-sm);
}

.ai-bubble-action:hover {
  color: var(--blue);
  text-decoration: underline;
}

.ai-bubble-action:focus-visible {
  border-radius: 4px;
  outline: 3px solid var(--blue);
  outline-offset: 2px;
}

.ai-chip:disabled,
.ai-chip:disabled:hover {
  border-color: var(--line);
  color: var(--muted);
  cursor: not-allowed;
}

/* Без подписки — начало разбора своей партии, остальное размыто
   (30.10.2026). Окно и аватар — те же .coach-summary/.coach-move-note,
   что у подписчика, чтобы было видно: это та же вкладка, только закрытая. */
.secondant-preview {
  padding: 4px 0 16px;
}

.secondant-preview[hidden] {
  display: none;
}

.secondant-preview-blur {
  filter: blur(5px);
  user-select: none;
  pointer-events: none;
}

.secondant-preview-cta {
  display: grid;
  justify-items: start;
  gap: 10px;
}

.secondant-preview-cta p {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

.secondant-preview-rows {
  display: grid;
  gap: 8px;
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
}

.secondant-preview-rows a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--an-card-border);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-decoration: none;
  text-transform: uppercase;
}

.secondant-preview-rows a:hover {
  border-color: var(--blue);
  color: var(--ink);
}

.secondant-preview-rows a:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
}

/* «Обзор»: приглашение во вкладку «Секундант» (30.10.2026). */
.secondant-teaser {
  display: flex;
  align-items: center;
  gap: 14px;
  border-left: 4px solid var(--blue);
}

.secondant-teaser[hidden] {
  display: none;
}

.secondant-teaser-avatar {
  flex: none;
  width: 48px;
}

.secondant-teaser-avatar svg {
  display: block;
  width: 100%;
  height: auto;
}

.secondant-teaser-body {
  flex: 1 1 auto;
  min-width: 0;
}

.secondant-teaser-title {
  margin: 0 0 2px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.secondant-teaser-text {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  line-height: 1.45;
}

.secondant-teaser > .primary-button {
  flex: none;
  white-space: nowrap;
}

@media (max-width: 520px) {
  .secondant-teaser {
    flex-wrap: wrap;
  }

  .secondant-teaser > .primary-button {
    width: 100%;
  }
}

/* Точка «новое» на вкладке «Секундант»: разбор новой партии ещё не
   открывали. */
#tabPlan.has-news::after {
  content: "";
  display: inline-block;
  width: 7px;
  height: 7px;
  margin-left: 6px;
  border-radius: 50%;
  background: var(--red);
  vertical-align: middle;
}

.ai-chat-suggestions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.ai-chat-suggestions[hidden] {
  display: none;
}

.ai-chip {
  padding: 6px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-body-sm);
  cursor: pointer;
}

.ai-chip:hover {
  border-color: var(--blue);
  color: var(--blue);
}

/* Правило «3 + 2» писалось под пять вкладок (был отдельный ИИ-чат). С
   четырьмя оно давало 3 + 1 с растянутой последней; замер 31.10.2026 на
   390px: вкладкам нужно 335px из ~350, в один ряд они помещаются. Перенос
   оставлен на совсем узкие экраны — строки делятся по ширине подписей. */
@media (max-width: 560px) {
  .analysis-stage .analysis-tabs {
    flex-wrap: wrap;
  }
}

/* Объяснение Секунданта пунктами (28.10.2026): итог, пункты, ходы-чипы. */
.ai-summary {
  margin: 0 0 10px;
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: 1.35;
}

.ai-points {
  display: grid;
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.ai-point {
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  transition: border-color 150ms ease, box-shadow 150ms ease;
}

.ai-point.is-active {
  border-color: var(--blue);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--blue) 18%, transparent);
}

.ai-point-head {
  display: grid;
  grid-template-columns: 22px minmax(0, 1fr) auto;
  align-items: start;
  gap: 8px;
  padding: 9px 10px;
}

.ai-point.has-line .ai-point-head {
  cursor: pointer;
}

.ai-point.has-line .ai-point-head:hover .ai-point-text {
  color: var(--blue);
}

.ai-point-head:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--blue) 55%, transparent);
  outline-offset: -2px;
  border-radius: 12px;
}

.ai-point-num {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--blue) 14%, var(--surface));
  color: var(--blue);
  font-size: var(--fs-caption);
  font-weight: 800;
}

.ai-point-text {
  color: var(--ink);
  font-size: var(--fs-body-sm);
  line-height: 1.45;
}

.ai-point-play {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 50%;
  background: var(--blue);
  color: #fff;
  font-size: 11px;
  cursor: pointer;
}

.ai-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding: 0 10px 10px 40px;
}

.ai-step {
  padding: 3px 8px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--surface-soft);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-caption);
  font-weight: 700;
  cursor: pointer;
}

.ai-step:hover {
  border-color: var(--blue);
}

.ai-step.is-current {
  border-color: var(--blue);
  background: var(--blue);
  color: #fff;
}

.ai-step-num {
  margin-right: 3px;
  opacity: 0.7;
  font-weight: 600;
}

.ai-hint-small {
  margin: 8px 0 0;
  font-size: var(--fs-caption);
}

/* Плашка варианта над доской на телефоне: кнопка «Вернуться к партии»
   уезжала за правый край. Переносим её под текст. */
@media (max-width: 560px) {
  .board-branch-banner {
    flex-wrap: wrap;
  }

  .board-branch-banner button {
    min-height: 32px;
    white-space: nowrap;
  }
}

/* ===== Настройки доски анализа: звук ходов и цвет (29.10.2026) ===== */
.board-settings {
  position: relative;
}

.board-settings-button svg {
  width: 18px;
  height: 18px;
}

/* Панель открывается вверх: кнопка стоит под доской, внизу экрана. */
.board-settings-panel {
  position: absolute;
  right: 0;
  bottom: calc(100% + 8px);
  z-index: 20;
  display: grid;
  gap: 14px;
  width: 220px;
  padding: 12px 14px;
  border: 1px solid var(--an-card-border);
  border-radius: 12px;
  background: var(--surface);
  box-shadow: var(--shadow);
}

.board-settings-panel[hidden] {
  display: none;
}

.board-settings-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  color: var(--ink);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.board-settings-row input {
  width: 18px;
  height: 18px;
  margin: 0;
  accent-color: var(--an-accent);
  cursor: pointer;
}

.board-settings-label {
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
}

.board-theme-swatches {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

/* Образец — четыре клетки 2×2 тех же цветов, что получит доска. */
.board-theme-swatch {
  display: grid;
  grid-template-columns: 1fr 1fr;
  width: 38px;
  height: 38px;
  padding: 0;
  overflow: hidden;
  border: 2px solid transparent;
  border-radius: 8px;
  background: none;
  cursor: pointer;
}

.board-theme-swatch:hover {
  transform: none;
}

.board-theme-swatch span:nth-child(1),
.board-theme-swatch span:nth-child(4) {
  background: var(--board-light);
}

.board-theme-swatch span:nth-child(2),
.board-theme-swatch span:nth-child(3) {
  background: var(--board-dark);
}

.board-theme-swatch[aria-checked="true"] {
  border-color: var(--an-accent);
}

.board-theme-swatch:focus-visible {
  outline: 2px solid var(--an-accent);
  outline-offset: 2px;
}

/* Цвета доски. «Дерево» — прежние --board-light/--board-dark темы сайта,
   остальные одинаковы в светлой и тёмной теме. Переменные ставятся на
   рамку доски анализа и на доску «Играть» (с 31.10.2026 выбор общий) —
   доска «Квестов» их не получает (Н-5). */
.board-theme-swatch[data-board-theme="green"],
.chess-board-frame[data-board-theme="green"],
.play-board[data-board-theme="green"] {
  --board-light: #e8ebd0;
  --board-dark: #7a9a5a;
}

.board-theme-swatch[data-board-theme="blue"],
.chess-board-frame[data-board-theme="blue"],
.play-board[data-board-theme="blue"] {
  --board-light: #dfe6ec;
  --board-dark: #8aa3b5;
}

.board-theme-swatch[data-board-theme="gray"],
.chess-board-frame[data-board-theme="gray"],
.play-board[data-board-theme="gray"] {
  --board-light: #e4e4e1;
  --board-dark: #9c9c98;
}

/* ===== Профиль (U-67, 03.11.2026) =====
   Редизайн по wireframe пользователя: шапка с меню «Аккаунт», слева «Ваша
   игра» и «Прогресс в задачах», справа «Активность» и «Последние партии»,
   ниже «Достижения», внизу подписка и ники. Плотно, чтобы страница
   прокручивалась как можно меньше. Цвета — только токены темы, поэтому
   тёмная тема работает сама. */
.info-section.profile-section {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
  padding: 0;
  background: none;
  box-shadow: none;
  backdrop-filter: none;
}

/* hidden побеждает display: grid/flex у блоков профиля. */
.profile-section [hidden] {
  display: none;
}

/* «.info-section p» в начале файла красит все абзацы раздела жирным
   серым — в профиле у абзацев свои стили. */
.info-section.profile-section p {
  font-weight: 400;
  line-height: 1.45;
}

.profile-section .pf-title {
  margin: 0;
  font-size: var(--fs-h2);
  line-height: 1.2;
}

.pf-card,
.pf-tile {
  min-width: 0;
  padding: 14px 16px;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
}

.pf-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 10px;
}

.pf-card-title,
.profile-section .pf-card-title {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: 1.3;
}

.pf-muted-inline {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 500;
}

.profile-section p.pf-muted,
.profile-section .pf-muted-small {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.profile-section .pf-muted-small {
  font-size: var(--fs-caption);
}

.pf-link {
  flex: none;
  padding: 2px 0;
  border: 0;
  background: none;
  color: var(--accent-text);
  font: inherit;
  font-size: var(--fs-caption);
  font-weight: 600;
  cursor: pointer;
}

.pf-link:hover {
  text-decoration: underline;
}

.pf-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 10%, var(--surface));
  color: var(--accent-text);
}

.pf-icon.is-large {
  width: 40px;
  height: 40px;
  border-radius: 12px;
}

/* --- Шапка --- */
.pf-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
}

.pf-avatar {
  display: grid;
  flex: none;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 14%, var(--surface));
  color: var(--accent-text);
  font-size: var(--fs-h4);
  font-weight: 700;
}

.pf-avatar.is-guest {
  background: var(--surface-strong);
  color: var(--muted);
}

.pf-identity {
  flex: 1 1 200px;
  min-width: 0;
}

.profile-section .pf-name {
  margin: 0;
  font-size: var(--fs-h4);
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.profile-section p.pf-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  margin: 2px 0 0;
  color: var(--muted);
  font-size: var(--fs-caption);
}

.pf-chip {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-strong);
  color: var(--muted);
  font-weight: 600;
}

.pf-chip.is-good {
  background: var(--green-soft);
  color: var(--green-text);
}

.pf-stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(64px, auto));
  margin: 0;
}

.pf-stats > div {
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-end;
  padding: 0 14px;
  border-left: 1px solid var(--line);
}

.pf-stats > div:first-child {
  border-left: 0;
}

.pf-stats dt {
  color: var(--muted);
  font-size: var(--fs-caption);
  line-height: 1.3;
}

.pf-stats dd {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-h4);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
}

.profile-section a.primary-button,
.profile-section a.ghost-button {
  text-decoration: none;
}

.pf-menu {
  position: relative;
  flex: none;
}

.pf-menu-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.pf-menu-list {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 30;
  display: grid;
  gap: 2px;
  min-width: 240px;
  padding: 6px;
  border-radius: var(--radius-control);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.profile-section p.pf-menu-who {
  display: grid;
  gap: 1px;
  margin: 0 0 4px;
  padding: 6px 10px 8px;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-size: var(--fs-caption);
}

.pf-menu-who strong {
  color: var(--ink);
  font-size: var(--fs-body-sm);
  overflow-wrap: anywhere;
}

.pf-menu-list button {
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-body-sm);
  text-align: left;
  cursor: pointer;
}

.pf-menu-list button:hover,
.pf-menu-list button:focus-visible {
  background: var(--surface-strong);
}

.pf-menu-list button.is-danger {
  color: var(--mistake-text);
}

.pf-guest-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.pf-guest-tiles {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
}

.pf-tile {
  display: grid;
  align-content: start;
  gap: 6px;
}

.profile-section .pf-tile h3 {
  margin: 4px 0 0;
  font-size: var(--fs-body);
}

.profile-section .pf-tile p {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

/* --- Две колонки --- */
.pf-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 12px;
  align-items: start;
}

.pf-grid.is-single {
  grid-template-columns: minmax(0, 1fr);
}

.pf-col {
  display: grid;
  gap: 12px;
  min-width: 0;
}

/* Ваша игра */
.pf-phases {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.pf-phase {
  display: grid;
  gap: 2px;
  min-width: 0;
  padding: 8px 10px;
  border-radius: var(--radius-control);
  background: var(--surface-soft);
}

.pf-phase.is-weakest {
  background: var(--amber-soft);
}

.pf-phase-top {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 2px 6px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

.pf-weak {
  color: var(--amber-text);
  font-weight: 700;
}

.pf-phase strong {
  color: var(--ink);
  font-size: var(--fs-h4);
  font-variant-numeric: tabular-nums;
}

.pf-phase-meta {
  color: var(--muted);
  font-size: var(--fs-caption);
}

.pf-insights {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  margin-top: 8px;
}

.pf-insight {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
}

.pf-insight-text {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.pf-insight-text > span {
  color: var(--muted);
  font-size: var(--fs-caption);
}

.pf-insight-text strong {
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  line-height: 1.35;
}

/* Прогресс в задачах */
.pf-puzzles {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 16px;
  align-items: center;
}

.pf-ring-box {
  display: grid;
  justify-items: center;
  gap: 4px;
  max-width: 132px;
  text-align: center;
}

.pf-ring {
  position: relative;
  width: 96px;
  height: 96px;
}

.pf-ring svg {
  display: block;
}

.pf-ring strong {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--ink);
  font-size: var(--fs-h4);
}

.pf-bars {
  display: grid;
  gap: 6px;
}

.pf-bar {
  display: grid;
  grid-template-columns: minmax(70px, 110px) minmax(0, 1fr) 36px;
  gap: 8px;
  align-items: center;
  color: var(--ink);
  font-size: var(--fs-caption);
}

.pf-bar-track {
  height: 6px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--surface-strong);
}

.pf-bar-track > span {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
}

.pf-bar-value {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Активность */
.pf-month {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
  font-size: var(--fs-body-sm);
}

.pf-month-button {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 8px;
  background: var(--surface-strong);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-body);
  line-height: 1;
  cursor: pointer;
}

.pf-month-button:disabled {
  opacity: 0.4;
  cursor: default;
}

.pf-calendar {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 3px;
  text-align: center;
}

.pf-weekday {
  padding-bottom: 2px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

.pf-day {
  display: grid;
  place-items: center;
  height: 26px;
  border-radius: 7px;
  color: var(--ink);
  font-size: var(--fs-caption);
  font-variant-numeric: tabular-nums;
}

.pf-day.is-muted {
  color: var(--muted);
  opacity: 0.45;
}

.pf-day.is-played {
  background: color-mix(in srgb, var(--accent) 18%, var(--surface));
  color: var(--accent-text);
  font-weight: 700;
}

.pf-day.is-today {
  box-shadow: inset 0 0 0 1.5px var(--accent);
}

.profile-section p.pf-activity-foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 4px 10px;
  margin: 8px 0 0;
  color: var(--muted);
  font-size: var(--fs-caption);
}

.pf-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 6px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--accent) 18%, var(--surface));
  vertical-align: 0;
}

/* Последние партии */
.pf-games {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pf-game {
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto auto;
  gap: 10px;
  align-items: center;
  padding: 7px 0;
  border-top: 1px solid var(--line);
}

.pf-game:first-child {
  padding-top: 0;
  border-top: 0;
}

.pf-side {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px var(--muted);
}

.pf-side.is-white {
  background: #ffffff;
}

.pf-side.is-black {
  background: #1f2937;
}

.pf-game-text {
  display: grid;
  min-width: 0;
}

.pf-game-text strong,
.pf-game-text span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pf-game-text strong {
  color: var(--ink);
  font-size: var(--fs-body-sm);
}

.pf-game-text span {
  color: var(--muted);
  font-size: var(--fs-caption);
}

.pf-result {
  padding: 2px 8px;
  border-radius: 999px;
  font-size: var(--fs-caption);
  font-weight: 700;
}

.pf-result.is-win {
  background: var(--green-soft);
  color: var(--green-text);
}

.pf-result.is-draw {
  background: var(--surface-strong);
  color: var(--muted);
}

.pf-result.is-loss {
  background: var(--mistake-soft);
  color: var(--mistake-text);
}

.pf-open {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  color: var(--accent-text);
  cursor: pointer;
}

.pf-open:disabled {
  opacity: 0.5;
  cursor: progress;
}

.profile-section p.pf-open-error {
  margin: 8px 0 0;
  color: var(--mistake-text);
  font-size: var(--fs-caption);
}

/* Достижения */
.pf-achievements {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
}

.pf-badges {
  display: grid;
  flex: 1 1 480px;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.pf-badge {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  padding: 6px 10px;
  border: 1px dashed var(--line);
  border-radius: var(--radius-control);
}

.pf-badge.is-done {
  border-color: transparent;
  background: var(--amber-soft);
}

.pf-badge-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--surface-strong);
  color: var(--muted);
}

.pf-badge.is-done .pf-badge-icon {
  background: var(--surface);
  color: var(--amber-text);
}

.pf-badge-text {
  display: grid;
  min-width: 0;
  font-size: var(--fs-caption);
}

.pf-badge-text strong {
  color: var(--ink);
}

.pf-badge-text span {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Без подписки: силуэт карточки и замок */
.pf-skeleton {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
}

.pf-skeleton span {
  height: 46px;
  border-radius: var(--radius-control);
  background: var(--surface-strong);
}

.pf-locked {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 10px;
  padding: 12px;
  border-radius: var(--radius-control);
  background: var(--surface-soft);
}

.profile-section .pf-locked p {
  flex: 1 1 220px;
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-body-sm);
}

/* Подписка и ники */
.pf-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  align-items: start;
}

.pf-row > .pf-card {
  display: grid;
  align-content: start;
  gap: 10px;
}

.pf-ratings {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 10px;
  min-height: 16px;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 400;
}

.pf-rating strong {
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 1180px) {
  /* Цифры уходят на вторую строку, «Аккаунт» остаётся рядом с именем. */
  .pf-menu {
    order: 2;
  }

  .pf-stats {
    flex-basis: 100%;
    order: 3;
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .pf-stats > div:first-child {
    padding-left: 0;
  }
}

@media (max-width: 860px) {
  .pf-grid,
  .pf-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .pf-badges {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 560px) {
  .pf-header {
    gap: 10px 14px;
  }

  .pf-identity {
    flex-basis: 0;
  }

  .pf-guest-actions {
    flex-basis: 100%;
  }

  .pf-stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    row-gap: 8px;
  }

  .pf-stats > div:nth-child(3) {
    padding-left: 0;
    border-left: 0;
  }

  .pf-insights,
  .pf-guest-tiles,
  .profile-nicks {
    grid-template-columns: minmax(0, 1fr);
  }

  .pf-puzzles {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
  }

  .pf-bars {
    width: 100%;
  }

  .pf-menu-list {
    min-width: min(240px, calc(100vw - 48px));
  }
}

/* ===== Квесты одним экраном (U-68, 03.11.2026) =====
   По wireframe пользователя: заголовок, переключатель «Задачи / Сценарии»
   на всю ширину, строка из 7 карточек и доска с панелью справа. Обе
   вкладки устроены одинаково — общие классы внутри Квестов; с доской
   «Анализа» общих селекторов нет (Н-5). Цвета — токены темы. */
.quest-page-head {
  display: grid;
  gap: 4px;
}

.quest-page-head h2 {
  margin: 0;
  font-size: var(--fs-h2);
  line-height: 1.2;
}

.quest-page-head p {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.quest-mode-switch {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px;
  padding: 4px;
  border-radius: 12px;
  background: var(--bg);
  box-shadow: var(--shadow-soft);
}

.quest-mode-tab {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-height: 46px;
  padding: 0 16px;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-body);
  font-weight: 600;
  cursor: pointer;
}

.quest-mode-tab:hover {
  background: var(--surface-soft);
  color: var(--ink);
}

.quest-mode-tab.is-active {
  background: var(--accent);
  color: #ffffff;
}

.quest-mode-count {
  font-weight: 500;
  opacity: 0.8;
}

.quest-mode-panel {
  display: grid;
  gap: 14px;
}

.quest-mode-panel[hidden] {
  display: none;
}

/* Строка карточек: 7 тем задач или 7 сценариев. */
.quest-strip {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 8px;
}

.quest-strip-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 3px;
  min-width: 0;
  min-height: 92px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.quest-strip-card:hover {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
}

.quest-strip-card.is-selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}

.quest-strip-kicker {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.quest-strip-kicker .quest-tier-emblem {
  width: 20px;
  height: 20px;
}

.quest-strip-title {
  font-size: var(--fs-body-sm);
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.quest-strip-sub,
.quest-strip-status {
  color: var(--muted);
  font-size: var(--fs-caption);
  line-height: 1.3;
}

.quest-strip-status {
  margin-top: auto;
  font-variant-numeric: tabular-nums;
}

.quest-theme-bar {
  display: block;
  width: 100%;
  height: 5px;
  margin-top: auto;
  overflow: hidden;
  border-radius: 999px;
  background: var(--surface-strong);
}

.quest-theme-bar > span {
  display: block;
  width: 0;
  height: 100%;
  border-radius: inherit;
  background: var(--accent);
}

.quest-theme-bar + .quest-strip-status {
  margin-top: 0;
}

/* Без подписки: замок вместо полоски, рамка пунктиром. */
.quest-theme-lock {
  display: none;
  width: 14px;
  height: 14px;
  margin-left: auto;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 11h12v9H6z M8 11V8a4 4 0 0 1 8 0v3'/></svg>") center / contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 11h12v9H6z M8 11V8a4 4 0 0 1 8 0v3'/></svg>") center / contain no-repeat;
}

.quest-theme-card.is-locked {
  border-style: dashed;
}

.quest-theme-card.is-locked .quest-theme-lock {
  display: block;
}

.quest-theme-card.is-locked .quest-theme-bar {
  display: none;
}

.quest-theme-card.is-locked .quest-strip-status {
  margin-top: auto;
}

/* Заголовок над доской: название и строка под ним. */
.quest-detail-titles {
  display: grid;
  flex: 1 1 auto;
  gap: 2px;
  min-width: 0;
}

.quest-detail .quest-detail-titles h3 {
  margin: 0;
  font-size: clamp(1.25rem, 1.8vw, 1.6rem);
  line-height: 1.2;
}

.quest-detail p.quest-detail-meta {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 500;
}

/* Панель справа от доски: по содержимому, без подгонки под высоту доски. */
.quest-info-panel {
  gap: 12px;
}

.quest-turn {
  display: flex;
  align-items: center;
  gap: 12px;
}

.quest-turn-dot {
  flex: none;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: 0 0 0 2px var(--ink);
}

.quest-turn-dot[data-side="black"] {
  background: #111827;
  /* --muted, а не --line: в тёмной теме кольцо цвета линий сливалось с фоном. */
  box-shadow: 0 0 0 2px var(--muted);
}

.quest-turn-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent) 12%, var(--surface));
  color: var(--accent-text);
}

.quest-turn-icon.is-large {
  width: 48px;
  height: 48px;
  border-radius: 14px;
}

.quest-turn-text {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.quest-turn-text strong {
  font-size: var(--fs-h4);
  line-height: 1.25;
}

.quest-turn-text span {
  color: var(--muted);
  font-size: var(--fs-body-sm);
  line-height: 1.4;
}

.quest-line {
  flex: none;
}

.quest-line-steps {
  max-height: 320px;
}

.quest-line-next {
  align-self: flex-start;
}

.quest-board-actions[hidden] {
  display: none;
}

.quest-board-actions .ghost-button {
  flex: 1 1 0;
  justify-content: center;
  min-height: 44px;
}

/* Доска задач без задачи — начальная позиция, приглушённая. */
.quest-puzzle-frame.is-idle .quest-board {
  opacity: 0.5;
}

.quest-puzzle-view {
  display: grid;
  align-content: start;
  gap: 12px;
}

.quest-puzzle-view[hidden] {
  display: none;
}

.quest-puzzle-kinds {
  display: grid;
  gap: 6px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface-soft);
}

.quest-detail .quest-puzzle-kinds p,
.quest-detail p.quest-puzzle-note {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 400;
  line-height: 1.45;
}

.quest-puzzle-kinds strong {
  color: var(--ink);
}

.quest-detail p.quest-puzzle-note[hidden] {
  display: none;
}

.quest-puzzle-locked > strong {
  font-size: var(--fs-h4);
  line-height: 1.3;
}

.quest-detail .quest-puzzle-locked p {
  margin: 0;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 400;
  line-height: 1.5;
}

.quest-puzzle-locked-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.quest-puzzle-locked a {
  text-decoration: none;
}

.quest-puzzle-locked a[hidden] {
  display: none;
}

/* Ответ на ход в задаче: `.quest-detail p` в начале файла перебивал цвет. */
.quest-detail p.quest-tasks-feedback {
  color: var(--ink);
  font-weight: 600;
}

.quest-detail p.quest-tasks-feedback.is-success {
  color: var(--green-text);
}

.quest-detail p.quest-tasks-feedback.is-error {
  color: var(--mistake-text);
}

.quest-tasks-complete {
  padding: 16px 8px;
}

@media (max-width: 980px) {
  /* Телефон и планшет — отдельной задачей; пока строка листается. */
  .quest-strip {
    grid-template-columns: none;
    grid-auto-columns: minmax(140px, 1fr);
    grid-auto-flow: column;
    overflow-x: auto;
    padding-bottom: 4px;
  }
}

/* ===== «Квесты» помещаются в окно без прокрутки страницы (U-69, 03.11.2026) =====
   Правило пользователя: на «Главной», «Анализе», «Играть» и «Квестах»
   страница не прокручивается — всё видно сразу. Раньше доска 620 px шла
   под заголовком, переключателем и строкой карточек, и на 1440×900 её
   приходилось докручивать на 197–255 px.
   Теперь экран ровно в высоту окна: заголовок задачи — в правой колонке над
   панелью, карточки плотнее, а доска считается от оставшегося места
   (container-запросы: cqw/cqh — ширина и высота области доски). Длинная
   панель справа прокручивается сама. Телефон — отдельной задачей. */
@media (min-width: 981px) {
  body.page-quests .quests-section {
    /* .app-shell: 22px сверху и 28px снизу, шторки на десктопе нет. */
    height: calc(100dvh - 50px);
    margin: 0;
    gap: 10px;
    grid-template-rows: auto auto minmax(0, 1fr);
  }

  body.page-quests .quest-page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 12px;
  }

  body.page-quests .quest-mode-tab {
    min-height: 44px;
  }

  body.page-quests .quest-mode-panel {
    min-height: 0;
    grid-template-rows: auto minmax(0, 1fr);
    gap: 10px;
  }

  body.page-quests .quest-strip-card {
    min-height: 0;
    padding: 8px 10px;
    gap: 2px;
  }

  /* Тема задач: группа, название, пояснение, а полоска и «N из M» — одной строкой. */
  body.page-quests .quest-theme-card {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-content: start;
    align-items: center;
    column-gap: 8px;
    row-gap: 2px;
  }

  body.page-quests .quest-theme-card > .quest-strip-kicker,
  body.page-quests .quest-theme-card > .quest-strip-title,
  body.page-quests .quest-theme-card > .quest-strip-sub {
    grid-column: 1 / -1;
  }

  body.page-quests .quest-theme-card > .quest-strip-sub {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  body.page-quests .quest-theme-card > .quest-theme-bar,
  body.page-quests .quest-theme-card > .quest-strip-status {
    margin-top: 4px;
  }

  body.page-quests .quest-theme-card.is-locked > .quest-strip-status {
    grid-column: 1 / -1;
  }

  /* Доска слева на всю высоту, справа — заголовок задачи и панель. */
  body.page-quests .quest-detail {
    min-height: 0;
    grid-template-columns: minmax(0, 1fr) clamp(320px, 30vw, 620px);
    grid-template-rows: auto minmax(0, 1fr);
    gap: 12px 16px;
    padding: 16px;
  }

  body.page-quests .quest-detail-grid {
    display: contents;
  }

  body.page-quests .quest-detail-head {
    grid-column: 2;
    grid-row: 1;
  }

  body.page-quests .quest-board-panel {
    grid-column: 1;
    grid-row: 1 / -1;
    min-height: 0;
    grid-template-rows: minmax(0, 1fr) auto;
  }

  body.page-quests .quest-info-panel {
    grid-column: 2;
    grid-row: 2;
    min-height: 0;
    overflow-y: auto;
  }

  /* Сторона доски — меньшее из ширины и высоты области (минус подписи
     координат: 16 px по горизонтали, 32 по вертикали, и шкала оценки у
     сценариев), не больше 720 и не меньше 240. */
  body.page-quests .quest-board-live {
    --quest-aside: 0px;
    --quest-board-size: max(240px, min(calc(100cqw - var(--quest-aside) - 16px), calc(100cqh - 32px), 720px));
    container-type: size;
    min-height: 0;
    align-items: center;
  }

  body.page-quests .quest-board-live:has(.quest-eval-bar-wrap) {
    --quest-aside: 48px;
  }

  body.page-quests .quest-board-live .quest-board-frame {
    flex: none;
    width: var(--quest-board-size);
  }

  body.page-quests .quest-eval-bar-wrap {
    height: calc(var(--quest-board-size) + 32px);
  }

  /* Место под «Подсказка / Пропустить» держится и без задачи, иначе при
     выборе темы доска уменьшалась бы на высоту кнопок. visibility, а не
     display: скрытые кнопки не видны и не попадают в Tab. */
  body.page-quests .quest-board-actions[hidden] {
    display: flex;
    visibility: hidden;
  }
}

#panelPlan,
#secondantUnlocked {
  min-width: 0;
}

/* ===== Окно Секунданта — окно чата (U-70, 03.11.2026) =================
   По макету, утверждённому пользователем 03.11.2026. Переключатель
   персонажа — маленький, над окном, без подписи. В окне: шапка со
   статусом («Итог партии» / «● В эфире · ваш ход …»), сообщение «в
   эфире», лента вопросов и ответов, готовые вопросы и поле с кнопкой-
   иконкой. Высоту окна под колонку задаёт fitSecondantChat() в app.js:
   поле вопроса всегда на виду, «Фазы» и «Стиль» — прокруткой колонки. */
.secondant-persona {
  display: flex;
  width: max-content;
  margin: 0 0 8px auto;
}

.secondant-persona[hidden] {
  display: none;
}

/* Как на макете: светлая плашка вокруг, выбранный — мягкая синяя заливка,
   а не чёрная (тот переключатель был крупнее и спорил с окном). */
.secondant-persona {
  padding: 2px;
  border-color: var(--line);
  background: var(--bg);
}

.secondant-persona .persona-button {
  min-height: 28px;
  padding: 0 10px;
  border-right: 0;
  border-radius: calc(var(--radius-control) - 2px);
  font-size: var(--fs-caption);
}

.secondant-persona .persona-button.is-active {
  background: color-mix(in srgb, var(--blue) 12%, var(--bg));
  color: var(--blue-dark);
}

.secondant-chat {
  /* Заливка вашего сообщения и кнопки «отправить» — тёмный акцент:
     белый текст на #3D5AF1 — 5.34:1, на #4F6BFF было бы 4.30. В тёмной
     теме тот же цвет: светлый --blue тёмной темы белый текст не держит. */
  --secondant-fill: #3D5AF1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
}

.secondant-chat-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
}

.secondant-chat-avatar {
  flex: none;
  width: 36px;
  height: 36px;
  overflow: hidden;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 6%, var(--bg));
}

.secondant-chat-avatar > span {
  display: block;
  height: 100%;
}

.secondant-chat-avatar > span[hidden] {
  display: none;
}

.secondant-chat-avatar svg {
  display: block;
  width: 100%;
  height: 100%;
}

.secondant-chat-name {
  flex: none;
  color: var(--ink);
  font-size: var(--fs-body);
}

/* Вернуться к итогу с любого хода. */
.secondant-summary-link {
  flex: none;
  margin-left: auto;
  padding: 3px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-caption);
  font-weight: 600;
  cursor: pointer;
}

.secondant-summary-link[hidden] {
  display: none;
}

.secondant-summary-link:hover {
  border-color: var(--blue);
  color: var(--blue-dark);
}

.secondant-summary-link:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
}

.secondant-summary-link:not([hidden]) + .secondant-chat-status {
  margin-left: 0;
}

.secondant-chat-status {
  min-width: 0;
  margin-left: auto;
  padding: 3px 10px;
  overflow: hidden;
  border-radius: 999px;
  background: color-mix(in srgb, var(--ink) 7%, var(--bg));
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.secondant-chat-status.is-live {
  background: var(--an-bad-soft);
  color: color-mix(in srgb, var(--an-bad) 85%, var(--ink));
}

.secondant-chat-status i {
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 6px;
  border-radius: 50%;
  background: currentColor;
  vertical-align: 0;
}

.secondant-chat-body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 12px;
  min-height: 0;
  padding: 14px 16px;
  overflow-y: auto;
}

/* Подписчик без ника: один вопрос, чтобы память заработала. */
.secondant-nick-ask {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-control);
  font-size: var(--fs-body-sm);
}

.secondant-nick-ask[hidden] {
  display: none;
}

.secondant-nick-ask p {
  flex: 1 1 100%;
  margin: 0;
  color: var(--ink);
  line-height: 1.45;
}

.secondant-nick-ask .ai-error {
  padding: 6px 10px;
}

/* Сообщение «в эфире» и ответы Секунданта — слева, мягкая заливка. */
.secondant-live,
.secondant-msg.is-bot .secondant-msg-bubble {
  border-radius: 4px 14px 14px 14px;
  background: color-mix(in srgb, var(--blue) 7%, var(--bg));
  color: var(--ink);
}

.secondant-live {
  display: grid;
  gap: 10px;
  align-self: flex-start;
  max-width: 92%;
  padding: 12px 14px;
  font-size: var(--fs-body-sm);
  line-height: 1.5;
}

.secondant-live:empty {
  display: none;
}

.secondant-live p {
  margin: 0;
}

.secondant-live .ai-error {
  margin: 0;
}

.secondant-lead {
  font-weight: 700;
}

.secondant-live-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.secondant-live-head strong {
  font-size: var(--fs-body);
}

/* Оценка хода — та же палитра, что у «Статистики ходов». Текст на четверть
   смешан с цветом текста страницы: так он держит 4.5:1 на своей заливке у
   всех оценок, минимум 5.08 (замер 03.11.2026, обе темы). */
.secondant-quality {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--q-soft, var(--quality-good-soft));
  color: color-mix(in srgb, var(--q-text, var(--quality-good-text)) 75%, var(--ink));
  font-size: var(--fs-caption);
  font-weight: 700;
}

.secondant-quality[data-tone="brilliant"] { --q-soft: var(--quality-brilliant-soft); --q-text: var(--quality-brilliant-text); }
.secondant-quality[data-tone="excellent"] { --q-soft: var(--quality-excellent-soft); --q-text: var(--quality-excellent-text); }
.secondant-quality[data-tone="good"] { --q-soft: var(--quality-good-soft); --q-text: var(--quality-good-text); }
.secondant-quality[data-tone="ok"] { --q-soft: var(--quality-ok-soft); --q-text: var(--quality-ok-text); }
.secondant-quality[data-tone="book"] { --q-soft: color-mix(in srgb, var(--quality-book-text) 14%, var(--surface)); --q-text: var(--quality-book-text); }
.secondant-quality[data-tone="missed"] { --q-soft: var(--quality-missed-soft); --q-text: var(--quality-missed-text); }
.secondant-quality[data-tone="inaccuracy"] { --q-soft: var(--quality-inaccuracy-soft); --q-text: var(--quality-inaccuracy-text); }
.secondant-quality[data-tone="mistake"] { --q-soft: var(--quality-mistake-soft); --q-text: var(--quality-mistake-text); }
.secondant-quality[data-tone="blunder"] { --q-soft: var(--quality-blunder-soft); --q-text: var(--quality-blunder-text); }

/* «Что тренировать» под итогом партии. Подпись — цветом текста, а не
   серым: серый на заливке сообщения давал 4.43:1, меньше нормы. */
.secondant-live .secondant-train {
  display: grid;
  gap: 2px;
}

.secondant-live .secondant-train-title {
  font-size: var(--fs-caption);
  font-weight: 700;
}

/* Одна кнопка вместо ходов-кнопок: лучший ход и линия движка за ним
   разыгрываются на доске. */
.secondant-show-best {
  display: inline-flex;
  align-items: center;
  justify-self: start;
  gap: 6px;
  min-height: 36px;
  padding: 0 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-control);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-body-sm);
  font-weight: 600;
  cursor: pointer;
}

.secondant-show-best svg {
  width: 12px;
  height: 12px;
}

.secondant-show-best:hover,
.secondant-show-best[aria-pressed="true"] {
  border-color: var(--blue);
  color: var(--blue-dark);
}

.secondant-show-best:focus-visible,
.secondant-send:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 2px;
}

/* Лента вопросов и ответов: ваш вопрос — справа, с меткой хода. */
.secondant-thread {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.secondant-thread[hidden] {
  display: none;
}

.secondant-msg {
  display: flex;
  flex-direction: column;
  gap: 3px;
  max-width: 88%;
}

.secondant-msg.is-user {
  align-self: flex-end;
  align-items: flex-end;
}

.secondant-msg.is-bot {
  align-self: flex-start;
}

.secondant-msg-tag {
  color: var(--muted);
  font-size: var(--fs-caption);
}

.secondant-msg-bubble {
  padding: 10px 12px;
  font-size: var(--fs-body-sm);
  line-height: 1.5;
}

.secondant-msg-bubble p {
  margin: 0 0 8px;
  color: inherit;
}

.secondant-msg-bubble p:last-child {
  margin-bottom: 0;
}

.secondant-msg.is-user .secondant-msg-bubble {
  border-radius: 14px 4px 14px 14px;
  background: var(--secondant-fill);
  color: #ffffff;
}

.secondant-msg .ai-error {
  margin: 0;
}

/* Готовые вопросы и поле — внизу окна. */
.secondant-chat-foot {
  display: grid;
  /* Без minmax(0, …) строка готовых вопросов в одну линию растягивала окно
     шире колонки, и на 1024 px появлялась горизонтальная прокрутка. */
  grid-template-columns: minmax(0, 1fr);
  gap: 10px;
  padding: 10px 16px 14px;
  border-top: 1px solid var(--line);
}

.secondant-chat-foot[hidden] {
  display: none;
}

.secondant-chat-foot .ai-chat-suggestions {
  gap: 6px;
}

.secondant-chat-foot .ai-chip {
  min-height: 32px;
  padding: 0 12px;
  border-radius: 999px;
}

.secondant-chat.is-compact .secondant-chat-foot .ai-chat-suggestions {
  flex-wrap: nowrap;
  padding-bottom: 4px;
  overflow-x: auto;
  scrollbar-width: thin;
}

.secondant-chat.is-compact .secondant-chat-foot .ai-chip {
  flex: none;
  white-space: nowrap;
}

.secondant-chat-foot .ai-quota {
  margin: 0;
}

.secondant-chat-foot .ai-quota[hidden] {
  display: none;
}

.secondant-chat-form {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}

/* Поле растёт по тексту: одна строка, длинный вопрос — до пяти строк. */
.secondant-chat-form textarea {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  max-height: 140px;
  padding: 11px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-control);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-body-sm);
  field-sizing: content;
  resize: none;
}

.secondant-chat-form textarea:focus {
  outline: 2px solid color-mix(in srgb, var(--blue) 55%, transparent);
  outline-offset: 1px;
}

.secondant-send {
  display: grid;
  flex: none;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: var(--radius-control);
  background: var(--secondant-fill);
  color: #ffffff;
  cursor: pointer;
}

.secondant-send svg {
  width: 18px;
  height: 18px;
}

.secondant-send:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* Узкая колонка: рядом с кнопкой итога статус сжимался до «● В…». Имя
   здесь можно убрать, вкладка и так называется «Секундант». */
@media (max-width: 1180px) {
  .secondant-chat-name {
    display: none;
  }
}

/* Телефон: колонка не ограничена по высоте, окно — по своему
   содержимому, лента прокручивается внутри. */
@media (max-width: 980px) {
  .secondant-chat-body {
    max-height: 60vh;
  }
}

/* В узкой колонке подпись шага переносится целиком, а не посреди слова. */
.loader-steps {
  flex-wrap: wrap;
  gap: 2px 10px;
}

.loader-steps span {
  white-space: nowrap;
}

@media (max-width: 980px) {
  .loader-foot button,
  .analysis-error button {
    min-height: 44px;
  }
}

/* Разбор считается или уже готов, а человек в другом разделе: полосы загрузки
   там нет, поэтому точка на пункте «Анализ» (05.11.2026, UX-исследование №10). */
.nav-tab[data-nav="analysis"],
.nav-tab[data-nav="play"] {
  position: relative;
}

/* Идёт партия: красная точка, часы не ждут. */
body.has-live-game:not(.page-play) .app-rail-item[data-rail="play"]::after,
body.has-live-game:not(.page-play) .nav-tab[data-nav="play"]::after {
  background: var(--mistake);
}

body.is-loading-analysis:not(.page-analysis) .app-rail-item[data-rail="analysis"]::after,
body.is-loading-analysis:not(.page-analysis) .nav-tab[data-nav="analysis"]::after,
body.has-unseen-analysis .app-rail-item[data-rail="analysis"]::after,
body.has-unseen-analysis .nav-tab[data-nav="analysis"]::after,
body.has-live-game:not(.page-play) .app-rail-item[data-rail="play"]::after,
body.has-live-game:not(.page-play) .nav-tab[data-nav="play"]::after {
  content: "";
  position: absolute;
  top: 6px;
  right: 14px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 0 0 2px var(--bg);
}

body.is-loading-analysis:not(.page-analysis) .app-rail-item[data-rail="analysis"]::after,
body.is-loading-analysis:not(.page-analysis) .nav-tab[data-nav="analysis"]::after {
  animation: analysis-busy-dot 1.4s ease-in-out infinite;
}

@keyframes analysis-busy-dot {
  50% {
    opacity: 0.35;
  }
}

@media (prefers-reduced-motion: reduce) {
  body.is-loading-analysis:not(.page-analysis) .app-rail-item[data-rail="analysis"]::after,
  body.is-loading-analysis:not(.page-analysis) .nav-tab[data-nav="analysis"]::after {
    animation: none;
  }
}

/* Выпадающий список языков: появляется с третьего языка. */
.language-select {
  min-height: 32px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.app-rail-lang.language-select {
  width: 64px;
}

/* Длинные языки: содержимое не шире колонки, текст кнопки и заголовка переносится,
   только если не помещается. Русский и английский помещаются. */
.board-side-scroll > * {
  min-width: 0;
}

.secondant-teaser .primary-button {
  flex: none;
  max-width: 60%;
  white-space: normal;
}

.panel-heading-title {
  flex: 1 1 0;
  min-width: 0;
}

/* В колонке 330 px карточке не хватает места на кнопку в строку: текст зажимало
   до 81 px, и панель вылезала на 19 px. */
@media (min-width: 981px) and (max-width: 1180px) {
  .secondant-teaser {
    flex-wrap: wrap;
  }

  .secondant-teaser-body {
    flex: 1 1 0;
  }

  .secondant-teaser .primary-button {
    width: 100%;
    max-width: none;
  }
}

/* Значок «?» у терминов ведёт в справку (UX-исследование №24). */
.term-help {
  display: inline-grid;
  place-items: center;
  width: 20px;
  height: 20px;
  margin-left: 4px;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  line-height: 1;
  text-decoration: none;
  vertical-align: 2px;
}

.term-help:hover {
  border-color: var(--blue);
  color: var(--blue);
}

.term-help:focus-visible,
.board-settings-link:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

.board-settings-link {
  display: block;
  margin-top: 10px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

/* Профиль без партий (UX-исследование №31). */
.profile-section.needs-nick .pf-title,
.profile-section.needs-nick #pfHeader {
  order: -2;
}

.profile-section.needs-nick #pfSettings {
  order: -1;
}

.profile-section.needs-nick #profileMemoryCard {
  order: -1;
}

.pf-start {
  display: grid;
  gap: 8px;
  margin: 0 0 14px;
  padding-left: 20px;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  line-height: 1.45;
}

.pf-start .is-done {
  color: var(--muted);
  text-decoration: line-through;
}

/* ═══ Телефонный вид (U-83, 06.11.2026) ═════════════════════════════════════
   При ширине до 980 px сайт устроен как приложение: компактная шапка сверху,
   нижнее меню из пяти разделов, на экране один раздел. Шире 980 px этих
   элементов нет, и компьютерная версия с левым меню не меняется.
   Контакты, Справка, Тарифы, язык и тема на телефоне — в Профиле, блок «Ещё». */
.app-tabbar,
.topbar-title,
.profile-more {
  display: none;
}

@media (max-width: 980px) {
  :root {
    --mob-gutter: 12px;
    --mob-header-h: 52px;
    --mob-tabbar-h: 58px;
    --mob-safe-bottom: env(safe-area-inset-bottom, 0px);
    --mob-bottom-space: calc(var(--mob-tabbar-h) + var(--mob-safe-bottom));
  }

  /* Прокрутка к элементу не должна уводить его под нижнее меню. */
  html {
    scroll-padding-bottom: calc(var(--mob-bottom-space) + 8px);
  }

  .app-shell {
    padding: 0 var(--mob-gutter) calc(var(--mob-bottom-space) + 8px);
  }

  .pricing-page,
  .auth-page {
    padding-bottom: calc(var(--mob-bottom-space) + 16px);
  }

  /* Шапка: на всю ширину, прилипает к верху, учитывает вырез экрана. */
  .topbar {
    position: sticky;
    top: 0;
    left: auto;
    z-index: 40;
    width: auto;
    max-width: none;
    display: flex;
    align-items: center;
    min-height: calc(var(--mob-header-h) + env(safe-area-inset-top, 0px));
    margin: 0 calc(-1 * var(--mob-gutter)) 8px;
    padding: env(safe-area-inset-top, 0px) max(var(--mob-gutter), env(safe-area-inset-right, 0px)) 0 max(var(--mob-gutter), env(safe-area-inset-left, 0px));
    flex-wrap: nowrap;
    justify-content: flex-start;
    gap: 10px;
    border-radius: 0;
    box-shadow: inset 0 -1px 0 var(--an-card-border);
    transform: none;
  }

  /* На отдельных страницах шапка не внутри .app-shell — выносить её за поля не нужно. */
  .pricing-page > .topbar,
  .auth-page > .topbar {
    margin: 0 0 12px;
    justify-content: space-between;
  }

  .topbar .brand {
    flex: 0 1 auto;
    gap: 8px;
  }

  .topbar .brand-mark {
    width: 36px;
    height: 32px;
  }

  .topbar .brand-wordmark small {
    display: none;
  }

  /* Разделы — в нижнем меню, тема и язык — в Профиле. */
  .app-shell > .topbar .top-nav,
  .app-shell > .topbar .top-actions {
    display: none;
  }

  /* Вне главной в шапке название раздела вместо надписи GOOD+SOLUTION. */
  body:not(.page-home) .app-shell > .topbar .brand-wordmark {
    display: none;
  }

  .topbar-title {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    overflow: hidden;
    color: var(--ink);
    font-size: 18px;
    font-weight: 800;
    line-height: 1.2;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  body.page-home .topbar-title {
    display: none;
  }

  /* Название раздела уже в шапке — второй такой же заголовок не нужен. */
  .pf-title {
    display: none;
  }

  /* Нижнее меню. */
  .app-tabbar {
    position: fixed;
    right: 0;
    bottom: 0;
    left: 0;
    z-index: 45;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    box-sizing: border-box;
    height: var(--mob-bottom-space);
    padding: 0 max(4px, env(safe-area-inset-right, 0px)) var(--mob-safe-bottom) max(4px, env(safe-area-inset-left, 0px));
    background: var(--bg);
    box-shadow: 0 -1px 0 var(--an-card-border);
  }

  .app-tabbar-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-width: 0;
    min-height: 48px;
    color: var(--muted);
    font-size: 11px;
    font-weight: 600;
    line-height: 14px;
    text-decoration: none;
    -webkit-tap-highlight-color: transparent;
  }

  .app-tabbar-item > span:last-child {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .app-tabbar-icon {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 28px;
    border-radius: 14px;
    transition: background-color 150ms ease;
  }

  .app-tabbar-icon svg {
    width: 22px;
    height: 22px;
  }

  .app-tabbar-item.is-active {
    color: var(--blue-dark);
    font-weight: 700;
  }

  .app-tabbar-item.is-active .app-tabbar-icon {
    background: color-mix(in srgb, var(--blue) 15%, transparent);
  }

  .app-tabbar-item:focus-visible {
    outline: 3px solid color-mix(in srgb, var(--blue) 34%, transparent);
    outline-offset: -3px;
    border-radius: 12px;
  }

  /* Точки те же, что в левом меню: разбор считается или готов, пока вы в другом
     разделе; партия идёт, пока вы не на «Играть». */
  body.is-loading-analysis:not(.page-analysis) .app-tabbar-item[data-rail="analysis"] .app-tabbar-icon::after,
  body.has-unseen-analysis .app-tabbar-item[data-rail="analysis"] .app-tabbar-icon::after,
  body.has-live-game:not(.page-play) .app-tabbar-item[data-rail="play"] .app-tabbar-icon::after {
    content: "";
    position: absolute;
    top: 1px;
    right: 11px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--blue);
    box-shadow: 0 0 0 2px var(--bg);
  }

  body.has-live-game:not(.page-play) .app-tabbar-item[data-rail="play"] .app-tabbar-icon::after {
    background: var(--mistake);
  }

  body.is-loading-analysis:not(.page-analysis) .app-tabbar-item[data-rail="analysis"] .app-tabbar-icon::after {
    animation: analysis-busy-dot 1.4s ease-in-out infinite;
  }

  /* Плашки «Разбор готов», «Отменить», «партия идёт» — над нижним меню. */
  .undo-toast {
    right: 12px;
    bottom: calc(var(--mob-bottom-space) + 12px);
    left: 12px;
  }

  /* Профиль › «Ещё». */
  .profile-more {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 4px;
  }

  .profile-more-title {
    margin: 0;
    padding: 0 4px;
    color: var(--muted);
    font-size: 13px;
    font-weight: 700;
  }

  .profile-more-list {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border-radius: var(--radius);
    background: var(--bg);
    box-shadow: var(--shadow);
  }

  .profile-more-item {
    display: flex;
    align-items: center;
    gap: 12px;
    box-sizing: border-box;
    width: 100%;
    min-height: 52px;
    padding: 0 14px;
    border: 0;
    border-bottom: 1px solid var(--an-card-border);
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-size: 15px;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
  }

  .profile-more-item:last-child {
    border-bottom: 0;
  }

  .profile-more-item.is-static {
    cursor: default;
  }

  .profile-more-item[hidden] {
    display: none;
  }

  .profile-more-item.profile-more-note {
    display: block;
    min-height: 0;
    margin: 0;
    padding: 12px 14px;
    color: var(--muted);
    font-size: 14px;
    line-height: 1.4;
  }

  .profile-more-item.profile-more-note[hidden] {
    display: none;
  }

  .profile-more-item > span:first-child {
    flex: 1 1 auto;
  }

  .profile-more-chevron {
    flex: none;
    width: 18px;
    height: 18px;
    color: var(--muted-faint);
  }

  .profile-more-lang {
    flex: none;
    margin: 0;
  }

  .profile-more-switch {
    position: relative;
    flex: none;
    width: 44px;
    height: 26px;
    border-radius: 13px;
    background: color-mix(in srgb, var(--muted) 45%, transparent);
    transition: background-color 150ms ease;
  }

  .profile-more-switch::after {
    content: "";
    position: absolute;
    top: 3px;
    left: 3px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #ffffff;
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.25);
    transition: transform 150ms ease;
  }

  [data-theme-switch][aria-pressed="true"] .profile-more-switch {
    background: var(--blue);
  }

  [data-theme-switch][aria-pressed="true"] .profile-more-switch::after {
    transform: translateX(18px);
  }
}

@media (max-width: 980px) and (prefers-reduced-motion: reduce) {
  body.is-loading-analysis:not(.page-analysis) .app-tabbar-item[data-rail="analysis"] .app-tabbar-icon::after {
    animation: none;
  }
}

/* ── Телефон: экран «Анализ» (U-83) ───────────────────────────────────────────
   Сверху вниз: вкладки, полоса загрузки и ошибка, игроки, доска, кнопки ходов,
   панель вкладки. Обёртки доски и боковой колонки на телефоне растворяются
   (display: contents), и их части выстраиваются одним столбцом. На «Партии»
   доски нет: экран отдан выбору партии. В окне выше 520 px (любой телефон в
   портретном положении) экран не прокручивается: доска закреплена, прокручивается
   только панель, её можно развернуть на место доски. В низком окне (телефон
   боком) прокручивается весь экран, а вкладки прилипают к шапке. Переворот доски
   и её настройки — в шапке. */
@media (max-width: 980px) {
  body.page-analysis {
    --an-board: calc(100vw - 2 * var(--mob-gutter) - 28px);
  }

  body.page-analysis .analysis-stage {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
  }

  body.page-analysis .analysis-stage .board-with-side,
  body.page-analysis .analysis-stage .board-zone,
  body.page-analysis .analysis-stage .board-side {
    display: contents;
  }

  body.page-analysis .analysis-tabs {
    order: 1;
    position: sticky;
    top: calc(var(--mob-header-h) + env(safe-area-inset-top, 0px));
    z-index: 5;
    margin: 0;
    background: var(--page);
  }

  body.page-analysis .analysis-loader,
  body.page-analysis .analysis-error {
    order: 2;
    margin: 0;
  }

  body.page-analysis #playerStrip {
    order: 3;
  }

  body.page-analysis .board-live {
    order: 4;
  }

  body.page-analysis .board-branch-banner,
  body.page-analysis #boardLiveLine {
    order: 5;
    margin: 0;
  }

  body.page-analysis .board-controls {
    order: 6;
  }

  body.page-analysis .board-side-scroll {
    order: 7;
    box-sizing: border-box;
    padding: 12px;
    border-radius: 16px;
    background: var(--bg);
    box-shadow: var(--shadow);
  }

  /* На «Партии» доски нет — весь экран под выбор партии. */
  body.page-analysis[data-report-tab="game"] .analysis-stage #playerStrip,
  body.page-analysis[data-report-tab="game"] .analysis-stage .board-live,
  body.page-analysis[data-report-tab="game"] .analysis-stage .board-controls,
  body.page-analysis[data-report-tab="game"] .analysis-stage .board-branch-banner,
  body.page-analysis[data-report-tab="game"] .analysis-stage #boardLiveLine {
    display: none;
  }

  /* Игроки — одной строкой: имя, точность и перформанс. */
  body.page-analysis #playerStrip {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    gap: 6px;
    align-items: stretch;
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
  }

  body.page-analysis #playerStrip.is-hidden {
    display: none;
  }

  body.page-analysis #playerStrip .player-side {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
    min-width: 0;
    padding: 6px 10px;
    border-radius: 12px;
  }

  body.page-analysis #playerStrip .player-side.is-black {
    align-items: flex-end;
    text-align: right;
  }

  body.page-analysis #playerStrip .player-card {
    display: block;
    min-width: 0;
    min-height: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
  }

  body.page-analysis #playerStrip .player-card img,
  body.page-analysis #playerStrip .player-card span,
  body.page-analysis #playerStrip .player-card small,
  body.page-analysis #playerStrip .score-card-icon,
  body.page-analysis #playerStrip #matchSource {
    display: none;
  }

  body.page-analysis #playerStrip .player-card strong {
    display: block;
    overflow: hidden;
    font-size: 14px;
    line-height: 18px;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  body.page-analysis #playerStrip .score-card-row {
    display: flex;
    flex-direction: column;
    gap: 0;
    width: auto;
    margin: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
  }

  body.page-analysis #playerStrip .score-card {
    display: block;
    flex: none;
    min-height: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
  }

  body.page-analysis #playerStrip .score-card-body {
    display: flex;
    align-items: baseline;
    gap: 4px;
  }

  body.page-analysis #playerStrip .is-black .score-card-body {
    justify-content: flex-end;
  }

  body.page-analysis #playerStrip .score-card-body span {
    font-size: 11px;
    line-height: 16px;
  }

  body.page-analysis #playerStrip .score-card-body strong {
    font-size: 13px;
    line-height: 16px;
  }

  body.page-analysis #playerStrip .versus-block {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-width: 0;
    padding: 0 2px;
  }

  body.page-analysis #playerStrip #matchOutcome {
    font-size: 10px;
    line-height: 12px;
    text-align: center;
  }

  /* Доска на всю ширину, оценка — узкой полосой слева. */
  body.page-analysis .board-live {
    align-items: stretch;
    justify-content: center;
    gap: 6px;
  }

  body.page-analysis .eval-bar-wrap {
    width: 22px;
    gap: 4px;
  }

  body.page-analysis .eval-bar {
    width: 10px;
    min-height: 0;
  }

  body.page-analysis .eval-bar-score {
    font-size: 10px;
  }

  body.page-analysis .analysis-stage .chess-board-frame {
    width: var(--an-board);
    margin: 0;
    border-radius: 12px;
  }

  /* Буквы и цифры — внутри доски, у края: снаружи им некуда встать. */
  body.page-analysis .analysis-stage .board-coordinate {
    color: rgba(0, 0, 0, 0.62);
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    text-shadow: 0 0 2px rgba(255, 255, 255, 0.75);
  }

  body.page-analysis .analysis-stage .board-coordinate.is-file {
    bottom: 2px;
  }

  body.page-analysis .analysis-stage .board-coordinate.is-rank {
    left: 3px;
  }

  /* Кнопки ходов — одной строкой, выбор хода занимает остаток. */
  body.page-analysis .analysis-stage .board-controls {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: space-between;
    gap: 4px;
    margin: 0;
  }

  body.page-analysis .analysis-stage .an-nav {
    flex: none;
    gap: 4px;
  }

  body.page-analysis .analysis-stage .board-controls-below .an-nav .icon-button {
    width: 40px;
    height: 40px;
    min-height: 40px;
  }

  body.page-analysis .analysis-stage .an-nav-right {
    flex: 1 1 auto;
    min-width: 0;
  }

  body.page-analysis .analysis-stage .an-ply-select {
    flex: 1 1 auto;
    width: 100%;
    min-width: 0;
    height: 40px;
  }

  /* Перевернуть и настройки доски — в шапке, справа от названия. */
  body.page-analysis .analysis-stage #flipButton,
  body.page-analysis .analysis-stage .board-settings {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 4px);
    z-index: 41;
  }

  body.page-analysis .analysis-stage #flipButton {
    right: calc(max(var(--mob-gutter), env(safe-area-inset-right, 0px)) + 44px);
  }

  body.page-analysis .analysis-stage .board-settings {
    right: max(calc(var(--mob-gutter) - 6px), env(safe-area-inset-right, 0px));
  }

  body.page-analysis .analysis-stage .board-controls-below #flipButton,
  body.page-analysis .analysis-stage .board-controls-below .board-settings-button {
    width: 44px;
    height: 44px;
    min-height: 44px;
    border: 0;
    background: transparent;
    color: var(--ink);
  }

  body.page-analysis .analysis-stage .board-settings-panel {
    top: calc(100% + 6px);
    bottom: auto;
  }
}

@media (max-width: 980px) and (min-height: 520px) {
  body.page-analysis .app-shell {
    padding-bottom: calc(var(--mob-bottom-space) + 8px);
  }

  /* Экран не прокручивается, поэтому вкладкам прилипать некуда. */
  body.page-analysis .analysis-tabs {
    position: relative;
    top: auto;
  }

  body.page-analysis .analysis-stage {
    height: calc(100vh - var(--mob-header-h) - 16px - var(--mob-bottom-space));
    height: calc(100dvh - var(--mob-header-h) - env(safe-area-inset-top, 0px) - 16px - var(--mob-bottom-space));
    overflow: hidden;
  }

  body.page-analysis .board-side-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* Доска не больше, чем оставляет панели под ней 100 px (дальше панель
     разворачивается ручкой), и не меньше 200 px. */
  body.page-analysis {
    --an-board: max(200px, min(calc(100vw - 2 * var(--mob-gutter) - 28px), calc(100dvh - 370px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px))));
  }
}

/* На «Секунданте» доска меньше: нужно место окну разговора. */
@media (max-width: 980px) {
  body.page-analysis[data-report-tab="plan"] {
    --an-board: min(62vw, 250px);
  }
}

@media (max-width: 980px) {
  /* Откуда партия — одной строкой из трёх, как вкладки. */
  body.page-analysis .game-panel > .segmented {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  body.page-analysis .game-panel > .segmented .segment {
    min-width: 0;
    padding: 0 6px;
    font-size: 13px;
    line-height: 1.15;
    white-space: normal;
    text-align: center;
  }
}

@media (max-width: 980px) {
  /* Название раздела не заходит под кнопки доски в шапке. */
  body.page-analysis:not([data-report-tab="game"]) .topbar-title {
    padding-right: 92px;
  }
}

/* ── Телефон: экран «Играть» (U-83) ──────────────────────────────────────────
   Лобби: переключатель «С человеком / С компьютером» вместо двух карточек подряд.
   Партия: всё в одном окне — соперник, доска, вы, ходы одной строкой, кнопки. */
.play-mode-switch {
  display: none;
}

@media (max-width: 980px) {
  /* Название раздела уже в шапке. */
  body.page-play .play-topbar {
    display: none;
  }

  .play-mode-switch {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2px;
    margin-bottom: 12px;
    padding: var(--switch-pad);
    border-radius: calc(var(--switch-radius) + var(--switch-pad));
    background: var(--switch-track);
  }

  .play-mode-tab {
    min-height: 40px;
    border: 0;
    border-radius: var(--switch-radius);
    background: transparent;
    color: var(--muted);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
  }

  .play-mode-tab.is-active {
    background: var(--bg);
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12);
    color: var(--blue-dark);
    font-weight: 700;
  }

  .play-mode-tab:focus-visible {
    outline: 3px solid color-mix(in srgb, var(--blue) 34%, transparent);
    outline-offset: 1px;
  }

  .play-lobby[data-mode="human"] .play-mode[aria-labelledby="playBotTitle"],
  .play-lobby[data-mode="bot"] .play-mode[aria-labelledby="playHumanTitle"] {
    display: none;
  }

  /* Заголовок карточки повторяет переключатель. */
  .play-lobby .play-mode-head h3 {
    display: none;
  }

  /* Партия. Колонка доски и панель растворяются, их части встают столбцом. */
  body.page-play .play-game:not([hidden]) {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  body.page-play .play-board-col,
  body.page-play .play-panel {
    display: contents;
  }

  body.page-play .play-panel-top {
    order: 0;
    min-height: 18px;
  }

  body.page-play .play-side-top {
    order: 1;
  }

  body.page-play .play-board-wrap {
    order: 2;
    align-self: center;
    width: min(100%, calc(100dvh - 300px));
  }

  body.page-play .play-side-bottom {
    order: 3;
  }

  body.page-play .play-result {
    order: 4;
    margin: 0;
  }

  body.page-play .play-moves {
    order: 5;
  }

  body.page-play .play-status {
    order: 6;
    min-height: 20px;
  }

  body.page-play .play-game-actions {
    order: 7;
  }

  body.page-play .play-panel-head {
    color: var(--muted);
    font-size: 12px;
    font-weight: 700;
  }

  /* Шестерёнка доски — в шапке справа, окно открывается вниз. */
  body.page-play .play-panel-top .board-settings {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 4px);
    right: max(calc(var(--mob-gutter) - 6px), env(safe-area-inset-right, 0px));
    z-index: 41;
  }

  body.page-play #playSettingsButton {
    width: 44px;
    height: 44px;
    min-height: 44px;
    border: 0;
    background: transparent;
    box-shadow: none;
    color: var(--ink);
  }

  /* Ходы — одной строкой, листаются вбок, последний ход виден. */
  body.page-play .play-moves {
    display: flex;
    flex: none;
    align-items: center;
    gap: 4px;
    height: 40px;
    min-height: 0;
    max-height: none;
    padding: 0 6px;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }

  body.page-play .play-moves::-webkit-scrollbar {
    display: none;
  }

  body.page-play .play-move-row {
    display: flex;
    flex: none;
    align-items: center;
    gap: 2px;
    padding: 0;
    background: none;
  }

  body.page-play .play-move-row:nth-child(even) {
    background: none;
  }

  body.page-play .play-move-num {
    font-size: 12px;
  }

  body.page-play .play-move {
    white-space: nowrap;
  }

  body.page-play .play-moves-empty {
    margin: 0;
    color: var(--muted);
    font-size: 13px;
  }

  /* На всю ширину телефона «Предложить ничью» помещается в половину строки. */
  body.page-play .play-panel .play-game-actions:not([hidden]) {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }

  body.page-play .play-game-actions .ghost-button {
    min-height: 48px;
    margin: 0;
  }
}

/* ── Телефон: экран «Квесты» (U-83) ──────────────────────────────────────────
   Заголовок раздела — в шапке. Задачи помещаются в окно: переключатель, строка
   тем, доска по высоте экрана, подсказка и пропуск. Сценарии: та же строка
   карточек, под ней выбранный сценарий; он длинный и прокручивается. */
@media (max-width: 980px) {
  body.page-quests .quest-page-head {
    display: none;
  }

  body.page-quests .quest-mode-switch {
    margin: 0 0 10px;
  }

  body.page-quests .quest-mode-tab {
    min-height: 40px;
    font-size: 14px;
  }

  /* Пока задачи нет, доска пуста и только растягивает страницу: на экране с замком
     у гостя прокрутка доходила до 188 px. */
  body.page-quests .quest-board-panel:has(.quest-puzzle-frame.is-idle) {
    display: none;
  }

  /* Строка карточек листается внутри себя и не расширяет страницу: без этого
     на «Сценариях» страница становилась шириной 914 px. */
  body.page-quests .quest-mode-panel {
    grid-template-columns: minmax(0, 1fr);
    min-width: 0;
    gap: 10px;
  }

  /* position: relative — иначе скрытая для глаз подпись в карточке сценария
     (position: absolute) отсчитывается от страницы, прокрутка строки её не
     обрезает, и страница на телефоне становилась шириной 863–914 px. */
  body.page-quests .quest-strip {
    position: relative;
    min-width: 0;
    max-width: 100%;
    grid-auto-columns: 132px;
    scrollbar-width: none;
  }

  body.page-quests .quest-strip::-webkit-scrollbar {
    display: none;
  }

  body.page-quests .quest-strip-card {
    min-height: 0;
    padding: 8px 10px;
  }

  /* Буквы и цифры — внутри доски, как на доске анализа. */
  body.page-quests .quest-board-coordinate {
    color: rgba(0, 0, 0, 0.62);
    font-size: 10px;
    line-height: 1;
    text-shadow: 0 0 2px rgba(255, 255, 255, 0.75);
  }

  body.page-quests .quest-board-coordinate.is-file {
    bottom: 2px;
  }

  body.page-quests .quest-board-coordinate.is-rank {
    left: 3px;
  }

  /* Задача: заголовок, «Ход белых» и ответ на ход, доска, подсказка и пропуск.
     Доска не больше, чем оставляет экран (но не меньше 260 px — тогда экран
     прокручивается). */
  body.page-quests #puzzleStage {
    padding: 10px;
  }

  body.page-quests #puzzleStage .quest-detail-head h3 {
    font-size: 17px;
  }

  body.page-quests #puzzleStage .quest-detail-grid {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
  }

  body.page-quests #puzzleStage .quest-info-panel {
    order: -1;
    min-height: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
  }

  body.page-quests #matePuzzleFrame {
    width: max(280px, min(100%, calc(100dvh - 495px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px))));
    margin: 0 auto;
  }

  body.page-quests .quest-strip-card {
    gap: 2px;
  }

  /* Плотнее над доской: меньше отступов, «Мат» в карточке темы повторяет её
     подпись, «Ход белых» — одной строкой с целью. */
  body.page-quests .quests-page {
    gap: 10px;
    margin: 0;
  }

  body.page-quests .quest-mode-switch {
    margin: 0;
  }

  body.page-quests .quest-theme-card > .quest-strip-kicker {
    display: none;
  }

  body.page-quests #puzzleStage .quest-turn {
    gap: 8px;
  }

  body.page-quests #puzzleStage .quest-turn-dot {
    width: 16px;
    height: 16px;
  }

  body.page-quests #puzzleStage .quest-turn-text {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: 8px;
  }

  body.page-quests #puzzleStage .quest-turn-text strong {
    font-size: 15px;
  }

  body.page-quests #puzzleStage .quest-tasks-feedback {
    margin: 0;
  }

  /* Сценарий: доска на всю ширину, оценка — узкой полосой слева. */
  body.page-quests #questScenariosPanel .quest-detail {
    padding: 10px;
  }

  body.page-quests #questScenariosPanel .quest-board-live {
    gap: 6px;
  }

  body.page-quests #questScenariosPanel .quest-eval-bar-wrap {
    width: 22px;
    gap: 4px;
  }

  body.page-quests #questScenariosPanel .quest-eval-bar {
    width: 10px;
    min-height: 0;
  }

  body.page-quests #questScenariosPanel .quest-eval-bar-score {
    font-size: 10px;
  }

  body.page-quests #questScenariosPanel .quest-board-frame {
    flex: none;
    width: calc(100% - 28px);
    margin: 0;
  }
}

/* ── Телефон: «Главная» (U-83) ───────────────────────────────────────────────
   Кнопки на всю ширину, карточки разделов — по две в строке, «Секундант» — на
   всю ширину последней строкой. */
@media (max-width: 980px) {
  body.page-home .welcome-copy h2 {
    font-size: 26px;
    line-height: 1.2;
  }

  body.page-home .welcome-actions {
    align-items: stretch;
  }

  body.page-home .welcome-actions .hero-action {
    justify-content: center;
    width: 100%;
    min-height: 48px;
  }

  body.page-home .welcome-features {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    padding: 0;
  }

  body.page-home .welcome-features > :is(article, a) {
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
    align-items: start;
    min-height: 0;
    padding: 12px;
  }

  body.page-home .welcome-features > :is(article, a):last-child {
    grid-column: 1 / -1;
    grid-template-columns: 36px minmax(0, 1fr);
    align-items: center;
  }

  body.page-home .welcome-features span {
    width: 36px;
    height: 36px;
    font-size: 18px;
  }

  body.page-home .welcome-features strong {
    font-size: 14px;
  }
}

/* ── Телефон: настоящая видимая высота (U-83, 06.11.2026) ─────────────────────
   Первая версия проверялась на высоте 844 px — это весь экран iPhone. В браузере
   адресная строка и панель занимают 100–200 px, видно 640–780 px, и разделы
   прокручивались. Здесь всё рассчитано на видимую высоту от 520 px: экраны
   «Анализ», «Играть», «Задачи» и «Главная» помещаются целиком, сценарий
   открывается своим экраном, длинный текст листается внутри. */
@media (max-width: 980px) {
  /* Анализ: игроки и счёт — одной строкой над доской. */
  body.page-analysis #playerStrip {
    display: flex;
    align-items: center;
    gap: 8px;
    height: 28px;
  }

  body.page-analysis #playerStrip .player-side,
  body.page-analysis #playerStrip .player-side.is-black {
    flex: 1 1 0;
    flex-direction: row;
    align-items: center;
    justify-content: flex-start;
    gap: 6px;
    min-width: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    color: var(--ink);
    text-align: left;
  }

  body.page-analysis #playerStrip .player-side.is-black {
    flex-direction: row-reverse;
    text-align: right;
  }

  /* Квадрат — цвет фигур игрока. */
  body.page-analysis #playerStrip .player-side::before {
    content: "";
    flex: none;
    width: 12px;
    height: 12px;
    border: 1px solid var(--line-strong);
    border-radius: 3px;
    background: #ffffff;
  }

  body.page-analysis #playerStrip .player-side.is-black::before {
    background: #1f2937;
  }

  body.page-analysis #playerStrip .player-card {
    flex: 0 1 auto;
    min-width: 0;
  }

  body.page-analysis #playerStrip .player-card strong {
    color: var(--ink);
    font-size: 13px;
  }

  body.page-analysis #playerStrip .score-card-row {
    flex: none;
    flex-direction: row;
    gap: 6px;
  }

  body.page-analysis #playerStrip .score-card-body,
  body.page-analysis #playerStrip .is-black .score-card-body {
    justify-content: flex-start;
  }

  /* Подписи «Точность» и «Перформанс» остаются для читалки экрана. */
  body.page-analysis #playerStrip .score-card-body span,
  body.page-analysis #playerStrip #matchOutcome {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  body.page-analysis #playerStrip .score-card-body strong {
    color: var(--ink);
    font-size: 13px;
  }

  body.page-analysis #playerStrip .score-card.is-performance .score-card-body strong {
    color: var(--muted);
    font-weight: 600;
  }

  body.page-analysis #playerStrip .is-lead strong,
  body.page-analysis #playerStrip strong.is-lead {
    color: var(--green-text);
  }

  body.page-analysis #playerStrip .is-behind strong,
  body.page-analysis #playerStrip strong.is-behind {
    color: var(--mistake-text);
  }

  body.page-analysis #playerStrip .versus-block {
    flex: none;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
  }

  body.page-analysis #playerStrip #matchResult {
    padding: 2px 6px;
    border-radius: 6px;
    background: var(--switch-track);
    color: var(--ink);
    font-size: 13px;
    line-height: 18px;
  }

  /* Вкладки ниже. */
  body.page-analysis .analysis-tabs .segment {
    min-height: 34px;
  }

  /* Ручка панели: потянуть вверх — панель встаёт на место доски. */
  .an-sheet-handle {
    order: 7;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 24px;
    margin: 0 0 -8px;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    touch-action: none;
  }

  .an-sheet-handle span {
    width: 40px;
    height: 4px;
    border-radius: 2px;
    background: var(--line-strong);
  }

  .an-sheet-handle:focus-visible {
    outline: 3px solid color-mix(in srgb, var(--blue) 34%, transparent);
    outline-offset: -3px;
    border-radius: 8px;
  }

  body.page-analysis .board-side-scroll {
    order: 8;
  }

  body.page-analysis[data-report-tab="game"] .an-sheet-handle {
    display: none;
  }

  body.page-analysis.an-sheet-open:not([data-report-tab="game"]) .analysis-stage #playerStrip,
  body.page-analysis.an-sheet-open:not([data-report-tab="game"]) .analysis-stage .board-live,
  body.page-analysis.an-sheet-open:not([data-report-tab="game"]) .analysis-stage .board-controls,
  body.page-analysis.an-sheet-open:not([data-report-tab="game"]) .analysis-stage .board-branch-banner,
  body.page-analysis.an-sheet-open:not([data-report-tab="game"]) .analysis-stage #boardLiveLine {
    display: none;
  }

  /* Главная: без картинки — экран помещается целиком. */
  body.page-home .welcome-visual {
    display: none;
  }

  body.page-home .welcome-copy {
    gap: 12px;
  }

  /* Играть: доска не больше, чем оставляет видимая высота. */
  body.page-play .play-board-wrap {
    width: max(200px, min(100%, calc(100dvh - 350px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px))));
  }

  /* Задачи: карточки тем ниже, заголовок задачи убран (прогресс — в полосе
     карточки), доска по видимой высоте. */
  body.page-quests #questTasksPanel .quest-theme-card > .quest-strip-status {
    display: none;
  }

  body.page-quests #questTasksPanel .quest-strip-card {
    padding: 7px 10px;
  }

  body.page-quests #puzzleStage .quest-detail-head {
    display: none;
  }

  body.page-quests #matePuzzleFrame {
    width: max(200px, min(100%, calc(100dvh - 381px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px))));
  }

  /* Сценарии: сначала список, сценарий — своим экраном со стрелкой назад. */
  .quest-back {
    display: inline-flex;
    flex: none;
    align-items: center;
    gap: 2px;
    min-height: 40px;
    padding: 0 8px 0 2px;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: var(--blue-dark);
    font: inherit;
    font-size: 14px;
    font-weight: 700;
    cursor: pointer;
  }

  .quest-back svg {
    width: 20px;
    height: 20px;
  }

  body.page-quests #questScenariosPanel:not(.is-detail) #questDetail {
    display: none;
  }

  body.page-quests #questScenariosPanel:not(.is-detail) .quest-strip {
    grid-auto-columns: auto;
    grid-auto-flow: row;
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
    overflow: visible;
  }

  body.page-quests #questScenariosPanel:not(.is-detail) .quest-strip-card {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "kicker status" "title status";
    align-items: center;
    column-gap: 10px;
    row-gap: 0;
    min-height: 52px;
    padding: 7px 12px;
  }

  body.page-quests #questScenariosPanel:not(.is-detail) .quest-strip-kicker {
    grid-area: kicker;
  }

  body.page-quests #questScenariosPanel:not(.is-detail) .quest-strip-title {
    grid-area: title;
  }

  body.page-quests #questScenariosPanel:not(.is-detail) .quest-strip-status {
    grid-area: status;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-strip,
  body.page-quests:has(#questScenariosPanel.is-detail:not([hidden])) .quest-mode-switch {
    display: none;
  }

  body.page-quests #questScenariosPanel.is-detail #questDetail {
    display: flex;
    flex-direction: column;
    gap: 8px;
    box-sizing: border-box;
    height: calc(100vh - var(--mob-header-h) - 16px - var(--mob-bottom-space));
    height: calc(100dvh - var(--mob-header-h) - env(safe-area-inset-top, 0px) - 16px - var(--mob-bottom-space));
    padding: 8px 10px;
    overflow: hidden;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-detail-head {
    display: flex;
    flex: none;
    align-items: center;
    gap: 6px;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-detail-titles {
    flex: 1 1 auto;
    min-width: 0;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-detail-titles h3 {
    overflow: hidden;
    font-size: 16px;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-detail-meta {
    display: none;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-detail-grid {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    min-height: 0;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-board-panel {
    flex: none;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-board-frame {
    width: max(220px, min(calc(100% - 28px), calc(100dvh - 380px - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px))));
  }

  body.page-quests #questScenariosPanel.is-detail .quest-board-live {
    justify-content: center;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-info-panel {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* На компьютере ни ручки панели, ни стрелки к списку сценариев нет. */
@media (min-width: 981px) {
  .an-sheet-handle,
  .quest-back {
    display: none;
  }
}

@media (max-width: 980px) {
  /* Главная в одно окно: без пустых промежутков, карточки — значок слева. */
  body.page-home .welcome-screen {
    gap: 12px;
    min-height: 0;
    padding: 0;
  }

  body.page-home .welcome-copy {
    gap: 10px;
    min-height: 0;
    padding: 0;
  }

  body.page-home .welcome-copy h2 {
    margin: 0;
  }

  body.page-home .welcome-actions {
    gap: 8px;
    margin: 4px 0 0;
  }

  body.page-home .welcome-features > :is(article, a),
  body.page-home .welcome-features > :is(article, a):last-child {
    grid-template-columns: 32px minmax(0, 1fr);
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
  }

  body.page-home .welcome-features span {
    width: 32px;
    height: 32px;
    font-size: 16px;
  }

  body.page-home .welcome-features strong {
    font-size: 13px;
    line-height: 1.3;
  }

  /* Задачи: переключатель и отступы ниже — доска остаётся прежней. */
  body.page-quests .quests-page {
    gap: 6px;
  }

  body.page-quests .quest-mode-tab {
    min-height: 34px;
  }

  body.page-quests #puzzleStage {
    padding: 8px;
  }

  body.page-quests #puzzleStage .quest-detail-grid {
    gap: 6px;
  }

  body.page-quests #matePuzzleTools .ghost-button {
    min-height: 40px;
  }
}

/* Самые низкие окна (iPhone SE с панелями браузера — около 548 px): ниже кнопки
   контроля времени, без второй строки в карточках тем, меньше отступы. */
@media (max-width: 980px) and (max-height: 600px) {
  body.page-play .play-control {
    min-height: 40px;
    padding: 4px 6px;
  }

  body.page-play .play-mode,
  body.page-play .play-guest {
    padding: 12px;
  }

  body.page-play .play-promise {
    display: none;
  }

  body.page-quests #questTasksPanel .quest-strip-sub {
    display: none;
  }

  body.page-quests #questScenariosPanel:not(.is-detail) .quest-strip {
    gap: 6px;
  }

  body.page-quests #questScenariosPanel:not(.is-detail) .quest-strip-card {
    min-height: 44px;
    padding: 4px 12px;
  }

  body.page-play .play-lobby-actions,
  body.page-play .play-controls {
    gap: 6px;
  }

  body.page-play .play-mode-switch {
    margin-bottom: 6px;
  }
}

@media (max-width: 980px) {
  /* Шапка открытого сценария — одной строкой: ‹, название, «Об этой партии». */
  body.page-quests #questScenariosPanel.is-detail .quest-detail-head {
    flex-wrap: nowrap;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-back span {
    display: none;
  }

  body.page-quests #questScenariosPanel.is-detail .quest-about-toggle {
    flex: none;
    min-height: 36px;
    padding: 0 10px;
    font-size: 13px;
  }
}

/* ── Задачи Lichess (06.11.2026) ─────────────────────────────────────────────
   Тем стало 18: строка «Задач» листается вбок и на компьютере. Уровень — первая
   ячейка этой строки, поэтому доска не теряет высоту. Строка сценариев не менялась. */
.quest-level-card {
  justify-content: center;
  gap: 6px;
  cursor: default;
}

.quest-level-card[hidden],
body.page-quests .quest-theme-card.is-off-level {
  display: none;
}

.quest-level-select {
  width: 100%;
  min-height: 36px;
  padding: 0 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius-control);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}

@media (min-width: 981px) {
  body.page-quests #questTasksPanel {
    min-width: 0;
  }

  body.page-quests #questTasksPanel .quest-strip {
    position: relative;
    min-width: 0;
    grid-template-columns: none;
    grid-auto-columns: 196px;
    grid-auto-flow: column;
    overflow-x: auto;
    padding-bottom: 2px;
  }

  /* Шире 196 px названия помещаются в строку, а полоса тонкая: высота строки
     тем не отнимается у доски. */
  body.page-quests #questTasksPanel .quest-strip::-webkit-scrollbar {
    height: 6px;
  }

  body.page-quests #questTasksPanel .quest-strip::-webkit-scrollbar-thumb {
    border-radius: 3px;
    background: var(--line-strong);
  }
}

@media (max-width: 980px) {
  /* Safari приближает страницу при фокусе на поле мельче 16 px. */
  .quest-level-select {
    min-height: 34px;
    font-size: 16px;
  }

  body.page-quests .quest-level-card {
    gap: 2px;
    padding-block: 5px;
  }

  /* Строка тем — название и полоса: с подписью длинные темы растягивали её
     до 95 px и экран начинал прокручиваться. */
  body.page-quests #questTasksPanel .quest-strip-sub {
    display: none;
  }

  body.page-quests #questTasksPanel .quest-theme-bar {
    margin-top: auto;
  }
}

/* Самые низкие окна (iPhone SE): названия тем в одну строку, подпись «Уровень»
   остаётся только для читалки экрана. */
@media (max-width: 980px) and (max-height: 600px) {
  body.page-quests #questTasksPanel .quest-strip-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  body.page-quests #questTasksPanel .quest-strip-card {
    padding-block: 3px;
  }

  body.page-quests #puzzleStage {
    padding-block: 6px;
  }

  body.page-quests .quest-level-card .quest-strip-kicker {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
  }
}

/* ═══ Экран анализа до выбора партии (08.11.2026, запрос пользователя) ═══════
   Пока партия не выбрана и не отправлен PGN, нет ни доски, ни вкладок
   «Партия / Обзор / Графики / Секундант»: они неактивны до готового разбора и
   только сбивали с толку. Виден один выбор партии. Доска появляется, как
   только начался разбор, вкладки — когда он готов (body.has-analysis). */
body.page-analysis:not(.has-analysis) .analysis-tabs {
  display: none;
}

body.page-analysis:not(.has-analysis):not(.is-loading-analysis) .board-zone,
body.page-analysis:not(.has-analysis):not(.is-loading-analysis) .an-sheet-handle {
  display: none;
}

body.page-analysis:not(.has-analysis):not(.is-loading-analysis) .board-with-side {
  grid-template-columns: minmax(0, 1fr);
}

body.page-analysis:not(.has-analysis):not(.is-loading-analysis) .board-side {
  width: 100%;
  max-width: 760px;
  margin: 0 auto;
}

@media (min-width: 981px) {
  body.page-analysis:not(.has-analysis):not(.is-loading-analysis) .analysis-stage {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "strip"
      "side";
  }
}

/* ═══ Крупная доска в партии онлайн (08.11.2026, запрос пользователя) ═══════
   Было: колонка доски не шире 560 px — на большом мониторе доска занимала
   меньше половины высоты. Теперь сторона доски — по высоте окна (минус
   строки игроков и отступы), но не больше 900 px. Заголовок «Играть онлайн»
   во время партии не нужен — его место тоже отдаём доске. */
@media (min-width: 981px) {
  .play-page:has(.play-game:not([hidden])) .play-topbar {
    display: none;
  }

  .play-game {
    grid-template-columns: minmax(0, min(900px, calc(100vh - 170px))) minmax(280px, 340px);
  }
}

/* ═══ Поиск соперника (08.11.2026, запрос пользователя) ══════════════════════
   Строка «Ищем соперника…» была мелкой и серой — поиск выглядел так, будто
   ничего не происходит. Теперь вместо кнопок — заметная карточка: крутится
   индикатор, рядом контроль времени и сколько уже ждём, под ней «Отменить». */
.play-seek-spinner,
.play-seek-elapsed {
  display: none;
}

.play-mode.is-seeking .play-lobby-actions {
  display: none;
}

.play-mode.is-seeking .play-lobby-waiting {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 8px 14px;
  width: 100%;
  padding: 16px 18px;
  border: 1px solid color-mix(in srgb, var(--blue) 35%, transparent);
  border-radius: var(--radius-control, 12px);
  background: color-mix(in srgb, var(--blue) 8%, var(--surface));
  animation: play-seek-glow 1.8s ease-in-out infinite;
}

.play-mode.is-seeking .play-seek-spinner {
  display: block;
  width: 26px;
  height: 26px;
  border: 3px solid color-mix(in srgb, var(--blue) 22%, transparent);
  border-top-color: var(--blue);
  border-radius: 50%;
  animation: play-seek-spin 0.9s linear infinite;
}

.play-mode.is-seeking .play-lobby-status {
  min-height: 0;
  color: var(--ink);
  font-size: var(--fs-body, 16px);
  font-weight: 700;
  text-align: left;
}

.play-mode.is-seeking .play-seek-elapsed {
  display: block;
  color: var(--blue-dark, var(--blue));
  font-size: 18px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}

.play-mode.is-seeking #playCancelSeek {
  grid-column: 1 / -1;
  justify-self: stretch;
}

@keyframes play-seek-spin {
  to { transform: rotate(360deg); }
}

@keyframes play-seek-glow {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--blue) 0%, transparent); }
  50% { box-shadow: 0 0 0 6px color-mix(in srgb, var(--blue) 12%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .play-mode.is-seeking .play-lobby-waiting,
  .play-mode.is-seeking .play-seek-spinner {
    animation: none;
  }
}

/* ═══ Шапки карточек лобби (08.11.2026, запрос пользователя) ═════════════════
   Понятно с первого взгляда, где живой человек, а где бот: значок слева,
   у людей зелёная точка и «Сейчас онлайн: N», у компьютера — «Stockfish». */
.play-mode-head {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}

.play-mode-head-text {
  min-width: 0;
}

.play-mode-icon {
  display: grid;
  flex: none;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: 14px;
}

.play-mode-icon svg {
  width: 26px;
  height: 26px;
}

.play-mode-icon.is-human {
  background: color-mix(in srgb, var(--green) 14%, var(--surface));
  color: var(--green-text);
}

.play-mode-icon.is-bot {
  background: color-mix(in srgb, var(--muted) 14%, var(--surface));
  color: var(--ink);
}

.play-mode-head p.play-mode-live,
.play-mode-head p.play-mode-meta {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 4px;
  font-weight: 700;
}

.play-mode-head p.play-mode-live {
  color: var(--green-text);
}

.play-mode-head p.play-mode-meta {
  color: var(--ink);
}

.play-live-dot {
  flex: none;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 50%, transparent);
  animation: play-live-pulse 1.8s ease-out infinite;
}

.play-mode-live.is-empty {
  color: var(--muted);
}

.play-mode-live.is-empty .play-live-dot {
  background: var(--muted);
  animation: none;
}

@keyframes play-live-pulse {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 50%, transparent); }
  80%, 100% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--green) 0%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .play-live-dot {
    animation: none;
  }
}

/* ── Главная: витрины (09.11.2026, запрос пользователя) ──────────────────────
   Вместо строки из пяти карточек — блоки «как у товара»: крупный заголовок с
   выделенной частью, настоящий снимок раздела с миниатюрами, цена, выгоды и
   кнопки. Сверху полоса фактов, затем сюжет из трёх шагов: снимок справа
   стоит на месте (sticky) и меняется, пока слева прокручиваются шаги.
   Главная теперь прокручивается (решение пользователя 09.11.2026); остальные
   разделы — по-прежнему в одно окно.
   Без JS всё видно сразу: прятать до появления начинаем только с .sc-js. */
.showcase {
  grid-column: 1 / -1;
  display: grid;
  gap: clamp(72px, 9vw, 140px);
  min-width: 0;
  padding: 8px clamp(4px, 1vw, 14px) 72px;
  --sc-stage-bg: var(--surface-strong);
  --sc-cta-bg: var(--blue-dark);
  --sc-cta-ink: #ffffff;
}

html[data-theme="dark"] .showcase {
  --sc-stage-bg: var(--surface-soft);
  --sc-cta-bg: #4F6BFF;
}

/* Полоса фактов — как «Featured in» в примере, но только правда о сайте. */
.sc-facts {
  overflow: hidden;
  padding: 16px 0;
  border-radius: var(--radius);
  /* Один цвет в обеих темах: белый текст на нём читается (5.5:1). */
  background: #3D5AF1;
  color: #ffffff;
}

.sc-facts-track {
  display: flex;
  width: max-content;
  animation: sc-marquee 42s linear infinite;
}

.sc-facts:hover .sc-facts-track {
  animation-play-state: paused;
}

.sc-facts-list {
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
}

.sc-facts-list li {
  display: flex;
  align-items: center;
  gap: 26px;
  padding-right: 26px;
  font-size: clamp(17px, 1.7vw, 24px);
  font-weight: 800;
  white-space: nowrap;
}

.sc-facts-list li::before {
  content: "★";
  font-size: 0.72em;
  opacity: 0.85;
}

@keyframes sc-marquee {
  to { transform: translateX(-50%); }
}

/* Заголовки блоков: обычная часть и жирная, как в примере. */
.sc-head {
  max-width: 900px;
  margin: 0 auto;
  text-align: center;
}

.sc-head h2 {
  margin: 0;
  color: var(--ink);
  font-size: clamp(28px, 3.3vw, 48px);
  font-weight: 400;
  line-height: 1.15;
}

.sc-head h2 b {
  display: block;
  font-weight: 800;
}

.sc-kicker {
  margin: 14px 0 0;
  color: var(--accent-text);
  font-size: var(--fs-h4);
  font-weight: 600;
}

html[data-theme="dark"] .sc-kicker {
  color: var(--blue);
}

/* ── Сюжет ── */
.sc-story-grid {
  display: grid;
  grid-template-columns: minmax(280px, 0.42fr) minmax(0, 1fr);
  gap: clamp(28px, 4vw, 64px);
  align-items: start;
  margin-top: 24px;
}

.sc-steps {
  margin: 0;
  padding: 0;
  list-style: none;
}

.sc-step {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 64vh;
  padding: 24px 0;
  opacity: 0.32;
  transition: opacity 450ms ease;
}

.sc-step.is-active {
  opacity: 1;
}

/* Большая бледная цифра за заголовком шага — как «1» в примере. */
.sc-step-num {
  position: absolute;
  top: calc(50% - 128px);
  left: -6px;
  color: var(--ink);
  font-size: 150px;
  font-weight: 800;
  line-height: 1;
  opacity: 0.06;
  pointer-events: none;
}

.sc-step h3 {
  position: relative;
  margin: 0 0 12px;
  color: var(--ink);
  font-size: clamp(22px, 2vw, 30px);
  font-weight: 700;
  line-height: 1.2;
}

.sc-step p {
  position: relative;
  max-width: 440px;
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-h4);
  line-height: 1.6;
}

/* Снимок внутри шага — только на телефоне, на компьютере он справа. */
.sc-step-shot {
  display: none;
}

.sc-story-stage {
  position: sticky;
  top: max(24px, calc(50vh - 290px));
  display: grid;
  grid-template-columns: 6px minmax(0, 1fr);
  gap: 18px;
}

.sc-progress {
  position: relative;
  overflow: hidden;
  border-radius: 3px;
  background: var(--line);
}

.sc-progress span {
  position: absolute;
  inset: 0 0 auto;
  height: var(--sc-progress, 0%);
  border-radius: 3px;
  background: var(--blue);
}

.sc-frame {
  overflow: hidden;
  border-radius: 18px;
  background: var(--surface);
  box-shadow: 0 2px 4px rgba(16, 24, 40, 0.06), 0 24px 60px rgba(16, 24, 40, 0.14);
}

html[data-theme="dark"] .sc-frame {
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4), 0 24px 60px rgba(0, 0, 0, 0.45);
}

.sc-frame-bar {
  display: flex;
  align-items: center;
  gap: 7px;
  height: 36px;
  padding: 0 14px;
  border-bottom: 1px solid var(--line);
  background: var(--surface-soft);
}

.sc-frame-bar i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--line-strong);
}

.sc-frame-bar span {
  margin-left: 14px;
  padding: 3px 14px;
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  font-size: var(--fs-caption);
}

.sc-frame-screen {
  position: relative;
  aspect-ratio: 1336 / 846;
  background: var(--page);
}

.sc-frame-screen img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transform: scale(1.03);
  transition: opacity 600ms ease, transform 900ms ease;
}

.sc-frame-screen img.is-active {
  opacity: 1;
  transform: none;
}

/* Третий шаг: колонка «Обзора» выезжает поверх доски крупным планом,
   доска за ней уходит в тень. */
.sc-frame-screen.is-focus img:not(.sc-frame-side) {
  filter: blur(2px) saturate(0.6);
  opacity: 0.45;
}

.sc-frame-screen img.sc-frame-side {
  inset: 1.5% 6% auto auto;
  width: 42%;
  height: auto;
  border-radius: 14px;
  box-shadow: 0 18px 50px rgba(16, 24, 40, 0.28);
  transform: translateY(40px) scale(0.94);
}

.sc-frame-screen img.sc-frame-side.is-active {
  transform: none;
}

.sc-story-cta,
.sc-final {
  display: flex;
  justify-content: center;
  margin-top: 32px;
}

.sc-story-cta .hero-action,
.sc-final .hero-action {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 0 32px;
  text-decoration: none;
}

/* ── Витрины разделов ── */
.sc-product-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(300px, 0.85fr);
  gap: clamp(28px, 5vw, 80px);
  align-items: center;
  margin-top: 44px;
}

.sc-product.is-flipped .sc-gallery {
  order: 2;
}

.sc-product.is-premium {
  padding: clamp(28px, 4vw, 56px) clamp(16px, 3vw, 48px);
  border-radius: 28px;
  background: color-mix(in srgb, var(--amber) 8%, var(--surface));
  box-shadow: var(--shadow), 0 0 40px color-mix(in srgb, var(--amber) 14%, transparent);
}

.sc-gallery {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr);
  gap: 14px;
  min-width: 0;
}

.sc-thumbs {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.sc-thumb {
  aspect-ratio: 1;
  padding: 4px;
  overflow: hidden;
  border: 2px solid transparent;
  border-radius: 12px;
  background: var(--sc-stage-bg);
  box-shadow: var(--shadow-soft);
  cursor: pointer;
  transition: border-color 160ms ease, transform 160ms ease;
}

.sc-thumb:hover {
  transform: translateY(-2px);
}

.sc-thumb.is-active {
  border-color: var(--blue);
}

.sc-thumb:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
}

.sc-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 8px;
  object-fit: cover;
  object-position: top left;
}

.sc-stage {
  position: relative;
  overflow: hidden;
  aspect-ratio: 1 / 1;
  border-radius: 22px;
  background: var(--sc-stage-bg);
  touch-action: pan-y;
  --sc-rx: 0deg;
  --sc-ry: 0deg;
}

.sc-stage-track {
  position: absolute;
  inset: 0;
  perspective: 1400px;
}

.sc-stage-track img {
  position: absolute;
  inset: 13% 5% 9%;
  width: 90%;
  height: 78%;
  object-fit: contain;
  opacity: 0;
  filter: drop-shadow(0 14px 28px rgba(16, 24, 40, 0.18));
  /* Иначе браузер начинает тащить картинку и отменяет свайп (pointercancel). */
  -webkit-user-drag: none;
  user-select: none;
  transform: translateX(40px) rotateX(var(--sc-rx)) rotateY(var(--sc-ry));
  transition: opacity 450ms ease, transform 450ms ease;
}

.sc-stage-track img.is-active {
  opacity: 1;
  transform: translateX(0) rotateX(var(--sc-rx)) rotateY(var(--sc-ry));
}

.sc-badge {
  position: absolute;
  z-index: 2;
  top: 18px;
  left: 18px;
  padding: 7px 16px;
  border-radius: 999px;
  color: #ffffff;
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

.sc-badge.is-free {
  background: #15803d;
}

.sc-badge.is-mixed {
  background: #3D5AF1;
}

.sc-badge.is-premium {
  background: #b45309;
}

.sc-dots {
  position: absolute;
  bottom: 18px;
  left: 50%;
  display: flex;
  gap: 8px;
  transform: translateX(-50%);
}

.sc-dots span {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--ink) 22%, transparent);
  transition: background 200ms ease, transform 200ms ease;
}

.sc-dots span.is-active {
  background: var(--blue);
  transform: scale(1.2);
}

.sc-info h3 {
  margin: 0;
  color: var(--ink);
  font-size: clamp(26px, 2.4vw, 36px);
  font-weight: 600;
  line-height: 1.2;
}

.sc-price {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin: 14px 0 0;
  color: var(--muted);
}

.sc-price strong {
  color: var(--accent-text);
  font-size: clamp(24px, 2.2vw, 32px);
  font-weight: 800;
  line-height: 1;
}

html[data-theme="dark"] .sc-price strong {
  color: var(--blue);
}

.sc-product.is-premium .sc-price strong {
  color: #b45309;
}

html[data-theme="dark"] .sc-product.is-premium .sc-price strong {
  color: var(--amber);
}

.sc-price em {
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--ink);
  color: var(--surface);
  font-size: var(--fs-caption);
  font-style: normal;
  font-weight: 600;
}

.sc-desc {
  max-width: 520px;
  margin: 22px 0 0;
  color: var(--muted);
  font-size: var(--fs-h4);
  line-height: 1.6;
}

.sc-perks {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 18px;
  margin: 30px 0 0;
  padding: 0;
  list-style: none;
}

.sc-perks li {
  display: grid;
  align-content: start;
  gap: 10px;
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: 1.35;
}

.sc-perk-icon {
  display: block;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--blue) 14%, var(--surface));
  box-shadow: -6px 0 0 color-mix(in srgb, var(--ink) 6%, transparent);
  position: relative;
}

.sc-product.is-premium .sc-perk-icon {
  background: color-mix(in srgb, var(--amber) 26%, var(--surface));
}

.sc-perk-icon::after {
  position: absolute;
  inset: 11px;
  background: var(--accent-text);
  content: "";
  -webkit-mask: var(--sc-icon) center / contain no-repeat;
  mask: var(--sc-icon) center / contain no-repeat;
}

html[data-theme="dark"] .sc-perk-icon::after {
  background: var(--blue);
}

.sc-product.is-premium .sc-perk-icon::after {
  background: #92400e;
}

html[data-theme="dark"] .sc-product.is-premium .sc-perk-icon::after {
  background: var(--amber);
}

.sc-perk-icon.is-chart { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 3v18h18'/%3E%3Cpath d='m7 15 4-5 3 3 5-7'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-clock { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Cpath d='M12 7v5l3 2'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-target { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='9'/%3E%3Ccircle cx='12' cy='12' r='5'/%3E%3Ccircle cx='12' cy='12' r='1'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-people { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='9' cy='8' r='3.5'/%3E%3Cpath d='M2.5 20c.8-3.6 3.4-5.5 6.5-5.5s5.7 1.9 6.5 5.5'/%3E%3Cpath d='M16 4.6a3.5 3.5 0 0 1 0 6.8'/%3E%3Cpath d='M18 14.8c1.9.7 3.1 2.4 3.5 5.2'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-cpu { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='4' width='18' height='12' rx='2'/%3E%3Cpath d='M8 20h8M12 16v4'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-review { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='m20 20-3.5-3.5'/%3E%3Cpath d='M8 11h6M11 8v6'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-puzzle { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='18' height='18' rx='2'/%3E%3Cpath d='M3 9h18M3 15h18M9 3v18M15 3v18'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-levels { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 20v-5M12 20v-10M19 20V5'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-history { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8 21h8M12 17v4'/%3E%3Cpath d='M7 4h10v5a5 5 0 0 1-10 0z'/%3E%3Cpath d='M7 6H4a3 3 0 0 0 3 4M17 6h3a3 3 0 0 1-3 4'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-summary { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 6h11M9 12h11M9 18h11'/%3E%3Cpath d='m3 6 1.5 1.5L7 5M3 12l1.5 1.5L7 11M3 18l1.5 1.5L7 17'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-phases { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='12' width='4' height='8' rx='1'/%3E%3Crect x='10' y='7' width='4' height='13' rx='1'/%3E%3Crect x='17' y='4' width='4' height='16' rx='1'/%3E%3C/svg%3E"); }
.sc-perk-icon.is-chat { --sc-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 12a8 8 0 0 1-11.6 7.1L4 20.5l1.4-4.6A8 8 0 1 1 21 12z'/%3E%3C/svg%3E"); }

.sc-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 36px;
}

.sc-cta,
.sc-cta-ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: 0 30px;
  border-radius: 999px;
  font-size: var(--fs-h4);
  font-weight: 700;
  text-decoration: none;
  transition: transform 160ms ease, box-shadow 160ms ease, background 160ms ease;
}

.sc-cta {
  border: 2px solid var(--sc-cta-bg);
  background: var(--sc-cta-bg);
  color: var(--sc-cta-ink);
  box-shadow: 0 10px 24px color-mix(in srgb, var(--sc-cta-bg) 30%, transparent);
}

.sc-product.is-premium .sc-cta {
  --sc-cta-bg: #b45309;
}

.sc-cta-ghost {
  border: 2px solid var(--ink);
  background: transparent;
  color: var(--ink);
}

.sc-cta:hover,
.sc-cta-ghost:hover {
  transform: translateY(-2px);
}

.sc-cta:focus-visible,
.sc-cta-ghost:focus-visible {
  outline: 3px solid var(--blue);
  outline-offset: 3px;
}

.sc-final {
  flex-direction: column;
  align-items: center;
  gap: 28px;
  margin-top: 0;
  text-align: center;
}

.sc-final h2 {
  margin: 0;
  color: var(--ink);
  font-size: clamp(28px, 3.3vw, 48px);
  font-weight: 400;
  line-height: 1.15;
}

.sc-final h2 b {
  display: block;
  font-weight: 800;
}

/* Появление при прокрутке — только когда JS на месте. */
.showcase.sc-js .sc-reveal {
  opacity: 0;
  transform: translateY(32px);
  transition: opacity 700ms ease, transform 700ms cubic-bezier(0.2, 0.7, 0.2, 1);
}

.showcase.sc-js .sc-reveal.is-visible {
  opacity: 1;
  transform: none;
}

.showcase.sc-js .sc-info.sc-reveal {
  transition-delay: 120ms;
}

/* Лёгкий параллакс снимка, привязанный к прокрутке, — настоящая
   scroll-driven анимация CSS там, где браузер её умеет. */
@supports (animation-timeline: view()) {
  .sc-stage-track {
    animation: sc-parallax linear both;
    animation-timeline: view();
    animation-range: cover;
  }

  @keyframes sc-parallax {
    from { transform: translateY(26px); }
    to { transform: translateY(-26px); }
  }
}

@media (prefers-reduced-motion: reduce) {
  .sc-facts-track {
    flex-wrap: wrap;
    width: auto;
    animation: none;
  }

  .sc-facts-list[aria-hidden="true"] {
    display: none;
  }

  .sc-facts-list {
    flex-wrap: wrap;
    justify-content: center;
    padding-inline: 16px;
  }

  .sc-stage-track {
    animation: none;
  }

  .sc-step,
  .sc-frame-screen img,
  .sc-stage-track img,
  .sc-thumb,
  .sc-cta,
  .sc-cta-ghost,
  .showcase.sc-js .sc-reveal {
    transition: none;
  }

  .showcase.sc-js .sc-reveal {
    opacity: 1;
    transform: none;
  }

  .sc-stage-track img,
  .sc-stage-track img.is-active,
  .sc-frame-screen img,
  .sc-frame-screen img.sc-frame-side {
    transform: none;
  }
}

/* ── Витрины на телефоне ── */
@media (max-width: 980px) {
  .showcase {
    gap: 64px;
    padding: 4px 0 40px;
  }

  .sc-facts {
    margin-inline: calc(-1 * var(--mob-gutter, 16px));
    padding: 12px 0;
    border-radius: 0;
  }

  .sc-facts-list li {
    gap: 18px;
    padding-right: 18px;
    font-size: 16px;
  }

  .sc-head h2,
  .sc-final h2 {
    font-size: 26px;
  }

  .sc-kicker {
    font-size: var(--fs-body);
  }

  .sc-story-grid {
    grid-template-columns: minmax(0, 1fr);
    margin-top: 8px;
  }

  .sc-story-stage {
    display: none;
  }

  .sc-step {
    min-height: 0;
    padding: 28px 0 0;
    opacity: 1;
  }

  .sc-step-num {
    top: 0;
    font-size: 96px;
  }

  .sc-step h3 {
    padding-top: 26px;
    font-size: 22px;
  }

  .sc-step p {
    font-size: var(--fs-body);
  }

  .sc-step-shot {
    display: block;
    margin: 18px 0 0;
    overflow: hidden;
    border-radius: 16px;
    background: var(--sc-stage-bg);
    box-shadow: var(--shadow);
  }

  .sc-step-shot img {
    display: block;
    width: 100%;
    height: auto;
  }

  .sc-step:last-child .sc-step-shot {
    max-width: 340px;
    margin-inline: auto;
  }

  .sc-product-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
    margin-top: 24px;
  }

  .sc-product.is-flipped .sc-gallery {
    order: 0;
  }

  .sc-product.is-premium {
    margin-inline: calc(-1 * var(--mob-gutter, 16px));
    padding: 32px var(--mob-gutter, 16px);
    border-radius: 0;
  }

  .sc-gallery {
    grid-template-columns: minmax(0, 1fr);
  }

  .sc-thumbs {
    display: none;
  }

  .sc-stage {
    aspect-ratio: 1 / 1;
    border-radius: 18px;
  }

  .sc-stage-track img {
    inset: 12% 4% 11%;
    width: 92%;
    height: 77%;
  }

  .sc-badge {
    top: 12px;
    left: 12px;
    padding: 5px 12px;
    font-size: var(--fs-caption);
  }

  .sc-info h3 {
    font-size: 26px;
  }

  .sc-desc {
    margin-top: 14px;
    font-size: var(--fs-body);
  }

  .sc-perks {
    gap: 12px;
    margin-top: 20px;
    font-size: var(--fs-body-sm);
  }

  .sc-perks li {
    font-size: var(--fs-body-sm);
  }

  .sc-perk-icon {
    width: 38px;
    height: 38px;
  }

  .sc-perk-icon::after {
    inset: 9px;
  }

  .sc-actions {
    flex-direction: column;
    margin-top: 24px;
  }

  .sc-cta,
  .sc-cta-ghost {
    width: 100%;
    min-height: 50px;
  }

  .sc-story-cta .hero-action,
  .sc-final .hero-action {
    justify-content: center;
    width: 100%;
  }
}
