/* Стол: тёмное фэнтези без внешних ресурсов — шрифты системные, вся графика на CSS.
   Разметку и логику клиент берёт у сервера, здесь только вид (README §3.7, §65). */

:root {
    --bg: #0a0d18;
    --bg2: #141a2e;
    --panel: rgba(20, 26, 46, 0.86);
    --panel2: rgba(30, 38, 64, 0.72);
    --line: rgba(217, 177, 104, 0.28);
    --gold: #d9b168;
    --gold2: #f4e0b0;
    --text: #e8e9f2;
    --muted: #9aa2bd;
    --hp: #7fc37f;
    --goldCoin: #f0c35a;
    --spell: #b48bff;
    --danger: #d95f5f;
    --radius: 14px;
    --shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
    --serif: Georgia, "Times New Roman", serif;
    --sans: "Segoe UI", "Trebuchet MS", system-ui, sans-serif;
}

* {
    box-sizing: border-box;
}

html,
body {
    margin: 0;
    min-height: 100%;
}

body {
    /* Рисунок стола — первым слоем: положишь файл `img/backgrounds/background-table.webp` — он появится,
       без файла видны те же градиенты, поэтому разметка не ломается (§3.7). */
    background-image: url('/img/backgrounds/background-table.webp'),
        radial-gradient(circle at 20% -10%, #2a2145 0%, transparent 45%),
        radial-gradient(circle at 90% 0%, #1c3350 0%, transparent 40%),
        linear-gradient(160deg, var(--bg) 0%, var(--bg2) 60%, #0a0d18 100%);
    background-position: center;
    background-size: cover, auto, auto, auto;
    background-repeat: no-repeat;
    /* Фон стола закреплён: при прокрутке лобби и стола он не «уезжает» из-под содержимого (§3.7). */
    background-attachment: fixed;
    color: var(--text);
    font-family: var(--sans);
    font-size: 15px;
    line-height: 1.45;
    padding: 0 0 32px;
}

.haze {
    position: fixed;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(circle at 50% 120%, rgba(217, 177, 104, 0.12), transparent 55%);
    mix-blend-mode: screen;
}

h1,
h2 {
    font-family: var(--serif);
    letter-spacing: 0.02em;
    margin: 0 0 6px;
}

h1 {
    font-size: 30px;
}

h2 {
    font-size: 17px;
    color: var(--gold2);
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.hint {
    color: var(--muted);
    font-size: 12.5px;
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
}

.lead {
    color: var(--muted);
    max-width: 62ch;
}

/* --- верхняя панель ------------------------------------------------------- */

/* Шапка и меню — общий блок на все страницы: вид в nav.css, разметку собирает client.js (§65). */

.chip {
    padding: 5px 14px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: linear-gradient(180deg, rgba(34, 42, 70, 0.85), rgba(12, 16, 28, 0.8));
    font-size: 13px;
    color: var(--text);
    white-space: nowrap;
}

/* Фаза — самая важная надпись в полосе: она чуть крупнее и золотится (§4). */
.chip#phaseChip {
    color: var(--gold2);
    border-color: rgba(217, 177, 104, 0.5);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-size: 12px;
}

.chip.timer b {
    color: var(--gold2);
}

.chip.timer.urgent {
    border-color: rgba(217, 95, 95, 0.7);
    animation: pulse 1s infinite;
}

.connection {
    margin-left: auto;
    color: var(--muted);
    font-size: 12.5px;
}

.connection.online {
    color: var(--hp);
}

.connection.offline {
    color: var(--danger);
}

button,
.link {
    font-family: inherit;
    font-size: 14px;
    cursor: pointer;
    border-radius: 10px;
    border: 1px solid var(--line);
    background: var(--panel2);
    color: var(--text);
    padding: 8px 14px;
    text-decoration: none;
    transition: transform 0.12s ease, box-shadow 0.16s ease, background 0.16s ease;
}

button:hover:not(:disabled),
.link:hover {
    transform: translateY(-1px);
    box-shadow: 0 8px 18px rgba(0, 0, 0, 0.4);
}

button:disabled {
    opacity: 0.42;
    cursor: not-allowed;
}

.primary {
    background: linear-gradient(180deg, #e7c983, var(--gold));
    border-color: #f0d69a;
    color: #2a1d08;
    font-weight: 600;
}

.primary.big {
    padding: 12px 26px;
    font-size: 16px;
}

.ghost {
    background: transparent;
}

.hidden,
[hidden] {
    display: none !important;
}

/* --- лобби, драфт, итоги -------------------------------------------------- */

/*
 * Лобби, поиск, драфт и итоги занимают весь экран. Раньше оверлей стоял в потоке и был ниже
 * окна: под ним просвечивал фон стола — «двойной фон», сверху один, снизу другой (§50).
 */
.overlay {
    position: fixed;
    inset: 0;
    z-index: 15;
    display: flex;
    justify-content: center;
    align-items: flex-start;
    /* Отступ сверху с запасом: полоса состояния может занять две строки, и заголовок не должен
       уезжать под меню — «Выберите героя» залезало под шапку (§50). */
    padding: 140px 20px 40px;
    overflow: auto;
}

/* Фоны оверлеев — необязательны: положишь файл в `img/backgrounds/` — он покажется под тёмной дымкой,
   без файла видно только затемнение, и разметка не меняется (§3.7). */
#lobby.overlay,
#search.overlay,
#result.overlay,
#draft {
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
    /* Фон лобби, поиска, итогов и драфта неподвижен: он не прокручивается вместе с содержимым (§3.7). */
    background-attachment: fixed;
}

#lobby.overlay,
#search.overlay {
    background-image: linear-gradient(rgba(8, 11, 20, 0.7), rgba(8, 11, 20, 0.85)),
        url('/img/backgrounds/background-lobby.webp');
}

#result.overlay {
    background-image: linear-gradient(rgba(8, 11, 20, 0.7), rgba(8, 11, 20, 0.85)),
        url('/img/backgrounds/background-result.webp');
}

#draft {
    background-image: linear-gradient(rgba(8, 11, 20, 0.72), rgba(8, 11, 20, 0.72)),
        url('/img/backgrounds/background-draft.webp');
}

.lobby,
.result {
    width: min(940px, 100%);
    padding: 32px;
    border: 1px solid var(--line);
    border-radius: 22px;
    background: linear-gradient(180deg, rgba(26, 32, 54, 0.94), rgba(14, 18, 32, 0.94));
    box-shadow: var(--shadow);
}

.lobbyForm {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 12px;
    align-items: end;
    margin: 22px 0 10px;
}

.lobbyForm label {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 12.5px;
    color: var(--muted);
}

input,
select {
    font-family: inherit;
    font-size: 14px;
    padding: 9px 11px;
    border-radius: 10px;
    border: 1px solid rgba(255, 255, 255, 0.14);
    background: rgba(8, 11, 20, 0.8);
    color: var(--text);
}

.portraits {
    display: flex;
    gap: 10px;
    margin-top: 18px;
    overflow: hidden;
}

.portraits img,
.portraits .monogram {
    width: 58px;
    height: 58px;
    border-radius: 12px;
    object-fit: cover;
    border: 1px solid var(--line);
    filter: saturate(0.85);
}

.draft {
    /* Драфт героев — тоже экран во всю высоту: под ним не должно быть второго фона (§50). */
    position: fixed;
    inset: 0;
    z-index: 15;
    overflow: auto;
    padding: 140px 20px 40px;
    max-width: none;
    margin: 0;
}

.heroGrid {
    /* Героев в драфте трое (§16), поэтому карточки крупные: портрет виден целиком, а не полоской. */
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 16px;
    margin: 16px auto 0;
    max-width: 1120px;
}

/* Выбран один герой — карточка компактная, по центру: огромный портрет на пол-экрана не нужен (§16). */
.heroGrid.single {
    grid-template-columns: minmax(0, 320px);
    justify-content: center;
    max-width: 320px;
}

.heroGrid.single .hero .portrait {
    max-width: 200px;
    margin: 0 auto;
}

/* Заголовок драфта и кнопка рерола стоят одной строкой: замена набора — действие этого экрана (§67). */
.draftHead {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-wrap: wrap;
    gap: 16px;
    max-width: 1120px;
    margin: 0 auto;
}

.rerollHeroes {
    padding: 8px 16px;
    border-radius: 10px;
    border: 1px solid rgba(217, 177, 104, 0.5);
    background: linear-gradient(180deg, rgba(217, 177, 104, 0.22), rgba(217, 177, 104, 0.08));
    color: var(--gold2);
    font: inherit;
    cursor: pointer;
}

.rerollHeroes:hover:not(:disabled) {
    border-color: var(--gold);
}

/* Сфер нет — кнопка гаснет: замена набора стоит сферу, а не бесплатна (§67). */
.rerollHeroes:disabled {
    cursor: default;
    color: var(--muted);
    border-color: var(--line);
    background: rgba(8, 11, 20, 0.5);
}

/* Подпись под заголовком драфта: выбравшему сказано, чего он ждёт (§16, §67). */
.draftHint {
    max-width: 1120px;
    margin: 10px auto 0;
    text-align: center;
    font-size: 13px;
    color: var(--muted);
}

.hero {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 14px;
    border-radius: var(--radius);
    border: 1px solid var(--line);
    background: linear-gradient(180deg, var(--panel2), rgba(12, 16, 28, 0.9));
    cursor: pointer;
    animation: rise 0.35s ease both;
}

.hero:hover:not(.taken):not(.locked) {
    border-color: var(--gold);
    box-shadow: 0 0 0 1px rgba(217, 177, 104, 0.35), var(--shadow);
}

