/* Экраны (attract / rules / countdown / result) и HUD.
   Зона ответственности: [ЭКРАНЫ].

   Правила файла:
   - ни одного захардкоженного фирменного цвета: всё из tokens.css,
     производные оттенки — через color-mix() от токенов;
   - кегли и отступы — токены (--fs-*, --gap-*), производные — calc() от них;
   - анимации только на transform/opacity (никаких width/top/margin в кадре);
   - экран рисуется ПОВЕРХ живой ленты выдачи: тёмные экраны кладут на неё
     фирменную 3D-графику (--brand-backdrop), а HUD во время гонки живёт в
     единственной полосе, которую не жалко занять, — на месте декоративного
     баннера витрины (см. блок HUD ниже). Выдачу, ради которой всё и
     затевалось, он не перекрывает. */

/* ------------------------------------------------------------------ */
/* Базовый слой экрана                                                 */
/* ------------------------------------------------------------------ */

.scr {
  /* Локальная палитра слоя: всё выведено из токенов сцены */
  --scr-panel: color-mix(in srgb, var(--stage-text) 7%, transparent);
  --scr-border: color-mix(in srgb, var(--stage-text) 18%, transparent);
  --scr-shadow: color-mix(in srgb, var(--stage-bg) 65%, transparent);

  /* Акцент на тёмном фоне — БИРЮЗОВЫЙ, как в логотипе Ритейл Медиа.
     Им набраны выделенное слово в названии, заголовки экранов, «СТАРТ!»,
     кольцо отсчёта, стрелка приглашения и полоса времени.
     Aqua по тёмной сцене даёт около 15:1 — видно мельком с трёх метров.
     Взят вместо Sea Green: тот читался слишком зелёным и расходился с тем,
     как воспринимается знак. Заодно акцент перестал совпадать с цветом
     второго игрока. */
  --scr-accent: var(--brand-aqua);

  /* Приглушённый текст экранов. --stage-muted (66% светлого charcoal) задуман
     для спокойного фона, а у нас под ним едет пёстрая лента: держим 80%. */
  --scr-muted: color-mix(in srgb, var(--stage-text) 80%, transparent);

  /* Непрозрачные плашки. Полупрозрачная панель ловит на себя картинки товаров
     и названия из ленты — читается это плохо. Тон — ультрамарин, чтобы плашка
     выглядела фирменной, а не серым прямоугольником. */
  --scr-surface: color-mix(in srgb, var(--brand-primary) 15%, var(--stage-bg));
  --scr-surface-2: color-mix(in srgb, var(--brand-primary) 58%, var(--stage-bg));

  /* Витрину под экраном глушим мягче, чем --stage-veil (0.88): поверх неё
     теперь лежит фирменная графика, и глухое затемнение съело бы узнаваемость
     выдачи по краям кадра, где графика уходит в прозрачность. */
  --scr-veil: color-mix(in srgb, var(--stage-bg) 70%, transparent);

  position: absolute;
  inset: 0;
  z-index: 10;

  font-family: var(--font-brand);
  color: var(--stage-text);
  -webkit-font-smoothing: antialiased;

  /* Киоск без мыши: экраны не должны ловить события */
  pointer-events: none;
  user-select: none;
  overflow: hidden;

  animation: scr-in 0.32s var(--ease-out) both;
}

/* Порядок слоёв тёмного экрана снизу вверх:
     0 — ::before, приглушённая живая витрина;
     1 — ::after, фирменная 3D-графика;
     2 — .scr__glow, медленное свечение (движение поверх статичной картинки);
     3 — .scr__content, текст.
   z-index задан явно: псевдоэлемент ::after по умолчанию лёг бы поверх текста. */

/* 1. Витрина уходит в фон, но остаётся узнаваемой — в этом смысл
      демонстрации: продвижение живёт в чужой выдаче. */
.scr--veil::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  background: var(--scr-veil);
}

/* 2. Фирменная 3D-графика (--brand-backdrop). Два слоя в одном фоне:
      сверху тёмный скрим, под ним сама картинка. Скрим обязателен — в графике
      есть светлые бирюзовые ядра, белый текст по ним даёт меньше 2:1.
      Маска по вертикали оставляет открытыми шапку магазина сверху и карточки
      снизу: кадр читается как «фирменная плашка поверх чужой выдачи». */
.scr--veil::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Скрим поверх фирменной графики. Значения подняты после смены гайдлайнов:
     новая 3D-графика заметно светлее и контрастнее прежней, и на прежнем
     скриме (52…74%) текст финала ложился прямо на светлую синюю форму —
     фиолетовый подзаголовок и число почти пропадали. Витрина при этом
     остаётся узнаваемой: она видна по краям кадра и продолжает ехать. */
  background-image: linear-gradient(
      180deg,
      color-mix(in srgb, var(--stage-bg) 74%, transparent) 0%,
      color-mix(in srgb, var(--stage-bg) 82%, transparent) 55%,
      color-mix(in srgb, var(--stage-bg) 88%, transparent) 100%
    ),
    var(--brand-backdrop);
  background-repeat: no-repeat;
  background-position: 50% 50%;
  background-size: cover;
  /* Чёрный здесь — не цвет, а непрозрачность маски. */
  -webkit-mask-image: linear-gradient(
    180deg,
    transparent 0%,
    rgba(0, 0, 0, 0.94) 17%,
    rgba(0, 0, 0, 0.94) 83%,
    transparent 100%
  );
  mask-image: linear-gradient(
    180deg,
    transparent 0%,
    rgba(0, 0, 0, 0.94) 17%,
    rgba(0, 0, 0, 0.94) 83%,
    transparent 100%
  );
}

