/* ==========================================================================
   Экран «Коллекция героев» — тёмный графит с золотом.
   Шрифты: Cinzel (заголовки/имена), Cormorant Garamond (текст/метаданные).
   Стили здесь намеренно изолированы под .collection-screen / .cg-*, чтобы
   не задевать остальные экраны на текущей фиолетовой теме (styles.css).
   ========================================================================== */

/* Переменные --cg-* переехали в :root (styles.css, Раздел 7): держать их
   на каждом экране-владельце оказалось ловушкой — модалка в document.body
   и экран отряда об это уже спотыкались. Здесь остаётся только оформление
   самих экранов. */
.collection-screen,
.squad-screen,
.combat-screen {
  font-family: "Cormorant Garamond", "Georgia", serif;
  color: var(--cg-text);
  background: radial-gradient(ellipse at top, #201c1f 0%, var(--cg-bg) 60%);
  border: 1px solid var(--cg-border);
  border-radius: 14px;
  padding: 22px 20px 28px;
}

.collection-screen *,
.squad-screen *,
.combat-screen *,
.cg-modal-overlay * {
  box-sizing: border-box;
}

/* Общие примитивы (заголовок, подзаголовок, разделитель, панель, кнопки)
   уехали в styles.css под именами .title / .subtitle / .divider / .panel /
   .btn — по одному имени на вещь. Здесь остаётся только то, что
   принадлежит конкретным экранам. */

.cg-panel__row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 24px;
  align-items: baseline;
  justify-content: space-between;
}

.cg-panel__stat {
  font-family: "Cinzel", serif;
  font-size: 15px;
  color: var(--cg-gold-bright);
}

.cg-panel__stat-label {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 13px;
  color: var(--cg-text-dim);
  margin-right: 6px;
}

.cg-panel__pity {
  font-family: "Cormorant Garamond", serif;
  font-size: 14px;
  color: var(--cg-text-dim);
  white-space: pre-line;
  margin-top: 8px;
}

/* Toolbar: search + element filters + sort */
.cg-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 18px;
}

.cg-search {
  flex: 1 1 220px;
  min-width: 180px;
  background: var(--cg-bg-panel);
  border: 1px solid var(--cg-border);
  border-radius: 999px;
  color: var(--cg-text);
  padding: 9px 16px;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 14px;
}

.cg-search:focus {
  outline: none;
  border-color: var(--cg-gold-dim);
  box-shadow: 0 0 0 1px var(--cg-gold-dim);
}

.cg-search::placeholder {
  color: var(--cg-text-dim);
}

.cg-sort {
  background: var(--cg-bg-panel);
  border: 1px solid var(--cg-border);
  border-radius: 999px;
  color: var(--cg-text);
  padding: 9px 16px;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 14px;
}

.cg-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.cg-chip {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 8px 16px;
  border-radius: 999px;
  border: 1px solid var(--cg-border);
  background: none;
  color: var(--cg-text-dim);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s, box-shadow 0.15s;
}

.cg-chip:hover {
  color: var(--cg-text);
  background: var(--cg-bg-card);
}

.cg-chip.is-active {
  color: #171217;
  border-color: var(--chip-color, var(--cg-gold));
  background: var(--chip-color, var(--cg-gold));
  box-shadow: 0 0 12px 1px var(--chip-color, var(--cg-gold));
}

/* Grid + cards */
.cg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 20px;
}

.cg-card {
  position: relative;
  background: linear-gradient(165deg, var(--cg-bg-card), #161417 75%);
  border: 1px solid var(--cg-border);
  border-radius: 12px;
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.25s ease, border-color 0.2s ease, box-shadow 0.25s ease;
  transform-style: preserve-3d;
  will-change: transform;
  --glow: var(--cg-el-nature);
}

.cg-card[data-element="fire"] { --glow: var(--cg-el-fire); }
.cg-card[data-element="water"] { --glow: var(--cg-el-water); }
.cg-card[data-element="nature"] { --glow: var(--cg-el-nature); }
.cg-card[data-element="air"] { --glow: var(--cg-el-air); }
.cg-card[data-element="metal"] { --glow: var(--cg-el-metal); }
.cg-card[data-element="light"] { --glow: var(--cg-el-light); }
.cg-card[data-element="dark"] { --glow: var(--cg-el-dark); }

/* Рамка и ореол — цвет редкости; --glow (стихия) остаётся у значка
   стихии и заглушки, чтобы обе подсказки жили одновременно. */
.cg-card {
  border-color: color-mix(in srgb, var(--rarity, var(--cg-border)) 45%, var(--cg-border));
}

.cg-card:hover {
  border-color: var(--rarity, var(--glow));
  box-shadow: 0 10px 28px -8px rgba(0, 0, 0, 0.65), 0 0 32px -4px var(--rarity, var(--glow));
}

/* Наклон карточки за курсором задаётся инлайн-стилем из JS
   (heroesCollection.js) — .cg-card:hover выше только красит рамку и
   свечение, transform ей больше не занимается, чтобы не спорить с JS. */

/* .cg-card__face — «лицевая сторона» карточки: пропорции 2:3,
   иллюстрация во весь кадр, плашка с именем поверх. С Раздела 7 её рисуют
   все три места — коллекция, каталог и призыв. Старая .cg-card__portrait
   (3:4 с текстом отдельным блоком снизу) удалена вместе с .cg-card__body
   и .cg-card__level: ими пользовался только призыв. */
.cg-card__face {
  position: relative;
  aspect-ratio: 2 / 3;
  overflow: hidden;
  background: var(--cg-bg-panel);
  border-radius: 12px;
}

.cg-card__face::before {
  content: "";
  position: absolute;
  inset: -20%;
  z-index: 0;
  background: radial-gradient(circle at 50% 35%, var(--glow) 0%, transparent 60%);
  opacity: 0.5;
  filter: blur(16px);
  animation: cg-glow-pulse 3.2s ease-in-out infinite;
  pointer-events: none;
}

.cg-card__face .cg-card__img {
  filter: brightness(0.82);
  transition: transform 0.25s ease, filter 0.25s ease;
}

.cg-card:hover .cg-card__face .cg-card__img {
  filter: brightness(1.05);
  transform: scale(1.06);
}

.cg-card__face .cg-card__placeholder {
  transition: transform 0.25s ease, filter 0.25s ease;
}

.cg-card:hover .cg-card__face .cg-card__placeholder {
  filter: brightness(1.15);
  transform: scale(1.03);
}

.cg-card__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(to top, rgba(8, 7, 9, 0.92) 0%, rgba(8, 7, 9, 0.6) 32%, transparent 62%);
  pointer-events: none;
}

.cg-card__plate {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  padding: 12px 14px 14px;
}

.cg-card__img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: drop-shadow(0 0 10px var(--glow));
}

.cg-card__placeholder {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Cinzel", serif;
  font-size: 42px;
  font-weight: 700;
  color: var(--cg-text);
  background: linear-gradient(160deg, color-mix(in srgb, var(--glow) 65%, #17151a), color-mix(in srgb, var(--glow) 25%, #17151a) 85%);
  text-shadow: 0 0 14px var(--glow);
}

.cg-card__element-tag {
  position: absolute;
  z-index: 4;
  top: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  padding: 5px;
  box-sizing: border-box;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(16, 15, 17, 0.75);
  color: var(--glow);
  border: 1px solid var(--glow);
  box-shadow: 0 0 8px var(--glow);
}

.cg-card__element-tag svg {
  display: block;
}

.cg-card__frame-corner {
  position: absolute;
  z-index: 4;
  width: 16px;
  height: 16px;
  border: 1px solid var(--cg-gold-dim);
  opacity: 0.8;
  pointer-events: none;
}

.cg-card__frame-corner--tl { top: 6px; left: 6px; border-right: none; border-bottom: none; }
.cg-card__frame-corner--br { bottom: 6px; right: 6px; border-left: none; border-top: none; }

/* Итог призыва — подпись под карточкой: новый герой, душа или эссенция. */
.cg-card__result {
  padding: 8px 10px 10px;
  border-top: 1px solid var(--cg-border-soft);
  text-align: center;
}

.cg-card__name {
  font-family: "Cinzel", serif;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--cg-gold-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cg-card__stars {
  color: var(--rarity, var(--cg-gold));
  font-size: 12px;
  letter-spacing: 1px;
  margin: 2px 0 6px;
  text-shadow: 0 0 7px color-mix(in srgb, var(--rarity, var(--cg-gold)) 60%, transparent);
}

.cg-card__role {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  color: var(--cg-text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cg-bar {
  height: 7px;
  border-radius: 999px;
  background: #0d0c0e;
  border: 1px solid var(--cg-border);
  overflow: hidden;
}

.cg-bar__fill {
  height: 100%;
  border-radius: 999px;
  transition: width 0.2s;
}

.cg-bar__fill--hp-high { background: linear-gradient(90deg, #3f8f5a, #58c97a); }
.cg-bar__fill--hp-mid { background: linear-gradient(90deg, var(--cg-gold-dim), var(--cg-gold)); }
.cg-bar__fill--hp-low { background: linear-gradient(90deg, #8f3f3f, #d15656); }

.cg-status {
  display: inline-block;
  margin-top: 6px;
  font-family: "Cormorant Garamond", serif;
  font-size: 11px;
  letter-spacing: 0.03em;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--cg-border);
  color: var(--cg-text-dim);
}

.cg-status--healthy { color: #7fd99a; border-color: #3f8f5a; }
.cg-status--wounded { color: var(--cg-gold-bright); border-color: var(--cg-gold-dim); }
.cg-status--unconscious { color: #d17a7a; border-color: #8f3f3f; }

@keyframes cg-glow-pulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 0.75; }
}

@media (prefers-reduced-motion: reduce) {
  .cg-card__face::before {
    animation: none;
  }
}

/* ---- Detail modal ------------------------------------------------------ */
.cg-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(6, 5, 6, 0.62);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 300;
  padding: 16px;
  overflow: hidden;
}

/* Иллюстрация героя за панелью: сильное размытие + затемнение, чтобы
   читалась как атмосферный фон, а не как вторая картинка. inset:-8%
   компенсирует «съедаемые» блюром края, иначе по периметру видны
   полупрозрачные полосы. */
.cg-modal-overlay__art {
  position: absolute;
  inset: -8%;
  background-size: cover;
  background-position: center 22%;
  filter: blur(38px) saturate(1.1) brightness(0.42);
  opacity: 0.85;
  transform: scale(1.08);
  pointer-events: none;
}

.cg-modal-overlay__art::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at center, rgba(6, 5, 6, 0.35) 0%, rgba(6, 5, 6, 0.88) 78%);
}

.cg-modal {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 880px;
  max-height: 90vh;
  overflow-y: auto;
  background: linear-gradient(165deg, rgba(31, 28, 34, 0.82), rgba(12, 11, 14, 0.88));
  -webkit-backdrop-filter: blur(18px) saturate(1.15);
  backdrop-filter: blur(18px) saturate(1.15);
  border: 1px solid color-mix(in srgb, var(--rarity, var(--cg-gold)) 35%, var(--cg-border));
  border-radius: 16px;
  padding: 26px 28px 28px;
  box-shadow: 0 24px 70px -20px rgba(0, 0, 0, 0.85), 0 0 46px -14px var(--rarity, var(--cg-gold));
  font-family: "Cormorant Garamond", serif;
  color: var(--cg-text);
  animation: cg-modal-in 0.22s ease-out;
}

@keyframes cg-modal-in {
  from { opacity: 0; transform: translateY(10px) scale(0.985); }
  to { opacity: 1; transform: none; }
}

.cg-modal__close {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 2;
  background: rgba(12, 11, 14, 0.6);
  border: 1px solid var(--cg-border);
  color: var(--cg-text-dim);
  border-radius: 50%;
  width: 30px;
  height: 30px;
  cursor: pointer;
  font-size: 14px;
  line-height: 1;
  transition: color 0.15s, border-color 0.15s, box-shadow 0.15s;
}

.cg-modal__close:hover {
  color: var(--cg-gold-bright);
  border-color: var(--cg-gold-dim);
  box-shadow: 0 0 12px -2px var(--cg-gold);
}

/* Слева карточка, справа данные. minmax(0,1fr) у правой колонки — иначе
   длинное описание растягивает грид и ломает пропорции. */
.cg-modal__grid {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: 26px;
  align-items: start;
}

.cg-modal__left {
  position: sticky;
  top: 0;
}

/* Та же лицевая сторона, что в сетке, но без наклона за курсором —
   в модалке он только мешает читать. */
.cg-detail-card {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--rarity, var(--cg-gold-dim));
  box-shadow: 0 0 34px -10px var(--rarity, var(--cg-gold));
}

.cg-detail-card .cg-card__img {
  filter: brightness(0.95);
}

.cg-modal__status-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-bottom: 10px;
}

.cg-modal__name {
  font-family: "Cinzel", serif;
  font-size: 22px;
  font-weight: 700;
  color: var(--cg-gold-bright, var(--gold-bright));
  margin: 0 0 4px;
}

.cg-modal__role {
  font-style: italic;
  color: var(--cg-text-dim, var(--muted));
  font-size: 15px;
}

.cg-modal__section-title {
  font-family: "Cinzel", serif;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cg-gold-dim, var(--gold-dim));
  margin: 16px 0 8px;
}

.cg-modal__desc {
  font-style: italic;
  color: var(--cg-text-dim, var(--muted));
  line-height: 1.5;
  font-size: 16px;
}

.cg-xp-label {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
  color: var(--cg-text-dim, var(--muted));
  margin-top: 4px;
}

/* ---- Производные боевые значения (крупные числа) ----------------------- */
.cg-derived-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: 10px;
}

.cg-derived {
  background: rgba(10, 9, 12, 0.5);
  border: 1px solid var(--cg-border);
  border-radius: 10px;
  padding: 10px 12px 9px;
  text-align: center;
}

.cg-derived__value {
  font-family: "Cinzel", serif;
  font-size: 24px;
  font-weight: 700;
  line-height: 1.15;
  background: linear-gradient(180deg, var(--cg-gold-bright), var(--cg-gold) 70%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Плитка со словом, а не числом: «Тип урона». Двадцать четыре пункта
   Cinzel рассчитаны на «310» и «90%», а «Смешанный» в ту же ширину
   не влезает — на 900 точках «Физический» обрезался до «Физичесі».
   Отдельный размер вместо переноса: слово короткое, и в две строки
   плитка стала бы выше соседних. */
/* Число с оговоркой: «до 205%». Двадцать четыре пункта Cinzel плюс
   «до» не влезают в плитку 111 точек и переносятся на вторую строку,
   распирая весь ряд — поймано глазами на 390. Число чуть мельче,
   оговорка своим шрифтом, перенос запрещён. */
.cg-derived__value--capped {
  font-size: 20px;
  white-space: nowrap;
}

.cg-derived__cap {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.02em;
  margin-right: 3px;
}

.cg-derived__value--word {
  font-size: 13px;
  letter-spacing: 0.01em;
  padding: 6px 0 5px;
  /* Сеть на случай экрана уже телефона автора: перенос вместо
     обрезки. На 375 точках «Смешанный» помещается в строку, но
     обрезанное слово читается поломкой, а перенесённое — нет. */
  overflow-wrap: break-word;
}

.cg-derived__label {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.02em;
  color: var(--cg-text-dim);
  margin-top: 3px;
}

/* ---- Полоски характеристик --------------------------------------------- */
.cg-statbar-group {
  display: grid;
  gap: 9px;
}

.cg-statbar__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 14px;
  margin-bottom: 3px;
}

.cg-statbar__label {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 12px;
  color: var(--cg-text-dim);
}

.cg-statbar__value {
  font-family: "Cinzel", serif;
  font-size: 14px;
  font-weight: 600;
  color: var(--cg-gold-bright);
}

.cg-statbar__track {
  height: 8px;
  border-radius: 999px;
  background: #0d0c0e;
  border: 1px solid var(--cg-border);
  overflow: hidden;
}

/* Ширину задаёт JS (heroesCollection.js): элемент вставляется нулевым и
   получает целевое значение на следующем кадре — этот переход и есть
   «наливание». transition-delay ставится там же, лесенкой по индексу. */
.cg-statbar__fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--cg-gold-dim), var(--cg-gold) 55%, var(--cg-gold-bright));
  box-shadow: 0 0 10px -2px var(--cg-gold);
  transition: width 0.75s cubic-bezier(0.22, 0.68, 0.24, 1);
}

@media (prefers-reduced-motion: reduce) {
  .cg-statbar__fill {
    transition: none;
  }
}