.hero.taken,
.hero.locked {
    opacity: 0.5;
    cursor: not-allowed;
}

.hero.locked {
    filter: grayscale(0.35);
}

/* Своя карточка не гаснет, как чужая: игрок смотрит на неё до начала подготовки (§16, §67). */
.hero.mine {
    border-color: var(--gold);
    box-shadow: 0 0 0 1px rgba(217, 177, 104, 0.35), var(--shadow);
    cursor: default;
}

.hero .heroMine {
    align-self: flex-start;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid rgba(217, 177, 104, 0.5);
    color: var(--gold2);
    font-size: 12px;
}

.hero .portrait {
    /* Портрет героя квадратный: картинки приходят квадратными, и обрезать их нечем (§10, §36).
       height: auto снимает базовую высоту .portrait (46px) — иначе картинка выходила полоской. */
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    border-radius: 12px;
    object-fit: cover;
    border: 1px solid rgba(217, 177, 104, 0.25);
}

.hero .monogram {
    /* Монограмма — та же квадратная плитка, что и портрет: без ширины она сжималась в полоску (§10). */
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: 12px;
    font-size: 40px;
}

.hero .name {
    font-family: var(--serif);
    font-size: 17px;
    color: var(--gold2);
}

.hero .meta {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
    color: var(--muted);
}

/* Способность героя в драфте: значок слева, текст справа — вместо класса героя (§16, §18). */
.hero .ability {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr);
    gap: 8px;
    align-items: start;
    font-size: 12.5px;
    color: #cdd3e8;
    text-align: left;
}

.hero .ability .abilityMark {
    width: 34px;
    height: 34px;
    border-radius: 8px;
    object-fit: cover;
    border: 1px solid rgba(217, 177, 104, 0.3);
    display: grid;
    place-items: center;
}

.hero .ability .abilityBody {
    min-width: 0;
}

/* Уровень героя игрока и причина, по которой герой недоступен (§16). */
.hero .heroLevel {
    color: var(--gold2);
}

.hero .heroWhy {
    font-size: 12px;
    color: #ffb3a7;
}

/* --- стол ----------------------------------------------------------------- */

.table {
    display: grid;
    /* Левая колонка шире: в ней имя игрока и его герой — длинный ник должен читаться целиком (§12). */
    grid-template-columns: 286px minmax(0, 1fr) 320px;
    gap: 16px;
    padding: 16px 20px 0;
    max-width: 1560px;
    margin: 0 auto;
}

/* Ленту боя видит только админ: без неё поле занимает всю ширину и лог его не «двигает» (§7). */
.table.noFeed {
    grid-template-columns: 286px minmax(0, 1fr);
}

.table.noFeed .feedPanel {
    display: none;
}

.panel {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--panel);
    padding: 14px;
    box-shadow: var(--shadow);
}

.panelSide {
    align-self: start;
    position: sticky;
    top: 76px;
    max-height: calc(100vh - 120px);
    display: flex;
    flex-direction: column;
}

.center {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
}

.zone {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--panel);
    padding: 14px;
    box-shadow: var(--shadow);
}

/* мой герой и экономика */

.me {
    position: relative;
    display: grid;
    grid-template-columns: 108px minmax(0, 1fr) auto;
    gap: 16px;
    align-items: center;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: linear-gradient(120deg, rgba(38, 30, 58, 0.95), rgba(16, 20, 34, 0.95));
    padding: 14px;
    box-shadow: var(--shadow);
}

/* Герой получил урон: вспышка по всей панели, а не по одной карточке — это событие раунда (§51). */
.me.flash {
    animation: heroHit 0.8s ease;
}

.me.flash .meHero img,
.me.flash .meHero .monogram {
    animation: pulse 0.8s ease;
}

.meHero {
    position: relative;
    width: 108px;
    height: 108px;
}

.meHero img,
.meHero .monogram {
    width: 100%;
    height: 100%;
    border-radius: 12px;
    object-fit: cover;
    border: 1px solid rgba(217, 177, 104, 0.4);
}

.meName {
    font-family: var(--serif);
    font-size: 22px;
    color: var(--gold2);
}

.meMeta {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 6px;
}

/* Полоска экономики: каждая цифра — своя плитка со значком, чтобы строки не сливались (§4). */
.pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 4px 12px 4px 10px;
    font-size: 12.5px;
    background: linear-gradient(180deg, rgba(30, 38, 64, 0.7), rgba(10, 13, 24, 0.6));
}

.pill b {
    font-size: 14px;
    font-weight: 700;
}

.pill i {
    color: var(--muted);
    font-style: normal;
    font-size: 11px;
}

.pill.hp {
    color: var(--hp);
    border-color: rgba(127, 195, 127, 0.4);
}

.pill.gold {
    color: var(--goldCoin);
    border-color: rgba(240, 195, 90, 0.4);
}

/*
 * Не хватило золота: счётчик вспыхивает красным и вздрагивает. Причина отказа видна там, где её ищут
 * глазами, — на самой цифре золота, а не только строкой уведомления (§9, §12).
 */
.pill.gold.short {
    animation: goldShort 0.9s ease;
}

/* Общее золото игры за столом не показывается (§67): им платят в магазине между матчами,
   а в таверне стоит золото раунда — путать два счёта нельзя. */

@keyframes goldShort {
    0% { box-shadow: 0 0 0 0 rgba(217, 95, 95, 0); transform: none; }
    20% { box-shadow: 0 0 22px 4px rgba(217, 95, 95, 0.5); transform: translateX(-3px); border-color: rgba(217, 95, 95, 0.85); }
    45% { transform: translateX(3px); }
    70% { transform: translateX(-2px); }
    100% { box-shadow: 0 0 0 0 rgba(217, 95, 95, 0); transform: none; }
}

.pill.tier {
    color: #b8c4ff;
    border-color: rgba(150, 170, 255, 0.4);
}

.pill.score {
    color: #e5b58b;
    border-color: rgba(229, 139, 107, 0.4);
}

.pill.attack {
    color: #e58b6b;
}

/* Место выбывшего игрока: узкая плитка с цифрой — она не отнимает ширину у имени (§12). */
.pill.place {
    color: #ffb3a7;
    border-color: rgba(217, 95, 95, 0.45);
    padding: 2px 8px;
    font-size: 11.5px;
}

/*
 * Значки плиток — инлайновые рисунки, а не символы шрифта: монета из эмодзи есть не в каждом
 * шрифте и превращается в пустой квадрат, а нарисованный значок виден всегда (§50).
 */
.icon {
    width: 16px;
    height: 16px;
    display: block;
    flex: 0 0 auto;
}

.pill .icon {
    width: 12px;
    height: 12px;
    margin-right: 1px;
}

.pill.gold .icon {
    color: var(--goldCoin);
}

.pill.hp .icon {
    color: var(--hp);
}

.pill.tier .icon {
    color: #b8c4ff;
}

.pill.score .icon {
    color: #e58b6b;
}

.pill.place .icon {
    width: 10px;
    height: 10px;
}

.actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 10px;
}

/* Действия подготовки: значок слева, подпись справа — кнопки не выглядят одной полосой текста (§4). */
.actions button {
    display: inline-flex;
    align-items: center;
    gap: 7px;
}

.actions .actIcon {
    display: inline-flex;
    align-items: center;
    line-height: 1;
}

.actions .actIcon .icon {
    width: 16px;
    height: 16px;
    color: var(--gold2);
}

/* Кнопка-значок (активная способность героя): только иконка, название — в подсказке (§86). */
.actions button.actIconOnly {
    position: relative;
    padding: 7px;
    gap: 0;
    min-width: 34px;
    justify-content: center;
}

.actions button.actIconOnly .actIcon .icon {
    width: 18px;
    height: 18px;
}

/* Остаток применений способности — маленький бейдж в углу значка (§86). */
.actions .actCount {
    position: absolute;
    top: -5px;
    right: -5px;
    min-width: 15px;
    height: 15px;
    padding: 0 3px;
    border-radius: 999px;
    display: grid;
    place-items: center;
    font-size: 10px;
    font-weight: 700;
    line-height: 1;
    color: #2a1d08;
    background: var(--gold2, #f4e0b0);
    border: 1px solid rgba(42, 29, 8, 0.35);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4);
}

.actions .actLabel {
    white-space: nowrap;
}

.ring {
    --progress: 1;
    width: 78px;
    height: 78px;
    border-radius: 50%;
    background: conic-gradient(var(--gold) calc(var(--progress) * 360deg), rgba(255, 255, 255, 0.08) 0);
    display: grid;
    place-items: center;
    position: relative;
}

.ring::after {
    content: "";
    position: absolute;
    inset: 6px;
    border-radius: 50%;
    background: #10142a;
}

/*
 * Цифра таймера стоит в просвете кольца. Диск `::after` рисуется позже содержимого, поэтому
 * без своего слоя он закрывал счётчик: кольцо показывало прогресс, но не время (§1).
 */
.ring span {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    line-height: 1;
    gap: 2px;
}

.ring span b {
    font-family: var(--serif);
    font-size: 24px;
    font-weight: 400;
    color: var(--gold2);
}

.ring span i {
    font-style: normal;
    font-size: 9px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
}

/* соперники */

.rivals {
    display: flex;
    flex-direction: column;
    gap: 10px;
    overflow: auto;
}

.rival {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) auto;
    gap: 8px;
    align-items: center;
    padding: 7px 8px;
    border-radius: 12px;
    border: 1px solid rgba(255, 255, 255, 0.08);
    background: rgba(12, 16, 28, 0.6);
}