/* 3. Мягкое фирменное свечение за контентом: графика статична, а экран висит
      часами — медленное «дыхание» не даёт кадру замереть. Только transform.
      Цвет — тот же акцент, что у заголовков: фиолетовый ореол оставался
      единственным следом прежней акцентной пары. Плотность чуть ниже
      (26% против 34%): бирюзовый ярче фиолетового и на той же прозрачности
      начинал забивать фирменную графику под собой. */
.scr__glow {
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  width: 1200px;
  height: 1200px;
  margin: -600px 0 0 -600px;
  border-radius: 50%;
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--scr-accent) 26%, transparent) 0%,
    transparent 68%
  );
  opacity: 0.34;
  will-change: transform;
  animation: scr-breathe 9s var(--ease-in-out) infinite;
}

.scr__content {
  position: relative;
  z-index: 3;
  width: 100%;
  height: 100%;
  box-sizing: border-box;
}

/* Полоса оставшегося времени экрана (уходит слева направо).
   Лежит у самого низа кадра, где маска фирменной графики уже отпустила
   витрину: и дорожка, и заливка непрозрачные, иначе на светлых карточках
   полосы просто не видно. */
.scr__timebar {
  position: absolute;
  z-index: 4;
  left: 0;
  right: 0;
  bottom: 0;
  height: 8px;
  background: color-mix(in srgb, var(--stage-text) 16%, var(--stage-bg));
}

.scr__timebar-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--scr-accent);
  transform-origin: left center;
  transform: scaleX(1);
  will-change: transform;
}

@keyframes scr-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes scr-breathe {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.14);
  }
}

/* Цветовые роли игроков: один --p-color на всю ветку DOM.
   --p-ink — тот же цвет, но для КРУПНОГО ТЕКСТА поверх фирменной 3D-графики.
   Насыщенный цвет игрока годится для рамок и заливок, но буквами по синему
   фону читается плохо: Electric Violet на синей графике почти пропадал.
   Светлый тон того же семейства держит и принадлежность игроку, и читаемость. */
.is-p1 {
  --p-color: var(--player-1);
  --p-ink: var(--player-1-soft);
}

.is-p2 {
  --p-color: var(--player-2);
  --p-ink: var(--player-2-soft);
}

/* ------------------------------------------------------------------ */
/* ATTRACT — аттрактор между забегами                                  */
/* ------------------------------------------------------------------ */

/* Человек идёт мимо, у него примерно секунда. Заголовок должен взять эту
   секунду безоговорочно: своя колонка во всю высоту, крупнее всего на экране,
   вокруг воздух. Топ-3 дня — служебная информация, она ужата вправо. */
.scr-attract .scr__content {
  display: grid;
  grid-template-columns: minmax(0, 1180px) minmax(0, 1fr);
  align-items: center;
  gap: var(--gap-xl);
  padding: var(--gap-xl);
}

/* Под левой колонкой — дополнительное затемнение: под буквами заголовка не
   должны читаться названия чужих товаров. Градиент уходит в ноль к правому
   краю, поэтому лента там остаётся живой. */
.scr-attract .scr__content::before {
  content: '';
  position: absolute;
  z-index: -1;
  inset: 0;
  background: linear-gradient(
    100deg,
    color-mix(in srgb, var(--stage-bg) 88%, transparent) 0%,
    color-mix(in srgb, var(--stage-bg) 76%, transparent) 42%,
    transparent 72%
  );
}

.attract__main {
  position: relative;
  display: flex;
  flex-direction: column;
  /* Ритм задаём поштучно: между заголовком и остальным нужен разрыв больше,
     чем между строками служебных блоков. */
  gap: 0;
}

/* Логотип Ритейл Медиа над названием игры. Файл широкий (939×120), поэтому
   задаём только высоту: ширина считается по пропорции, растянуть или обрезать
   логотип нельзя. Высоту берём от кегля основного текста, чтобы блок жил
   в общей типографической сетке. */
.attract__brand {
  margin-bottom: var(--gap-lg);
}

.attract__brand-logo {
  display: block;
  height: calc(var(--fs-body) * 2.6); /* ≈68px при --fs-body 26px */
  width: auto;
  /* Атрибуты width/height у <img> задают только пропорцию — без этого
     строка «прыгала» бы, пока SVG не загрузился. */
  max-width: 100%;
  object-fit: contain;
  object-position: left center;
}