@media (max-width: 640px) {
  .cg-grid {
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 14px;
  }

  /* На телефоне панель — «шторка» снизу: прижимаем к нижнему краю,
     убираем внешние отступы и скругляем только верх. */
  .cg-modal-overlay {
    align-items: flex-end;
    padding: 0;
  }

  .cg-modal {
    max-width: none;
    max-height: 92vh;
    border-radius: 18px 18px 0 0;
    border-bottom: none;
    padding: 22px 18px calc(22px + env(safe-area-inset-bottom, 0px));
    animation: cg-sheet-up 0.26s ease-out;
  }

  /* minmax(0,1fr), а не 1fr: голый 1fr означает minmax(auto,1fr), и
     длинное описание способности растягивает грид — как в десктопной
     версии выше, где то же знание стоит над двухколоночной сеткой. */
  .cg-modal__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: 18px;
  }

  /* Карточка не липнет и не занимает весь экран: на узкой ширине она
     всего лишь заставка сверху, дальше сразу данные. */
  .cg-modal__left {
    position: static;
    max-width: 190px;
    margin: 0 auto;
  }

  .cg-derived-grid {
    grid-template-columns: repeat(auto-fit, minmax(92px, 1fr));
  }
}

@keyframes cg-sheet-up {
  from { transform: translateY(100%); }
  to { transform: none; }
}

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

/* ==========================================================================
   Экран «Отряды» (.sq-*)
   Здесь только то, что принадлежит самому экрану: доска, ячейки, схемы
   расстановок. Заголовок, кнопки, поля, пустые состояния — общие
   (.title / .btn / .field / .empty-state в styles.css).
   ========================================================================== */

.sq-section-title {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: "Cinzel", serif;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cg-gold-dim);
  margin: 22px 0 10px;
}

.sq-name-input {
  max-width: 340px;
}

.sq-save-row {
  display: flex;
  justify-content: flex-end;
  margin-top: 6px;
}

/* ---- Список отрядов ----------------------------------------------------- */
.sq-list {
  display: grid;
  gap: 12px;
}

.sq-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
  background: var(--cg-bg-panel);
  border: 1px solid var(--cg-border);
  border-radius: 12px;
  padding: 14px 16px;
  transition: border-color 0.2s, box-shadow 0.2s;
}

.sq-row:hover {
  border-color: var(--cg-gold-dim);
}

.sq-row.is-active {
  border-color: var(--cg-gold-dim);
  box-shadow: inset 3px 0 0 var(--cg-gold);
}

.sq-row__name-line {
  display: flex;
  align-items: center;
  gap: 10px;
}

.sq-row__name {
  font-family: "Cinzel", serif;
  font-size: 17px;
  font-weight: 600;
  color: var(--cg-gold-bright);
}

.sq-row__badge {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.03em;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--cg-gold);
  color: #171217;
  font-weight: 600;
}

.sq-row__meta {
  font-size: 14px;
  color: var(--cg-text-dim);
  margin-top: 3px;
}

.sq-row__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* ---- Карточки расстановки ----------------------------------------------- */
.sq-formations {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}

.sq-formation {
  background: var(--cg-bg-panel);
  border: 1px solid var(--cg-border);
  border-radius: 12px;
  padding: 14px 12px 12px;
  cursor: pointer;
  color: var(--cg-text);
  font-family: inherit;
  text-align: center;
  transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}

.sq-formation:hover {
  border-color: var(--cg-gold-dim);
  transform: translateY(-2px);
}

.sq-formation.is-active {
  border-color: var(--cg-gold);
  box-shadow: 0 0 18px -6px var(--cg-gold), inset 0 0 0 1px var(--cg-gold-dim);
}

.sq-formation__title {
  font-family: "Cinzel", serif;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--cg-gold-bright);
  margin-bottom: 10px;
}

.sq-formation__caption {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 10.5px;
  line-height: 1.35;
  color: var(--cg-text-dim);
  margin-top: 10px;
}

/* Схема из точек: три линии сверху вниз — передняя, средняя, задняя. */
.sq-diagram {
  display: grid;
  gap: 6px;
  padding: 8px 0;
}

.sq-diagram__line {
  display: grid;
  grid-template-columns: 14px 1fr;
  align-items: center;
  gap: 8px;
}

.sq-diagram__line-label {
  font-family: "Cinzel", serif;
  font-size: 10px;
  color: var(--cg-gold-dim);
}

.sq-diagram__dots {
  display: flex;
  gap: 6px;
  justify-content: center;
  min-height: 12px;
  align-items: center;
  border-bottom: 1px dashed var(--cg-border);
  padding-bottom: 5px;
}

.sq-diagram__dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--cg-text-dim);
  transition: background 0.2s, box-shadow 0.2s;
}

.sq-formation.is-active .sq-diagram__dot {
  background: var(--cg-gold);
  box-shadow: 0 0 8px var(--cg-gold);
}

/* ---- Доска отряда ------------------------------------------------------- */
.sq-counter {
  font-family: "Cinzel", serif;
  font-size: 13px;
  letter-spacing: 0.04em;
  color: var(--cg-text-dim);
  border: 1px solid var(--cg-border);
  border-radius: 999px;
  padding: 2px 12px;
  text-transform: none;
}

.sq-counter.is-full {
  color: var(--cg-gold-bright);
  border-color: var(--cg-gold-dim);
}

.sq-counter.is-shaking {
  animation: sq-shake 0.36s ease;
}

@keyframes sq-shake {
  0%, 100% { transform: translateX(0); }
  20% { transform: translateX(-5px); }
  45% { transform: translateX(4px); }
  70% { transform: translateX(-2px); }
}

.sq-board {
  display: grid;
  gap: 10px;
  background: rgba(10, 9, 12, 0.45);
  border: 1px solid var(--cg-border);
  border-radius: 12px;
  padding: 14px;
}

.sq-line {
  display: grid;
  grid-template-columns: 120px 1fr;
  align-items: center;
  gap: 12px;
}

.sq-line__label {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--cg-text-dim);
  text-align: right;
}

.sq-line__cells {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.sq-cell {
  position: relative;
  width: 78px;
  height: 104px;
  border-radius: 10px;
  cursor: pointer;
  padding: 0;
  overflow: hidden;
  font-family: inherit;
  transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}

.sq-cell--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px dashed var(--cg-border);
  background: rgba(255, 255, 255, 0.015);
  color: var(--cg-text-dim);
  font-size: 26px;
  line-height: 1;
}

.sq-cell--empty:hover {
  border-color: var(--cg-gold-dim);
  color: var(--cg-gold-bright);
}

.sq-cell--filled {
  border: 1px solid var(--rarity, var(--cg-gold-dim));
  background: var(--cg-bg-panel);
  box-shadow: 0 0 14px -6px var(--rarity, var(--cg-gold));
}

.sq-cell--filled:hover {
  transform: translateY(-2px);
  box-shadow: 0 0 20px -6px var(--rarity, var(--cg-gold));
}

/* Выбранная для обмена ячейка — золотом, а не цветом стихии: это
   состояние интерфейса, а не свойство героя. */
.sq-cell--filled.is-picked {
  border-color: var(--cg-gold-bright);
  box-shadow: 0 0 0 2px var(--cg-gold), 0 0 22px -4px var(--cg-gold);
  transform: translateY(-2px);
}

