
  :root {
    --bg: #0e1117;
    --panel: #161b24;
    --gold: #fbbf24;
    --mine: #4ade80;
    --enemy: #f87171;
    --text: #e5e7eb;
    --muted: #6b7280;
    /* Отступ сверху под "шторку"/системную область телефона в полноэкранном
       режиме Telegram. Значение подставляется из JS через событие
       safeAreaChanged (см. 04-telegram-init.js); 0px — дефолт вне fullscreen. */
    --tg-safe-area-top: 0px;
  }
  * { box-sizing: border-box; }
  * {
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    /* На iOS одного user-select мало — долгое нажатие всё равно показывает
       системное меню "Copy/Look Up" (callout), из-за которого и "выделяется"
       текст при удержании кнопки атаки. Эти два свойства убирают именно его. */
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
  }
  /* Отдельно — тайлы карты (Leaflet рисует их через <img>) и вообще любые
     img/svg. iOS показывает "лупу"-выделение (двойной тап и удержание) именно
     на изображениях независимо от user-select, а .leaflet-tile может иметь
     собственные встроенные стили Leaflet с более высокой специфичностью —
     поэтому здесь дублируем с !important, чтобы точно перебить их. */
  img, svg, .leaflet-tile, .leaflet-image-layer {
    -webkit-touch-callout: none !important;
    -webkit-user-select: none !important;
    -webkit-user-drag: none !important;
    user-select: none !important;
  }
  html, body {
    margin: 0; padding: 0; height: 100%;
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    overflow: hidden;
    touch-action: manipulation; /* убирает системный zoom-"лупу" по двойному тапу/долгому нажатию мимо карты */
  }
  #map { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: var(--bg); touch-action: pan-x pan-y; }

  /* Диагностическая панель (17-perf.js). Временный инструмент — вместе с ним
     удаляется и этот блок. pointer-events:none, чтобы не мешала играть. */
  #perf-box {
    position: fixed;
    left: 8px; bottom: 8px;
    z-index: 99999;
    padding: 6px 9px;
    font: 600 11px/1.5 ui-monospace, Menlo, Consolas, monospace;
    color: #a7f3d0;
    background: rgba(2, 6, 23, 0.88);
    border: 1px solid rgba(16, 185, 129, 0.45);
    border-radius: 8px;
    white-space: nowrap;
    pointer-events: none;
  }
  #perf-box b { color: #fff; font-size: 13px; }

  /* ===== ДЕШЁВЫЙ РЕЖИМ НА ВРЕМЯ ЖЕСТА =====
     Класс map-gesturing висит на контейнере карты, пока идёт ЛЮБОЙ жест —
     зум или перетаскивание (см. setMapGestureBusy в 06-map-init.js).

     Пока палец на экране, тени и анимации никто разглядывать не будет, а
     платить за них приходится каждый кадр: drop-shadow заставляет браузер
     заново растеризовать элемент при любой перерисовке. Умножаем на число
     маркеров на экране — и получаем просадку кадров. Возвращается всё через
     ~140 мс после жеста, переход не заметен.

     !important здесь оправдан: правило должно перебивать все частные стили
     маркеров разом, не перечисляя их поимённо. */
  /* ===== ДЕШЁВЫЙ РЕЖИМ НА ВРЕМЯ ЖЕСТА — УДАЛЁН =====
     Здесь снимались filter и box-shadow со всех маркеров на время жеста. Смысл
     был, пока в DOM жили шестьсот построек и каждый drop-shadow приходилось
     растеризовать заново каждый кадр.
     Постройки уехали на канвас, и DOM-маркерами остались только монументы —
     единицы штук. Экономии больше нет, а вред есть: вместе с фильтром пропадало
     свечение Разлома и горящего монумента, то есть при каждом зуме и
     перетаскивании они заметно тускнели.
     Класс map-gesturing остаётся — он по-прежнему поднимает панель карты в
     отдельный слой (will-change) и глушит пересборку маркеров. */

  /* ===== ПОСТОЯННЫЙ РАЗМЕР ИКОНОК ПРИ ЗУМЕ =====
     Во время зума панель карты масштабируется целиком — иначе пришлось бы
     пересчитывать позицию каждого маркера каждый кадр, а это и есть источник
     лагов. Но вместе с позициями растут и сами иконки, чего быть не должно:
     постройка обязана сохранять свой размер на экране при любом масштабе.

     Гасим встречным масштабом 1/s. Переменную --inv-zoom пишет JS ОДИН раз за
     кадр на контейнер, дальше браузер применяет трансформ ко всем иконкам сам —
     без JS, без пересчёта раскладки, без проекций координат.

     Масштабируем СОДЕРЖИМОЕ маркера, а не сам маркер: на маркере лежит
     инлайновый transform от Leaflet (позиция), перебивать его нельзя.
     transform-origin у каждой иконки свой — его проставляет
     prepMarkerCounterScale по якорю, один раз на старте жеста. */
  #map.map-icon-scale .leaflet-marker-icon > * {
    transform: scale(var(--inv-zoom, 1));
  }
  .leaflet-container { background: var(--bg); }
  /* Фильтр раньше висел на .leaflet-tile-pane целиком — при зуме этот контейнер
     целиком трансформируется CSS-transform'ом, и браузеру приходилось пересчитывать
     brightness/contrast по всей запечённой панели на каждый кадр анимации зума
     (главная причина лагов при приближении/отдалении). Применяем фильтр к каждому
     тайлу отдельно — браузер кеширует уже отфильтрованную картинку тайла один раз
     при загрузке, а сам зум остаётся дешёвой трансформацией готовых растров. */
  .leaflet-tile { filter: brightness(1.6) contrast(0.9); }
  /* Leaflet временно скрывает overlay-pane (гекс-сетку) классом leaflet-zoom-hide
     при резких/быстрых зумах — держим её видимой, чтобы не мигала. */
  .leaflet-overlay-pane.leaflet-zoom-hide {
    visibility: visible !important;
    opacity: 1 !important;
  }

  /* ===== ЭКРАН ВХОДА =====
     Одна разметка на три состояния (#boot-loader в index.html): загрузка →
     .ready (приветствие и кнопка) → .working (ждём GPS). Экран живёт от первой
     секунды до появления игрока на карте, поэтому он и не «прелоадер»: это
     первое, что человек видит в игре, и последнее, что видит до неё.

     Три зоны по высоте: название/приветствие сверху, загрузка по центру,
     кнопка внизу — под большим пальцем, чтобы не перехватывать телефон. */
  #boot-loader {
    position: fixed;
    inset: 0;
    background: var(--bg);
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 0 22px calc(24px + env(safe-area-inset-bottom, 0px));
  }

  /* Верхняя зона. Название и приветствие лежат ДРУГ НА ДРУГЕ (оба absolute),
     потому что одно сменяет другое: в потоке подмена дёргала бы раскладку. */
  #boot-top {
    position: relative;
    width: 100%;
    height: 240px;
    /* Опущено к центру: и название, и приветствие — это обращение к игроку, а
       не шапка страницы. Прижатые к верхней кромке, они читались как заголовок
       экрана. */
    margin-top: 22vh;
    flex: none;
  }
  #boot-title, #boot-welcome {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
  }

  /* Название: проявляется само, как только экран показан. */
  #boot-title {
    font-size: 40px;
    font-weight: 800;
    letter-spacing: 0.5px;
    background: linear-gradient(180deg, #ffffff 0%, #9fe6c4 100%);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    opacity: 0;
    animation: boot-title-in 0.9s ease 0.15s forwards;
    transition: opacity 0.45s ease, transform 0.45s ease;
  }
  /* Всё загружено — название уходит вверх и растворяется. */
  #boot-loader.ready #boot-title {
    animation: none;
    opacity: 0;
    transform: translateY(-14px);
  }

  /* Приветствие проявляется НА МЕСТЕ названия, с задержкой — чтобы не
     пересекаться с его уходом. */
  #boot-welcome {
    opacity: 0;
    transform: translateY(10px);
    transition: opacity 0.5s ease 0.4s, transform 0.5s ease 0.4s;
    pointer-events: none;
  }
  #boot-loader.ready #boot-welcome { opacity: 1; transform: none; }

  /* Иконка игрока — БЕЗ подложки и рамки. Кружок под ней добавлял смысла ровно
     ноль: иконка и так эмодзи с собственным силуэтом, а обводка вокруг читалась
     как отдельный элемент интерфейса. Взамен иконка стала крупнее — на экране
     входа это главный объект, а не аватарка в углу профиля. */
  #boot-avatar {
    font-size: 88px;
    line-height: 1;
  }
  #boot-greeting {
    font-size: 23px;
    font-weight: 800;
    line-height: 1.25;
    text-align: center;
    /* Приветствие приходит с переводом строки внутри («С возвращением,\nИмя!») —
       переносим по нему, а не по ширине: иначе имя отрывается от восклицания. */
    white-space: pre-line;
  }

  /* Загрузка — по центру экрана, между приветствием и кнопкой. */
  #boot-middle {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
  }

  /* Полоска прогресса. Доля РЕАЛЬНО загруженных файлов (см. bootProgress),
     а не бегущая анимация: когда загрузка встаёт, полоска встаёт вместе с ней,
     и это честнее «вечно ползущей». */
  #boot-progress {
    width: min(260px, 70vw);
    height: 6px;
    border-radius: 999px;
    background: rgba(255,255,255,0.10);
    overflow: hidden;
  }
  #boot-progress-fill {
    width: 0%;
    height: 100%;
    border-radius: 999px;
    background: var(--mine);
    transition: width 0.18s ease;
  }
  /* Ждём GPS — доли тут уже нет, поэтому полоска переходит в «работаю». */
  #boot-loader.working #boot-progress-fill {
    animation: boot-bar-pulse 1.1s ease-in-out infinite;
  }
  @keyframes boot-bar-pulse { 50% { opacity: 0.45; } }

  #boot-text { font-size: 13px; color: var(--muted); }

  /* Место под кнопку занято ВСЕГДА, ещё на загрузке. Раньше блок появлялся
     через display: none → flex, и в этот момент вся раскладка пересчитывалась:
     полоска прогресса с подписью прыгали вверх ровно тогда, когда игрок на них
     и смотрел. Теперь меняется только прозрачность — геометрия неподвижна.
     visibility: hidden (а не одна opacity) обязательна: по невидимой кнопке
     иначе можно нажать до того, как игра готова. */
  #boot-bottom {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    flex: none;
    opacity: 0;
    visibility: hidden;
    transform: translateY(8px);
    transition: opacity 0.5s ease 0.4s, transform 0.5s ease 0.4s;
  }
  #boot-loader.ready #boot-bottom { opacity: 1; visibility: visible; transform: none; }

  #boot-play {
    width: min(420px, 100%);
    padding: 17px 24px;
    border: none;
    border-radius: 18px;
    background: var(--mine);
    color: #08131f;
    font-size: 18px;
    font-weight: 800;
    cursor: pointer;
    box-shadow: 0 8px 24px rgba(0,0,0,0.45);
  }
  #boot-play:active { transform: scale(0.985); }
  #boot-play:disabled { opacity: 0.65; cursor: default; }
  #boot-note {
    font-size: 12px;
    line-height: 1.4;
    color: var(--muted);
    text-align: center;
  }
  @keyframes boot-title-in {
    from { opacity: 0; transform: translateY(12px) scale(0.97); }
    to   { opacity: 1; transform: none; }
  }


  #hud-top {
    position: absolute;
    top: calc(12px + var(--tg-safe-area-top)); left: 12px; right: 12px;
    display: flex; justify-content: space-between; align-items: flex-start;
    z-index: 1000;
    pointer-events: none;
  }
  .pill {
    background: rgba(22,27,36,0.92);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 999px;
    padding: 8px 14px;
    font-size: 13px;
    font-weight: 600;
    backdrop-filter: blur(6px);
    pointer-events: auto;
  }
  #status-pill { color: var(--muted); }
  #status-pill.online { color: var(--mine); }
  #status-pill.offline { color: var(--enemy); }
  /* Алмазы в плашке на карте. display:inline (а не block, как было у дохода
     в час) — цифра должна стоять В СТРОКУ с золотом, а не под ним. */
  #pill-diamonds { font-size: 13px; color: #7fd4ff; margin-left: 6px; white-space: nowrap; }

  /* Вертикальная полупрозрачная капсула-меню у правой стенки экрана,
     посередине по высоте. Пока без функционала — applied позже. */
  #side-menu {
    position: absolute;
    /* Чуть ниже шапки с балансом (#hud-top: top ~12px + её собственная высота),
       а не по центру экрана и не в самом низу. */
    top: calc(64px + var(--tg-safe-area-top));
    right: 4px;
    z-index: 900;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    background: rgba(22,27,36,0.75);
    backdrop-filter: blur(8px);
    border: 1px solid rgba(255,255,255,0.06);
    border-radius: 999px;
    padding: 12px 6px;
  }
  .side-menu-btn {
    width: 42px;
    height: 42px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    line-height: 1;
    background: none;
    border-radius: 50%;
    border: none;
    color: var(--text);
    position: relative;
    padding: 0;
    /* Быстрый, дешёвый (только background-color — без пересчёта layout)
       отклик на нажатие, чтобы было явно видно, какую именно иконку жмёшь.
       Раньше тут был ещё transform:scale — убрано: кнопки/разделители между
       ними должны висеть статично, не менять размер при нажатии. */
    transition: background-color 0.1s ease;
  }
  .side-menu-btn:active {
    background: rgba(255,255,255,0.16);
  }
  .side-menu-btn:not(:last-child)::after {
    content: '';
    position: absolute;
    bottom: -5px;
    left: 22%;
    right: 22%;
    height: 1px;
    background: rgba(255,255,255,0.1);
  }

  /* меню постройки на выбранном гексе */
  /* Меню постройки на выбранном гексе. Во всю ширину, как остальные карточки.
     ВАЖНО: раз ширина полная и левый край в нуле, сдвиг translateX(-50%) в
     .show обязан исчезнуть — иначе шторка уезжает влево на половину экрана.
     Именно так она однажды и уехала. */
  #build-menu {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    width: 100%;
    transform: translateY(100%);
    background: var(--panel);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 16px 16px 0 0;
    padding: 12px 14px calc(12px + env(safe-area-inset-bottom, 0px));
    z-index: 1000;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s, transform 0.2s;
  }
  #build-menu.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
  #manage-menu, #enemy-menu {
    position: absolute;
    /* Во всю ширину, как карточка монумента и осады. Узкая колонка по центру
       заставляла всё содержимое расти вниз: строки-факты жались в столбик, а
       кнопки — в мелкую сетку. По ширине места полно, и оно бесплатное. */
    bottom: 0; left: 0; right: 0;
    width: 100%;
    transform: translateY(100%);
    background: var(--panel);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 16px 16px 0 0;
    padding: 12px 14px calc(12px + env(safe-area-inset-bottom, 0px));
    z-index: 1000;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s, transform 0.2s;
  }
  #manage-menu.show, #enemy-menu.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
  /* ===== Общий язык карточек =====
     Меню построек, ниндзя, мимика и ящика говорят на том же языке, что и
     карточка монумента: тёмная шапка с названием, строки-факты блоками, одно
     крупное действие внизу. Разметка у них своя и историческая (фиксированные
     id, .hint / .manage-stats / .manage-btn), поэтому приводим их СТИЛЯМИ, а не
     переписыванием: ни один обработчик и ни один id не тронут, а значит нечему
     и сломаться.

     Шапка — из .hint. */
  #manage-menu .hint,
  #enemy-menu .hint,
  #mimic-menu .hint,
  #ninja-menu .hint,
  #chest-menu .hint {
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    padding: 10px 12px;
    margin-bottom: 8px;
    border-radius: 12px;
    background: rgba(56, 189, 248, 0.10);
    border: 1px solid rgba(56, 189, 248, 0.20);
    font-size: 15px;
    font-weight: 700;
    color: #f1f5f9;
    text-align: left;
    /* Длинное название обрезается, а не распирает шторку. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Строки-факты. Каждый span в .manage-stats становится отдельным блоком —
     как .mon-row у монумента. Раньше они шли в одну строку через запятую и
     сливались в кашу. */
  .manage-stats {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    margin-bottom: 10px;
    font-size: 13px;
    font-weight: 600;
  }
  .manage-stats span {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 11px;
    border-radius: 11px;
    background: rgba(148, 163, 184, 0.09);
    color: #cbd5e1;
    white-space: normal;
  }

  /* Кнопки. Одна в ряду — во всю ширину, как .mon-action; две и больше —
     остаются сеткой, но с тем же скруглением и размером текста. */
  .manage-grid { gap: 6px; margin-bottom: 6px; }
  .manage-btn {
    border-radius: 12px;
    padding: 11px 8px;
    background: rgba(148, 163, 184, 0.10);
    border: 1px solid rgba(148, 163, 184, 0.16);
  }
  .manage-btn .label { font-size: 13px; }
  .manage-btn .desc { font-size: 10px; }
  .manage-btn .price { font-size: 12px; }
  /* Атака — тот же красный градиент, что у кнопки осады: это одно и то же
     действие, и выглядеть оно должно одинаково. */
  .manage-btn.sell {
    background: linear-gradient(135deg, #ef4444, #b91c1c);
    border: none;
  }
  .manage-btn.sell .label, .manage-btn.sell .price, .manage-btn.sell .desc { color: #fff; }
  .manage-btn.capture {
    background: linear-gradient(135deg, #22c55e, #15803d);
    border: none;
  }
  .manage-btn.capture .label, .manage-btn.capture .desc { color: #fff; }

  /* Вид .manage-stats задан выше, в общем блоке «язык карточек»: строки-факты
     идут блоками друг под другом, как у монумента. */
  .manage-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 5px;
    margin-bottom: 4px;
  }
  .manage-repair-btn {
    display: none; /* показывается через JS, когда HP постройки не 100% */
    background: rgba(74,222,128,0.12);
    border: 1px solid rgba(74,222,128,0.4);
    border-radius: 8px;
    /* Высота подогнана так, чтобы совпадать с гридом из 4 кнопок улучшения —
     * когда кнопка восстановления заменяет собой грид, меню не должно менять
     * общую высоту (те же габариты, что у застройки/улучшения/чужой постройки). */
    padding: 30px 6px;
    margin-bottom: 6px;
    cursor: pointer;
    text-align: center;
  }
  .manage-repair-btn:active { background: rgba(74,222,128,0.22); }
  .manage-repair-btn .label { font-weight: 700; font-size: 14px; color: var(--mine); }
  /* Базовый вид кнопки задан выше, в общем блоке «язык карточек». Здесь
     остаётся только то, что от него не зависит. */
  .manage-btn { cursor: pointer; text-align: center; }
  .manage-btn:active { transform: scale(0.98); }
  .manage-btn .label { font-weight: 700; }
  .manage-btn .desc { color: var(--muted); margin: 1px 0; }
  .manage-btn .price { color: var(--gold); font-weight: 700; }
  .manage-btn.disabled {
    opacity: 0.4;
    pointer-events: none;
    cursor: default;
  }

  /* Обёртка сама невидима (без фона/радиуса) — просто ставит пилюлю-баланс и
     карточку ресурсов друг под другом. Ключевая идея фикса "овала": форма
     пилюли (border-radius: 999px, см. .pill) теперь НИКОГДА не меняется —
     раньше именно она "доезжала" от 999px до 16px одновременно с ростом
     высоты, и на невысоком блоке большой радиус визуально клэмпился в
     форму таблетки/овала. Карточка ресурсов — отдельный элемент с СРАЗУ
     фиксированным небольшим радиусом (16px), у неё анимируется только высота/
     прозрачность, самому радиусу анимировать вообще нечего. */
  #gold-pill-wrap {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  #gold-pill { color: var(--gold); }
  #gold-pill-summary {
    display: flex;
    align-items: center;
    gap: 4px;
    cursor: pointer;
  }
  #resources-toggle {
    margin-left: 4px;
    width: 18px; height: 18px;
    border-radius: 50%;
    background: rgba(255,255,255,0.08);
    display: flex; align-items: center; justify-content: center;
    font-size: 10px;
    color: var(--text);
    transition: transform 0.2s ease;
    flex: none;
  }
  #gold-pill-wrap.expanded #resources-toggle { transform: rotate(180deg); }
  #resources-expanded {
    background: rgba(22,27,36,0.92);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 16px; /* фиксированный — никогда не анимируется */
    backdrop-filter: blur(6px);
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 0 14px;
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    transition: max-height 0.25s ease, opacity 0.2s ease, padding 0.25s ease;
  }
  #gold-pill-wrap.expanded #resources-expanded {
    max-height: 160px; /* с запасом под три строки ресурсов */
    opacity: 1;
    padding: 10px 14px;
  }
  .resource-row {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
    white-space: nowrap;
  }
  .resource-row .emoji { font-size: 15px; line-height: 1; }
  .resource-rate { font-size: 11px; color: var(--mine); font-weight: 600; margin-left: auto; }

  /* Меню чужой постройки — та же ширина, что у остальных; заголовок/владелец
     выровнены влево, кнопка атаки крупнее для комфортного нажатия. */
  #enemy-menu {
    padding-top: 18px;
    padding-bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  }
  /* Вид шапки задан выше, в общем блоке «язык карточек», включая отступ под
     кнопку закрытия. Здесь оставлять нечего — своё правило перебивало бы общее
     (оно ниже по файлу), и кнопка снова наезжала бы на название. */
  #enemy-menu .manage-stats { margin-bottom: 10px; }

  /* ===== Своя постройка: факты в одну строку =====
     Добыча слева, прочность справа — вместо двух блоков друг под другом. Это
     две цифры по три символа, и занимать ими две строки на всю ширину экрана
     расточительно: меню становилось высоким на ровном месте и перекрывало
     карту, по которой игрок как раз и выбирает следующую цель. */
  #manage-menu .manage-stats {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 10px;
  }
  /* «Кто поджёг» — на свою строку целиком: он появляется редко, текст длинный,
     и втискивать его третьим в ряд с добычей и прочностью значит сжать все
     три до нечитаемого. */
  #manage-menu #manage-burned-by,
  #enemy-menu #enemy-burned-by { flex-basis: 100%; justify-content: flex-start; }
  #manage-menu .manage-stats span {
    flex: 1;
    min-width: 0;
    justify-content: center;
    /* Длинные значения обрезаем, а не переносим: перенос вернул бы ту самую
       высоту, ради которой всё и затевалось. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Кнопки улучшения тоже становятся ниже: на всю ширину им хватает
     компактной высоты, а раньше padding компенсировал узость колонки. */
  #manage-menu .manage-btn { padding: 9px 8px; }
  /* Строка-описание в гриде управления больше не используется: целевой уровень
     и доля возврата переехали в заголовок кнопки. Прячем её здесь, а не
     вычищаем из разметки, — id остаются на месте, ломать нечего. */
  #manage-actions-grid .manage-btn .desc { display: none; }

  /* Кнопка закрытия — В УГЛУ ШАПКИ, а не над ней. Раньше она висела в углу
     самой шторки и читалась как отдельный элемент, оторванный от карточки;
     теперь садится в правый край блока с названием. Отступ справа у шапки
     нужен, чтобы длинное название не заезжало под кнопку. */
  #manage-menu .menu-close-btn,
  #enemy-menu .menu-close-btn,
  #mimic-menu .menu-close-btn,
  #ninja-menu .menu-close-btn,
  #chest-menu .menu-close-btn {
    top: 18px;
    right: 20px;
    width: 24px;
    height: 24px;
    background: rgba(255, 255, 255, 0.10);
  }
  #manage-menu .hint,
  #enemy-menu .hint,
  #mimic-menu .hint,
  #ninja-menu .hint,
  #chest-menu .hint { padding-right: 42px; }
  #manage-menu .manage-repair-btn { padding: 14px 8px; }
  #enemy-attack-btn {
    padding: 16px 10px;
    /* Браузер не должен считать касание кнопки началом скролла/пинча: иначе при
       касании экрана вторым пальцем он отбирает указатель у кнопки
       (pointercancel), и зажатая атака обрывается. */
    touch-action: none;
  }
  #enemy-attack-btn .label { font-size: 13px; }
  .menu-close-btn {
    position: absolute;
    top: 8px; right: 8px;
    width: 26px; height: 26px;
    border-radius: 50%;
    background: rgba(255,255,255,0.08);
    border: none;
    color: var(--text);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
    z-index: 1;
  }
  .menu-close-btn:active { background: rgba(255,255,255,0.18); }
  /* Шапка меню застройки — та же, что у остальных карточек (общий блок «язык
     карточек» выше). Здесь только отступ под кнопку закрытия, которая теперь
     сидит в её правом углу. */
  #build-menu .hint {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    padding-right: 42px;
    margin-bottom: 8px;
    border-radius: 12px;
    background: rgba(56, 189, 248, 0.10);
    border: 1px solid rgba(56, 189, 248, 0.20);
    font-size: 15px;
    font-weight: 700;
    color: #f1f5f9;
    text-align: left;
  }
  #build-menu .menu-close-btn {
    top: 18px;
    right: 20px;
    width: 24px;
    height: 24px;
    background: rgba(255, 255, 255, 0.10);
  }
  /* Вариант постройки — тот же блок, что строка-факт и кнопка в других
     карточках: скругление 12, тёмная подложка, крупная цена справа. Раньше он
     был мельче и бледнее всего остального, из-за чего меню застройки
     выбивалось из общего вида. */
  .build-option {
    display: flex; justify-content: space-between; align-items: center;
    gap: 10px;
    background: rgba(148, 163, 184, 0.10);
    border: 1px solid rgba(148, 163, 184, 0.16);
    border-radius: 12px;
    padding: 12px 14px;
    margin-bottom: 8px;
    cursor: pointer;
    transition: background 0.15s;
  }
  .build-option:active { background: rgba(148, 163, 184, 0.20); transform: scale(0.99); }
  .build-option .label { font-weight: 700; font-size: 15px; color: #f1f5f9; }
  .build-option .desc { font-size: 12px; color: #94a3b8; margin-top: 2px; }
  .build-option .price {
    color: var(--gold); font-weight: 700; font-size: 15px;
    font-variant-numeric: tabular-nums;
    flex: none;
  }

  #toast {
    position: absolute;
    top: calc(70px + var(--tg-safe-area-top)); left: 50%;
    transform: translateX(-50%) translateY(-10px);
    background: var(--panel);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 10px;
    padding: 8px 16px;
    font-size: 13px;
    z-index: 1100;
    opacity: 0;
    transition: opacity 0.2s, transform 0.2s;
    pointer-events: none;
  }
  #toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

  .me-marker {
    width: 38px; height: 38px;
    display: flex; align-items: center; justify-content: center;
    font-size: 30px;
    line-height: 1;
    pointer-events: none; /* клики должны проходить сквозь свой юзерпик к постройке под ним */
  }
  .player-marker {
    width: 34px; height: 34px;
    display: flex; align-items: center; justify-content: center;
    font-size: 26px;
    line-height: 1;
  }
  .building-label {
    font-size: 16px;
    text-shadow: 0 0 3px rgba(0,0,0,0.8);
    text-align: center;
  }
  .building-label-selected-mine {
    outline: 2px solid #4ade80;
    outline-offset: 2px;
    border-radius: 8px;
    box-shadow: 0 0 6px rgba(74,222,128,0.7);
  }
  .building-label-selected-enemy {
    outline: 2px solid #f87171;
    outline-offset: 2px;
    border-radius: 8px;
    box-shadow: 0 0 6px rgba(248,113,113,0.7);
  }
  .building-marker-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    height: 100%;
    gap: 1px;
    position: relative;
    overflow: visible;
    pointer-events: none; /* клики обрабатываются геометрическим hit-test'ом, не самой иконкой */
  }
  .building-fly-icon {
    font-size: 28px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    filter: drop-shadow(0 3px 4px rgba(0,0,0,0.6));
    /* will-change: transform здесь БЫЛ и был вреден. Он поднимает КАЖДУЮ
       постройку в отдельный слой композитора: на пяти элементах это ускоряет,
       на пятидесяти — само количество слоёв становится расходом (память +
       время склейки кадра). Leaflet и так двигает маркеры трансформом, браузер
       разберётся сам. */
  }
  .building-gold-pill {
    font-size: 10px;
    font-weight: 700;
    color: var(--gold);
    background: rgba(0,0,0,0.65);
    border-radius: 6px;
    padding: 1px 5px;
    white-space: nowrap;
  }
  .building-gold-pill.gold-pill-hidden { opacity: 0; }
  .building-fire {
    position: absolute;
    left: 50%; bottom: 0;
    transform: translate(-50%, 10%);
    font-size: 20px;
    line-height: 1;
    filter: drop-shadow(0 0 3px rgba(0,0,0,0.9));
    pointer-events: none;
  }
  .building-burn-timer {
    /* Раньше таймер был position:absolute и цеплялся к ВЕРХУ всей карточки
       маркера (top:-4px + translateY(-100%)), то есть висел выше огня, плашки
       золота и самой иконки — отсюда и ощущение, что он улетел куда-то в небо.
       Теперь он обычный элемент потока: карточка выкладывает содержимое снизу
       вверх (flex-column + justify-end), поэтому таймер сам встаёт вплотную над
       постройкой, сразу за огнём. */
    font-size: 10px;
    font-weight: 700;
    color: #fca5a5;
    background: rgba(0,0,0,0.7);
    border-radius: 6px;
    padding: 1px 5px;
    white-space: nowrap;
    pointer-events: none;
  }
  .building-hp-bar-track {
    width: 28px;
    height: 4px;
    background: rgba(0,0,0,0.55);
    border-radius: 3px;
    overflow: hidden;
    margin-top: 2px;
  }
  .building-hp-bar-track.hp-bar-hidden { opacity: 0; }
  .building-hp-bar-fill {
    height: 100%;
    background: var(--mine);
    border-radius: 3px;
  }
  .collect-popup {
    font-size: 14px;
    font-weight: 800;
    color: var(--gold);
    text-shadow: 0 0 4px rgba(0,0,0,0.9), 0 0 2px rgba(0,0,0,0.9);
    white-space: nowrap;
    animation: collect-float 1.3s ease-out forwards;
    pointer-events: none;
  }
  .xp-popup {
    font-size: 14px;
    font-weight: 800;
    color: #60a5fa;
    text-shadow: 0 0 4px rgba(0,0,0,0.9), 0 0 2px rgba(0,0,0,0.9);
    white-space: nowrap;
    animation: collect-float 1.3s ease-out forwards;
    pointer-events: none;
  }
  /* ====== ЛЕВЕЛ-АП: конфетти + цифра уровня ====== */
  .levelup-effect {
    position: relative;
    width: 140px;
    height: 90px;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    pointer-events: none;
  }
  .levelup-confetti {
    position: absolute;
    left: 50%;
    bottom: 38px; /* примерно на уровне юзерпика под эффектом */
    width: 0;
    height: 0;
  }
  .levelup-confetti-particle {
    position: absolute;
    left: 0;
    top: 0;
    width: 6px;
    height: 6px;
    border-radius: 1px;
    opacity: 0;
    animation: levelup-confetti-fly 0.9s ease-out forwards;
    animation-delay: var(--delay, 0s);
  }
  /* Разные цвета по кругу — золото/жёлтый/оранжевый, праздничная гамма */
  .levelup-confetti-particle:nth-child(3n) { background: #fbbf24; }
  .levelup-confetti-particle:nth-child(3n+1) { background: #fde68a; }
  .levelup-confetti-particle:nth-child(3n+2) { background: #f97316; }
  /* --tx/--ty — конечная точка разлёта в px, посчитана заранее в JS
     (см. spawnLevelUpEffect) — каждая частица летит в свою сторону. */
  @keyframes levelup-confetti-fly {
    0%   { opacity: 1; transform: translate(0, 0) rotate(0deg); }
    100% { opacity: 0; transform: translate(var(--tx), var(--ty)) rotate(360deg); }
  }
  .levelup-number {
    font-size: 15px;
    font-weight: 900;
    color: var(--gold);
    text-shadow: 0 0 6px rgba(0,0,0,0.95), 0 0 3px rgba(0,0,0,0.95), 0 1px 0 rgba(0,0,0,0.6);
    white-space: nowrap;
    text-align: center;
    animation: levelup-number-pop 1.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
    pointer-events: none;
  }
  /* Появляется с "перелётом" (растёт чуть больше нормы, потом садится),
     трясётся из стороны в сторону пару раз, задерживается читаемой и
     нормального размера, и только потом уплывает вверх и тает. */
  @keyframes levelup-number-pop {
    0%   { opacity: 0; transform: scale(0.4) translateY(10px); }
    15%  { opacity: 1; transform: scale(1.15) translateY(-6px); }
    22%  { transform: scale(0.95) translateY(-6px) rotate(-4deg); }
    28%  { transform: scale(1.05) translateY(-6px) rotate(4deg); }
    34%  { transform: scale(0.98) translateY(-6px) rotate(-2deg); }
    40%  { transform: scale(1) translateY(-6px) rotate(0deg); }
    80%  { opacity: 1; transform: scale(1) translateY(-10px); }
    100% { opacity: 0; transform: scale(1) translateY(-16px); }
  }
  .attack-shot {
    font-size: 20px;
    filter: drop-shadow(0 0 3px rgba(0,0,0,0.6));
    animation: attack-spin 0.4s linear;
    pointer-events: none;
  }
  @keyframes attack-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
  .damage-popup {
    font-size: 14px;
    font-weight: 800;
    color: var(--enemy);
    text-shadow: 0 0 4px rgba(0,0,0,0.9), 0 0 2px rgba(0,0,0,0.9);
    white-space: nowrap;
    text-align: center;
    animation: damage-float 1.1s ease-out forwards;
    pointer-events: none;
  }
  /* --dx — случайное горизонтальное смещение конечной точки (в px), задаётся
     инлайново из JS отдельно для каждого попапа (см. spawnDamagePopup) — так
     цифры урона каждый раз отлетают в свою сторону, а не строго вверх. */
  @keyframes damage-float {
    0%   { opacity: 0; transform: translate(0, 6px) scale(0.85); }
    15%  { opacity: 1; transform: translate(calc(var(--dx) * 0.2), 0) scale(1); }
    100% { opacity: 0; transform: translate(var(--dx), -44px) scale(1); }
  }
  @keyframes collect-float {
    0%   { opacity: 0;   transform: translateY(6px) scale(0.85); }
    15%  { opacity: 1;   transform: translateY(0px) scale(1); }
    75%  { opacity: 1;   transform: translateY(-26px) scale(1); }
    100% { opacity: 0;   transform: translateY(-36px) scale(1); }
  }
  #onboarding-overlay {
    position: absolute;
    inset: 0;
    background: rgba(5,7,11,0.85);
    backdrop-filter: blur(4px);
    z-index: 3000;
    display: none;
    align-items: center;
    justify-content: center;
  }
  #onboarding-overlay.show { display: flex; }
  #onboarding-card {
    width: min(320px, 88vw);
    background: var(--panel);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 18px;
    padding: 24px 20px;
    text-align: center;
  }
  #onboarding-icon-circle {
    width: 64px; height: 64px;
    margin: 0 auto 14px;
    border-radius: 50%;
    background: rgba(255,255,255,0.08);
    display: flex; align-items: center; justify-content: center;
    font-size: 32px;
  }
  #onboarding-card h2 {
    font-size: 17px;
    margin: 0 0 18px;
    color: var(--text);
  }
  #onboarding-name {
    width: 100%;
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 10px;
    padding: 11px 12px;
    color: var(--text);
    font-size: 14px;
    margin-bottom: 10px;
    box-sizing: border-box;
    text-align: center;
  }
  #onboarding-name::placeholder { color: var(--muted); }
  #onboarding-pick-icon-btn {
    width: 100%;
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 10px;
    padding: 11px 12px;
    color: var(--text);
    font-size: 14px;
    margin-bottom: 6px;
  }
  #onboarding-pick-icon-btn:active { background: rgba(255,255,255,0.1); }
  #onboarding-error {
    color: var(--enemy);
    font-size: 12px;
    min-height: 16px;
    margin-bottom: 4px;
  }
  #onboarding-confirm-btn {
    width: 100%;
    background: var(--mine);
    color: #07140c;
    border: none;
    border-radius: 10px;
    padding: 12px;
    font-weight: 700;
    font-size: 14px;
    margin-top: 6px;
  }
  #onboarding-confirm-btn:active { opacity: 0.85; }

  /* ===== Вход через Telegram-бота (без Telegram-контекста) =====
     Полноценная НЕПРОЗРАЧНАЯ страница (а не полупрозрачный оверлей поверх
     карты) — карта вообще не должна проглядывать, пока личность не известна. */
  #link-code-overlay {
    position: absolute;
    inset: 0;
    background: var(--bg);
    z-index: 3500;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 20px;
    box-sizing: border-box;
  }
  #link-code-overlay.show { display: flex; }
  #link-code-card {
    width: min(360px, 92vw);
    text-align: center;
  }
  #link-code-logo {
    font-size: 20px;
    font-weight: 800;
    margin-bottom: 18px;
  }
  #link-code-logo span { color: var(--text); }
  #link-code-card > p {
    font-size: 13px;
    color: var(--muted);
    margin: 0 0 18px;
    line-height: 1.4;
  }
  #link-code-value {
    font-size: 36px;
    font-weight: 800;
    letter-spacing: 8px;
    color: var(--text);
    background: var(--panel);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 14px;
    padding: 18px 12px;
    margin-bottom: 14px;
  }
  #link-code-hint {
    font-size: 12px;
    color: var(--muted);
    margin: 10px 0 4px;
    line-height: 1.4;
  }
  #link-code-timer {
    font-size: 12px;
    color: var(--muted);
    margin: 0 0 16px;
  }
  #link-code-overlay button, #link-code-overlay a {
    display: block;
    width: 100%;
    box-sizing: border-box;
    border-radius: 12px;
    padding: 13px 12px;
    font-size: 14px;
    font-weight: 700;
    text-align: center;
    text-decoration: none;
    margin-bottom: 10px;
    border: 1px solid rgba(255,255,255,0.1);
    background: var(--panel);
    color: var(--text);
    cursor: pointer;
  }
  #link-code-overlay button:active, #link-code-overlay a:active { opacity: 0.8; }
  #link-code-open-tg {
    background: #2ea6ff;
    border-color: #2ea6ff;
    color: #06121f;
  }
  #link-code-regenerate {
    background: transparent;
    color: var(--muted);
    border-color: transparent;
  }

  #icon-picker-overlay {
    position: absolute;
    inset: 0;
    background: rgba(5,7,11,0.85);
    backdrop-filter: blur(4px);
    z-index: 3100;
    display: none;
    align-items: center;
    justify-content: center;
  }
  #icon-picker-overlay.show { display: flex; }
  #icon-picker-card {
    width: min(320px, 88vw);
    background: var(--panel);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 18px;
    padding: 20px;
    text-align: center;
  }
  #icon-picker-card .hint { font-size: 14px; font-weight: 600; margin-bottom: 14px; color: var(--text); }
  #icon-picker-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
  }
  .icon-option {
    font-size: 28px;
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 12px;
    padding: 10px 0;
    cursor: pointer;
  }
  .icon-option:active { background: rgba(255,255,255,0.12); }
  #confirm-overlay {
    position: absolute;
    inset: 0;
    z-index: 5800;   /* ВЫШЕ инвентаря (3300), меню предмета (5000) и ящика (5500),
                      но ниже нокаута (6000). Раньше стояло 3200 — то есть ПОД
                      инвентарём, и подтверждение автосплава уезжало под меню
                      автофьюза: вопрос заданный, а ответить нечем. Модальный
                      вопрос обязан быть поверх того, что его вызвало. */
    display: none;
    pointer-events: none;
  }
  #confirm-overlay.show { display: block; }
  #confirm-card {
    position: absolute;
    bottom: 0; left: 50%;
    transform: translateX(-50%) translateY(100%);
    background: var(--panel);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 14px 14px 0 0;
    padding: 14px 16px calc(14px + env(safe-area-inset-bottom, 0px));
    width: min(340px, 92vw);
    text-align: center;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s, transform 0.2s;
  }
  #confirm-overlay.show #confirm-card {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
    pointer-events: auto;
  }
  #confirm-text {
    white-space: pre-line;   /* многострочный текст подтверждения */
    font-size: 13px;
    color: var(--text);
    margin-bottom: 12px;
    line-height: 1.4;
  }
  #confirm-buttons {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }
  #confirm-yes, #confirm-no {
    border: none;
    border-radius: 10px;
    padding: 10px;
    font-weight: 700;
    font-size: 13px;
  }
  #confirm-yes { background: var(--enemy); color: #2a0a0a; }
  #confirm-no { background: rgba(255,255,255,0.08); color: var(--text); }
  #confirm-yes:active, #confirm-no:active { opacity: 0.8; }

  /* ====== ТОП ИГРОКОВ / ЗАДАНИЯ / ДОСТИЖЕНИЯ / НАГРАДЫ ====== */
  #leaderboard-overlay {
    position: absolute;
    inset: 0;
    background: rgba(5,7,11,0.9);
    backdrop-filter: blur(4px);
    z-index: 3300;
    display: none;
    justify-content: center;
    /* Открывается почти во весь экран, но сверху с большим запасом — чтобы
       не залезать под системные кнопки Telegram (закрыть/меню мини-аппы). */
    padding: calc(64px + var(--tg-safe-area-top)) 0 0;
  }
  /* .closing держит оверлей отрисованным ещё чуть-чуть после того, как .show
     снят — иначе #leaderboard-card пропадал бы мгновенно вместе с оверлеем
     (display:none), не успевая доиграть анимацию скрытия (см. closeLeaderboard
     в 11-leaderboard.js). Анимируем только opacity/transform — это
     GPU-композитинг без пересчёта layout на каждый кадр, дёшево по ресурсам. */
  #leaderboard-overlay.show, #leaderboard-overlay.closing, #leaderboard-overlay.opening { display: flex; }
  #leaderboard-card {
    position: relative;
    width: 100%;
    max-width: 480px; /* на узких мобильных экранах это и так 100% ширины */
    height: 100%;
    background: var(--panel);
    border: 1px solid rgba(255,255,255,0.08);
    border-bottom: none;
    border-radius: 20px 20px 0 0; /* снизу упирается в край экрана — там скруглять нечего */
    padding: 16px 16px calc(16px + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    gap: 12px;
    overflow: hidden;
    opacity: 0;
    transform: translateY(60vh); /* стартует у нижнего края экрана */
    transition: opacity 0.28s cubic-bezier(0.16, 1, 0.3, 1),
                transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
  }
  #leaderboard-overlay.show #leaderboard-card {
    opacity: 1;
    transform: translateY(0);
  }
  #leaderboard-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-right: 32px; /* место под .menu-close-btn, который абсолютно спозиционирован в углу карточки */
    flex: none;
  }
  #leaderboard-title { font-size: 20px; font-weight: 700; color: var(--text); }

  /* --- 4 главные вкладки: Задания / Достижения / Награда за уровень / Топ --- */
  #leaderboard-main-tabs {
    display: flex;
    gap: 4px;
    flex: none;
    overflow-x: auto;
  }
  .leaderboard-main-tab {
    flex: 1;
    white-space: nowrap;
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 999px;
    padding: 10px 8px;
    font-size: 20px; /* только эмодзи-иконка, без текста — покрупнее */
    line-height: 1;
    color: var(--muted);
  }
  .leaderboard-main-tab.active {
    background: rgba(251,191,36,0.15);
    border-color: var(--gold);
    color: var(--gold);
  }

  /* --- Панели вкладок --- */
  .leaderboard-panel {
    display: none;
    flex-direction: column;
    gap: 12px;
    min-height: 0; /* чтобы дочерний overflow-y:auto (#leaderboard-body) реально скроллился внутри flex-колонки */
    flex: 1;
  }
  .leaderboard-panel.active { display: flex; }
  .leaderboard-placeholder {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    color: var(--muted);
    font-size: 14px;
    padding: 24px;
  }

  /* Достижения/Награда за уровень — общая вкладка с переключателем внутри */
  #achievements-subtabs {
    display: flex;
    gap: 6px;
    flex: none;
  }
  .achievements-subtab {
    flex: 1;
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 999px;
    padding: 8px 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
  }
  .achievements-subtab.active {
    background: rgba(251,191,36,0.15);
    border-color: var(--gold);
    color: var(--gold);
  }
  .achievements-subpanel {
    display: none;
    flex-direction: column;
    flex: 1;
    min-height: 0;
  }
  .achievements-subpanel.active { display: flex; }

  #leaderboard-tabs, #leaderboard-subtabs {
    display: flex;
    gap: 6px;
    flex: none;
  }
  #leaderboard-subtabs { padding-left: 4px; }
  .leaderboard-tab, .leaderboard-subtab {
    flex: 1;
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 999px;
    padding: 8px 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
  }
  .leaderboard-tab.active, .leaderboard-subtab.active {
    background: rgba(251,191,36,0.15);
    border-color: var(--gold);
    color: var(--gold);
  }
  .leaderboard-subtab { flex: none; padding: 6px 14px; font-size: 11px; }
  #leaderboard-body {
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-height: 0;
  }

  /* --- Пьедестал (топ-3) --- */
  #leaderboard-podium {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 8px;
    padding-top: 8px;
  }
  .podium-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    width: 32%;
    min-width: 0;
  }
  .podium-medal { font-size: 20px; line-height: 1; }
  .podium-avatar {
    width: 52px; height: 52px;
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-size: 26px;
    background: rgba(255,255,255,0.06);
    border: 2px solid rgba(255,255,255,0.15);
  }
  .podium-1 .podium-avatar {
    width: 64px; height: 64px;
    font-size: 32px;
    border-color: var(--gold);
    box-shadow: 0 0 14px rgba(251,191,36,0.55);
  }
  .podium-2 .podium-avatar { border-color: #cbd5e1; }
  .podium-3 .podium-avatar { border-color: #d97706; }
  .podium-name {
    font-size: 12px; font-weight: 700; color: var(--text);
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .podium-value { font-size: 11px; font-weight: 700; color: var(--gold); }
  .podium-level-badge {
    font-size: 10px; color: var(--muted);
    background: rgba(255,255,255,0.06);
    border-radius: 999px;
    padding: 1px 8px;
  }
  .podium-base {
    width: 100%;
    border-radius: 8px 8px 0 0;
    margin-top: 4px;
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 4px;
    font-weight: 800;
    font-size: 15px;
  }
  .podium-1 .podium-base { height: 54px; background: linear-gradient(180deg, #fde68a, #f59e0b); color: #422006; }
  .podium-2 .podium-base { height: 38px; background: linear-gradient(180deg, #e5e7eb, #94a3b8); color: #1f2937; }
  .podium-3 .podium-base { height: 28px; background: linear-gradient(180deg, #fbbf9a, #c2703d); color: #3b1d09; }
  .podium-empty { visibility: hidden; }

  /* --- Список 4+ --- */
  #leaderboard-list {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  .leaderboard-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 10px;
    background: rgba(255,255,255,0.03);
  }
  .leaderboard-row-me { background: rgba(74,222,128,0.1); border: 1px solid rgba(74,222,128,0.35); }
  .leaderboard-rank { width: 20px; text-align: center; color: var(--muted); font-weight: 700; font-size: 13px; }
  .leaderboard-avatar { font-size: 19px; width: 30px; text-align: center; }
  .leaderboard-info { flex: 1; min-width: 0; }
  .leaderboard-row-name {
    font-size: 13px; font-weight: 600; color: var(--text);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .leaderboard-row-sub { font-size: 11px; color: var(--muted); }
  .leaderboard-row-value { font-size: 13px; font-weight: 700; color: var(--gold); white-space: nowrap; }

  #profile-sheet {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    background: var(--panel);
    border-radius: 20px 20px 0 0;
    border: 1px solid rgba(255,255,255,0.08);
    box-shadow: 0 -4px 20px rgba(0,0,0,0.35);
    z-index: 1500;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
    display: flex;
    align-items: center;
    gap: 12px;
    cursor: pointer;
    transition: transform 0.25s ease;
  }
  /* Полностью прячем полоску профиля за пределы экрана (не просто сворачиваем) —
     иначе она перекрывает build-menu/manage-menu, которые тоже прижаты к низу
     экрана. См. window.hideProfileBar/showProfileBar в 03-profile-sheet.js. */
  #profile-sheet.hidden {
    transform: translateY(120%);
    pointer-events: none;
  }
  #profile-icon {
    width: 52px; height: 52px;
    border-radius: 50%;
    background: rgba(255,255,255,0.06);
    display: flex; align-items: center; justify-content: center;
    font-size: 28px;
    flex-shrink: 0;
    cursor: pointer;
  }
  #profile-info { flex: 1; min-width: 0; }
  #profile-name-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 4px;
  }
  #profile-name {
    font-size: 18px;
    font-weight: 800;
    color: var(--text);
    text-transform: uppercase;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  #profile-level-badge {
    background: #3b82f6;
    color: #fff;
    font-size: 13px;
    font-weight: 800;
    border-radius: 999px;
    padding: 1px 9px;
    flex-shrink: 0;
  }
  #profile-xp-text {
    font-size: 13px;
    color: var(--muted);
    margin-bottom: 6px;
  }
  #profile-xp-bar-track {
    height: 8px;
    background: rgba(255,255,255,0.08);
    border-radius: 999px;
    overflow: hidden;
  }
  #profile-xp-bar-fill {
    height: 100%;
    width: 0%;
    background: linear-gradient(90deg, #3b82f6, #60a5fa);
    border-radius: 999px;
    transition: width 0.4s ease;
  }

  /* ====== ИНВЕНТАРЬ — полноэкранное меню, тот же паттерн, что и топ игроков
     (см. #leaderboard-overlay выше) — тот же слайд снизу + fade, тот же
     трюк с .opening/.show/.closing, чтобы анимация никогда не "прыгала". ====== */
  #inventory-overlay {
    position: absolute;
    inset: 0;
    background: rgba(5,7,11,0.9);
    backdrop-filter: blur(4px);
    z-index: 3300;
    display: none;
    justify-content: center;
    /* Поднимаем шторку инвентаря почти под самые системные кнопки Telegram
       ("Закрыть" и "..."), но не под них: 44px — высота этих кнопок, +8px зазор,
       чтобы ничего не налезало друг на друга. Раньше было 64px — оставался
       лишний пустой коридор сверху. */
    padding: calc(var(--tg-safe-area-top) + 28px) 0 0;
  }
  #inventory-overlay.show, #inventory-overlay.closing, #inventory-overlay.opening { display: flex; }
  #inventory-card {
    position: relative;
    width: 100%;
    max-width: 480px;
    height: 100%;
    background: var(--panel);
    border: 1px solid rgba(255,255,255,0.08);
    border-bottom: none;
    border-radius: 20px 20px 0 0;
    padding: 16px 16px calc(16px + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    gap: 12px;
    overflow: hidden;
    opacity: 0;
    transform: translateY(60vh);
    transition: opacity 0.28s cubic-bezier(0.16, 1, 0.3, 1),
                transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
  }
  #inventory-overlay.show #inventory-card {
    opacity: 1;
    transform: translateY(0);
  }
  #inventory-card-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-right: 32px;
    flex: none;
  }
  #inventory-card-title { font-size: 20px; font-weight: 700; color: var(--text); }
  #inventory-card-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    /* Скроллится ТОЛЬКО по вертикали. Без явного hidden ось X у скролл-
       контейнера сама превращается в auto (браузер не умеет "auto по Y,
       visible по X"), и любой элемент шире экрана — длинное число осколков,
       ряд чипов — давал горизонтальный люфт всей странице инвентаря. */
    overflow-x: hidden;
    touch-action: pan-y;   /* и жест тоже принимаем только вертикальный */
    display: flex;
    flex-direction: column;
  }
  /* Дети скролл-контейнера не имеют права распирать его по ширине: флекс-
     элементам по умолчанию нельзя ужиматься меньше контента (min-width: auto),
     из-за этого один неудачный ряд растягивал всю колонку. */
  #inventory-card-body > * { min-width: 0; max-width: 100%; }

  /* ====== ЭКИПИРОВКА ====== */
  #equipment-panel {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 4px 0 14px;
  }
  #equip-slots-left, #equip-slots-right {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .equip-slot {
    width: 48px; height: 48px;
    border-radius: 12px;
    background: rgba(255,255,255,0.04);
    border: 2px solid rgba(255,255,255,0.15);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 22px;
    opacity: 0.5;
  }
  .equip-slot-filled { opacity: 1; }
  .equip-slot-icon { line-height: 1; }
  #equip-avatar {
    width: 84px; height: 84px;
    border-radius: 50%;
    background: rgba(255,255,255,0.05);
    border: 2px solid rgba(255,255,255,0.15);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 40px;
    flex: none;
  }

  /* ====== ХАРАКТЕРИСТИКИ ====== */
  #stats-panel {
    display: flex;
    gap: 8px;
    padding: 0 2px 16px;
  }
  .stats-box {
    flex: 1;
    background: rgba(255,255,255,0.04);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 12px;
    padding: 8px 10px;
  }
  .stats-box-title {
    font-size: 11px;
    font-weight: 700;
    color: var(--muted);
    margin-bottom: 4px;
  }
  .stats-row {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    font-size: 12px;
    font-weight: 700;
    color: var(--text);
    padding: 2px 0;
  }

  /* ====== ИНВЕНТАРЬ ====== */
  #inventory-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 0 2px 8px;
    flex-wrap: wrap;
  }
  #inventory-title {
    font-size: 13px;
    font-weight: 700;
    color: var(--muted);
  }
  #inventory-sort {
    display: flex;
    gap: 4px;
  }
  .inventory-sort-btn {
    background: rgba(255,255,255,0.05);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: 999px;
    padding: 5px 10px;
    font-size: 11px;
    font-weight: 600;
    color: var(--muted);
  }
  .inventory-sort-btn.active {
    background: rgba(251,191,36,0.15);
    border-color: var(--gold);
    color: var(--gold);
  }
  #inventory-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
    padding-bottom: 16px;
  }
  .inventory-tile {
    position: relative;
    aspect-ratio: 1;
    background: rgba(255,255,255,0.04);
    border: 2px solid rgba(255,255,255,0.15);
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .inventory-tile-equipped { background: rgba(74,222,128,0.08); }
  .inventory-tile-emoji { font-size: 26px; line-height: 1; }
  .inventory-tile-level {
    position: absolute;
    top: 2px; left: 4px;
    font-size: 9px;
    font-weight: 800;
    color: var(--text);
    text-shadow: 0 0 3px rgba(0,0,0,0.9), 0 0 2px rgba(0,0,0,0.9);
  }
  .inventory-tile-count {
    position: absolute;
    bottom: 2px; right: 4px;
    font-size: 10px;
    font-weight: 800;
    color: var(--text);
    text-shadow: 0 0 3px rgba(0,0,0,0.9), 0 0 2px rgba(0,0,0,0.9);
  }
  .inventory-tile-badge {
    position: absolute;
    top: -5px; right: -5px;
    width: 16px; height: 16px;
    border-radius: 50%;
    background: var(--mine);
    color: #04250f;
    font-size: 10px;
    font-weight: 900;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .inventory-empty {
    grid-column: 1 / -1;
    text-align: center;
    color: var(--muted);
    font-size: 13px;
    padding: 20px;
  }

/* ====== ОБЗОРНЫЙ ГЛОБУС (MapLibre) ====== */
#worldmap-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 4000;
  background: #060a14;
}
#worldmap-overlay.show { display: block; }
#worldmap-globe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* Топбар опущен ниже шапки Telegram. Используем ту же динамическую переменную,
   что и весь игровой HUD: --tg-safe-area-top (safeAreaInset + contentSafeAreaInset,
   считается в 04-telegram-init.js и обновляется при поворотах/fullscreen). Это
   надёжно для ЛЮБОГО экрана — вырез, шапка Telegram с крестиком/⋯ учтены. +8px
   воздуха под них. Фолбэк на env(safe-area-inset-top) — если вдруг вне Telegram. */
#worldmap-topbar {
  position: absolute;
  left: 0; right: 0;
  top: calc(var(--tg-safe-area-top, env(safe-area-inset-top, 0px)) + 8px);
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 14px;
  pointer-events: none;   /* пустое место топбара пропускает жесты к глобусу */
  z-index: 5;
}
#worldmap-topbar button {
  pointer-events: auto;
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 11px;
  background: rgba(30, 41, 59, 0.92);
  color: #fff;
  font-size: 18px;
  line-height: 1;
  padding: 0;
  cursor: pointer;
  flex: none;
  box-shadow: 0 2px 8px rgba(0,0,0,0.4);
}
#worldmap-topbar button:active { transform: scale(0.94); }
/* Иконка внутри кнопки — блоком, иначе строчный svg ловит baseline-отступ
   снизу и уезжает вверх на пару пикселей внутри flex-центрирования. */
