:root {
  --ink: #16201c;
  --ink-soft: #4a5a52;
  --paper: #fbfaf7;
  --paper-alt: #f2f0ea;
  --line: #ddd8cc;
  --green: #1f6f52;
  --green-dark: #14503a;
  --amber: #c8873a;
  --white: #fff;
  --radius: 14px;
  --shadow: 0 2px 4px rgba(22, 32, 28, .04), 0 12px 32px rgba(22, 32, 28, .07);
}

* { box-sizing: border-box; }

/* Атрибут hidden должен побеждать любой display из классов.
   Без этого правила .board-overlay с display:grid перебивал hidden
   и плашка висела поверх доски с самого начала. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  background: var(--paper);
  color: var(--ink);
  line-height: 1.6;
  font-size: 16px;
}

h1, h2, h3 {
  line-height: 1.15;
  letter-spacing: -.02em;
  margin: 0 0 .5em;
  font-weight: 650;
}

h1 { font-size: clamp(1.9rem, 4.5vw, 3rem); }
h2 { font-size: clamp(1.4rem, 2.6vw, 2rem); }
h3 { font-size: 1.1rem; }

p { margin: 0 0 1em; }

a { color: var(--green); text-decoration: none; }
a:hover { text-decoration: underline; }

.muted { color: var(--ink-soft); }
.small { font-size: .875rem; }
.lead { font-size: 1.125rem; color: var(--ink-soft); max-width: 46ch; }

/* ---------- Nav ---------- */

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.5rem;
  max-width: 1180px;
  margin: 0 auto;
  flex-wrap: wrap;
}

.brand {
  font-weight: 700;
  font-size: 1.125rem;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: .5rem;
  letter-spacing: -.01em;
}
.brand:hover { text-decoration: none; }
.brand-mark {
  display: inline-grid;
  place-items: center;
  width: 32px; height: 32px;
  background: var(--green);
  color: var(--white);
  border-radius: 9px;
  font-size: 1.1rem;
}

.nav-links {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  flex-wrap: wrap;
}
.nav-links a { color: var(--ink-soft); font-size: .9375rem; }
.nav-links a:hover { color: var(--ink); }
.nav-user { font-size: .875rem; color: var(--ink-soft); }

/* ---------- Buttons ---------- */

.btn {
  display: inline-block;
  padding: .65rem 1.15rem;
  border-radius: 10px;
  border: 1px solid transparent;
  font: inherit;
  font-size: .9375rem;
  font-weight: 550;
  cursor: pointer;
  transition: background .15s, border-color .15s, transform .05s;
  text-align: center;
}
.btn:hover { text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .45; cursor: not-allowed; }

.btn-primary { background: var(--green); color: var(--white); }
.btn-primary:hover { background: var(--green-dark); }

.btn-ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--ink);
}
.btn-ghost:hover:not(:disabled) { background: var(--white); border-color: var(--ink-soft); }

/* ---------- Hero ---------- */

.hero {
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  gap: 3rem;
  align-items: center;
  max-width: 1180px;
  margin: 0 auto;
  padding: 3rem 1.5rem 4rem;
}

.eyebrow {
  text-transform: uppercase;
  letter-spacing: .12em;
  font-size: .75rem;
  font-weight: 650;
  color: var(--amber);
  margin-bottom: .75rem;
}

.hero h1 { margin-bottom: .6rem; }

.hero-actions {
  display: flex;
  gap: .75rem;
  flex-wrap: wrap;
  margin: 1.75rem 0 1rem;
}

.hero-note { font-size: .875rem; color: var(--ink-soft); }

.hero-board { display: flex; justify-content: center; }

/* Портрет тренера вместо схематичной доски.
   Размер не с потолка: блок ссылок в шапке («Игры · О тренере · Отзывы ·
   Записаться · Войти») занимает 407px, колонка под фото — 461px. Борис
   просил «не уже, чем место с кнопками», поэтому на широком экране фото
   тянется на всю колонку, а 407px — нижняя граница, ниже которой оно
   начинает читаться как иконка рядом с текстом.
   На узких экранах 407px не влезли бы в 358px телефона — там min-width
   снимается, и фото занимает всю доступную ширину. */
.hero-photo {
  display: block;
  width: 100%;
  max-width: 461px;
  border-radius: var(--radius);
  overflow: hidden;
  box-shadow: var(--shadow);
  background: var(--paper-alt);
}

.hero-photo img {
  display: block;
  width: 100%;
  height: auto;
}

/* Нижняя граница ширины — только там, где колонка её вмещает. */
@media (min-width: 901px) {
  .hero-photo { min-width: 407px; }
}

/* ---------- Sections ---------- */

.section {
  max-width: 1180px;
  margin: 0 auto;
  padding: 3.5rem 1.5rem;
}
.section-alt {
  background: var(--paper-alt);
  max-width: none;
  padding-left: 0;
  padding-right: 0;
}
.section-alt > * { max-width: 1180px; margin: 0 auto; }
.section-alt .cards,
.section-alt .about,
.section-alt .booking { max-width: 1180px; margin: 0 auto; }
.section-alt h2 { max-width: 1180px; margin: 0 auto .5em; }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.25rem;
  margin-top: 2rem;
}
.cards-3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

.card {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
}
.card h3 { margin-bottom: .4rem; }
.card p { margin: 0; color: var(--ink-soft); font-size: .9375rem; }

.card-num {
  display: block;
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .1em;
  color: var(--amber);
  margin-bottom: .75rem;
}

