@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 — навигация под доской */

  --bg: #f8fafd;
  --bg-grid: rgba(37, 99, 235, 0.06);
  --surface: #ffffff;
  --surface-soft: #f8fafd;
  --surface-strong: #eef1f6;
  --ink: #1f2937;
  --muted: #6b7280;
  /* было #9ca3af — 2.54:1 на белом, ниже WCAG AA. */
  --muted-faint: #6b7280;
  --line: #e6eaf2;
  --line-strong: #c9d1de;

  /* Иллюстрация на титульной (partials/hero_board.svg). Отдельные токены,
     а не переиспользование --surface/--blue: у доски своя роль и свой
     контраст, и подмешивать её в общие токены — значит привязать любую
     будущую правку темы к картинке. 14.10.2026, при замене растрового
     board-hero-radial.png на вектор. */
  --hero-light: #ffffff;
  --hero-dark: #86a2cf;
  --hero-frame: #dde5f2;
  --hero-edge-a: #e8edf6;
  --hero-edge-b: #d6deec;
  --hero-shadow: rgba(39, 61, 102, 0.16);
  --hero-piece-light: #ffffff;
  --hero-piece-light-edge: #94a7c6;
  --hero-piece-dark: #2b3c5e;
  --hero-piece-dark-edge: #1b2740;
  --hero-card: #ffffff;
  --hero-card-edge: #e6ecf6;
  --hero-accent: #2563eb;
  --hero-ring-bg: #e3eafa;
  --blue: #2563eb;
  --blue-dark: #1d4ed8;
  --performance: #4a7dff;
  /* UI-акцент по визуальному референсу (Design Specification, 03.08.2026):
     indigo, отдельно от --blue. Применяется к .primary-button, активному
     табу «Обзора» и синей стороне очков — остальные места на --blue пока
     не трогать (не эта итерация). Двухслойно, как --green/--green-text
     (U-5): --accent — заливка (кнопка, подчёркивание таба), --accent-text —
     тот же цвет как текст, безопасный по контрасту на --surface. */
  --accent: #3e48de;
  --accent-text: #3e48de;
  /* Идентичность чёрной стороны в очках партии (Перформанс) — измерено на
     референсе, отдельно от декоративного --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 сохранены (их уже
     использует много селекторов), поменялись только значения. */
  --shadow: 10px 10px 22px rgba(163, 177, 198, 0.55), -10px -10px 22px rgba(255, 255, 255, 0.9);
  --shadow-soft: 6px 6px 13px rgba(163, 177, 198, 0.45), -6px -6px 13px rgba(255, 255, 255, 0.85);
  /* Вдавленные состояния (поля ввода, неактивные пункты сегментов, нажатые
     кнопки) — та же пара теней, но inset. */
  --shadow-inset: inset 6px 6px 14px rgba(163, 177, 198, 0.55), inset -6px -6px 14px rgba(255, 255, 255, 0.9);
  --shadow-inset-soft: inset 3px 3px 8px rgba(163, 177, 198, 0.45), inset -3px -3px 8px rgba(255, 255, 255, 0.85);
  /* Светлая половина пары теней отдельным токеном — переиспользуется в
     цветных выпуклых бейджах качества хода (принцип 6: свой цвет сохраняется,
     объём добавляется), где тёмная половина берётся из --quality-*. */
  --neu-highlight: rgba(255, 255, 255, 0.8);
  --radius: 20px;
  /* Крупные контейнеры (карточка задачи «Квестов» и т.п.) — свой, больший
     радиус, не общий --radius. */
  --radius-lg: 28px;

  /* Пять уровней качества хода — измерено на референсе («Статистика ходов»,
     «Переломные моменты»): отдельная шкала от --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: var(--blue);
  --quality-book-text: var(--blue-dark);
  /* "Упущенная возможность" — запрос пользователя 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-book-soft: color-mix(in srgb, var(--quality-book) 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));
  /* Заливка .inspector-quality-badge (карточка хода) под белый текст —
     запрос пользователя 27.08.2026, посчитано перебором, не на глаз:
     --quality-brilliant/-excellent/-good САМИ ПО СЕБЕ дают с белым текстом
     всего 2.49/4.41/2.41:1 — ниже порога AA 4.5:1. -text-варианты для
     светлой темы уже достаточно тёмные (5.47/4.64/4.64:1), переиспользуются
     как есть. Общие --quality-* токены не трогаем — от них зависят другие
     места (иконки, «Лента ходов» и т.д.), это отдельные, только для бейджа. */
  --badge-brilliant: var(--quality-brilliant-text);
  --badge-excellent: var(--quality-excellent-text);
  --badge-good: var(--quality-good-text);
  --badge-ok: var(--quality-ok-text);
  --badge-book: var(--quality-book-text);
  --badge-missed: var(--quality-missed-text);
}

html[data-theme="dark"] {
  --bg: #0f172a;
  --bg-grid: rgba(255, 255, 255, 0.045);
  --surface: #1e293b;
  --surface-soft: #253248;
  --surface-strong: #2c3b54;
  --ink: #f8fafc;
  --muted: #cbd5e1;
  --muted-faint: #94a3b8;
  --line: #334155;
  --line-strong: #475569;

  /* Тёмная тема иллюстрации — нарисована, а не продавлена фильтрами:
     прежний PNG приходилось гасить brightness(0.72)/contrast(1.12), из-за
     чего он выглядел грязным. Светлые клетки здесь не белые, а светло-синие
     — чистое белое на тёмном фоне бьёт по глазам. */
  --hero-light: #cbd8ee;
  --hero-dark: #4a628f;
  --hero-frame: #46577a;
  --hero-edge-a: #2b3a55;
  --hero-edge-b: #1f2b42;
  --hero-shadow: rgba(0, 0, 0, 0.4);
  --hero-piece-light: #eef3fc;
  --hero-piece-light-edge: #7e93b8;
  --hero-piece-dark: #16203a;
  --hero-piece-dark-edge: #0b1223;
  --hero-card: #243149;
  --hero-card-edge: #38486a;
  --hero-accent: #60a5fa;
  --hero-ring-bg: #33415e;
  --blue: #60a5fa;
  --blue-dark: #93c5fd;
  --performance: #60a5fa;
  /* --accent/--player-black (заливки) не меняются — их контраст считается
     против белого текста кнопки, а не против фона страницы. Только
     текстовые варианты светлее: сами значения на --surface тёмной темы
     дают 2.2:1 и 2.4:1 — ниже AA даже для крупного текста (3:1). */
  --accent-text: #99a1f7;
  --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: 10px 10px 22px rgba(0, 0, 0, 0.55), -10px -10px 22px rgba(44, 58, 82, 0.55);
  --shadow-soft: 6px 6px 13px rgba(0, 0, 0, 0.5), -6px -6px 13px rgba(44, 58, 82, 0.5);
  --shadow-inset: inset 6px 6px 14px rgba(0, 0, 0, 0.55), inset -6px -6px 14px rgba(44, 58, 82, 0.55);
  --shadow-inset-soft: inset 3px 3px 8px rgba(0, 0, 0, 0.5), inset -3px -3px 8px rgba(44, 58, 82, 0.5);
  --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);
  /* -text здесь равен базовому (см. выше) — под белый текст плашки не
     годится (1.86/3.04/1.95:1). Отдельные, потемнее, посчитаны тем же
     перебором до первого значения ≥4.5:1: brilliant -38%, excellent -20%,
     good -36% к чёрному от своего --quality-* этой темы. */
  --badge-brilliant: #1c8376;
  --badge-excellent: #508254;
  --badge-good: #6c7d45;
  --badge-ok: #797219;
  /* --badge-book НЕ равен var(--quality-book-text) (=var(--blue-dark)
     =#93c5fd в тёмной теме — светлый, под белый текст плашки не годится,
     та же ловушка, что у badge-ok/missed выше). Взят литеральный
     #2563eb — тот же оттенок, что светлая тема использует как --blue,
     уже проверен на контраст в этой же палитре (5.17:1 с белым текстом,
     см. комментарий у --quality-book выше). */
  --badge-book: #2563eb;
  --badge-missed: #7f530c;
}

* {
  box-sizing: border-box;
}

html {
  min-width: 320px;
  background:
    radial-gradient(circle at 72% 28%, color-mix(in srgb, var(--blue) 14%, transparent), transparent 34%),
    linear-gradient(135deg, color-mix(in srgb, var(--blue) 8%, transparent), transparent 42%),
    linear-gradient(var(--bg-grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--bg-grid) 1px, transparent 1px),
    var(--bg);
  background-size: auto, auto, 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;
  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: 999px;
  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;
}

@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;
}

.app-shell {
  width: min(1720px, 100%);
  margin: 0 auto;
  padding: 22px 28px 28px;
}

.header-hover-trigger {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 41;
  height: 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
     пересчитывал размытие на каждом кадре выезда. Отсюда и ощущение,
     что шторка «подтормаживает». */
  transform: translate(-50%, -140%);
  /* Уезжает медленно и с задержкой — курсор успевает переехать на саму
     шторку, пока та ещё не начала прятаться. Выезжает быстро: 320ms на
     появление читались как «задерживается» (жалоба 07.10.2026).
     Уход замедлен до 520ms и переведён на ту же кривую, что у шкалы
     оценки, — по просьбе пользователя 07.10.2026 исчезновение было
     слишком резким. Кривая мягко стартует и мягко тормозит, поэтому
     движение не «обрывается» в конце, как у ease. */
  transition: transform 520ms cubic-bezier(0.4, 0, 0.2, 1) 180ms;
  pointer-events: none;
}