#worldmap-topbar button svg { display: block; }
/* Пока ждём снимок с сервера — иконка крутится: видно, что нажатие сработало. */
#worldmap-refresh.spinning svg { animation: wm-refresh-spin 0.8s linear infinite; }
@keyframes wm-refresh-spin { to { transform: rotate(360deg); } }
#worldmap-status {
  position: absolute;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 20px);
  transform: translateX(-50%);
  pointer-events: none;
  padding: 8px 16px;
  border-radius: 20px;
  background: rgba(15, 23, 42, 0.85);
  color: #e2e8f0;
  font-size: 14px;
  font-weight: 600;
  white-space: nowrap;
  text-shadow: 0 1px 4px rgba(0,0,0,0.85);
  z-index: 5;
}

/* Юзерпик игрока на глобусе — эмодзи в кружке вместо стандартной палочки. */
.worldmap-pin {
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;
  background: rgba(15, 23, 42, 0.92);
  border: 2px solid #38bdf8;
  border-radius: 50%;
  cursor: pointer;
  box-shadow: 0 2px 6px rgba(0,0,0,0.5);
}
.worldmap-pin-me {
  border-color: #fbbf24;                 /* свой — золотой ободок */
  box-shadow: 0 0 10px rgba(251,191,36,0.8);
  transform: scale(1.15);
}
/* Попап с ником в тёмном стиле игры. */
.maplibregl-popup-content {
  background: #0f1720;
  color: #e2e8f0;
  border-radius: 10px;
  font-size: 13px;
  padding: 8px 12px;
}
.maplibregl-popup-tip { border-top-color: #0f1720 !important; border-bottom-color: #0f1720 !important; }

/* ---- Карточка игрока и промпт места (обзорный глобус) ---- */
#worldmap-card, #worldmap-place-prompt {
  position: absolute;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 20px);
  transform: translateX(-50%) translateY(20px);
  z-index: 6;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