/* ---------- Специалисты ---------- */

/* Контакт — моноширинно и с переносом: телефоны и ники длинные,
   а карточка узкая. Без overflow-wrap строка вылезает за границу блока
   и рвёт вёрстку на телефоне. */
.specialist-card { display: flex; flex-direction: column; gap: .35rem; }
.specialist-card .eyebrow { margin-bottom: 0; color: var(--ink-soft); }

/* Фото специалиста. Квадрат, а не «как получилось»: у разных людей разные
   пропорции, и карточки в ряду должны быть одной высоты. object-fit: cover
   обрезает лишнее по центру вместо того, чтобы сплющить лицо. */
.specialist-photo {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 12px;
  background: var(--line);
  margin-bottom: .35rem;
  display: block;
}
/* Заглушка без фото: первая буква имени. Не «пустая рамка» — иначе карточка
   выглядит сломанной, а не «человек без фотографии». */
.specialist-photo-empty {
  display: grid;
  place-items: center;
  font-size: 2rem;
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--bg);
  border: 1px dashed var(--line);
}
.specialist-card p { margin: 0; }
.specialist-contact {
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
  color: var(--ink);
  font-weight: 550;
  padding-top: .35rem;
}
.specialist-card .btn-small { align-self: flex-start; margin-top: .5rem; padding: .4rem .8rem; font-size: .875rem; }

/* Ссылка «Комната» в меню. Отдельный цвет, чтобы её находили глазами:
   это единственный путь назад в урок, и он не должен сливаться с «Главной». */
.nav-room { color: var(--green) !important; font-weight: 600; }
.nav-active { color: var(--ink) !important; font-weight: 600; }
/* «Тексты» — ссылка владельца. Единственная служебная ссылка в меню,
   поэтому отделена рамкой: её видно, но она не спорит с «Комнатой». */
.nav-owner {
  padding: .25rem .6rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: .8125rem;
}

.quote {
  margin: 0;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
}
.quote p { font-size: 1rem; }
.quote cite {
  font-style: normal;
  font-size: .875rem;
  color: var(--ink-soft);
}

/* ---------- About ---------- */

.about { padding: 0 1.5rem; }
.about-text { max-width: 62ch; }

.facts {
  list-style: none;
  padding: 0;
  margin: 1.5rem 0 0;
  display: grid;
  gap: .5rem;
}
.facts li {
  padding-left: 1.5rem;
  position: relative;
  font-size: .9375rem;
}
.facts li::before {
  content: "♞";
  position: absolute;
  left: 0;
  color: var(--green);
}

/* ---------- Booking ---------- */

.booking {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
  padding: 0 1.5rem;
  align-items: start;
}

/* ---------- Forms ---------- */

.form {
  display: grid;
  gap: 1rem;
}
.form label {
  display: grid;
  gap: .35rem;
  font-size: .875rem;
  font-weight: 550;
  color: var(--ink-soft);
}
.form input,
.form textarea {
  font: inherit;
  font-size: 1rem;
  padding: .65rem .85rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--white);
  color: var(--ink);
  width: 100%;
}
.form input:focus,
.form textarea:focus {
  outline: 2px solid var(--green);
  outline-offset: -1px;
  border-color: var(--green);
}
.form button { justify-self: start; }

/* ---------- Выбор «кто вы» при регистрации ---------- */

/* Две карточки вместо поля с кодом тренера. Задача не украсить, а объяснить
   последствие выбора: у тренера аккаунт включается не сразу. Поэтому в
   карточке есть вторая строка с пояснением, а не только название. */
.roles {
  border: 0;
  margin: 0;
  padding: 0;
  display: grid;
  gap: .5rem;
}
.roles legend {
  padding: 0;
  margin-bottom: .15rem;
  font-size: .875rem;
  font-weight: 550;
  color: var(--ink-soft);
}
.role-card {
  display: flex !important;
  align-items: flex-start;
  gap: .6rem;
  padding: .7rem .8rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--white);
  cursor: pointer;
  font-weight: 400 !important;
  color: var(--ink) !important;
}
.role-card input {
  width: auto !important;
  margin: .2rem 0 0;
  flex: none;
  accent-color: var(--green);
}
.role-body { display: grid; gap: .15rem; }
.role-title { font-weight: 600; font-size: .9375rem; }
.role-note { font-size: .8125rem; color: var(--ink-soft); line-height: 1.35; }
/* Выбранная карточка подсвечивается целиком: на телефоне в радио-кнопку
   пальцем не попасть, а в карточку — легко. */
.role-card:has(input:checked) {
  border-color: var(--green);
  box-shadow: inset 0 0 0 1px var(--green);
  background: #f4faf6;
}
/* :has не понимают старые движки (Safari 14, Chrome до 105). Тогда подсветки
   нет — но выбор всё равно виден по самой радио-кнопке. Поломкой это не
   является, поэтому отдельный JS-фолбэк не заводим. */
.role-card input:focus-visible {
  outline: 2px solid var(--green);
  outline-offset: 2px;
}

.form-inline {
  display: grid;
  gap: .75rem;
}

