/* =====================================================================
   Oxide — Act 1 hero. Vanilla CSS, без build-шага.
   Чёрный фон везде, чтобы не было белых/чёрных вспышек между фазами.
   ===================================================================== */

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  background: #000;
  color: #fff;
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  /* Скролл по вертикали — движок всей секвенции. */
  overflow-x: hidden;
}

/* ---------------------------------------------------------------------
   SCROLL-TRACK: только высота. Height задаётся из JS (vh-параметры).
   Не fixed — он в потоке и даёт документу прокручиваемую высоту.
   --------------------------------------------------------------------- */
.scroll-track {
  position: relative;
  width: 100%;
  pointer-events: none;
  /* height — из main.js: (SCRUB_A_VH + VIDEO_VH + SCRUB_B_VH + 100)vh */
}

/* ---------------------------------------------------------------------
   Общие полноэкранные фиксированные слои. Порядок по z-index:
   canvas(0) < video(1) < ui-overlay(2) < loader(10)
   --------------------------------------------------------------------- */
.scene-canvas,
.scene-video,
.ui-overlay {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100dvh;   /* 100vh→100dvh: на iOS 100vh = высота БЕЗ URL-бара (больше innerHeight),
                       а буфер canvas сайзится под innerHeight → кадр тянет по вертикали
                       = сплющивает по горизонтали. dvh совпадает с innerHeight → без искажения. */
}

/* Canvas — неподвижная сцена. pointer-events:none, чтобы не ел скролл. */
.scene-canvas {
  z-index: 0;
  display: block;
  background: #000;
  pointer-events: none;
}

/* Video — поверх canvas, скрыт по умолчанию.
   object-fit:cover повторяет cover-fit канвы. */
.scene-video {
  z-index: 1;
  display: none;            /* включается только в видео-фазе (JS) */
  object-fit: cover;
  /* ФОН ПРОЗРАЧНЫЙ, НЕ чёрный. Видео лежит ПОВЕРХ канваса (z-index 1 > 0), а showVideo()
     включает display:block + play() ещё ДО декода 1-го кадра. Чёрный фон в этот момент перекрыл
     бы кадр-подложку scrubA (UNDER_VIDEO_FRAME, рисуется ровно тут) → чёрное мигание при
     «холодном» кеше/медленной буферизации. Прозрачный фон оставляет подложку видимой, пока
     видео не покажет свой 1-й кадр (тот же шот) — бесшовно. Фулскрин имеет собственный background:#000. */
  background: transparent;
  pointer-events: none;     /* в обычном режиме не перехватывает ввод */
}

/* Пустой UI-слой на будущее. */
.ui-overlay {
  z-index: 2;
  pointer-events: none;     /* пока пустой — прозрачен для событий */
}

/* ---------------------------------------------------------------------
   КНОПКА ПЛЕЙ видео-фазы — ТОЛЬКО мобильная (включается в медиа 580).
   Кольцо с текстом «Explore this dangerous world» медленно вращается
   вокруг НЕПОДВИЖНОГО треугольника — поэтому это два отдельных <img>, а не один
   общий: вращать надо лишь кольцо.
   Прозрачность ведёт JS (showVideo/hideVideo) через .is-on.
   --------------------------------------------------------------------- */
/* Сцена видео — контейнер, который идёт в фулскрин (см. комментарий в index.html).
   z-index 3 — выше #ui-overlay(2), иначе кнопки прятались бы под затемнением
   и текстовыми блоками. Сам контейнер прозрачен для событий; кликабельны лишь
   кнопки, и ТОЛЬКО когда реально показаны (см. pointer-events ниже). */
.video-stage {
  position: fixed; inset: 0; z-index: 3;
  pointer-events: none;
}
/* В ФУЛСКРИНЕ контейнер ДОЛЖЕН ловить касания. Страница под фулскрином недосягаема для
   тач-скролла, поэтому «проскроллить видео» ловит JS сам (videoSwipeExit) — но событие
   должно сперва куда-то прилететь, а при pointer-events:none хит-тест не попадает ни во
   что и тачей не будет вообще. Вне фулскрина остаётся none, чтобы контейнер не
   глотал тапы на остальных фазах. touch-action:none — чтобы браузер не съел жест собственным
   pull-to-refresh/зумом. Кнопки внутри имеют свой pointer-events и не зависят
   от этого правила. */