.sq-cell__img,
.sq-cell__placeholder {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.sq-cell__placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Cinzel", serif;
  font-size: 26px;
  font-weight: 700;
  color: var(--cg-text);
  background: linear-gradient(160deg, color-mix(in srgb, var(--glow) 60%, #17151a), color-mix(in srgb, var(--glow) 20%, #17151a) 85%);
}

.sq-cell__index {
  position: absolute;
  top: 4px;
  left: 4px;
  min-width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--cg-gold);
  color: #171217;
  font-family: "Cinzel", serif;
  font-size: 11px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

.sq-cell__name {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 12px 5px 4px;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 10px;
  color: var(--cg-text);
  background: linear-gradient(to top, rgba(8, 7, 9, 0.95), transparent);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- Сетка выбора героев ------------------------------------------------ */
.sq-picker-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 14px;
  border-radius: 12px;
  transition: box-shadow 0.3s;
}

/* Короткая подсветка сетки, когда её позвали кнопкой «+» с доски. */
.sq-picker-grid.is-calling {
  box-shadow: 0 0 0 2px var(--cg-gold-dim), 0 0 26px -8px var(--cg-gold);
}

.sq-pick {
  position: relative;
}

.sq-pick.is-selected {
  border-color: var(--cg-gold);
  box-shadow: 0 0 0 2px var(--cg-gold), 0 0 24px -6px var(--cg-gold);
}

.sq-pick__index {
  position: absolute;
  z-index: 5;
  top: 6px;
  left: 6px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: linear-gradient(180deg, var(--cg-gold-bright), var(--cg-gold) 70%);
  color: #171217;
  font-family: "Cinzel", serif;
  font-size: 13px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 0 12px -2px var(--cg-gold);
}

@media (prefers-reduced-motion: reduce) {
  .sq-counter.is-shaking {
    animation: none;
  }

  .sq-formation,
  .sq-cell {
    transition: none;
  }
}

@media (max-width: 640px) {
  .sq-line {
    grid-template-columns: 1fr;
    gap: 5px;
  }

  .sq-line__label {
    text-align: left;
  }

  .sq-picker-grid {
    grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
    gap: 10px;
  }

  .sq-row__actions {
    width: 100%;
  }
}

/* ==========================================================================
   Призыв: экран (.sm-screen …) и сцена анимации (.sm-overlay …)
   Цвет уровня события приходит из JS переменной --tier, цвет стихии
   героя — --glow, цвет редкости конкретной карточки — --rarity.
   ========================================================================== */

/* Баннер: кто выбран и кого можно выбрать. Стоит над панелью призыва —
   игрок читает, за кем идёт, до того как тратит жетоны. */
.sm-banner {
  display: grid;
  gap: 10px;
  padding: 14px 16px;
  margin-bottom: 14px;
  border: 1px solid var(--cg-gold-dim);
  border-radius: 12px;
  background: linear-gradient(165deg, var(--cg-bg-card), #161417 78%);
  text-align: center;
}

/* Блока нет вовсе, когда сервер не прислал баннер: рамка без
   содержимого читалась бы поломкой. */
.sm-banner.is-empty {
  display: none;
}

.sm-banner__title {
  font-size: 15px;
  color: var(--cg-text-dim);
}

.sm-banner__title.is-chosen {
  color: var(--cg-gold);
  font-weight: 600;
}

/* Кнопки в строку с переносом: пятёрок сегодня три, завтра будет
   восемь, и на 390 они обязаны переноситься сами, а не выезжать. */
.sm-banner__list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
}

.sm-banner__choice {
  flex: 0 1 auto;
  min-height: 36px;
  padding: 7px 14px;
  border: 1px solid var(--cg-gold-dim);
  border-radius: 999px;
  background: transparent;
  color: var(--cg-text);
  font-size: 14px;
  cursor: pointer;
}

.sm-banner__choice.is-picked {
  border-color: var(--cg-gold);
  background: rgba(198, 163, 90, 0.16);
  color: var(--cg-gold);
  font-weight: 600;
}

.sm-banner__hint {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 13px;
  color: var(--cg-text-dim);
}

.sm-panel {
  display: grid;
  gap: 20px;
  justify-items: center;
  padding: 8px 0 4px;
}

.sm-crystals {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.sm-crystals__value {
  font-family: "Cinzel", serif;
  font-size: 40px;
  font-weight: 700;
  background: linear-gradient(180deg, var(--gold-bright), var(--gold) 70%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.sm-crystals__label {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 15px;
  color: var(--cg-text-dim);
}

.sm-pity {
  width: 100%;
  max-width: 420px;
}

.sm-pity__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 12px;
  color: var(--cg-text-dim);
  margin-bottom: 5px;
}

.sm-pity__count {
  color: var(--cg-gold-bright);
}

/* Мягкий гарант уже включился — полоса перестаёт быть просто счётчиком. */
.bar__fill.is-soft {
  background: linear-gradient(90deg, var(--gold), var(--gold-bright));
  box-shadow: 0 0 12px -2px var(--gold);
}

.sm-pull-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
  width: 100%;
  max-width: 560px;
}

.sm-pull {
  display: grid;
  gap: 3px;
  justify-items: center;
  padding: 16px 14px;
  border-radius: 12px;
  border: 1px solid var(--cg-gold-dim);
  background: linear-gradient(165deg, var(--cg-bg-card), #161417 78%);
  color: var(--cg-text);
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}

.sm-pull:hover:not(:disabled) {
  border-color: var(--cg-gold);
  box-shadow: 0 0 20px -6px var(--cg-gold);
  transform: translateY(-2px);
}

.sm-pull--hero {
  border-color: var(--cg-gold);
  box-shadow: inset 0 0 24px -14px var(--cg-gold);
}

.sm-pull:disabled {
  opacity: 0.42;
  cursor: not-allowed;
  transform: none;
  box-shadow: none;
  border-color: var(--cg-border);
}

.sm-pull__label {
  font-family: "Cinzel", serif;
  font-size: 17px;
  font-weight: 600;
  color: var(--cg-gold-bright);
}

.sm-pull__cost {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 12px;
  color: var(--cg-text);
}

.sm-pull__note {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  color: var(--cg-text-dim);
  text-align: center;
}

/* ==========================================================================
   Магазин (.shop-*) — инструмент разработки, а не игровая механика.
   Пометка о тестовом режиме сделана нарочито заметной: через месяц никто
   не должен принять эти цены за настоящую экономику.
   ========================================================================== */

.shop-notice {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
  padding: 12px 16px;
  border: 1px solid var(--cg-gold);
  border-left-width: 4px;
  border-radius: 10px;
  background: linear-gradient(90deg, rgba(201, 163, 92, 0.16), rgba(201, 163, 92, 0.05));
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 13px;
  color: var(--cg-gold-bright);
  letter-spacing: 0.01em;
}

.shop-notice__mark {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 1px solid var(--cg-gold);
  font-family: "Cinzel", serif;
  font-weight: 700;
}

.shop-wallet {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}

.shop-wallet__item {
  display: grid;
  justify-items: center;
  gap: 2px;
  padding: 12px 10px;
  border: 1px solid var(--cg-border);
  border-radius: 10px;
  background: var(--cg-bg-panel);
}

.shop-wallet__value {
  font-family: "Cinzel", serif;
  font-size: 24px;
  font-weight: 700;
  background: linear-gradient(180deg, var(--cg-gold-bright), var(--cg-gold) 70%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.shop-wallet__label {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  color: var(--cg-text-dim);
}

.shop-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 14px;
}

.shop-card {
  display: grid;
  gap: 8px;
  justify-items: center;
  padding: 18px 16px;
  border: 1px solid var(--cg-gold-dim);
  border-radius: 12px;
  background: linear-gradient(165deg, var(--cg-bg-card), #161417 78%);
  text-align: center;
}

.shop-card.is-blocked {
  border-color: var(--cg-border);
  opacity: 0.7;
}

.shop-card__title {
  font-family: "Cinzel", serif;
  font-size: 18px;
  font-weight: 600;
  color: var(--cg-gold-bright);
}

.shop-card__note {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 15px;
  color: var(--cg-text-dim);
  min-height: 22px;
}

/* Ползунок количества. Во всю ширину карточки: узкий на 390 давал бы
   меньше хода пальцу, а промах здесь стоит денег. */
.shop-card__slider {
  width: 100%;
  margin: 2px 0 0;
  accent-color: var(--cg-gold);
  /* Палец толще ползунка: полоса тонкая, а трогать её надо уверенно.
     Высота задаётся здесь, а не оставляется на усмотрение браузера. */
  height: 28px;
  cursor: pointer;
}

/* Итог выбора: сколько и почём. Своя строка, а не подпись кнопки —
   игрок читает её до нажатия, и она обязана стоять на месте. */
.shop-card__preview {
  font-size: 15px;
  font-weight: 600;
  color: var(--cg-gold);
  min-height: 20px;
}

.shop-card__buy {
  margin-top: 4px;
  width: 100%;
}

@media (max-width: 640px) {
  .shop-grid {
    grid-template-columns: 1fr;
  }

  .shop-notice {
    font-size: 12px;
    padding: 10px 12px;
  }
}

/* ---- Сцена -------------------------------------------------------------- */

.sm-overlay {
  position: fixed;
  inset: 0;
  z-index: 400;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 20px 16px;
  background: rgba(4, 4, 6, 0);
  transition: background 0.4s ease;
  overflow-y: auto;
}

.sm-overlay.is-dark {
  background: rgba(4, 4, 6, 0.93);
  /* Тем же цветом за краями. На молчании башни сцена дрожит — вся,
     оверлеем, — и без этой каймы на краю экрана мигала бы полоска
     игры под ним. Тень не занимает места и за экраном не видна. */
  box-shadow: 0 0 0 8px rgba(4, 4, 6, 0.93);
}

/* В финале кнопки «Пропустить» уже нет, поэтому верхний отступ под неё не
   нужен — он же и выталкивал сетку за экран. */
.sm-overlay.is-final {
  background: rgba(4, 4, 6, 0.96);
  justify-content: center;
  padding-top: 16px;
  gap: 12px;
}

.sm-overlay.is-final .sm-stage {
  gap: 10px;
}

.sm-canvas {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Вспышка — отдельный слой поверх частиц, чтобы гаснуть независимо.
   Длительность приходит из JS переменной --flash-ms: у перламутра она
   заметно дольше, и держать её в двух местах значило бы рассинхрон. */
.sm-flash {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  --flash-ms: 340ms;
  /* Запасной белый не украшение: у десятки --tier появляется только в
     удар, и до него ссылка была бы в пустоту — всё объявление стало бы
     недействительным, а вспышка невидимой. */
  background: radial-gradient(circle at center, #fff 0%, var(--tier, #ffffff) 28%, transparent 72%);
}

.sm-flash.is-firing {
  animation: sm-flash var(--flash-ms) ease-out;
}

/* Расходящиеся лучи — только для 5★. Конический градиент с
   прозрачными секторами даёт лучи без единой картинки. */
.sm-flash.is-rays::after {
  content: "";
  position: absolute;
  inset: -40%;
  background: conic-gradient(
    from 0deg,
    transparent 0deg 8deg,
    rgba(255, 255, 255, 0.55) 9deg 11deg,
    transparent 12deg 30deg,
    rgba(240, 209, 144, 0.5) 31deg 33deg,
    transparent 34deg 60deg,
    rgba(217, 212, 242, 0.45) 61deg 63deg,
    transparent 64deg 90deg
  );
  animation: sm-rays var(--flash-ms) ease-out;
  mix-blend-mode: screen;
}

@keyframes sm-flash {
  0% { opacity: 0; transform: scale(0.6); }
  22% { opacity: 1; transform: scale(1.05); }
  100% { opacity: 0; transform: scale(1.25); }
}

@keyframes sm-rays {
  0% { opacity: 0; transform: rotate(0deg) scale(0.5); }
  30% { opacity: 0.9; }
  100% { opacity: 0; transform: rotate(34deg) scale(1.5); }
}

/* Ударная волна кульминации: расходящееся кольцо поверх всей сцены. */
.sm-overlay.is-shockwave::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 40px;
  height: 40px;
  margin: -20px 0 0 -20px;
  border-radius: 50%;
  border: 2px solid rgba(255, 248, 240, 0.85);
  box-shadow: 0 0 40px 10px rgba(240, 209, 144, 0.35);
  pointer-events: none;
  animation: sm-shockwave 0.9s cubic-bezier(0.16, 0.75, 0.3, 1);
}

@keyframes sm-shockwave {
  0% { transform: scale(0.2); opacity: 0; border-width: 3px; }
  15% { opacity: 1; }
  100% { transform: scale(34); opacity: 0; border-width: 0.5px; }
}

/* Виньетка: края темнее центра, чтобы взгляд держался там, где событие.

   Отдельный слой, а не фильтр на весь оверлей: фильтр затемнил бы вместе
   со сценой и сетку результатов, и кнопку «Пропустить».

   z-index 1 — выше канваса и вспышки (у обоих его нет, они ложатся по
   порядку разметки) и ниже .sm-stage (2).

   Градиент тот же, что у виньетки страницы в styles.css: держать два
   разных затемнения краёв в одном приложении незачем. */
.sm-vignette {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.4s ease;
  background: radial-gradient(ellipse at center, transparent 40%, rgba(0, 0, 0, 0.55) 100%);
}

/* Приходит вместе с затемнением фона и с той же длительностью. Иначе тёмная
   рама возникает в первом же кадре — за 0.4 с до самой темноты, поверх ещё
   видимого экрана игры. */
.sm-overlay.is-dark .sm-vignette {
  opacity: 1;
}

/* Виньетка сходится к центру на молчании башни: тёмные края медленно
   наползают. Второй градиент поверх первого, а не подмена первого:
   градиенты один в другой не переходят, и перекрасить его нечем.

   Длительность приходит из JS переменной --close-ms — это длина всего
   отрезка молчания, а она у каждой записи своя. */
.sm-vignette::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  background: radial-gradient(ellipse at center, transparent 8%, rgba(0, 0, 0, 0.92) 78%);
  /* Распахивается она быстрее, чем сходилась, и число это своё:
     сходится виньетка весь отрезок молчания, а распахивается на ударе,
     вместе со вспышкой. Тянись она обратно те же две секунды — удар
     разъехался бы с собственным последствием. */
  transition: opacity 320ms ease-out;
}

.sm-overlay.is-closing .sm-vignette::after {
  opacity: 1;
  transition: opacity var(--close-ms, 900ms) ease-in;
}

/* В финале виньетка уходит совсем: сетка результатов должна читаться
   целиком, включая карточки по краям. */
.sm-overlay.is-final .sm-vignette {
  display: none;
}

/* ==========================================================================
   Сцена появления 5★ (.hs-…): чёрный экран, субтитры, стихия, лицо

   Живёт внутри оверлея призыва и поверх его канваса, но ниже кнопок:
   слои там расписаны по z-index — виньетка 1, сцена 2, кнопки 3.
   ========================================================================== */

.hs-layer {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  overflow: hidden;
}

.hs-canvas {
  position: absolute;
  inset: 0;
}

/* Субтитр СТОИТ НА МЕСТЕ и стоит по центру экрана. Блок фиксированной
   высоты, текст в нём по центру: фраза из трёх слов и фраза из десяти
   занимают одно и то же место, и при смене ничего не прыгает — глаз
   ловил бы прыжок вместо чтения.

   Середина свободна: арт приходит через три с половиной секунды после
   того, как гаснет последняя фраза, и накладок не бывает.

   Шрифт заголовочный, Cinzel, — тот же, которым набраны заголовки
   экранов и имя героя под артом. Системный на чёрном экране под
   оркестр читался дешевле, чем звучит. Разрядка и мягкое свечение —
   чтобы строка держалась на любом фоне, включая светлый арт. */
.hs-line {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(86%, 26em);
  height: 4.5em;
  text-align: center;
  font-family: "Cinzel", serif;
  font-size: 22px;
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.05em;
  color: var(--text);
  text-shadow: 0 2px 16px rgba(0, 0, 0, 0.95), 0 0 26px rgba(0, 0, 0, 0.8);
  opacity: 0;
  transition: opacity 0.25s ease;
}

.hs-line.is-on {
  opacity: 1;
}

/* Лицо героя и имя под ним. Появляются вместе: в реплике имени нет, и
   без подписи игрок не узнает, кого призвал. */
.hs-face {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.35s ease;
}

.hs-face.is-on {
  opacity: 1;
}

/* Кадрирование то же, что у башни: по высоте экрана, ширина следом,
   центр по горизонтали. Соотношение не искажается. На широком экране по
   бокам остаётся тёмное поле, на узком — уходят края; лицо и подпись
   при этом на месте, они в середине и внизу. */
.hs-art {
  position: absolute;
  left: 50%;
  top: 0;
  height: 100%;
  width: auto;
  max-width: none;
  transform: translateX(-50%);
}

/* Запасной путь: чибик вместо карточки. Та же лестница, что в сетке
   результатов, поэтому и правила вписывания общие — здесь только
   размер кадра под него. */
.hs-chibi {
  position: absolute;
  left: 50%;
  top: 0;
  transform: translateX(-50%);
  width: min(100%, 66vh);
  height: 100%;
}

/* Последняя ступень: буква. Место занято, ничего не разъезжается. */
.hs-letter {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Cinzel", serif;
  font-size: 22vh;
  color: color-mix(in srgb, var(--glow, var(--gold)) 45%, #17151a);
}

/* Имя — в нижней пятой карточки: она тёмная у обоих артов, накладывать
   поверх лица не приходится. */
.hs-name {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 7%;
  text-align: center;
  font-family: "Cinzel", serif;
  font-size: clamp(24px, 7vw, 34px);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--gold-bright);
  text-shadow: 0 2px 20px rgba(0, 0, 0, 0.95);
}

/* Пропуск сцены. Появляется только вместе с артом — до раскрытия
   кнопка сама по себе спойлер, — и потому в разметке её до этого нет
   вовсе. Слой сцены событий мыши не ловит, кнопке они нужны. */
.hs-skip {
  position: absolute;
  top: 4%;
  right: 5%;
  pointer-events: auto;
}

@media (max-width: 480px) {
  .hs-line {
    font-size: 19px;
  }
}

/* Строка замера кадров: видна только при ?perf=1. Нарочно не похожа на
   игровую подпись — это отладочное число, а не часть сцены. */
.sm-perf {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 12px;
  /* Отступы в строках замера значащие: числа дополнены пробелами слева,
     и столбцы стоят друг под другом только благодаря им. Обычный перенос
     схлопывает подряд идущие пробелы, а вместе с ними и выравнивание;
     break-word держит длинную строку внутри карточки, а не гонит её
     за край экрана. */
  white-space: pre-wrap;
  overflow-wrap: break-word;
  color: #8a8594;
  border: 1px dashed rgba(138, 133, 148, 0.45);
  border-radius: 6px;
  padding: 4px 8px;
}

.sm-stage {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: 100%;
  max-width: 900px;
}

.sm-controls {
  position: relative;
  z-index: 3;
  display: flex;
  gap: 10px;
}

/* «Пропустить» видна с первого кадра — сотый призыв не должен смотреть
   всю сцену целиком. */
.sm-skip {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 5;
}

/* ---- Карточка в сцене --------------------------------------------------- */

/* width:100% обязателен. Сетка — flex-элемент в колонке .sm-stage с
   align-items:center, из-за чего её ширина схлопывается до содержимого;
   при неопределённой ширине repeat(auto-fit, …) сваливается в одну
   колонку — именно так десятка и выкладывалась столбиком. */
.sm-grid {
  display: grid;
  gap: 14px;
  width: 100%;
  justify-content: center;
}

/* Одиночный призыв — одна крупная карточка. Селектор с :not обязателен:
   у .sm-grid и .sm-grid--many одинаковая специфичность, и правило для
   одной карточки, оказавшись ниже по файлу, перебивало число колонок у
   десятки — из-за этого сетка на телефоне и схлопывалась в столбик. */
.sm-grid:not(.sm-grid--many) {
  grid-template-columns: minmax(0, 210px);
}

/* Десятка должна помещаться в экран целиком, поэтому размер карточки
   ограничен не шириной колонки, а высотой окна: строки карточек 2:3 плюс
   подписи обязаны уложиться в один экран. Число колонок задаётся точками
   останова ниже, число строк считается от него.
   clamp снизу не даёт карточкам превратиться в марки на низких экранах —
   тогда лучше короткая прокрутка, чем нечитаемая сетка. */
.sm-grid--many {
  --sm-cols: 5;
  --sm-rows: 2;
  --sm-gap: 10px;
  --sm-labels: 62px; /* имя + звёзды + плашка под карточкой */
  --sm-chrome: 150px; /* отступы оверлея, итоговая строка, «Продолжить» */
  --sm-vh: 100vh;
  --sm-card: clamp(
    64px,
    calc((var(--sm-vh) - var(--sm-chrome) - var(--sm-rows) * var(--sm-labels)) / var(--sm-rows) / 1.5),
    132px
  );
  gap: var(--sm-gap);
  grid-template-columns: repeat(var(--sm-cols), minmax(0, 1fr));
  max-width: calc(var(--sm-cols) * var(--sm-card) + (var(--sm-cols) - 1) * var(--sm-gap));
}

/* dvh учитывает свёрнутую адресную строку на телефоне; vh выше остаётся
   запасным вариантом для старых браузеров. */
@supports (height: 100dvh) {
  .sm-grid--many {
    --sm-vh: 100dvh;
  }
}

/* Компактные подписи: на мелкой карточке остаются имя, звёзды и короткая
   плашка, стихия с ролью уходят в подсказку при наведении (title). */
.sm-grid--many .sm-card__name {
  font-size: 12px;
  margin-top: 4px;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sm-grid--many .sm-card__stars {
  font-size: 11px;
  letter-spacing: 1px;
}

.sm-grid--many .sm-card__meta {
  display: none;
}

.sm-grid--many .sm-plaque {
  font-size: 10px;
  padding: 2px 7px;
  margin-top: 2px;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sm-plaque__short {
  display: none;
}

@media (max-width: 899px) {
  .sm-grid--many {
    --sm-cols: 4;
    --sm-rows: 3;
  }
}

@media (max-width: 599px) {
  .sm-grid--many {
    --sm-cols: 3;
    --sm-rows: 4;
  }
}

.sm-card {
  display: grid;
  gap: 4px;
  justify-items: center;
  opacity: 0;
  transform: scale(0.72);
}

.sm-card.is-revealed {
  animation: sm-card-in 0.8s cubic-bezier(0.18, 0.9, 0.28, 1.08) forwards;
}

@keyframes sm-card-in {
  0% { opacity: 0; transform: scale(0.72) rotate(-4deg); }
  55% { opacity: 1; transform: scale(1.06) rotate(2deg); }
  78% { transform: scale(0.98) rotate(-1deg); }
  100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

/* Иллюстрация — главное, эффект — обрамление. Поверх арта не лежит
   ничего: свечение ушло во внешнюю тень, inset-подсветку убрал, рамка
   тонкая по краю. */
.sm-card__frame {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 3;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--rarity, var(--glow));
  box-shadow: 0 0 34px -8px var(--rarity, var(--glow));
}

.sm-card__img,
.sm-card__placeholder {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.sm-card__placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Cinzel", serif;
  font-size: 40px;
  font-weight: 700;
  color: var(--cg-text);
  background: linear-gradient(160deg, color-mix(in srgb, var(--glow) 60%, #17151a), #14121a 85%);
}

.sm-card__name {
  font-family: "Cinzel", serif;
  font-size: 15px;
  font-weight: 600;
  color: var(--cg-gold-bright);
  margin-top: 6px;
  text-align: center;
}

.sm-card__stars {
  color: var(--rarity);
  font-size: 13px;
  letter-spacing: 2px;
  text-shadow: 0 0 9px color-mix(in srgb, var(--rarity) 65%, transparent);
}

.sm-card__meta {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  color: var(--cg-text-dim);
  text-align: center;
}

.sm-plaque {
  margin-top: 4px;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  padding: 4px 11px;
  border-radius: 999px;
  border: 1px solid var(--cg-border);
  color: var(--cg-text-dim);
  text-align: center;
}

.sm-plaque--new {
  color: #7fd99a;
  border-color: #3f8f5a;
  box-shadow: 0 0 14px -6px #3f8f5a;
}

.sm-plaque--soul {
  color: var(--cg-gold-bright);
  border-color: var(--cg-gold-dim);
}

.sm-summary {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  font-size: 17px;
  color: var(--cg-text);
  text-align: center;
  padding-top: 6px;
}

/* Искры для 4★ и 5★ — вокруг карточки, но не поверх иллюстрации:
   слой лежит ниже рамки (z-index: -1) и виден только за её пределами.
   Прежняя версия рисовала точки прямо по арту. */
.sm-overlay.is-sparkling .sm-card {
  position: relative;
  isolation: isolate;
}

.sm-overlay.is-sparkling .sm-card::before {
  content: "";
  position: absolute;
  inset: -14% -18%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(circle at 8% 22%, var(--rarity, var(--tier)) 0 1.5px, transparent 2px),
    radial-gradient(circle at 94% 34%, var(--rarity, var(--tier)) 0 1.2px, transparent 2px),
    radial-gradient(circle at 4% 74%, var(--rarity, var(--tier)) 0 1.6px, transparent 2px),
    radial-gradient(circle at 97% 68%, var(--rarity, var(--tier)) 0 1.1px, transparent 2px),
    radial-gradient(circle at 50% 104%, var(--rarity, var(--tier)) 0 1.4px, transparent 2px);
  animation: sm-sparks 2.6s linear infinite;
}

@keyframes sm-sparks {
  from { transform: translateY(-8%); opacity: 0.9; }
  to { transform: translateY(12%); opacity: 0; }
}

/* Единственное, что допустимо поверх арта: едва заметный стеклянный
   блик, скользящий при наведении. */
.sm-card__frame::after,
.cg-card__face::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(104deg, transparent 38%, rgba(255, 255, 255, 0.14) 48%, transparent 58%);
  transform: translateX(-40%);
  transition: opacity 0.25s ease, transform 0.5s ease;
}

.sm-card:hover .sm-card__frame::after,
.cg-card:hover .cg-card__face::after {
  opacity: 1;
  transform: translateX(40%);
}

@media (prefers-reduced-motion: reduce) {
  .sm-overlay,
  .sm-flash,
  .sm-flash.is-firing,
  .sm-flash.is-rays::after,
  .sm-overlay.is-shockwave::after,
  .sm-overlay.is-closing .sm-vignette::after,
  .sm-card__frame::after,
  .cg-card__face::after,
  .sm-card.is-revealed {
    transition: none;
    animation: none;
  }

  .sm-card {
    opacity: 1;
    transform: none;
  }

  .sm-overlay.is-sparkling .sm-card__frame::after {
    animation: none;
    opacity: 0.5;
  }
}

/* Узкий экран (~380px): Mini App открывают в основном с телефона. */
@media (max-width: 480px) {
  .sm-crystals__value {
    font-size: 34px;
  }

  .sm-pull-row {
    grid-template-columns: 1fr;
    gap: 10px;
  }

  .sm-pull {
    padding: 13px 12px;
  }

  .sm-overlay {
    padding: 14px 10px;
    justify-content: flex-start;
    padding-top: 62px;
  }

  .sm-grid:not(.sm-grid--many) {
    grid-template-columns: minmax(0, 178px);
  }

  /* Телефон: три в ряд. Подгонка размера под высоту окна здесь снята
     (max-width: none) — пять или четыре строки в экран всё равно не
     помещаются, а расчёт по высоте ужимал карточки до 64 px и оставлял
     широкие пустые поля по бокам. Ширина теперь делится между колонками
     целиком: на 380 px карточка выходит около 113 px.

     Почему три, а не две: десять карточек в два столбца — это пять строк
     против четырёх, то есть заметно больше прокрутки, а жалоба была
     именно на неё. Если 113 px окажется мелко — поменяйте --sm-cols на 2
     в этом блоке, остальное подстроится само. */
  .sm-grid--many {
    --sm-cols: 3;
    --sm-gap: 8px;
    max-width: none;
  }

  /* Совсем узкие экраны (320–339 px): три колонки дали бы меньше 95 px,
     здесь читаемость важнее длины прокрутки. */
  @media (max-width: 339px) {
    .sm-grid--many {
      --sm-cols: 2;
    }
  }

  .sm-grid--many .sm-card__name {
    font-size: 11px;
    margin-top: 3px;
  }

  .sm-grid--many .sm-card__stars {
    font-size: 10px;
    letter-spacing: 0.5px;
  }

  .sm-grid--many .sm-plaque {
    font-size: 9px;
    padding: 2px 6px;
    border-radius: 6px;
  }

  .sm-grid--many .sm-plaque__full {
    display: none;
  }

  .sm-grid--many .sm-plaque__short {
    display: inline;
  }

  .sm-card__name { font-size: 12px; }
  .sm-card__meta { font-size: 10px; }
  .sm-plaque { font-size: 10px; padding: 3px 8px; }
  .sm-summary { font-size: 15px; }
}

/* ==========================================================================
   Боевой экран (.cb-*)
   Противники сверху, отряд снизу, между ними — сцена для эффектов.
   Старые .combat-* из styles.css удалены: ими пользовался только этот
   экран.
   ========================================================================== */

/* Обёртка: экран и слой эффектов рядом. render() пересобирает только
   содержимое .combat-screen, поэтому живущее на слое переживает любую
   пересборку. Слой лежит поверх экрана и ничего не ловит: клики уходят
   сквозь него плиткам и кнопкам. */
.cb-wrap {
  position: relative;
}

.cb-fx-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
  z-index: 6;
}

.cb-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--cg-border-soft);
}

/* «Отступить» переехало сюда из нижней панели: низ экрана отдан
   действиям целиком, чтобы ход делался одним касанием под большим
   пальцем. */
.cb-retreat {
  margin-left: auto;
  padding: 5px 12px;
  border-radius: 999px;
  border: 1px solid var(--cg-border);
  background: none;
  color: var(--cg-text-dim);
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 12px;
  cursor: pointer;
}

.cb-retreat:hover:not(:disabled) {
  color: #d17a7a;
  border-color: var(--combat-red-dim);
}

.cb-topbar__floor {
  font-family: "Cinzel", serif;
  font-size: 18px;
  font-weight: 700;
  color: var(--cg-gold-bright);
  letter-spacing: 0.04em;
}

.cb-topbar__wave {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 13px;
  color: var(--cg-text-dim);
}

/* Раунд стоит рядом с волной и набран так же: это две разные меры одного
   боя, и выделять одну из них незачем. Отделён точкой, чтобы на узком
   экране «Волна 1 из 2» и «Раунд 3» не слиплись в одну строку. */
.cb-topbar__round {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 13px;
  color: var(--cg-text-dim);
}

.cb-topbar__round::before {
  content: "·";
  margin-right: 6px;
  color: var(--cg-border);
}

.cb-side {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  justify-content: center;
  padding: 16px 0;
}

.cb-side--enemy {
  align-items: flex-start;
}

.cb-side--player {
  align-items: flex-end;
}

/* Сцена между рядами. Всплывающие числа к ней не привязаны — они
   вылетают из самих портретов, — поэтому в бою она сжата почти в ноль и
   разворачивается только под итог. Прежние 54px постоянного воздуха на
   телефоне стоили дороже, чем давали. */
.cb-stage {
  position: relative;
  min-height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(ellipse at center, rgba(201, 163, 92, 0.09) 0%, transparent 70%);
  border-top: 1px solid var(--cg-border-soft);
  border-bottom: 1px solid var(--cg-border-soft);
}

.cb-stage.is-open {
  min-height: 54px;
}

/* ---- Очередь ходов ------------------------------------------------------ */
/* Полоса между сторонами была пустой в бою и занята баннером после него.
   Очередь встаёт именно сюда: высоты у панели действий не отнимает,
   а стоит между теми, кто в очереди и стоит.

   Ряд не переносится и не растёт: три значка и одно имя влезают в 375
   точек с запасом. Имя при нехватке места обрезается многоточием —
   обрезанное с многоточием читается как «не поместилось», а обрезанное
   без него читается поломкой (урок «Физичесі»). */
.cb-queue {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 5px 8px;
  min-width: 0;
}

.cb-queue__label {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cg-text-dim);
  flex: 0 0 auto;
}

/* Сжимается, а не растягивается: три значка обязаны стоять подряд,
   иначе строка читается не как очередь, а как два разных сообщения
   по краям экрана. Уступает место именно имя — оно единственное,
   что переживает обрезку с многоточием. */
.cb-queue__next {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex: 0 1 auto;
}

.cb-queue__name {
  font-family: "Cinzel", serif;
  font-size: 12px;
  font-weight: 600;
  color: var(--cg-gold-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

.cb-queue__face {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  overflow: hidden;
  background:
    radial-gradient(circle at 50% 40%, color-mix(in srgb, var(--glow) 40%, #17151a), #121016 75%);
  border: 1px solid var(--cg-border-soft);
}

/* Свой и чужой различаются кромкой, а не только буквой: у противников
   чибиков нет у тринадцати из пятнадцати, и очередь из букв «Г»
   без этого не отвечала бы даже на вопрос «чей ход». */
.cb-queue__face[data-side="player"] {
  border-color: color-mix(in srgb, var(--cg-gold) 55%, transparent);
}

.cb-queue__face[data-side="enemy"] {
  border-color: color-mix(in srgb, #a8371a 55%, transparent);
}

.cb-queue__face--next {
  width: 26px;
  height: 26px;
}

/* Дальние двое мельче намеренно: предсказание на них сбывается
   в 74–92% случаев против 93–97% у первого, и размер об этом говорит
   раньше, чем игрок успеет на них понадеяться. */
.cb-queue__face--later {
  width: 20px;
  height: 20px;
  opacity: 0.72;
}

.cb-queue__img {
  width: 150%;
  height: 150%;
  object-fit: contain;
  object-position: center 35%;
}

.cb-queue__letter {
  font-family: "Cinzel", serif;
  font-weight: 700;
  font-size: 12px;
  color: var(--cg-text-dim);
}

.cb-queue__face--later .cb-queue__letter {
  font-size: 10px;
}

/* ---- Плитка бойца ------------------------------------------------------- */
/* Коробки вокруг бойца нет: фигура стоит прямо на сцене. Рамка и фон
   переехали на .cb-unit__portrait — то есть достаются только заглушке,
   иначе буква висела бы в пустоте. Ширину плитки делят между собой:
   трое врагов получают больше места, четверо героев — меньше, и никто
   не уезжает на второй ряд. */
.cb-unit {
  position: relative;
  flex: 1 1 0;
  min-width: 0;
  max-width: 120px;
  border-radius: 12px;
  padding: 4px 2px 8px;
  text-align: center;
  transition: opacity 0.3s, filter 0.3s, transform 0.2s;
}

.cb-unit__portrait {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: 8px;
  overflow: hidden;
  background: #131116;
  border: 1px solid var(--cg-border);
}

/* Значок стихии бойца. Тот же кружок, что на карточке героя и на
   карточке этажа, — класс cg-card__element-tag переиспользуется целиком,
   как и там; здесь только переезд в левый угол кадра. Угол именно левый:
   правый занят кругом ультимейта, и два кружка в одном углу спорили бы.

   Цвет приходит своим --glow, поставленным по стихии бойца, а не тем,
   что стоит на плитке: плитка противника светится нейтрально-серым,
   и на ней та же стихия выглядела бы иначе, чем у героя.

   Тап уходит сквозь значок на плитку — ею выбирают цель, и значок
   не имеет права её заслонять. Стихия словом живёт в подсказке плитки. */
.cb-unit__element {
  top: 3px;
  left: 3px;
  right: auto;
  width: 22px;
  height: 22px;
  padding: 4px;
  z-index: 3;
  pointer-events: none;
}

/* ---- Чибик ---------------------------------------------------------------
   Спрайт с прозрачностью стоит на поле, а не лежит в обрезанном окошке:
   у плитки с чибиком нет ни рамки кадра, ни фона. Файл подхватывается по
   имени героя (assets/hero_chibi/<hero_id>.png), поэтому добавление
   следующего героя — это положить файл, а не править код.
   -------------------------------------------------------------------------- */

.cb-unit__stage {
  position: relative;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  --ring: transparent;
}

/* Подсветка — светящийся овал под ногами вместо прямоугольной рамки.
   Лежит под фигурой, поэтому читается как свет на полу, а не как обводка. */
.cb-unit__stage::before {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 3%;
  width: 88%;
  height: 20%;
  transform: translateX(-50%);
  border-radius: 50%;
  background: radial-gradient(ellipse at center, var(--ring) 0%, transparent 72%);
  opacity: 0;
  transition: opacity 0.25s ease;
  pointer-events: none;
}

.cb-unit--chibi.is-active .cb-unit__stage {
  --ring: var(--cg-gold);
}

/* Цвет подсветки — по стороне. Красный остаётся цветом врага; союзнику —
   тёплая зелёная пара --pick-ally (объявлена рядом с --combat-red
   в styles.css). Иначе лечение выглядело бы как удар. */
.cb-unit--chibi.is-pickable[data-side="enemy"] .cb-unit__stage {
  --ring: var(--combat-red);
}

.cb-unit--chibi.is-pickable[data-side="player"] .cb-unit__stage {
  --ring: var(--pick-ally);
}

.cb-unit--chibi.is-active .cb-unit__stage::before,
.cb-unit--chibi.is-pickable .cb-unit__stage::before {
  opacity: 0.95;
}

.cb-unit__chibi {
  position: relative;
  z-index: 1; /* поверх овала подсветки */
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* Ноги к нижнему краю: так фигуры разного роста встают на одну линию. */
  object-position: bottom center;
  display: block;
  transform-origin: 50% 100%;
  filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
  transition: filter 0.3s ease;
}

/* Свечение по силуэту, а не по прямоугольнику: drop-shadow идёт по
   контуру прозрачного PNG. */
.cb-unit--chibi.is-active .cb-unit__chibi {
  filter: drop-shadow(0 0 7px var(--cg-gold)) drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
}

.cb-unit--chibi.is-pickable[data-side="enemy"] .cb-unit__chibi {
  filter: drop-shadow(0 0 7px var(--combat-red)) drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
}

.cb-unit--chibi.is-pickable[data-side="player"] .cb-unit__chibi {
  filter: drop-shadow(0 0 7px var(--pick-ally)) drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
}

/* Покой — еле заметное покачивание. Разная задержка по стихии развела бы
   бойцов по фазе; пока хватает того, что анимация стартует с загрузкой. */
.cb-unit--chibi .cb-unit__chibi {
  animation: cb-chibi-idle 3.2s ease-in-out infinite;
}

@keyframes cb-chibi-idle {
  0%, 100% { transform: translateY(0) rotate(0deg) scaleY(1); }
  50% { transform: translateY(-2px) rotate(0.7deg) scaleY(1.012); }
}

/* Атака — рывок вперёд и возврат. Направление берётся из data-side:
   отряд бьёт вверх, к врагам, враги — вниз. */
.cb-unit--chibi[data-side="player"].is-acting .cb-unit__chibi {
  animation: cb-chibi-lunge-up var(--cb-lunge-ms, 500ms) cubic-bezier(0.2, 0.9, 0.3, 1);
}

.cb-unit--chibi[data-side="enemy"].is-acting .cb-unit__chibi {
  animation: cb-chibi-lunge-down var(--cb-lunge-ms, 500ms) cubic-bezier(0.2, 0.9, 0.3, 1);
}

@keyframes cb-chibi-lunge-up {
  0% { transform: translateY(0) scale(1); }
  28% { transform: translateY(-14px) scale(1.06); }
  55% { transform: translateY(-4px) scale(1.02); }
  100% { transform: translateY(0) scale(1); }
}

@keyframes cb-chibi-lunge-down {
  0% { transform: translateY(0) scale(1); }
  28% { transform: translateY(14px) scale(1.06); }
  55% { transform: translateY(4px) scale(1.02); }
  100% { transform: translateY(0) scale(1); }
}

/* Получен урон — вздрагивание со вспышкой. Вспышка сделана свечением, а
   не осветлением: brightness выбелил бы весь спрайт и потерял силуэт. */
.cb-unit--chibi.is-hit .cb-unit__chibi {
  /* Длительность совпадает со снятием класса в JS — оба числа из
     реестра TIMINGS (hit). */
  animation: cb-chibi-hurt var(--cb-hit-ms, 420ms) ease-out;
}

@keyframes cb-chibi-hurt {
  0% {
    transform: translateX(0);
    filter: drop-shadow(0 0 0 #fff) drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
  }
  15% {
    transform: translateX(-5px) rotate(-2deg);
    filter: drop-shadow(0 0 10px #fff) drop-shadow(0 0 18px var(--combat-red)) brightness(1.5);
  }
  40% { transform: translateX(4px) rotate(1.5deg); }
  65% { transform: translateX(-2px) rotate(-0.8deg); }
  100% {
    transform: translateX(0);
    filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
  }
}

/* Три силы удара — три встряски по нарастающей (25 августа 2026):
   обычная ±5px выше, усиленная ±8px, крит ±12px и трясёт ещё и саму
   плитку. Длительность у всех одна — из реестра TIMINGS (hit): сила
   читается амплитудой и вспышкой, а не растянутым временем. */
.cb-unit--chibi.is-hit--power .cb-unit__chibi {
  animation: cb-chibi-hurt-power var(--cb-hit-ms, 420ms) ease-out;
}

@keyframes cb-chibi-hurt-power {
  0% { transform: translateX(0); }
  15% {
    transform: translateX(-8px) rotate(-3deg);
    filter: drop-shadow(0 0 12px #fff) drop-shadow(0 0 22px var(--combat-red)) brightness(1.6);
  }
  40% { transform: translateX(7px) rotate(2.5deg); }
  65% { transform: translateX(-3px) rotate(-1.2deg); }
  100% { transform: translateX(0); }
}

.cb-unit--chibi.is-hit--crit .cb-unit__chibi {
  animation: cb-chibi-hurt-crit var(--cb-hit-ms, 420ms) ease-out;
}

@keyframes cb-chibi-hurt-crit {
  0% { transform: translateX(0); }
  12% {
    transform: translateX(-12px) rotate(-5deg);
    filter: drop-shadow(0 0 14px #fff) drop-shadow(0 0 26px var(--cb-flash-color, var(--combat-red))) brightness(1.8);
  }
  34% { transform: translateX(10px) rotate(4deg); }
  56% { transform: translateX(-6px) rotate(-2deg); }
  78% { transform: translateX(3px) rotate(1deg); }
  100% { transform: translateX(0); }
}

/* Крит трясёт плитку целиком — резко и мелко, фигура при этом ходит
   своей встряской. Двигается только transform. */
.cb-unit.is-hit--crit {
  animation: cb-tile-shake var(--cb-hit-ms, 420ms) linear;
}

@keyframes cb-tile-shake {
  0%, 100% { transform: translateX(0); }
  15% { transform: translateX(-5px); }
  35% { transform: translateX(4px); }
  55% { transform: translateX(-3px); }
  75% { transform: translateX(2px); }
}

/* Вспышка сильного удара: свечение по краю зоны фигуры, середина
   прозрачна — иллюстрацию не заливает (канон раздела 3). Анимируется
   только прозрачность; сами тени нарисованы статично. */
.cb-flash {
  position: absolute;
  inset: -3px;
  border-radius: 12px;
  pointer-events: none;
  opacity: 0;
  animation: cb-flash-fade var(--cb-hit-ms, 420ms) ease-out forwards;
}

.cb-flash--power {
  border: 1px solid rgba(255, 236, 210, 0.9);
  box-shadow: 0 0 20px 2px rgba(255, 236, 210, 0.8),
    inset 0 0 12px rgba(255, 236, 210, 0.35);
}

.cb-flash--crit {
  border: 2px solid var(--cb-flash-color, var(--combat-red));
  box-shadow: 0 0 30px 6px var(--cb-flash-color, var(--combat-red)),
    inset 0 0 16px var(--cb-flash-color, var(--combat-red));
}

@keyframes cb-flash-fade {
  0% { opacity: 0; }
  12% { opacity: 1; }
  100% { opacity: 0; }
}

/* ---------------------------------------------------------------------
   Момент ультимейта (25 августа 2026, последний пункт каталога)
   ---------------------------------------------------------------------
   Две вещи, и только две: приглушённый фон и свечение вокруг бьющего.
   Виньетки нет намеренно — три эффекта разом превратили бы ультимейт
   в маленький призыв (решение автора).

   Обе живут на слое эффектов: момент длиннее шага обмена, и внутри
   экрана его снесла бы первая пересборка. backdrop-filter не берём —
   запрет iOS; движение только transform и opacity, тени статичны.
   -------------------------------------------------------------------- */

.cb-ult-dim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: rgba(6, 5, 8, 0.66);
  opacity: 0;
  animation: cb-ult-dim var(--cb-ult-ms, 1000ms) ease-in-out forwards;
}

@keyframes cb-ult-dim {
  0% { opacity: 0; }
  18% { opacity: 1; }
  62% { opacity: 1; }
  100% { opacity: 0; }
}

/* Свечение — контур вокруг фигуры: середина прозрачна, светятся края
   (канон раздела 3 — вокруг спрайта, не поверх). Цвет приходит из JS
   одним правилом с вспышкой крита. */
.cb-ult-glow {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  border: 1px solid var(--cb-ult-color, var(--combat-red));
  box-shadow: 0 0 38px 12px var(--cb-ult-color, var(--combat-red)),
    inset 0 0 24px var(--cb-ult-color, var(--combat-red));
  animation: cb-ult-glow var(--cb-ult-ms, 1000ms) ease-out forwards;
}

@keyframes cb-ult-glow {
  0% { opacity: 0; transform: scale(0.84); }
  22% { opacity: 1; transform: scale(1.07); }
  62% { opacity: 0.92; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.14); }
}

/* Свечение без движения: для prefers-reduced-motion. Момент обязан
   читаться и там — снимается ход, а не содержание (канон раздела 43). */
@keyframes cb-ult-glow-still {
  0% { opacity: 0; }
  20% { opacity: 1; }
  70% { opacity: 1; }
  100% { opacity: 0; }
}

/* Бьющий поднимается над слоем эффектов — затемнение его не касается.
   .combat-screen своего контекста наложения не создаёт, поэтому плитка
   с z-index 7 встаёт выше слоя (z-index 6) внутри .cb-wrap. */
.cb-unit.is-ultimate-actor {
  z-index: 7;
}

/* Поражение — наклон и затемнение. Анимация покоя здесь обязана
   выключиться, иначе павший продолжит покачиваться. */
.cb-unit--chibi.is-dead .cb-unit__chibi {
  animation: none;
  transform: rotate(-14deg) translateY(6px);
  filter: grayscale(1) brightness(0.4) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6));
  transition: transform 0.5s ease, filter 0.5s ease;
}

/* Смерть в момент гибели: замирание и оседание (25 августа 2026).
   Задержка анимации — та самая пауза перед оседанием; fill-режим both
   держит бойца замершим на время паузы (кадр 0%) и в финальной позе
   после (кадр 100%). Финальная поза совпадает с is-dead точка в точку —
   подмену класса в JS не видно. Уже мёртвый в пересобранном экране
   получает is-dead сразу и не оседает второй раз. */
.cb-unit--chibi.is-dying .cb-unit__chibi {
  animation: cb-death-settle var(--cb-death-settle-ms, 700ms) ease-in
    var(--cb-death-pause-ms, 250ms) both;
}

@keyframes cb-death-settle {
  0% {
    transform: translateX(0) rotate(0);
    filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.55));
  }
  60% {
    transform: rotate(-11deg) translateY(4px);
    filter: grayscale(0.7) brightness(0.6) drop-shadow(0 3px 5px rgba(0, 0, 0, 0.6));
  }
  100% {
    transform: rotate(-14deg) translateY(6px);
    filter: grayscale(1) brightness(0.4) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6));
  }
}

/* Указатель цели. Одной красной рамки на мелком портрете мало —
   треугольник над плиткой виден боковым зрением, не всматриваясь. */

@keyframes cb-marker {
  0%, 100% { transform: translate(-50%, 0); }
  50% { transform: translate(-50%, 3px); }
}

.cb-unit__img,
.cb-unit__placeholder {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* У противников нет иллюстраций (tower_config.py), поэтому заглушка
   должна выглядеть намеренной, а не «картинка не загрузилась». */
.cb-unit__placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Cinzel", serif;
  font-size: 34px;
  font-weight: 700;
  color: var(--cg-text-dim);
  background:
    radial-gradient(circle at 50% 38%, color-mix(in srgb, var(--glow) 35%, #17151a), #121016 72%);
}

.cb-unit__name {
  font-family: "Cinzel", serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--cg-gold-bright);
  margin-top: 7px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cb-unit__level {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 10.5px;
  color: var(--cg-text-dim);
  margin-bottom: 6px;
}

/* Полоса HP лежит поверх нижнего края портрета, а не отдельной строкой
   под ним: так каждый ряд бойцов ниже примерно на 20px.
   У чибика портрета нет, и там полоса встаёт в поток — см. --under. */
.cb-unit__hp {
  position: absolute;
  left: 4px;
  right: 4px;
  bottom: 4px;
  height: 6px;
  border-radius: 999px;
  background: rgba(6, 5, 8, 0.85);
  border: 1px solid rgba(0, 0, 0, 0.6);
  overflow: hidden;
  z-index: 2;
}

/* У плитки с чибиком абсолютная полоса цеплялась за низ всей плитки и
   ложилась на точки энергии со значками. Здесь она обычная строка под
   фигурой: ничего не перекрывает и никого не перекрывают. */
.cb-unit__hp--under {
  position: static;
  margin-top: 5px;
}

.cb-unit__hp-fill {
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, #3f8f5a, #58c97a);
  transition: width var(--cb-hp-ms, 350ms) ease, background var(--cb-hp-ms, 350ms) ease;
}

/* Боевые сигналы держатся на --combat-red — приглушённом кирпичном.
   Алый редкости (--rarity-5) заметно ярче и насыщеннее, чтобы низкое HP
   и подсветка цели не читались как «выпала пятёрка». */
.cb-unit__hp-fill.is-low {
  background: linear-gradient(90deg, var(--combat-red-dim), var(--combat-red));
}

.cb-unit__hp-text {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 10px;
  color: var(--cg-text-dim);
  margin-top: 3px;
}

.cb-unit__energy {
  display: flex;
  gap: 4px;
  justify-content: center;
  margin-top: 6px;
}

/* ---------------------------------------------------------------------
   Чибик на месте карточки
   --------------------------------------------------------------------- */

/* Чибик квадратный, а место под карточку вертикальное. Растягивать его
   нельзя, обрезать — тоже, поэтому он вписывается целиком и прижимается
   к низу: так фигура стоит, а не парит посередине кадра.
   Фон — тот же градиент по стихии, что был под буквой, иначе прозрачный
   спрайт повис бы в пустоте.
   Правило одно на все экраны, размеры и форма — за рамкой конкретного
   места. */
.hero-chibi {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  overflow: hidden;
  background: linear-gradient(
    160deg,
    color-mix(in srgb, var(--glow) 60%, #17151a),
    color-mix(in srgb, var(--glow) 20%, #17151a) 85%
  );
}

.hero-chibi__img {
  /* Отступы в процентах, а не в пикселях: одна и та же доля кадра на
     плитке коллекции, в ячейке отряда и на карточке призыва. */
  max-width: 82%;
  max-height: 88%;
  margin-bottom: 4%;
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.55));
}

/* Ячейка отряда заметно мельче остальных мест, и те же 82% там уже
   нечитаемы — фигуре отдаётся почти весь кадр. Правило вписывания при
   этом прежнее: пропорции сохраняются, обрезки нет. */
.sq-cell__chibi .hero-chibi__img {
  max-width: 94%;
  max-height: 94%;
  margin-bottom: 2%;
}

/* ---------------------------------------------------------------------
   Боевая мощь (CP)
   --------------------------------------------------------------------- */

.cg-cp {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-top: 6px;
}

.cg-cp__label {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--cg-text-dim);
}

.cg-cp__value {
  font-family: "Cinzel", serif;
  font-size: 24px;
  font-weight: 700;
  color: var(--cg-gold-bright);
}

/* Число в углу плитки: сравнивается взглядом, поэтому мелкое и не
   спорит с портретом. */
.cg-card__cp {
  position: absolute;
  right: 6px;
  bottom: 6px;
  z-index: 3;
  padding: 2px 6px;
  border-radius: 999px;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 10px;
  color: var(--cg-gold-bright);
  background: rgba(6, 5, 8, 0.72);
  border: 1px solid var(--cg-border);
}

/* Подпись линии в редакторе отряда. Бонусы и доля атак стоят под
   названием, чтобы выбор схемы был осознанным, а не наугад. */
.sq-line__bonus,
.sq-line__share {
  display: block;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 10.5px;
  line-height: 1.35;
  color: var(--cg-text-dim);
}

.sq-line__bonus {
  color: var(--cg-gold);
}

.cg-line-note {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11.5px;
  color: var(--cg-text-dim);
  margin-top: 8px;
}

/* Сумма CP над доской отряда: состав решается здесь, поэтому число
   стоит крупно и рядом с местом, где его меняют. */
.sq-total-cp {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  margin: 2px 0 10px;
}

.sq-total-cp__label {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  letter-spacing: 0.12em;
  color: var(--cg-text-dim);
}

.sq-total-cp__value {
  font-family: "Cinzel", serif;
  font-size: 26px;
  font-weight: 700;
  color: var(--cg-gold-bright);
}

.sq-cell__cp {
  display: block;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 10px;
  color: var(--cg-gold);
}

.sq-row__cp {
  margin-left: auto;
  font-family: "Cinzel", serif;
  font-size: 14px;
  font-weight: 700;
  color: var(--cg-gold-bright);
}

.cp-compare {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 10px;
  border-radius: 8px;
  background: rgba(10, 9, 12, 0.5);
  border: 1px solid var(--cg-border);
}

.cp-compare__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.cp-compare__value {
  font-family: "Cinzel", serif;
  font-size: 16px;
  font-weight: 700;
  color: var(--cg-gold-bright);
}

/* Только существующая палитра: золото акцентов, обычный светлый текст
   и алый пятизвёздной редкости. Новых цветов не заводим.

   Зон стало четыре 6 сентября 2026, и четвёртому вердикту достался
   приглушённый золотой: он читается как предупреждение, оставаясь
   в той же палитре. Имена классов — ключи зон с сервера. */
.cp--easy { color: var(--cg-gold-bright); }
.cp--capable { color: var(--cg-text); }
.cp--edge { color: var(--cg-gold-dim); }
.cp--beyond { color: var(--rarity-5); }

/* ---------------------------------------------------------------------
   Экипировка
   --------------------------------------------------------------------- */

/* Прибавка от экипировки — золотом, цветом акцентов интерфейса.
   Основное значение остаётся обычным, иначе на глаз не отличить, где
   собственная сила героя, а где чужая. */
.cg-bonus {
  color: var(--cg-gold-bright);
  font-weight: 600;
}

.eq-cells {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
  gap: 8px;
  margin-top: 8px;
}

.eq-cells__loading,
.eq-cells__note {
  grid-column: 1 / -1;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 12px;
  color: var(--cg-text-dim);
}

/* Плитка предмета. Рамка и свечение несут редкость, поэтому иконка одна
   на тип оружия или направление брони — восемь вместо шестнадцати. */
.eq-item {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  width: 100%;
  padding: 9px 10px;
  text-align: left;
  border-radius: 8px;
  cursor: pointer;
  color: inherit;
  background: rgba(14, 12, 16, 0.72);
  border: 1px solid var(--cg-border);
  transition: border-color 0.2s, box-shadow 0.2s, transform 0.12s;
}

.eq-item:hover {
  transform: translateY(-1px);
}

.eq-item--r3 {
  border-color: var(--rarity-3);
  box-shadow: 0 0 14px -6px var(--rarity-3);
}

.eq-item--r4 {
  border-color: var(--rarity-4);
  box-shadow: 0 0 14px -6px var(--rarity-4);
}

.eq-item--r5 {
  border-color: var(--rarity-5);
  box-shadow: 0 0 16px -5px var(--rarity-5);
}

.eq-item--empty {
  border-style: dashed;
  opacity: 0.75;
}

/* Заглушка вместо арта: иконки рисуются отдельно, и до их появления
   плитка обязана оставаться читаемой, а не пустой. */
.eq-item__icon {
  flex: 0 0 auto;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  font-size: 16px;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.45);
  border: 1px solid var(--cg-border);
}

/* Настоящая иконка вместо символа: фон убран, поэтому подложка ей не
   нужна — рамка редкости у самой плитки. */
.eq-item__icon--art {
  object-fit: contain;
  background: none;
  border-color: transparent;
}

.eq-item__body {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.eq-item__name {
  font-family: "Cinzel", serif;
  font-size: 12.5px;
  color: var(--cg-gold-bright);
}

.eq-item__meta,
.eq-item__note,
.eq-item__sleeping,
.eq-item__bonus {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  color: var(--cg-text-dim);
}

.eq-item__bonus {
  color: var(--cg-gold);
}

/* Спящее свойство именной вещи. Приглушённо-тревожный, а не красный:
   это не поломка и не отказ, это «работает не здесь». Красная строка
   на карточке читалась бы ошибкой. */
.eq-item__sleeping {
  color: var(--cg-text-dim);
  font-style: italic;
  opacity: 0.85;
}

.eq-picker__list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 60vh;
  overflow-y: auto;
}

.eq-picker__lines {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 8px 0 12px;
}

.eq-picker__actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

/* Метки эффектов. Цвет холодный и приглушённый: это не боевой сигнал
   вроде низкого HP, а состояние бойца, и спорить за внимание с полосой
   здоровья ему не нужно. */
/* Полоса значков: высота постоянная и занята всегда, даже когда
   эффектов нет. Ряд бойцов от этого стоит ровно — прежде плитка
   с эффектом была выше соседней, и при выравнивании по низу верх
   уезжал. Строка одна: перенос на вторую вернул бы ту же беду. */
.cb-unit__effects {
  display: flex;
  flex-wrap: nowrap;
  gap: 3px;
  justify-content: center;
  align-items: center;
  height: 18px;
  margin-top: 5px;
  /* Больше двух значков на узкой плитке не помещается — остальные
     доезжают прокруткой, а не растягивают полосу. */
  overflow-x: auto;
  scrollbar-width: none;
}

.cb-unit__effects::-webkit-scrollbar {
  display: none;
}

/* У героя полоса стоит над плиткой: снизу и так тесно — имя, HP,
   энергия, — а сверху пусто. */
.cb-unit__effects--above {
  margin-top: 0;
  margin-bottom: 4px;
}

.cb-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  border: 1px solid var(--cg-border);
  background: #0d0c0e;
  transition: background 0.25s, box-shadow 0.25s, border-color 0.25s;
}

.cb-dot.is-on {
  background: linear-gradient(180deg, var(--cg-gold-bright), var(--cg-gold));
  border-color: var(--cg-gold-dim);
  box-shadow: 0 0 7px -1px var(--cg-gold);
}

/* Чей сейчас ход. Прямоугольная подсветка осталась только у заглушки —
   у чибика её заменяет овал под ногами и свечение по силуэту. */
.cb-unit:not(.cb-unit--chibi).is-active .cb-unit__portrait {
  border-color: var(--cg-gold);
  box-shadow: 0 0 0 1px var(--cg-gold), 0 0 26px -6px var(--cg-gold);
}

/* Короткий импульс в момент, когда этот боец бьёт. У чибика вместо этого
   рывок самой фигуры (cb-chibi-lunge-*), поэтому плитку не трогаем —
   иначе вернулся бы прямоугольник, который мы только что убрали. */
.cb-unit:not(.cb-unit--chibi).is-acting .cb-unit__portrait {
  box-shadow: 0 0 0 1px var(--cg-gold-bright), 0 0 30px -4px var(--cg-gold);
}

.cb-unit:not(.cb-unit--chibi).is-acting {
  transform: translateY(-4px);
}

.cb-unit.is-dead {
  opacity: 0.42;
  filter: grayscale(1);
}

/* Цель, закрытая провокацией: бить её сервер не даст, и плитка обязана
   сказать это до нажатия. Гашение слабее, чем у выбывшего, — она жива
   и через ход снова станет доступной; курсор «нельзя» договаривает. */
.cb-unit.is-barred {
  opacity: 0.55;
  filter: grayscale(0.7);
  cursor: not-allowed;
}

/* Плитка гаснет вместе с оседанием фигуры — одна задержка, одна
   длительность, тот же финал, что у is-dead. Работает и на плитке
   с портретом-заглушкой, у которой оседать нечему. */
.cb-unit.is-dying {
  animation: cb-death-tile var(--cb-death-settle-ms, 700ms) ease-in
    var(--cb-death-pause-ms, 250ms) both;
}

@keyframes cb-death-tile {
  0% { opacity: 1; filter: none; }
  100% { opacity: 0.42; filter: grayscale(1); }
}

.cb-unit.is-selectable {
  cursor: pointer;
}

.cb-unit.is-selectable:hover {
  transform: translateY(-2px);
}

/* Текущая цель. Подсвечена всегда, а не только после выбора действия:
   ход уходит по ней одним касанием кнопки внизу. */
.cb-unit:not(.cb-unit--chibi).is-pickable[data-side="enemy"] .cb-unit__portrait {
  border-color: var(--combat-red);
  box-shadow: 0 0 0 1px var(--combat-red), 0 0 24px -6px var(--combat-red);
}

.cb-unit:not(.cb-unit--chibi).is-pickable[data-side="player"] .cb-unit__portrait {
  border-color: var(--pick-ally);
  box-shadow: 0 0 0 1px var(--pick-ally), 0 0 24px -6px var(--pick-ally);
}

/* У чибика вздрагивание и вспышка живут на самой фигуре
   (cb-chibi-hurt) — прямоугольная вспышка нужна только заглушке. */
.cb-unit:not(.cb-unit--chibi).is-hit .cb-unit__portrait {
  animation: cb-hit 0.42s ease;
}

@keyframes cb-hit {
  0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
  18% { box-shadow: 0 0 0 3px rgba(255, 236, 210, 0.9), 0 0 34px -2px var(--combat-red); }
  100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}

/* ---- Всплывающие числа -------------------------------------------------- */
.cb-unit__fx {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
}

.cb-pop {
  position: absolute;
  left: 50%;
  top: 42%;
  transform: translateX(-50%);
  display: flex;
  align-items: baseline;
  gap: 5px;
  font-family: "Cinzel", serif;
  font-size: 26px;
  font-weight: 700;
  color: #ffd9c8;
  text-shadow: 0 0 10px rgba(209, 86, 86, 0.9), 0 2px 4px rgba(0, 0, 0, 0.8);
  /* Длительность приходит из реестра TIMINGS в combat.js; запасное
     значение совпадает с ним на случай экрана без переменных. */
  animation: cb-pop-up var(--cb-pop-ms, 1050ms) ease-out forwards;
  white-space: nowrap;
}

.cb-pop--miss {
  font-size: 17px;
  color: var(--cg-text-dim);
  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.85);
}

/* Уклонение — не промах, и выглядеть должно иначе. Промах серый,
   потому что ничего не произошло; уклонение — заслуга цели, и цвет
   у него холодный светлый, тот же, каким на значках эффектов помечено
   положительное. Нового цвета не заводится. */
.cb-pop--dodge {
  color: #a9cbe0;
  text-shadow: 0 0 10px rgba(150, 195, 225, 0.5), 0 2px 4px rgba(0, 0, 0, 0.85);
}

/* Крит: пометка алым, число крупнее, взлёт резче и дольше.
   Алый — тот же --rarity-5, что и у пятизвёздочной редкости; новый
   цвет не заводится. */
.cb-pop--crit {
  font-size: 36px;
  animation: cb-pop-up-crit var(--cb-pop-crit-ms, 1400ms) cubic-bezier(0.16, 0.9, 0.28, 1) forwards;
  text-shadow: 0 0 14px color-mix(in srgb, var(--rarity-5) 80%, transparent),
    0 2px 5px rgba(0, 0, 0, 0.85);
}

/* Лечение: зелёный уже есть в палитре — тот же --hp-high, каким полоса
   HP показывает здоровье. Нового цвета не заводится. */
.cb-pop--heal {
  color: var(--hp-high);
  text-shadow: 0 0 10px color-mix(in srgb, var(--hp-high) 60%, transparent),
    0 2px 4px rgba(0, 0, 0, 0.85);
}

/* Съеденное щитом — ниже и мельче числа урона, тем же голубовато-белым,
   каким щит помечен на значке эффекта (.cb-badge--shield). Нового цвета
   не заводится. */
.cb-pop--shield {
  top: 58%;
  font-size: 15px;
  color: #d6e7f2;
  text-shadow: 0 0 10px rgba(200, 228, 245, 0.5), 0 2px 4px rgba(0, 0, 0, 0.85);
}

/* Кровь Селин — сестра строки щита и стоит там же, только красная:
   щит объясняет неподвижную полосу, кровь — выросшее число. */
.cb-pop--blood {
  top: 58%;
  font-size: 15px;
  color: #f2c9c4;
  text-shadow: 0 0 10px rgba(226, 122, 108, 0.55), 0 2px 4px rgba(0, 0, 0, 0.85);
}

/* Сброс готовности — третья строка того же семейства, что щит и кровь,
   и стоит она выше числа урона: щит и кровь объясняют само число,
   а отложенный ход к нему отношения не имеет. Цвет — тот же холодный
   светлый, каким помечено уклонение: и там, и здесь речь о ходе,
   которого не случилось. Нового цвета не заводится. */
.cb-pop--readiness {
  top: 26%;
  font-size: 15px;
  color: #a9cbe0;
  text-shadow: 0 0 10px rgba(150, 195, 225, 0.5), 0 2px 4px rgba(0, 0, 0, 0.85);
}

/* Украденная энергия — рядом с отложенным ходом и тем же размером:
   обе строки про то, чего цель теперь не сделает. Цвет — золото шкалы
   энергии (.cb-dot.is-on), чтобы игрок связал строку с точками под
   портретом. Нового цвета не заводится. */
.cb-pop--energy {
  top: 36%;
  font-size: 15px;
  color: var(--cg-gold-bright);
  text-shadow: 0 0 10px color-mix(in srgb, var(--cg-gold) 60%, transparent),
    0 2px 4px rgba(0, 0, 0, 0.85);
}

.cb-pop__mark {
  font-size: 15px;
  letter-spacing: 0.08em;
  color: var(--rarity-5);
  text-shadow: 0 0 10px color-mix(in srgb, var(--rarity-5) 70%, transparent),
    0 1px 3px rgba(0, 0, 0, 0.9);
}

@keyframes cb-pop-up {
  0% { opacity: 0; transform: translate(-50%, 8px) scale(0.85); }
  18% { opacity: 1; transform: translate(-50%, 0) scale(1.1); }
  35% { transform: translate(-50%, -4px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -46px) scale(1); }
}

/* Тот же взлёт, но с большим замахом и заметной паузой на пике —
   крит должен успеть прочитаться. */
@keyframes cb-pop-up-crit {
  0% { opacity: 0; transform: translate(-50%, 10px) scale(0.6); }
  14% { opacity: 1; transform: translate(-50%, -2px) scale(1.32); }
  26% { transform: translate(-50%, -6px) scale(0.98); }
  38% { transform: translate(-50%, -8px) scale(1.06); }
  60% { opacity: 1; transform: translate(-50%, -18px) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -62px) scale(1); }
}

/* ---- Кнопки действий ---------------------------------------------------- */
/* Панель действий прибита к низу окна.
   sticky, а не fixed: липкий элемент остаётся в потоке, поэтому не надо
   вручную подбирать нижний отступ экрана под его высоту — и он не
   перекроет журнал, если контент всё же окажется длиннее экрана. */
.cb-actions {
  position: sticky;
  bottom: 0;
  z-index: 6;
  display: grid;
  gap: 10px;
  justify-items: center;
  padding: 12px 0 calc(10px + env(safe-area-inset-bottom, 0px));
  margin-top: 10px;
  background: linear-gradient(to top, var(--cg-bg) 62%, rgba(10, 10, 14, 0.85));
  border-top: 1px solid var(--cg-border-soft);
}

.cb-actions--empty {
  min-height: 40px;
}

.cb-hint {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-family: "Cormorant Garamond", serif;
  font-size: 16px;
  color: var(--cg-text-dim);
}

.cb-hint__actor {
  font-family: "Cinzel", serif;
  color: var(--cg-gold-bright);
}

.cb-hint__arrow {
  color: var(--cg-text-dim);
}

.cb-hint__target {
  font-family: "Cinzel", serif;
  color: #e08a8a;
}

.cb-action-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
  width: 100%;
}

.cb-action {
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: flex-start;
  text-align: left;
  padding: 13px 16px;
  border-radius: 10px;
  border: 1px solid var(--cg-gold-dim);
  background: linear-gradient(165deg, var(--cg-bg-card), #161417 78%);
  color: var(--cg-text);
  cursor: pointer;
  font-family: inherit;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}

.cb-action:hover:not(.is-disabled) {
  border-color: var(--cg-gold);
  box-shadow: 0 0 18px -6px var(--cg-gold);
  transform: translateY(-2px);
}

/* Взведённая кнопка: действие выбрано, экран ждёт цель. Язык тот же,
   что у наведения, но состояние обязано держаться само — на телефоне
   наведения нет. Класс is-armed ставит render (combat.js), снимается
   он выбором цели или отменой. */
.cb-action.is-armed,
.cb-act.is-armed {
  border-color: var(--cg-gold);
  box-shadow: 0 0 18px -6px var(--cg-gold), inset 0 0 12px -6px var(--cg-gold);
  background: linear-gradient(
    165deg,
    color-mix(in srgb, var(--cg-gold) 12%, var(--cg-bg-card)),
    #161417 78%
  );
}

.cb-action__name {
  font-family: "Cinzel", serif;
  font-size: 15px;
  font-weight: 600;
  color: var(--cg-gold-bright);
}

/* Значок вида слева от имени: те же силуэты, что на карточке героя
   (ABILITY_KIND_GLYPHS). Приглушён до цвета подсказки — кнопка остаётся
   кнопкой, а не витриной. Размер фиксирован, имя переносится как
   и прежде: значок не даёт кнопке вырасти на узком экране. */
.cb-action__head {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.cb-ability__glyph {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  color: var(--cg-text-dim);
}

.cb-action__hint {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  color: var(--cg-text-dim);
}

.cb-action.is-disabled {
  opacity: 0.38;
  cursor: not-allowed;
  border-color: var(--cg-border);
  box-shadow: none;
  transform: none;
}

/* ---- Исход боя ---------------------------------------------------------- */
.cb-outcome {
  display: grid;
  gap: 10px;
  justify-items: center;
  padding: 18px 0;
}

.cb-banner {
  font-family: "Cinzel Decorative", "Cinzel", serif;
  font-size: clamp(30px, 7vw, 52px);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: cb-banner-in 0.5s ease-out;
}

/* Фон модификаторов — только `background-image`: сокращённое `background`
   сбрасывало обрезку по тексту из `.cb-banner` в border-box, и баннер
   рисовался сплошным прямоугольником без слова (найдено 14 сентября 2026
   на арене, касалось и Башни). */
.cb-banner--victory {
  background-image: linear-gradient(180deg, #ffe9a8, var(--cg-gold) 55%, #8a713f);
  filter: drop-shadow(0 0 18px rgba(212, 175, 55, 0.45));
}

.cb-banner--defeat {
  background-image: linear-gradient(180deg, #f0a0a0, #b03636 55%, #5e1f1f);
  filter: drop-shadow(0 0 18px rgba(176, 54, 54, 0.4));
}

.cb-banner--retreated {
  background-image: linear-gradient(180deg, #cfc6d4, var(--cg-text-dim) 60%, #5a545f);
}

@keyframes cb-banner-in {
  from { opacity: 0; transform: scale(0.9); }
  to { opacity: 1; transform: none; }
}

.cb-banner__note {
  font-family: "Cormorant Garamond", serif;
  font-style: italic;
  color: var(--cg-text-dim);
  margin: 0;
}

/* ---- Журнал ------------------------------------------------------------- */
/* Журнал свёрнут до одной последней строки и разворачивается по тапу.
   Развёрнутый занимал около 160px — четверть экрана телефона ради того,
   что игрок только что видел анимацией. */
.cb-log {
  margin-top: 12px;
  border: 1px solid var(--cg-border);
  border-radius: 10px;
  background: rgba(10, 9, 12, 0.45);
  overflow: hidden;
}

.cb-log__toggle {
  display: block;
  width: 100%;
  text-align: left;
  padding: 8px 12px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 12px;
  line-height: 1.35;
  color: var(--cg-text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cb-log__toggle:hover {
  color: var(--cg-gold-bright);
}

.cb-log__lines {
  display: none;
  max-height: 132px;
  overflow-y: auto;
  padding: 4px 12px 10px;
  flex-direction: column;
  gap: 4px;
  border-top: 1px solid var(--cg-border);
}

.cb-log.is-open .cb-log__lines {
  display: flex;
}

.cb-log__line {
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 12px;
  line-height: 1.4;
  color: var(--cg-text-dim);
}

.cb-log__line:last-child {
  color: var(--cg-text);
}

/* Пометка крита в журнале — тем же алым, что и всплывающее число. */
.cb-log__crit {
  color: var(--rarity-5);
  font-weight: 600;
  letter-spacing: 0.06em;
  margin-right: 6px;
}

.cb-log__line--crit {
  color: var(--cg-text);
}

/* ---------------------------------------------------------------------
   Шкала ультимейта — круг, заполняющийся снизу вверх
   ---------------------------------------------------------------------
   Личный у каждого героя: цена разная, общей шкалы нет. Стоит в углу
   плитки, а не отдельной строкой, — строка стоила бы высоты на телефоне,
   а иллюстрацию круг не перекрывает, потому что фигура стоит по центру.
   -------------------------------------------------------------------- */

/* Круг — сам себе кнопка: отдельной строки у ультимейта больше нет.
   Поэтому он и вырос до пальца, 40px, и стоит в углу, где не спорит
   ни с фигурой (она прижата к низу), ни с выбором цели по плитке. */
.cb-orb {
  position: absolute;
  /* Внутрь плитки, с отступом: соседняя стоит в 14px, и свечение круга
     раньше ложилось ей на край. Теперь и круг, и его свет остаются
     в своих границах. */
  top: 3px;
  right: 3px;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  overflow: hidden;
  z-index: 3;
  background: rgba(8, 7, 12, 0.82);
  border: 1px solid var(--cg-border);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.6);
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}

/* Неполный круг — показание, а не кнопка: тап уходит сквозь него на
   плитку, которой выбирают союзника-цель. Рассказать о себе он всё
   равно может — удержанием. */
.cb-orb.is-through {
  cursor: inherit;
}

/* Готовый ультимейт своего героя: пульсирует, потому что это
   единственная кнопка на экране, ради которой копят. */
.cb-orb.is-ready {
  animation: cb-orb-call 2.4s ease-in-out infinite;
}

/* Пульс живёт внутри круга: наружное свечение упирается в 3px отступа
   до края плитки, поэтому дышит внутренний свет, а не ореол. */
@keyframes cb-orb-call {
  0%, 100% {
    box-shadow: 0 0 6px -1px var(--cg-gold), inset 0 0 10px -3px var(--gold-bright);
  }
  50% {
    box-shadow: 0 0 6px -1px var(--cg-gold), inset 0 0 14px 0 var(--gold-bright);
  }
}

@media (prefers-reduced-motion: reduce) {
  .cb-orb.is-ready {
    animation: none;
    box-shadow: 0 0 6px -1px var(--cg-gold), inset 0 0 14px 0 var(--gold-bright);
  }
}

/* Столбик от дна: доля шкалы читается высотой, как в шаре маны. */
.cb-orb__fill {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(180deg, #9d7bd8, #5b3fa0);
  transition: height 0.4s ease;
}

.cb-orb__text {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  line-height: 1;
  color: var(--cg-text);
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9);
}

/* Взведённый круг: ультимейт выбран, экран ждёт союзника-цель. Та же
   роль, что у is-armed кнопок панели, — состояние держится без курсора. */
.cb-orb.is-armed {
  border-color: var(--cg-gold-bright);
  box-shadow: 0 0 10px -2px var(--cg-gold), inset 0 0 12px -3px var(--gold-bright);
}

/* Полный круг — конечное состояние, переполнения не бывает. Свечение
   и есть повод нажать. */
.cb-orb.is-full {
  border-color: var(--cg-gold);
  box-shadow: 0 0 6px -1px var(--cg-gold), inset 0 0 10px -2px var(--gold-bright);
}

.cb-orb.is-full .cb-orb__fill {
  background: linear-gradient(180deg, var(--gold-bright), #a8792f);
}

/* ---------------------------------------------------------------------
   Значки эффектов
   ---------------------------------------------------------------------
   Фигуры рисуются в коде (см. glyphEl): арта под эффекты нет. Цвет — по
   полярности: положительные холодные и светлые, отрицательные
   приглушённо тёплые. Кислотных цветов в палитре проекта не бывает.
   -------------------------------------------------------------------- */

.cb-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 2px 4px;
  border-radius: 6px;
  border: 1px solid transparent;
  background: rgba(20, 18, 24, 0.75);
  cursor: pointer;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 9.5px;
  line-height: 1;
}

.cb-badge__glyph {
  width: 12px;
  height: 12px;
  fill: currentColor;
}

.cb-badge__turns {
  color: var(--cg-text-dim);
}

/* Буква единицы: «2р» — два раунда, «1х» — один ход. Единиц в бою две,
   и на значке в двенадцать пикселей это единственное место, куда их
   различие влезает: цвет занят полярностью, форма — самим эффектом.
   Приглушена, чтобы читалось прежде всего число. Слово целиком
   показывает нажатие. */
.cb-badge__unit {
  margin-left: 1px;
  font-size: 8px;
  opacity: 0.7;
}

/* Число слоёв, а не пять одинаковых значков подряд: три отравы — это
   «3» в углу. */
.cb-badge__layers {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 12px;
  padding: 0 2px;
  border-radius: 999px;
  background: var(--cg-bg-card);
  border: 1px solid currentColor;
  font-size: 8.5px;
  text-align: center;
}

.cb-badge--positive {
  color: #a9cbe0;
  border-color: rgba(150, 195, 225, 0.45);
}

.cb-badge--negative {
  color: #c99a7a;
  border-color: rgba(190, 140, 105, 0.45);
}

.cb-badge--neutral {
  color: var(--cg-text-dim);
  border-color: var(--cg-border);
}

/* Щит показывает остаток поглощения, а не только ходы: число и полоска.
   Цвет между голубым и белым — это защита, а не лечение и не магия. */
.cb-badge--shield {
  color: #d6e7f2;
  border-color: rgba(200, 228, 245, 0.55);
  padding-bottom: 5px;
}

.cb-badge__amount {
  color: #eaf4fa;
}

.cb-badge__pool {
  position: absolute;
  left: 3px;
  right: 3px;
  bottom: 2px;
  height: 2px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.5);
  overflow: hidden;
}

.cb-badge__pool-fill {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, #8fc6e8, #f2fbff);
  transition: width 0.35s ease;
}

/* ---------------------------------------------------------------------
   Кнопки способностей
   ---------------------------------------------------------------------
   Ультимейт крупнее навыка: это кнопка, ради которой копят. Обе стоят
   над четырьмя базовыми действиями, потому что нажимают их реже, но
   решают они больше.
   -------------------------------------------------------------------- */

.cb-ability-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  width: 100%;
}

.cb-ability {
  border-color: var(--cg-border);
}

/* ---------------------------------------------------------------------
   Базовые действия — сеткой, а не столбцом
   ---------------------------------------------------------------------
   На кнопке значок, короткое слово и число, ради которого её выбирают.
   Объяснение ушло в долгое нажатие: каждый ход игрок решает не «что это
   такое», а «хватает ли энергии».
   -------------------------------------------------------------------- */

.cb-act {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--cg-gold-dim);
  background: linear-gradient(165deg, var(--cg-bg-card), #161417 78%);
  color: var(--cg-text);
  cursor: pointer;
  font-family: inherit;
  /* Палец не должен вызывать выделение текста при удержании. */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}

.cb-act:hover:not(.is-disabled) {
  border-color: var(--cg-gold);
  box-shadow: 0 0 18px -6px var(--cg-gold);
}

.cb-act.is-disabled {
  opacity: 0.38;
  cursor: not-allowed;
}

.cb-act__glyph {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  fill: var(--cg-gold-bright);
}

.cb-act__name {
  font-family: "Cinzel", serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--cg-gold-bright);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Число у правого края: глаз ищет именно его. */
.cb-act__value {
  margin-left: auto;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  color: var(--cg-text);
}

/* Причина отказа словами. Она приходит с сервера — клиент условие
   не повторяет и чисел не хранит. */
.cb-action__hint--why {
  color: #c99a7a;
}

/* ---------------------------------------------------------------------
   Длящиеся состояния бойца (слой .cb-fx-layer)
   ---------------------------------------------------------------------
   Четыре зоны вокруг бойца: над головой, вокруг силуэта, под ногами,
   сбоку. Что где показывается — таблица EFFECT_ZONES в combat.js;
   здесь только вид. Всё движение — transform и прозрачность, фильтры
   не анимируются (iOS). Ничто не ложится поверх лица и фигуры:
   зоны стоят над, под и вокруг спрайта, сфера — контур без заливки.
   ------------------------------------------------------------------- */

.cb-status-layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.cb-status {
  position: absolute;
  pointer-events: none;
}

.cb-status__zone {
  position: absolute;
}

/* Импульс наложения — один на все состояния: короткое разрастание.
   Снятое гаснет тем же сроком. */
.cb-status__zone.is-born {
  animation: cb-status-born var(--cb-status-pulse-ms, 280ms) ease-out;
}

.cb-status__zone.is-fading,
.cb-status.is-fading {
  opacity: 0;
  transition: opacity var(--cb-status-pulse-ms, 280ms) ease-out;
}

@keyframes cb-status-born {
  0% { transform: scale(0.55); opacity: 0; }
  60% { transform: scale(1.12); opacity: 1; }
  100% { transform: scale(1); }
}

/* --- Над головой ----------------------------------------------------- */

.cb-status__zone--head {
  left: 50%;
  top: -16px;
  width: 34px;
  height: 34px;
  transform: translateX(-50%);
}

/* Вращается контейнер целиком — три звёздочки идут по кругу. */
.cb-status__orbit {
  position: absolute;
  inset: 0;
  animation: cb-status-spin 3.2s linear infinite;
}

.cb-status--stars {
  color: var(--cg-gold-bright);
}

.cb-status__star {
  position: absolute;
  width: 7px;
  height: 7px;
  background: currentColor;
  clip-path: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
  filter: drop-shadow(0 0 3px currentColor);
}

.cb-status__star:nth-child(1) { left: 45%; top: 0; }
.cb-status__star:nth-child(2) { left: 6%; top: 62%; }
.cb-status__star:nth-child(3) { left: 82%; top: 62%; }

@keyframes cb-status-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* Перечёркнутая нота безмолвия: без вращения, цвет приглушённый. */
.cb-status--mute {
  color: var(--cg-text-dim);
}

.cb-status__glyph {
  position: absolute;
  inset: 0;
  fill: currentColor;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.8));
}

/* --- Вокруг силуэта --------------------------------------------------- */

/* Сфера — эллипс-контур чуть шире плитки спрайта: линия обходит фигуру
   по краю, центр остаётся пустым. Заливки нет — иллюстрация не
   перекрывается. */
.cb-status__zone--aura {
  inset: -3% -6% 10% -6%;
  border-radius: 50%;
  border: 2px solid currentColor;
  box-shadow: 0 0 14px -3px currentColor, inset 0 0 14px -5px currentColor;
}

/* Неуязвимость перебивает щит — золотая сфера. */
.cb-status--aura-gold {
  color: var(--cg-gold-bright);
}

/* Щит: голубовато-белый значка щита; плотность (opacity) приходит
   из остатка поглощения — числами её не дублируем, они на значке. */
.cb-status--aura-shield {
  color: #8fc6e8;
}

/* Защита света без щита: та же сфера, тоньше и тише. */
.cb-status--aura-ward {
  color: #8fc6e8;
  border-width: 1px;
  opacity: 0.55;
}

/* Скрытность: вид живёт на плитке (is-hidden-state ниже), нода зоны —
   пустой якорь учёта. */
.cb-status--fade-out {
  display: none;
}

/* Истаивание скрытности: не туман — заливки поверх фигуры нет, спрайт
   сам полупрозрачен и медленно «то проступает, то тает». Движется
   только прозрачность: фильтры не анимируются (iOS). Снятие класса
   при спадении — резкое проявление в момент атаки из скрытности,
   отдельного эффекта на это не нужно. */
.cb-unit.is-hidden-state .cb-unit__stage,
.cb-unit.is-hidden-state .cb-unit__portrait {
  animation: cb-status-hidden-breath 3.6s ease-in-out infinite;
}

@keyframes cb-status-hidden-breath {
  0%, 100% { opacity: 0.42; }
  50% { opacity: 0.58; }
}

/* --- Под ногами ------------------------------------------------------- */

.cb-status__zone--feet {
  left: 12%;
  right: 12%;
  bottom: 2%;
  height: 24%;
}

.cb-status__mote {
  position: absolute;
  left: calc(8% + var(--i) * 26%);
  top: 10%;
  width: 5px;
  height: 7px;
  border-radius: 50% 50% 60% 60%;
  background: currentColor;
  animation: cb-status-drip 1.7s ease-in infinite;
  animation-delay: calc(var(--i) * 0.4s);
  opacity: 0;
}

/* Капли вниз: отрава — приглушённый болотный, без кислоты; кровь —
   боевой красный. Цвета собраны из палитры, новых не заводится. */
.cb-status--drips-poison {
  color: color-mix(in srgb, var(--element-nature) 60%, #2a2a24);
}

.cb-status--drips-blood {
  color: var(--combat-red);
}

@keyframes cb-status-drip {
  0% { transform: translateY(0); opacity: 0; }
  18% { opacity: 0.85; }
  80% { transform: translateY(11px); opacity: 0; }
  100% { transform: translateY(11px); opacity: 0; }
}

/* Поджог: искры вверх, приглушённый огненный. */
.cb-status--sparks {
  color: color-mix(in srgb, var(--element-fire) 70%, #3a3540);
}

.cb-status--sparks .cb-status__mote {
  top: auto;
  bottom: 10%;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  animation: cb-status-spark 1.4s ease-out infinite;
  animation-delay: calc(var(--i) * 0.33s);
}

@keyframes cb-status-spark {
  0% { transform: translateY(0); opacity: 0; }
  20% { opacity: 0.9; }
  80% { transform: translateY(-13px); opacity: 0; }
  100% { transform: translateY(-13px); opacity: 0; }
}

/* --- Сбоку у плитки --------------------------------------------------- */

/* Две боковые зоны: замедление справа, ускорение слева, зеркально.
   Эффекты действуют вместе — и видны вместе, приоритета между ними
   нет (таблица EFFECT_ZONES: это разные зоны). */
.cb-status__zone--side,
.cb-status__zone--side-left {
  top: 22%;
  bottom: 26%;
  width: 13px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
}

.cb-status__zone--side {
  right: -5px;
}

.cb-status__zone--side-left {
  left: -5px;
}

.cb-status__chevron {
  width: 13px;
  height: 9px;
  fill: currentColor;
  animation: cb-status-slide 1.5s ease-in-out infinite;
  animation-delay: calc(var(--i) * 0.25s);
  opacity: 0.5;
}

/* Замедление: стрелки вниз, тускло-фиолетовый — намеренно далёкий
   от чистой Тьмы Олдрика; направление стрелки само говорит «хуже».
   Алый не берётся: он в бою значит цель и урон. */
.cb-status--arrows-down {
  color: color-mix(in srgb, var(--element-dark) 42%, var(--hairline));
}

/* Ускорение: стрелки вверх, приглушённый синий. */
.cb-status--arrows-up {
  color: color-mix(in srgb, var(--element-water) 65%, var(--hairline));
}

.cb-status--arrows-up .cb-status__chevron {
  transform: rotate(180deg);
  animation-name: cb-status-slide-up;
}

@keyframes cb-status-slide {
  0%, 100% { transform: translateY(-2px); opacity: 0.35; }
  50% { transform: translateY(2px); opacity: 0.9; }
}

@keyframes cb-status-slide-up {
  0%, 100% { transform: rotate(180deg) translateY(-2px); opacity: 0.35; }
  50% { transform: rotate(180deg) translateY(2px); opacity: 0.9; }
}

/* Замер FPS (?perf=1) — живёт на экране, не в консоли. */
.cb-fps {
  position: absolute;
  top: 4px;
  right: 6px;
  padding: 2px 7px;
  border-radius: 6px;
  background: rgba(8, 7, 12, 0.8);
  border: 1px solid var(--cg-border);
  color: var(--cg-text-dim);
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 11px;
  z-index: 9;
}

@media (prefers-reduced-motion: reduce) {
  .cb-pop {
    animation-duration: var(--cb-pop-reduced-ms, 500ms);
  }

  /* Крит остаётся заметным пометкой и размером, но без резкого замаха
     и долгого зависания — движение здесь и просили убавить. */
  .cb-pop--crit {
    animation: cb-pop-up var(--cb-pop-crit-reduced-ms, 600ms) ease-out forwards;
  }

  .cb-unit,
  .cb-unit__hp-fill {
    transition: none;
  }

  .cb-unit:not(.cb-unit--chibi).is-hit .cb-unit__portrait,
  .cb-banner {
    animation: none;
  }

  /* Покачивание в покое отключается целиком. Рывок при атаке и
     вздрагивание при уроне оставлены: это не фон, а обратная связь на
     действие — без них непонятно, кто ударил и кому прилетело. */
  .cb-unit--chibi .cb-unit__chibi {
    animation: none;
  }

  /* Состояния: движение убирается, сам знак остаётся — оглушённый
     обязан читаться оглушённым (канон decisions.md, раздел 43).
     Частицы застывают видимыми: их покой без правки прозрачности
     означал бы пустую зону. */
  .cb-status__orbit,
  .cb-status__zone.is-born {
    animation: none;
  }

  .cb-status__mote,
  .cb-status--sparks .cb-status__mote {
    animation: none;
    opacity: 0.7;
  }

  .cb-status__chevron {
    animation: none;
    opacity: 0.7;
  }

  .cb-status__zone.is-fading,
  .cb-status.is-fading {
    transition: none;
  }

  /* Скрытность без движения: колебание снимается, пониженная
     прозрачность остаётся — состояние обязано читаться и так. */
  .cb-unit.is-hidden-state .cb-unit__stage,
  .cb-unit.is-hidden-state .cb-unit__portrait {
    animation: none;
    opacity: 0.5;
  }

  /* Сильные удары: встряски снимаются — и фигуры, и плитки, — вспышка
     остаётся: она короткая и двигает только прозрачность. Крит обязан
     читаться и здесь: вспышка, пометка и размер числа при нём. */
  .cb-unit--chibi.is-hit--power .cb-unit__chibi,
  .cb-unit--chibi.is-hit--crit .cb-unit__chibi,
  .cb-unit.is-hit--crit {
    animation: none;
  }

  /* Смерть без движения: оседания нет, финальная поза и серость —
     сразу (движение убирается, содержание нет: павший обязан читаться
     павшим). Подмена is-dying → is-dead в JS этой позы не меняет. */
  .cb-unit.is-dying {
    animation: none;
    opacity: 0.42;
    filter: grayscale(1);
  }

  .cb-unit--chibi.is-dying .cb-unit__chibi {
    animation: none;
    transform: rotate(-14deg) translateY(6px);
    filter: grayscale(1) brightness(0.4) drop-shadow(0 2px 4px rgba(0, 0, 0, 0.6));
  }

  /* Момент ультимейта без движения: затемнение и свечение остаются,
     но короче и без наплыва — гаснут и загораются прозрачностью.
     Событие обязано читаться и здесь. */
  .cb-ult-dim {
    animation-duration: var(--cb-ult-reduced-ms, 420ms);
  }

  .cb-ult-glow {
    animation: cb-ult-glow-still var(--cb-ult-reduced-ms, 420ms) ease-out forwards;
  }
}

/* Телефон: весь цикл хода обязан помещаться в экран без прокрутки.
   Бюджет на 380×640 — верхняя строка 28, ряд врагов ~140, сцена 22,
   ряд отряда ~140, свёрнутый журнал 32, панель действий ~128.
   Около 490 из 640, запас на вырез экрана и четвёртого героя. */
@media (max-width: 640px) {
  .combat-screen {
    padding: 12px 10px 0;
  }

  /* Ширина не задаётся: плитки делят ряд поровну (flex: 1 1 0 выше).
     Трое врагов получают по ~104px, четверо героев — по ~88px, и никто
     не уезжает на второй ряд, который стоил бы ещё 140px высоты. */
  .cb-unit {
    padding: 2px 1px 6px;
    max-width: 108px;
  }

  .cb-side {
    gap: 6px;
    padding: 6px 0;
  }

  /* Уровень отдельной строкой убран — он в подсказке по удержанию.
     Экономит по 14px на каждом ряду. */
  .cb-unit__level {
    display: none;
  }

  .cb-unit__name {
    font-size: 11px;
    margin-top: 5px;
  }

  .cb-unit__hp-text {
    font-size: 9px;
    margin-top: 2px;
  }

  .cb-unit__energy {
    gap: 3px;
    margin-top: 4px;
  }

  .cb-dot {
    width: 6px;
    height: 6px;
  }

  /* Высота боевого экрана на телефоне посчитана впритык — полоса
     значков не должна её увеличивать, иначе вернётся прокрутка в бою. */
  .cb-unit__effects {
    height: 16px;
    margin-top: 3px;
    gap: 2px;
  }

  .cb-unit__effects--above {
    margin-top: 0;
    margin-bottom: 3px;
  }

  .cb-badge {
    padding: 1px 3px;
    font-size: 8.5px;
  }

  .cb-badge__glyph {
    width: 11px;
    height: 11px;
  }

  /* Квадратную сцену больше не ужимаем: коробка ушла, места хватает, и
     фигура должна быть видна целиком, а не занимать четверть плитки. */

  .cb-action-row {
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }

  .cb-action {
    padding: 10px 12px;
  }

  .cb-action__name {
    font-size: 14px;
  }

  .cb-action__hint {
    font-size: 10px;
  }

  .cb-hint {
    font-size: 14px;
  }

  .cb-log {
    margin-top: 8px;
  }

  .cb-log__lines {
    max-height: 40vh;
  }
}

/* Значок стихии этажа. Тот же кружок, что на карточке героя, — класс
   cg-card__element-tag переиспользуется целиком, здесь только снимается
   его абсолютное позиционирование: на карточке героя он приколот
   к углу портрета, а на карточке этажа стоит в строке. */
.floor-elements {
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}

.floor-elements__tags {
  gap: 6px;
}

.floor-element {
  position: static;
}

/* ---------------------------------------------------------------------------
   Башня: подвкладки, крупный номер этажа, группы пройденных, зачистка
   --------------------------------------------------------------------------- */

/* Подвкладки живут внутри экрана, а не в навбаре: наверху перечислены
   места игры, а это одно место с тремя видами. */
.subtabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 10px 0 4px;
}

.subtab {
  padding: 8px 16px;
  border: 1px solid var(--cg-border);
  border-radius: 999px;
  background: var(--cg-bg-card);
  color: var(--cg-text-dim);
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 14px;
  cursor: pointer;
}

.subtab:hover {
  color: var(--cg-text);
  border-color: var(--cg-border-soft);
}

.subtab.active {
  color: var(--void);
  background: linear-gradient(180deg, var(--gold-bright), var(--gold));
  border-color: var(--gold);
  font-weight: 600;
}

/* Крупно — потому что подъём это один шаг, а не список. Номер должен
   читаться раньше всего остального на экране. */
.climb-mark {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 4px 2px 10px;
}

.climb-mark__word {
  font-family: "Cinzel", serif;
  font-size: 18px;
  letter-spacing: 0.08em;
  color: var(--muted);
}

.climb-mark__number {
  font-family: "Cinzel Decorative", "Cinzel", serif;
  font-size: 56px;
  line-height: 1;
  font-weight: 700;
  background: linear-gradient(180deg, var(--gold-bright), var(--gold) 60%, var(--gold-dim));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.floor-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.floor-group__title {
  font-family: "Cinzel", serif;
  font-size: 15px;
  letter-spacing: 0.06em;
  color: var(--gold-dim);
  padding-top: 6px;
}

.badge--locked {
  color: var(--muted);
  border-color: var(--hairline);
}

.depths--locked {
  opacity: 0.72;
}

/* Зачистка. Отделена линией сверху: это не продолжение описания места,
   а действие над ним. */
.sweep {
  border-top: 1px solid var(--cg-border);
  padding-top: 10px;
  margin-top: 2px;
}

.sweep--closed {
  font-size: 13px;
}

.sweep__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.sweep__runs {
  width: 84px;
}

.sweep__cost {
  font-size: 13px;
}

.sweep__answer {
  margin-top: 8px;
}

.sweep__answer--fail {
  color: var(--danger);
  font-size: 14px;
}

/* Добыча — одним списком за всю пачку, а не по заходу. */
.sweep-loot {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid var(--cg-border-soft);
  border-radius: 8px;
  background: rgba(201, 163, 92, 0.06);
}

.sweep-loot__title {
  font-family: "Cinzel", serif;
  font-size: 13px;
  letter-spacing: 0.05em;
  color: var(--gold);
  margin-bottom: 4px;
}

.sweep-loot__row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 14px;
}

.sweep-loot__name {
  color: var(--cg-text);
}

.sweep-loot__amount {
  color: var(--gold-bright);
  font-variant-numeric: tabular-nums;
}

/* Узкий телефон: поле и кнопка перестают тесниться, номер этажа
   уменьшается, чтобы не переносился. */
@media (max-width: 480px) {
  .climb-mark__number {
    font-size: 44px;
  }

  .sweep__controls {
    gap: 6px;
  }

  .sweep__runs {
    width: 72px;
  }

  .sweep__cost {
    flex-basis: 100%;
  }
}

/* Что дают Недра. Отделено от описания противников: игрок читает эти
   две строки, решая, спускаться ли вообще. */
.depths-drop {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border-left: 3px solid var(--gold-dim);
  border-radius: 0 8px 8px 0;
  background: rgba(201, 163, 92, 0.06);
}

.depths-drop__title {
  font-family: "Cinzel", serif;
  font-size: 13px;
  letter-spacing: 0.05em;
  color: var(--gold);
}

/* ---------------------------------------------------------------------
   Инвентарь. Витрина предметов: группа — панель, предмет — строка.

   Строк десять, и это не сетка: у каждой строки одна пара «сколько»,
   которую глаз ищет у правого края. Сетка карточек разложила бы их
   по ширине и заставила бы искать число в разных местах.
   --------------------------------------------------------------------- */

.inventory-group + .inventory-group {
  margin-top: 12px;
}

.inventory-group__name {
  margin: 0 0 10px;
  font-family: "Cinzel", serif;
  font-size: 15px;
  letter-spacing: 0.04em;
  color: var(--parchment);
}

.inventory-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.inventory-item + .inventory-item {
  border-top: 1px solid var(--hairline);
}

/* Строка целиком — кнопка: на телефоне попасть пальцем в название
   легче, чем в значок-стрелку сбоку. Выглядит она при этом строкой,
   а не кнопкой, — нажимается всё, что видно. */
.inventory-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px 2px;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.inventory-row:hover .inventory-row__name {
  color: var(--gold);
}

.inventory-row__glyph {
  flex: 0 0 22px;
  width: 22px;
  height: 22px;
  color: var(--gold);
  opacity: 0.85;
}

.inventory-row__name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 14px;
  color: var(--parchment);
}

/* Число у правого края и моноширинным: столбик количеств читается
   сверху вниз, а пропорциональные цифры его расшатывают. */
.inventory-row__amount {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--gold);
}

/* Треугольник раскрытия. Стоит после числа, чтобы столбик количеств
   не разъезжался от разной ширины подсказки. */
.inventory-row::after {
  content: "▸";
  flex: 0 0 auto;
  color: var(--muted);
  font-size: 11px;
  transition: transform 120ms ease;
}

.inventory-row[aria-expanded="true"]::after {
  transform: rotate(90deg);
}

.inventory-row__source {
  margin: 0 0 10px;
  padding: 0 2px 0 32px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
}

@media (prefers-reduced-motion: reduce) {
  .inventory-row::after {
    transition: none;
  }
}

/* Узкий телефон: значок уступает место названию, отступ подсказки
   ужимается под него. */
@media (max-width: 380px) {
  .inventory-row {
    gap: 8px;
  }

  .inventory-row__glyph {
    flex-basis: 18px;
    width: 18px;
    height: 18px;
  }

  .inventory-row__source {
    padding-left: 26px;
  }
}

/* ---------------------------------------------------------------------
   Способности на карточке героя.

   Стоят между снаряжением и боевыми показателями: сначала что герой
   умеет, потом чем он считается. Строк от двух до четырёх — сколько
   у героя способностей, а не сколько их бывает.
   --------------------------------------------------------------------- */

.cg-abilities {
  list-style: none;
  margin: 0;
  padding: 0;
}

.cg-ability-item + .cg-ability-item {
  border-top: 1px solid var(--hairline);
}

.cg-ability {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 9px 2px;
  background: none;
  border: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.cg-ability:hover .cg-ability__name {
  color: var(--gold);
}

/* Значок вида, а не способности: их шесть на весь ростер. Стоит
   у верхней строки, а не по центру блока, — иначе при распущенной
   подписи он уезжает вниз от названия, к которому относится. */
.cg-ability__glyph {
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  color: var(--gold);
  opacity: 0.85;
}

.cg-ability__body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.cg-ability__head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.cg-ability__name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 14px;
  color: var(--parchment);
}

/* Цена и перезарядка у правого края одной группой: глаз ищет их
   в одном месте, а не по разным углам строки. */
.cg-ability__price {
  flex: 0 0 auto;
  display: flex;
  gap: 8px;
  font-size: 12px;
  white-space: nowrap;
}

.cg-ability__cost {
  color: var(--gold);
  font-variant-numeric: tabular-nums;
}

.cg-ability__cooldown {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* **Подпись режет разметка, а не клиент.** Строка приходит с сервера
   готовой, и укоротить её в коде значило бы разобрать то, что
   собирать клиенту запрещено: после пробуждения подпись изменится
   целиком, и любое правило разбора соврёт. Здесь она просто
   не помещается и обрывается многоточием. */
.cg-ability__says {
  color: var(--muted);
  font-size: 12.5px;
  line-height: 1.4;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cg-ability[aria-expanded="true"] .cg-ability__says {
  white-space: normal;
}

/* Треугольник раскрытия — у верхней строки, как и значок. */
.cg-ability::after {
  content: "▸";
  flex: 0 0 auto;
  margin-top: 3px;
  color: var(--muted);
  font-size: 11px;
  transition: transform 120ms ease;
}

.cg-ability[aria-expanded="true"]::after {
  transform: rotate(90deg);
}

@media (prefers-reduced-motion: reduce) {
  .cg-ability::after {
    transition: none;
  }
}

/* Узкий телефон: цена уходит под название, чтобы длинному имени
   не приходилось переноситься на два слова. */
@media (max-width: 380px) {
  .cg-ability__head {
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
  }

  .cg-ability__glyph {
    flex-basis: 18px;
    width: 18px;
    height: 18px;
  }
}

/* ---------------------------------------------------------------------
   Страница прослушивания черновых голосов (?voices=1, voiceBench.js)
   ---------------------------------------------------------------------
   Не игра: открывается адресом с параметром, снимается вместе со
   стендом после расстановки отобранного. Стили нарочно скромные —
   это верстак, а не витрина.
   -------------------------------------------------------------------- */

.vb {
  max-width: 640px;
  margin: 0 auto;
  padding: 20px 14px 60px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.vb__title {
  font-size: 1.3rem;
}

.vb-volume {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.vb-volume__row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.vb-volume__slider {
  flex: 1 1 auto;
}

.vb-kind__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}

.vb-kind__title {
  font-family: "Cinzel", serif;
  font-size: 1.05rem;
  color: var(--cg-gold-bright);
  margin: 0;
}

.vb-kind__note {
  margin: 0 0 8px;
  font-size: 12px;
}

.vb-variant {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 7px 6px;
  border-radius: 8px;
}

.vb-variant__play {
  flex: 0 0 auto;
  padding: 4px 12px;
}

.vb-variant__index {
  flex: 0 0 auto;
  color: var(--cg-text-dim);
  font-size: 12px;
}

.vb-variant__name {
  flex: 0 0 auto;
  font-weight: 600;
  color: var(--cg-text);
}

.vb-variant__says {
  color: var(--cg-text-dim);
  font-size: 13px;
}

/* Строка вспыхивает на запуске звука: в «подряд» глаз видит, который
   из вариантов сейчас звучит, и сравнивает не по памяти. */
.vb-variant.is-sounding {
  animation: vb-sounding 0.8s ease-out;
}

@keyframes vb-sounding {
  0% {
    background: color-mix(in srgb, var(--cg-gold) 22%, transparent);
  }
  100% {
    background: transparent;
  }
}

/* ---------------------------------------------------------------------------
   Достижения: подвкладка Профиля и витрина двух последних ступеней
   --------------------------------------------------------------------------- */

/* Панель сверху списка: сумма «на руках» и «Получить всё» одной кнопкой.
   Перенос вместо сжатия — тот класс поломки, что случился с карточкой
   героя 24 августа: голый flex без wrap и min-width на детях распирает
   узкий экран длинной строкой. */
.achv-head {
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}

.achv-head__text {
  min-width: 0;
  flex: 1 1 220px;
  font-size: 14px;
}

.achv-row {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.achv-row__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 12px;
}

.achv-row__name {
  min-width: 0;
  font-weight: 600;
  /* Длинные названия переносятся, а не толкают строку вбок. */
  overflow-wrap: anywhere;
}

.achv-row__reward {
  flex: 0 0 auto;
  color: var(--gold-bright);
  font-size: 13px;
  white-space: nowrap;
}

.achv-row__foot {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.achv-row__progress {
  color: var(--cg-text-dim);
  font-size: 13px;
}

.achv-row--done {
  gap: 4px;
}

/* Витрина Профиля: две последние забранные ступени, не журнал. */
.achv-showcase__title {
  font-family: "Cinzel", serif;
  font-size: 14px;
  letter-spacing: 0.04em;
  color: var(--cg-text-dim);
}

.achv-showcase__row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 10px;
  padding: 6px 0;
  border-top: 1px solid var(--cg-border-soft);
}

.achv-showcase__row:first-of-type {
  border-top: none;
}

.achv-showcase__name {
  min-width: 0;
  overflow-wrap: anywhere;
  font-weight: 600;
}

.achv-showcase__reward {
  color: var(--gold-bright);
  font-size: 13px;
}

.achv-showcase__date {
  margin-left: auto;
  color: var(--cg-text-dim);
  font-size: 11px;
}

/* Живой бой (блок 6 PvP): с кем, чей ход и сколько осталось — над
   очередью, тем же шрифтом, что подписи верхней строки. */
.cb-live {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px 14px;
  margin-bottom: 6px;
  font-family: "Work Sans", system-ui, sans-serif;
  font-size: 13px;
  color: var(--cg-text-dim);
}
.cb-live__with {
  color: var(--cg-gold-bright);
}
.cb-live__clock {
  min-width: 4ch;
  font-variant-numeric: tabular-nums;
  color: var(--cg-text);
}
.cb-live__clock.is-late {
  color: #d17a7a;
}