#worldmap-card.show, #worldmap-place-prompt.show {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}

#worldmap-card {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  width: min(340px, calc(100vw - 32px));
  padding: 14px;
  border-radius: 16px;
  background: rgba(15, 23, 42, 0.96);
  border: 1px solid rgba(56, 189, 248, 0.35);
  box-shadow: 0 8px 28px rgba(0,0,0,0.55);
}
.wm-card-buttons {
  display: flex;
  gap: 8px;
  width: 100%;      /* кнопки на своей строке под инфой */
}
.wm-card-icon {
  width: 46px;
  height: 46px;
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
  background: rgba(56, 189, 248, 0.15);
  border: 2px solid #38bdf8;
  border-radius: 50%;
}
.wm-card-info { flex: 1; min-width: 0; }
.wm-card-name {
  color: #f1f5f9;
  font-size: 16px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wm-card-level {
  color: #fbbf24;
  font-size: 13px;
  margin-top: 2px;
  letter-spacing: 1px;
}
.wm-card-open, .wm-place-open {
  flex: 1;
  padding: 10px 16px;
  border: none;
  border-radius: 11px;
  background: linear-gradient(135deg, #38bdf8, #0284c7);
  color: #fff;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.wm-card-close, .wm-place-close {
  flex: none;
  padding: 10px 16px;
  border: none;
  border-radius: 11px;
  background: rgba(148, 163, 184, 0.22);
  color: #e2e8f0;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.wm-card-open:active, .wm-place-open:active,
.wm-card-close:active, .wm-place-close:active { transform: scale(0.95); }

#worldmap-place-prompt {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: min(340px, calc(100vw - 32px));
  padding: 14px 16px;
  border-radius: 16px;
  background: rgba(15, 23, 42, 0.96);
  border: 1px solid rgba(56, 189, 248, 0.3);
  box-shadow: 0 8px 28px rgba(0,0,0,0.5);
  color: #e2e8f0;
  font-size: 15px;
  font-weight: 600;
  text-align: center;
}
.wm-place-buttons {
  display: flex;
  gap: 8px;
}

/* Метка места 📍 на глобусе */
.worldmap-place-pin {
  font-size: 28px;
  line-height: 1;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.6));
  cursor: pointer;
}