.video-stage:fullscreen { background: #000; pointer-events: auto; touch-action: none; }
.video-stage:-webkit-full-screen { background: #000; pointer-events: auto; touch-action: none; }

/* В ФУЛСКРИНЕ КНОПКИ НЕ МОГУТ ЗАВИСЕТЬ ОТ МЕДИА-ЗАПРОСА ШИРИНЫ.
   display:block им даёт блок 580px — но в фулскрине вьюпорт это уже экран, а не
   узкое окно: медиа-запрос перестаёт матчиться и кнопки исчезают ровно тогда, когда
   они единственный элемент управления (заметнее всего — паузы просто нет). Поэтому включаем
   их признаком «мы в фулскрине», который от ширины не зависит.
   position:absolute вместо fixed — чтобы они крепились к самому контейнеру, а не
   к вьюпорту: в фулскрине это разные коробки, и fixed-кнопка может уехать за пределы
   видимого (ярче всего — в эмуляции устройства в девтулзах).
   videoManualMode() это не ломает: он читает display кнопки плей как единственный источник
   правды о мобильном режиме, но зовётся лишь на ВХОДЕ в видео-фазу, а туда не
   попасть, пока videoFsActive держит фазу — то есть пока :fullscreen действует. */
.video-stage:fullscreen .ui-vplay,
.video-stage:fullscreen .ui-vpause,
.video-stage:-webkit-full-screen .ui-vplay,
.video-stage:-webkit-full-screen .ui-vpause { display: block; position: absolute; }

.ui-vplay {
  display: none;
  position: fixed; left: 50%; top: 50%;
  width: 130px; height: 128px;                 /* собственный размер кольца */
  margin: -64px 0 0 -65px;                     /* центрирование без transform — его съедало бы вращение */
  padding: 0; border: 0; background: none;
  cursor: pointer;
  /* pointer-events ТОЛЬКО вместе с видимостью. Кнопка лежит на z-index 3 поверх
     всего экрана — оставить ей auto при opacity:0 означало бы повторить баг спрятанного
     лоадера: невидимый элемент по центру экрана, глотающий тапы на каждой фазе. */
  pointer-events: none;
  opacity: 0; transition: opacity 0.35s ease;
}
.ui-vplay.is-on { opacity: 1; pointer-events: auto; }
.ui-vplay__ring {
  position: absolute; inset: 0; width: 100%; height: 100%;
  animation: vplaySpin 22s linear infinite;
}
.ui-vplay__tri {
  position: absolute; left: 50%; top: 50%;
  width: 30px; height: 35px; margin: -17.5px 0 0 -13px;   /* оптический центр треугольника чуть левее геометрического */
}
@keyframes vplaySpin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .ui-vplay__ring { animation: none; } }

/* Пауза — снизу-справа на полях --edge, пока видео играет. */
.ui-vpause {
  display: none;
  position: fixed; right: var(--edge); bottom: 20px;
  padding: 10px; border: 0; background: none;
  cursor: pointer; line-height: 0;
  pointer-events: none;              /* включается только вместе с .is-on — см. .ui-vplay */
  opacity: 0; transition: opacity 0.25s ease;
}
.ui-vpause.is-on { opacity: 1; pointer-events: auto; }
.ui-vpause img { display: block; width: 26px; height: 34px; }

/* ---------------------------------------------------------------------
   ЗАТЕМНЕНИЕ НИЗА ЭКРАНА — общее для всех актов. Лёгкая подложка под
   тексты и элементы, прибитые к низу: на светлых кадрах (песок, небо)
   они иначе теряются.

   ТОЛЬКО МОБИЛЬНАЯ ВЕРСИЯ — в десктопе накладки нет (display:none ниже,
   включается в медиа 580). Элемент остаётся в разметке всегда, потому что его
   ПОЗИЦИЯ В DOM и есть слой (см. комментарий в index.html); прячем именно
   показом, а не удалением, чтобы порядок детей оверлея не «плавал».

   Геометрия: чёрный 45% у самого низа → в ноль на самом верху, то есть
   градиент растянут на ВСЮ высоту экрана (первый вариант — 35% на нижнюю
   четверть — на кадрах был почти незаметен).
   --------------------------------------------------------------------- */
.ui-shade {
  display: none;
  position: fixed; inset: 0;
  pointer-events: none;
  background: linear-gradient(to top, rgba(0,0,0,0.45) 0%, rgba(0,0,0,0) 100%);
  /* Прозрачность ведёт JS (updateShade): плашка есть лишь там, где на экране есть текст.
     Стартуем с 1, потому что первый экран — с текстом, и до первого тика мигания не будет. */
  opacity: 1;
}

/* ---------------------------------------------------------------------
   LOADER: полноэкранный, пока не готов стартовый буфер кадров.
   --------------------------------------------------------------------- */
.loader {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
  overflow: hidden;                    /* обрезать blur-выступ фона */
  transition: opacity 0.6s ease, visibility 0s;
}
/* СПРЯТАННЫЙ ЛОАДЕР НЕ СМЕЕТ ЛОВИТЬ КЛИКИ.
   pointer-events:none на ПРЕДКЕ этого НЕ гарантирует: потомок, который ставит auto, перекрывает предка —
   а внутри лоадера это .loader__logo и три .store-btn, и все они <a href="#">. Лоадер лежит на
   z-index:10 (overlay — лишь 2), то есть висит ПОВЕРХ всего сайта: невидимые ссылки ловили клики
   по центру экрана на любой фазе и кидали на "#", то есть на первый экран.
   Поэтому ДВА рубежа:
   1) pointer-events:none НА САМИХ ссылках — бьёт .store-btn по специфичности и действует МГНОВЕННО,
      не завися от того, доиграет ли переход (в фоновой вкладке анимации стоят);
   2) visibility:hidden — убирает их ещё и из таб-порядка и a11y-дерева; снимаем ПОСЛЕ фейда
      (задержка 0.6s), чтобы не обрезать исчезновение. */
.loader.hidden {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.6s ease, visibility 0s linear 0.6s;
}
.loader.hidden a { pointer-events: none; }
/* Фон: крошечный размытый первый кадр (LQIP, ~0.8КБ base64) — появляется мгновенно,
   без отдельного запроса. inset:-60px + scale, чтобы blur не оголял чёрные края;
   brightness — затемнение под контраст текста (как в макете). */
.loader__bg {
  position: absolute; inset: -60px;
  background: url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDABIMDRANCxIQDhAUExIVGywdGxgYGzYnKSAsQDlEQz85Pj1HUGZXR0thTT0+WXlaYWltcnNyRVV9hnxvhWZwcm7/2wBDARMUFBsXGzQdHTRuST5Jbm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm7/wAARCAAkAEADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwDkRyORS+T0K1GJMN61IkxBwRUDGNGyvnFPERkOMVajJkACLmr9lZMsokdcD0ouOxlLbgKfWo5YQoy/BrcureCKRj364rGuzucntWkdSJaFUjYaDJTs54NRMMHFDQJkyAKeetTYD7cCoD81SxHbUFG7pcEe3GK0Lz5YgVHSsCK9eJAF4rTt7szQgualopMq3AeRi54zWbdqEOOpNal1chVIWse4l31pAzmQj73NNdMnINBNJyatiQ5etPUnNFFZFkjE4qa0lcKRniiih7DW4+7JCjFZ70UVcdjOW42gUUVQH//Z") center / cover no-repeat;
  filter: blur(26px) brightness(0.5);
  transform: scale(1.08);
}
.loader__inner {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: center;
  text-align: center;
}
.loader__logo img { display: block; width: 120px; height: auto; }   /* как hero-логотип */
/* Лоадбар — стиль Global Wipe (.ui-wipe__bar): 4px, трек чёрный 35% + 1px бордер
   белого 35%; заливка — оранжевый градиент CE412B→FF8F7C со свечением. Ширина ≈ ряду store-btn. */
.loader__bar {
  position: relative;
  width: min(539px, 82vw);             /* ≈ ширина ряда из трёх .store-btn (3×173 + 2×10) */
  height: 4px; margin-top: 52px;
  background: rgba(0,0,0,0.35);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.35);
}
.loader__fill {
  position: absolute; left: 0; top: 0; height: 100%;
  width: 0%;
  background: linear-gradient(90deg, #CE412B 0%, #CE412B 72%, #FF8F7C 100%);
  box-shadow: 6px 0 11px -1px rgba(255,143,124,0.6);
  transition: width 0.2s ease;
}
.loader__note {
  margin: 52px 0 0;
  font-family: 'Inter', sans-serif; font-weight: 400;
  font-size: 13px; line-height: 1.4; letter-spacing: 0.08em;
  text-transform: uppercase; color: rgba(255,255,255,0.85);
}
.loader__stores { margin-top: 26px; }  /* .ui-cta__row даёт flex+gap; .store-btn — 1-в-1 с hero */

/* ---------------------------------------------------------------------
   ЧИП «БУФЕРИЗАЦИЯ» (поактная буферизация): мелкая плашка внизу-центр, которая
   появляется на стыке акта, пока не доехала его голова (слабый интернет).
   Классическое бэкинг-стекло (rgba(10,10,10,.4)+blur+--line) + оранжевый
   спиннер #CE412B — тот же визуальный словарь, что и вся секция. На
   быстром соединении JS её не включает вообще. pointer-events:none.
   --------------------------------------------------------------------- */
.buf {
  position: fixed; left: 50%; bottom: 40px;
  transform: translateX(-50%) translateY(8px);
  display: flex; align-items: center; gap: 10px;
  padding: 9px 16px; border-radius: 999px;
  background: rgba(10,10,10,0.4); border: 1px solid var(--line);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  font-family: 'Inter', sans-serif; font-weight: 400;
  font-size: 12px; line-height: 1; letter-spacing: 0.05em; text-transform: uppercase;
  color: rgba(255,255,255,0.85);
  opacity: 0; pointer-events: none; z-index: 9;   /* off = чисто opacity (aria-hidden + pointer-events),
                                                      без visibility-перехода, который зависает в фоновой вкладке */
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.buf.is-on {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.buf__spin {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.25); border-top-color: #CE412B;
  animation: bufSpin 0.7s linear infinite;
}
@keyframes bufSpin { to { transform: rotate(360deg); } }
body.reduced-motion .buf__spin { animation: none; }   /* reduced-motion: гейта нет, но на всякий — без вращения */

/* ---------------------------------------------------------------------
   ДОСТУПНОСТЬ: prefers-reduced-motion.
   В JS выключаем idle-луп и скраб; видео получает нативные controls.
   Тут разрешаем кликать по controls видео.
   --------------------------------------------------------------------- */
body.reduced-motion .scene-video {
  pointer-events: auto;
}

/* =====================================================================
   ШРИФТЫ (локальные, из корня проекта)
   ===================================================================== */
@font-face {
  font-family: 'Alumni Sans';
  src: url(fonts/alumni-sans-400-33285ae6.ttf) format('truetype');
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Alumni Sans';
  src: url(fonts/alumni-sans-400-e1537000.ttf) format('truetype');
  font-weight: 600; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Alumni Sans';
  src: url(fonts/alumni-sans-600-9493821d.ttf) format('truetype');
  font-weight: 700; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Alumni Sans';
  src: url(fonts/alumni-sans-700-f322d53d.ttf) format('truetype');
  font-weight: 800; font-style: normal; font-display: swap;
}
@font-face {
  font-family: 'Inter';
  src: url(fonts/inter-800-bd1976c1.ttf) format('truetype');
  font-weight: 400; font-style: normal; font-display: swap;
}

:root {
  --accent: #d9481f;          /* фирменный красно-оранжевый */
  --edge: 40px;               /* отступ контейнеров от краёв (в мобайле — 20, см. медиа 580) */
  /* НИЗ ОТСЧЁТА для всего, что прибито к низу экрана. В десктопе — 30px от края
     вьюпорта; в мобайле навигатор переезжает вниз, и эта же переменная становится «30px от
     навигатора» (см. медиа 580). Поэтому в правилах нет жёстких 30px — только var(--bottom),
     и одно изменение переменной двигает ВСЕ прибитые к низу блоки согласованно.
     Исключение лишь одно, задокументированное: HUD стоит на 30 выше — calc(var(--bottom) + 30px). */
  --bottom: 30px;
  --line: rgba(255,255,255,0.18);
  --dim: rgba(255,255,255,0.5);
  /* ФЛАЖОК РАСКЛАДКИ для JS: 0 = десктопная, 1 = мобильная (медиа 580 ставит 1).
     Нужен там, где от раскладки зависит не стиль, а ТАЙМИНГ — его CSS задать не
     может (см. mEnterFrom в реестре textBlocks: в портрете текст Act 2 ждёт, пока
     уйдёт HUD). Это тот же принцип, что и videoManualMode(): брейкпоинт остаётся
     ОДИН и живёт в медиа-запросе, а JS лишь читает, какая раскладка сейчас активна, и не
     имеет собственного сравнения ширины, которое могло бы с ней разойтись. */
  --mobile: 0;
  /* Расстояние центр→боковая эмблема в карусели псевдовыбора Act 5, В vw и БЕЗ единиц
     (число читает JS: applyClanChoose считает позиции в px, потому что пишет их в transform).
     Тут, а не константой в коде, потому что в портрете вся карусель больше — см. медиа 580. */
  --cp-slot: 13;
}

/* =====================================================================
   UI-OVERLAY: контейнер прозрачен для событий; интерактив включает auto.
   ===================================================================== */
.ui-overlay { font-family: 'Inter', system-ui, sans-serif; color: #fff; }
.ui-overlay a,
.ui-modal a,
.loader a { color: inherit; text-decoration: none; }   /* модалка и лоадер живут ВНЕ .ui-overlay — им нужен тот же ресет ссылок (иначе дефолтное подчёркивание + фиолет :visited) */

/* ---------- HEADER (логотип + навигатор + счётчики) — постоянный ---- */
.ui-header {
  position: absolute;
  top: 16px;
  left: var(--edge);
  right: var(--edge);
  /* Выше .ui-menu(20) — обязательно: панель меню теперь перекрывает контентный UI,
     и без этого крестик закрытия оказался бы ПОД ней, то есть меню не закрыть. */
  z-index: 21;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}

/* 1) Логотип: 120px по ширине, высота пропорционально (480×156) */
.ui-logo { pointer-events: auto; display: block; }
.ui-logo img { display: block; width: 120px; height: auto; }

/* 2) Счётчики Online / Servers — УДАЛЕНЫ. Шапка больше не «свопится»: .ui-actions
   видима всегда, прячется лишь DOWNLOAD (opacity, updateHeroFade + rsHeader). Правила
   .ui-counters / .counter / .counter__top / .counter__label / .counter__num / .counter .dot
   убраны отсюда 2026-08-25 как мёртвые — в разметке этих классов нет ни одного.
   ТОКЕНЫ ОСТАВЛЯЮ ЗДЕСЬ НАМЕРЕННО: на «.counter» до сих пор ссылаются комментарии ниже (.ui-informers,
   лидерборд, футер) и в main.js — это был эталон пары «тусклый лейбл + жирное число»:
     лейбл  = Inter 400, 10px, line-height 1.35, letter-spacing .10em, uppercase, --dim
     число  = Alumni Sans 800, 20px, line-height 0.9, letter-spacing .01em, #fff
     плашка = padding 6px 10px, 1px solid --line, rgba(10,10,10,.28) + blur(6px)
     точка  = 7px, flex:none + aspect-ratio:1 (иначе сплющивает во флекс-строке), #46d36b */

/* НЕ мёртвый: pulseDot крутит .ui-informers .dot (ниже), и из него сделан pulseDotCoral. */
@keyframes pulseDot {
  0%   { box-shadow: 0 0 0 0 rgba(70,211,107,0.6); opacity: 1; }
  50%  { box-shadow: 0 0 0 5px rgba(70,211,107,0);  opacity: 0.65; }
  100% { box-shadow: 0 0 0 0 rgba(70,211,107,0);    opacity: 1; }
}

/* Справа в хедере: группа кнопок. Когда-то тут был перекрёстный фейд счётчики ↔ CTA —
   счётчиков уже нет; position:relative остаётся якорем для абсолютной .ui-actions. */
.ui-header__right { position: relative; }

/* Кнопки хедера DOWNLOAD/SHOP/CREATORS/меню. Группа видима ВСЕГДА (SHOP/CREATORS/меню
   постоянные); отдельно прячется лишь DOWNLOAD (его opacity ведёт JS). Наложена поверх
   места, где раньше были счётчики (top/right:0). */
.ui-actions {
  position: absolute; top: 0; right: 0;
  display: flex; align-items: stretch;
  gap: 8px;
  white-space: nowrap;
  pointer-events: auto;
}
/* Унифицированная кнопка хедера: высота 30, Alumni 16 uppercase. --primary = сплошной CE412B,
   --ghost = чёрная 65% с рамкой (как меню-kebab рядом). */
.hdr-btn {
  display: flex; align-items: center; justify-content: center;
  height: 30px; padding: 0 16px;
  pointer-events: auto;
  font-family: 'Alumni Sans', sans-serif; font-weight: 400;
  font-size: 16px; line-height: 1; letter-spacing: 0;
  text-transform: uppercase; color: #fff; white-space: nowrap;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.hdr-btn--primary { background: #CE412B; }
.hdr-btn--primary:hover { background: #b8371f; }
.hdr-btn__ico { display: none; }   /* иконка в кнопке (Shop) — только на мобайле; десктоп показывает надпись */
.hdr-btn--ghost {
  background: rgba(0, 0, 0, 0.65);
  border: 1px solid rgba(255, 255, 255, 0.35);
}
.hdr-btn--ghost:hover {
  border-color: #CE412B;
  background: linear-gradient(rgba(206, 65, 43, 0.35), rgba(206, 65, 43, 0.35)), rgba(0, 0, 0, 0.65);
}

/* CTA «Become a new legend» (старая кнопка со стрелкой) — остаётся лишь как токен для
   .eco-btn в секциях Shop/Ecosystem (.ui-cta-btn__label). В хедере больше не используется. */
.ui-cta-btn {
  position: relative;                 /* для бордер-оверлея ::after */
  display: flex; align-items: stretch;
  height: 38px;
}
/* Бордер 1px CE412B рисуем оверлеем поверх контента: обычный border на кнопке
   перекрывался дочерними блоками (иконка height:38px и лейбл) по низу и правому краю.
   ::after с inset:0 + box-sizing:border-box даёт ровно 1px внутрь по всему периметру. */
.ui-cta-btn::after {
  content: ''; position: absolute; inset: 0;
  border: 1px solid #CE412B;
  pointer-events: none;
}
.ui-cta-btn__icon {
  flex: none;
  width: 38px; height: 38px;
  display: flex; align-items: center; justify-content: center;
  gap: 2px;
  background: rgba(0, 0, 0, 0.65);  /* полупрозрачный чёрный 65% */
}
.ui-cta-btn .cta-chev {             /* правый шеврон (CE412B из ассета) — тот же хвост, направление вправо */
  height: 16px; width: auto;
  display: block;
  opacity: 0.15;
  animation: chevRun 0.9s ease-in-out infinite;
}
.ui-cta-btn .cta-chev:nth-child(1) { animation-delay: 0s; }
.ui-cta-btn .cta-chev:nth-child(2) { animation-delay: 0.3s; }
.ui-cta-btn .cta-chev:nth-child(3) { animation-delay: 0.6s; }
.ui-cta-btn__label {
  display: flex; align-items: center;   /* отцентровано по высоте */
  padding: 0 16px;                       /* паддинг слева/справа 16 */
  background: #CE412B;
  font-family: 'Alumni Sans', sans-serif;
  font-weight: 400;
  font-size: 20px; line-height: 0.9;
  text-transform: uppercase;
  color: #fff;
}

/* Кнопка-меню (kebab) — квадрат с бордером, белая иконка из 3 точек */
.ui-menu-btn {
  flex: none;
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  background: rgba(0, 0, 0, 0.65);   /* та же заливка, что под иконкой в .ui-cta-btn__icon рядом */
  border: 1px solid rgba(255, 255, 255, 0.35);
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease;
}
/* только class-селекторы: иначе `.ui-menu-btn img` (специфичность выше) перебивал бы
   display:none и обе иконки показывались бы в первичном состоянии одновременно */
.ui-menu-btn__open  { display: block; width: 3px; height: 15px; }
.ui-menu-btn__close { display: none; width: 14px; height: 14px; filter: brightness(0) invert(1); }  /* X белым */
.ui-menu-btn.is-open .ui-menu-btn__open  { display: none; }
.ui-menu-btn.is-open .ui-menu-btn__close { display: block; }
/* Ховер: оранжевый ЛОЖИТСЯ ПОВЕРХ чёрной заливки, а не заменяет её (иначе кнопка на ховере светлела бы,
   потому что под шапкой небо). Два слоя в одном background: градиент-заглушка = сплошной цвет верхним
   слоем, под ним — тот же чёрный 65%. Цвет ховера — как в store-btn. */
.ui-menu-btn:hover {
  border-color: #CE412B;
  background: linear-gradient(rgba(206, 65, 43, 0.35), rgba(206, 65, 43, 0.35)), rgba(0, 0, 0, 0.65);
}

/* ====================== ПОПАП Download ====================== */
/* Появление/исчезновение попапа: фейд + сдвиг. Открытие — коробка ЕДЕТ ВНИЗ на место (стартует
   выше), закрытие — идёт ВВЕРХ, то есть ровно назад тем же путём.
   display НЕ анимируется, поэтому он теперь всегда flex, а показ ведёт opacity/visibility. */
.ui-modal {
  position: fixed; inset: 0; z-index: 30;
  display: flex;
  align-items: center; justify-content: center;
  padding: 20px;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  /* visibility с задержкой — чтобы элемент дожил до конца фейда, а не исчез мгновенно */
  transition: opacity 0.28s ease, visibility 0s linear 0.28s;
}
.ui-modal.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 0.28s ease, visibility 0s;
}
/* КРИТИЧНО. Внутри попапа лежат <a class="store-btn"> с pointer-events:auto, а
   родительский none детей НЕ выключает — это уже стреляло в проекте спрятанным лоадером,
   чьи ссылки ловили клики через весь экран и кидали на "#" (то есть в начало).
   Попап теперь всегда в потоке на весь экран, поэтому без этой строки баг повторился бы
   один-в-один. Гасим КЛАССОМ, не переходом: pointer-events должен слетать мгновенно,
   не дожидаясь анимации (в фоновой вкладке она может вообще не проиграться). */
.ui-modal:not(.is-open) a,
.ui-modal:not(.is-open) button { pointer-events: none; }
.ui-modal.is-open .ui-modal__box { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .ui-modal, .ui-modal__box { transition: none; }
  .ui-modal__box { transform: none; }
}
.ui-modal__box {
  position: relative;
  /* Стартует выше и съезжает на место; при закрытии идёт назад вверх. Кривая —
     ease-out: заезд тормозит в конце, а не втыкается. */
  transform: translateY(-28px);
  transition: transform 0.28s cubic-bezier(0.22, 0.61, 0.36, 1);
  max-width: 640px;
  padding: 50px;
  background: rgba(0, 0, 0, 0.85);   /* чёрный 85% — как и панель меню */
  text-align: center;
}
.ui-modal__close {                   /* иконка 14px в паддинге 8 → 16px от краёв коробки */
  position: absolute; top: 8px; right: 8px;
  display: flex; padding: 8px;
  background: none; border: none; cursor: pointer; line-height: 0;
}
.ui-modal__close img { display: block; width: 14px; height: 14px; }
.ui-modal__title {
  margin: 0 0 36px;
  font-family: 'Alumni Sans', sans-serif;
  font-weight: 800;
  font-size: 48px; line-height: 0.9; letter-spacing: -0.03em;
  text-transform: uppercase;
  color: #fff;
}
.ui-modal .ui-cta__row { justify-content: center; flex-wrap: wrap; }

/* ============ Меню-панель (выезжает справа) ============ */
.ui-menu {
  /* absolute (не fixed): та же система координат, что и баннер (оба в overlay width:100vw),
     иначе скроллбар сдвигает их на ~15px и расстояние до баннера ломается */
  position: absolute; top: 0; right: 0; bottom: 0;
  /* ЕДИНСТВЕННЫЙ ЯВНЫЙ z-index внутри #ui-overlay (вместе с .ui-header ниже).
     Остальной overlay сознательно лежит на auto, и порядок в DOM = порядок слоёв (см.
     комментарий у .ui-shade в разметке). Но меню стоит в DOM РАНО — намеренно,
     чтобы быть под шапкой, — а значит оказывалось и под ВСЕМ контентным UI, который идёт
     дальше: развёрнутый HUD Survival ложился просто поверх открытой панели. Явный
     z-index выводит из DOM-очереди лишь эти два элемента, ничего больше не задевая. */
  z-index: 20;
  width: 380px;          /* левый край на 40px левее стики-баннера (баннер: right40 + width300 = 340 от края) */
  background: rgba(0, 0, 0, 0.85);          /* чёрный 85% */
  transform: translateX(100%);              /* скрыта за краем */
  transition: transform 0.4s ease;
  pointer-events: none;
  overflow-y: auto;
  /* flex-колонка — чтобы .ui-menu__foot прижимался к низу панели через margin-top:auto */
  display: flex; flex-direction: column;
}
.ui-menu.is-open { transform: translateX(0); pointer-events: auto; }
.ui-menu__list { list-style: none; margin: 0; padding: 96px 0 0; }   /* отступ под шапку */
.ui-menu__item {
  display: block;
  padding: 10px 40px;                /* верх/низ 10; правый 40 = глобальный край контента */
  text-align: right;                 /* прибито правой выключкой к границе паддинга (40px) */
  font-family: 'Alumni Sans', sans-serif; font-weight: 400;   /* Regular */
  font-size: 20px; line-height: 0.9; letter-spacing: 0.02em; text-transform: uppercase;
  color: #fff;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
  transition: background 0.2s ease, border-color 0.2s ease;
}
.ui-menu__item:hover {               /* оранжевая заливка как у store-btn + нижняя линия CE412B */
  background: rgba(206, 65, 43, 0.35);
  border-bottom-color: #CE412B;
}
/* Подвал меню — дубль .rs-legal из финала, теми же токенами (Inter 400 / 10px / 135% /
   0.05em / белое 65% = .store-btn__small). margin-top:auto прижимает его к низу панели,
   сколько бы пунктов ни было в списке. Выключка — по тому же правому краю (40px), что и пункты. */
.ui-menu__foot {
  margin-top: auto;
  padding: 24px 40px 30px;
  text-align: right;
  font-family: 'Inter', sans-serif; font-weight: 400;
  font-size: 10px; line-height: 1.35; letter-spacing: 0.05em;
  text-transform: uppercase; color: rgba(255,255,255,0.65);
}
.ui-menu__links { display: block; margin-bottom: 18px; }
.ui-menu__links a { transition: color 0.2s ease; }
.ui-menu__links a:hover { color: #fff; }
.ui-menu__dot { opacity: 0.5; margin: 0 14px; }
.ui-menu__copy { display: block; }

/* 3) Навигатор актов — фиксирован ПО ЦЕНТРУ ЭКРАНА */
.ui-nav {
  position: absolute;
  top: 0;                    /* тянем навбар на высоту логотипа и центруем его содержимое по вертикали */
  left: 50%;
  transform: translateX(-50%);
  width: 326px;              /* макс. ширина лоадбара */
  height: 39px;              /* = высота логотипа (120px лого → 39px): центр навбара совпадает с лого слева и кнопками справа */
  display: flex;
  flex-direction: column;
  justify-content: center;   /* бар+подпись как группа отцентрованы по оси шапки */
  pointer-events: none;
}
/* Лоадбар навигатора — стиль 1-в-1 с Global Wipe (.ui-wipe__bar): 4px, 1px бордер
   внутрь (белое 35%), трек чёрный 35%. Заливка — БЕЛАЯ (без оранжевого/градиента/свечения). */
.nav-bar {
  position: relative; width: 100%; height: 4px;
  background: rgba(0,0,0,0.35);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.35);
}
.nav-bar__fill {
  position: absolute; left: 0; top: 0; height: 100%;
  width: var(--nav-progress, 0%);
  background: #fff;
}
/* Заголовок акта — по центру под баром. Скобки [ ] статичны, внутри — .nav-txt,
   которая меняется посимвольной печатью (JS: navTypeTick). Вся метка отцентрована,
   поэтому при изменении длины текста скобки симметрично «дышат» вокруг центра. */
.nav-labels {
  position: relative; height: 18px; margin-top: 8px;
}
.nav-label {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex; align-items: baseline;
  font-family: 'Alumni Sans', sans-serif;
  font-weight: 400;                  /* Regular начертание */
  font-size: 16px;
  line-height: 1;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #fff;
}
.nav-txt { margin: 0 0.35em; }       /* гэп между скобками и текстом (пустой текст → [  ]) */

/* =====================================================================
   HERO-группа (первый экран) — исчезает при скролле (управляется JS opacity)
   ===================================================================== */
.ui-hero { transition: opacity 0.15s linear; }
.ui-hero.is-hidden { visibility: hidden; }

/* 4) Заголовок: Alumni Sans 76 / lh 90% / tracking -3% / ширина 326 */
.ui-hero__title {
  position: absolute;
  left: var(--edge);
  top: 50%;
  transform: translateY(-50%);
  width: 22.64vw;          /* ≈326px на 1440; масштабируется пропорционально ширине (~четверть экрана) */
  margin: 0;
  font-family: 'Alumni Sans', sans-serif;
  font-weight: 800;
  font-size: 5.28vw;       /* ≈76px на 1440; кегль растёт вместе с блоком */
  line-height: 0.9;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: #fff;
}

/* ---- справа: описание + бейдж scroll-down (накидка) ---- */
.ui-right {
  position: absolute;
  right: var(--edge);
  top: 50%;
  --hero-dir: 1;             /* при фейде hero едет ВПРАВО (заголовок — влево, дефолт -1) */
  transform: translateY(-50%);
  width: 20.83vw;          /* ≈300px на 1440; масштабируется пропорционально ширине (~четверть экрана) */
}
.ui-desc {
  font-family: 'Inter', sans-serif;
  font-weight: 400;
  font-size: 0.97vw;       /* ≈14px на 1440 */
  line-height: 1.35;
  letter-spacing: 0.05em;  /* 5% */
  color: #fff;
  margin: 0 0 1.11vw;      /* гэп до кнопки ≈16px на 1440 (масштабируется с блоком) */
}

/* =====================================================================
   ACT 2 (Survival) — текстовый блок на секвенции.
   Параметры 1-в-1 с hero (.ui-hero__title / .ui-desc). Прозрачностью и сдвигом
   управляет JS (updateAct2): fade-in на frames-1, fade-out под конец frames-2.
   ===================================================================== */
.ui-act2 {
  position: absolute;
  left: var(--edge);
  top: 50%;
  transform: translate(0, -50%);
  opacity: 0;                /* стартово скрыто; JS выставляет по прогрессу скролла */
  will-change: opacity, transform;
}
.ui-act2.is-hidden { visibility: hidden; }
.ui-act2__title {
  margin: 0;
  width: 22.64vw;            /* как .ui-hero__title */
  font-family: 'Alumni Sans', sans-serif;
  font-weight: 800;
  font-size: 5.28vw;         /* ≈76px на 1440 */
  line-height: 0.9;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  color: #fff;
}
.ui-act2__desc {
  width: 22.64vw;            /* под ширину заголовка */
  margin: 1.39vw 0 0;        /* гэп над описанием ≈20px на 1440 */
  font-family: 'Inter', sans-serif;
  font-weight: 400;
  font-size: 0.97vw;         /* как .ui-desc */
  line-height: 1.35;
  letter-spacing: 0.05em;
  color: #fff;
}
/* Связка слов в заголовке, которые нельзя разрывать переносом (как .ui-desc .nb).
   Базовое правило, не мобильное: в десктопе заголовки и так nowrap, поэтому оно там
   ничего не меняет, а в портрете задаёт ТОЧКУ ПЕРЕНОСА без всякого <br>. */
.ui-act2__title .nb { white-space: nowrap; }

/* #ui-gather «Find and Gather» — заголовок в одну строку (узкая колонка 22.64vw переносила бы) */
#ui-gather .ui-act2__title { width: auto; white-space: nowrap; }

/* #ui-explore «Explore Your New Home» — справа-центр (не левая выключка, как остальные).
   Заголовок в одну строку; описание оставляет стандартную ширину (~2 строки). Вертикальное
   центрирование даёт translate(...,-50%) из applyTextBlock (как у остальных .ui-act2). */
#ui-explore { left: 51%; top: 47%; }
#ui-explore .ui-act2__title { width: auto; white-space: nowrap; }

/* #ui-night «Night Changes Everything» — ПО ЦЕНТРУ. Заголовок в одну строку; описание —
   центрованная колонка. Горизонтальное центрирование + вертикальный сдвиг даёт translate(-50%,…)
   из applyPassYBlock (появляется сверху вниз, исчезает вниз). */
#ui-night { left: 50%; top: 48%; text-align: center; }
#ui-night .ui-act2__title { width: auto; white-space: nowrap; }
#ui-night .ui-act2__desc { margin-left: auto; margin-right: auto; }

/* #ui-forward «Keep Moving Forward» (ACT 3, Progress) — левая выключка, как #ui-act2;
   заголовок в одну строку (узкая колонка 22.64vw переносила бы), описание — стандартная ширина. */
#ui-forward .ui-act2__title { width: auto; white-space: nowrap; }

/* #ui-challenges «Stay Ahead of the Challenges» (ACT 3→4) — справа-центр, как #ui-explore.
   Заголовок в одну строку; описание оставляет стандартную ширину. */
#ui-challenges { left: 50%; top: 48%; }
#ui-challenges .ui-act2__title { width: auto; white-space: nowrap; }

/* #ui-shelter «Build Your First Shelter» (ACT 4, Base) — ЛЕВАЯ выключка, как #ui-forward/#ui-act2
   (базовое .ui-act2: left:--edge, top:50%). Заголовок в одну строку; описание — стандартная ширина. */
#ui-shelter .ui-act2__title { width: auto; white-space: nowrap; }

/* #ui-clans «You're Not Alone Anymore» (ACT 5, Clans) — СПРАВА, как hero .ui-right:
   колонка прижата к ПРАВОМУ краю (right:--edge), четверть ширины (22.64vw, заголовок
   на 3 строки), выезд/заезд СПРАВА (slideDir:1). Слева остаётся свободная сторона сцены. */
#ui-clans { left: auto; right: var(--edge); top: 50%; }

/* #ui-createbig «Create Something Bigger» (ACT 5, Clans) — делим высоту экрана пополам: ВЕРХНЯЯ ГРАНИЦА
   блока лежит на середине НИЖНЕЙ половины (75% высоты), и блок растёт вниз. Поэтому top:75% БЕЗ -50%
   по Y (в отличие от остальных .ui-act2, которые этим -50% центрируются). По центру по X.
   Вход — фейд + slide-up; выход — фейд + slide-down (синхронно с каруселью), управляет
   updateClanChoose() — его ty добавляется к этому якорю. Заголовок в одну строку; описание центровано. */
#ui-createbig { left: 50%; right: auto; top: 75%; bottom: auto; transform: translateX(-50%); text-align: center; }
#ui-createbig .ui-act2__title { width: auto; white-space: nowrap; }
#ui-createbig .ui-act2__desc { width: 26vw; margin: 1.39vw auto 0; }

/* #ui-war «The Island Is Not Yours» (ACT 6, War) — ПО ЦЕНТРУ, как #ui-night. Заголовок в одну
   строку; описание — центрованная колонка. Горизонтальное центрирование + вертикальный сдвиг даёт
   translate(-50%,…) из applyPassYBlock (вход мгновенный на 1-м кадре акта, выход — фейд + вниз). */
#ui-war { left: 50%; top: 48%; text-align: center; }
#ui-war .ui-act2__title { width: auto; white-space: nowrap; }
#ui-war .ui-act2__desc { margin-left: auto; margin-right: auto; }

/* #ui-conquer «Conquer the Island» (ACT 6, War) — ЛЕВАЯ выключка, как #ui-forward/#ui-shelter
   (базовое .ui-act2: left:--edge, top:50%). Заголовок в одну строку; описание — стандартная ширина. */
#ui-conquer .ui-act2__title { width: auto; white-space: nowrap; }

/* =====================================================================
   ACT 5 (Clans) карусель ПСЕВДО-ВЫБОРА эмблемы клана (act5-frames-2→3).
   Три эмблемы-карточки (устройство 1-в-1 с .gear-item акта Progress: бордер --line
   + фон rgba(10,10,10,.4) + backdrop-blur) абсолютны в .cp-stage; позицией
   (слот)/масштабом/прозрачностью/ховером каждой управляет JS (updateClanChoose). Осевшая
   центральная capshield — .is-picked (ховер CE412B, как .store-btn:hover). Под
   центром — приставленная риска + дуга arc-choose (как в оригинале, выпуклая ВВЕРХ)
   с надписью (шрифт как «AVAILABLE NOW»).
   ===================================================================== */
.ui-clanpick {
  position: absolute;
  left: 50%; top: 33%;
  transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center;
  pointer-events: none;
  opacity: 0;                 /* стартово скрыто; ведёт JS */
  will-change: opacity, transform;
}
.ui-clanpick.is-hidden { visibility: hidden; }
.cp-stage { position: relative; width: 46vw; height: 10vw; }   /* высота = центральная карточка (низ карточки = низ сцены → риска впритык) */
.cp-emblem {
  position: absolute;
  left: 50%; top: 50%;
  width: 10vw; height: 10vw;
  transform: translate(-50%, -50%);
  will-change: transform, opacity;
  pointer-events: auto;          /* КЛИКАБЕЛЬНАЯ (оверлей — none): пользователь выбирает эмблему */
  cursor: pointer;
}
/* Плавный переход в центр при выборе (класс .is-chosen включает JS). Во время скролл-перебора
   transition НЕ активен (иначе лагал бы скраб) — только после клика, когда позиции зафиксированы. */
.ui-clanpick.is-chosen .cp-emblem { transition: transform 0.4s cubic-bezier(.4,0,.2,1), opacity 0.35s ease; }
/* Карточка-«клетка» — строение как .gear-item (бордер + полупрозрачный фон + блюр). */
.cp-cell {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%;
  box-sizing: border-box;
  border: 1px solid var(--line);
  background: rgba(10,10,10,0.4);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  transition: border-color 0.2s ease, background 0.2s ease;
}
.cp-cell:hover,                /* ховер-аффорданс (кликабельно) + осевшая/выбранная — наш обычный ховер */
.cp-cell.is-picked {           /* (как .store-btn:hover) */
  border-color: #CE412B;
  background: rgba(206, 65, 43, 0.35);
}
.cp-cell img { width: 82%; height: 82%; object-fit: contain; display: block; }
/* Дуга (выпуклая ВВЕРХ, без флипа) с риской и надписью ВНУТРИ. .cp-arc — позиционный контейнер;
   риска и надпись абсолютны относительно пика дуги (= верх .cp-arc__img). ВСЁ в vw (не px), чтобы
   композиция масштабировалась ЦЕЛИКОМ с дугой — иначе на более широком экране надпись «отъезжает»,
   а риска остаётся тонкой. Значения подобраны так, что на 1440 = прежние px (8/16/1/12px). */
.cp-arc { position: relative; margin-top: 0.55vw; }   /* пик дуги чуть ниже карточки → риска перехлёстывает его */
.cp-arc__img { width: 34vw; height: auto; display: block; }   /* тянется чуть за боковые эмблемы */
.cp-tick {                                          /* симметрично сквозь пик, впритык к карточке; толщина растёт с дугой */
  position: absolute; left: 50%; top: -0.55vw;
  transform: translateX(-50%);
  width: 0.08vw; height: 1.11vw;
  background: rgba(255,255,255,0.4);
}
.cp-arc__label {                                    /* ВНУТРИ дуги — под пиком, в куполе; масштабируется с дугой */
  position: absolute; left: 50%; top: 1.9vw;
  transform: translateX(-50%);
  white-space: nowrap;
  font-family: 'Inter', sans-serif; font-weight: 400;
  font-size: 0.83vw; line-height: 1.35; letter-spacing: 0.05em;   /* 0.83vw ≈ 12px на 1440 (как «AVAILABLE NOW») */
  text-transform: uppercase;
  color: #fff;                  /* 100% белого — 1-в-1 с «AVAILABLE NOW» */
}

/* =====================================================================
   ACT 5 (Clans) ЧАТ: круглая иконка внизу-справа (плашка как карточки, radius 50%)
   + сообщения, всплывающие вверх каскадом. Прозрачностью/сдвигом управляет updateChat().
   ===================================================================== */
.ui-chat {
  position: absolute;
  right: var(--edge); bottom: var(--bottom);      /* те же маргины, что и Global Wipe */
  pointer-events: none;
}
.chat-btn {                              /* круглая плашка (как карточки) + белая иконка */
  width: 72px; height: 72px;
  border-radius: 50%;                    /* 100% от размера → круг */
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line);
  background: rgba(10,10,10,0.4);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  opacity: 0;                            /* стартово скрыто; ведёт JS */
  will-change: opacity, transform;
}
.chat-btn img { width: 24px; height: 24px; display: block; }
.chat-feed {                             /* стек сообщений НАД иконкой, правый край = край иконки */
  position: absolute;
  right: 0; bottom: 88px;                /* над иконкой (72 + 16 гэп) */
  width: 44vw;                           /* поле под текст; сообщения прижаты вправо */
  pointer-events: none;
}
.chat-msg {                              /* отдельное сообщение — тёмная плашка + бордер (как на скрине) */
  position: absolute;
  right: 0; bottom: 0;
  white-space: nowrap;
  padding: 8px 14px;
  border: 1px solid var(--line);
  background: rgba(10,10,10,0.55);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  font-family: 'Inter', sans-serif; font-weight: 400;
  font-size: 14px; line-height: 1.35; letter-spacing: 0.05em;   /* Inter Regular 14 / 135% / 5% */
  color: #fff;
  opacity: 0;                            /* стартово скрыто; ведёт JS */
  will-change: opacity, transform;
}
.chat-user { color: #CE412B; margin-right: 0.4em; }   /* ник — акцентный оранжевый */

/* =====================================================================
   ACT 5 (Clans) «облачка-мысли» кабана (act5-frames-6): boar-attack / boar-runaway.
   Позицией (центр = xf,yf вьюпорта), масштабом и прозрачностью покадрово управляет updateBoar();
   лёгкое вертикальное плавание — CSS-анимация boarFloat на внутреннем img.
   ===================================================================== */
.ui-boar { position: absolute; inset: 0; pointer-events: none; }
.ui-boar.is-hidden { visibility: hidden; }
.boar-bubble {
  position: absolute; left: 0; top: 0;
  width: 16vw;                         /* ОДИНАКОВЫЙ базовый размер обоих; attack уменьшает масштабом в JS */
  transform-origin: center;
  opacity: 0;                          /* стартово скрыто; позицию/масштаб/прозрачность ведёт JS */
  will-change: opacity, transform;
}
/* лёгкое вертикальное ПЛАВАНИЕ (независимо от скролла; на внутреннем img, чтобы не конфликтовать
   с JS-трансформом .boar-bubble). Разные длительности/фаза — чтобы облачка не качались синхронно. */
.boar-bubble img { display: block; width: 100%; height: auto; animation: boarFloat 3.4s ease-in-out infinite; }
.boar-bubble--runaway img { animation-duration: 4.2s; animation-delay: -1.3s; }
@keyframes boarFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-9px); } }
@media (prefers-reduced-motion: reduce) { .boar-bubble img { animation: none; } }

/* =====================================================================
   ACT 3 gear HUD (frames-3→4) — ДВЕ тройки ассетов на одном треке с
   большим промежутком (.gear-spacer) между ними. Тройка №1 (кирка/факел/
   кам.молот) въезжает с левого края и «оседает», дальше весь трек едет влево:
   №1 выходит слева, а №2 (шлем/кираса/штаны) заходит справа. Позицией
   трека (translateX) и подсветкой .is-active строго управляет JS (updateGear).
   Контейнер почти на всю ширину (карточки доезжают до обоих краёв),
   overflow:hidden прячет ещё-не-доехавшие / уже-уехавшие карточки. Подсветка
   CE412B — та же акцентная, что и hover на .store-btn.
   ===================================================================== */
.ui-gear {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 100vw;               /* full-bleed: клип overflow точно по КРАЮ ЭКРАНА
                                 (карточки появляются/исчезают ровно на краю, без
                                 40px-маржи). Контент сдвинут на --edge в JS
                                 (updateGear), поэтому позиции оседания остаются те же. */
  overflow: hidden;
  pointer-events: none;
  opacity: 0;                 /* стартово скрыто; JS выставляет по прогрессу скролла */
}
.ui-gear.is-hidden { visibility: hidden; }
.gear-track {
  display: flex;
  gap: 1.5vw;                 /* промежуток между карточками (действует и по бокам .gear-spacer) */
  will-change: transform;
}
.gear-item {
  flex: none;
  width: 17.5vw;
  height: 18vw;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line);
  background: rgba(10,10,10,0.4);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  transition: border-color 0.2s ease;
}
.gear-item.is-active { border-color: #CE412B; }
.gear-item img { width: 82%; height: 82%; object-fit: contain; }
.gear-spacer { flex: none; width: 63vw; }   /* большой промежуток между тройкой №1 и №2 */

/* =====================================================================
   ACT 6 (War) карусель ПСЕВДО-ВЫБОРА оружия (фаза X = act6-frames-1-2).
   Устройство карточек 1-в-1 с .gear-item (бордер --line + полупрозрачный фон + блюр);
   карточка выбора — .is-active: наш обычный ховер (бордер #CE412B + заливка
   rgba(206,65,43,.35)), как .cp-cell.is-picked / .store-btn:hover.
   Лента translateX + подсветка управляются JS (updateWeapons); выпукло: оседает
   на АРБАЛЕТЕ. Full-bleed клип по КРАЮ экрана — карточки всегда заполняют экран.
   ===================================================================== */
.ui-weapons {
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 100vw;
  overflow: hidden;
  opacity: 0;                 /* стартово скрыто; JS выставляет по прогрессу скролла */
  pointer-events: none;       /* псевдовыбор — клики не нужны */
  will-change: opacity;
}
.ui-weapons.is-hidden { visibility: hidden; }
.wpn-track {
  display: flex;
  gap: 1.5vw;                 /* 1-в-1 с .gear-track (Act 3) */
  will-change: transform;
}
/* Карточки — СТРОЕНИЕ И МАСШТАБ 1-в-1 с .gear-item (Act 3): 17.5vw × 18vw, бордер + фон + блюр,
   img 82% contain; .is-active — обычный ховер (бордер + красная заливка), как .cp-cell.is-picked. */
.wpn-item {
  flex: none;
  width: 17.5vw;
  height: 18vw;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line);
  background: rgba(10,10,10,0.4);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  transition: border-color 0.2s ease, background 0.2s ease;   /* заливка перетекает с карты на карту, как бордер */
}
/* Осевший псевдовыбор — НАШ ОБЫЧНЫЙ ХОВЕР, 1-в-1 как .cp-cell.is-picked / .store-btn:hover:
   бордер + КРАСНАЯ ПОЛУПРОЗРАЧНАЯ ЗАЛИВКА. Одного бордера мало — на пёстрой сцене он не выделял. */