.header-hover-trigger:hover + .topbar,
.topbar.is-peek,
.topbar:hover,
.topbar:focus-within {
  transform: translate(-50%, 0);
  transition: transform 170ms ease 0ms;
  pointer-events: auto;
}

/* Устройства без hover (тач-планшеты, телефоны): шапка всегда видима.
   Привязка к возможностям ввода, а не к ширине экрана. */
@media (hover: none), (pointer: coarse) {
  .header-hover-trigger {
    display: none;
  }

  .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;
}

.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);
  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);
  /* Сам переключатель — выпуклый компактный элемент (ТЗ 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;
}

.muted-pill,
.small-badge {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

.analysis-loader {
  position: sticky;
  top: 12px;
  z-index: 19;
  display: none;
  gap: 8px;
  margin-bottom: 14px;
  padding: 12px 14px;
  border: 1px solid color-mix(in srgb, var(--blue) 28%, var(--line));
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  box-shadow: var(--shadow-soft);
  backdrop-filter: blur(16px);
}

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-detail {
  display: flex;
  align-items: center;
  min-height: 32px;
  padding: 8px 10px;
  border: 1px solid color-mix(in srgb, var(--blue) 18%, var(--line));
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--blue) 6%, var(--surface-soft));
  color: color-mix(in srgb, var(--ink) 78%, var(--blue-dark));
  font-size: var(--fs-body-sm);
  font-weight: 700;
  line-height: 1.35;
}

.loader-track {
  height: 10px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--surface-strong);
}

.loader-fill {
  width: 0;
  height: 100%;
  border-radius: inherit;
  background:
    linear-gradient(90deg, var(--green), var(--blue), var(--violet));
  box-shadow: 0 0 18px color-mix(in srgb, var(--blue) 36%, transparent);
  transition: width 260ms ease;
}

body:not(.page-analysis) .workspace,
body:not(.page-analysis) .deep-dive,
body:not(.page-analysis) .tables-section,
body:not(.page-analysis) .training-roadmap {
  display: none;
}

body:not(.page-home) .welcome-screen {
  max-height: 0;
  min-height: 0;
  margin: 0;
  overflow: hidden;
  opacity: 0;
  padding-block: 0;
  pointer-events: none;
  transform: scale(0.985);
  filter: blur(14px);
}

body:not(.page-quests) .quests-section,
body:not(.page-profile) .profile-section,
body:not(.page-contact) .contact-section {
  display: none;
}

body.page-analysis:not(.has-analysis) .analysis-stage,
body.page-analysis:not(.has-analysis) .deep-dive,
body.page-analysis:not(.has-analysis) .tables-section,
body.page-analysis:not(.has-analysis) .training-roadmap,
body.page-analysis:not(.has-analysis) .input-leaderboard-card {
  display: none;
}

body.has-analysis .analysis-empty {
  display: none;
}

.welcome-screen,
.info-section,
.quests-page,
.workspace,
.board-zone,
.quest-board,
.training-roadmap {
  scroll-margin-top: 112px;
}

.welcome-screen {
  position: relative;
  display: grid;
  grid-template-columns: minmax(420px, 0.68fr) minmax(520px, 1fr);
  gap: clamp(24px, 4vw, 56px);
  align-items: center;
  min-height: calc(100vh - 150px);
  margin-top: 26px;
  transition: opacity 420ms ease, transform 420ms ease, filter 420ms ease, max-height 520ms ease, padding 420ms ease;
}

.analysis-empty,
.info-section {
  /* .analysis-empty здесь задан повторно (дублирует более позднее правило
     .input-panel,.analysis-stage,...,.analysis-empty — border:0 там уже
     побеждает по порядку каскада), но .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;
  padding: clamp(18px, 4vw, 58px) 0 clamp(18px, 4vw, 58px) clamp(10px, 2vw, 42px);
}

.welcome-copy h2 {
  max-width: 680px;
  margin-bottom: 18px;
  color: #07142e;
  font-size: clamp(2.55rem, 4.8vw, 5.2rem);
  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;
}

.welcome-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  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 {
  max-width: 420px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  line-height: 1.4;
}

.welcome-visual {
  position: relative;
  min-width: 0;
  isolation: isolate;
  align-self: center;
  padding-block: 10px;
  /* 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%;
  background:
    radial-gradient(circle at 58% 50%, color-mix(in srgb, var(--blue) 14%, transparent), transparent 58%),
    linear-gradient(120deg, color-mix(in srgb, var(--surface) 58%, transparent), transparent 68%);
  filter: blur(34px);
}

.welcome-visual::after {
  right: 6%;
  bottom: 4%;
  width: 78%;
  height: 20%;
  background:
    linear-gradient(90deg, transparent, color-mix(in srgb, var(--blue) 18%, transparent), transparent);
  filter: blur(38px);
  opacity: 0.62;
}

.hero-board {
  position: relative;
  z-index: 1;
  display: block;
  /* Было width:108% + margin-right:-18px — картинка намеренно вылезала за
     колонку, и это не было видно, потому что её края растворяла радиальная
     маска. У вектора край резкий, и тот же вылет дал 20px горизонтальной
     прокрутки на 1009px. Поэтому ровно по колонке. */
  width: min(100%, 940px);
  height: auto;
  margin-inline: auto;
  transform: translateY(-4px);
  /* Ни маски, ни filter: у растра края кадра приходилось растворять
     радиальным градиентом, иначе в тёмной теме проступала прямоугольная
     граница. У вектора кадра нет — фон виден между фигурами сам. */
  filter: drop-shadow(0 26px 40px rgba(47, 104, 216, 0.13));
}

.hero-board .hero-cap {
  fill: var(--muted);
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
}

.hero-board .hero-big {
  fill: var(--ink);
  font-family: inherit;
  font-size: 38px;
  font-weight: 800;
  letter-spacing: -0.02em;
}

/* .hero-performance-fix удалён 14.10.2026: это была плашка, которой
   абсолютным позиционированием и поворотом на 6° закрывали дефект
   растрового рендера. У вектора дефекта нет — закрывать нечего. */

html[data-theme="dark"] .welcome-visual::before {
  background:
    radial-gradient(circle at 58% 50%, color-mix(in srgb, var(--blue) 28%, transparent), transparent 58%),
    linear-gradient(120deg, color-mix(in srgb, #ffffff 5%, transparent), transparent 68%);
  opacity: 0.82;
}

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);
  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.is-premium {
  text-decoration: none;
  transition: transform 160ms ease, box-shadow 160ms ease;
}

.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: var(--radius);
  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);
}

.profile-section {
  display: block;
}

.profile-grid {
  display: grid;
  grid-template-columns: minmax(280px, 1.3fr) minmax(240px, 1fr);
  gap: 16px;
  margin-top: 16px;
  align-items: start;
}

.profile-grid .endgame-wrap,
.profile-grid .leaderboard-card {
  min-width: 0;
}

@media (max-width: 860px) {
  .profile-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

.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-topbar,
.quest-disclosure,
.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);
}

.quest-topbar {
  padding: 12px clamp(16px, 2.3vw, 26px);
}

.quest-topbar h2 {
  max-width: 820px;
  margin: 0;
  font-size: clamp(1.3rem, 2vw, 1.85rem);
  line-height: 1.12;
}

/* Переключатель «Сценарии»/«Задачи» — новый раздел Квестов (запрос
   пользователя 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: 999px;
  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;
}

.quest-tasks-intro {
  margin: 0;
  padding: 0 clamp(16px, 2.3vw, 26px) 12px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  max-width: 720px;
}

/* Заголовок группы «Распознай мотив» (06.10.2026) — отделяет позиции на
   узнавание приёма от матов и тактики, где выигрыш подтверждён движком. */
.quest-tasks-group-title {
  margin: 18px 0 2px;
  padding: 0 clamp(16px, 2.3vw, 26px);
  font-size: var(--fs-h4);
  font-weight: 600;
}

.quest-tasks-group-note {
  margin: 0;
  padding: 0 clamp(16px, 2.3vw, 26px) 12px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  max-width: 720px;
}

/* «Площадка задач» (запрос пользователя 07.09.2026, второй заход —
   первая версия была статичным списком с атрибуцией "кто с кем играл",
   пользователь попросила убрать список/атрибуцию и сделать вход по
   разделу сложности → непрерывный поток задач одна за другой, по образцу
   тренажёра тактики Lichess). Экран входа (.quest-tasks-entry) и экран
   решения (.quest-tasks-arena) — ровно один из двух виден одновременно,
   переключаются через `hidden` в app.js, не через CSS-классы. */
.quest-tasks-entry {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 12px;
  padding: 0 clamp(16px, 2.3vw, 26px) 24px;
}

.quest-tasks-entry-card {
  display: grid;
  gap: 4px;
  padding: 18px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  text-align: left;
  cursor: pointer;
}

.quest-tasks-entry-card:hover {
  border-color: var(--accent);
}

.quest-tasks-entry-card strong {
  color: var(--ink);
  font-size: var(--fs-body);
}

.quest-tasks-entry-card > span:not(.quest-tasks-entry-count) {
  color: var(--muted);
  font-size: var(--fs-body-sm);
}

.quest-tasks-entry-count {
  margin-top: 6px;
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--accent);
}