/*
 * Следующий соперник виден ещё в таверне: строка подсвечена и подписана. Сервер готовит пары
 * в начале подготовки, поэтому игрок знает, с кем сойдётся, и может готовить отряд под него (§4, §12).
 */
.rival.opponent {
    border-color: rgba(217, 95, 95, 0.6);
    box-shadow: 0 0 0 1px rgba(217, 95, 95, 0.25);
}

.rival.next {
    border-color: rgba(217, 95, 95, 0.95);
    background: linear-gradient(90deg, rgba(217, 95, 95, 0.22), rgba(12, 16, 28, 0.6) 70%);
    box-shadow: 0 0 0 1px rgba(217, 95, 95, 0.5), 0 0 18px rgba(217, 95, 95, 0.35);
    animation: nextOpponent 2.2s ease-in-out infinite;
}

@keyframes nextOpponent {
    0%, 100% { box-shadow: 0 0 0 1px rgba(217, 95, 95, 0.5), 0 0 14px rgba(217, 95, 95, 0.28); }
    50% { box-shadow: 0 0 0 1px rgba(217, 95, 95, 0.8), 0 0 24px rgba(217, 95, 95, 0.55); }
}

/* Выбывший игрок в списке: тёмный и обесцвеченный — видно, что он уже не в игре (§12). */
.rival.dead {
    opacity: 0.42;
    filter: grayscale(1) brightness(0.55);
    border-color: rgba(217, 95, 95, 0.35);
}

/* Командный режим (§65): союзник в списке — свой цвет, враги — цвет боя. */
.rival.mate {
    border-color: rgba(76, 139, 245, 0.55);
    background: rgba(20, 34, 62, 0.65);
}

.rival.enemy {
    border-color: rgba(217, 95, 95, 0.28);
}

/* Свой игрок в списке — золотой рамкой и меткой «вы»: видно себя среди остальных (§12). */
.rival.self {
    border-color: rgba(217, 177, 104, 0.6);
    background: rgba(217, 177, 104, 0.12);
}

.rival .mateTag,
.rival .selfTag {
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #9fd2ff;
    border: 1px solid rgba(76, 139, 245, 0.5);
}

.rival .selfTag {
    color: var(--gold2);
    border-color: rgba(217, 177, 104, 0.5);
}

/* Метки «следующий бой» и «зеркальный бой»: тот же вид, что у «союзник» и «вы» (§12). */
.rival .nextTag,
.rival .mirrorTag {
    margin-left: 6px;
    padding: 1px 6px;
    border-radius: 999px;
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #ffb3b3;
    border: 1px solid rgba(217, 95, 95, 0.55);
}

.rival .mirrorTag {
    color: #d9c7ff;
    border-color: rgba(150, 130, 240, 0.55);
}

.rival img,
.rival .monogram {
    width: 44px;
    height: 44px;
    border-radius: 10px;
    object-fit: cover;
    border: 1px solid var(--line);
}

/*
 * Имя игрока переносится на вторую строку, а не обрезается многоточием: раньше длинный ник
 * не влезал в колонку и превращался в «Кос…» (§12). Метка «вы» не рвётся по буквам.
 */
.rival .rivalName {
    font-size: 13px;
    line-height: 1.25;
    overflow-wrap: break-word;
    max-height: 2.6em;
    overflow: hidden;
}

.rival .rivalName .mateTag,
.rival .rivalName .selfTag {
    white-space: nowrap;
    vertical-align: middle;
}