.wpn-item.is-active {
  border-color: #CE412B;
  background: rgba(206, 65, 43, 0.35);
}
.wpn-item img { width: 82%; height: 82%; object-fit: contain; }

/* =====================================================================
   HUD (Survival) — игроцкий интерфейс снизу-слева на секвенции frames-1.
   Низ — 60px, левая выключка — --edge(40). Информеры — как .store-btn (другие
   иконки, без pointer-events/ховера). Бар (угол+бар+1px-линия+sq-point) —
   белый 65%; линия тянется до sq-point, который стоит по центру экрана.
   Прогрессом сборки/разборки управляет JS (updateHud).
   ===================================================================== */
.ui-hud {
  position: absolute;
  left: var(--edge);
  bottom: calc(var(--bottom) + 30px);   /* HUD — задокументированное исключение: на 30 выше общего правила */
  pointer-events: none;
  /* Докуда тянется бар — ДОЛЯ ШИРИНЫ ВЬЮПОРТА ОТ ТОГО КРАЯ, К КОТОРОМУ ПРИБИТ
     САМ HUD, до sq-point. Тут 0.5 = точка по центру экрана, где стоит герой.
     Мерить от «своего» края, а не всегда от левого — это то, что позволяет зеркалить
     всю группу вправо одной сменой стороны: формула в layoutHud() остаётся та же.
     Живёт в CSS, а не в JS, чтобы пропорция задавалась в медиа-запросе и код не был обязан
     знать о брейкпоинте (тот же принцип, что и в videoManualMode). */
  --hud-span: 0.5;
}
.ui-hud.is-hidden { visibility: hidden; }

/* --- Три информера (структура/оформление как .store-btn) --- */
.hud-row { display: flex; gap: 10px; margin-bottom: 3px; }   /* 3px гэп до бара */
.hud-stat {
  display: flex; align-items: stretch;
  width: 173px;                                /* одинаковый размер, как .store-btn */
  border: 1px solid var(--line);
  background: rgba(10,10,10,0.4);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  opacity: 0;                                  /* стартово скрыты; JS поднимает снизу-вверх */
  transform: translateY(16px);
  will-change: opacity, transform;
}
/* Иконки HUD заполняют ВЕСЬ квадрат 56×56 — без паддинга/ограничения размера.
   Селектор .hud-stat .hud-stat__icon img (спец. 0,2,1) перебивает .store-btn__icon img (0,1,1, 24px). */
.hud-stat .hud-stat__icon img { width: 100%; height: 100%; object-fit: cover; }
.hud-stat--avatar .hud-stat__icon { background: transparent; }     /* аватар закрывает квадрат */