.quest-tasks-arena {
  padding: 0 clamp(16px, 2.3vw, 26px) 24px;
  /* 520×1.3≈676, округлено до 680 — иначе увеличенная доска (см.
     .quest-tasks-board-panel .quest-board-frame ниже) упиралась бы в
     старый более узкий контейнер и не смогла бы вырасти. */
  max-width: 680px;
  /* Запрос пользователя 08.09.2026 — площадка задач висела у левого края
     секции Квестов (родитель #questTasksPanel шире её собственного
     max-width, без auto-отступов блок прижимался к началу). */
  margin: 0 auto;
}

.quest-tasks-arena-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 10px;
}

.quest-tasks-back {
  border: 0;
  background: none;
  color: var(--accent);
  font-weight: 600;
  font-size: var(--fs-body-sm);
  cursor: pointer;
  padding: 4px 0;
}

#matePuzzleProgress {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

.quest-tasks-board-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

/* Обёртка "идёт решение" внутри .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-complete-actions {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}

.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);
}

/* Подсказка/пропуск на площадке задач (запрос пользователя 08.09.2026) —
   свои классы, не переиспользуют .icon-button/.quest-tasks-back (те не под
   этот визуальный вес: нужна текстовая пилюля, не квадратная иконка и не
   голая ссылка). */
.quest-tasks-tools {
  display: flex;
  gap: 8px;
}

.quest-tasks-tool-button {
  flex: 1;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  padding: 8px 14px;
  cursor: pointer;
  box-shadow: var(--shadow-soft);
  transition: box-shadow 150ms ease;
}

.quest-tasks-tool-button:hover {
  box-shadow: var(--shadow);
}

.quest-tasks-tool-button:active {
  box-shadow: var(--shadow-inset-soft);
}

.quest-disclosure > summary {
  padding: 11px clamp(16px, 2.3vw, 26px);
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
}

.quest-disclosure > summary::-webkit-details-marker {
  display: none;
}

.quest-disclosure > summary::after {
  content: "▾";
  margin-left: 8px;
  color: var(--muted);
}

.quest-disclosure[open] > summary::after {
  content: "▴";
}

.quest-disclosure-body {
  display: grid;
  gap: 12px;
  padding: 0 clamp(16px, 2.3vw, 26px) 14px;
}

.quest-disclosure p:not(.eyebrow) {
  max-width: 760px;
  margin: 0;
  color: var(--muted);
  font-weight: 600;
  line-height: 1.55;
}

.quest-progress-panel {
  display: grid;
  gap: 10px;
  padding: 14px;
}

.quest-progress-panel div {
  display: grid;
  gap: 4px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
}

.quest-progress-panel strong {
  font-size: var(--fs-h2);
}

.quest-progress-panel span,
.quest-card span,
.quest-card small,
.quest-brief span,
.contact-actions span {
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  line-height: 1.4;
}

.quest-level-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 12px;
}

.quest-level-strip span,
.quest-rewards span {
  min-height: 34px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

/* Активная задача — на всю ширину, «Другие квесты» под ней, не сбоку
   (запрос пользователя 12.08.2026, редизайн страницы активного квеста).
   Раньше здесь была двухколоночная сетка (доска+каталог рядом) — теперь
   единственная колонка, каталог просто следующий блок в потоке. */
.quest-layout {
  display: grid;
  gap: 24px;
}

/* «Другие квесты» под активной задачей. Сама .quest-catalog — просто
   вертикальный стек (заголовок + сетка карточек-уровней), сетка карточек
   вынесена в .quest-level-grid (запрос пользователя 13.08.2026: ровно
   5 карточек-уровней вместо одной карточки на каждый квест). */
.quest-catalog {
  display: grid;
  gap: 12px;
}

.quest-catalog-title {
  margin: 0;
}

/* Ровно 5 карточек-уровней в ряд на десктопе; auto-fit сам уплотняет
   ряд, если места меньше — карточки переносятся, но порядок в разметке
   (Бронза → Серебро → Золото → Платина → Легенда) не меняется никогда,
   т.к. это порядок DOM, а не CSS order/сортировка по данным. */
.quest-level-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
}

.quest-level-card {
  display: grid;
  align-content: start;
  gap: 10px;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 90%, transparent);
  box-shadow: var(--shadow-soft);
}

.quest-level-head {
  display: flex;
  align-items: center;
  gap: 8px;
}

.quest-level-items {
  display: grid;
  gap: 10px;
}

.quest-level-divider {
  height: 1px;
  margin: 0;
  background: var(--line);
}

.quest-level-card .quest-tier-label.gold {
  color: var(--amber-text);
}

.quest-level-card .quest-tier-label.legend,
.quest-level-card .quest-tier-label.platinum {
  color: var(--violet-text);
}

.quest-level-card .quest-tier-label.silver {
  color: var(--blue-dark);
}

.quest-level-card .quest-tier-label.bronze {
  color: var(--bronze-text);
}

.quest-level-card .quest-tier-label {
  font-size: var(--fs-caption);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

/* Внутри карточки уровня .quest-card — строка группы, а не отдельная
   плитка каталога (эмблема+подпись уровня показаны один раз в
   .quest-level-head, не на каждом квесте): без тени и с меньшими
   отступами, но цветная обратная связь hover/выбрано/решено/показано
   у самой .quest-card (выше) сохраняется — статус каждого квеста
   по-прежнему виден. */
.quest-level-card .quest-card {
  padding: 4px 2px;
  box-shadow: none;
}

/* Редизайн карточки квеста в узкой колонке каталога (запрос пользователя
   12.08.2026, задача «визуальное оформление раздела Квесты») — эмблема +
   короткая подпись уровня вместо крупной цветной плашки на всю ширину,
   явная иерархия текста (название > краткая инфо о партии > описание),
   статус со значком (✓/▶/🔒). Только вид: данные квестов, статусы
   (is-completed/is-revealed), клик-по-карточке-выбирает-квест —
   не менялись, только новая вёрстка/CSS. */
.quest-card {
  display: grid;
  gap: 6px;
  width: 100%;
  padding: 14px;
  border: 0;
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 90%, transparent);
  color: var(--ink);
  text-align: left;
  box-shadow: var(--shadow-soft);
  transition: box-shadow 150ms ease;
}

/* Статус карточки раньше кодировался цветом рамки — рамок в этом
   редизайне больше нет нигде (принцип 5), поэтому статус теперь цветное
   свечение (тот же приём, что у .critical-item-badge на Анализе): сам
   смысл цвета не меняется, меняется только его форма (принцип 6). Фон
   по-прежнему тонируется — это отдельный, самостоятельный сигнал статуса,
   не только рамка/тень, его не трогаю. */
.quest-card:hover,
.quest-card.is-selected {
  background: color-mix(in srgb, var(--blue) 8%, var(--surface));
  box-shadow: 6px 6px 13px color-mix(in srgb, var(--blue) 30%, transparent), -6px -6px 13px var(--neu-highlight);
}

.quest-card.is-completed {
  background: color-mix(in srgb, var(--green) 8%, var(--surface));
  box-shadow: 6px 6px 13px color-mix(in srgb, var(--green) 30%, transparent), -6px -6px 13px var(--neu-highlight);
}

.quest-card.is-revealed {
  background: color-mix(in srgb, var(--amber) 7%, var(--surface));
  box-shadow: 6px 6px 13px color-mix(in srgb, var(--amber) 30%, transparent), -6px -6px 13px var(--neu-highlight);
}

/* Эмблема — обрезанный кроп готового листа иконок (не нарисована заново),
   короткая подпись рядом — по ТЗ раздел 4: «не нужно повторять крупную
   текстовую плашку, уровень воспринимается через эмблему + подпись».
   Раньше эмблема+подпись повторялись на каждой карточке квеста; теперь
   показаны один раз в .quest-level-head (запрос пользователя 13.08.2026). */
.quest-tier-emblem {
  flex: 0 0 auto;
  width: 36px;
  height: 36px;
  object-fit: contain;
}

/* Иерархия текста по ТЗ раздел 6: название — главный элемент, краткая
   информация о партии — вторична, описание — самое спокойное и обрезано
   до 2 строк, чтобы карточка не растягивалась по высоте (раздел 15). */
.quest-card .quest-card-title {
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: 1.3;
}

.quest-card .quest-card-meta {
  display: block;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
  line-height: 1.4;
}

/* 18.08.2026 (запрос пользователя, редизайн Квестов): полное описание
   каталога — 10 карточек × отдельное предложение — было главным вкладом
   в "стену текста" ещё до выбора квеста. Название + короткая meta-строка
   остаются видны всегда, полное предложение — только у наведённой/
   сфокусированной/уже выбранной карточки. */
.quest-card .quest-card-text {
  display: none;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 500;
  line-height: 1.4;
}

.quest-card:hover .quest-card-text,
.quest-card:focus-visible .quest-card-text,
.quest-card.is-selected .quest-card-text {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  line-clamp: 2;
  overflow: hidden;
}

/* Статус — иконка (простой текстовый символ, не картинка — см. отчёт
   перед редизайном) + текст; отдельный цвет на каждое состояние. */
.quest-card .quest-card-progress {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  justify-self: start;
  min-height: 28px;
  padding: 5px 10px;
  border: 1px solid color-mix(in srgb, var(--blue) 18%, var(--line));
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--blue) 7%, var(--surface-soft));
  color: color-mix(in srgb, var(--blue-dark) 62%, var(--ink));
  font-size: var(--fs-caption);
  font-weight: 700;
}

.quest-card.is-completed .quest-card-progress {
  border-color: color-mix(in srgb, var(--green) 30%, var(--line));
  background: color-mix(in srgb, var(--green) 12%, var(--surface-soft));
  color: var(--green-text);
}