/* ====== МОНУМЕНТЫ ====== */
#monument-form-overlay {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 4200;   /* выше глобуса (4000) — форма вызывается с экрана глобуса */
  background: rgba(6, 10, 20, 0.7);
  align-items: flex-end;
  justify-content: center;
}
#monument-form-overlay.show { display: flex; }
#monument-form {
  width: min(440px, 100%);
  background: #0f1720;
  border-radius: 18px 18px 0 0;
  padding: 20px 18px calc(env(safe-area-inset-bottom, 0px) + 18px);
  border-top: 1px solid rgba(56, 189, 248, 0.3);
}
.monument-form-title {
  font-size: 18px;
  font-weight: 700;
  color: #f1f5f9;
  margin-bottom: 14px;
}
.monument-field {
  display: block;
  margin-bottom: 12px;
}
.monument-field span {
  display: block;
  font-size: 13px;
  color: var(--muted);
  margin-bottom: 5px;
}
.monument-field input, .monument-field textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid rgba(148, 163, 184, 0.3);
  background: #1e293b;
  color: #f1f5f9;
  font-size: 15px;
  font-family: inherit;
  resize: none;
}
#monument-icon { width: 70px; text-align: center; font-size: 22px; }
.monument-form-actions {
  display: flex;
  gap: 10px;
  margin-top: 6px;
}
.monument-btn-primary, .monument-btn-secondary {
  flex: 1;
  padding: 12px;
  border: none;
  border-radius: 11px;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}