/* --- Нижний бар: угол + сплошной бар + 1px линия + sq-point (белый 65%) --- */
.hud-bar {
  display: flex; align-items: center;          /* 1px-линия по вертикальному центру 9px-бара */
  height: 9px;
  opacity: 0.65;                               /* весь автолейаут — белый 65% */
  width: calc(50vw - var(--edge) + 4px);       /* fallback; точную ширину ставит JS (центр по innerWidth) */
}
.hud-bar__solid {
  flex: none; display: flex;
  width: var(--hud-row-w, 539px);              /* = ширина row информеров; JS уточняет */
  height: 9px;
  transform-origin: right center;              /* «выезжает» влево (scaleX с правого края) */
}
.hud-bar__corner { flex: none; width: 10px; height: 9px; line-height: 0; }
.hud-bar__corner img { display: block; width: 10px; height: 9px; }
.hud-bar__fill { flex: 1 1 auto; height: 9px; background: #fff; }    /* = row_width − 10px угла */
.hud-bar__line {
  flex: 1 1 auto; min-width: 0; height: 1px; background: #fff;
  transform-origin: right center;              /* растёт влево от sq-point */
}
.hud-bar__point { flex: none; width: 8px; height: 8px; line-height: 0; }
.hud-bar__point img { display: block; width: 8px; height: 8px; }
/* Слишком узкий экран: линия до центра не помещается — оставляем только сплошной бар. */
.hud--nolane .hud-bar__line, .hud--nolane .hud-bar__point { display: none; }

/* =====================================================================
   ACT 2 ВЫНОСКИ (frames-4): подписи-выноски на объекты сцены (кирка + руда).
   Визуальный язык бара взят из Survival-HUD (угол start-end-corner + fill +
   1px-линия + sq-point), развёрнутый горизонтально: sq-point-«указатель» →
   линия → бар, а на бар опирается квадрат с иконкой (над баром — .callout--up,
   под баром — .callout--down). Показ/скрытие: updateCallouts(); стадийная
   анимация (sq-point→линия→бар→квадрат) — как applyHud.
   Позиция sq-point задаётся inline (left/top в % вьюпорта); длину линии —
   переменная --line-w, размер квадрата (= ширина бара) — --box.
   ===================================================================== */
.ui-callouts { position: absolute; inset: 0; pointer-events: none; }
.ui-callouts.is-hidden { visibility: hidden; }
.callout { position: absolute; display: flex; align-items: center; }   /* left/top (inline) = позиция sq-point */
.co-point { flex: none; width: 8px; height: 8px; line-height: 0; will-change: opacity, transform; }
.co-point img { display: block; width: 8px; height: 8px; }
.co-line { flex: none; width: var(--line-w, 120px); height: 1px; background: #fff; opacity: 0.7;
  transform-origin: left center; will-change: transform; }
.co-bar { position: relative; flex: none; display: flex; width: var(--box, 130px); height: 9px;
  opacity: 0.7; transform-origin: left center; will-change: transform; }
.co-bar__fill { flex: 1 1 auto; height: 9px; background: #fff; }
.co-bar__corner { flex: none; width: 10px; height: 9px; line-height: 0; transform: scaleX(-1); }  /* диагональ вправо (кирка) */
.co-bar__corner img { display: block; width: 10px; height: 9px; }
.callout--down .co-bar__corner { transform: scaleX(-1) scaleY(-1); }   /* руда: срез отзеркален по ВЕРТИКАЛИ относительно кирки (бар сверху квадрата) */
/* Маска квадрата: он выезжает ИЗ-ЗА края бара, а скрытая часть обрезается
   (поэтому не «прилетает» из-за собственных границ). Край маски = кромка бара, откуда выезд. */
.co-boxclip { position: absolute; right: 0; width: var(--box, 130px); height: var(--box, 130px); overflow: hidden; }
.callout--up   .co-boxclip { bottom: 100%; }   /* маска над баром; нижний край маски = верхняя кромка бара */
.callout--down .co-boxclip { top: 100%; }        /* маска под баром; верхний край маски = нижняя кромка бара */
.co-box { position: absolute; inset: 0;
  background: rgba(12,12,12,0.6); border: 1px solid var(--line);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center; will-change: opacity, transform; }
.co-box img { display: block; width: 82%; height: 82%; object-fit: contain; }

/* ГЕОМЕТРИЯ ДВУХ ВЫНОСОК. Раньше стояла инлайном в разметке — пришлось вынести сюда,
   потому что в портрете она совсем другая (см. медиа 580), а инлайн из стиля перебивается
   только !important. left/top — позиция sq-point (в --down после зеркаления это левый
   край бара, см. мобильный блок); --line-w — длина линии; --box — сторона квадрата
   (она же ширина бара). */
#co-pickaxe { left: 41%; top: 57%; --line-w: 10vw; --box: 8vw; }
#co-ore     { left: 57%; top: 74%; --line-w: 17vw; --box: 8vw; }

/* =====================================================================
   ACT 2 КАРТА (frames-5→6, слева-внизу). Сама карта выезжает СЛЕВА фейдом
   (JS: .map-img opacity+translateX), а ПОСЛЕ полного появления карты собирается
   HUD «You are here» тем же принципом, что и Survival-HUD: квадратик (sq-point)
   → линия → надпись. Управляет updateMap(); показ/реверс синхронно с #ui-explore.
   ===================================================================== */
.ui-map { position: absolute; left: var(--edge); bottom: var(--bottom); pointer-events: none; }
.ui-map.is-hidden { visibility: hidden; }
/* Классическая подложка (как .hud-stat): тёмная полупрозрачная + блюр. Остров — PNG с ПРОЗРАЧНЫМ
   фоном, поэтому ложится прямо на подложку. Выезд слева + фейд ведёт JS (translateX+opacity). */
.map-img { display: block; width: 16vw;
  border: 1px solid var(--line);
  background: rgba(10,10,10,0.4); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  will-change: opacity, transform; }
.map-img img { display: block; width: 100%; height: auto; }
/* HUD «You are here»: квадратик-указатель в точке игрока НА карте (left/top в % размера карты),
   от него линия вправо ЗА рамку, надпись — справа снаружи карты. */
.map-hud { position: absolute; left: 52%; top: 75%; transform: translateY(-50%);
  display: flex; align-items: center; opacity: 0.9; white-space: nowrap; }
.mh-point { flex: none; width: 8px; height: 8px; line-height: 0; will-change: opacity, transform; }
.mh-point img { display: block; width: 8px; height: 8px; }
.mh-line { flex: none; width: 11vw; height: 1px; background: #fff; transform-origin: left center; will-change: transform; }
.mh-text {
  margin-left: 10px; color: #fff; text-transform: uppercase; will-change: opacity;
  font-family: 'Inter', sans-serif; font-weight: 400;   /* Inter Regular */
  font-size: 0.69vw;        /* «10» на базе 1440 (как остальная vw-типографика) */
  line-height: 1.35;        /* 135% */
  letter-spacing: 0.05em;   /* 5% */
}

/* Кнопка скролла — масштабируется вместе с правым блоком (vw) */
.ui-badge {
  position: relative;
  display: block;
  width: 8.06vw; height: 8.06vw;   /* ≈116px на 1440 */
  pointer-events: auto;
  cursor: pointer;
}
.ui-badge__disc {                  /* круглая SVG (текст по кругу) — медленное вращение */
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  animation: spin 16s linear infinite;
}
.ui-badge__arrows {                /* стрелки в центре — НЕ вращаются */
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  display: flex; flex-direction: column; align-items: center;
  gap: 0.1vw;
}
.ui-badge .chev {
  width: 1.667vw; height: auto;     /* ≈18px на 1440 */
  display: block;
  opacity: 0.15;
  animation: chevRun 0.9s ease-in-out infinite;
}
.ui-badge .chev path { fill: #000; }
.ui-badge .chev:nth-child(1) { animation-delay: 0s; }
.ui-badge .chev:nth-child(2) { animation-delay: 0.3s; }
.ui-badge .chev:nth-child(3) { animation-delay: 0.6s; }
/* бегущая стрелка вниз: яркий пик по очереди сверху вниз, затухание в полупрозрачность */
/* непрерывный хвост: быстрый подъём до чёрного, плавное затухание — перед и за
   чёрной стрелкой всегда идут полупрозрачные (разной прозрачности), без плоского 0 */
@keyframes chevRun { 0% { opacity: 1; } 33% { opacity: 0.55; } 67% { opacity: 0.15; } 100% { opacity: 1; } }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- снизу слева: AVAILABLE NOW + кнопки сторов (накидка) ---- */
.ui-cta { position: absolute; left: var(--edge); bottom: var(--bottom); }   /* низ — 30px (глобальное правило) */
.ui-cta__label {
  font-family: 'Inter', sans-serif;
  font-weight: 400;
  font-size: 12px; line-height: 1.35; letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #fff;                  /* 100% белого — НЕ --dim: это подпись к кнопкам, а не второстепенный лейбл */
  margin-bottom: 16px;          /* гэп до кнопок */
}
.ui-cta__row { display: flex; gap: 10px; }
.store-btn {
  pointer-events: auto;
  display: flex; align-items: stretch;
  width: 173px;                 /* одинаковый размер всех трёх */
  border: 1px solid var(--line);
  background: rgba(10,10,10,0.4);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  transition: border-color 0.2s ease;
}
/* Ховер — ТОЛЬКО для мышей. На таче :hover «залипает» после случайного свайпа мимо кнопки
   (тач триггерит :hover и не снимает его до тапа в другом месте) — именно отсюда застрявший красный
   бордер на кнопках Play on. Поэтому ховер за @media(hover:hover), а тачу — мгновенный :active ниже. */
@media (hover: hover) {
  .store-btn:hover { border-color: #CE412B; }                                /* ховер: бордер оранжевый */
  .store-btn:hover .store-btn__icon { background: rgba(206, 65, 43, 0.35); } /* + квадратик CE412B 35% (иконка остаётся белой) */
}
/* Тач (телефон/планшет): отклик на нажатии — бордер + полупрозрачная красная заливка САМОЙ
   кнопки (в мобильной раскладке иконочного квадрата нет, поэтому подсвечиваем кнопку). :active не залипает. */
@media (hover: none) {
  .store-btn:active { border-color: #CE412B; background: rgba(206, 65, 43, 0.35); }
}
.store-btn__icon {
  flex: none;
  width: 56px; height: 56px;    /* иконка 24 + паддинг 16 с каждой стороны */
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.10);          /* пассив: заливка 10% белого */
  transition: background 0.2s ease;
}
.store-btn__icon img {          /* svg уже белые — показываем напрямую */
  width: 24px; height: 24px;
  display: block;
}
.store-btn__txt {
  display: flex; flex-direction: column; justify-content: center;
  align-items: flex-start;        /* текст всегда по левому краю (и в попапе тоже) */
  padding: 0 10px;
  min-width: 0;
  text-align: left;
}
.store-btn__small {
  font-family: 'Inter', sans-serif; font-weight: 400;
  font-size: 10px; line-height: 1.35; letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.65);               /* 65% белого */
  margin-bottom: 2px;
}
.store-btn__name {
  font-family: 'Alumni Sans', sans-serif; font-weight: 800;
  font-size: 24px; line-height: 0.9; letter-spacing: -0.03em;
  text-transform: uppercase; color: #fff;
  white-space: nowrap;
}

/* ACT 4 (Base) нижняя плашка PARTICIPANTS — коробка 1-в-1 со .store-btn (бордер --line,
   фон 10,10,10/0.4 + блюр), но NON-interactive (без ховера/клика) и снизу по центру.
   JS (updateParticipants) фейдит её снизу вверх и меняет счётчик 01/24 → 02/24. */
.ui-participants {
  position: absolute;
  left: 50%; bottom: var(--bottom);
  transform: translate(-50%, 16px);            /* стартово ниже; JS поднимает translateY→0 */
  display: flex; align-items: stretch;
  border: 1px solid var(--line);
  background: rgba(10,10,10,0.4);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  opacity: 0;                                  /* стартово скрыто; ведёт JS */
  pointer-events: none;
  will-change: opacity, transform;
}
/* Эмблема-карточка (60×80, портрет) — contain с полями, сидит в квадрате с отступом
   (как на референсе), а не full-bleed cover (тот обрезал и увеличивал эмблему). */
.ui-participants .store-btn__icon img {
  width: 100%; height: 100%;
  object-fit: contain;
  padding: 6px; box-sizing: border-box;
}

/* ---- снизу справа: GLOBAL WIPE + дата + прогресс-бар (накидка) ---- */
/* ИНФОРМЕРЫ ПЕРВОГО ЭКРАНА — Online / Servers / Global wipe, внизу справа (--edge/--bottom),
   БЕЗ подложек. Лейблы/числа — те же токены, что были в .counter (Inter 10 dim + Alumni 800 20). */
.ui-informers {
  position: absolute; right: var(--edge); bottom: var(--bottom);
  display: flex; align-items: center; gap: 20px;
}
.informer__top { display: flex; align-items: center; gap: 6px; margin-bottom: 2px; }
.informer__label {
  font-family: 'Inter', sans-serif; font-weight: 400;
  font-size: 10px; line-height: 1.35; letter-spacing: 0.10em;
  text-transform: uppercase; color: var(--dim);
}
.informer__num, .ui-informers .ui-wipe__date {   /* = .store-btn__name: Alumni 800 / 24px / 90% */
  font-family: 'Alumni Sans', sans-serif; font-weight: 800;
  font-size: 24px; line-height: 0.9; letter-spacing: 0.01em;
  color: #fff; margin: 0;
}
.ui-informers .dot {                            /* зелёный пульсирующий индикатор Online (как в .counter .dot) */
  position: relative; top: -1px;                /* оптическая коррекция: поднимаем диск на 1px выше геом. центра, чтобы он сидел по центру капители ONLINE */
  /* РОВНО КРУГЛАЯ НА ЛЮБОМ DPR. Два разных источника «сплющенности», оба закрыты здесь:
     (1) flex-shrink — точка является флекс-элементом .informer__top, и при нехватке места её жмёт
         по ширине, а height остаётся; отсюда flex:none + aspect-ratio:1 (как в .tag-dot);
     (2) ДРОБНЫЙ DPR — 6px при масштабе Windows 125% это 7.5 device px, и растеризация
         круга на полпикселя даёт видимый эллипс. 8 кратно 4, поэтому даёт ЦЕЛОЕ число device px
         на 125/150/175/200% — тот же принцип, что и в сетке блоков стирания (waEnsureMask). */
  width: 8px; height: 8px; flex: none; aspect-ratio: 1; border-radius: 50%;
  background: #46d36b;
  box-shadow: 0 0 0 0 rgba(70,211,107,0.7);
  animation: pulseDot 2.4s ease-in-out infinite;
}
/* Global wipe: круглая шкала слева + (лейбл над датой) справа */
.informer--wipe { display: flex; align-items: center; gap: 12px; }
.wipe-ring { width: 32px; height: 32px; flex: none; transform: rotate(-90deg); }  /* внеш. габарит 32px; -90° → старт сверху, дуга по часовой */
.wipe-ring circle { fill: none; stroke-width: 5; }
.wipe-ring__track { stroke: rgba(255,255,255,0.35); }                             /* незаполненное поле — белое 35% */
.wipe-ring__fill {                                                                /* заполнение — CE412B (как у старого бара) */
  stroke: #CE412B; stroke-linecap: butt;        /* ровный срез концов дуги (НЕ скруглённый) */
  stroke-dasharray: 100;                        /* pathLength=100 → dashoffset в процентах */
  stroke-dashoffset: calc(100 - var(--wipe-progress, 30));
}

/* =====================================================================
   ACT 7 (Legends) — СЕКЦИЯ-СКРОЛЛ (заголовок + таблица лидеров) поверх застывшего
   последнего кадра War. МАСШТАБИРУЕТСЯ в vw (база 1440). #ui-legends — секция на весь
   экран, которая НАЕЗЖАЕТ снизу / УЕЗЖАЕТ вверх (translateY+opacity, JS). Внутри:
   .lb-bg — фон секции (изображение Legends); .lb-window — окно с ФЕЙДОМ СВЕРХУ И СНИЗУ
   (mask, привязанный к вьюпорту), в котором .lb-track (заголовок + таблица-плашка) едет
   вверх. Плашка — обычный бордер (--line) + блюр, как .store-btn/.gear-item, шириной
   в ТРЕТЬ экрана; ШАПКИ НЕТ (одна колонка — уровень, подписанный «LVL» в самой
   строке). Строки — чёрные 35%/15%.
   ===================================================================== */
.ui-legends {
  position: absolute;
  left: 0; top: 0;
  width: 100vw; height: 100dvh;   /* 1-в-1 со .scene-canvas (100dvh против сплющивания на мобилке — см. .scene-canvas) */
  transform: translateY(100dvh);  /* стартово ниже фолда; JS наезжает/выезжает (dvh — согласовано с height) */
  pointer-events: none;            /* заглушка — не интерактивная (дальше данные по API) */
  will-change: transform, opacity;
}
.ui-legends.is-hidden { visibility: hidden; }

/* Фон секции: canvas с САМОПРОИГРЫВАЮЩИМСЯ ЛУПОМ act7-frames-1 (16:9-cover, рисует
   startLegendsLoop/drawLegendsBg). Наезжает/уезжает ВМЕСТЕ с секцией. Буфер
   (width/height) выставляет sizeLbBg() под DPR; CSS-размер — на весь экран. */
.lb-bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}

/* Окно таблицы: overflow прячет то, что уехало; ФЕЙД СВЕРХУ И СНИЗУ (mask, привязанный
   к вьюпорту, когда секция стоит). Верхний фейд — чтобы заголовок/строки не налезали на
   навигатор; нижний — чтобы новые строки проявлялись снизу, а последняя выходила из фейда. */
.lb-window {
  position: absolute; inset: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 18%, #000 76%, transparent 94%);
          mask-image: linear-gradient(to bottom, transparent 0%, #000 18%, #000 76%, transparent 94%);
}

/* Трек — подвижная группа (заголовок + таблица). inset:0 → полная высота, чтобы top:% детей
   (заголовок 25%, панель 37%) считались от ВЬЮПОРТА. JS сдвигает трек вверх (translateY). */
.lb-track { position: absolute; inset: 0; will-change: transform; }

/* Заголовок (стили .ui-act2__title, как другие акты) — по центру, в верхней трети. */
.lb-title {
  position: absolute; left: 50%; top: 25%;
  transform: translate(-50%, -50%);
  width: auto; white-space: nowrap; text-align: center; margin: 0;
}

/* Таблица-плашка: обычный бордер (--line) + блюр, как .store-btn/.gear-item. */
.lb-panel {
  position: absolute; left: 50%; top: 37%;
  transform: translateX(-50%);
  width: 33.33vw;             /* ТРЕТЬ экрана (было 46vw); масштаб с окном, как asset-блоки.
                                 Шрифты остаются в vw от ВЬЮПОРТА, поэтому от сужения плашки они
                                 НЕ уменьшаются — колонка уровня просто теснее (см. .lb-row). */
  border: 1px solid var(--line);
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  overflow: hidden;           /* строки в пределах бордера */
}

/* Шапка: акцентный бар. Те же колонки, что и строки → цифры стоят под подписями. */
/* Колонка значений одна — УРОВЕНЬ; держим её в тех же 17% справа, где раньше был KDR,
   поэтому ритм таблицы не поехал. Шапки (#CE412B бар + подписи колонок) больше нет. */
.lb-row {
  display: grid;
  grid-template-columns: 1fr 17%;
  align-items: center;
  padding: 0 1.667vw;         /* ≈24px на 1440 */
}

/* Тело: зелёный «спайн» ранга слева (яркий вверху, гаснет книзу). */
.lb-body { position: relative; }
.lb-body::before {
  content: '';
  position: absolute;
  left: 0; top: 0;
  width: 0.35vw;
  height: 19.8vw;             /* ≈4.6 строк (4.31vw каждая) — как было при 46% от списка из 10 строк.
                                 В vw, а НЕ в % от .lb-body: список теперь псевдо-бесконечный (32 строки),
                                 и 46% растянули бы спайн через полэкрана. */
  background: linear-gradient(180deg, #35c04a, rgba(53,192,74,0));
  pointer-events: none;
}

/* Строки: чёрный с прозрачностью 50% / 35% (чередование). */
.lb-row { height: 4.31vw; background: rgba(0,0,0,0.5); }    /* ≈62px на 1440 */
.lb-row:nth-child(even) { background: rgba(0,0,0,0.35); }

/* Имена: Alumni Sans Regular, 24 / 90% / -3% (как просил). */
.lb-name {
  font-family: 'Alumni Sans', sans-serif; font-weight: 400;
  font-size: 1.667vw;         /* «24» на базе 1440 */
  line-height: 0.9; letter-spacing: -0.03em;
  text-transform: uppercase; color: #fff;
  text-align: left;
  /* Ники приходят из API — длина любая. Строка имеет ФИКСИРОВАННУЮ высоту (.lb-row 4.31vw),
     поэтому перенос развалил бы сетку; min-width:0 обязателен, иначе grid-колонка 1fr
     растянется под длинный ник и съест колонку уровня. */
  min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Значение: Alumni Sans ExtraBold, 24 / 90% / -3% (как .store-btn__name). */
.lb-val {
  font-family: 'Alumni Sans', sans-serif; font-weight: 800;
  font-size: 1.667vw;
  line-height: 0.9; letter-spacing: -0.03em;
  color: #fff;
  text-align: center;
}
/* Префикс «LVL» внутри значения — вместо подписи колонки, что жила в шапке.
   Связка тусклый лейбл + жирное число, как .counter__label + .counter__num в хедере. */
.lb-val__lbl {
  font-weight: 400;
  font-size: 1.111vw;         /* «16» на базе 1440 — размер бывших подписей колонок */
  color: rgba(255,255,255,0.65);
  margin-right: 0.35vw;
}

/* =====================================================================
   ACT 8 (Ecosystem) — 3 блока поверх сцены act8. Стили переиспользованы:
   заголовок/описание — .ui-act2; кнопка — оранжевый .ui-cta-btn__label; карточки
   карусели — бордер --line + счётчики (SUBSCRIBERS/num) + теги с точкой.
   Проявлением управляет updateTextBlocks (левый/правый) + updateCreators (карусель).
   ===================================================================== */
/* Слева: заголовок в одну строку на каждый <br> (левая выключка, как #ui-forward). */
#ui-ecosystem .ui-act2__title { width: auto; white-space: nowrap; }

/* Справа: описание + кнопка (как hero .ui-right / #ui-clans). */
#ui-ecosystem-cta { left: auto; right: var(--edge); top: 50%; }
/* Кнопка «Creators program» — ИДЕНТИЧНА кнопке Download (.ui-cta-btn), но без иконки и в vw
   (масштабируется с блоком). Оранжевый лейбл (.ui-cta-btn__label: Alumni 400 / lh .9 / uppercase /
   #fff / bg #CE412B) + бордер #CE412B оверлеем — всё из существующих стилей, лишь px→vw. */
.eco-btn {
  position: relative;
  display: inline-flex; align-items: stretch;
  height: 2.64vw;                 /* «38» на базе 1440 (как .ui-cta-btn) */
  margin-top: 1.53vw;             /* ≈22px гэп над кнопкой */
  pointer-events: auto;           /* #ui-overlay = none → включаем события для кнопки (клик/ховер) */
  cursor: pointer;
}
.eco-btn::after {                 /* бордер, как .ui-cta-btn::after */
  content: ''; position: absolute; inset: 0;
  border: 1px solid #CE412B; pointer-events: none;
  transition: border-color 0.2s ease;
}
.eco-btn .ui-cta-btn__label {
  padding: 0 1.11vw;              /* «16» на базе 1440 */
  font-size: 1.39vw;             /* «20» на базе 1440 */
  transition: background 0.2s ease;
}
.eco-btn:hover .ui-cta-btn__label { background: #b5381f; }   /* ховер: чуть темнее оранжевый */
.eco-btn:hover::after { border-color: #b5381f; }

/* =====================================================================
   ACT 9 (Shop) — правый блок + БЕГУЩАЯ СТРОКА снизу.
   Блок — ни одного нового шрифта/цвета: .ui-act2__title + .ui-act2__desc + .eco-btn из Act 8.
   ===================================================================== */
/* «Official shop» — справа-центр (как #ui-explore); заголовок в одну строку. */
#ui-shop { left: 51%; top: 50%; }
#ui-shop .ui-act2__title { width: auto; white-space: nowrap; }

/* Бегущая строка: full-bleed, 30px от низа (глобальное правило), фон чёрный 65%. */
.ui-marquee {
  position: absolute; left: 0; bottom: var(--bottom);
  width: 100vw; overflow: hidden;
  padding: 10px 0;
  background: rgba(0,0,0,0.65);
  opacity: 0;                          /* стартово скрыто; ведёт updateMarquee — ЧИСТЫЙ фейд, без сдвига */
  pointer-events: none;
  will-change: opacity;
}
.ui-marquee.is-hidden { visibility: hidden; }
/* Трек едет САМ (CSS-анимация по времени, не по скроллу) — лента живёт, даже когда юзер стоит.
   Трансформ трека — ТОЛЬКО анимации; фейд/сдвиг показа висит на .ui-marquee, чтобы они не бились
   (тот же принцип, что и boarFloat vs JS-трансформ).
   --mq-shift (= ширина ОДНОГО набора, px) и длительность выставляет layoutMarquee: в % делать НЕЛЬЗЯ —
   надписи в px, поэтому набор НЕ масштабируется с экраном, и фиксированный -50% оставлял бы дыру справа. */
.mq-track {
  display: flex; align-items: center; width: max-content;
  animation: mqRun 30s linear infinite;
  will-change: transform;
}
@keyframes mqRun {          /* сдвиг ровно на один набор → следующий встаёт на место предыдущего, стык бесшовный */
  from { transform: translateX(0); }
  to   { transform: translateX(var(--mq-shift, -50%)); }
}
.mq-item { flex: none; display: flex; align-items: center; }
.mq-txt {
  font-family: 'Alumni Sans', sans-serif; font-weight: 800;
  font-size: 24px; line-height: 0.9; letter-spacing: -0.03em;
  text-transform: uppercase; color: #FFDB17;
  white-space: nowrap;
}
.mq-sep {                   /* разделитель — иконка Premium, 20px; интервал до текста — 2px с боков */
  flex: none; display: block;
  width: 20px; height: 20px; margin: 0 2px;
}

/* Снизу: карусель креаторов — full-bleed лента карточек (клип по краям экрана), 30px от низа. */
.ui-creators {
  position: absolute; left: 0; bottom: var(--bottom);
  width: 100vw; overflow: hidden;
  display: flex; justify-content: center;   /* ЦЕНТРУЕТ трек (шире экрана) → крайние карточки клипятся по бокам */
  opacity: 0;                          /* стартово скрыто; ведёт updateCreators */
  pointer-events: none;
  will-change: opacity, transform;
}
.ui-creators.is-hidden { visibility: hidden; }
.creators-track {
  flex: none; display: flex; gap: 10px;   /* гэп между карточками 10px */
  will-change: transform;                 /* JS сдвигает translateX (бесконечная прокрутка) */
}
.creator-card {
  flex: none; width: 19vw;
  padding: 1.25vw 1.4vw 1.4vw;
  border: 1px solid rgba(255,255,255,0.35);   /* [1] ПОЛНЫЙ бордер: белое 35% */
  background: rgba(0,0,0,0.65);               /* [1] чёрный 65% */
  backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  display: flex; flex-direction: column;      /* вертикальные интервалы задаются margin'ами ниже */
}
/* [7] стат-блок выровнен по верхней границе верхнего паддинга (align-items:flex-start) */
.creator-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.7vw; }
.creator-av {
  flex: none; width: 3.9vw; height: 3.9vw;   /* ≈56px на 1440 */
  border-radius: 50%; overflow: hidden;
  border: 1px solid rgba(255,255,255,0.35);  /* [2] бордер аватара: белое 35% */
}
.creator-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* [7] внутри — левая выключка: значение выровнено по лейблу слева */
.creator-stat { display: flex; flex-direction: column; text-align: left; }
.creator-lbl {                              /* [3] лейбл SUBSCRIBERS: Inter Reg, белое 65%, 135%/5% */
  font-family: 'Inter', sans-serif; font-weight: 400;
  font-size: 0.69vw; line-height: 1.35; letter-spacing: 0.05em;
  text-transform: uppercase; color: rgba(255,255,255,0.65); margin-bottom: 0.14vw;
}
.creator-num {                              /* [4] счётчик: Alumni ExtraBold 24, 90%/-3% */
  font-family: 'Alumni Sans', sans-serif; font-weight: 800;
  font-size: 1.67vw; line-height: 0.9; letter-spacing: -0.03em; color: #fff;
}
.creator-name {                             /* [4] @ник: Alumni ExtraBold 24, 90%/-3% */
  font-family: 'Alumni Sans', sans-serif; font-weight: 800;
  font-size: 1.67vw; line-height: 0.9; letter-spacing: -0.03em;
  text-transform: uppercase; color: #fff;
  margin-top: 6px;                          /* [6] интервал аватар↔ник = 6px */
}
.creator-tags {
  display: flex; flex-wrap: wrap; gap: 0.4vw;
  margin-top: 16px;                         /* [6] интервал блок↔соц-платформы = 16px */
}
.creator-tag {                              /* пилюля: [3] Inter Reg, белое 65%, 135%/5% */
  display: inline-flex; align-items: center; gap: 0.35vw;
  padding: 6px 10px;                        /* [5] внутр. паддинг: горизонт. 10, верт. 6 */
  border: 1px solid rgba(206,65,43,0.6);
  font-family: 'Inter', sans-serif; font-weight: 400;
  font-size: 0.62vw; line-height: 1.35; letter-spacing: 0.05em;
  text-transform: uppercase; color: rgba(255,255,255,0.65);
}
.tag-dot {                                  /* [5] ровно круглый (aspect-ratio:1) + пульс как онлайн-индикатор */
  width: 0.42vw; aspect-ratio: 1; border-radius: 50%; flex: none;
  background: #E8674F;                       /* коралловая точка */
  box-shadow: 0 0 0 0 rgba(232,103,79,0.7);
  animation: pulseDotCoral 2.4s ease-in-out infinite;
}
@keyframes pulseDotCoral {                   /* тот же принцип, что и зелёный pulseDot на хиро */
  0%   { box-shadow: 0 0 0 0 rgba(232,103,79,0.6); opacity: 1; }
  50%  { box-shadow: 0 0 0 0.35vw rgba(232,103,79,0); opacity: 0.65; }
  100% { box-shadow: 0 0 0 0 rgba(232,103,79,0);    opacity: 1; }
}

/* =====================================================================
   ACT 10 "WIPE" (фаза AE) — экран стирания.
   Геометрия 1-в-1 с .scene-canvas (fixed + 100vw/100vh, буфер под innerWidth),
   иначе верхний слой не совпал бы со #scene пиксель-в-пиксель и появление секции
   читалось бы как скачок масштаба. Поэтому же и clientX/clientY ложатся прямо в
   координаты canvas без пересчёта (как и в #scene, 100vw = innerWidth).
   Три слоя: .wa-under (подложка) → .wa-top (кадр Act 9, в нём дыры) →
   .wa-parts (частицы, летающие ПОВЕРХ нестёртого).
   ПРЕФИКС wa-/WA_: .ui-wipe/wipeEl/wipeProgress заняты блоком «Global wipe».
   ===================================================================== */
.ui-wipeact {
  position: fixed;
  inset: 0;
  width: 100vw; height: 100dvh;   /* 1-в-1 со .scene-canvas (100dvh против сплющивания на мобилке — см. .scene-canvas) */
  pointer-events: auto;          /* единственная интерактивная секция рядом с #ui-clanpick */
  cursor: none;                  /* курсор заменяет .wa-cursor */
  will-change: opacity;
}
.ui-wipeact.is-hidden { visibility: hidden; }
/* Слой тряски: сам по себе нейтральный (transform ставит JS лишь пока трясёт, и снимает в ноль —
   иначе постоянный scale зумил бы экран стирания относительно #scene, и появление секции читалось бы). */
.wa-shake {
  position: absolute; inset: 0;
  will-change: transform;
}
.wa-under, .wa-top, .wa-parts {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}
/* Мерцающая заливка (непрозрачность ведёт JS, 0..0.35). Оранжевый — тот же, что был на иконке.
   Оверскан, как у .wa-shake: заливка ездит вместе с тряской, поэтому должна вылезать за кромку. */
.wa-tint {
  position: absolute; inset: -2px;
  background: #CE412B;
  opacity: 0;
  pointer-events: none;
  will-change: opacity;
}
/* Иконка-курсор. Десктоп: JS ведёт её за указателем (translate в px вьюпорта).
   Тач: сидит по центру и её ТЯНУТ (см. .is-touch). Крутится по часовой. */
.wa-cursor {
  position: absolute;
  left: 0; top: 0;
  width: 120px; height: 120px;
  margin: -60px 0 0 -60px;       /* центр иконки в точке translate */
  pointer-events: none;
  will-change: transform;
}
/* Появление: масштаб ИЗ ЦЕНТРА + фейд (не резкий показ). Отдельный уровень от .wa-cursor (translate в JS)
   и от <img> (вращение) — иначе три трансформа затирали бы друг друга. */
.wa-cursor__in {
  display: block;
  width: 100%; height: 100%;
  opacity: 0;
  transform: scale(0.15);
}
.ui-wipeact.is-in .wa-cursor__in { animation: waCursorIn 0.5s cubic-bezier(.4,0,.2,1) both; }
@keyframes waCursorIn {
  from { opacity: 0; transform: scale(0.15); }
  to   { opacity: 1; transform: scale(1); }
}
/* Исчезновение = ТА ЖЕ анимация задом наперёд (`reverse`), а не отдельные keyframes: иначе два описания
   одного движения разъехались бы при первой же правке появления. Идёт и на выходе из акта (.is-out, секция
   ещё живёт лишние 0.5с — см. waApplyVisible), и на входе в финал (.is-final, секция остаётся).
   ДОЛЖНО стоять ПОСЛЕ правила .is-in: специфичность одинаковая (три класса), поэтому выигрывает порядком —
   в финале .is-in ещё висит на секции. */
.ui-wipeact.is-out .wa-cursor__in,
.ui-wipeact.is-final .wa-cursor__in { animation: waCursorIn 0.5s cubic-bezier(.4,0,.2,1) reverse both; }
/* Пока иконка улетает, секция уже ничья: канвасы прочь (иначе сквозь дырки светила бы подложка
   вместо Act 9), взаимодействие и курсор — назад странице. */
.ui-wipeact.is-out { pointer-events: none; cursor: auto; }
.ui-wipeact.is-out .wa-shake { visibility: hidden; }
.wa-cursor img {
  width: 100%; height: 100%;
  display: block;
  /* Иконка БЕЛАЯ, хотя в самом SVG заливка оранжевая (#CE412B): красим фильтром, а не правкой
     ассета — brightness(0) кладёт все цвета в чёрный, invert(1) делает из него белый; альфа цела.
     Оранжевый теперь живёт в .wa-tint. */
  filter: brightness(0) invert(1);
  animation: waSpin 4s linear infinite;   /* ПРОТИВ часовой: отрицательный угол. 4s = в 1.5× быстрее 6s */
  user-select: none; -webkit-user-drag: none;
}
@keyframes waSpin { to { transform: rotate(-360deg); } }
/* Финал (фаза AF): стирать уже нечего — иконка улетает (см. reverse выше), обычный курсор
   назад (там кнопки). Вместо display:none — pointer-events:none, потому что анимации нужен живой
   элемент. И это ОБЯЗАТЕЛЬНО: на таче .is-touch даёт .wa-cursor pointer-events:auto, и невидимая
   иконка 120px по центру глотала бы тапы по кнопкам финала (тот же класс бага, что с лоадером). */
/* КРИТИЧНО: секция стирания (fixed, 100vw/100dvh, pointer-events:auto) ОСТАЁТСЯ ВИДИМОЙ под
   финалом (waInAE покрывает AE+AF — чтобы под полупрозрачным кросс-фейдом был фон). Но когда финал
   уже ПОЛНОСТЬЮ накрыл (is-final = после кросс-фейда), эта полноэкранная секция на таче глотала скролл
   (iOS: fixed pointer-events:auto элемент ест свайп даже без preventDefault) — «на финале не
   пускает вверх / меню не работает». Поэтому на is-final отдаём секцию сквозь себя: pointer-events:none —
   тач идёт на страницу (скролл), а фон кросс-фейда остаётся (visibility не трогаем). */
.ui-wipeact.is-final { pointer-events: none; cursor: auto; }
.ui-wipeact.is-final .wa-cursor,
.ui-wipeact.is-out .wa-cursor { pointer-events: none; }
/* Иконка — элемент для ПЕРЕТАСКИВАНИЯ (тач), поэтому ей нужны события. БЕЗ .is-touch в селекторе:
   режим решает само событие (pointerType в waOnMove/waOnDragStart), а класс в девтулзах может
   быть из прошлого — привязка к нему делала иконку неперетаскиваемой. Десктопу это не вредит:
   события с иконки всплывают на секцию, поэтому waOnMove по движению мыши работает как и работал.
   А вот :not(.is-final):not(.is-out) — не косметика: без него это правило (ниже по тексту)
   выиграло бы у guard-а выше по порядку и вернуло pointer-events:auto улетающей иконке,
   а та глотала бы тапы по кнопкам финала. */
.ui-wipeact.is-touch { cursor: auto; }
.ui-wipeact:not(.is-final):not(.is-out) .wa-cursor { pointer-events: auto; touch-action: none; }
/* Пока гейт держит (акт ещё не «открыт» на WA_RELEASE) тач-перетаскивание по любой точке сцены
   должно СТИРАТЬ, а не панорамировать страницу, поэтому глушим touch-action на всей секции. Класс ведёт
   JS (waGateSet) и снимает его на релизе — тогда touch-action возвращается, чтобы доскроллить до
   финала. Колесо не затрагивается (отдельный гейт waGateWheel), поэтому в девтулзах скролл мышью жив. */
.ui-wipeact.is-wiping { touch-action: none; }
@media (prefers-reduced-motion: reduce) {
  .wa-cursor img { animation: none; }
  .wa-cursor { display: none; }        /* стирание выключено — курсор-иконка не нужна */
}

/* =====================================================================
   ACT 10, ФИНАЛ (фаза AF) — «The island resets».
   Геометрия — 1-в-1 со .scene-canvas / .ui-wipeact (fixed + 100vw/100vh),
   чтобы кросс-фейд секвенции шёл ровно поверх недостёртого экрана.
   Футер — ДВЕ секции: соцсети (справа) и легалка (полоса по низу).
   Никаких новых токенов: --edge, --line, --dim, .counter__label, .ui-cta-btn.
   ===================================================================== */
.ui-reset {
  position: fixed;
  inset: 0;
  width: 100vw; height: 100dvh;   /* 1-в-1 со .scene-canvas (100dvh против сплющивания на мобилке — см. .scene-canvas) */
  pointer-events: none;          /* интерактив включают сами ссылки (как везде в overlay) */
  opacity: 0;                    /* показ ведёт updateResetAct */
  will-change: opacity;
}
.ui-reset.is-hidden { visibility: hidden; }
.rs-bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}

/* Кнопка «на первый экран» — только на финале. Красный диск (тот же #CE412B, что и
   скролл-бейдж) + «бегущая» стрелка ВВЕРХ (rotate180 на контейнере разворачивает и направление
   стрелок, и порядок «бега» — снизу вверх). Позицию по вертикали (30px над футером на
   десктопе; центр легалки на мобилке) ставит layoutResetAct через --rs-top-bottom.
   Показ ведёт updateResetAct той же обёрткой f (fade+подъём), что и подвал. */
.rs-top {
  position: absolute;
  right: var(--edge);
  bottom: var(--rs-top-bottom, 150px);
  width: 80px; height: 80px;
  border-radius: 50%;
  background: #CE412B;
  border: 0; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  pointer-events: auto;
  opacity: 0;                       /* показ ведёт updateResetAct */
  will-change: opacity, transform;
  -webkit-tap-highlight-color: transparent;
  transition: filter 0.2s ease;
}
.rs-top:hover { filter: brightness(1.08); }
.rs-top__arrows {
  display: flex; flex-direction: column; align-items: center;
  gap: 2px;
  transform: rotate(180deg);        /* стрелки вниз → вверх; заодно реверс «бега» на снизу-вверх */
}
.rs-top .chev {
  width: 18px; height: auto; display: block;
  opacity: 0.15;
  animation: chevRun 0.9s ease-in-out infinite;
}
.rs-top .chev path { fill: #000; }  /* чёрные на красном — как на скролл-бейдже (#scrollBtn) */
.rs-top .chev:nth-child(1) { animation-delay: 0s; }
.rs-top .chev:nth-child(2) { animation-delay: 0.3s; }
.rs-top .chev:nth-child(3) { animation-delay: 0.6s; }

/* Футер: обе секции держит один столбец по низу. */
.rs-footer {
  position: absolute;
  left: 0; right: 0; bottom: 0;
}
/* 1) Соцсети — ПО ЦЕНТРУ, над легалкой. 30px от легалки по глобальному правилу низа.
   align-items:center центрирует и сам блок по ширине футера, и лейбл над рядом иконок (оба —
   элементы этой колонки, поэтому центрируются по одной оси). */
.rs-social {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 0 var(--edge) 30px;
}
/* Лейбл и легалка — 1-в-1 токен «Play on» (.store-btn__small): Inter 400 / 10px / 135% / 5% / 65%.
   НЕ .counter__label: там letter-spacing 10% и --dim (50%) — другой лейбл, легко перепутать. */
.rs-social__label {
  font-family: 'Inter', sans-serif;
  font-weight: 400;
  font-size: 10px;
  line-height: 1.35;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.65);
}
.rs-social__row { display: flex; align-items: center; gap: 22px; }
.rs-social__ico {
  display: block;
  pointer-events: auto;
  opacity: 0.75;
  transition: opacity 0.2s ease;
}
.rs-social__ico:hover { opacity: 1; }
.rs-social__ico img { display: block; width: 20px; height: 20px; }
/* 2) Легалка — полоса на всю ширину по самому низу. ТРИ зоны: лейбл Uprising (слева),
   Privacy·Terms (СТРОГО по центру полосы), копирайт (справа). Сетка 1fr auto 1fr даёт
   центральной колонке настоящий центр независимо от ширины боков (space-between так не умеет). */
.rs-legal {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 14px;
  padding: 14px var(--edge);
  background: #000;              /* подложка — чёрная 100%; она же и отбивает полосу от неба,
                                    поэтому --line-бордера сверху тут не надо */
  font-family: 'Inter', sans-serif;
  font-weight: 400;
  font-size: 10px;
  line-height: 1.35;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.65);
}
/* Лейбл «Created by Uprising Agency» — иконка up_sign (оранжевая в самом SVG) + текст тем
   же Inter-10 токеном; линк на uprising.agency. */
.rs-legal__by {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  justify-self: start;
  pointer-events: auto;
  color: inherit;
  transition: color 0.2s ease;
}
.rs-legal__by:hover { color: #fff; }
.rs-legal__mark { display: block; width: auto; height: 16px; }
.rs-legal__links { display: inline-flex; align-items: center; gap: 14px; justify-self: center; }
.rs-legal__links a { pointer-events: auto; transition: color 0.2s ease; }
.rs-legal__links a:hover { color: #fff; }
.rs-legal__dot { opacity: 0.5; }
/* Копирайт — теперь линк на hyperhug.com; тот же Inter-10 токен, hover → белый. */
.rs-legal__copy { justify-self: end; pointer-events: auto; color: inherit; transition: color 0.2s ease; }
.rs-legal__copy:hover { color: #fff; }
/* Принудительный перенос лейбла/копирайта — ТОЛЬКО в портрете (см. мобильный блок); на десктопе
   скрыт, поэтому оба остаются в одну строку. */
.rs-legal__nl { display: none; }

/* Финальный текст — ПО ЦЕНТРУ (passY-блок, как #ui-night/#ui-war: X центрует сам applyPassYBlock). */
.ui-final {
  left: 50%;
  top: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}
.ui-final .ui-act2__title { width: auto; white-space: nowrap; }
/* Лейбл ряда кнопок — 1-в-1 с «AVAILABLE NOW» (.ui-cta__label) на первом экране: Inter 400 /
   12px / 135% / 5% / БЕЛЫЙ 100%. Это подпись к кнопкам, поэтому 12px и полный белый, а не 10px/65%
   токена «Play on» (.store-btn__small) — тот для второстепенных лейблов (футер, «Play on» в кнопке). */
.ui-final__label {
  margin-top: 30px;
  font-family: 'Inter', sans-serif;
  font-weight: 400;
  font-size: 12px;
  line-height: 1.35;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #fff;
}
.ui-final__stores { margin-top: 12px; }   /* .ui-cta__row уже даёт flex+gap:10; .store-btn — сам pointer-events:auto */
/* Подложка кнопок финала — чёрная 65% (базовые .store-btn по сайту — rgba(10,10,10,.4); тут глуше,
   потому что под ними светлое небо). Тот же чёрный 65%, что и в .ui-cta-btn__icon. */
.ui-final .store-btn { background: rgba(0, 0, 0, 0.65); }

/* ---------------------- адаптив (накидка) ---------------------- */
/* Бывший 900-блок — теперь ТОЛЬКО телефон (≤580), как дополнение к основному
   мобильному блоку ниже: единственное, что он реально даёт, — размер карточек .gear-item/.wpn-item
   (40vw), которого мобильный блок не задаёт; остальные его правила перекрывает мобильный блок
   (он идёт позже). Планшет (581–1024) сюда НЕ попадает — его ведёт отдельный блок внизу. */
/* служебный перенос строки только на мобильных (напр. заголовок #downloadModal) */
.only-mob { display: none; }
/* обратное: перенос только на десктопе/планшете, а на мобильном прячем (напр. #ui-act2 «The Rules of Survival» — на мобилке в одну строку) */
.only-desk { display: inline; }

@media (max-width: 580px) {
  .only-mob { display: inline; }
  .only-desk { display: none; }
  .ui-nav { display: none; }
  /* на мобильных vw-блоки стали бы слишком узкими — даём большие доли ширины (накидка) */
  .ui-hero__title { width: 72vw; font-size: 9.5vw; }   /* 11→9.5vw: меньший кегль текст-блоков на мобилке (компенсация высоты баров браузера) */
  .ui-right { width: 60vw; }
  .ui-desc { font-size: 3.15vw; }
  .ui-act2__title { width: 72vw; font-size: 9.5vw; }   /* 11→9.5vw (компенсация баров браузера) */
  .ui-act2__desc { width: 72vw; font-size: 3.15vw; }   /* 3.6→3.15vw */
  #ui-gather .ui-act2__title { width: 72vw; white-space: normal; }  /* на мобильном возвращаем перенос */
  #ui-explore { left: var(--edge); }                                /* на мобильном — левая выключка */
  #ui-explore .ui-act2__title { width: 72vw; white-space: normal; }
  #ui-night .ui-act2__title { width: 72vw; white-space: normal; }   /* центр остаётся (left:50%) */
  #ui-war .ui-act2__title { width: 72vw; white-space: normal; }     /* центр остаётся (left:50%) */
  #ui-conquer .ui-act2__title { width: 72vw; white-space: normal; } /* #ui-conquer уже слева (базовое .ui-act2) */
  #ui-forward .ui-act2__title { width: 72vw; white-space: normal; }
  #ui-challenges { left: var(--edge); top: 48%; }                   /* на мобильном — левая выключка */
  #ui-clans { left: var(--edge); right: auto; top: 48%; }           /* на мобильном #ui-clans тоже слева (desktop right:--edge сбрасываем) */
  #ui-challenges .ui-act2__title { width: 72vw; white-space: normal; }
  #ui-shelter .ui-act2__title { width: 72vw; white-space: normal; } /* #ui-shelter уже слева (базовое .ui-act2) */
  .gear-item { width: 40vw; height: 42vw; }
  .wpn-item { width: 40vw; height: 42vw; }
  .ui-cta__row { flex-wrap: wrap; }
  /* Заголовок + таблица лидеров (Legends): на мобильном — заголовок выше, таблица шире/ниже. */
  .lb-title { top: 22%; width: 84vw; white-space: normal; }   /* кегль заголовка ставит правило в блоке ≤1024 (43px), тут только позиция/перенос */
  .lb-panel { width: 92vw; top: 40%; }
  .lb-row { padding: 0 3.5vw; height: 12vw; }
  .lb-name { font-size: 5vw; }
  .lb-val { font-size: 5.6vw; }
  .lb-val__lbl { font-size: 2.6vw; margin-right: 1vw; }
  .lb-body::before { height: 55vw; }     /* те же ≈4.6 строк, но строка тут 12vw */
}

/* ---------------------------------------------------------------------
   ЛОАДЕР НА УЗКОМ ЭКРАНЕ — три кнопки столбиком, на всю ширину.

   БРЕЙКПОИНТ ПОСЧИТАН, А НЕ ВЗЯТ НА ГЛАЗ: ряд из трёх .store-btn в
   десктопном виде занимает 3×173 + 2×10 (gap) = 539px, плюс поля по 20px
   с боков = 579px. То есть 580px — это ровно та граница, где кнопки ещё стоят
   «полноценно как в десктопе»; ниже они уже не влезают и идут вертикально.
   МЕНЯЕШЬ width .store-btn или gap .ui-cta__row — ПЕРЕСЧИТАЙ это число.

   Вертикальный ритм (52px логотип→бар, 52px бар→текст, 26px текст→кнопки)
   уже совпадает с макетом и живёт в базовых правилах — тут только горизонталь.

   Поле 20px, а не привычные --edge(40px): на узком экране 40px съедают
   слишком много ширины, и в макете прелоадера именно 20.

   БРЕЙКПОИНТ РАСШИРЕН 580 → 1024: этот блок задаёт СТРУКТУРУ (низ-прибитые стеки,
   навбар внизу, тач-видео/вайп через --mobile, шейд) для телефона И планшета.
   Телефон (≤580) остаётся байт-в-байт (он ⊂ ≤1024, тех же правил). Планшет
   (581–1024) берёт ту же структуру, но РАЗМЕРЫ перекрывает отдельный блок ниже
   (@media 581–1024) — контент там в vw, то есть течёт с вьюпортом, как десктоп.
   --------------------------------------------------------------------- */
@media (max-width: 1024px) {
  /* --- общая трактовка кнопок: лоадер + попап Download --- */
  .loader__stores,
  .ui-modal .ui-cta__row { width: 100%; flex-direction: column; gap: 10px; flex-wrap: nowrap; }
  /* row-reverse: в разметке иконка идёт ПЕРВОЙ, а в мобильном макете она справа —
     поэтому просто разворачиваем ряд, вместо того чтобы дублировать HTML. */
  .loader__stores .store-btn,
  .ui-modal .store-btn { width: 100%; flex-direction: row-reverse; }
  .loader__stores .store-btn__txt,
  .ui-modal .store-btn__txt { flex: 1 1 auto; padding: 0 16px; }

  /* --- прелоадер --- */
  .loader__inner { width: 100%; padding: 0 20px; }
  .loader__bar   { width: 100%; }        /* перебивает min(539px, 82vw) */
  .loader__note  { width: 100%; }

  /* --- попап Download --- */
  /* .ui-modal уже имеет padding:20px, поэтому коробка на всю ширину даёт те же поля 20,
     что и у прелоадера. Внутренний паддинг с 50 → 24: на 350px коробки 50 съели бы
     треть ширины. Крестик остаётся как был (он и в десктопе #CE412B, как в макете). */
  .ui-modal__box   { width: 100%; padding: 30px 24px; }
  .ui-modal__title { font-size: 32px; margin-bottom: 24px; }

  /* =====================================================================
     СЕТКА МОБИЛЬНОЙ ВЕРСИИ (задаёт расположение ВСЕХ повторяющихся элементов)
     Поля контента: с боков 20, сверху 16 (шапка), снизу 20.
     ===================================================================== */
  :root {
    --edge: 20px;
    /* Навигатор переехал вниз, поэтому «30 от низа» теперь означает «30 от навигатора».
       Число ВЫВЕДЕНО, не на глаз: 20 (поле под навбаром) + 30 (высота навбара:
       .nav-bar 4 + margin-top 8 + .nav-labels 18) + 30 (сам отступ) = 80.
       Меняешь геометрию навбара — пересчитай. */
    --bottom: 80px;
    --mobile: 1;                /* флаг раскладки для JS — см. :root выше */
  }

  /* --- затемнение низа экрана: только мобильная версия --- */
  .ui-shade { display: block; }

  /* --- кнопка плей видео-фазы: только мобильная --- */
  /* Именно это свойство — единственный источник правды о «мобильном режиме видео»:
     JS не проверяет ширину экрана, а смотрит, показана ли кнопка вообще
     (videoManualMode). Поэтому брейкпоинт остаётся только тут. */
  .ui-vplay { display: block; }
  .ui-vpause { display: block; }

  /* --- навигатор скролла: из шапки вниз, на всю ширину --- */
  /* Два обязательных момента:
     1) display:flex — блок 900px прячет навигатор (.ui-nav{display:none}), без этого
        он просто не появится внизу;
     2) position:FIXED, а не absolute — навигатор лежит ВНУТРИ .ui-header, а та
        сама absolute, поэтому absolute-bottom считался бы от шапки (проверено: клеился
        под ней на y=14), а не от экрана. Тут нужно именно крепление к вьюпорту.
        Скроллбарной ловушки 100vw это не касается: на мобильных гуттера нет, а сам
        навбар pointer-events:none. Выйдя из flex-потока шапки, он заодно отдаёт
        логотип и счётчики на space-between — ровно как в макете. */
  /* --nav-lift — ЕДИНСТВЕННОЕ, что сдвигает навигатор с его 20px: в финале внизу появляется
     чёрная полоса легалки, и [WIPE] ложился просто на неё. JS (rsNavLift) ведёт эту переменную
     от 0 до высоты полосы вместе с появлением подвала. Вне финала она 0, поэтому правило
     читается как обычные bottom:20px. */
  .ui-nav {
    display: flex;
    position: fixed;
    top: auto; bottom: calc(20px + var(--nav-lift, 0px));
    left: var(--edge); right: var(--edge);
    width: auto; height: auto; transform: none;
  }

  /* --- первый экран: вертикальный стек, прижатый к низу --- */
  /* Порядок в разметке (заголовок → описание → CTA → wipe) НЕ совпадает с мобильным
     (wipe → заголовок → описание → CTA), поэтому переставляем через order, а не правкой HTML —
     десктоп должен остаться байт-в-байт тем же. */
  .ui-hero {
    position: absolute;
    left: var(--edge); right: var(--edge); bottom: var(--bottom);
    display: flex; flex-direction: column;
  }
  .ui-hero__title, .ui-right, .ui-cta, .ui-informers {
    position: static; width: auto; max-width: none;
    left: auto; right: auto; top: auto; bottom: auto;
  }
  /* ИСЧЕЗНОВЕНИЕ ПЕРВОГО ЭКРАНА (см. updateHeroFade). В десктопе блоки разъезжаются в
     разные стороны и центрированы по вертикали; тут — все три едут ВЛЕВО ОДНИМ ДВИЖЕНИЕМ:
     --hero-ty: 0   — блоки в потоке, вертикального центрирования нет, сдвигать не надо
     --hero-dir: -1 — и заголовок, И правый блок идут влево (в десктопе .ui-right имеет +1)
     --wipe-dx/-dy  — индикатор Global wipe едет влево вместе с текстом, а не вниз, как в десктопе */
  .ui-hero__title, .ui-right { --hero-ty: 0; --hero-dir: -1; }
  .ui-informers { --wipe-dx: -1; --wipe-dy: 0; }

  /* Кнопки шапки на мобайле — МЕЛЬЧЕ десктопа (там 30px/16px выглядели слишком крупными).
     CREATORS остаётся спрятанным (в меню). DOWNLOAD — текстом, меньше. SHOP показываем как
     КВАДРАТНУЮ кнопку-иконку (корзина shop.svg), стиль --primary как у Download, линк тот же на магазин. */
  #creatorsBtn { display: none; }
  /* Высота кнопок = высота лого (120px шир. → 39px выс.); все одинаковые, квадратные остаются квадратом. */
  .hdr-btn { height: 39px; padding: 0 14px; font-size: 18px; }
  .ui-menu-btn { width: 39px; height: 39px; }
  #shopBtn { padding: 0; width: 39px; }               /* квадрат 39×39 под иконку */
  #shopBtn .hdr-btn__label { display: none; }         /* без надписи «Shop» */
  #shopBtn .hdr-btn__ico { display: block; width: 22px; height: auto; }

  /* Информеры — строка вверху hero-колонки (как раньше стоял Global wipe), слева.
     Компактнее gap + перенос как предохранитель на узких экранах. */
  .ui-informers   { order: 1; margin-bottom: 20px; gap: 24px; flex-wrap: wrap; row-gap: 16px; }
  .ui-hero__title { order: 2; font-size: 43px; margin-bottom: 16px; }  /* 64→43 (−⅓ кегль), гэп 30→16 */
  .ui-right       { order: 3; margin-bottom: 0; }                      /* CTA ниже скрыто → лишнего воздуха под правым блоком не нужно */
  /* Блок «Available now» + кнопки Play-on на мобайле НЕ нужен: загрузку ведёт кнопка
     Download в шапке (см. updateHeroFade — она видима на первом экране на мобайле). Прячем
     весь .ui-cta. Общее правило .ui-cta .store-btn / .ui-final .store-btn остаётся — оно ещё
     обслуживает кнопки ФИНАЛА. */
  .ui-cta         { display: none; }

  .ui-desc   { font-size: 14px; margin: 0; }
  /* «over the» одним куском → «over» переезжает на третью строку вместо того,
     чтобы висеть в конце второй (см. комментарий в разметке .ui-desc). */
  .ui-desc .nb { white-space: nowrap; }
  /* #ui-challenges «Stay Ahead of the Challenges»: в планшете/мобайле держим «the Challenges»
     вместе, чтобы артикль THE переехал на 2-ю строку («Stay Ahead of» / «The Challenges»).
     Класс МОБИЛЬНО-СКОУПНУТЫЙ (не глобальный .nb): на десктопе «THE CHALLENGES» шире
     колонки 22.64vw и вылезло бы — поэтому nowrap только тут, до 1024. */
  .ui-act2__title .nb-mob { white-space: nowrap; }
  .ui-badge  { display: none; }        /* круглого скролл-бейджа в мобильном макете нет */
  .rs-top { width: 60px; height: 60px; }   /* кнопка «на первый экран» пропорционально меньше; вертикаль ставит layoutResetAct (центр легалки) */
  .rs-top .chev { width: 14px; }

  /* Кнопки сторов ПЕРВОГО ЭКРАНА И ФИНАЛА — не такие, как в лоадере/попапе: там вертикальный
     стек с иконкой справа, а тут три квадрата в ряд, иконка СВЕРХУ и без серой
     подложки. Поэтому правило отдельное (.ui-cta/.ui-final), а не общее. flex:1 делит ширину
     на три равные части, поэтому 3×110 + 2×10 = 350 выходит именно из поля 20px.
     ФИНАЛ ЕДЕТ ТЕМ ЖЕ СПИСКОМ, а не своей копией этих чисел: кнопки там те же, и
     расползание двух описаний одного вида — вопрос времени. Без этого ряд из трёх
     .store-btn по 173px (539 вместе) вылезал за экран в обе стороны. */
  .ui-cta__row { flex-wrap: nowrap; gap: 10px; }
  .ui-cta .store-btn,
  .ui-final .store-btn {
    flex: 1 1 0; width: auto; min-width: 0; height: 110px;
    /* Просвет «иконка → текст» = ПАДДИНГ кнопки (12). Было space-between: контент распирало
       по всей высоте 110px и между иконкой и подписью зияло 26.5px — вдвое больше поля.
       center+gap оставляет ту же высоту плитки, но внутренний ритм становится единым: 12 везде. */
    flex-direction: column; align-items: flex-start; justify-content: center; gap: 12px;
    padding: 12px;
  }
  .ui-cta .store-btn__icon,
  .ui-final .store-btn__icon { width: auto; height: auto; background: none; }
  .ui-cta .store-btn:hover .store-btn__icon,
  .ui-final .store-btn:hover .store-btn__icon { background: none; }   /* подложки нет — нечего подсвечивать */
  .ui-cta .store-btn__icon img,
  .ui-final .store-btn__icon img { width: 24px; height: 24px; }   /* меньшая иконка на мобилке (было 32) */
  .ui-cta .store-btn__txt,
  .ui-final .store-btn__txt { padding: 0; }
  .ui-cta .store-btn__name,
  .ui-final .store-btn__name { font-size: 20px; }

  /* =====================================================================
     ТЕКСТОВЫЕ БЛОКИ АКТОВ — прибиты к низу, типографика как на первом экране.
     На десктопе каждый блок стоит по-своему (left/top в процентах, кто-то справа,
     кто-то по центру) и центрируется по вертикали через translate(...,-50%), который
     ИНЛАЙНОМ пишет JS. В портрете все они — одна и та же композиция: на всю
     ширину контента, прижатые к низу на те же 30px над навигатором, что и
     остальные нижние блоки (--bottom(80) = 20 поле навбара + 30 его высота + 30).
     Кегль 64/14 — 1-в-1 с hero (.ui-hero__title / .ui-desc в этом же блоке), гэп 30
     тоже даёт заголовок, а не описание.

     --tb-ty/--tb-tx ГАСЯТ центрирование, которое JS вписывает инлайном: без них привязка
     к низу не сработала бы вообще, потому что инлайновый transform бьёт любую вёрстку
     (тот же приём и та же причина, что и --hero-ty на первом экране).

     #ui-overlay в селекторе — не украшение: позиции блоков заданы ЧЕРЕЗ ID
     (#ui-explore{left:51%} и т.п.), и правило на самих классах их не перебило бы.

     ИСКЛЮЧЕНИЯ (оставлены как есть, осознанно):
     - #ui-ecosystem + #ui-ecosystem-cta — Act 8 держит эти ДВА блока на экране
       ОДНОВРЕМЕННО (заголовок слева, описание с кнопкой справа). Прибить оба к
       низу означало бы положить их друг на друга, а как именно их складывать в портрете
       — вопрос к макету, не механический.
     #ui-final в исключениях БЫЛ и больше НЕ ЕСТЬ: финал — такой же текстовый блок, лишь
     прибитый не к --bottom, а выше на соцсети (см. его правило ниже).
     ===================================================================== */
  /* :not(:where(...)) — а не :not(#a):not(#b):not(#c): :where() даёт НУЛЕВУЮ
     специфичность, поэтому всё правило остаётся (1,1,0), и точечное исключение вроде
     «#ui-overlay #ui-shelter» (2,0,0) может его перебить. С цепочкой :not() каждый id
     добавлял бы свой вес, селектор становился (4,1,0) — и ни один разумный override не
     проходил бы без наращивания такого же хвоста. */
  #ui-overlay .ui-act2:not(:where(#ui-ecosystem, #ui-ecosystem-cta)) {
    left: var(--edge); right: var(--edge);
    top: auto; bottom: var(--bottom);
    width: auto;
    text-align: left;
    --tb-tx: 0; --tb-ty: 0;
  }
  /* Два блока делят экран с плашкой PARTICIPANTS (она тоже прибита к --bottom, по
     центру): #ui-shelter в Act 4 и #ui-clans в Act 5 — плашка появляется вместе с
     первым и держится почти до конца act5-frames-1, то есть сквозь второй. Поднимаем
     текст РОВНО на 30 над ней. 88 — выведенное, не на глаз: 58 (высота плашки: иконка 56
     + 2×1 бордер) + 30. Меняешь геометрию плашки — пересчитай.
     #ui-createbig (тот же Act 5) сюда НЕ входит: он появляется с pFR(30), когда
     плашка уже исчезла (pFQ(90)), так что делить с ней экран ему не с чем.
     Подъём статический, а плашка уходит чуть раньше текста — но окно, где текст уже без
     неё, это лишь хвост pFQ(90)→pFR(24), на котором он и так уезжает. */
  #ui-overlay #ui-shelter,
  #ui-overlay #ui-clans { bottom: calc(var(--bottom) + 88px); }

  /* #ui-shop делит экран с бегущей строкой — она тоже прибита к --bottom, на всю
     ширину, и ложилась просто на кнопку «Open the shop» (замерено: строка 722…764,
     кнопка 726…764 — кнопки не видно вообще). Поднимаем блок на 30 над строкой.
     72 — выведенное: 42 (высота строки: паддинги 10+10 + строка 22) + 30.
     Меняешь кегль строки или её паддинги — пересчитай. */
  #ui-overlay #ui-shop { bottom: calc(var(--bottom) + 72px); }
  /* width:auto снимает колонку 22.64vw/72vw, white-space:normal — точечные nowrap'ы,
     заданные для десктопных однострочных заголовков (#ui-gather, #ui-explore, …). */
  #ui-overlay .ui-act2:not(:where(#ui-ecosystem, #ui-ecosystem-cta)) .ui-act2__title {
    width: auto; white-space: normal;
    font-size: 43px; margin: 0 0 16px;   /* 64→43 (−⅓ кегль на мобилке), гэп заголовок→описание 30→16 */
  }
  /* margin:0 заодно сбрасывает auto-маргины от центрированных блоков (#ui-night, #ui-war). */
  #ui-overlay .ui-act2:not(:where(#ui-ecosystem, #ui-ecosystem-cta)) .ui-act2__desc {
    width: auto; margin: 0; font-size: 14px;
  }

  /* --- Act 8 Ecosystem: карусель убрана, два блока становятся одним нижним --- */
  /* Карусель креаторов в портрете не показываем. display, а не visibility — блок должен уйти
     из раскладки. JS это переживает: updateCreators() крутит ленту лишь при
     creatorsSetW > 0, а layoutCreators() у спрятанного блока намеряет 0 и сам выключается. */
  .ui-creators { display: none; }

  /* Эти два блока были исключены из общего правила текстовых блоков именно потому, что Act 8
     держит их на экране ОДНОВРЕМЕННО (заголовок слева, описание с кнопкой справа) и прибить
     оба к низу означало бы положить их друг на друга. Теперь, когда карусель ушла,
     они складываются в один нижний блок: заголовок → описание → кнопка. */
  #ui-overlay #ui-ecosystem,
  #ui-overlay #ui-ecosystem-cta {
    left: var(--edge); right: var(--edge);
    top: auto; width: auto; text-align: left;
    --tb-tx: 0; --tb-ty: 0;
  }
  #ui-overlay #ui-ecosystem-cta { bottom: var(--bottom); }
  /* Заголовок стоит на 30 выше блока описания+кнопки. 155 — выведенное и замеренное, не на глаз:
     высота этого блока 125 (описание 3 строки = 57 + гэп 30 + кнопка 38) + 30 промежутка.
     Меняется копирайтинг описания — ПЕРЕСЧИТАЙ: лишняя строка описания это +19. */
  #ui-overlay #ui-ecosystem { bottom: calc(var(--bottom) + 155px); }
  #ui-overlay #ui-ecosystem .ui-act2__title {
    width: auto; white-space: normal; font-size: 43px; margin: 0;   /* 64→43 (−⅓), в паре с остальными заголовками */
  }
  #ui-overlay #ui-ecosystem-cta .ui-act2__desc { width: auto; margin: 0; font-size: 14px; }
  /* Кнопка тоже задана в vw под десктоп (2.64vw = 10px в портрете) — фиксируем на
     десктопных величинах: 38 высота, 16 паддинг, 20 кегль; гэп над ней — стандартные 30. */
  .eco-btn { height: 38px; margin-top: 30px; }
  .eco-btn .ui-cta-btn__label { padding: 0 16px; font-size: 20px; }

  /* --- Act 10, финал: тот же текстовый блок, только прибитый над соцсетями --- */
  /* СОЦСЕТИ ОТСЧИТЫВАЕМ ОТ НАВИГАТОРА, а не от легалки. В .rs-footer они лежали
     обычным потоком над чёрной полосой, поэтому их 30px были «30 от легалки» — а в
     портрете легалка и навигатор стоят на разной высоте, и просвет выходил свой.
     Делаем блок абсолютным в том же футере (он absolute bottom:0, поэтому его низ = низ
     экрана) и ставим на --bottom — это и есть глобальное «30 над навигатором». Легалка
     остаётся полосой по самому низу, она из этой колонки просто выпадает. */
  /* + --nav-lift В ОБОИХ: навигатор в финале сам едет вверх на высоту легалки (см. .ui-nav),
     а «30 над навигатором» должно остаться 30. Без этого слагаемого соцблок налезал на него
     ровно на высоту полосы (замерено: -12px). Вся нижняя группа привязана к ОДНОЙ переменной —
     поэтому она и поднимается одним движением. */
  .rs-social {
    position: absolute; left: 0; right: 0;
    bottom: calc(var(--bottom) + var(--nav-lift, 0px));
    padding: 0 var(--edge);
  }
  /* 74 — выведенное и замеренное, не на глаз: 44 (высота соцблока: лейбл 13.5 + гэп 10 +
     иконки 20) + 30 (просвет до кнопок). Меняется состав соцблока — ПЕРЕСЧИТАЙ.
     Просвет 50→30, чтобы ВСЕ три промежутка нижней группы были одинаковы: заголовок→кнопки,
     кнопки→соцсети и соцсети→индикатор скролла (последний = 30 и задаёт ритм). */
  #ui-overlay #ui-final { bottom: calc(var(--bottom) + var(--nav-lift, 0px) + 74px); }
  /* Заголовок→кнопки тоже 30: заголовок уже несёт margin-bottom 16 из общего правила
     текстовых блоков, поэтому лейблу остаётся 14 (16 + 14 = 30). */
  #ui-overlay #ui-final .ui-final__label { margin-top: 14px; }
  /* В десктопе финал центрован (align-items:center); в портрете он левый, а ряд
     кнопок должен тянуться на всю ширину блока — то есть stretch. Без этого ряд остался бы
     шириной с контент (539px) и flex:1 нечего было бы делить. */
  .ui-final { align-items: stretch; }
  /* Подложка кнопок — как на ПЕРВОМ ЭКРАНЕ (базовые rgba(10,10,10,.4)), а не глушёный
     чёрный 65% десктопного финала: в портрете это те же кнопки, что в hero. */
  .ui-final .store-btn { background: rgba(10, 10, 10, 0.4); }
  /* Легалка в портрете — ДВЕ строки ПО ЦЕНТРУ (ссылки сверху, копирайт снизу), как в
     дубле подвала меню (.ui-menu__foot), а не десктопный space-between в одну строку.
     DOM-порядок «копирайт → ссылки», поэтому column-reverse кладёт ссылки сверху.
     Полоса остаётся чёрной на всю ширину по самому низу. */
  /* Портрет: Privacy·Terms ПО ЦЕНТРУ сверху, под ними ДВА столбца — лейбл Uprising слева,
     копирайт справа (оба в 2 строки). DOM-порядок [by, links, copy], а надо links
     сверху на всю ширину — поэтому раскладка через grid-areas (не поток). Оба текстовых
     блока прижаты к верху строки (align-items:start), их первые строки совпадают. */
  /* Портрет (по макету): легалка — ОДИН левый столбик в порядке Privacy·Terms → копирайт →
     Created by Uprising, всё прижато влево. Справа оставляем место под кнопку scroll-up
     (она absolute right:--edge, по центру легалки — layoutResetAct), поэтому копирайт больше с ней
     НЕ накладывается (раньше copy стоял justify-self:end — ровно под кнопкой). Строки — по одной
     (без принудительного переноса). DOM-порядок [by, links, copy] → раскладка через grid-areas. */
  .rs-legal {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-areas:
      "links"
      "copy"
      "by";
    justify-items: start;
    align-items: start;
    row-gap: 26px;
    padding: 28px calc(var(--edge) + 68px) 28px var(--edge);   /* справа — резерв под кнопку scroll-up */
    text-align: left;
  }
  .rs-legal__links { grid-area: links; justify-self: start; }
  .rs-legal__copy  { grid-area: copy;  justify-self: start; text-align: left; }
  .rs-legal__by {
    grid-area: by;
    justify-self: start;
    align-items: center;   /* иконку 28px центруем по вертикали текста */
    gap: 10px;
    max-width: 100%;
  }
  .rs-legal__mark { height: 28px; flex: none; }   /* up_sign увеличен пропорционально (14×20 → ~19.6×28) */
  .rs-legal__nl { display: none; }   /* каждый элемент — одна строка (как на макете) */

  /* --- Act 7 Legends: заголовок и таблица --- */
  /* Заголовок носит .ui-act2__title, но живёт НЕ внутри .ui-act2, поэтому под общее
     правило текстовых блоков не попадает — кегль надо задать здесь отдельно. 43px — тот
     же, что и у остальных заголовков на мобилке (.ui-hero__title / .ui-act2__title);
     выключка центральная (в отличие от них).

     30px до таблицы задаём через bottom, а не через top: высота заголовка зависит от
     того, на сколько строк он переносится, поэтому любой top-процент давал бы
     «плавающий» промежуток. .lb-title и .lb-panel — соседи в .lb-track (inset:0), панель
     стоит на top:40%, значит bottom заголовка = 100% − 40% + 30 = calc(60% + 30px).
     Вместе с этим снимаем вертикальную половину из transform (она двигала бы нижний край). */
  .lb-title {
    top: auto; bottom: calc(60% + 30px);
    transform: translateX(-50%);
    width: calc(100% - 2 * var(--edge));
    font-size: 43px;
    white-space: normal;
    text-align: center;
  }
  /* Поля таблицы — те же 20, что и у всего контента (--edge), вместо 92vw (≈15.5).
     top:40% — чтобы заголовок (bottom: calc(60% + 30px)) стоял ровно на 30px над панелью;
     раньше этот top жил в 900-блоке, и когда тот стал ≤580, планшет его терял (панель
     ехала на десктопную позицию, заголовок налезал). Теперь тут — покрывает телефон и планшет. */
  .lb-panel { width: calc(100% - 2 * var(--edge)); top: 40%; }

  /* ТАБЛИЦА НЕ МАСШТАБИРУЕТСЯ, А РАСТЯГИВАЕТСЯ. Вся её внутренняя геометрия задана в vw от
     вьюпорта, поэтому в портрете она просто ссыхалась вместе с ним: замерено на 390 — строка
     46.8px, паддинг 13.65, ник 19.5, значение 21.8, «LVL» 10.1. Тут те же величины, что и
     на десктопе 1440, но в ПИКСЕЛЯХ — то есть содержимое остаётся постоянным, а сужается лишь
     сама строка. Числа не с потолка: это десктопные vw, переведённые по базе 1440
     (4.31vw=62, 1.667vw=24, 1.111vw=16, 0.35vw=5); спайн 285 = 4.6 строки по 62.

     Колонка значения из 17% становится auto — и это обязательно: 17% от более узкой строки дают
     ~59px, а «LVL 122» при 24/16px занимает ~69 и переносился бы в две строки, ломая
     фиксированную высоту. При auto колонка держит свою ширину, а сужение съедает колонка
     ника — у неё уже есть min-width:0 с многоточием для длинных ников. */
  .lb-row  { height: 62px; padding: 0 24px; grid-template-columns: 1fr auto; }
  .lb-name { font-size: 24px; }
  .lb-val  { font-size: 24px; white-space: nowrap; }
  .lb-val__lbl { font-size: 16px; margin-right: 5px; }
  .lb-body::before { width: 5px; height: 285px; }

  /* --- Иконка-курсор стирания (Act 10): в портрете 60 вместо 120 --- */
  /* margin — ровно половина размера: он центрирует иконку в точке, куда её ведёт JS
     (translate в px вьюпорта), поэтому менять width без margin означает сбить прицел.
     Радиус самой кисти (WA_BRUSH_VW) это не задевает — она в vw и масштабируется сама. */
  .wa-cursor { width: 60px; height: 60px; margin: -30px 0 0 -30px; }

  /* --- Облачка-мысли кабана (Act 5): в портрете они больше --- */
  /* Та же причина, что и у псевдовыбора: 16vw заданы под десктоп и в портрете дают
     51/62px — облачка превращаются в марки. 40vw = 156px на 390 для runaway (s=1) и
     127px для attack (s=0.8125) — ровно как в макете. Соотношение между облачками НЕ
     трогаем: 0.8125 уже равно 130/158 из макета, то есть меньшее и так правильное.
     Позиции — не тут: их задаёт покадровая таблица BOAR_KF (mxf/myf), потому что это анимация. */
  .boar-bubble { width: 40vw; }

  /* --- Псевдовыбор эмблемы (Act 5): на всю ширину, а не уменьшенный --- */
  /* Вся композиция задана в vw под десктоп, поэтому в портрете она просто ссыхалась:
     замерено на 390 — центральная эмблема 39px, боковые по 20, подпись высотой 4px.
     Тут та же композиция, масштабированная ×2.7, с теми же пропорциями внутри
     (риска = 11% высоты карточки, дуга чуть шире разлёта боковых эмблем, подпись на
     ~5.7% ширины дуги ниже её пика) — меняется лишь масштаб, не устройство.
     Разлёт: 34.5vw. Боковые эмблемы JS рисует в scale(0.5), поэтому их внешний край
     = 50vw ± (34.5 + 27·0.5/2) = ±41.25vw → 34..356px на 390 при полях 20/370. Влезает.
     Подпись — фиксированные 11px, а не vw: пропорциональные 2.15vw дали бы 8.4px, нечитаемо. */
  :root { --cp-slot: 34.5; }
  .cp-stage   { width: 96vw; height: 27vw; }   /* height = высота центральной карточки */
  .cp-emblem  { width: 27vw; height: 27vw; }
  .cp-arc     { margin-top: 1.5vw; }
  .cp-arc__img { width: 88vw; }
  .cp-tick    { top: -1.5vw; width: 1px; height: 3vw; }
  .cp-arc__label { top: 5vw; font-size: 11px; }

  /* --- Карта с «You are here»: в портрете её нет --- */
  /* Снимаем именно display, а не visibility: блок должен уйти из раскладки полностью, а не
     остаться прозрачной дырой в нижней полосе, где теперь стоит текст #ui-explore (они
     на экране вместе и по --bottom накладывались). updateMap() от этого не страдает —
     applyMap() лишь пишет opacity/transform и ничего не мерит, поэтому спрятанный блок его
     не ломает и пересчёта на ресайзе не требует. */
  .ui-map { display: none; }

  /* =====================================================================
     ВЫНОСКИ ACT 2 — отдельная портретная раскладка, снятая с макета (390×844).
     Размеры в ПИКСЕЛЯХ, а не в vw: 8vw задуманы под 1440 (=115px) и в портрете
     ссыхаются до 30px — иконка становится точкой. Квадрат = 60 (задано макетом).

     Группа обеих выносок по макету: 122 от левого края, 20 от правого (= --edge),
     346 от верха, 300 от низа → коробка 248×198. Вертикаль сходится точно:
     60 (квадрат кирки) + 9 (бар) + 60 (промежуток) + 9 (бар) + 60 (квадрат руды) = 198.
     Поэтому top задан так, чтобы кирка началась на 346, а руда закончилась на 544:
     бар кирки на 406 = 48.10% от 844; бар руды на 475 = 56.28%.

     Кирка прибита ПРАВЫМ краем (right: --edge) — это и есть та самая аннотированная 20, и так
     квадрат остаётся у края на любой ширине. Точка тогда сама ложится на
     390 − 20 − 60 − 180 − 8 = 122, как в макете.
     ===================================================================== */
  #co-pickaxe { left: auto; right: var(--edge); top: 48.10%; --line-w: 180px; --box: 60px; }
  #co-ore     { left: 42.56%;                   top: 56.28%; --line-w:  70px; --box: 60px; }

  /* РУДА В ПОРТРЕТЕ ЗЕРКАЛЬНАЯ: квадрат с баром слева, линия идёт ВПРАВО, sq-point
     у неё на конце. В разметке порядок всегда «точка → линия → бар», поэтому разворачиваем
     ряд. Последствия, каждое обязательное:
     - маска квадрата прижата к right:0 → теперь к left:0, иначе квадрат уехал
       бы на противоположный от бара конец;
     - сам бар тоже разворачиваем, чтобы срез угла оказался на ЛЕВОМ краю, как в макете;
     - срез угла имеет ещё один горизонтальный переворот: было scaleX(-1) scaleY(-1),
       после зеркаления остаётся только scaleY(-1);
     - transform-origin линии и бара с left на right: applyCallouts() разворачивает их
       через scaleX, и волна должна идти ОТ точки наружу, а не задом наперёд.
     left здесь задаёт левый край ряда, то есть край квадрата (166px на 390) — не точку. */
  #co-ore { flex-direction: row-reverse; }
  #co-ore .co-boxclip   { left: 0; right: auto; }
  #co-ore .co-bar       { flex-direction: row-reverse; transform-origin: right center; }
  #co-ore .co-bar__corner { transform: scaleY(-1); }
  #co-ore .co-line      { transform-origin: right center; }

  /* Навигатор актов живёт ВНУТРИ шапки, поэтому едет вместе с ней на z-index 21 —
     то есть поверх открытого меню. На десктопе это никому не мешает (панель 380px
     справа, навбар по центру шапки — не пересекаются), а тут панель на всю
     ширину и навбар ложится просто на подвал меню: «[ SURVIVAL ]» поверх копирайта.
     Пока меню открыто — убираем его. Соседний комбинатор работает, потому что .ui-header
     стоит в разметке сразу за .ui-menu (и именно этот порядок держит шапку сверху). */
  .ui-menu.is-open ~ .ui-header .ui-nav { opacity: 0; }

  /* --- HUD (Survival): в портрете Premium лишний --- */
  /* display:none, а не visibility: карточка должна уйти из колонки, а не оставить пустое
     место в стеке. JS это переживает: applyHud() просто пишет opacity/transform в
     спрятанный узел, а layoutHud() считает ширину по ПРАВОМУ краю карточек — у спрятанной
     rect нулевой, поэтому она в max() не попадает. Ширину колонки задаёт PLAYER NAME
     (самая длинная), поэтому и бар под ней не меняется. */
  .hud-stat--premium { display: none; }
  .hud-stat--wallet  { display: none; }   /* на мобилке HUD сокращаем ещё на один — остаётся только Player name */

  /* Аватар получает ту же подложку, что и остальные иконки. В десктопе её выключает
     .hud-stat--avatar (чтобы портрет читался на тёмном), но PNG аватара — вырезанный,
     54% пикселей прозрачны, поэтому подложка сквозь него видна так же, как под иконками
     Premium/Wallet. Значение — то же, что в .store-btn__icon (белое 10%); меняешь там
     — измени и тут. */
  .hud-stat--avatar .hud-stat__icon { background: rgba(255,255,255,0.10); }

  /* --- HUD (Survival): три информера в КОЛОНКУ, все по самому широкому --- */
  /* Снимаем десктопное исключение «+30». На десктопе --bottom = 30 от края экрана, и
     HUD намеренно стоит на 30 выше остальных. Тут --bottom(80) уже САМ означает «30 над
     навигатором» (20 его поле + 30 высота + 30), поэтому +30 давало бы 60 — вдвое больше
     нужного. Значит ровно var(--bottom), как у всех остальных нижних блоков. */
  /* ВСЯ ГРУППА ЗЕРКАЛИТСЯ НА ПРАВУЮ СТОРОНУ: карточки прижаты вправо, сплошной бар
     под ними тоже справа, а тонкая линия с sq-point идёт ВЛЕВО. В портрете слева
     от головы героя остаётся пусто, поэтому блок там читается лучше.
     Зеркалится именно раскладка, а не карточка: внутри каждой карточки иконка так
     же слева, текст справа — как в макете. */
  .ui-hud {
    left: auto; right: var(--edge);
    bottom: var(--bottom);
    /* Доля меряется от ПРАВОГО края (HUD теперь прибит им), поэтому 0.76 даёт
       sq-point на 0.24 ширины — ровно зеркало десктопной логики. В портрете
       центр экрана (0.5) был бы всего ~15px от края карточек: линия выродилась бы
       в огрызок, и hasLane прятал её вместе с квадратиком — именно это и было видно,
       бар обрывался вровень с карточками. 0.76 повторяет пропорцию из макета
       (линия ≈ 0.7 ширины карточек); на 375 квадратик встаёт на 90px. */
    --hud-span: 0.76;
  }
  /* Ряд уже бара (152 против 269), а он блочный — сам лёг бы к левому краю
     .ui-hud. auto-маргин прижимает его к правому, вровень с баром. */
  .hud-row { margin-left: auto; }
  /* row-reverse даёт порядок справа налево: сплошной бар → линия → sq-point.
     Вместе с ним зеркалим и то, ОТКУДА растёт анимация сборки: оба
     transform-origin были right center (появлялась точка → линия влево → бар влево),
     теперь left center — та же волна, но от точки вправо. */
  .hud-bar        { flex-direction: row-reverse; }
  .hud-bar__solid { flex-direction: row-reverse; transform-origin: left center; }
  .hud-bar__line  { transform-origin: left center; }
  /* Срез угла должен смотреть в другую сторону — тем же scaleX(-1), что уже
     используют углы выносок (.co-bar__corner). */
  .hud-bar__corner { transform: scaleX(-1); }

  /* В десктопе это ряд из трёх по 173px = 539 + гэпы; на 375 третья карточка просто
     уезжала за экран (.ui-hud — absolute со shrink-to-fit, поэтому карточки не сжимались,
     а вылезали). Стек — это не только flex-direction:
     width:max-content НА САМОМ РЯДУ — это и есть «выровнять по первому самому длинному»:
     ряд становится шириной с самую длинную карточку (PLAYER NAME), а align-items:stretch
     (дефолтный) подтягивает две другие ровно к ней. Без max-content ряд взял бы ширину
     .ui-hud, а её раздувает соседний .hud-bar (JS ставит ему инлайн-ширину) — и
     карточки растянуло бы на пол-экрана.
     width:auto на карточке обязателен: зашитые 173px не дали бы ей ни сжаться до
     контента, ни растянуться до соседки. */
  .hud-row  { flex-direction: column; width: max-content; }
  .hud-stat { width: auto; }

  /* --- меню: на всю ширину экрана, пункты по центру --- */
  /* Панель уже absolute top/right/bottom, поэтому width:100% = весь экран; slide-in
     translateX(100%) от этого не меняется. Пункты выше (50px против 38 в десктопе:
     padding 16 + строка 18) — под палец, как в макете. */
  .ui-menu { width: 100%; }
  .ui-menu__item { text-align: center; padding: 16px 20px; }
  .ui-menu__foot { text-align: center; padding: 24px 20px 30px; }
}

/* =====================================================================
   ПЛАНШЕТ 581–1024 — ТОЛЬКО РАЗМЕРЫ (структуру берёт мобильный блок выше).
   Контент течёт с вьюпортом (vw), как десктоп при росте разрешения. Откалибровано по
   базе 581: на ~581 = мобильные px, дальше растёт до 1024. Заголовок/описание 11vw / 2.4vw
   держат мобильное 64:14 = 4.57:1. Где чистый vw дал бы слишком крупное на 1024 — clamp с потолком.

   ВСЕ vw-величины ОБЁРНУТЫ round(…, 1px) → кегль/размер/позиция всегда ЦЕЛЫЕ CSS-пиксели
   (по требованию «без дробных»; чище и предсказуемее, шаг 1px при изменении ширины).
   ВНИМАНИЕ: это НЕ причина и НЕ лекарство от размытости в responsive-режиме DevTools — десктоп
   тоже дробный (76.032px) и чёткий; мутность там от масштаба превью <100%, не от CSS.
   ПОЗИЦИИ/ПОРЯДОК/ЗЕРКАЛЕНИЕ — НЕ ТУТ (из мобильного).
   ===================================================================== */
@media (min-width: 581px) and (max-width: 1024px) {
  /* поля растут от мобильных 20 до десктопных 40 (целые px) */
  :root { --edge: round(clamp(20px, 3.44vw, 40px), 1px); }

  /* --- первый экран --- */
  .ui-hero__title { font-size: round(11vw, 1px); margin-bottom: 30px; }  /* 64px@581 → 113px@1024; гэп возвращаем к 30 (16 — мобильная правка) */
  .ui-desc { font-size: round(2.4vw, 1px); }           /* держит мобильное 64:14 = 4.57:1 */

  /* кнопки сторов (первый экран + финал) */
  .ui-cta .store-btn,
  .ui-final .store-btn { height: round(clamp(110px, 18.9vw, 150px), 1px); padding: round(clamp(12px, 2.1vw, 18px), 1px); }
  .ui-cta .store-btn__icon img,
  .ui-final .store-btn__icon img { width: round(clamp(32px, 5.5vw, 46px), 1px); height: round(clamp(32px, 5.5vw, 46px), 1px); }
  .ui-cta .store-btn__name,
  .ui-final .store-btn__name { font-size: round(clamp(20px, 3.44vw, 30px), 1px); }

  /* --- текстовые блоки актов (та же выключка, что и в мобильном) --- */
  /* margin возвращаем к 30px: уменьшенный гэп 16px — мобильная правка (блок max-width:580),
     а тут (планшет) кегль другой (11vw ≈ 88px), поэтому оставляем прежний промежуток. */
  #ui-overlay .ui-act2:not(:where(#ui-ecosystem, #ui-ecosystem-cta)) .ui-act2__title { font-size: round(11vw, 1px); margin: 0 0 30px; }
  #ui-overlay .ui-act2:not(:where(#ui-ecosystem, #ui-ecosystem-cta)) .ui-act2__desc  { font-size: round(2.4vw, 1px); }

  /* Ширина текстовой колонки. Заголовки — 60vw: та же пропорция колонка/кегль (≈5.47),
     что на мобилке 350px/64px, поэтому переносы 1-в-1 (сверено). Осн. текст — 50vw (≈половина). */
  .ui-hero__title,
  #ui-overlay .ui-act2:not(:where(#ui-ecosystem, #ui-ecosystem-cta)) .ui-act2__title { max-width: round(60vw, 1px); }
  .ui-desc,
  #ui-overlay .ui-act2:not(:where(#ui-ecosystem, #ui-ecosystem-cta)) .ui-act2__desc  { max-width: round(50vw, 1px); }

  /* лифты нижних блоков НЕ масштабируем в vw — они учитывают высоту компонентов
     (плашка 58, строка 42, соцблок 44), а те в мобильных px; vw-lift раздувал бы промежуток.
     Оставляем мобильные 88/72/94 (из блока выше). Исключение — #ui-ecosystem: его блок описания
     масштабируется (кегль 2.4vw), поэтому lift слегка растёт, с потолком чтобы не раздулся. */
  #ui-overlay #ui-ecosystem { bottom: calc(var(--bottom) + round(clamp(155px, 26.68vw, 210px), 1px)); }

  /* --- Act 8 ecosystem + кнопка --- */
  #ui-overlay #ui-ecosystem .ui-act2__title      { font-size: round(11vw, 1px); }
  /* Описание исключено из общего body-правила (max-width:50vw) через :not(#ui-ecosystem-cta),
     а мобильное правило дало ему width:auto → на планшете тянулось на всю ширину. Ограничиваем
     теми же 50vw (≈половина), как остальной основной текст. */
  #ui-overlay #ui-ecosystem-cta .ui-act2__desc   { font-size: round(2.4vw, 1px); max-width: round(50vw, 1px); }
  .eco-btn { height: round(clamp(38px, 6.54vw, 54px), 1px); }
  .eco-btn .ui-cta-btn__label { font-size: round(clamp(20px, 3.44vw, 30px), 1px); padding: 0 round(clamp(16px, 2.75vw, 22px), 1px); }

  /* --- Act 7 Legends: заголовок + таблица --- */
  .lb-title { font-size: round(11vw, 1px); }
  .lb-row  { height: round(clamp(62px, 10.67vw, 84px), 1px); padding: 0 round(clamp(24px, 4.13vw, 32px), 1px); }
  .lb-name { font-size: round(clamp(24px, 4.13vw, 34px), 1px); }
  .lb-val  { font-size: round(clamp(24px, 4.13vw, 34px), 1px); }
  .lb-val__lbl { font-size: round(clamp(16px, 2.75vw, 22px), 1px); }
  .lb-body::before { height: round(clamp(285px, 49vw, 380px), 1px); }

  /* --- gear / weapon карты: между мобильными 40vw и десктопными 17.5vw --- */
  .gear-item, .wpn-item { width: round(26vw, 1px); height: round(27vw, 1px); }

  /* --- Act 2 выноски: квадрат/бар/линия растут (мобильные 60/180/70 px) --- */
  #co-pickaxe { --line-w: round(30.98vw, 1px); --box: round(10.33vw, 1px); }
  #co-ore     { --line-w: round(12.05vw, 1px); --box: round(10.33vw, 1px); }

  /* --- кабан: мобильные 40vw на 1024 = 410px, великовато → чуть меньше --- */
  .boar-bubble { width: round(28vw, 1px); }

  /* --- псевдовыбор эмблемы: мобильные vw на 1024 великоваты → чуть сжато --- */
  :root { --cp-slot: 24; }
  .cp-stage  { width: round(72vw, 1px); height: round(19vw, 1px); }
  .cp-emblem { width: round(19vw, 1px); height: round(19vw, 1px); }
  .cp-arc__img { width: round(64vw, 1px); }

  /* --- Survival HUD: sq-point до СЕРЕДИНЫ экрана (мобильные 0.76 на планшете залезают
     за центр; 0.5 = середина, как десктоп; карточки тут уже, поэтому линия не огрызок) --- */
  .ui-hud { --hud-span: 0.5; }
}