.form-status {
  margin: 0;
  font-size: .875rem;
  min-height: 1.25em;
}
.form-status.ok { color: var(--green); }
.form-status.err { color: #b3402f; }

/* ---------- Auth ---------- */

.narrow {
  max-width: 440px;
  margin: 0 auto;
  padding: 3rem 1.5rem 5rem;
}
.switch { margin-top: 1.5rem; font-size: .9375rem; color: var(--ink-soft); }

/* ---------- Страница прослушивания звуков ---------- */

/* Прямая ссылка на .mp3 во встроенном браузере открывает пустой экран.
   Здесь <audio> стоит внутри страницы, поэтому плеер рисуется всегда. */
.sound-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .9rem 0;
  border-bottom: 1px solid var(--line);
}
.sound-row:first-of-type { border-top: 1px solid var(--line); }
.sound-info { min-width: 0; flex: 1 1 200px; }
.sound-info h3 { margin: 0 0 .15rem; font-size: 1rem; }
.sound-info p { margin: 0; }
/* Кнопка вместо нативного плеера: звуки короче секунды, и <audio controls>
   показывал «0:00 / 0:00» — выглядело как пустой файл. */
.sound-play {
  flex: 0 0 auto;
  min-width: 140px;
  font-size: .9375rem;
  padding: .55rem 1.1rem;
}
/* Отклик на нажатие. Звуки длятся 160–450 мс, поэтому подпись держится
   минимум 900 мс — иначе человек не видит, что кнопка сработала. */
.sound-play.is-playing { background: var(--green, #3f7a4f); border-color: var(--green, #3f7a4f); }
.sound-play.is-error { background: #a33; border-color: #a33; }

/* ---------- Cabinet ---------- */

.cabinet {
  max-width: 880px;
  margin: 0 auto;
  padding: 2rem 1.5rem 5rem;
}
.cabinet h1 { margin-bottom: .25rem; }

.cabinet-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem;
  margin-top: 2rem;
}

.panel {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.5rem;
  margin-top: 1.25rem;
}
.cabinet-grid .panel { margin-top: 0; }
.panel h2 { font-size: 1.1125rem; margin-bottom: 1rem; }
.panel h3 { font-size: 1rem; margin-bottom: .75rem; }
/* Подзаголовки внутри одной панели (сводка / где бросают / отзывы):
   без отступа сверху блоки слипаются в один список. */
.panel h3:not(:first-of-type) { margin-top: 1.5rem; }
.panel-tight { padding: 1rem; }

.rooms-list { display: grid; gap: .5rem; }

.room-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  padding: .75rem .9rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--paper);
}
.room-item-main { display: grid; gap: .1rem; min-width: 0; }
/* Кнопки в строке заявки тренера: «Скопировать ссылку», «Новый код»,
   «Подтвердить». Без этого блока три кнопки подряд разъезжались по вертикали
   и строка вырастала втрое выше соседних.
   flex-wrap на телефоне: три кнопки в одну строку не влезают, и без переноса
   они вылезали за край карточки. */
.room-item-actions {
  display: flex;
  align-items: center;
  gap: .4rem;
  flex-wrap: wrap;
  justify-content: flex-end;
  flex-shrink: 0;
}
/* Миниатюра в строке кабинета. 2.25rem — ровно в половину высоты строки,
   чтобы кнопки справа не разъезжались по вертикали. */
.room-thumb {
  width: 2.25rem;
  height: 2.25rem;
  flex: 0 0 auto;
  object-fit: cover;
  border-radius: 8px;
  background: var(--line);
}
.room-thumb-empty {
  display: grid;
  place-items: center;
  font-size: .9375rem;
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--bg);
  border: 1px dashed var(--line);
}
.room-item-text { display: grid; gap: .1rem; min-width: 0; }
/* Строка с миниатюрой: main становится флексом, иначе буква встанет над именем. */
.room-item-main:has(> .room-thumb) {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
}
.room-item-name { font-weight: 550; font-size: .9375rem; }
.room-item-meta { font-size: .8125rem; color: var(--ink-soft); }
.room-item a { font-size: .875rem; white-space: nowrap; }

/* Метка на строке: «ждёт подтверждения», «без тренера».
   Не цветная плашка на весь экран — маленькая пометка справа от имени.
   Задача метки — привлечь взгляд в списке из тридцати строк, а не орать. */
.tag {
  display: inline-block;
  margin-left: .35rem;
  padding: .05rem .4rem;
  border-radius: 999px;
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .02em;
  text-transform: uppercase;
  color: #7a4a10;
  background: #fdf0d8;
  vertical-align: middle;
}

/* Ссылка для учеников: адрес и кнопка в одну строку.
   Адрес моноширинный и переносится по словам: на телефоне /register?coach=K7M2QX
   шире экрана, и без переноса кнопка «Скопировать» уезжает за край. */
.coach-link-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
}
.coach-link-row code {
  padding: .35rem .5rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--side, #f6f4ef);
  font-size: .8125rem;
  overflow-wrap: anywhere;
}

/* Плашка «Заявка отправлена» в кабинете ожидающего тренера.
   Не ошибка и не тревога — просто состояние. Поэтому зелёная рамка и ровный
   фон, а не красный: человек сделал всё правильно и ждёт подтверждения. */
.panel-note {
  border-color: color-mix(in srgb, var(--green) 40%, var(--line));
  background: color-mix(in srgb, var(--green) 5%, var(--white));
}
.panel-note h2 { color: var(--green-dark); }
.panel-note p:last-child { margin-bottom: 0; }

/* Выданный код тренера: нажимается и копируется одним касанием.
   Выглядит как код комнаты — моноширинный, в рамке, — чтобы не приходилось
   объяснять, что это то же самое. */
.coach-code-slot { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; }
.coach-code-chip {
  padding: .25rem .55rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--side, #f6f4ef);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .875rem;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--ink);
  cursor: pointer;
}
.coach-code-chip:hover { border-color: var(--green); color: var(--green-dark); }
.coach-code-chip.is-copied { border-color: var(--green); color: var(--green); }