.rival .rivalHero {
    font-size: 11.5px;
    color: var(--muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.bar {
    height: 7px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.1);
    overflow: hidden;
    margin-top: 4px;
}

.bar i {
    display: block;
    height: 100%;
    background: linear-gradient(90deg, #4e8f5a, var(--hp));
    transition: width 0.4s ease;
}

.dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #555f7d;
}

.dot.on {
    background: var(--hp);
    box-shadow: 0 0 8px rgba(127, 195, 127, 0.8);
}

/* --- карты на столе ------------------------------------------------------- */

.slots {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    min-height: 118px;
}

.slot {
    flex: 0 0 auto;
    width: 118px;
    border: 1px dashed rgba(255, 255, 255, 0.12);
    border-radius: 12px;
    min-height: 112px;
    display: grid;
    place-items: center;
    color: rgba(255, 255, 255, 0.22);
    font-size: 12px;
}

/*
 * Карта отряда: то же лицо, что в таверне (§10), но своя — её можно тащить, двигать и продавать.
 * Размер средний и постоянный: растянутая на всю ширину карта вытесняла остальные, и отряд
 * переставал читаться целиком (§10, §14).
 */
.face.board {
    flex: 0 0 auto;
    width: 118px;
    animation: rise 0.3s ease both;
}

/* Усиленная карта: зелёная вспышка — видно, что именно её усилили (§13, §18). */
.face.board.buffed {
    animation: cardBuff 0.9s ease;
}

/* Значки ключевых слов читаются с одного взгляда: провокация красноватая, щит жёлтый (§86). */
.kwMark.kw-Taunt {
    color: #ff9c8a;
    border-color: rgba(255, 120, 96, 0.7);
    background: rgba(120, 30, 24, 0.55);
}
.kwMark.kw-Shield {
    color: #ffd977;
    border-color: rgba(255, 207, 107, 0.8);
    background: rgba(120, 92, 20, 0.55);
}
.kwMark.kw-Elusive {
    color: #9fd4ff;
    border-color: rgba(120, 180, 255, 0.7);
    background: rgba(24, 52, 84, 0.55);
}

/* Метки «когда срабатывает»: клич, хрип, при атаке — чтобы тип эффекта был виден на карте (§86). */
.faceTags {
    display: flex;
    flex-wrap: wrap;
    gap: 3px;
    margin-top: 2px;
}
.faceTag {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    color: #cfd4e6;
    background: rgba(255, 255, 255, 0.08);
    border: 1px solid rgba(255, 255, 255, 0.16);
    border-radius: 999px;
}
.faceTag .icon {
    width: 10px;
    height: 10px;
}
.faceTag.tag-battlecry { color: #ffd977; border-color: rgba(255, 207, 107, 0.55); }
.faceTag.tag-deathrattle { color: #c9b6ff; border-color: rgba(180, 160, 255, 0.55); }
.faceTag.tag-attack { color: #ffb0a0; border-color: rgba(255, 150, 130, 0.5); }
.faceTag.tag-turn { color: #a8e6c1; border-color: rgba(150, 220, 180, 0.5); }
.faceTag.tag-start { color: #9fd4ff; border-color: rgba(140, 200, 255, 0.5); }

/* Подсказка стола (§86): всплывает рядом с курсором и объясняет базовую механику. */
.hintTip {
    position: fixed;
    z-index: 90;
    max-width: 264px;
    padding: 8px 10px;
    font-size: 12px;
    line-height: 1.35;
    color: #e8eaf4;
    background: rgba(14, 16, 26, 0.96);
    border: 1px solid rgba(255, 207, 107, 0.45);
    border-radius: 10px;
    box-shadow: 0 10px 26px rgba(0, 0, 0, 0.45);
    pointer-events: none;
}
.hintTip b {
    display: block;
    margin-bottom: 3px;
    font-size: 12px;
    color: #ffcf6b;
}
/* Заклинание с целью (§86): в таверне видно, что ему нужна карта, а после клика — что ждём выбора. */
.faceButton.needsTarget { position: relative; }
.faceButton.needsTarget::after {
    content: 'цель';
    position: absolute;
    top: 4px;
    right: 4px;
    padding: 1px 6px;
    font-size: 10px;
    font-weight: 600;
    color: #10121b;
    background: #ffcf6b;
    border-radius: 999px;
    letter-spacing: 0.02em;
}
.faceButton.needsTarget.armed::after { content: 'выберите карту'; }

/* Наведение заклинания (§86): свои карты подсвечены — видно, во что попадёт эффект. */
.face.board.aim {
    cursor: pointer;
    outline: 2px solid #ffcf6b;
    outline-offset: 2px;
    border-radius: 10px;
    animation: aimPulse 1.1s ease-in-out infinite;
}

@keyframes aimPulse {
    0%, 100% { box-shadow: 0 0 0 0 rgba(255, 207, 107, 0); }
    50% { box-shadow: 0 0 16px 3px rgba(255, 207, 107, 0.55); }
}

@keyframes cardBuff {
    0% { box-shadow: 0 0 0 0 rgba(168, 233, 168, 0); }
    30% { box-shadow: 0 0 22px 4px rgba(168, 233, 168, 0.65); border-color: rgba(168, 233, 168, 0.9); }
    100% { box-shadow: 0 0 0 0 rgba(168, 233, 168, 0); }
}

/* Кнопки карты отряда: появляются по наведению, чтобы лицо карты оставалось чистым. */
.cardActions {
    position: absolute;
    inset: auto 6px 32px 6px;
    display: none;
    gap: 4px;
    justify-content: center;
}

.face.board:hover .cardActions {
    display: flex;
}

.cardActions button {
    padding: 2px 8px;
    font-size: 12px;
    border-radius: 8px;
    background: rgba(8, 11, 20, 0.9);
}

/* таверна */

/* Таверна: карты стоят одной строкой, но не растягиваются во всю ширину. Когда слотов мало или
   окно узкое, ряд переносится, а карта остаётся своего размера (§9). */
.shop {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: stretch;
}

.shop .faceButton {
    flex: 0 0 auto;
    width: 124px;
}

/* Галочка «куда покупать» под таверной: место покупок — выбор игрока, он переживает перезагрузку (§12). */
.buySwitch {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin-top: 8px;
    font-size: 12.5px;
    color: var(--muted);
    cursor: pointer;
}

.buySwitch:hover {
    color: var(--text);
}

.buySwitch input {
    width: 15px;
    height: 15px;
    accent-color: var(--gold);
    cursor: pointer;
}

/*
 * Рука: сюда попадают карты от заклинаний и способностей («взять карту из магазина», «вернуть
 * проданную»). Карта того же размера, что в отряде, и так же тянется в отряд перетаскиванием (§12).
 */
.hand {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    min-height: 118px;
}

.hand .face {
    flex: 0 0 auto;
    width: 118px;
    cursor: grab;
}

.hand .face:active {
    cursor: grabbing;
}

/* Карты таверны, поля и справочника рисуются одним лицом: правила в card.css (§10, §33). */

/* --- портреты и соперники ------------------------------------------------- */

/* Портрет героя без контекста: тот же размер, что у монограммы в списках. */
.portrait {
    width: 46px;
    height: 46px;
    border-radius: 10px;
    object-fit: cover;
    border: 1px solid var(--line);
}

.lobbyButtons {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    margin: 16px 0 6px;
}

.rivalBody {
    flex: 1;
    min-width: 0;
}

.rivalSide {
    display: grid;
    gap: 4px;
    justify-items: center;
}


/* --- лента боя ------------------------------------------------------------ */

.feed {
    overflow: auto;
    display: flex;
    flex-direction: column-reverse;
    gap: 6px;
    font-size: 12.5px;
    padding-right: 4px;
    max-height: 100%;
}

.feedItem {
    border-left: 3px solid rgba(255, 255, 255, 0.14);
    background: rgba(12, 16, 28, 0.55);
    border-radius: 0 10px 10px 0;
    padding: 6px 9px;
    animation: rise 0.3s ease both;
    color: #cfd4e8;
}

.feedItem b {
    color: var(--gold2);
}

.feedItem.round {
    border-left-color: var(--gold);
    color: var(--gold2);
    font-weight: 600;
}

.feedItem.combat {
    border-left-color: #e58b6b;
}

.feedItem.gold {
    border-left-color: var(--goldCoin);
}

.feedItem.death {
    border-left-color: var(--danger);
}

.feedItem.buy {
    border-left-color: #6ea8ff;
}

.feedItem.spell {
    border-left-color: var(--spell);
}

.feedItem.elim {
    border-left-color: #ff8a8a;
    background: rgba(60, 20, 26, 0.5);
    color: #ffd7d7;
    font-weight: 600;
}

.feed .empty {
    color: var(--muted);
}

/* --- итоги ---------------------------------------------------------------- */

.podium {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 16px;
    margin: 24px 0;
    min-height: 210px;
}

.step {
    width: 150px;
    text-align: center;
    animation: rise 0.5s ease both;
}

.step .portrait,
.step .monogram,
.step .portrait {
    width: 84px;
    height: 84px;
    border-radius: 14px;
    object-fit: cover;
    border: 1px solid var(--line);
    margin-bottom: 8px;
}

.step .stepBody {
    border-radius: 14px 14px 0 0;
    border: 1px solid var(--line);
    border-bottom: none;
    background: linear-gradient(180deg, rgba(58, 46, 24, 0.9), rgba(20, 24, 40, 0.9));
    padding: 12px 8px;
}

.step.first .stepBody {
    background: linear-gradient(180deg, rgba(94, 74, 30, 0.95), rgba(28, 24, 18, 0.9));
    box-shadow: 0 0 30px rgba(217, 177, 104, 0.3);
}

.step .place {
    font-family: var(--serif);
    font-size: 26px;
    color: var(--gold2);
}

.step .who {
    font-size: 13px;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.step .whoHero {
    font-size: 11.5px;
    color: var(--muted);
}

.placements {
    width: 100%;
    border-collapse: collapse;
    margin-top: 8px;
    font-size: 13.5px;
}

.placements th,
.placements td {
    text-align: left;
    padding: 8px 10px;
    border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.placements th {
    color: var(--muted);
    font-weight: 500;
    text-transform: uppercase;
    font-size: 11px;
    letter-spacing: 0.08em;
}

.placements tr.winner td {
    color: var(--gold2);
    font-weight: 600;
}

/* Кнопка дружбы в итогах матча (§80): одна компактная надпись в правом столбце. Строка таблицы
   от этого не растёт: у друга здесь стоит та же по размеру метка «в друзьях». */
.placements .friendCell {
    text-align: right;
    white-space: nowrap;
}

.friendAsk {
    font-size: 12px;
    padding: 5px 10px;
}

.row {
    display: flex;
    gap: 10px;
    align-items: center;
    flex-wrap: wrap;
    margin-top: 18px;
}

/* --- уведомления и анимации ---------------------------------------------- */

.toast {
    position: fixed;
    left: 50%;
    bottom: 26px;
    transform: translateX(-50%);
    padding: 10px 18px;
    border-radius: 12px;
    border: 1px solid var(--line);
    background: rgba(12, 16, 28, 0.96);
    box-shadow: var(--shadow);
    z-index: 40;
}

.toast.warn {
    border-color: rgba(217, 95, 95, 0.7);
    color: #ffd7d7;
}

/* --- онбординг новичка (§41) ---------------------------------------------- */

/* Реплика-подсказка стоит в углу стола и стол не перекрывает: это диалог, а не окно (§41). */
.onboarding {
    position: fixed;
    right: 18px;
    bottom: 18px;
    width: min(360px, calc(100vw - 36px));
    padding: 12px 14px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: rgba(12, 16, 28, 0.96);
    box-shadow: var(--shadow);
    /* Выше полосы состояния и лобби, но ниже подсказки карты: реплике новичка подсказку не перекрывать (§13). */
    z-index: 45;
    display: grid;
    gap: 8px;
}

.onboarding[hidden] {
    display: none;
}

.onboardingHead {
    display: flex;
    justify-content: space-between;
    gap: 10px;
    align-items: baseline;
}

.onboardingChapter {
    color: var(--gold);
    font-family: var(--serif);
    font-size: 13px;
}

.onboardingStep {
    color: var(--muted);
    font-size: 12px;
}

.onboardingLine {
    display: grid;
    grid-template-columns: 56px 1fr;
    gap: 10px;
    align-items: start;
}

.onboardingFace {
    width: 56px;
    height: 56px;
    border-radius: 50%;
    object-fit: cover;
    border: 1px solid var(--line);
}

.onboardingSpeech {
    display: grid;
    gap: 2px;
    min-width: 0;
}

.onboardingTitle {
    color: var(--gold2);
    font-size: 13px;
}

.onboardingName {
    color: var(--gold);
    font-weight: 600;
    font-size: 13px;
}

.onboardingText {
    color: var(--text);
    font-size: 13px;
    line-height: 1.35;
}

.onboardingActions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
}

.onboardingWhere {
    margin: 0;
    color: var(--muted);
    font-size: 12px;
}

.onboardingNote {
    margin: 0;
}

/* Место, о котором говорит реплика: мягкая золотая рамка — видно, куда смотреть, и играть не мешает (§41). */
.onboardingSpot {
    border-radius: var(--radius);
    box-shadow: 0 0 0 2px rgba(217, 177, 104, 0.65);
    /* Подсветка дышит: место, о котором говорит реплика, видно, даже если стол перерисовывается (§41). */
    animation: onboardingSpot 2.2s ease-in-out infinite;
}

@keyframes onboardingSpot {

    0%,
    100% {
        box-shadow: 0 0 0 2px rgba(217, 177, 104, 0.5);
    }

    50% {
        box-shadow: 0 0 0 5px rgba(240, 214, 144, 0.55), 0 0 26px rgba(217, 177, 104, 0.35);
    }
}

/* Полоска прогресса онбординга: сколько шагов пройдено (§41). */
.onboardingProgress {
    display: block;
    height: 4px;
    margin: 6px 0 8px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.12);
    overflow: hidden;
}

.onboardingProgress i {
    display: block;
    height: 100%;
    border-radius: inherit;
    background: linear-gradient(90deg, var(--gold, #d9b168), var(--gold2, #f4e0b0));
    transition: width 0.3s ease;
}

.monogram {
    display: grid;
    place-items: center;
    background: radial-gradient(circle at 30% 25%, rgba(217, 177, 104, 0.35), rgba(20, 24, 40, 0.95));
    color: var(--gold2);
    font-family: var(--serif);
    font-size: 22px;
}

@keyframes pop {
    0% { transform: scale(0.86); box-shadow: 0 0 0 0 rgba(217, 177, 104, 0.6); }
    60% { transform: scale(1.03); }
    100% { transform: none; box-shadow: 0 0 0 0 rgba(217, 177, 104, 0); }
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.55; }
}

/* --- бой: анимация по событиям движка (§4, §51) --------------------------- */

.battle {
    /*
     * Бой показывается на весь экран: обе армии видны целиком, каждый отряд в один ряд (§4, §51).
     * Фон — арена: рисунок стола под затемнением, а не пустое чёрное поле; без файла остаётся градиент.
     */
    position: fixed;
    inset: 0;
    z-index: 40;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto auto;
    gap: 10px;
    padding: 14px 22px 12px;
    overflow: auto;
    border: 0;
    background-image: linear-gradient(rgba(6, 8, 16, 0.78), rgba(6, 8, 16, 0.92)),
        url('/img/backgrounds/background-arena.webp'),
        url('/img/backgrounds/background-table.webp');
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
    /* Затухание без transform: подсказка кладётся внутрь панели боя, а сдвиг сцены сбивал бы её (§13). */
    animation: battleFade 0.25s ease both;
}

@keyframes battleFade {
    from { opacity: 0; }
    to { opacity: 1; }
}

/* Верхняя строка боя: только заголовок состава. Стороны подписаны по месту, у своих рядов (§51). */
.battleTop {
    display: grid;
    place-items: center;
}

/* Подпись стороны стоит над своим рядом: противник сверху, игрок снизу — как на сцене (§51). */
.battleTag {
    justify-self: center;
    padding: 3px 14px;
    border-radius: 999px;
    border: 1px solid rgba(217, 95, 95, 0.5);
    background: rgba(8, 11, 20, 0.8);
    color: #ffc9c0;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.battleTag.mine {
    border-color: rgba(217, 177, 104, 0.65);
    color: var(--gold2);
}

.battle h2 {
    margin: 0;
    font-size: 17px;
    letter-spacing: 0.04em;
    text-align: center;
    color: var(--gold2);
}

.battleSides {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto minmax(0, 1fr) auto;
    gap: 2px;
    align-content: center;
    min-height: 0;
}

.battleSide {
    display: flex;
    flex-wrap: nowrap;
    justify-content: center;
    align-items: flex-start;
    gap: 10px;
    min-height: 120px;
    /* Ряд не обрезает своих бойцов: атакующая карта уходит к цели за пределы ряда (§51). */
    overflow: visible;
    /* Верхний отступ — место вылетающим числам: без него они уходили под подпись и обрезались (§51). */
    padding: 44px 8px 14px;
}

/* Середина арены: знак «VS» — экран боя читается как сцена, а не как список (§51).
   Полоска идёт под картами: выпад атакующего проходит над ней, а не прячется за знаком (§51). */
.battleVersus {
    position: relative;
    z-index: 1;
    display: grid;
    place-items: center;
}

.battleVersus::before {
    content: "";
    position: absolute;
    left: 3%;
    right: 3%;
    top: 50%;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(217, 177, 104, 0.55), transparent);
}

.battleVersus span {
    position: relative;
    font-family: var(--serif);
    font-size: 20px;
    letter-spacing: 0.2em;
    color: var(--gold2);
    padding: 2px 18px;
    border-radius: 999px;
    border: 1px solid rgba(217, 177, 104, 0.5);
    background: rgba(8, 11, 20, 0.92);
    box-shadow: 0 0 26px rgba(217, 177, 104, 0.3);
}

/* Портрет героя стороны боя: слева от отряда, чтобы видеть, кто с кем сражается (§51). */
.battleHero {
    position: relative;
    display: grid;
    justify-items: center;
    gap: 3px;
    width: 128px;
    flex: 0 0 auto;
    padding: 8px;
    border-radius: 12px;
    border: 1px solid rgba(255, 255, 255, 0.1);
    background: rgba(8, 11, 20, 0.72);
}

/* Герой получил урон в этом бою: панель вздрагивает и подсвечивается красным, но без засветки (§51). */
.battleHero.hurt {
    animation: heroHurt 1.2s ease;
}

@keyframes heroHurt {
    0% { box-shadow: 0 0 0 0 rgba(217, 95, 95, 0); transform: none; }
    15% { box-shadow: 0 0 26px 4px rgba(217, 95, 95, 0.42); transform: translateX(-4px); }
    35% { transform: translateX(4px); }
    55% { transform: translateX(-3px); }
    100% { box-shadow: 0 0 0 0 rgba(217, 95, 95, 0); transform: none; }
}

.battleHero.mine {
    border-color: rgba(217, 177, 104, 0.55);
    background: rgba(38, 30, 22, 0.7);
}

.battleHero .portrait,
.battleHero .monogram {
    width: 72px;
    height: 72px;
    border-radius: 10px;
    object-fit: cover;
    border: 1px solid var(--line);
}

.battleHero .monogram {
    display: grid;
    place-items: center;
    font-size: 28px;
    color: var(--gold);
    background: radial-gradient(circle at 30% 25%, rgba(217, 177, 104, 0.35), rgba(20, 24, 40, 0.95));
}

.battleHeroName {
    font-size: 11.5px;
    color: var(--gold2);
    text-align: center;
}

/* «вы» и «противник» прямо на панели героя: стороны не приходится угадывать по именам (§51). */
.battleHeroWho {
    font-size: 9.5px;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--muted);
}

.battleHero.mine .battleHeroWho {
    color: var(--gold2);
}

.battleHeroBar {
    display: block;
    width: 100%;
    height: 6px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.12);
    overflow: hidden;
}

.battleHeroBar i {
    display: block;
    height: 100%;
    background: linear-gradient(90deg, #4e8f5a, var(--hp));
}

.battleHeroHp {
    font-size: 13px;
    font-weight: 700;
    color: var(--hp);
}

.battleCard {
    position: relative;
    width: 148px;
    border-radius: 10px;
    border: 1px solid var(--line);
    background: linear-gradient(180deg, var(--cardTop, #2b3550), #10142a);
    padding: 6px;
    text-align: center;
    font-size: 13px;
    transition: transform 0.12s ease, opacity 0.2s ease, box-shadow 0.15s ease;
}

.battleCard img {
    width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: 6px;
    object-fit: cover;
    display: block;
}

.battleCardName {
    margin-top: 3px;
    font-family: var(--serif);
    font-size: 11.5px;
    color: var(--gold2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.battleCard .battleStats {
    display: flex;
    justify-content: space-between;
    font-weight: 700;
    font-size: 14px;
}

/*
 * Атака: карта бросается к тому, кого бьёт, и возвращается. Стороны стоят друг под другом, поэтому
 * атакующие уходят вниз, а защищающиеся вверх — движение идёт «к противнику», а не «вверх вообще» (§51).
 * Выпад идёт поверх полоски «VS»: z-index выше, чем у середины арены.
 */
#battleAttackers .battleCard.attacking {
    z-index: 8;
    animation: lungeDown 0.42s ease;
}

#battleDefenders .battleCard.attacking {
    z-index: 8;
    animation: lungeUp 0.42s ease;
}

.battleCard.hit {
    animation: battleHit 0.4s ease;
}

/*
 * Удар по силе урона (§86): искры летят тем шире, чем сильнее урон. Уровни 1–4 считает
 * `impactLevel` по корню из урона — 2 урона заметны, 20 не превращают бой в кашу.
 */
.battleCard.impact-1::after,
.battleCard.impact-2::after,
.battleCard.impact-3::after,
.battleCard.impact-4::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 10px;
    pointer-events: none;
    animation: sparkBurst 0.45s ease-out forwards;
}

.battleCard.impact-1::after { --spark: 10px; --sparkColor: rgba(255, 196, 120, 0.9); }
.battleCard.impact-2::after { --spark: 18px; --sparkColor: rgba(255, 168, 96, 0.95); }
.battleCard.impact-3::after { --spark: 26px; --sparkColor: rgba(255, 132, 72, 0.95); }
.battleCard.impact-4::after { --spark: 36px; --sparkColor: rgba(255, 92, 60, 0.95); }

@keyframes sparkBurst {
    0% {
        box-shadow: 0 0 0 0 var(--sparkColor);
        opacity: 0.9;
    }
    100% {
        box-shadow: 0 0 var(--spark) calc(var(--spark) / 2) transparent;
        opacity: 0;
    }
}

/* Снятый щит: вспышка холодным светом — видно, что удар ушёл в щит, а не в здоровье (§86). */
.battleCard.shield-break {
    animation: shieldBreak 0.5s ease;
}

@keyframes shieldBreak {
    0% { box-shadow: 0 0 0 0 rgba(150, 205, 255, 0); }
    35% { box-shadow: 0 0 26px 8px rgba(150, 205, 255, 0.75); border-color: rgba(190, 225, 255, 0.95); }
    100% { box-shadow: 0 0 0 0 rgba(150, 205, 255, 0); }
}
/*
 * Призыв в бою: новый боец появляется — проявляется, вырастает из меньшего размера и подсвечивается.
 * Без анимации карта просто возникала в ряду, и призыв читался как «уже стоял» (§51).
 */
.battleCard.summoned {
    z-index: 7;
    animation: cardSummon 0.5s ease-out both;
}

@keyframes cardSummon {
    0% {
        opacity: 0;
        transform: scale(0.62) translateY(14px);
        filter: brightness(2) saturate(0.6);
        box-shadow: 0 0 26px rgba(240, 214, 144, 0.9);
    }
    60% {
        opacity: 1;
        transform: scale(1.06) translateY(-3px);
        filter: brightness(1.25);
        box-shadow: 0 0 18px rgba(240, 214, 144, 0.55);
    }
    100% {
        opacity: 1;
        transform: none;
        filter: none;
        box-shadow: none;
    }
}

/*
 * Уничтожение карты: вспышка, сжатие и рассыпание — карта уходит с поля явно, а не просто тускнеет.
 * Дальше место пустует (`visibility` держит размер), поэтому строй не сдвигается (§51).
 */
.battleCard.dying {
    z-index: 4;
    animation: cardDestroy 0.7s ease-in forwards;
}

@keyframes cardDestroy {
    0% {
        filter: brightness(2.2) saturate(0.5);
        transform: none;
    }

    35% {
        filter: brightness(1.5) saturate(0.8);
        transform: scale(1.06) rotate(-2deg);
    }

    100% {
        filter: brightness(0.3) saturate(0.2);
        transform: scale(0.7) rotate(4deg);
        opacity: 0;
    }
}

.battleCard.dead {
    visibility: hidden;
}

/* Число, вылетающее из карты: урон, лечение, прибавка или имя способности (§18, §51).
   Цифры крупные и висят долго: игрок должен успеть прочитать, кому сколько прилетело. */
.battleFloat {
    position: absolute;
    left: 50%;
    top: 2px;
    z-index: 2;
    transform: translateX(-50%);
    font-weight: 700;
    font-size: 19px;
    white-space: nowrap;
    pointer-events: none;
    text-shadow: 0 2px 8px rgba(0, 0, 0, 0.95);
    animation: floatUp 1.7s ease-out both;
}

.battleFloat.damage {
    color: #ff9a8a;
}

.battleFloat.big {
    font-size: 42px;
    color: #ff7a6a;
}

.battleFloat.buff,
.battleFloat.heal {
    color: #a8e9a8;
}

/* Усиление с названием способности: строка длиннее цифры, поэтому кегль меньше (§18). */
.battleFloat.named {
    font-size: 13px;
}

.battleFloat.ability {
    font-size: 14px;
    color: #ddc9ff;
}

/* Вторая строка вылетающего числа: от какой способности, героя или карты прилетело (§18, §51). */
.battleFloat .floatNote {
    display: block;
    margin-top: 2px;
    font-size: 13px;
    font-style: normal;
    font-weight: 600;
    color: #ddc9ff;
}

/* Вклад бойца в урон по герою: маленькая пометка над картой победившей стороны (§51). */
.battleFloat.note {
    font-size: 13px;
    color: #ffd0a8;
}

.battleFloat.dead {
    font-size: 30px;
    color: #e8e9f2;
}

/*
 * Выпад атаки: карта действительно идёт к той, которую бьёт, и возвращается. Смещение приходит
 * из game.js (`aimBattleLunge`) по центрам карт; без него остаётся короткий кивок в сторону врага,
 * поэтому выпад виден и тогда, когда разметку измерить нельзя (§51).
 */
@keyframes lungeDown {
    0% { transform: none; }
    45% { transform: translate(var(--lunge-x, 0px), var(--lunge-y, 16px)) scale(1.08); box-shadow: 0 14px 26px rgba(0, 0, 0, 0.6); }
    100% { transform: none; }
}

@keyframes lungeUp {
    0% { transform: none; }
    45% { transform: translate(var(--lunge-x, 0px), var(--lunge-y, -16px)) scale(1.08); box-shadow: 0 14px 26px rgba(0, 0, 0, 0.6); }
    100% { transform: none; }
}

@keyframes floatUp {
    0% { opacity: 0; transform: translate(-50%, 6px) scale(0.85); }
    35% { opacity: 1; transform: translate(-50%, -10px) scale(1.15); }
    100% { opacity: 0; transform: translate(-50%, -34px) scale(1); }
}

@keyframes battleHit {
    0% { box-shadow: 0 0 0 0 rgba(255, 120, 120, 0.45); transform: none; }
    35% { box-shadow: 0 0 14px rgba(255, 90, 90, 0.55); transform: translateX(-3px); }
    70% { transform: translateX(3px); }
    100% { box-shadow: 0 0 0 0 rgba(255, 90, 90, 0); transform: none; }
}

.battleLog {
    margin-top: 10px;
    display: grid;
    gap: 3px;
    font-size: 12px;
    color: var(--muted);
    /* Лог не должен «двигать» поле боя: его высота ограничена, лишнее прокручивается (§51). */
    max-height: 108px;
    overflow: auto;
}

.battleLog b {
    color: var(--text);
}

/* Журнал боя видит только админ: обычному игроку он не нужен и лишь сдвигает сцену боя (§7, §51). */
.battle.noLog .battleLog {
    display: none;
}

/* --- сводка боя: итог и цена для каждой стороны (§51) --------------------- */

.battleSummary {
    display: grid;
    gap: 6px;
    justify-items: center;
    padding: 10px 18px;
    border-radius: 14px;
    border: 1px solid var(--line);
    background: rgba(8, 11, 20, 0.88);
    box-shadow: var(--shadow);
}

.battleOutcome {
    font-family: var(--serif);
    font-size: 34px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/*
 * Итог боя видно сразу, но без засветки: по краям поля проходит мягкая цветная волна — зелёная
 * за победу, красная за поражение. Свет только по краям, центр остаётся читаемым (§51).
 */
.battle.win::after,
.battle.lose::after,
.battle.draw::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    animation: battleWave 1.6s ease-out;
}

.battle.win::after {
    background: radial-gradient(ellipse at 50% 120%, rgba(110, 240, 140, 0.22), transparent 58%);
    box-shadow: inset 0 0 90px rgba(110, 240, 140, 0.16);
}

.battle.lose::after {
    background: radial-gradient(ellipse at 50% 120%, rgba(240, 90, 80, 0.24), transparent 58%);
    box-shadow: inset 0 0 90px rgba(240, 90, 80, 0.18);
}

.battle.draw::after {
    background: radial-gradient(ellipse at 50% 120%, rgba(217, 177, 104, 0.18), transparent 58%);
    box-shadow: inset 0 0 90px rgba(217, 177, 104, 0.12);
}

@keyframes battleWave {
    0% { opacity: 0; }
    20% { opacity: 0.85; }
    100% { opacity: 0; }
}

.battleOutcome.win {
    color: #a8e9a8;
    text-shadow: 0 0 20px rgba(168, 233, 168, 0.45);
}

.battleOutcome.lose {
    color: #ff9a8a;
}

.battleOutcome.draw {
    color: var(--gold2);
}

.battleResultRow {
    display: flex;
    gap: 28px;
    flex-wrap: wrap;
    justify-content: center;
}

.battleResultSide {
    display: flex;
    align-items: center;
    gap: 10px;
    font-size: 13px;
}

.battleResultLabel {
    font-size: 10.5px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted);
}

.battleResult b {
    color: var(--gold2);
}

.battleResult .lost {
    color: #ff9a8a;
    font-style: normal;
}

/* От чего прилетело герою: имя способности или выжившие бойцы противника (§51). */
.battleResult .from {
    color: var(--muted);
    font-size: 11.5px;
    font-style: normal;
}

.battleResult .out {
    color: var(--muted);
    font-size: 11px;
    font-style: normal;
}

.battleResultMeta {
    font-size: 12px;
    color: var(--muted);
}

/* --- подсказка карты или героя при наведении (§13) ------------------------ */

.preview {
    position: fixed;
    /*
     * Подсказка — самый верхний слой стола: во время боя она кладётся внутрь панели боя, и без
     * высокого слоя поле боя рисовалось поверх неё — «информация за полем боя» (§13, §51).
     */
    z-index: 200;
    /* Шире — тексту способностей хватает места, и он не «ужимается» в узкую колонку (§13). */
    width: min(340px, calc(100vw - 24px));
    /* Подсказка не выходит за экран: длинный список способностей прокручивается внутри неё (§13). */
    max-height: calc(100vh - 24px);
    overflow: hidden;
    padding: 12px;
    border-radius: 12px;
    border: 1px solid rgba(217, 177, 104, 0.5);
    background: rgba(8, 11, 20, 0.97);
    box-shadow: var(--shadow);
    pointer-events: none;
    display: grid;
    gap: 7px;
    font-size: 12.5px;
}

/* Портрет в подсказке строго квадратный: ширина и высота — одно и то же число (§13). */
.preview img,
.preview .monogram {
    width: min(100%, 38vh);
    height: auto;
    aspect-ratio: 1 / 1;
    margin: 0 auto;
    border-radius: 10px;
    object-fit: cover;
    display: block;
}

.preview .monogram {
    width: min(62%, 30vh);
}

/* Отряд игрока в подсказке: сколько существ и какая раса преобладает (§12, §33). */
.preview .previewTroops {
    text-align: center;
    font-size: 12.5px;
    color: var(--muted);
}

.preview .previewTroops b {
    color: var(--text);
}

/* Прошлый бой и выбывание в подсказке игрока: портрет противника, выбывание и место (§12). */
.preview .previewLast,
.preview .previewOut {
    text-align: center;
    font-size: 12px;
    color: var(--muted);
}

.preview .previewLast {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
}

/* Портрет противника в прошлом бое — картинкой, а не именем (§12). */
.preview .previewFoe {
    position: relative;
    display: inline-block;
    flex: 0 0 auto;
}

.preview .previewFoe img,
.preview .previewFoe .monogram {
    width: 48px;
    height: 48px;
    border-radius: 10px;
    object-fit: cover;
    border: 1px solid var(--line);
    margin: 0;
}

.preview .previewFoeName {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 12ch;
}

/* Обложка героя в подсказке игрока: тот же квадрат, но с местом под значок урона (§12). */
.preview .previewHero {
    position: relative;
    display: block;
    width: min(100%, 38vh);
    margin: 0 auto;
}

.preview .previewHero img,
.preview .previewHero .monogram {
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 10px;
    display: block;
    margin: 0;
}

/* Урон стоит на том, кто проиграл обмен: на своём портрете или на портрете противника (§51). */
.preview .duelDamage {
    position: absolute;
    top: -8px;
    right: -10px;
    padding: 1px 7px;
    border-radius: 999px;
    font-style: normal;
    font-weight: 700;
    font-size: 14px;
    color: #ffd7d0;
    background: rgba(120, 30, 30, 0.95);
    border: 1px solid rgba(255, 140, 130, 0.8);
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
}

.preview .previewOut {
    color: #ffb3a7;
}

/* Заклинание в подсказке: знак, имя и текст эффекта — то же, что показывает таверна (§18, §33). */
.preview .previewSpell {
    display: grid;
    place-items: center;
    height: 64px;
    border-radius: 10px;
    border: 1px solid rgba(180, 139, 255, 0.5);
    background: radial-gradient(circle at 50% 30%, rgba(180, 139, 255, 0.35), rgba(12, 10, 24, 0.95));
}

.preview .previewSpellIcon {
    width: 34px;
    height: 34px;
    color: #ddc9ff;
    /* Значок заклинания — картинка: держим её ровно и без искажений (§86). */
    object-fit: contain;
    border-radius: 6px;
}

.preview .previewEffect {
    color: #e6dcff;
    text-align: center;
}

/* Способность в подсказке: своя картинка + текст — отдельно от портрета героя (§18). */
.preview .previewAbility {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr);
    gap: 10px;
    align-items: start;
    padding: 7px 8px;
    border-radius: 10px;
    background: rgba(180, 139, 255, 0.08);
    border: 1px solid rgba(180, 139, 255, 0.22);
    line-height: 1.4;
    /* Строка способности занимает всю ширину подсказки: текст не жмётся в узкую колонку (§13). */
    width: 100%;
    text-align: left;
}

.preview .previewAbilityBody {
    min-width: 0;
    overflow-wrap: anywhere;
}

.preview .previewAbility img,
.preview .previewAbility .abilityMark {
    width: 44px;
    height: 44px;
    border-radius: 9px;
    object-fit: cover;
    border: 1px solid var(--line);
}

.preview .abilityMark {
    display: grid;
    place-items: center;
    font-size: 20px;
    color: #ddc9ff;
    background: rgba(180, 139, 255, 0.16);
}

.preview .previewName {
    font-family: var(--serif);
    font-size: 16px;
    color: var(--gold2);
    /* Имя героя или карты — по центру подсказки, а не по левому краю (§13). */
    text-align: center;
}

.preview .previewMeta {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    justify-content: center;
}

.preview .previewBuffs {
    text-align: center;
    color: #a8e9a8;
}

/* --- триплет: копии слились в золотую карту (§11) ------------------------- */

.triplet {
    position: fixed;
    left: 50%;
    top: 76px;
    transform: translateX(-50%);
    z-index: 36;
    display: grid;
    gap: 6px;
    justify-items: center;
    padding: 14px 22px;
    border-radius: 16px;
    border: 1px solid var(--gold);
    background: radial-gradient(circle at 50% 0%, rgba(240, 214, 144, 0.25), rgba(8, 11, 20, 0.97) 70%);
    box-shadow: 0 0 40px rgba(217, 177, 104, 0.35);
    animation: tripletIn 0.5s ease both;
}

.triplet .tripletTitle {
    font-family: var(--serif);
    font-size: 20px;
    color: var(--gold2);
}

.triplet .tripletText {
    font-size: 13px;
    color: #f0e3c4;
}

.triplet .face {
    width: 140px;
}

@keyframes tripletIn {
    0% { opacity: 0; transform: translateX(-50%) translateY(-12px) scale(0.9); }
    100% { opacity: 1; transform: translateX(-50%) translateY(0) scale(1); }
}

/* --- перетаскивание: таверна → отряд, отряд → отряд (§8) ------------------ */

.dragging {
    opacity: 0.45;
}

.dropTarget {
    outline: 2px dashed rgba(217, 177, 104, 0.7);
    outline-offset: 2px;
}

/* Таверна во время перетаскивания своей карты: сюда её можно сбросить, чтобы продать (§8). */
.shop.sellTarget {
    outline: 2px dashed rgba(217, 95, 95, 0.7);
    outline-offset: 4px;
    border-radius: 12px;
}

/* Цель атаки в бою: подсветка, чтобы было видно, кого бьют (§4). */
.battleCard.targeted {
    border-color: rgba(255, 150, 150, 0.8);
}

.battleCard.ability {
    animation: battleHit 0.4s ease;
    border-color: rgba(180, 139, 255, 0.9);
}

/* Таверна обновилась не по кнопке игрока (способность героя, заклинание, начало раунда):
   ассортимент вспыхивает и об этом пишется в ленте — «мелькание» становится видимым событием (§16). */
.shop.refreshed .faceButton .face {
    animation: shopRefresh 0.8s ease;
}

@keyframes shopRefresh {
    0% { box-shadow: 0 0 0 0 rgba(180, 139, 255, 0); }
    35% { box-shadow: 0 0 24px rgba(180, 139, 255, 0.6); border-color: rgba(200, 170, 255, 0.9); }
    100% { box-shadow: 0 0 0 0 rgba(180, 139, 255, 0); }
}

/* Урон герою: сдержанное красное свечение и лёгкая отдача — видно, что удар дошёл, но без засветки (§51). */
@keyframes heroHit {
    0% { box-shadow: 0 0 0 0 rgba(217, 95, 95, 0.45); transform: none; }
    15% { box-shadow: 0 0 34px 4px rgba(217, 95, 95, 0.34); transform: translateY(2px); }
    40% { box-shadow: 0 0 22px 2px rgba(217, 95, 95, 0.24); border-color: rgba(217, 95, 95, 0.7); }
    100% { box-shadow: var(--shadow); transform: none; }
}

/*
 * Усиление в таверне: панель отвечает зелёной волной. Заклинание или боевой клич куплены здесь,
 * поэтому и эффект бафа виден здесь же, а не только в бою (§18, §51).
 */
.shop.buffed {
    border-radius: 12px;
    animation: shopBuff 0.9s ease;
}

@keyframes shopBuff {
    0% { box-shadow: 0 0 0 0 rgba(168, 233, 168, 0); }
    35% { box-shadow: 0 0 26px 6px rgba(168, 233, 168, 0.45); }
    100% { box-shadow: 0 0 0 0 rgba(168, 233, 168, 0); }
}

/* --- адаптив -------------------------------------------------------------- */

@media (max-width: 1280px) {
    .table {
        grid-template-columns: 248px minmax(0, 1fr) 260px;
    }

    .table.noFeed {
        grid-template-columns: 248px minmax(0, 1fr);
    }
}

@media (max-width: 1080px) {
    .table {
        grid-template-columns: minmax(0, 1fr);
    }

    .table.noFeed {
        grid-template-columns: minmax(0, 1fr);
    }

    .panelSide {
        position: static;
        max-height: none;
    }

    .feed {
        max-height: 260px;
    }
}

/* --- пресеты матча: игрок выбирает готовый набор, числа задаёт админ (§65) --- */

.presets {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 10px;
    margin: 12px 0 8px;
}

.preset {
    display: grid;
    gap: 2px;
    padding: 12px 14px;
    text-align: left;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--panel2);
    color: var(--text);
    font-family: var(--sans);
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}

.preset:hover:not(:disabled) {
    border-color: var(--gold);
}

.preset.active {
    border-color: var(--gold);
    background: rgba(217, 177, 104, 0.14);
    box-shadow: inset 0 0 0 1px rgba(217, 177, 104, 0.35);
}

.preset:disabled {
    opacity: 0.55;
    cursor: default;
}

.presetIcon {
    font-size: 22px;
    line-height: 1.1;
}

.presetName {
    font-family: var(--serif);
    font-size: 16px;
}

.presetMeta {
    color: var(--muted);
    font-size: 12px;
}

/* --- очередь: сколько игрок уже ждёт (§65) --- */

.wait {
    margin: 10px 0 6px;
    font-size: 16px;
}

.wait b {
    color: var(--gold2);
    font-size: 20px;
}

/* --- поиск соперников: анимация ожидания (§27) ---------------------------- */

/* Пока игрок ждёт, вокруг портала крутятся руны, меняются герои и идут подсказки — ждать не скучно. */
.searchLobby {
    text-align: center;
}

.searchLobby .lead,
.searchLobby .hint {
    margin-left: auto;
    margin-right: auto;
}

.searchLobby .lobbyButtons {
    justify-content: center;
}

.summon {
    --runeRadius: calc((min(320px, 70vw) - 40px) / 2);
    position: relative;
    width: min(320px, 70vw);
    aspect-ratio: 1 / 1;
    margin: 14px auto 4px;
    display: grid;
    place-items: center;
}

.summonRing {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 1px dashed rgba(180, 139, 255, 0.35);
    animation: summonSpin 14s linear infinite;
}

.summonRing.middle {
    inset: 12%;
    border-style: dotted;
    border-color: rgba(217, 177, 104, 0.45);
    animation: summonSpin 9s linear infinite reverse;
}

.summonRing.inner {
    inset: 26%;
    border-style: solid;
    border-color: rgba(180, 139, 255, 0.55);
    animation: summonPulse 2.6s ease-in-out infinite;
}

.summonCore {
    position: absolute;
    inset: 34%;
    border-radius: 50%;
    background: radial-gradient(circle at 50% 45%, rgba(217, 177, 104, 0.5), rgba(96, 64, 168, 0.32) 55%, rgba(8, 11, 20, 0) 74%);
    animation: summonPulse 2.2s ease-in-out infinite;
}

.summonRunes {
    position: absolute;
    inset: 0;
    animation: summonSpin 24s linear infinite;
}

.summonRunes .rune {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 26px;
    height: 26px;
    margin: -13px 0 0 -13px;
    display: grid;
    place-items: center;
    color: #cdb6ff;
    /* Руна стоит по кругу: угол задаёт её номер, поэтому разметка знаков не повторяется (§50). */
    transform: rotate(calc(var(--i) * 45deg)) translateY(calc(-1 * var(--runeRadius))) rotate(calc(var(--i) * -45deg));
    animation: runeGlow 3s ease-in-out infinite;
    animation-delay: calc(var(--i) * -0.37s);
}

.summonRunes .runeIcon {
    width: 20px;
    height: 20px;
}

.summonPortrait {
    position: relative;
    width: 34%;
    aspect-ratio: 1 / 1;
    opacity: 0;
}

.summonPortrait.show {
    animation: summonPortraitIn 2.6s ease both;
}

.summonPortrait img,
.summonPortrait .monogram {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    border: 2px solid rgba(217, 177, 104, 0.75);
    box-shadow: 0 0 30px rgba(217, 177, 104, 0.4);
}

/* Места стола: заполненные — те, кого уже нашли (§27). */
.summonSeats {
    display: flex;
    gap: 8px;
    justify-content: center;
    margin: 4px 0 10px;
}

.summonSeats .seat {
    width: 16px;
    height: 16px;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.22);
    background: rgba(255, 255, 255, 0.06);
    transition: background 0.4s ease, box-shadow 0.4s ease, transform 0.4s ease;
}

.summonSeats .seat.taken {
    background: linear-gradient(180deg, #e7c983, var(--gold));
    border-color: #f0d69a;
    box-shadow: 0 0 12px rgba(217, 177, 104, 0.7);
    transform: scale(1.15);
}

.summonTip {
    min-height: 38px;
    margin: 0 auto 4px;
    max-width: 48ch;
    color: #cdd3e8;
    font-size: 13px;
    animation: summonTipFade 9s ease-in-out infinite;
}

@keyframes summonSpin {
    to { transform: rotate(360deg); }
}

@keyframes summonPulse {
    0%, 100% { opacity: 0.55; transform: scale(0.96); }
    50% { opacity: 1; transform: scale(1.04); }
}

@keyframes runeGlow {
    0%, 100% { color: #8f7bc4; }
    50% { color: #e2d4ff; filter: drop-shadow(0 0 8px rgba(180, 139, 255, 0.8)); }
}

@keyframes summonPortraitIn {
    0% { opacity: 0; transform: scale(0.9); }
    18% { opacity: 1; transform: scale(1); }
    82% { opacity: 1; transform: scale(1); }
    100% { opacity: 0; transform: scale(1.05); }
}

@keyframes summonTipFade {
    0%, 100% { opacity: 0.6; }
    15%, 85% { opacity: 1; }
}

/* --- союзник по команде: его герой, отряд и смайлики (§65) --- */

.team {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--panel);
    padding: 8px 12px;
    margin: 10px 0;
}

.teamHead {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.teamTitle {
    color: var(--gold);
    font-family: var(--serif);
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.teamPortrait img,
.teamPortrait .monogram {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    object-fit: cover;
}

.teamName {
    font-weight: 600;
}

.teamMeta {
    color: var(--muted);
    font-size: 12px;
}

.teamPings {
    margin-left: auto;
    display: flex;
    gap: 6px;
}

.teamPings .ping {
    border: 1px solid var(--line);
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
    color: var(--text);
    font-size: 16px;
    line-height: 1;
    padding: 6px 8px;
    cursor: pointer;
}

.teamPings .ping:hover {
    border-color: var(--gold);
}

.pingBubble {
    font-size: 18px;
}

.teamBoard {
    display: flex;
    gap: 6px;
    overflow-x: auto;
    margin-top: 8px;
    padding-bottom: 4px;
}

.teamBoard .face.mate {
    width: 58px;
    min-height: 74px;
    font-size: 9px;
}

.teamBoard .face.mate .faceArt img {
    height: 34px;
}

.teamBoard .face.mate .faceName {
    font-size: 9px;
}

.teamBoard .face.mate .statBubble {
    font-size: 10px;
    padding: 0 4px;
}

/* Карта союзника размером с ноготь: бейдж уровня уменьшается вместе с картой, иначе он её перевесит. */
.teamBoard .face.mate .faceTier {
    top: 3px;
    right: 3px;
    min-width: 14px;
    height: 14px;
    padding: 0 3px;
    font-size: 9px;
}

/* --- меню карты: смайлик союзнику и передача карты (§65) --- */

.cardMenu {
    position: fixed;
    z-index: 60;
    min-width: 190px;
    padding: 8px;
    display: grid;
    gap: 2px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: rgba(12, 16, 28, 0.98);
    box-shadow: var(--shadow);
}

.cardMenu[hidden] {
    display: none;
}

.menuTitle {
    font-family: var(--serif);
    padding: 2px 6px 6px;
    border-bottom: 1px solid var(--line);
}

.menuGroup {
    color: var(--muted);
    font-size: 12px;
    padding: 6px 6px 2px;
}

.menuItem {
    text-align: left;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--text);
    font: inherit;
    padding: 7px 8px;
    cursor: pointer;
}

.menuItem:hover:not(:disabled) {
    background: rgba(217, 177, 104, 0.16);
}

.menuItem:disabled {
    opacity: 0.5;
    cursor: default;
}

.menuHint {
    color: var(--muted);
    font-size: 11px;
    padding: 4px 6px 0;
}

/* Командные события в ленте: подарок карты и смайлик союзника (§65). */
.feedItem.team {
    color: #9fd2ff;
    border-left-color: #4c8bf5;
}

/* --- таверна: уровень и действия у её существ (§9, §81) ------------------- */

/*
 * Метки игрока стоят своей строкой под именем, как «вы»: имя не переносится, а подпись боя
 * читается отдельно. В строке они идут друг за другом без отступа от имени (§12).
 */
.rival .rivalTags {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-top: 3px;
}

.rival .rivalTags .selfTag,
.rival .rivalTags .nextTag,
.rival .rivalTags .mirrorTag,
.rival .rivalTags .mateTag {
    margin-left: 0;
}

/* Заголовок таверны: название, уровень и её действия в одну строку над существами (§9). */
#shopZone > h2 {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.shopTitle {
    white-space: nowrap;
}

/* Уровень таверны — та же плитка, что была в панели героя: цифра и значок короны (§9). */
.shopTier {
    color: var(--gold2);
    border-color: rgba(217, 177, 104, 0.5);
}

.shopTier b {
    font-size: 14px;
}

/* Золото раунда — в заголовке таверны, рядом с её существом и кнопками: там, где его тратят (§9). */
.shopGold {
    color: var(--goldCoin);
    border-color: rgba(240, 195, 90, 0.45);
    font-variant-numeric: tabular-nums;
}

.shopGold .icon {
    color: var(--goldCoin);
}

.shopGold b {
    font-size: 14px;
}

/* Действия таверны: обновить, заморозить, поднять уровень — рядом с её существами (§9). */
.shopControls {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin-left: auto;
}

.shopControls button,
.shopAction {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 30px;
    padding: 4px 10px;
    border-radius: 9px;
    border: 1px solid rgba(217, 177, 104, 0.5);
    background: linear-gradient(180deg, rgba(217, 177, 104, 0.22), rgba(217, 177, 104, 0.08));
    color: var(--gold2);
    font: inherit;
    font-size: 12.5px;
    cursor: pointer;
}

.shopControls button:hover:not(:disabled),
.shopAction:hover:not(:disabled) {
    border-color: var(--gold);
}

.shopControls button:disabled,
.shopAction:disabled {
    color: var(--muted);
    border-color: var(--line);
    background: rgba(8, 11, 20, 0.5);
    cursor: default;
}

.shopControls .actIcon .icon,
.shopAction .actIcon .icon {
    width: 14px;
    height: 14px;
}

/* Подсказка таверны («обновление — кнопкой…») на широком экране уходит вправо под заголовком (§9). */
#shopZone > h2 .hint {
    width: 100%;
    margin-top: -2px;
}

/* --- бой: усиления и ключевые слова видны на карте (§18, §51) ------------ */

/* Прибавка от усиления в бою: тот же зелёный чип, что на карте отряда в подготовке (§13, §18). */
.battleCard .battleBonus {
    position: absolute;
    top: 4px;
    left: 4px;
    z-index: 3;
    padding: 1px 5px;
    border-radius: 999px;
    font-size: 10px;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: #d8ffd0;
    background: rgba(20, 60, 30, 0.9);
    border: 1px solid rgba(168, 233, 168, 0.8);
    pointer-events: none;
}

/* Ключевые слова в бою: значок на рисунке карты, а не строка и не буква поверх характеристик (§18, §51). */
.battleCard .kwMarks {
    position: absolute;
    top: 5px;
    right: 5px;
    display: flex;
    flex-direction: column;
    gap: 3px;
    z-index: 3;
    pointer-events: none;
}

.battleCard .kwMark {
    width: 19px;
    height: 19px;
}

.battleCard .kwMark .icon {
    width: 12px;
    height: 12px;
}

/* Щит, провокация, неуловимость и защита от магии меняют саму карту в бою, как и на столе (§10, §18). */
.battleCard.kw-shield {
    box-shadow: 0 0 0 1px rgba(140, 200, 255, 0.5), 0 0 18px rgba(90, 160, 255, 0.35);
}

.battleCard.kw-taunt {
    box-shadow: inset 0 0 0 3px rgba(217, 177, 104, 0.45);
}

.battleCard.kw-elusive {
    box-shadow: 0 0 0 1px rgba(180, 139, 255, 0.55), 0 0 18px rgba(150, 110, 255, 0.3);
}

.battleCard.kw-immunemagic {
    box-shadow: 0 0 0 1px rgba(120, 220, 210, 0.6);
}

/* Усиленный боец: зелёная волна по карте, как у карты отряда после усиления (§13, §18). */
.battleCard.buffed {
    animation: cardBuff 0.9s ease;
}