.monument-btn-primary { background: linear-gradient(135deg, #38bdf8, #0284c7); color: #fff; }
.monument-btn-secondary { background: rgba(148, 163, 184, 0.2); color: #e2e8f0; }
.monument-btn-primary:active, .monument-btn-secondary:active { transform: scale(0.97); }

/* Маркеры монументов на карте */
.monument-pin {
  font-size: 30px;
  line-height: 1;
  filter: drop-shadow(0 3px 4px rgba(0,0,0,0.6));
  text-align: center;
}
.monument-pick-pin {
  font-size: 30px;
  line-height: 1;
  filter: drop-shadow(0 3px 4px rgba(0,0,0,0.7));
}
.monument-pick-emoji {
  display: inline-block;
  animation: monument-pick-bounce 0.6s ease infinite alternate;
}
@keyframes monument-pick-bounce {
  from { transform: translateY(0); }
  to { transform: translateY(-4px); }
}

/* Метка одобренного монумента на глобусе */
.worldmap-monument-pin {
  /* Монумент на глобусе — не рядовая точка, а цель, к которой идут через
     полгорода: он должен читаться с общего вида, не заставляя приближать.
     Отсюда 27 px вместо исходных 22. Полторы ставки (33 px) пробовали — метка
     начинала спорить с самой картой и слипаться с соседними. Коробка растёт в
     ту же меру, чтобы запас вокруг эмодзи остался прежним. */
  font-size: 27px;
  line-height: 1;
  filter: drop-shadow(0 2px 3px rgba(0,0,0,0.6));
  cursor: default;
  /* Коробка ЗАМЕТНО больше самого эмодзи: попасть пальцем ровно в глиф
     нереально, а промах в пару пикселей читался как «монумент не нажимается».
     Сам эмодзи лежит в .wm-mon-glyph по центру и размера не меняет. */
  width: 54px;
  height: 54px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Указатель метка НЕ ловит: попадание считает openMonumentNearPoint по клику
     на карте (16-monuments.js). Иначе у двух близких монументов верхний по
     порядку добавления забирал бы все тапы, и до нижнего было не добраться. */
  pointer-events: none;
  /* z-index: 0 (а не просто position: relative) — метка становится собственным
     стековым контекстом. Без него свечение ниже с z-index: -1 провалилось бы
     под саму карту; с ним оно гарантированно остаётся ВНУТРИ метки, но под её
     содержимым. */
  position: relative;
  z-index: 0;
}

/* ===== «Ломал / не ломал» на глобусе =====
   Мягкое свечение ПОД меткой — как подсветка от земли, а не нимб вокруг
   монумента. Отсюда и форма: сплюснутый эллипс у основания, а не круг по
   центру. Так подсказка не спорит с самим эмодзи за внимание и не мешает
   читать метку.

   Ничего не пульсирует. Меток на глобусе бывает много сразу, и любая анимация
   там мгновенно превращается в гирлянду; это фоновая справка, а не событие.
   Прозрачность держим низкой намеренно: цель — чтобы разницу было видно, когда
   ищешь её глазами, а не чтобы она лезла в глаза, когда смотришь на другое.

   Псевдоэлемент, а не отдельный узел: он переживает el.textContent = ... —
   сама метка перерисовывается именно так, детей у неё нет. Слой задаётся
   явным z-index (см. ниже), а не порядком ::before/::after: для абсолютно
   позиционированных элементов порядок в разметке на отрисовку не влияет. */
.worldmap-monument-pin::before {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 9px;               /* у подошвы эмодзи (коробка метки выше него) */
  width: 43px;
  height: 18px;
  transform: translateX(-50%);
  border-radius: 50%;
  pointer-events: none;
  /* Слоем НИЖЕ эмодзи. Абсолютно позиционированный псевдоэлемент по правилам
     отрисовки уезжает в слой позиционированных потомков — то есть ПОВЕРХ
     текстового содержимого, сколько его ни объявляй через ::before. Поэтому
     свечение затирало сам монумент, и лечится это только явным z-index. */
  z-index: -1;
  /* Два стопа вместо одного: ядро пятна держится в полной силе до 35%, и
     только потом уходит в прозрачность. С одним стопом свечение гасло почти
     сразу от центра и на глаз читалось заметно слабее, чем говорят цифры. */
  background: radial-gradient(ellipse at center,
              var(--mon-glow, transparent) 0%,
              var(--mon-glow, transparent) 35%,
              transparent 75%);
  transition: background 0.25s ease;
}

.worldmap-monument-pin.is-conquered { --mon-glow: rgba(251, 191, 36, 0.85); }  /* тёплое — ломал */
.worldmap-monument-pin.is-untouched { --mon-glow: rgba(56, 189, 248, 0.70); }  /* холодное — ещё нет */

/* Меню монумента (клик по монументу на игровой карте или глобусе) */
/* Шторка во всю ширину, как остальные нижние меню игры: прижата к низу,
   скруглены только верхние углы, снизу — отступ под системную полосу. Была
   узкой карточкой по центру, из-за чего строки жались и меню выглядело выше,
   чем есть. */
#monument-menu {
  display: none;
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 4300;   /* выше глобуса (4000) — меню работает и на глобусе */
  padding: 14px 14px calc(14px + env(safe-area-inset-bottom, 0px));
  border-radius: 16px 16px 0 0;
  /* Полностью непрозрачный: даже 3% прозрачности хватало, чтобы сквозь меню
     просвечивал интерфейс под ним. */
  background: #0f172a;
  border-top: 1px solid rgba(56, 189, 248, 0.30);
  box-shadow: 0 -8px 28px rgba(0,0,0,0.55);
  text-align: center;
}
#monument-menu.show { display: block; }
.monument-menu-icon { font-size: 40px; line-height: 1; }
.monument-menu-title {
  font-size: 18px;
  font-weight: 700;
  color: #f1f5f9;
  margin: 8px 0 14px;
}
#monument-menu-open, #monument-menu-close { width: 100%; margin-top: 6px; }
/* Кнопка атаки — это .mon-action, отдельного набора стилей ей больше не нужно;
   класс monument-attack-btn остался только как «ручка» для кода (поиск, класс
   .attacking, блокировка по дистанции). */

.monument-menu-open { width: 100%; margin-bottom: 8px; }

/* Плашка подтверждения места монумента (на глобусе) */
#monument-confirm {
  display: none;
  position: fixed;
  left: 50%;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 20px);
  transform: translateX(-50%);
  z-index: 4300;   /* выше глобуса */
  flex-direction: column;
  gap: 12px;
  width: min(340px, calc(100vw - 32px));
  padding: 14px 16px;
  border-radius: 16px;
  background: rgba(15, 23, 42, 0.97);
  border: 1px solid rgba(56, 189, 248, 0.35);
  box-shadow: 0 8px 28px rgba(0,0,0,0.55);
  color: #e2e8f0;
  font-size: 15px;
  font-weight: 600;
  text-align: center;
}
#monument-confirm.show { display: flex; }