/* Запасная надпись: показывается, только если логотип не загрузился */
.attract__brand-text {
  display: none;
  font-size: var(--fs-body);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--scr-muted);
}

.attract__brand.is-logo-missing .attract__brand-logo {
  display: none;
}

.attract__brand.is-logo-missing .attract__brand-text {
  display: block;
}

.attract__title {
  margin: 0 0 var(--gap-xl);
  font-family: var(--font-display);
  /* Крупнее токена: это единственный элемент, который обязан прочитаться
     с трёх метров боковым зрением. */
  font-size: calc(var(--fs-hero) * 1.3);
  font-weight: 800;
  line-height: 0.92;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

/* Последнее слово названия — акцентом, чтобы заголовок читался ритмично */
.attract__title-accent {
  color: var(--scr-accent);
}

.attract__invite {
  display: flex;
  align-items: center;
  gap: var(--gap-md);
  margin-bottom: var(--gap-lg);
  font-size: var(--fs-lead);
  font-weight: 600;
  line-height: 1.15;
}

/* Медленная «дышащая» стрелка вниз — на платформу */
.attract__arrow {
  flex: none;
  font-size: var(--fs-title);
  line-height: 1;
  color: var(--scr-accent);
  will-change: transform;
  animation: attract-arrow 2.4s var(--ease-in-out) infinite;
}

@keyframes attract-arrow {
  0%,
  100% {
    transform: translate3d(0, -10%, 0);
  }
  50% {
    transform: translate3d(0, 22%, 0);
  }
}

/* Две плашки платформ */
.attract__platforms {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap-md);
  max-width: 900px;
}

.attract__platform {
  display: flex;
  align-items: center;
  gap: var(--gap-sm);
  padding: var(--gap-md);
  border: 3px solid var(--scr-border);
  border-radius: var(--radius-lg);
  /* Непрозрачная подложка, а не полупрозрачная панель: плашка ложится поверх
     пёстрой ленты с картинками товаров и обязана читаться на любом кадре. */
  background: color-mix(in srgb, var(--stage-text) 7%, var(--stage-bg));
  font-size: var(--fs-lead);
  font-weight: 700;
  transition: border-color 0.25s var(--ease-out), background-color 0.25s var(--ease-out);
}

.attract__platform-dot {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--p-color);
  opacity: 0.35;
  will-change: transform, opacity;
  transition: opacity 0.2s linear;
}

/* Заливка непрозрачная: полупрозрачная подсветка «встал на платформу»
   пропускала под собой карточки товаров. */
.attract__platform.is-ready {
  border-color: var(--p-color);
  background: color-mix(in srgb, var(--p-color) 26%, var(--stage-bg));
}

.attract__platform.is-ready .attract__platform-dot {
  opacity: 1;
  animation: attract-dot 1.1s var(--ease-in-out) infinite;
}

@keyframes attract-dot {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.35);
  }
}

/* Топ-3 дня. Служебный блок: заметный, но заведомо тише заголовка.
   Плашка непрозрачная — раньше сквозь неё читались названия товаров
   и цифры продаж накладывались на цены из ленты. */
.attract__board {
  align-self: center;
  justify-self: end;
  width: 100%;
  max-width: 520px;
  padding: var(--gap-md) var(--gap-lg) var(--gap-lg);
  border: 2px solid var(--scr-border);
  border-radius: var(--radius-lg);
  background: var(--scr-surface);
  box-shadow: 0 30px 90px var(--scr-shadow);
}

.attract__board.is-empty {
  display: none;
}

.attract__board-title {
  margin: 0 0 var(--gap-md);
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--scr-muted);
}