/* Заявка с лендинга. Контакт — главное, что здесь нужно: по нему Борис
   пишет сам. Поэтому имя и контакт идут первыми и крупнее прочего. */
.lead-item { align-items: flex-start; }
.lead-message {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  padding-top: .15rem;
}
.lead-when {
  font-variant-numeric: tabular-nums;
  opacity: .75;
}

/* ---------- Room ---------- */

.room {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 1.5rem 4rem;
}

.room-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
  flex-wrap: wrap;
}
.room-head h1 { font-size: 1.5rem; margin-bottom: .25rem; }
.room-code { font-size: .875rem; color: var(--ink-soft); margin: 0; }

/* Плашка просмотра прошлого. Показывается, когда тренер отмотал партию:
   без неё ученик тычет неподвижные фигуры и думает, что связь сломалась.
   Фон заметный, но не кричащий: это состояние, а не ошибка.
   Пустая плашка (hidden) места не занимает — правило [hidden] выше. */
.viewing-hint {
  margin: 0 0 1rem;
  padding: .55rem .85rem;
  border-radius: 10px;
  background: #fdf4e3;
  border: 1px solid #ecd9b4;
  color: #6b4d18;
  font-size: .875rem;
}

.code-copy {
  /* Код и слово «Скопировано» лежат в одной ячейке сетки друг под другом.
     Ширина кнопки = ширина более длинного из двух — и не меняется никогда.
     Это важно не для красоты: если кнопка станет шире в момент клика, шапка
     дёрнется, доска уедет вниз, а chessground посчитает тап по старым
     координатам. Ровно тот баг, что Борис ловил на телефоне. */
  display: inline-grid;
  place-items: center;
  font: inherit;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-weight: 650;
  letter-spacing: .08em;
  background: var(--paper-alt);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: .1rem .45rem;
  cursor: pointer;
  color: var(--ink);
}
.code-copy:hover { background: var(--white); }

/* Обе подписи — в одну и ту же ячейку (grid-area 1/1) и ОБЕ всегда участвуют
   в раскладке. Поэтому ширина кнопки равна ширине более длинной подписи
   и не меняется ни в момент клика, ни после. Прячем именно visibility:
   display:none выкинул бы «Скопировано» из расчёта ширины, кнопка выросла бы
   на клике, шапка дёрнулась бы, доска уехала — и chessground посчитал бы тап
   по старым координатам. Это тот самый баг, что Борис ловил на телефоне. */
.copy-code,
.copy-done {
  grid-area: 1 / 1;
}
.copy-done {
  visibility: hidden;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-weight: 650;
  letter-spacing: 0;
  font-size: .8125rem;
  white-space: nowrap;
}
.code-copy.is-copied .copy-code { visibility: hidden; }
.code-copy.is-copied .copy-done { visibility: visible; }
.code-copy.is-copied,
.code-copy.is-copied:hover {
  background: #dff0e4;
  border-color: #9fc9ae;
}
.code-copy.is-copied .copy-done { color: #276543; }

.room-meta { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }

/* Ссылка для ученика в комнате.
   Первое нажатие показывает адрес, второе копирует — поэтому адрес должен
   выглядеть кликабельным и переноситься: /register?room=ABC123 на телефоне
   шире экрана, и без переноса кнопка уезжает за край. */
.room-link-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem;
  margin-top: .35rem;
}
.room-student-link {
  padding: .35rem .5rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--side, #f6f4ef);
  font-size: .8125rem;
  overflow-wrap: anywhere;
  cursor: pointer;
  max-width: 100%;
}
.room-student-link:hover { border-color: var(--green); color: var(--green-dark); }
.room-student-link.is-copied {
  border-color: var(--green);
  background: #dff0e4;
  color: #276543;
}

.status-dot {
  font-size: .8125rem;
  color: var(--ink-soft);
  display: inline-flex;
  align-items: center;
  gap: .4rem;
}
.status-dot::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: 50%;
  background: #b9b3a6;
}
.status-dot.on::before { background: #2e9e6b; }
.status-dot.off::before { background: #c4553f; }

.turn-pill {
  font-size: .8125rem;
  font-weight: 550;
  padding: .2rem .65rem;
  border-radius: 999px;
  background: var(--paper-alt);
  border: 1px solid var(--line);
}

.room-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 1.5rem;
  align-items: start;
}

/* Доска должна влезать в экран целиком по высоте, иначе нижний ряд фигур
   срезается: в шапке комнаты ещё заголовок, статус, а под доской кнопки.
   При viewport 1280×900 выходило 768 px и низ доски уезжал за экран.
   max(280px, …) — страховка для низких экранов, чтобы доска не схлопнулась. */
.board-wrap {
  position: relative;
  width: 100%;
  max-width: min(100%, max(280px, calc(100vh - 13rem)));
  margin-inline: auto;
}

/* Слой-штамп творческого режима. Лежит поверх доски, но НИЖЕ плашек (20):
   результат партии и блокировка должны перекрывать его, а не наоборот.
   Показывается только при выбранной фигуре или ластике — иначе перехватывал
   бы перетаскивание фигур. */
.stamp-layer {
  position: absolute;
  inset: 0;
  z-index: 15;
  cursor: crosshair;
  touch-action: manipulation;
}

/* Слой стрелок. viewBox 0 0 8 8 — координаты в клетках, поэтому размер доски
   может быть любым: стрелка масштабируется сама. preserveAspectRatio="none" —
   потому что доска всегда квадратная, а слой тянется по её рамке.
   pointer-events: none — рисование ловит board-wrap в capture-фазе, иначе
   слой перекрыл бы перетаскивание фигур и клики по доске. */
.arrow-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 9;
  pointer-events: none;
  overflow: visible;
}