.quest-card.is-revealed .quest-card-progress {
  border-color: color-mix(in srgb, var(--amber) 30%, var(--line));
  background: color-mix(in srgb, var(--amber) 12%, var(--surface-soft));
  color: var(--amber-text);
}

.quest-card .quest-card-progress-icon {
  color: inherit;
  font-size: 0.95em;
  font-weight: 700;
  line-height: 1;
}

.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);
  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-toggle-caret {
  color: var(--muted);
}

.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);
  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-guide (3 шага-инструкции над доской) удалён вместе с разметкой
   (запрос пользователя 12.08.2026) — пользователь понимает механику
   через саму интерактивную доску, инструкции ничем не заменялись. */

/* Иерархия действий: акцент страницы принадлежит доске, поэтому primary-кнопки
   здесь нет вовсе. «Показать настоящий ход» — отказ от самостоятельного
   решения, у него минимальный вес. */
.quest-action-secondary {
  border-color: color-mix(in srgb, var(--blue) 34%, var(--line));
  color: color-mix(in srgb, var(--blue-dark) 62%, var(--ink));
}

.quest-reveal-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 34px;
  padding: 0 10px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.quest-reveal-button:hover {
  color: var(--ink);
}

.quest-reveal-button:focus-visible {
  border-color: var(--blue);
  outline: none;
}

.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;
}

/* 19.08.2026 (запрос пользователя, п.4): flex вместо grid — нужно, чтобы
   "Историческая линия" сама дотягивалась до нижнего края доски (flex:1 на
   .quest-line ниже), а когда снизу появляется "Миссия решена", линия сама
   чуть сжималась, отдавая ей место — оба тянутся к одной и той же нижней
   границе (высоте доски), а не оставляют пустоту или отдельно скроллятся.
   overflow-y:auto остаётся подстраховкой на случай, если контента реально
   больше, чем высота доски. */
.quest-info-scroll {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 10px;
  min-height: 0;
  overflow-y: auto;
  padding-right: 4px;
}

.quest-board-head,
.quest-board-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.quest-board-head {
  order: 1;
  /* Единственный оставшийся ребёнок (после удаления подписи "Позиция",
     запрос пользователя 18.08.2026, п.8) — держим его у правого края,
     как и раньше, а не даём space-between схлопнуть его влево. */
  justify-content: flex-end;
}

.quest-board-actions {
  flex-wrap: wrap;
  order: 3;
}

.quest-board-actions .compact {
  flex: 1 1 130px;
  justify-content: center;
}

.quest-board-head div {
  display: grid;
  gap: 2px;
}

.quest-board-head span,
.quest-board-head small {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
  text-transform: uppercase;
}

.quest-board-head strong {
  font-size: var(--fs-body);
}

/* Статистика стоит ПОСЛЕ обратной связи: сначала результат хода, потом цифры.
   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: var(--radius);
  /* Внешняя тень на контейнере (ТЗ 27.08.2026, 5.8) — своя копия того же
     приёма, что у .chess-board-frame на Анализе, свой класс — Н-5. */
  box-shadow: var(--shadow);
}

/* Доска ПЛОЩАДКИ ЗАДАЧ +30% (запрос пользователя 08.09.2026 — «это
   касается только задач», НЕ рукописных сценариев Квестов, которые
   делят класс .quest-board-frame с этой же площадкой). Селектор
   специфичнее общего правила выше за счёт родителя (.quest-tasks-
   board-panel есть только в разметке площадки задач) — правило
   рукописного сценария (сам .quest-board-frame) не трогается. Числа —
   360/46vw/620 базового правила ×1.3. */
.quest-tasks-board-panel .quest-board-frame {
  width: min(100%, clamp(468px, 60vw, 806px));
  /* .quest-tasks-board-panel — flex-column, .quest-board-frame сам по
     себе с фиксированной шириной (см. выше) — без align-self он всё
     равно прижимался к левому краю панели, а не центрировался в ней. */
  align-self: center;
}

.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(--blue));
  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(--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(--blue) 70%, #fff);
}

.quest-square.is-last-to::after {
  background: color-mix(in srgb, var(--blue) 26%, transparent);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--blue) 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(--blue) 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(--blue) 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(--blue) 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(--blue);
}

.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:
    linear-gradient(135deg, color-mix(in srgb, var(--green) 8%, var(--surface)), var(--surface-soft));
}

.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-result-rewards (плоские текстовые плашки) удалены 18.08.2026 —
   заменены на .quest-result-badges/.quest-celebration-badges с иконками,
   см. ниже в файле. .quest-rewards span.is-earned — превью-плашки ДО
   решения (не в результате), эта строка всё ещё используется. */
.quest-rewards span.is-earned {
  border-color: color-mix(in srgb, var(--green) 34%, var(--line));
  background: var(--green-soft);
  color: color-mix(in srgb, var(--green-text) 58%, var(--ink));
}

.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:
    linear-gradient(135deg, color-mix(in srgb, var(--green) 11%, var(--surface)), var(--surface-soft));
}

.quest-brief .quest-insight strong {
  color: color-mix(in srgb, var(--green-text) 48%, var(--ink));
}

.quest-rewards {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.quest-rewards.is-hidden-duplicate {
  display: none;
}

/* Оверлей-поздравление при завершении линии своим ходом (запрос
   пользователя 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;
}

.empty-flow span {
  min-height: 34px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

.workspace {
  display: grid;
  grid-template-columns: minmax(300px, 420px) minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

body.page-analysis.has-analysis.is-input-collapsed .workspace {
  grid-template-columns: 56px minmax(0, 1fr);
}

.input-panel,
.analysis-stage,
.coach-panel,
.deep-dive,
.training-roadmap,
.endgame-wrap,
.analysis-empty {
  /* Рамка убрана (ТЗ 27.08.2026) — этот общий класс держал ОДНОВРЕМЕННО
     рамку и уже неоморфную box-shadow: var(--shadow) (значение токена
     поменялось раньше в этой же задаче), из-за чего у всей левой панели
     «Источник партии» (.input-panel) и всего правого блока результата
     (.analysis-stage) сохранялась плоская обводка поверх объёма —
     пропущено в прошлом заходе по «Анализу», досмотрел только вложенные
     карточки, не сами внешние обёртки. */
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
}

.analysis-empty {
  display: grid;
  min-height: 460px;
  align-content: center;
  gap: 22px;
  padding: clamp(22px, 4vw, 48px);
}

.analysis-empty h2 {
  max-width: 680px;
  margin-bottom: 10px;
  font-size: clamp(1.7rem, 4vw, 3.7rem);
  line-height: 1;
}

.analysis-empty p:not(.eyebrow) {
  max-width: 620px;
  margin-bottom: 0;
  color: var(--muted);
  font-weight: 600;
  line-height: 1.55;
}

.empty-flow {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.input-panel {
  position: sticky;
  top: 12px;
  display: flex;
  min-height: calc(100vh - 32px);
  flex-direction: column;
  gap: 14px;
  padding: 16px;
}

body.page-analysis.has-analysis .input-panel {
  max-height: calc(100vh - 32px);
  min-height: auto;
  overflow: auto;
  scrollbar-width: thin;
}

.input-panel-toggle {
  display: none;
  flex-shrink: 0;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 42px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
  color: var(--ink);
  cursor: pointer;
  transition: background-color 140ms ease, border-color 140ms ease;
}

body.page-analysis.has-analysis .input-panel-toggle {
  display: flex;
}

.input-panel-toggle:hover {
  border-color: var(--blue);
  background: color-mix(in srgb, var(--blue) 8%, var(--surface-soft));
}

.input-panel-body {
  display: flex;
  min-width: 0;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 14px;
  margin-top: 14px;
}

body.is-input-collapsed .input-panel-body {
  display: none;
}

body.page-analysis.has-analysis.is-input-collapsed .input-panel {
  padding: 10px 6px;
  overflow: visible;
}

body.is-input-collapsed .input-panel-toggle {
  min-height: 200px;
  flex-direction: column;
  justify-content: flex-start;
  padding: 14px 0;
}

.input-panel-toggle-icon {
  font-size: var(--icon-lg);
  line-height: 1;
  flex-shrink: 0;
}

.input-panel-toggle-label {
  font-size: var(--fs-body-sm);
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
}

body.is-input-collapsed .input-panel-toggle-label {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: var(--fs-caption);
  letter-spacing: 0.02em;
  color: var(--muted);
}

body.page-analysis.has-analysis .field textarea {
  min-height: 180px;
}

.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;
}

.chart-zoom-label {
  display: grid;
  min-width: 96px;
  height: 34px;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  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). */
.panel-heading-title {
  display: flex;
  align-items: center;
  gap: 8px;
}

.panel-heading-info {
  display: grid;
  width: 18px;
  height: 18px;
  place-items: center;
  border-radius: 999px;
  color: var(--muted);
  font-size: var(--icon-sm);
  line-height: 1;
}

.icon-button {
  display: grid;
  width: 42px;
  height: 42px;
  flex: 0 0 auto;
  place-items: center;
  border: 0;
  border-radius: var(--radius);
  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;
}

.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);
  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;
}