.attract__board-list {
  display: flex;
  flex-direction: column;
  gap: var(--gap-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.attract__row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: baseline;
  gap: var(--gap-sm);
  padding: var(--gap-sm) var(--gap-md);
  border-radius: var(--radius-md);
  background: var(--scr-panel);
}

.attract__row:first-child {
  background: var(--scr-surface-2);
}

.attract__rank {
  font-family: var(--font-display);
  /* Ранг был кеглем заголовка второго уровня и спорил с названием игры */
  font-size: var(--fs-lead);
  font-weight: 800;
  line-height: 1;
  min-width: 1.2em;
}

.attract__sales {
  justify-self: end;
  font-size: var(--fs-lead);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.attract__sales-unit {
  font-size: var(--fs-body);
  font-weight: 500;
  color: var(--scr-muted);
}

/* ------------------------------------------------------------------ */
/* RULES — правила и отсчёт, ОДНО окно                                 */
/* ------------------------------------------------------------------ */

/* Правки клиента: текст крупнее и появляется по одному предложению, а отсчёт
   идёт в этой же карточке, а не отдельным экраном.

   Ключ к спокойной картинке — карточка не меняет размер. Все предложения
   лежат в разметке с первого кадра и только проявляются (opacity), а под ними
   заранее отведено место под отсчёт (.rules__countdown, min-height). Поэтому
   ни одно уже прочитанное предложение не уезжает из-под глаз, ничего не
   наползает друг на друга и в момент появления цифр карточка не прыгает. */

.scr-rules .scr__content {
  display: grid;
  place-items: center;
  padding: var(--gap-lg) var(--gap-xl);
}

.rules__card {
  display: flex;
  flex-direction: column;
  gap: var(--gap-lg);
  width: 100%;
  max-width: 1560px;
  padding: var(--gap-lg) var(--gap-xl);
  border: 2px solid var(--scr-border);
  border-radius: var(--radius-lg);
  /* Непрозрачно: правила читают один раз и быстро, лента под текстом мешает */
  background: var(--scr-surface);
  box-shadow: 0 40px 120px var(--scr-shadow);
  animation: rules-card-in 0.45s var(--ease-out) both;
  transition: background-color 0.22s linear, border-color 0.22s linear,
    box-shadow 0.22s linear;
}

@keyframes rules-card-in {
  from {
    opacity: 0;
    transform: translate3d(0, 26px, 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

.rules__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-title);
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--scr-accent);
  transition: opacity 0.22s linear;
}

.rules__list {
  display: flex;
  flex-direction: column;
  gap: var(--gap-md);
  margin: 0;
  transition: opacity 0.4s var(--ease-out);
}

/* Крупнее прежнего: правила читают с двух-трёх метров, стоя на платформе.

   Строка — сетка из двух колонок: номер порядка и текст (правка клиента
   «при возникновении строчек добавлять цифру порядка»). Колонка номера
   фиксированной ширины, поэтому все предложения начинаются по одной
   вертикали, а перенос длинной строки уходит под текст, а не под цифру.
   Ширина задана в em самой строки: сменится кегль — колонка поедет за ним. */
.rules__p {
  display: grid;
  grid-template-columns: 1.15em minmax(0, 1fr);
  /* По базовой линии, а не по верху: цифра и первая строка текста набраны
     разными гарнитурами, и выравнивание по коробке дало бы разнобой. */
  align-items: baseline;
  column-gap: var(--gap-sm);
  margin: 0;
  font-size: calc(var(--fs-lead) * 1.2);
  line-height: 1.24;
  text-wrap: pretty;
  /* Место занято сразу, видимости нет — предложение проявится по своему такту */
  opacity: 0;
  will-change: opacity, transform;
}

/* Номер порядка. Кегль тот же, что у текста, — он не должен перекрикивать
   само правило; отличают его гарнитура заголовков, насыщенность и акцентный
   цвет. С трёх метров это читается как нумерованный список с одного взгляда.
   Появление номера отдельно не анимируется: он лежит внутри .rules__p и
   проявляется вместе со своим предложением одним и тем же rules-line-in. */
.rules__num {
  font-family: var(--font-display);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--scr-accent);
}

.rules__p.is-shown {
  animation: rules-line-in 0.5s var(--ease-out) both;
}

@keyframes rules-line-in {
  from {
    opacity: 0;
    transform: translate3d(0, 16px, 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

/* Место под отсчёт. Отведено с первого кадра — иначе появление цифр сдвинуло бы
   правила вверх ровно в тот момент, когда на них смотрят. */
.rules__countdown {
  display: grid;
  place-items: center;
  min-height: 240px;
}

/* Отсчёт внутри карточки правил заметно компактнее, чем на отдельном экране:
   над ним стоит текст, и цифра не должна его перекрикивать.

   Площадка растянута во всю ширину карточки, хотя цифра занимает середину.
   Это не оформление, а необходимость: на «СТАРТ!» кегль вырастает шире
   площадки, а grid не центрирует ребёнка, который в неё не помещается, —
   он прижимает его к левому краю, и команда уезжала вправо от середины
   экрана. Кольцо при этом задано собственным квадратом, а не растяжкой по
   краям, иначе оно превратилось бы в эллипс во всю строку. */
.rules__countdown .countdown__stage {
  width: 100%;
  height: calc(var(--fs-hero) * 2.1);
}

.rules__countdown .countdown__ring {
  inset: auto;
  width: calc(var(--fs-hero) * 2.1);
  height: calc(var(--fs-hero) * 2.1);
}

.rules__countdown .countdown__digit {
  font-size: calc(var(--fs-hero) * 1.35);
}

.rules__countdown .countdown__digit.is-go {
  font-size: calc(var(--fs-hero) * 0.85);
}

/* Пошёл отсчёт: правила остаются на экране, но уходят в тень — теперь смотрят
   на цифры. Текст не убираем: человек может дочитать последнюю строку. */
.scr-rules.is-countdown .rules__list {
  opacity: 0.45;
}

/* Кадр «СТАРТ!»: гонка уже идёт, и карточка правил не имеет права её закрывать.
   Текст и подложка уходят за 0,22 с, на экране остаётся одна команда — и она
   вырастает до кегля отдельного экрана отсчёта. */
.scr-rules.is-go .rules__card {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}

.scr-rules.is-go .rules__title,
.scr-rules.is-go .rules__list {
  opacity: 0;
}

.scr-rules.is-go .rules__countdown .countdown__digit.is-go {
  font-size: calc(var(--fs-hero) * 1.5);
}

/* Полоса времени отмеряет только часть с правилами: дальше время показывает
   сам отсчёт, и две шкалы одновременно только путали бы. */
.scr-rules.is-countdown .scr__timebar {
  opacity: 0;
  transition: opacity 0.3s var(--ease-out);
}

/* ------------------------------------------------------------------ */
/* COUNTDOWN — «3 · 2 · 1 · СТАРТ!»                                     */
/* ------------------------------------------------------------------ */

.scr-countdown .scr__content {
  display: grid;
  place-items: center;
}

.countdown__stage {
  position: relative;
  display: grid;
  place-items: center;
  width: calc(var(--fs-hero) * 4);
  height: calc(var(--fs-hero) * 4);
}

/* Кольцо, сжимающееся за секунду — задаёт ритм */
.countdown__ring {
  position: absolute;
  inset: 0;
  border: 8px solid color-mix(in srgb, var(--scr-accent) 70%, transparent);
  border-radius: 50%;
  opacity: 0;
  will-change: transform, opacity;
}

.countdown__digit {
  position: relative;
  font-family: var(--font-display);
  font-size: calc(var(--fs-hero) * 2.4);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  will-change: transform, opacity;
}

.countdown__digit.is-go {
  font-size: calc(var(--fs-hero) * 1.5);
  letter-spacing: 0.01em;
  color: var(--scr-accent);
  text-shadow: 0 0 80px color-mix(in srgb, var(--brand-violet) 80%, transparent);
}

/* Анимация проигрывается один раз на такт: countdown.js на каждую цифру
   создаёт новые узлы, поэтому перезапуск гарантирован без reflow-хаков. */
.countdown__digit {
  animation: countdown-pop 0.9s var(--ease-out) both;
}

.countdown__digit.is-go {
  animation: countdown-go 0.5s var(--ease-out) both;
}

.countdown__ring {
  animation: countdown-ring 1s linear both;
}

.countdown__ring.is-go {
  animation: countdown-ring-go 0.6s var(--ease-out) both;
}

@keyframes countdown-pop {
  0% {
    opacity: 0;
    transform: scale(1.55);
  }
  18% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 1;
    transform: scale(0.94);
  }
}

@keyframes countdown-go {
  0% {
    opacity: 0;
    transform: scale(0.6);
  }
  40% {
    opacity: 1;
    transform: scale(1.08);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes countdown-ring {
  0% {
    opacity: 0.9;
    transform: scale(1);
  }
  100% {
    opacity: 0.15;
    transform: scale(0.62);
  }
}

@keyframes countdown-ring-go {
  0% {
    opacity: 0.9;
    transform: scale(0.62);
  }
  100% {
    opacity: 0;
    transform: scale(1.5);
  }
}

/* ------------------------------------------------------------------ */
/* HUD — полоса на месте баннера витрины                               */
/* ------------------------------------------------------------------ */

/* Витрина — предмет демонстрации: смысл Ритейл Медиа в том, что промо-карточка
   живёт внутри нормальной выдачи ритейлера. Значит, HUD не имеет права закрыть
   ни шапку магазина, ни колонку фильтров, ни карточки товаров.

   Геометрия витрины (feed.css, перепроверено по кадру 1920×1080):
     0…104   .shop__header — логотип, поиск, меню;
     104…160 .shop__banner — декоративная промо-полоса, 56px;
     160…180 верхний отступ .shop__body (padding: 20px), пустой;
     180…    заголовок «Фильтры» и карточки товаров.

   Свободна только полоса 104…180 = 76px. Её HUD и занимает целиком: плашек
   поверх страницы больше нет, вместо них одна горизонтальная лента во всю
   ширину. Тень тоже не нужна — слой ничего не перекрывает, а значит, и
   объяснять наложение нечем. */

.hud {
  --hud-top: 104px; /* высота .shop__header */
  --hud-h: 76px; /* баннер 56 + отступ .shop__body 20 */
  --hud-scale-h: 8px; /* весы перевеса по нижнему краю полосы */

  /* Подписи читают боковым зрением: --stage-muted (66%) для них слишком тихий. */
  --hud-muted: color-mix(in srgb, var(--stage-text) 78%, transparent);
  --hud-line: color-mix(in srgb, var(--stage-text) 16%, var(--stage-bg));

  position: absolute;
  top: var(--hud-top);
  left: 0;
  right: 0;
  height: var(--hud-h);
  /* Выше ленты выдачи, но ниже экранов-оверлеев: вспышка «СТАРТ!» должна
     ложиться поверх уже работающего HUD, а не наоборот. */
  z-index: 5;

  font-family: var(--font-brand);
  color: var(--stage-text);
  pointer-events: none;
  user-select: none;
  animation: scr-in 0.25s var(--ease-out) both;
}

/* Полоса читается одной строкой:
   ИГРОК 1 ▲ 4 210 БАЛЛОВ … 12 СЕК … 3 180 БАЛЛОВ ▼ ИГРОК 2 */
.hud__bar {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  height: calc(var(--hud-h) - var(--hud-scale-h));
  /* Непрозрачно: полоса заменяет собой баннер, просвечивать ей нечем. */
  background: var(--stage-bg);
  border-top: 1px solid var(--hud-line);
}

/* --- Блок игрока: у своего края экрана, зеркально --- */

.hud__player {
  position: relative;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 var(--gap-lg);
  /* Держит подсветку усилия внутри своей половины. */
  overflow: hidden;
}

.hud__player--2 {
  justify-content: flex-end;
}

/* Подпись, стрелка, число и слово «место» стоят на одной базовой линии —
   иначе на полосе высотой 68px мелкий текст заметно «плавает» относительно
   крупной цифры. Сама строка при этом центрируется в полосе целиком. */
.hud__line {
  display: flex;
  align-items: baseline;
  gap: var(--gap-md);
}

/* Второй игрок — та же строка, прочитанная справа налево. */
.hud__player--2 .hud__line {
  flex-direction: row-reverse;
}

/* Подсветка текущего усилия: свет от края экрана внутрь, только scaleX.
   20% цвета игрока — предел, при котором подпись поверх подсветки остаётся
   читаемой (Rose Bonbon по этой заливке даёт около 3,6:1 при кегле 26px). */
.hud__effort {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  background: linear-gradient(
    to right,
    color-mix(in srgb, var(--p-color) 20%, transparent),
    transparent 72%
  );
  transform: scaleX(0);
  transform-origin: left center;
  will-change: transform;
}

.hud__player--2 .hud__effort {
  background: linear-gradient(
    to left,
    color-mix(in srgb, var(--p-color) 20%, transparent),
    transparent 72%
  );
  transform-origin: right center;
}

.hud__name {
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: 1;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--p-color);
}

/* Стрелка «выше/ниже соперника» — читается боковым зрением, без слов */
.hud__lead {
  position: relative;
  flex: 0 0 auto;
  width: 1em;
  height: 1em;
  font-size: calc(var(--fs-lead) * 0.95);
  line-height: 1;
  text-align: center;
  color: var(--p-color);
}

.hud__lead-up,
.hud__lead-down {
  position: absolute;
  inset: 0;
  opacity: 0;
  will-change: transform, opacity;
  transition: opacity 0.18s linear;
}

.hud__player.is-lead .hud__lead-up {
  opacity: 1;
  animation: hud-lead 1.6s var(--ease-in-out) infinite;
}

.hud__player.is-trail .hud__lead-down {
  opacity: 0.55;
}

@keyframes hud-lead {
  0%,
  100% {
    transform: translate3d(0, 0, 0);
  }
  50% {
    transform: translate3d(0, -22%, 0);
  }
}

.hud__place {
  display: flex;
  align-items: baseline;
  gap: var(--gap-sm);
}

.hud__player--2 .hud__place {
  flex-direction: row-reverse;
}

/* Крупное число баллов — главное, что игрок ловит боковым зрением на бегу.
   Кегль чуть меньше прежнего числа места: в баллах к концу забега четыре-пять
   знаков вместо двух, и на 62px строка вылезала бы к центру полосы. */
.hud__num {
  font-family: var(--font-display);
  font-size: calc(var(--fs-hero) * 0.55);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  /* Поле сразу под пятизначное число с разделителем разрядов: счётчик растёт
     от 10 до 10 000, и подписи рядом не должны ездить на каждом новом знаке.
     Моноширинные цифры плюс фиксированное поле = строка стоит. */
  min-width: 3.2em;
  text-align: center;
  color: var(--p-color);
  /* Число не переносится по разделителю разрядов (там неразрывный пробел),
     но подстраховаться дешевле, чем поймать перенос на стенде. */
  white-space: nowrap;
}

/* Лидер светится, отстающий приглушён — третий признак сверх стрелки и весов. */
.hud__player.is-lead .hud__num {
  text-shadow: 0 0 28px color-mix(in srgb, var(--p-color) 60%, transparent);
}

.hud__player.is-trail .hud__place {
  opacity: 0.78;
}

.hud__unit {
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--hud-muted);
}

/* --- Центр: таймер --- */

.hud__center {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--gap-sm);
  padding: 0 var(--gap-md);
}

/* Таймер белый — он не принадлежит ни одному игроку, и на полосе с двумя
   цветными числами это единственный нейтральный. */
/* Ширина под две цифры — иначе на 9-й секунде центральная колонка сузилась бы
   и вся строка HUD сдвинулась. Кегль тот же, что у мест: одна базовая линия. */
.hud__timer {
  font-family: var(--font-display);
  font-size: calc(var(--fs-hero) * 0.6);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  min-width: 1.25em;
  text-align: center;
  will-change: transform;
}

.hud__timer-unit {
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--hud-muted);
}

/* --- Весы перевеса: нижний край полосы во всю ширину --- */
/* Место сообщают цифры, а насколько один оторвался от другого — эта линейка:
   заливки расходятся от центра, обе — только scaleX. */

.hud__scale {
  position: relative;
  display: flex;
  height: var(--hud-scale-h);
  background: color-mix(in srgb, var(--stage-text) 14%, var(--stage-bg));
}

.hud__scale-half {
  flex: 1 1 50%;
}

.hud__scale-fill {
  display: block;
  width: 100%;
  height: 100%;
  transform: scaleX(0);
  will-change: transform;
}

.hud__scale-half--1 .hud__scale-fill {
  background: var(--player-1);
  transform-origin: right center;
}

.hud__scale-half--2 .hud__scale-fill {
  background: var(--player-2);
  transform-origin: left center;
}

.hud__scale-tick {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 2px;
  margin-left: -1px;
  background: color-mix(in srgb, var(--stage-text) 55%, var(--stage-bg));
}

/* --- Последние 5 секунд --- */

.hud.is-urgent .hud__timer-unit {
  color: var(--stage-text);
}

/* Пульс только на transform. Размах ограничен 1,1: полоса не обрезает
   содержимое, и таймер не должен вылезать на шапку магазина. */
.hud.is-urgent .hud__timer {
  animation: hud-urgent 1s var(--ease-out) infinite;
}

@keyframes hud-urgent {
  0% {
    transform: scale(1.1);
  }
  22% {
    transform: scale(1);
  }
  100% {
    transform: scale(1);
  }
}

/* ------------------------------------------------------------------ */
/* RESULT — два кадра: сначала итоги, потом оффер                      */
/* ------------------------------------------------------------------ */

/* Правка клиента: «разделить на два экрана. В первом экране итоги, кто сколько
   набрал… через 5 секунд меняется на следующий слайд, где у них „продвигайте
   товары…“». Раньше итоги и оффер делили экран пополам и мешали друг другу:
   слева крупное число, справа мелкий оффер с QR. Теперь каждый кадр получает
   всю ширину — число стало крупнее, QR тоже.

   Кадры лежат в ОДНОЙ ячейке грида и меняются затуханием: ни пересборки
   оверлея, ни мигания затемнения, ни второго таймлайна. */

.scr-result .scr__content {
  display: grid;
  place-items: center;
  padding: var(--gap-lg) var(--gap-xl);
}

.result__stage {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
}

.result__phase {
  /* Обе секции в одной ячейке — они сменяют друг друга, а не соседствуют */
  grid-area: 1 / 1;
  width: 100%;
  opacity: 0;
  will-change: opacity, transform;
}

.result__phase.is-active {
  animation: result-phase-in 0.45s var(--ease-out) both;
}

.result__phase.is-past {
  animation: result-phase-out 0.35s var(--ease-out) both;
}

@keyframes result-phase-in {
  from {
    opacity: 0;
    transform: translate3d(0, 28px, 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

@keyframes result-phase-out {
  from {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
  to {
    opacity: 0;
    transform: translate3d(0, -28px, 0);
  }
}

/* --- Кадр 1: итоги --- */

.result__phase--score {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap-lg);
  text-align: center;
}

.result__badge {
  align-self: center;
  padding: var(--gap-xs) var(--gap-md);
  border-radius: var(--radius-lg);
  background: var(--brand-primary);
  color: var(--brand-on-primary);
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  animation: result-badge 0.5s var(--ease-out) 0.35s both;
}

.result__badge.is-hidden {
  display: none;
}

@keyframes result-badge {
  from {
    opacity: 0;
    transform: scale(0.8);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

.result__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-hero);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.01em;
}

.result__subtitle {
  margin: 0;
  font-size: var(--fs-lead);
  font-weight: 600;
  line-height: 1.2;
  color: var(--p-ink, var(--stage-text));
}

/* Крупное число баллов победителя. Кадр теперь его собственный, во всю
   ширину, — число выросло с 1,5 до 1,9 кегля героя. */
.result__sales {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: var(--gap-md);
}

.result__sales-num {
  font-family: var(--font-display);
  font-size: calc(var(--fs-hero) * 1.9);
  font-weight: 800;
  line-height: 0.92;
  font-variant-numeric: tabular-nums;
  /* Светлый тон игрока: насыщенный цвет буквами по 3D-графике не читается */
  color: var(--p-ink, var(--scr-accent));
}

.result__sales-unit {
  font-size: var(--fs-title);
  font-weight: 700;
  color: var(--scr-muted);
}

/* Итог обоих игроков — «кто сколько набрал». Ради этого кадр и заведён,
   поэтому плашки шире и крупнее прежних. */
.result__scores {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap-lg);
  width: 100%;
  max-width: 1200px;
}

.result__score {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap-xs);
  padding: var(--gap-md) var(--gap-lg);
  border: 3px solid color-mix(in srgb, var(--p-color) 45%, var(--stage-bg));
  border-radius: var(--radius-md);
  /* Итоги обоих игроков лежат прямо на ленте — плашки непрозрачные */
  background: var(--scr-surface);
}

.result__score.is-winner {
  border-color: var(--p-color);
  background: color-mix(in srgb, var(--p-color) 22%, var(--stage-bg));
}

.result__score-name {
  font-size: var(--fs-body);
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--p-color);
}

.result__score-value {
  font-size: calc(var(--fs-title) * 1.35);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.result__score-unit {
  font-size: var(--fs-caption);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--scr-muted);
}

/* --- Кадр 2: оффер. Светлая «бумага», чтобы QR читался камерой --- */

/* Оффер получил кадр целиком: его не читают, а фотографируют, и запас по
   размеру здесь важнее плотности. */
.result__phase--offer {
  display: grid;
  place-items: center;
}

.result__phase--offer .offer {
  width: 100%;
  max-width: 1520px;
}

/* QR вырос с 260 до 360 px: снимают телефоном, часто от бедра и не с первой
   попытки — за оставшиеся секунды человек должен успеть наверняка. */
.result__phase--offer .offer__qr {
  width: 360px;
  height: 360px;
}

.result__phase--offer .offer__title {
  font-size: calc(var(--fs-title) * 1.2);
}

.result__phase--offer .offer__lead {
  font-size: calc(var(--fs-body) * 1.15);
}

.offer {
  display: grid;
  /* minmax(0, 1fr): без этого колонка текста растягивается по самой длинной
     неразрывной строке заголовка и выдавливает QR за край плашки. */
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    'brand brand'
    'text qr'
    'legal legal';
  gap: var(--gap-md) var(--gap-lg);
  padding: var(--gap-lg);
  border-radius: var(--radius-lg);
  background: var(--brand-paper);
  color: var(--brand-ink);
  box-shadow: 0 40px 120px var(--scr-shadow);
  animation: offer-in 0.5s var(--ease-out) 0.15s both;
}

@keyframes offer-in {
  from {
    opacity: 0;
    transform: translate3d(36px, 0, 0);
  }
  to {
    opacity: 1;
    transform: translate3d(0, 0, 0);
  }
}

/* Подпись оффера: логотип Ритейл Медиа на белой «бумаге» — здесь работает
   оригинальный файл, синий по белому. Высота задана, ширина по пропорции. */
.offer__brand {
  grid-area: brand;
}

.offer__brand-logo {
  display: block;
  height: calc(var(--fs-body) * 1.9); /* ≈50px */
  width: auto;
  max-width: 100%;
  object-fit: contain;
  object-position: left center;
}

/* Запасная надпись на случай, если файл логотипа не отдался */
.offer__brand-text {
  display: none;
  font-size: var(--fs-caption);
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--brand-primary);
}