/* Панель стрелок — в любом режиме, для тренера и ученика. */
.arrow-bar {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
  margin-top: .85rem;
  padding: .55rem .75rem;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.arrow-bar .btn { padding: .45rem .8rem; font-size: .875rem; }
.arrow-toggle-icon { font-weight: 700; }

/* Рисование включено — кнопка тёмная, состояние видно без догадок. */
.arrow-bar .arrow-toggle[aria-pressed="true"] {
  background: var(--ink, #16201c);
  color: #fff;
  border-color: var(--ink, #16201c);
}

.arrow-colors { display: flex; gap: .3rem; }
/* 30px: попадать пальцем. Цвет — тот же, что у стрелки на доске. */
.arrow-color {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer;
  padding: 0;
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .55);
}
.arrow-color[data-arrow-color="green"] { background: #15781B; }
.arrow-color[data-arrow-color="red"] { background: #882020; }
.arrow-color[data-arrow-color="blue"] { background: #003088; }
.arrow-color[data-arrow-color="yellow"] { background: #e68f00; }
.arrow-color[aria-pressed="true"] {
  border-color: var(--ink, #16201c);
  box-shadow: 0 0 0 2px #fff, inset 0 0 0 2px rgba(255, 255, 255, .55);
}

.arrow-hint { flex-basis: 100%; }

/* На компьютере памятка вместо кнопок, на телефоне — наоборот.
   Определяем по типу указателя, а не по ширине экрана: планшет с мышью
   остаётся «компьютером», а узкое окно на ноутбуке — тоже. */
.arrow-memo {
  flex-basis: 100%;
  font-size: .8125rem;
  color: var(--muted, #6b7a72);
  line-height: 1.5;
}
.arrow-memo b { color: var(--ink, #16201c); font-weight: 600; }

@media (hover: hover) and (pointer: fine) {
  .only-touch { display: none !important; }
}
@media (hover: none), (pointer: coarse) {
  .only-mouse { display: none !important; }
}

/* Пока включено рисование, доска показывает прицел, а не «лапу» перетаскивания. */
body.drawing .board-wrap { cursor: crosshair; }
/* touch-action: none — иначе на телефоне браузер считает жест прокруткой
   страницы и не отдаёт pointermove: стрелка не дорисовывается, а экран едет. */
body.drawing .board-wrap { touch-action: none; }
/* И не выделяет текст, если провели мимо доски. */
body.drawing { user-select: none; -webkit-user-select: none; }

/* Квадрат доски БЕЗ aspect-ratio.
   aspect-ratio не понимают Safari 14 и старше, Chrome до 88, Android WebView
   тех же лет — а туда как раз и попадают встроенные браузеры Instagram и
   Telegram на старых телефонах. Когда правило не понимают, оно игнорируется
   целиком: у .board остаётся height:auto, дети у него позиционированы
   абсолютно — значит высота 0 и на месте доски серая полоса. Сайт при этом
   не падает и ошибок не пишет, поэтому поломку видит только человек.
   Классический распорный блок работает везде, где вообще есть CSS. */
.board {
  position: relative;
  width: 100%;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: var(--shadow);
  user-select: none;
}
.board::before {
  content: "";
  display: block;
  padding-bottom: 100%;
}
/* Высоту даёт распорка выше, поэтому доска тянется по ней, а не по контенту:
   процентная высота у ребёнка работает только при заданной высоте родителя. */
.board > .cg-wrap {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: auto;
  height: auto;
}

.board-controls {
  display: flex;
  gap: .5rem;
  flex-wrap: wrap;
  margin-top: .85rem;
  align-items: center;
}
.board-controls .btn { padding: .45rem .85rem; font-size: .875rem; }

/* ---------- Перемотка партии ---------- */

.rewind-bar {
  display: flex;
  align-items: center;
  gap: .4rem;
  flex-wrap: wrap;
  margin-top: .75rem;
}

.rewind-btn {
  width: 34px;
  height: 34px;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.25rem;
  line-height: 1;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--white);
  color: var(--ink);
  cursor: pointer;
}
.rewind-btn:hover:not(:disabled) { background: var(--paper-alt); }
.rewind-btn:disabled { opacity: .4; cursor: not-allowed; }

/* Счётчик моноширинный и фиксированной ширины: иначе при перемотке
   «7 / 12» → «10 / 12» кнопки уезжают из-под пальца. */
.rewind-pos {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: .8125rem;
  color: var(--ink-soft);
  min-width: 4.6em;
  text-align: center;
}

.rewind-resume { padding: .4rem .8rem; font-size: .875rem; }

/* ---------- Отмена хода по согласованию ---------- */

.undo-bar {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  margin-top: .6rem;
  padding: .5rem .7rem;
  background: var(--paper-alt);
  border-radius: 10px;
  min-height: 2.6rem;
}
.undo-bar .btn { padding: .4rem .85rem; font-size: .875rem; }

.undo-wait { color: var(--ink-soft); font-size: .875rem; }

.undo-link {
  background: none;
  border: 0;
  padding: 0 0 0 .4rem;
  color: var(--green);
  font: inherit;
  font-size: .875rem;
  text-decoration: underline;
  cursor: pointer;
}
.undo-link:hover { color: var(--green-dark); }

.undo-ask {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  font-size: .875rem;
  font-weight: 550;
}
.undo-answers { display: inline-flex; gap: .35rem; }

/* Галочка и крестик — крупные: по ним жмёт ребёнок младше семи лет,
   и промах на 4 пикселя не должен значить «нет». */
.undo-yes,
.undo-no {
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 1.05rem;
  line-height: 1;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--white);
  cursor: pointer;
}
.undo-yes { color: var(--green); border-color: #b9d6c9; }
.undo-yes:hover { background: #eaf4ef; border-color: var(--green); }
.undo-no { color: #a33; border-color: #e3c7c7; }
.undo-no:hover { background: #fbeeee; border-color: #a33; }

.undo-note { font-size: .875rem; color: var(--ink-soft); }

/* ---------- Панель режимов ---------- */

.mode-bar {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  margin-bottom: .35rem;
}
.mode-label {
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--muted);
  font-weight: 600;
}
.mode-buttons {
  display: flex;
  gap: .3rem;
  flex-wrap: wrap;
}
/* Кнопки режимов — «таблетки». Активный режим залит тёмным: тренер должен
   видеть, в каком режиме идёт урок, не читая подпись под доской. */
.mode-btn {
  font: inherit;
  font-size: .8125rem;
  padding: .4rem .8rem;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  transition: background .12s ease, color .12s ease, border-color .12s ease;
}
.mode-btn:hover { border-color: var(--green, #3f7a4f); }
.mode-btn[aria-pressed="true"] {
  color: #fff;
  background: var(--ink);
  border-color: var(--ink);
  font-weight: 600;
}
/* Ученик видит режим, но не переключает. */
.mode-current {
  font-size: .875rem;
  font-weight: 600;
  padding: .3rem .75rem;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 999px;
}
/* Высота подписи режима зарезервирована под ДВЕ строки.
   Почему: текст приходит после подключения к комнате, а не при отрисовке
   страницы. Пока его нет — подпись занимает одну строку; когда приходит
   текст, она становится двумя и ТОЛКАЕТ ДОСКУ ВНИЗ. Замер на телефоне:
   сдвиг 28 пикселей при клетке 45 — тап уезжает на клетку ниже.
   Резерв убирает сам скачок, а сброс кеша координат в room.js (syncBoardBounds)
   страхует на случай, если подпись окажется длиннее двух строк.

   3.2em — это ровно две строки: font-size здесь .875rem (14px), а line-height
   наследуется 1.6, то есть 2 × 1.6 × 14 = 44.8px. Замер по всем пяти режимам
   на 360 и 390 пикселях ширины дал 45px — резерв совпадает с фактом. */
.mode-hint { margin: 0 0 .7rem; min-height: 3.2em; }

/* ---------- Палитра творческого режима ---------- */

.palette {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
  margin-top: .85rem;
  padding: .6rem .75rem;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.palette-label {
  font-size: .75rem;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--muted);
  font-weight: 600;
}
.palette-pieces { display: flex; gap: .2rem; flex-wrap: wrap; }
/* Фигура в палитре — кнопка с той же картинкой, что и на доске.
   Размер 34px: попадать пальцем, но не занимать пол-экрана. */
.palette-piece {
  width: 38px;
  height: 38px;
  padding: 0;
  background-color: #f3ede2;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  display: grid;
  place-items: center;
  overflow: hidden;
}

/* Рисунок фигуры берём из vendor-CSS chessground: он привязан к
   `.cg-wrap piece.pawn.white`. Поэтому внутри кнопки лежит обёртка .cg-wrap
   и тег <piece>. Но базовый CSS задаёт фигуре 12.5% ширины (клетка доски),
   а тут клетки нет — растягиваем на всю кнопку, иначе фигура будет 4px. */
.palette-piece .cg-wrap {
  position: relative;
  width: 100%;
  height: 100%;
}
.palette-piece .cg-wrap piece {
  width: 100%;
  height: 100%;
  transform: none;
  background-size: contain;
  background-position: center;
}

.palette-group { display: flex; align-items: center; gap: .4rem; }
.palette-sub {
  font-size: .72rem;
  color: var(--muted, #6b7a72);
  min-width: 3.6rem;
}
.palette-row { display: flex; gap: .2rem; }
.palette-pieces { display: flex; gap: .7rem; flex-wrap: wrap; }
.palette-eraser[aria-pressed="true"] {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--ink);
}

/* «Готово» — выход из расстановки. Тёмная, чтобы её находили без поиска:
   пока фигура выбрана, доска перехватывает клики и это единственный выход. */
.palette-done {
  background: var(--accent, #2b6b4f);
  border-color: var(--accent, #2b6b4f);
  color: #fff;
  font-weight: 600;
}
.palette-done:hover { filter: brightness(1.06); }

/* ---------- Плашка блокировки ---------- */

/* Вторая плашка поверх доски. z-index тот же 20, что у плашки результата:
   обе — «поверх доски», а не поверх друг друга. */
.lock-overlay { z-index: 20; }
.lock-overlay .overlay-card { max-width: 85%; }

/* Кнопка блокировки в активном состоянии — заметная, чтобы тренер
   не забыл, что доска закрыта, и не искал причину «почему не двигается». */
.board-controls .btn[data-action="lock"][aria-pressed="true"] {
  color: #fff;
  background: #a33;
  border-color: #a33;
}

/* Кнопка «нажмите, чтобы слышать ходы». Появляется только когда браузер
   заблокировал звук: ученик вошёл на страницу и ничего не нажимал.
   Одно касание снимает запрет автоплея. */
.sound-hint {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  margin-top: .6rem;
  padding: .5rem .9rem;
  font-size: .875rem;
  font-family: inherit;
  color: var(--ink);
  background: #fdf6e3;
  border: 1px solid var(--amber);
  border-radius: 999px;
  cursor: pointer;
  animation: sound-hint-pulse 2.2s ease-in-out infinite;
}
.sound-hint:hover { background: #fbeecb; }
.sound-hint-icon { font-size: 1rem; line-height: 1; }

@keyframes sound-hint-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(200, 135, 58, .45); }
  50% { box-shadow: 0 0 0 6px rgba(200, 135, 58, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .sound-hint { animation: none; }
}

/* Плашка обязана быть поверх ЛЮБОЙ фигуры.
   chessground ставит своим фигурам z-index: 2, анимируемой 8,
   перетаскиваемой 11 !important (см. public/vendor/chessground.base.css).
   У плашки z-index не был задан вовсе — значит auto, то есть ниже всех.
   На скриншоте Бориса после мата фигура перекрывала даже текст карточки.
   20 выбран с запасом: выше 11 и выше любых будущих слоёв доски. */
.board-overlay {
  position: absolute;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  background: rgba(22, 32, 28, .35);
  backdrop-filter: blur(2px);
  border-radius: 8px;
}
.overlay-card {
  background: var(--white);
  padding: 1.5rem;
  border-radius: var(--radius);
  text-align: center;
  max-width: 80%;
  box-shadow: var(--shadow);
}
.overlay-title { font-weight: 600; font-size: 1.05rem; margin-bottom: 1rem; }

.side { display: grid; gap: 1rem; }

.video-slot {
  background: var(--paper-alt);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  aspect-ratio: 16 / 10;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 1rem;
}
.video-placeholder p { margin: 0 0 .35rem; }

.chat {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  display: grid;
  grid-template-rows: 1fr auto;
  min-height: 200px;
}
.chat-log {
  padding: .75rem;
  display: grid;
  gap: .4rem;
  align-content: start;
  overflow-y: auto;
  max-height: 240px;
  font-size: .875rem;
}
.chat-msg { display: grid; gap: .1rem; }
.chat-msg b { font-weight: 600; font-size: .8125rem; color: var(--green); }
.chat-msg span { color: var(--ink); word-break: break-word; }
.chat-msg time { font-size: .6875rem; color: var(--ink-soft); }

.chat-form {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .5rem;
  padding: .6rem;
  border-top: 1px solid var(--line);
}
.chat-form input {
  font: inherit;
  font-size: .9375rem;
  padding: .5rem .75rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper);
}
.chat-form input:focus { outline: 2px solid var(--green); outline-offset: -1px; }
.chat-form .btn { padding: .5rem .9rem; }

.history {
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem;
}
.history h3 { font-size: .9375rem; margin-bottom: .6rem; }

.moves {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .3rem;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: .8125rem;
}
.moves li {
  background: var(--paper-alt);
  border-radius: 6px;
  padding: .1rem .4rem;
  color: var(--ink-soft);
}
.moves li.half {
  margin-right: -.15rem;
}

/* Ход, который сейчас показан на доске. Без метки тренер, вернувшись
   на три полухода назад, не видит, где именно он стоит. */
.moves li.current {
  background: var(--ink);
  color: var(--paper);
}

/* Будущие ходы: уже в истории, но доска их ещё не показывает.
   Приглушены — иначе партия выглядит сыгранной до конца. */
.moves li.future {
  opacity: .45;
}

.moves li.clickable { cursor: pointer; }
.moves li.clickable:hover { background: var(--line); }
.moves li.clickable:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.moves li.current.clickable:hover { background: var(--ink); }

/* ---------- Выбор фигуры при превращении пешки ---------- */

/* Панель всплывает НАД доской, по центру: превращение случается у последней
   линии, и глаза ребёнка уже там. absolute внутри board-wrap. */
.promo-picker {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 30; /* выше плашек: иначе выбор фигуры окажется под ними */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .5rem;
  padding: .7rem .8rem;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, .22);
}

.promo-title {
  margin: 0;
  font-size: .875rem;
  font-weight: 600;
  color: var(--ink);
}

.promo-pieces {
  display: flex;
  gap: .35rem;
}

/* Кнопка с фигурой. Размер рассчитан на палец: 48px — не меньше. */
.promo-piece {
  width: 48px;
  height: 48px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper-alt);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.promo-piece:hover { background: var(--line); }
.promo-piece:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Картинку даёт только vendor-CSS по селектору `.cg-wrap piece.queen.white`,
   а базовая ширина фигуры — 12.5% клетки доски (то есть 0 в кнопке).
   Поэтому задаём размер явно, как в палитре. */
.promo-piece .cg-wrap {
  width: 40px;
  height: 40px;
  display: block;
  position: relative;
}
.promo-piece .cg-wrap piece {
  width: 100%;
  height: 100%;
  background-size: cover;
}

.promo-cancel {
  border: 0;
  background: none;
  padding: .1rem .2rem;
  font-size: .8125rem;
  color: var(--ink-soft);
  text-decoration: underline;
  cursor: pointer;
}
.promo-cancel:hover { color: var(--ink); }

/* ---------- Footer ---------- */

.footer {
  border-top: 1px solid var(--line);
  padding: 2rem 1.5rem;
  max-width: 1180px;
  margin: 0 auto;
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  font-size: .875rem;
  color: var(--ink-soft);
  flex-wrap: wrap;
}

/* ---------- Chessground tweaks ---------- */

.cg-wrap {
  width: 100%;
  height: 100%;
}

/* ---------- Responsive ---------- */

/* На узком экране фото уходит наверх, к заголовку: сначала лицо,
   потом текст. Раньше здесь стоял max-width для схематичной доски —
   портрету он не нужен, он тянется на всю ширину колонки. */
@media (max-width: 900px) {
  .hero { grid-template-columns: 1fr; gap: 2rem; padding-top: 1.5rem; }
  .hero-board { order: -1; }
  .hero-photo { max-width: 420px; }
  .booking { grid-template-columns: 1fr; gap: 1.5rem; }
  .cabinet-grid { grid-template-columns: 1fr; }
  .room-grid { grid-template-columns: 1fr; }
  .side { order: 2; }
}

@media (max-width: 560px) {
  .nav { padding: .85rem 1rem; }
  .nav-links { gap: .85rem; font-size: .875rem; }
  .nav-links a { font-size: .875rem; }
  .section { padding: 2.5rem 1rem; }
  .hero { padding: 1.5rem 1rem 2.5rem; }
  .room { padding: 0 1rem 3rem; }
  .cabinet { padding: 1.5rem 1rem 4rem; }
  .board-controls .btn { padding: .4rem .7rem; font-size: .8125rem; }
  .rewind-bar { gap: .3rem; }
  .rewind-btn { width: 32px; height: 32px; }
  .undo-bar { padding: .45rem .55rem; gap: .5rem; }
  .undo-yes, .undo-no { width: 32px; height: 32px; }
  .nav-user { display: none; }

  /* Шапка комнаты на телефоне — всегда в две строки.
     Причина: .room-head это flex с flex-wrap, и перенос решался СОДЕРЖИМЫМ.
     При отрисовке в плашке хода стоит «—», и мета влезала в одну строку
     с названием комнаты. После подключения по сокету там появляется
     «Ход белых», блок становится шире — и шапка переносится на вторую строку.
     Замер в наборе: .room-head 59 → 104, то есть +45 px. Клетка на телефоне
     тоже 45 px, поэтому тап уходил на клетку ниже ровно до первой перерисовки.
     В колонку — и высота шапки больше не зависит от того, что в неё впишут. */
  .room-head {
    flex-direction: column;
    align-items: stretch;
    gap: .5rem;
  }
}

.page-room { background: var(--paper); }

/* ---------- Копирование кода комнаты ---------- */

/* Подсказка живёт рядом с кодом, а не вместо него: код должен остаться
   читаемым — его диктуют голосом, если ничего не вышло. */
.copy-note {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  margin-left: .5rem;
  font-size: .8125rem;
  vertical-align: middle;
  flex-wrap: wrap;
}
.copy-note.is-ok { color: #2f6b45; }
.copy-note.is-fail { color: #8a3b3b; }

/* Ручной путь: когда ни execCommand, ни Clipboard API не сработали.
   Поле выделено целиком — остаётся нажать «копировать» в меню системы. */
.copy-fallback {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: .8125rem;
  padding: .1rem .35rem;
  border: 1px solid #d8b9b9;
  border-radius: 5px;
  background: #fff;
  color: var(--ink);
  max-width: 22ch;
}

@media (max-width: 560px) {
  .copy-note { margin-left: .35rem; font-size: .75rem; }
  .copy-fallback { max-width: 18ch; }
}

/* ---------- Панель просмотров (только владелец) ---------- */
/* Блок стоит первым на главной: владельцу он нужен сразу, а гостю его
   вообще не отдают — сервер не передаёт данные, значит нет и разметки. */
.stats-panel {
  margin: 0 auto 1.5rem;
  max-width: 1100px;
  padding: 1rem 1.25rem;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 14px;
}
.stats-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .75rem;
  flex-wrap: wrap;
  margin-bottom: .75rem;
}
.stats-head h2 { margin: 0; font-size: 1.05rem; }
.stats-live {
  font-size: .875rem;
  color: #6b7a72;
  font-weight: 600;
}
/* Точка «сейчас кто-то есть» — единственный цвет в блоке, который что-то
   значит. Без людей он серый: зелёный цвет без события был бы ложью. */
.stats-live.is-live { color: #1c6b3a; }
.stats-live.is-live::before {
  content: "";
  display: inline-block;
  width: 8px; height: 8px;
  margin-right: .4rem;
  border-radius: 50%;
  background: #1c6b3a;
  vertical-align: middle;
  animation: stats-pulse 2s ease-in-out infinite;
}
@keyframes stats-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: .5rem;
}
.stats-cell {
  padding: .6rem .7rem;
  background: #f6f8f6;
  border-radius: 9px;
}
.stats-cell-muted { background: #f2f2f2; }
.stats-num {
  display: block;
  font-size: 1.4rem;
  font-weight: 700;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.stats-label { display: block; font-size: .75rem; color: #64726b; margin-top: .15rem; }
.stats-more { margin-top: .6rem; font-size: .875rem; }
.stats-more summary { cursor: pointer; color: #2f6b45; }
.stats-list { margin: .4rem 0 0; padding: 0; list-style: none; }
.stats-list li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: .2rem 0;
  border-bottom: 1px dashed #e3e8e3;
}
.stats-note { margin: .7rem 0 0; font-size: .75rem; color: #7b8781; }