.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: 4px 4px 9px color-mix(in srgb, var(--blue) 45%, transparent), -3px -3px 8px color-mix(in srgb, white 24%, transparent);
  /* 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);
  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: 8px 8px 16px color-mix(in srgb, var(--accent) 42%, transparent), -4px -4px 12px color-mix(in srgb, white 22%, transparent);
  transition: box-shadow 150ms ease, transform 150ms ease;
}

.primary-button:hover {
  box-shadow: 10px 10px 20px color-mix(in srgb, var(--accent) 48%, transparent), -5px -5px 14px color-mix(in srgb, white 26%, transparent);
}

.primary-button:active {
  box-shadow: inset 4px 4px 9px color-mix(in srgb, black 35%, var(--accent)), inset -3px -3px 8px color-mix(in srgb, white 18%, var(--accent));
  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);
}

.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;
}

.recent-games-list {
  display: grid;
  gap: 8px;
  max-height: 360px;
  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 {
  border-left-color: var(--amber);
}

.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: 999px;
  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: calc(var(--radius) + 6px);
}

/* Подсветка блока игрока по стороне (запрос пользователя 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: 999px;
  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;
}

/* Пункт №7: строка статуса живёт внутри .input-panel-body, а та скрывается
   после анализа (U-10) — сообщение об ошибке становилось невидимым. Баннер
   вынесен на уровень .workspace: .analysis-stage не годится, он сам скрыт
   до первого успешного анализа, то есть как раз когда падает первый разбор.
   grid-column растягивает баннер над обеими колонками рабочей области. */
.analysis-error {
  grid-column: 1 / -1;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
  padding: 12px 14px;
  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 > div {
  display: grid;
  gap: 3px;
  min-width: 0;
}

.analysis-error strong {
  color: var(--mistake-text);
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

.analysis-error span {
  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;
}

body.page-analysis.has-analysis .analysis-stage {
  grid-column: 2;
}

.tab-panel > .move-table-wrap,
.tab-panel > .analysis-review-grid,
.tab-panel > .tables-section,
.tab-panel > .analysis-facts-details,
.tab-panel > .training-roadmap,
.tab-panel > .deep-dive {
  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-stage,
   поэтому собственные горизонтальные отступы им больше не нужны. */
.tab-panel > .deep-dive,
.tab-panel > .training-roadmap {
  padding-inline: 0;
}

.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;
}

.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);
  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(--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;
}

.board-branch-banner button {
  flex-shrink: 0;
  width: auto;
  padding: 0 12px;
}

.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: var(--radius);
  /* Мягкая внешняя тень на контейнере (ТЗ 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(--blue);
}

.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(--blue) 30%, transparent);
  box-shadow: inset 0 0 0 3px color-mix(in srgb, var(--blue) 84%, #fff);
  opacity: 1;
  transform: scale(0.94);
}

.square.is-best-from::after {
  border: 1.5px dashed color-mix(in srgb, var(--blue) 68%, #fff);
  background: color-mix(in srgb, var(--blue) 8%, transparent);
  opacity: 0.72;
}

.square.is-best-to::after {
  border: 1.5px solid color-mix(in srgb, var(--green) 62%, var(--blue));
  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 MOVE_QUALITY_ICON_FILE) — держим все три места синхронно:
   brilliant→brilliant, excellent-группа (best/mate/great)→great (стрелка
   вверх), good→best (звезда), inaccuracy→mistake (стрелка вниз),
   mistake→miss (крестик), blunder→blunder (двойная стрелка вниз). Раньше
   здесь была отдельная, не совпадающая с остальным сайтом группировка
   (--green на mate/brilliant/best/good вместе, --blue на great) — она
   больше не нужна, значения ниже приведены к общему мэппингу.
   Селектор ".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/miss.png");
}

.square .square-quality-badge.severity-inaccuracy {
  background-image: url("/static/img/move-quality/mistake.png");
}

.square .square-quality-badge.severity-good {
  background-image: url("/static/img/move-quality/best.png");
}

/* "Неплохой" — новой категории нет в готовом наборе из 6 иконок, обычный
   цветной кружок вместо картинки. */
.square .square-quality-badge.severity-missed {
  background-image: none;
  background-color: transparent;
  border: 2px solid var(--quality-missed);
}

.square .square-quality-badge.severity-ok {
  background-image: none;
  background-color: var(--quality-ok);
}

/* "Теоретический ход" — тоже нет готовой иконки, но сознательно не
   переиспользует форму "ok"/"missed" (кружок): квадрат — узнаваемый
   отдельный силуэт, "форма" книжной страницы/карточки, не оценочный
   кружок. Тот же приём различения, что уже применён к "missed" (залитый
   vs полый) — здесь различение через форму, не заливку. */
.square .square-quality-badge.severity-book {
  background-image: none;
  background-color: transparent;
  border: 2px solid var(--quality-book);
  border-radius: 3px;
}

.square .square-quality-badge.severity-mate,
.square .square-quality-badge.severity-best,
.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");
}

.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(--blue) 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(--blue) 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(--blue));
}

.best-move-arrow.is-match .best-move-head {
  fill: color-mix(in srgb, var(--green) 76%, var(--blue));
}

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(--blue) 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(--blue) 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(--blue) 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: calc(var(--radius) + 4px);
  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: calc(var(--radius) + 4px);
  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 {
  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);
  padding: 12px 14px;
}

/* «Лента ходов» — единственная карточка «Обзора», где рамку явно попросили
   оставить как было (запрос пользователя 27.08.2026): свой класс есть
   только на этой карточке (проверено — .board-moves-wrap больше нигде в
   разметке не встречается), поэтому безопасно откатывать именно здесь. */
.board-moves-wrap {
  border: 1px solid var(--line);
  background: var(--surface);
  box-shadow: none;
}

.inspector-card {
  display: grid;
  align-content: start;
  gap: 10px;
}

.side-card-title {
  margin: 0 0 8px;
  color: var(--ink);
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.2;
}

.move-inspector {
  display: grid;
  align-content: start;
  gap: 8px;
  width: 100%;
  min-height: 108px;
  padding: 0;
  border-left: 0;
}

/* Пустое состояние (нет выбранного хода) — сохранившийся текстовый вид. */
.move-inspector strong {
  font-size: var(--fs-body);
}

/* :not([class]) — иначе правило (0,1,1) перебивает собственный цвет бейджа
   и других классованных span внутри инспектора (специфичность 0,1,0). */
.move-inspector > span:not([class]) {
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 400;
  line-height: 1.38;
}

/* Заполненное состояние (итерация 3): категория читается первой — бейдж
   идёт до записи хода, а не рядом с ней; «лучше было» — второй уровень
   (мельче и тише). Главный блок правой колонки, поэтому запись хода
   крупнее прежнего (h2, не h3), а иконка фигуры — заметно крупнее текста. */
.inspector-eyebrow {
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 600;
}

.inspector-quality-badge {
  display: inline-flex;
  align-self: start;
  /* .move-inspector — grid: justify-self (не align-self) держит бейдж
     по ширине контента, иначе grid растягивает его на всю колонку. */
  justify-self: start;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 0 12px;
  border-radius: 999px;
  background: var(--quality-blunder);
  color: #fff;
  font-size: var(--fs-body-sm);
  font-weight: 700;
}

.inspector-quality-badge.severity-mistake {
  background: var(--quality-mistake);
}

.inspector-quality-badge.severity-inaccuracy {
  background: var(--quality-inaccuracy);
}

/* Положительные степени качества хода — приведено к той же группировке,
   что «Статистика ходов» и «Лента ходов» (запрос пользователя 27.08.2026):
   brilliant — своя строка, mate/best/great — одна строка «Отличные»,
   good — отдельная строка «Хорошие». Старая 2-цветная схема (--green на
   4 категории сразу, --blue на 1) не совпадала с подсчётом в таблице. */
.inspector-quality-badge.severity-brilliant {
  background: var(--badge-brilliant);
}

.inspector-quality-badge.severity-mate,
.inspector-quality-badge.severity-best,
.inspector-quality-badge.severity-great {
  background: var(--badge-excellent);
}

.inspector-quality-badge.severity-good {
  background: var(--badge-good);
}

.inspector-quality-badge.severity-ok {
  background: var(--badge-ok);
}

.inspector-quality-badge.severity-book {
  background: var(--badge-book);
}

.inspector-quality-badge.severity-missed {
  background: var(--badge-missed);
}

.move-inspector .inspector-move {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  font-size: var(--fs-h2);
  font-weight: 700;
  line-height: 1.15;
}

/* Раньше (по референсу) запись хода была всегда нейтрального цвета —
   качество передавал только бейдж выше. Запрос пользователя 07.08.2026
   сначала отменил это только для иконки фигуры, вторым заходом в тот же
   день — распространил на весь ход целиком (номер + фигура + клетка,
   например «14.♘d3»): красим сам .inspector-move, а не отдельно иконку,
   остальной текст наследует цвет. Палитра та же, что уже используется в
   .inspector-quality-badge выше. */

.inspector-move-icon {
  font-size: 1.3em;
  line-height: 1;
}

.inspector-move.severity-blunder {
  color: var(--quality-blunder-text);
}

.inspector-move.severity-mistake {
  color: var(--quality-mistake-text);
}

.inspector-move.severity-inaccuracy {
  color: var(--quality-inaccuracy-text);
}

.inspector-move.severity-mate,
.inspector-move.severity-brilliant,
.inspector-move.severity-best,
.inspector-move.severity-good {
  color: var(--green-text);
}

.inspector-move.severity-great {
  color: var(--blue-dark);
}

.inspector-move.severity-ok {
  color: var(--quality-ok-text);
}

.inspector-move.severity-book {
  color: var(--quality-book-text);
}

.inspector-move.severity-missed {
  color: var(--quality-missed-text);
}

.inspector-best {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 400;
}