.offer__brand.is-logo-missing .offer__brand-logo {
  display: none;
}

.offer__brand.is-logo-missing .offer__brand-text {
  display: block;
}

.offer__text {
  grid-area: text;
  display: flex;
  flex-direction: column;
  gap: var(--gap-md);
}

.offer__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-title);
  font-weight: 800;
  line-height: 1.02;
  letter-spacing: -0.01em;
  text-wrap: balance;
}

.offer__lead {
  margin: 0;
  font-size: var(--fs-body);
  line-height: 1.3;
  color: color-mix(in srgb, var(--brand-ink) 72%, transparent);
}

.offer__bonus {
  margin: 0;
  font-size: var(--fs-lead);
  font-weight: 800;
  line-height: 1.1;
  color: var(--brand-primary);
}

.offer__promo {
  display: flex;
  align-items: baseline;
  gap: var(--gap-sm);
  align-self: flex-start;
  padding: var(--gap-sm) var(--gap-md);
  border: 3px dashed color-mix(in srgb, var(--brand-primary) 55%, transparent);
  border-radius: var(--radius-md);
  font-size: var(--fs-lead);
  font-weight: 700;
}

.offer__promo-code {
  font-family: var(--font-display);
  font-size: var(--fs-title);
  font-weight: 800;
  letter-spacing: 0.06em;
  color: var(--brand-primary);
}

.offer__qr {
  grid-area: qr;
  align-self: center;
  /* 260 px: с полуметра телефон снимает такой код уверенно, а колонке текста
     остаётся ширина, на которой заголовок оффера ложится в две строки. */
  width: 260px;
  height: 260px;
  padding: var(--gap-sm);
  box-sizing: border-box;
  border-radius: var(--radius-md);
  background: var(--brand-paper);
  border: 2px solid color-mix(in srgb, var(--brand-ink) 12%, transparent);
}

/* Деградация: если assets/qr.svg не загрузился — блок исчезает,
   вёрстка не ломается, промокод остаётся на месте */
.offer.is-qr-missing .offer__qr {
  display: none;
}

.offer.is-qr-missing {
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    'brand'
    'text'
    'legal';
}

.offer__qr-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Мелкий шрифт по бумаге: 58% charcoal давали 3,4:1 — под нормой даже для
   сноски. 72% держат 5,3:1 и при этом остаются заметно тише основного текста. */
.offer__legal {
  grid-area: legal;
  margin: 0;
  font-size: var(--fs-legal);
  line-height: 1.35;
  color: color-mix(in srgb, var(--brand-ink) 72%, transparent);
}