/* ===== Боевой монумент ===== */
/* Полоска HP под маркером на карте, цвет по стадии */
.monument-hp {
  width: 40px;
  height: 4px;
  margin: 2px auto 0;
  background: rgba(0,0,0,0.5);
  border-radius: 2px;
  overflow: hidden;
}
.monument-hp-fill { height: 100%; transition: width 0.3s; }
/* Целый монумент полоску не показывает. Прячем классом, а не отсутствием узла:
   так разметка маркера не меняется при переходе «ранен ↔ цел», и Leaflet не
   пересоздаёт иконку (см. syncMonumentPin в 16-monuments.js). */
.monument-hp.is-full { visibility: hidden; }
.monument-stage-1 { background: #4ade80; }   /* стадия 1 — зелёный */
.monument-stage-2 { background: #fbbf24; }   /* стадия 2 — жёлтый */
.monument-stage-3 { background: #f87171; }   /* стадия 3 — красный */
/* Под щитом полоска синяя: цвет стадии сейчас не значит ничего — урон всё
   равно не проходит, а здоровье заморожено на пороге. */
.monument-hp-shielded {
  background: linear-gradient(90deg, #38bdf8, #7dd3fc);
  box-shadow: 0 0 8px rgba(56, 189, 248, 0.6);
}
/* Строка прочности под щитом — тот же синий язык, что и купол на карте. */
.mon-row-hp.is-shielded {
  border: 1px solid rgba(56, 189, 248, 0.45);
  background: rgba(56, 189, 248, 0.10);
}
.mon-row-hp.is-shielded .mon-row-label { color: #7dd3fc; }
/* Кнопка атаки под щитом: гашеная и синяя, чтобы не звала жать. */
.mon-action.mon-action-shielded {
  background: rgba(56, 189, 248, 0.16);
  border: 1px solid rgba(56, 189, 248, 0.45);
  color: #7dd3fc;
  cursor: default;
}
.monument-burning { position: relative; }
/* Сама иконка под огнём обугливается — гаснет и теряет цвет. */
.monument-icon-charred {
  display: inline-block;
  filter: brightness(0.5) saturate(0.45);
}
/* Огонь лежит ПОВЕРХ иконки, по центру, а не значком в углу. Вместо мерцания
   (оно читалось как «моргает баг») — живое пламя: качается, дышит размером и
   тянется вверх от основания. transform-origin внизу, чтобы огонь «рос» от
   постройки, а не пульсировал вокруг своей середины. */
.monument-fire {
  position: absolute;
  left: 50%; top: 50%;
  font-size: 30px;
  line-height: 1;
  transform-origin: 50% 85%;
  pointer-events: none;
  filter: drop-shadow(0 0 7px rgba(249, 115, 22, 0.85));
  /* Базовый сдвиг ОБЯЗАТЕЛЕН, хотя его же задаёт первый кадр анимации: если
     анимация окажется снята (медиазапрос, отладка, будущее правило), без него
     огонь потеряет центрирование и уедет вниз-вправо от иконки. */
  transform: translate(-50%, -52%);
  animation: monument-burn 1.15s ease-in-out infinite;
}
@keyframes monument-burn {
  0%   { transform: translate(-50%, -52%) scale(1.00) rotate(-4deg); opacity: 0.90; }
  35%  { transform: translate(-50%, -60%) scale(1.14) rotate(3deg);  opacity: 1; }
  65%  { transform: translate(-50%, -55%) scale(1.04) rotate(-2deg); opacity: 0.96; }
  100% { transform: translate(-50%, -52%) scale(1.00) rotate(-4deg); opacity: 0.90; }
}
/* Обратный отсчёт до превращения в Разлом — плашкой НАД маркером. */
.monument-burn-timer {
  position: absolute;
  left: 50%; bottom: 100%;
  transform: translateX(-50%);
  margin-bottom: 3px;
  padding: 1px 6px;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;   /* цифры не «прыгают» при смене секунды */
  line-height: 1.45;
  white-space: nowrap;
  color: #fed7aa;
  background: rgba(15, 23, 42, 0.9);
  border: 1px solid rgba(249, 115, 22, 0.5);
  border-radius: 7px;
}
/* Обёртка монумента. Пустая по виду и намеренно БЕЗ анимаций: к ней
   применяется встречный масштаб при зуме, а любой transform из @keyframes
   перебил бы его (см. monumentPinHtml в 16-monuments.js). Всё анимированное —
   огонь, появление Разлома — живёт внутри. */
.monument-wrap {
  position: relative;
  width: 44px; height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.monument-rift { filter: drop-shadow(0 0 6px rgba(139, 92, 246, 0.7)); }

/* ===== Сгорание: горит → Разлом ===== */
/* Отдельный слой поверх монумента, живёт ~1.9 с (см. playMonumentBurnout).
   Пламя вспыхивает и перекрывает постройку, постройка под ним истлевает,
   затем пламя оседает и открывает уже готовый Разлом. */
.monument-burnout {
  position: relative;
  width: 44px; height: 44px;
  pointer-events: none;
}
.monument-burnout .mb-icon {
  position: absolute; left: 50%; top: 50%;
  font-size: 30px; line-height: 1;
  transform: translate(-50%, -50%);   /* см. комментарий у .monument-fire */
  animation: mb-icon-away 0.95s ease-in forwards;
}
@keyframes mb-icon-away {
  0%   { opacity: 1;   transform: translate(-50%, -50%) scale(1);    filter: brightness(0.5) saturate(0.45); }
  55%  { opacity: 0.5; transform: translate(-50%, -50%) scale(0.82); filter: brightness(0.28) saturate(0.2); }
  100% { opacity: 0;   transform: translate(-50%, -50%) scale(0.55); filter: brightness(0.15) saturate(0); }
}
.monument-burnout .mb-fire {
  position: absolute; left: 50%; top: 50%;
  font-size: 30px; line-height: 1;
  transform-origin: 50% 85%;
  filter: drop-shadow(0 0 12px rgba(249, 115, 22, 0.95));
  transform: translate(-50%, -52%);   /* см. комментарий у .monument-fire */
  animation: mb-fire 1.9s ease-in-out forwards;
}
@keyframes mb-fire {
  0%   { transform: translate(-50%, -52%) scale(1.0);  opacity: 0.95; }
  20%  { transform: translate(-50%, -66%) scale(2.0);  opacity: 1; }    /* вспышка — перекрывает монумент */
  45%  { transform: translate(-50%, -64%) scale(1.75); opacity: 1; }
  70%  { transform: translate(-50%, -58%) scale(1.2);  opacity: 0.85; }
  100% { transform: translate(-50%, -48%) scale(0.18); opacity: 0; }    /* оседает и гаснет */
}
/* Разлом проявляется под оседающим пламенем, а не возникает рывком. */
.monument-rift-emerge { animation: mb-rift-in 1.9s ease-out both; }
@keyframes mb-rift-in {
  0%, 35% { opacity: 0; transform: scale(0.25); }
  75%     { opacity: 1; transform: scale(1.12); }
  100%    { opacity: 1; transform: scale(1); }
}
/* Сломанный монумент на глобусе и его карточка — призраком.
   ТОЛЬКО прозрачностью, без grayscale: обесцвечивание опознаётся как «сломан»
   лишь у цветных эмодзи, а монумент вполне может быть чёрно-белым сам по себе
   (футбольный мяч, шахматная фигура) — такой выглядел бы сломанным всегда.
   Прозрачность же читается одинаково на любой картинке. */
/* Сломанный монумент (горит или уже Разлом) — метка ПРОСВЕЧИВАЕТ: с орбиты
   важно «здесь стоял вот этот монумент, и он сейчас лежит». Только
   прозрачность, безо всякого обесцвечивания: иконку монумента выбирал игрок,
   и по цвету её и узнают в списке — серая заглушка узнаётся хуже, а сломан он
   или нет, уже сказано самой полупрозрачностью. 0.4, а не ниже: на тёмных
   тайлах метка иначе пропадает совсем, а её ещё надо находить и открывать.
   Область тапа не меняется — коробка метки и допуск в openMonumentNearPoint
   от прозрачности не зависят. */
/* Сам эмодзи метки. Отдельным узлом — чтобы перерисовка иконки не сносила
   коробку с областью тапа. */
.wm-mon-glyph { pointer-events: none; }

/* Сломанный монумент (горит или уже Разлом) — просвечивает ТОЛЬКО иконка.
   Прозрачность висит на .wm-mon-glyph, а не на самой метке, и это принципиально:
   opacity наследуется всем содержимым разом, поэтому на метке она гасила заодно
   и подсветку «ломал / не ломал» под ней. А подсветка к состоянию монумента
   отношения не имеет — она про ЛИЧНУЮ историю игрока, и на лежачем монументе
   этот вопрос как раз возникает чаще всего. */
.worldmap-monument-pin.is-broken .wm-mon-glyph { opacity: 0.4; }
/* Разлом (монумент лежит) уже притушен целиком правилом выше — подложку
   отдельно не прячем: «ломал ли я его» остаётся верным и для лежащего, а
   именно там этот вопрос и возникает чаще всего. */
.monument-ghost { opacity: 0.45; }

/* ===== Щит монумента =====
   Пока живы защитники, монумент неуязвим — и это должно читаться с карты
   мгновенно, без открывания карточки. Купол рисуем ::after поверх метки:
   полупрозрачная сфера с бликом, как силовое поле.

   Пульсация здесь УМЕСТНА, в отличие от подсветки «ломал / не ломал» на
   глобусе: щит — не фоновая справка, а активное состояние, которое игрок
   должен заметить прямо сейчас, и висит оно минуты, а не всегда. */
.monument-pin.is-shielded { position: relative; }

.monument-pin.is-shielded::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46px;
  height: 46px;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle at 35% 30%,
              rgba(186, 230, 253, 0.40) 0%,
              rgba(56, 189, 248, 0.22) 45%,
              rgba(14, 165, 233, 0.10) 70%,
              transparent 100%);
  border: 1.5px solid rgba(125, 211, 252, 0.75);
  box-shadow: 0 0 12px rgba(56, 189, 248, 0.55), inset 0 0 10px rgba(186, 230, 253, 0.35);
  animation: monShieldPulse 2s ease-in-out infinite;
}

@keyframes monShieldPulse {
  0%, 100% { opacity: 0.75; transform: translate(-50%, -50%) scale(0.96); }
  50%      { opacity: 1;    transform: translate(-50%, -50%) scale(1.06); }
}

/* ===== Карточка монумента =====
   Одна структура на все три состояния: шапка, строки-факты, одно действие.
   Раньше у каждого состояния была своя разметка «в столбик» — меню выходило
   высоким и рыхлым, а состояния было не сравнить глазами. */
.monument-menu-body { text-align: left; }

.mon-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  margin-bottom: 8px;
  border-radius: 12px;
  background: rgba(56, 189, 248, 0.10);
  border: 1px solid rgba(56, 189, 248, 0.20);
}
.mon-head-icon { font-size: 26px; line-height: 1; }
.mon-head-icon.is-burning { filter: drop-shadow(0 0 7px rgba(249,115,22,0.75)); }
.mon-head-icon.is-rift    { filter: drop-shadow(0 0 7px rgba(139,92,246,0.75)); }
.mon-head-title {
  font-size: 16px; font-weight: 700; color: #f1f5f9;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* min-width: 0 обязателен: без него длинное название распирает flex-строку и
     выдавливает уровень за край вместо того, чтобы обрезаться многоточием. */
  flex: 1; min-width: 0; text-align: left;
}
.mon-head-lvl {
  flex: none;
  font-size: 14px; font-weight: 700; color: #fbbf24;
  font-variant-numeric: tabular-nums;
  padding: 3px 9px;
  border-radius: 9px;
  background: rgba(251, 191, 36, 0.12);
}

.mon-rows { display: flex; flex-direction: column; gap: 6px; margin-bottom: 10px; }
.mon-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 11px;
  border-radius: 11px;
  background: rgba(148, 163, 184, 0.09);
  font-size: 13px;
}
.mon-row-icon  { font-size: 15px; line-height: 1; }
.mon-row-label { color: #94a3b8; font-weight: 600; }
/* Значение прижато вправо — так столбец читается сверху вниз одним взглядом. */
.mon-row-value { margin-left: auto; color: #f1f5f9; font-weight: 700; font-variant-numeric: tabular-nums; }
.mon-row-hp { flex-direction: column; align-items: stretch; gap: 6px; }
.mon-row-head { display: flex; align-items: center; gap: 8px; }
.mon-row-note { font-size: 11px; color: #64748b; }

.mon-action {
  width: 100%; padding: 12px; border: none; border-radius: 12px;
  font-size: 15px; font-weight: 700; cursor: pointer;
  background: linear-gradient(135deg, #ef4444, #b91c1c); color: #fff;
}
.mon-action:active { transform: scale(0.98); }
.mon-action-locked {
  position: relative; overflow: hidden;
  background: rgba(148, 163, 184, 0.15); color: #94a3b8; cursor: default;
}

.monument-lvl { font-size: 13px; color: #94a3b8; font-weight: 600; }
.monument-menu-sub { font-size: 13px; color: #94a3b8; margin-bottom: 12px; }
.monument-menu-hp {
  width: 100%; height: 8px; background: rgba(0,0,0,0.4);
  border-radius: 4px; overflow: hidden;
}
.monument-menu-hp-fill { height: 100%; transition: width 0.3s; }
.monument-attack-btn, .siege-attack-btn {
  /* Вид кнопки задаёт .mon-action — здесь остаётся только то, что нужно самой
     МЕХАНИКЕ удержания: touch-action отключает системные жесты под пальцем,
     user-select — выделение текста при долгом нажатии. */
  touch-action: none;
  user-select: none;
}
.monument-attack-btn.attacking, .siege-attack-btn.attacking { transform: scale(0.96); filter: brightness(1.2); }
/* Вне радиуса атаки: гасим и полностью снимаем реакцию на касания, чтобы
   удержание физически нельзя было начать (pointer-events, как у .manage-btn). */
.monument-attack-btn.disabled, .siege-attack-btn.disabled {
  opacity: 0.4;
  pointer-events: none;
  cursor: default;
  background: linear-gradient(135deg, #64748b, #475569);
}

/* Цель не выбрана (предыдущая погибла). Кнопка приглушена, но ОСТАЁТСЯ живой:
   pointer-events здесь не трогаем, в отличие от .disabled выше. Палец в этот
   момент обычно уже на кнопке, и отбирать у него возможность начать новое
   удержание нельзя — огонь просто ждёт, пока игрок ткнёт в цель на карте. */
.siege-attack-btn.is-idle {
  background: linear-gradient(135deg, #475569, #334155);
  font-size: 14px;
}

/* Разлом: кнопка прыжка заблокирована жёлто-чёрными ленточками */
/* Кнопка прыжка в разлом — это .mon-action.mon-action-locked. Отдельный класс
   .monument-jump-btn больше не используется, но правила ниже нужны: они рисуют
   ленточки поверх кнопки. */
.monument-jump-label { position: relative; z-index: 1; }
.monument-tape {
  position: absolute; inset: 0; z-index: 2;
  background: repeating-linear-gradient(
    45deg, #facc15 0 14px, #1a1a1a 14px 28px
  );
  opacity: 0.85;
}
.mon-action-locked::after {
  content: '🚧'; position: absolute; z-index: 3;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  font-size: 20px;
}

  /* Статусная плашка в режиме ошибки: жёлтая, между «онлайн» и «офлайн» по
     тревожности — это не разрыв связи, а отказ в конкретном действии. */
  #status-pill.status-warn { color: #f0c14b; }

/* Метка сборки в меню монумента на глобусе. Нужна ровно затем, чтобы одним
   взглядом отличить «код не работает» от «код не доехал»: если этой строки на
   экране нет, значит браузер выполняет старый файл, и искать надо в доставке,
   а не в логике. Убрать, когда всё устоится. */
.mon-build { margin-top: 8px; text-align: center; font-size: 10px; color: #4a5260; letter-spacing: 0.5px; }

/* ===== Кнопка «История сломов» ===== */
.mon-hist-btn {
  width: 100%; margin-top: 12px;
  display: flex; align-items: center; gap: 10px;
  padding: 13px 14px; border: none; border-radius: 14px; cursor: pointer;
  color: #fff; background: linear-gradient(135deg, #f472b6, #ec4899);
  box-shadow: 0 4px 14px rgba(236,72,153,0.35);
  transition: transform 0.12s, filter 0.2s;
}
.mon-hist-btn:active { transform: scale(0.98); filter: brightness(1.05); }
.mon-hist-btn-icon { font-size: 18px; line-height: 1; }
.mon-hist-btn-label { font-size: 15px; font-weight: 800; letter-spacing: 0.2px; }
.mon-hist-btn-count:not(:empty) { font-size: 12px; font-weight: 800; padding: 2px 9px; border-radius: 999px; background: rgba(0,0,0,0.28); }
.mon-hist-btn-arrow { margin-left: auto; font-size: 22px; font-weight: 700; opacity: 0.85; }

/* ===== Всплывающее меню истории ===== */
#mon-history-overlay {
  display: none; position: fixed; inset: 0; z-index: 5900;
  background: rgba(4, 8, 18, 0.72);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  align-items: center; justify-content: center; padding: 20px;
}
#mon-history-overlay.show { display: flex; animation: mon-hist-fade 0.18s ease; }
@keyframes mon-hist-fade { from { opacity: 0; } to { opacity: 1; } }
#mon-history-sheet {
  pointer-events: auto;
  width: min(440px, 100%); max-height: min(76vh, 640px);
  display: flex; flex-direction: column;
  background: #0f1830; border: 1px solid rgba(148,163,184,0.16);
  border-radius: 20px; box-shadow: 0 24px 60px rgba(0,0,0,0.6); overflow: hidden;
  animation: mon-hist-pop 0.22s cubic-bezier(0.2,0.9,0.3,1.2);
}
@keyframes mon-hist-pop { from { transform: translateY(16px) scale(0.96); opacity: 0; } to { transform: none; opacity: 1; } }
#mon-history-header { display: flex; align-items: center; gap: 12px; padding: 16px 52px 14px 16px; border-bottom: 1px solid rgba(148,163,184,0.1); position: relative; }
#mon-history-icon {
  font-size: 26px; line-height: 1; flex: none; width: 46px; height: 46px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(56,189,248,0.1); border: 1px solid rgba(56,189,248,0.25); border-radius: 14px;
}
.mon-history-titletext { min-width: 0; flex: 1; }
#mon-history-name { font-size: 18px; font-weight: 800; color: #f1f5f9; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#mon-history-sub { font-size: 12px; color: #94a3b8; margin-top: 2px; }
#mon-history-close {
  position: absolute; top: 12px; right: 12px;
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  padding: 0; line-height: 1;
  border-radius: 10px; border: none; cursor: pointer;
  background: rgba(148,163,184,0.14); color: #cbd5e1;
  font-size: 16px;
}
#mon-history-close:active { background: rgba(148,163,184,0.22); }
#mon-history-tabs { display: flex; gap: 8px; padding: 12px 16px; }
.mon-htab { flex: 1; padding: 11px 8px; border: none; border-radius: 12px; cursor: pointer; font-size: 13px; font-weight: 700; color: #94a3b8; background: rgba(148,163,184,0.08); transition: background 0.15s, color 0.15s; }
.mon-htab.is-active { background: linear-gradient(135deg, #f472b6, #ec4899); color: #fff; box-shadow: 0 3px 10px rgba(236,72,153,0.35); }
#mon-history-list { padding: 4px 12px 16px; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.mon-hist-empty { padding: 30px 16px; text-align: center; color: #64748b; font-size: 13px; }
.mon-hist-row { display: flex; align-items: center; gap: 12px; padding: 11px 10px; border-radius: 14px; }
.mon-hist-row:nth-child(odd) { background: rgba(148,163,184,0.05); }
.mon-hist-rank { flex: none; width: 24px; text-align: center; font-size: 17px; font-weight: 800; color: #94a3b8; }
.mon-hist-ava { flex: none; width: 42px; height: 42px; display: flex; align-items: center; justify-content: center; font-size: 22px; line-height: 1; border-radius: 50%; background: rgba(15,23,42,0.9); border: 1px solid rgba(148,163,184,0.2); }
.mon-hist-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.mon-hist-who { font-size: 15px; font-weight: 700; color: #f1f5f9; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mon-hist-when { font-size: 12px; color: #64748b; }
.mon-hist-lvl { flex: none; margin-left: auto; font-size: 12px; font-weight: 800; color: #cbd5e1; font-variant-numeric: tabular-nums; background: rgba(148,163,184,0.14); border: 1px solid rgba(148,163,184,0.2); padding: 4px 11px; border-radius: 999px; }

/* Кнопки истории — гарантированно кликабельны на мобильных. */
#mon-history-close, .mon-htab {
  pointer-events: auto;
  touch-action: manipulation;
  -webkit-tap-highlight-color: rgba(255,255,255,0.1);
  user-select: none;
}
#mon-history-list { pointer-events: auto; }