/* Короткое объяснение идеи рекомендуемого хода под «Лучше было: ...»
   (запрос пользователя 07.08.2026) — текст берёт уже существующая, но до
   этого нигде не подключённая moveReason() (app.js). Строка «Лучше было
   {ход}» из её текста для inaccuracy/mistake/blunder убрана в i18n —
   дублировала .inspector-best выше. */
.inspector-reason {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 400;
  line-height: 1.4;
}

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);
  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);
}

.input-leaderboard-card {
  display: grid;
  gap: 8px;
  padding: 12px;
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
}

.input-leaderboard-card h2 {
  margin-bottom: 0;
  font-size: var(--fs-body);
}

.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);
  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;
}

/* Ход без замечаний: карточка на месте и видна, просто спокойная. */
.coach-move-note.is-quiet {
  border-left-color: var(--line-strong);
  background: color-mix(in srgb, var(--blue) 5%, var(--surface));
}

@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;
}

.coach-move-note.is-good {
  border-left-color: var(--green);
  background: color-mix(in srgb, var(--green) 12%, var(--surface));
}

.coach-move-note.is-bad {
  border-left-color: var(--red);
  background: color-mix(in srgb, var(--red) 10%, var(--surface));
}

/* «Итог партии» — четыре прежние карточки под одним свёрнутым заголовком.
   Раньше они раскрывались сразу и вываливали всю сводку; теперь это итог,
   который открывают по желанию. */
.coach-summary-total > summary {
  padding: 10px 0;
  cursor: pointer;
  list-style: none;
}

.coach-summary-total > summary::-webkit-details-marker {
  display: none;
}

.coach-summary-total > summary h3 {
  display: inline-flex;
  gap: 8px;
  align-items: center;
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.coach-summary-total > summary h3::after {
  content: "▸";
  transition: transform 160ms ease;
}

.coach-summary-total[open] > summary h3::after {
  transform: rotate(90deg);
}

.coach-summary-total > summary:focus-visible {
  border-radius: 6px;
  outline: 3px solid var(--blue);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .coach-summary-total > summary h3::after {
    transition: none;
  }
}

/* План улучшения — сворачиваемый (19.10.2026). Заголовок остаётся видимым
   всегда и работает кнопкой; раскрытое содержимое появляется под ним. */
.training-roadmap-details > summary {
  cursor: pointer;
  list-style: none;
}

.training-roadmap-details > summary::-webkit-details-marker {
  display: none;
}

.training-roadmap-details > summary .section-heading h2::after {
  content: "▸";
  display: inline-block;
  margin-left: 10px;
  color: var(--muted);
  transition: transform 160ms ease;
}

.training-roadmap-details[open] > summary .section-heading h2::after {
  transform: rotate(90deg);
}

.training-roadmap-details > summary:focus-visible {
  border-radius: 8px;
  outline: 3px solid var(--blue);
  outline-offset: 4px;
}

@media (prefers-reduced-motion: reduce) {
  .training-roadmap-details > summary .section-heading h2::after {
    transition: none;
  }
}

.coach-summary-grid {
  display: grid;
  /* Раньше 3 равные колонки — рассчитано на полную ширину вкладки.
     Теперь вкладка «Секундант» всегда живёт в узкой .board-side
     (запрос пользователя 15.08.2026: доска неподвижна, только правая
     панель переключается) — 3 колонки сжимали карточки до ~50px и
     давали горизонтальный оverflow. Один столбец подходит для любой
     ширины .board-side без media-query. */
  grid-template-columns: 1fr;
  gap: 16px;
}

.coach-summary-card {
  padding: 16px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
}

.coach-summary-card h3 {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-caption);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* .coach-summary-card теперь <details> (запрос пользователя 16.08.2026,
   ТЗ «реструктуризация layout», п.12) — сами карточки/типографика/цвет
   не тронуты, только заголовок стал кликабельным <summary> со своим
   чеквроном (тот же паттерн, что у .analysis-facts-details). Margin у h3
   убран отсюда и перенесён на summary — иначе он давил бы и в свёрнутом
   состоянии, где под ним ничего нет. */
.coach-summary-card summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 0 0 12px;
  cursor: pointer;
  list-style: none;
}

.coach-summary-card[open] summary {
  margin-bottom: 12px;
}

.coach-summary-card:not([open]) summary {
  margin-bottom: 0;
}

.coach-summary-card summary::-webkit-details-marker {
  display: none;
}

.coach-summary-card summary::after {
  content: "▾";
  flex: 0 0 auto;
  color: var(--muted);
  font-size: var(--icon-sm);
  transition: transform 160ms ease;
}

.coach-summary-card:not([open]) summary::after {
  transform: rotate(-90deg);
}

.coach-summary-card p {
  margin: 0;
  color: var(--ink);
  line-height: 1.55;
}

.coach-summary-list p {
  margin: 0 0 10px;
  color: var(--ink);
  line-height: 1.55;
}

.coach-summary-list p:last-child {
  margin-bottom: 0;
}

/* Мини-иконки фигур рядом с человеко-читаемым описанием хода
   ("слон бьёт пешку") — переиспользуют существующий набор Cburnett,
   те же файлы, что и доска. */
.coach-piece-icon {
  width: 18px;
  height: 18px;
  vertical-align: -3px;
  margin-right: 3px;
}

/* border-left-color тут НЕ менялся (Н-3 в docs/DECISIONS.md: "не
   менять цвета обводок-индикаторов" — это про сам цвет каёмки). Фон-подсветка
   ниже — добавка, не замена: те же токены var(--green)/var(--red)/var(--teal),
   просто ещё и лёгким фоном всей карточки, а не только 3px полоской (запрос
   пользователя 19.08.2026 — карточки "Что получилось"/"Что не получилось"
   визуально не отличались от нейтральных, хотя по смыслу одна хорошая
   новость, другая плохая). */
.coach-summary-card.is-positive {
  border-left-color: var(--green);
  background: color-mix(in srgb, var(--green) 6%, var(--surface-soft));
}

.coach-summary-card.is-negative {
  border-left-color: var(--red);
  background: color-mix(in srgb, var(--red) 6%, var(--surface-soft));
}

.coach-summary-card.is-action {
  border-left-color: var(--teal);
  background: color-mix(in srgb, var(--teal) 6%, var(--surface-soft));
}

/* Миниатюра позиции в "Что получилось"/"Что не получилось" (запрос
   пользователя 19.08.2026) — переиспользует renderCriticalThumbnail() и
   классы .critical-item-board/.critical-item-square как есть: это одна и
   та же страница "Анализ" (Секундант — вкладка на ней же), не Quest Board,
   поэтому Н-5 (разделение подсистем) тут не при чём — прямое переиспользование
   уместно. */
.coach-summary-thumb {
  float: left;
  margin: 0 10px 6px 0;
}

.coach-summary-thumb .critical-item-board {
  border: 1px solid var(--line);
}

/* "Вау"-блок стиля наверху вкладки — убран 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-persona {
  width: min(100%, 180px);
  margin-bottom: 12px;
}

.secondant-locked-persona svg {
  width: 100%;
  height: auto;
  display: block;
}

.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;
}

.secondant-locked-points {
  list-style: none;
  margin: 0 0 28px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-align: left;
}

.secondant-locked-points li {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-body);
  color: var(--ink);
}

.secondant-locked-points li::before {
  content: "✓";
  flex: none;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  background: var(--green-soft);
  color: var(--green-text);
  font-size: var(--icon-sm);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Тот же принцип 6, что у .critical-item-badge — цвет-смысл сохранён,
     плоская заливка получает небольшой выпуклый рельеф. */
  box-shadow: 2px 2px 5px color-mix(in srgb, var(--green) 40%, transparent), -1px -1px 4px var(--neu-highlight);
}

/* Единый список вместо двух таблиц бок о бок (запрос пользователя
   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,
   сама иконка вместо кружка — запрос 20-21.08.2026: пользователь прислал
   готовый набор из 6 иконок, обрезаны в move-quality/*.png, мэппинг —
   ровно как она сама расписала (не по подписям в её референсе, а по
   категориям сайта): brilliant→brilliant, excellent→great(стрелка вверх),
   good→best(звезда), inaccuracy→mistake(стрелка вниз), mistake→miss
   (крестик), blunder→blunder(двойная стрелка). Цвет с иконки не снят —
   она уже цветная в своём файле; здесь просто заменяем ЦЕЛИКОМ заливку
   кружка на background-image, никакой доп. перекраски через 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");
}

/* "Лучший" — отдельная строка с 07.09.2026 (была слита с «Отличный» в
   одну «Отличные» до этого, см. историю выше), готовой 7-й иконки под
   неё нет (набор пользователя — на 6 категорий, все уже разобраны:
   brilliant/great/best/mistake/miss/blunder — "best.png" уже занят
   строкой "Хороший", см. ниже). Ромб — тот же приём "простая геометрия
   без готовой иконки", что уже применён к "ok"/"missed"/"book" (кружок/
   кольцо/квадрат), тут — залитый ромб, чтобы форма не повторяла ни одну
   из них. Цвет — существующий `--quality-excellent` (не новый токен):
   "Лучший" — тот же положительный уровень серьёзности "good", что и
   "Отличный"/"Бриллиантовый" выше, различие только по форме, не по тону. */
.quality-table-row.is-best > span::before {
  width: 8px;
  height: 8px;
  border-radius: 2px;
  background: var(--quality-excellent);
  background-image: none;
  transform: rotate(45deg);
}

.quality-table-row.is-good > span::before {
  background-image: url("/static/img/move-quality/best.png");
}

/* "Неплохой" — новая категория, для неё нет готовой PNG-иконки из набора
   пользователя (тот был на 6 категорий). Не подрисовываю несуществующую
   иконку — обычный цветной кружок, как категории выглядели до 20.08.2026. */
.quality-table-row.is-ok > span::before {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--quality-ok);
  background-image: none;
}

/* "Теоретический ход" — тот же квадратный силуэт, что у значка на доске
   (severity-book выше): визуально отличим от круглых маркеров остальных
   категорий, читается как "по книге", не как оценка хода. */
.quality-table-row.is-book > span::before {
  width: 9px;
  height: 9px;
  border: 2px solid var(--quality-book);
  border-radius: 2px;
  background: transparent;
  background-image: none;
}

/* "Упущенная возможность" — своя, не позаимствованная у chess.com идея:
   ПОЛОЕ кольцо, не закрашенный кружок/иконка. Закрашенное = "это
   реально случилось" (хороший/плохой ход), полое = "возможность была,
   но её не взяли". */
.quality-table-row.is-missed > span::before {
  width: 8px;
  height: 8px;
  border: 2px solid var(--quality-missed);
  border-radius: 50%;
  background: transparent;
  background-image: none;
}

.quality-table-row.is-inaccuracy > span::before {
  background-image: url("/static/img/move-quality/mistake.png");
}

.quality-table-row.is-mistake > span::before {
  background-image: url("/static/img/move-quality/miss.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;
}

.leaderboard-card ol {
  display: grid;
  gap: 8px;
  margin: 12px 0 0;
  padding: 0;
  list-style: none;
}

.leaderboard-card li {
  display: grid;
  grid-template-columns: 26px minmax(0, 1fr) auto;
  gap: 8px;
  align-items: center;
  min-height: 34px;
  padding: 7px 9px;
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow-soft);
  font-weight: 700;
}

.leaderboard-card .rank {
  color: var(--muted);
}

.leaderboard-card .value {
  color: var(--blue-dark);
}


/* Карточки в ряд по референсу: 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;
}

.critical-more {
  width: 100%;
  margin-top: 10px;
}

.critical-more.is-hidden {
  display: none;
}

/* Карточка по референсу: миниатюра позиции слева, три строки текста
   справа. Вся карточка — кнопка (переход к ходу), поэтому текст слева,
   без центрирования как у обычных кнопок. */
.critical-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  text-align: left;
}

/* Accordion: развёрнут максимум один (запрос пользователя 16.08.2026,
   ТЗ «реструктуризация layout», п.10) — свёрнутые превращаются в одну
   компактную строку "▸ Ход N ... −X.XX" без мини-доски, полное превью —
   только у активного момента. */
.critical-item-chevron {
  display: none;
  flex: 0 0 auto;
  color: var(--muted);
  font-size: var(--fs-caption);
}

.critical-item:not(.is-expanded) {
  padding: 6px 8px;
}

.critical-item:not(.is-expanded) .critical-item-chevron {
  display: inline-block;
}

.critical-item:not(.is-expanded) .critical-item-board {
  display: none;
}

.critical-item:not(.is-expanded) .critical-item-label {
  display: none;
}

.critical-item:not(.is-expanded) .critical-item-body {
  display: flex;
  flex-direction: row;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
}

.critical-item:not(.is-expanded) .critical-item-move {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.critical-item-board {
  position: relative;
  display: grid;
  flex: 0 0 auto;
  width: 52px;
  height: 52px;
  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: -6px;
  bottom: -6px;
  display: inline-grid;
  min-width: 18px;
  min-height: 18px;
  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);
}

.critical-item-body {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.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;
}

.deep-dive,
.tables-section,
.analysis-review-grid {
  margin-top: 16px;
}

.deep-dive {
  padding: 16px;
}

.deep-grid {
  display: grid;
  /* Раньше два столбца с жёстким минимумом (260+320px) — рассчитано на
     полную ширину вкладки. Теперь «Фазы партии»/«Стиль игры» всегда в
     узкой .board-side (запрос пользователя 15.08.2026) — минимум 580px
     туда не помещался, был горизонтальный overflow. Один столбец. */
  grid-template-columns: 1fr;
  gap: 16px;
  margin-top: 16px;
  align-items: start;
}

.phase-panel,
.style-panel,
.segments-panel {
  grid-area: auto;
  min-width: 0;
}

.segments-panel canvas {
  height: 260px;
  min-height: 240px;
}

.training-roadmap {
  margin-top: 16px;
  padding: 16px;
}

.roadmap-grid {
  display: grid;
  /* Раньше рассчитано на 3 карточки на полную ширину вкладки (сейчас
     реально используется одна — «Задачи на тренировку»). Теперь вкладка
     всегда в узкой .board-side (запрос пользователя 15.08.2026) —
     минимум 780px не помещался. */
  grid-template-columns: 1fr;
  gap: 14px;
  margin-top: 16px;
  align-items: stretch;
}

.roadmap-card {
  display: grid;
  align-content: start;
  gap: 12px;
  min-width: 0;
  min-height: 210px;
  padding: 15px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background:
    linear-gradient(145deg, color-mix(in srgb, var(--surface-soft) 94%, transparent), color-mix(in srgb, var(--blue) 5%, var(--surface)));
}

.roadmap-focus-card {
  border-color: color-mix(in srgb, var(--blue) 34%, var(--line));
  background:
    linear-gradient(145deg, color-mix(in srgb, var(--blue) 11%, var(--surface)), color-mix(in srgb, var(--green) 8%, var(--surface-soft)));
}

.coach-roadmap-card {
  border-color: color-mix(in srgb, var(--green) 30%, var(--line));
  background:
    linear-gradient(145deg, color-mix(in srgb, var(--green) 10%, var(--surface)), color-mix(in srgb, var(--blue) 6%, var(--surface-soft)));
}

.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-card h3 {
  margin: 0;
  font-size: var(--fs-body);
}

.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;
}

.roadmap-list,
.readiness-list {
  display: grid;
  gap: 9px;
}

.roadmap-item,
.readiness-item,
.roadmap-moment {
  display: grid;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface-soft);
}

.roadmap-item {
  grid-template-columns: 30px minmax(0, 1fr);
  gap: 9px;
  align-items: start;
  padding: 10px;
}

.roadmap-item > span,
.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;
}

.roadmap-item strong,
.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;
}

.phase-list,
.style-list,
.endgame-list {
  display: grid;
  gap: 12px;
}

.phase-row,
.style-row {
  display: grid;
  gap: 7px;
}

.row-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  color: var(--muted);
  font-size: var(--fs-body-sm);
  font-weight: 600;
}

.row-top span:first-child {
  color: var(--ink);
}

.bar {
  height: 12px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--surface-strong);
}

.bar-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--green);
}

.style-row:nth-child(1) .bar-fill {
  background: var(--red);
}

.style-row:nth-child(2) .bar-fill {
  background: var(--amber);
}

.style-row:nth-child(3) .bar-fill {
  background: var(--green);
}

.style-row:nth-child(4) .bar-fill {
  background: var(--blue);
}

/* .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;
  max-height: 420px;
}

/* Раньше здесь рядом с «Временем на ход» стояла таблица «Статистика
   ходов» (.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,
.endgame-wrap {
  min-width: 0;
  padding: 16px;
}

/* Профиль → блок подписки «Секундант» (ТЗ §19-23). Та же карточная
   обвязка, что у .input-leaderboard-card, только новый класс — общих
   селекторов с существующими карточками нет, поэтому их стиль этой
   правкой не затронут. */
.profile-secondant-card {
  display: grid;
  gap: 8px;
  padding: 16px;
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
  min-width: 0;
}

.profile-secondant-card h2 {
  margin-bottom: 0;
  font-size: var(--fs-body);
}

.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);
}

.profile-secondant-status {
  margin: 0;
  font-size: var(--fs-h4);
  font-weight: 700;
  color: var(--ink);
}

.profile-secondant-status.is-active {
  color: var(--green-text);
}

.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);
}

/* Растущая история анализов (запрос пользователя 14.09.2026, пункт 2) —
   тот же визуальный язык, что у .profile-secondant-card рядом (карточка
   в том же .profile-grid), не новая палитра. */
.profile-history-card {
  display: grid;
  gap: 8px;
  padding: 16px;
  border: 0;
  border-radius: var(--radius);
  background: var(--bg);
  box-shadow: var(--shadow);
  min-width: 0;
}

.profile-history-card h2 {
  margin-bottom: 0;
  font-size: var(--fs-body);
}

.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);
}

.profile-history-section {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--line);
}

.profile-history-section-title {
  margin: 0;
  font-size: var(--fs-caption);
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.profile-history-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--fs-body-sm);
  color: var(--ink);
}

.profile-history-stat {
  color: var(--muted);
  font-size: var(--fs-caption);
}

.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;
}

/* Подпись колонок над лентой ходов (запрос пользователя 26.08.2026) —
   тот же грид на 2 колонки, что у .move-table ниже, чтобы подписи
   совпадали с реальными колонками, а не просто лежали сверху текстом. */
.move-table-head {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px 8px;
  margin-top: 8px;
  padding: 0 6px;
  color: var(--muted);
  font-size: var(--fs-caption);
  /* Жирный (запрос пользователя 26.08.2026, было 600). */
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Лента ходов по макету: две колонки (белые слева, чёрные справа) с
   вертикальной прокруткой вместо прежней горизонтальной строки PGN. */
.move-table {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: baseline;
  gap: 2px 8px;
  margin-top: 4px;
  /* Ровно 5 строк видно (28px строка + 2px gap × 4 промежутка), остальные —
     по скроллу (запрос пользователя 06.08.2026, было 340px ≈ 11 строк). */
  max-height: 152px;
  overflow-x: hidden;
  overflow-y: auto;
  padding-bottom: 6px;
  scroll-behavior: smooth;
  scrollbar-width: thin;
  /* Сплошной вертикальный раздел по стороне (запрос пользователя
     26.08.2026, третий заход — первые две попытки красили КАЖДЫЙ токен
     отдельно, с зазором между ними, получался «шахматный» узор мелких
     плашек, не единая заливка; пользователь явно попросила один сплошной
     раздел на всю ленту). Жёсткий 50/50 градиент ровно на границе двух
     колонок грида (обе — 1fr, т.е. по 50% каждая) — красится ОДИН раз на
     контейнере, а не на каждой строке, поэтому визуально сплошной, без
     разрывов между ходами. background-attachment по умолчанию (scroll)
     держит разметку неподвижной относительно самого блока при
     внутренней прокрутке — раздел не «едет» вместе с содержимым.

     26.08.2026, четвёртый заход — «сделай цвета в соответствии с
     карточкой игрока»: цвета взяты буквально те же, что у .player-side/
     .player-card/.score-card is-white/is-black (см. выше). Белая
     карточка игрока НЕ имеет собственной заливки вообще (только
     свечение-рамка) — левая половина тут тоже без заливки в ОБЕИХ
     темах, что отменяет более раннюю приглушённую подсветку белых в
     тёмной теме (тот отдельный запрос был раньше этого уточнения).
     Чёрная карточка — фиксированный #4b5563 в обеих темах сайта; правая
     половина использует тот же #4b5563, но НЕ сплошным (там на карточке
     игрока текст тоже перекрашен в светлый, а тут просьба текст ходов
     не трогать) — альфа подобрана раздельно под каждую тему сайта,
     чтобы существующий текст ходов (var(--ink)/категорийные цвета)
     оставался читаемым, а сама заливка — заметно графитовой, не
     терялась на фоне карточки:
       светлая тема, alpha 0.18 → контраст текста 3.55–10.79 (все
       текстовые токены ленты, включая самый слабый «great»/blue-dark);
       тёмная тема нужна alpha сильно выше (0.55, не 0.18) — на самой
       тёмной карточке (--surface тёмной темы темнее #4b5563) низкая
       альфа давала бы контраст ~1.1 против карточки — фактически
       невидимую заливку; на 0.55 заливка уже заметно графитовая
       (контраст к карточке 1.4), а текст всё ещё читаем (4.5–5.9). */
  background: linear-gradient(to right, transparent 50%, rgba(75, 85, 99, 0.18) 50%);
}

html[data-theme="dark"] .move-table {
  background: linear-gradient(to right, transparent 50%, rgba(75, 85, 99, 0.55) 50%);
}

.move-token {
  display: flex;
  flex-shrink: 0;
  align-items: baseline;
  justify-content: flex-start;
  gap: 6px;
  width: 100%;
  padding: 3px 6px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--ink);
  font-size: var(--fs-body-sm);
  font-weight: 700;
  line-height: 1.6;
}

.move-token:hover {
  background: var(--surface-soft);
}

.move-token.is-active {
  background: color-mix(in srgb, var(--blue) 14%, transparent);
}

/* Раньше здесь было отдельное правило .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: номер, фигура
   и клетка одним цветом, палитра та же, что у .inspector-quality-badge.
   Фон-подложку (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);
  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;
  }

  .workspace {
    grid-template-columns: minmax(270px, 340px) minmax(0, 1fr);
  }

  .deep-grid {
    grid-template-columns: 1fr 1fr;
  }

  .roadmap-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .roadmap-focus-card {
    grid-column: 1 / -1;
  }

  .segments-panel {
    grid-column: auto;
  }
}

@media (max-width: 1180px) {
  .hero-board {
    width: min(100%, 820px);
    margin-right: 0;
  }

  /* Раньше здесь переопределялся только .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;
  }

  .move-inspector {
    border-left: 0;
    padding: 6px 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-board {
    width: min(100%, 760px);
    margin-inline: auto;
  }

  .welcome-features {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .quest-layout,
  .quest-detail-grid,
  .quest-level-grid {
    grid-template-columns: 1fr;
  }

  .welcome-actions {
    align-items: flex-start;
    flex-direction: column;
  }

  .workspace,
  .deep-grid,
  .analysis-review-grid,
  .tables-section,
  .roadmap-grid,
  .coach-summary-grid {
    grid-template-columns: 1fr;
  }

  .coach-summary {
    flex-direction: column;
    align-items: center;
  }

  .coach-avatar {
    width: 72px;
  }

  .roadmap-focus-card {
    grid-column: auto;
  }

  body.page-analysis.has-analysis .analysis-stage {
    grid-column: 1;
  }

  body.page-analysis:not(.has-analysis) .workspace {
    grid-template-columns: 1fr;
  }

  /* Специфичность (0,4,1) — равна базовому правилу свёрнутой панели,
     побеждает по порядку в каскаде. Без неё рельса 56px остаётся на мобильных. */
  body.page-analysis.has-analysis.is-input-collapsed .workspace {
    grid-template-columns: minmax(0, 1fr);
  }

  .input-panel {
    position: static;
    max-height: none;
    min-height: auto;
    overflow: visible;
  }

  body.page-analysis.has-analysis .analysis-stage {
    order: 1;
  }

  body.page-analysis.has-analysis .input-panel {
    max-height: none;
    order: 2;
    overflow: visible;
  }

  body.is-input-collapsed .input-panel-toggle {
    min-height: 42px;
    flex-direction: row;
    justify-content: flex-start;
    padding: 0 12px;
  }

  body.is-input-collapsed .input-panel-toggle-label {
    writing-mode: horizontal-tb;
    transform: none;
    font-size: var(--fs-body-sm);
    color: var(--ink);
  }

  body.page-analysis.has-analysis.is-input-collapsed .input-panel {
    padding: 16px;
  }

  .score-card-row {
    width: 100%;
  }

  .player-strip {
    grid-template-columns: 1fr;
  }

  .versus-block {
    min-width: 0;
  }

  .chess-board {
    width: min(100%, 620px);
  }

  .move-inspector {
    width: min(100%, 620px);
  }

  .quest-board-frame {
    width: min(100%, 560px);
  }

  /* Тот же +30%, только для площадки задач — см. базовое правило выше
     по файлу (комментарий там же). 560×1.3=728. */
  .quest-tasks-board-panel .quest-board-frame {
    width: min(100%, 728px);
  }
}

@media (max-width: 620px) {
  /* На узком экране карточки «Точность» и «Перформанс» стояли по две в
     ряд, и на подпись оставалось 15px ширины: текст ломался по 1-2 буквы
     в строку («То/чн/ос/ть»). Раньше это маскировал шрифт 9.6px, но он
     ниже минимума читаемости (пункт №8 аудита). Ставим карточки в один
     столбец — подпись получает всю ширину карточки игрока. */
  .score-card-row .score-card {
    flex: 1 1 100%;
  }

  .app-shell {
    padding: 7px;
  }

  /* Те же 5 строк, что и на десктопе (запрос пользователя 06.08.2026) —
     раньше здесь было отдельное большее значение (220px). */
  .move-table {
    max-height: 152px;
  }

  .header-hover-trigger {
    display: none;
  }

  .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,
  .analysis-empty {
    min-height: auto;
  }

  .analysis-stage,
  .input-panel,
  .deep-dive,
  .training-roadmap,
  .move-table-wrap,
  .endgame-wrap,
  .quest-detail {
    padding: 10px;
  }

  .quest-board-panel,
  .board-zone {
    gap: 8px;
  }

  .welcome-features {
    grid-template-columns: 1fr;
  }

  .analysis-loader {
    top: 8px;
  }

  .loader-head,
  .loader-steps {
    align-items: flex-start;
    flex-direction: column;
  }

  .welcome-copy h2,
  .analysis-empty 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,
  .move-inspector {
    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 на этой ширине — шапка остаётся неподвижной. */
  .header-hover-trigger:hover + .topbar,
  .topbar:hover,
  .topbar:focus-within {
    transform: none;
  }

  /* Пункт 3: «Источник партии» — сразу после шапки, карточки игроков —
     после него (было наоборот через order:1/2 на брейкпоинте 980px,
     общем с планшетом — здесь переопределяем только для телефона). */
  body.page-analysis.has-analysis .input-panel {
    order: 1;
  }

  body.page-analysis.has-analysis .analysis-stage {
    order: 2;
  }

  /* Пункт 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,
  .input-panel,
  .analysis-empty,
  .board-controls,
  .report-actions {
    display: none !important;
  }

  .workspace,
  .deep-grid,
  .analysis-review-grid,
  .tables-section,
  .roadmap-grid {
    display: block !important;
  }

  .analysis-stage,
  .deep-dive,
  .training-roadmap,
  .chart-zone,
  .segments-panel,
  .move-table-wrap,
  .endgame-wrap,
  .roadmap-card,
  .coach-panel {
    break-inside: avoid;
    box-shadow: none !important;
    border-color: #d1d5db !important;
    background: #fff !important;
  }

  .analysis-stage,
  .deep-dive,
  .training-roadmap {
    margin: 0 0 12px;
    padding: 10px;
  }

  .chess-board {
    width: 120mm;
    max-width: 100%;
    box-shadow: none;
  }

  canvas {
    min-height: 70mm;
  }

  .roadmap-grid,
  .deep-grid,
  .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);
  }
}
