/**
 * wheel-visual.css — оформление модуля «Колесо фортуны».
 *
 * Все классы с префиксом wf-, чтобы не пересечься с легаси-стилями брендов
 * (.popup/.window/.btn у zabkabet, .kn-* у krazik, .kassa-* в кассе).
 * Цвета приходят из JS переменными --wf-*; сама таблица палитр не содержит,
 * иначе тема перестала бы быть данными кампании (theme_json, §5.2).
 *
 * Мобильный приоритет: базовые правила рассчитаны на 360×640, десктоп —
 * в @media выше. Высоты только в dvh: 100vh на iOS даёт «подрезанный» экран
 * под адресной строкой (см. project_bug_notifications_scroll_dvh).
 *
 * ДВА ПРАВИЛА КОМПОЗИЦИИ, которые легко сломать обратно (N23):
 *  1. Срез колеса — следствие нехватки высоты, а не константа. --wf-crop это
 *     ПОТОЛОК среза; пока высота есть, --wf-view равна --wf-size и круг цел.
 *     Резать при наличии пустоты — то, за что модалку и вернули на доработку.
 *  2. Свободную высоту распределяют распорки .wf-inner::before/::after через
 *     flex-grow. Любое auto-поле (margin-top:auto и т.п.) в этом контейнере
 *     ОТМЕНЯЕТ распределение: авто-поля съедают остаток раньше flex-grow,
 *     и весь запас снова соберётся в один провал.
 *
 * N29. КОМПАКТНОЕ ОКНО (класс .wf-overlay--compact, гостевой режим) — это не
 * «уменьшенный оверлей», а ДРУГАЯ коробка: .wf-window. Фон кампании и лучи
 * переезжают внутрь неё, оверлей остаётся голой подложкой, сквозь которую
 * видно страницу. Три запрета, каждый оплачен дефектом:
 *   • у .wf-window не может быть ни z-index, ни transform, ни filter, ни
 *     isolation: любое из них создаёт КОНТЕКСТ НАЛОЖЕНИЯ, и слои оверлея
 *     замыкаются внутри окна. Тогда карточка приза (z-index 7, ребёнок
 *     оверлея) легла бы поверх ВСЕГО окна вместе с крестиком (z-index 9) —
 *     ровно дефект B1, из-за которого крестик и поднимали;
 *   • режим включается только от 720×620 (@media-гейт ниже). Меньше — окно
 *     объявлено display:contents и исчезает как коробка, оставляя ровно
 *     полноэкранную разметку: на телефоне компактная модалка означала бы
 *     колесо ~250px, то есть подпись мельче 8px;
 *   • доли экрана 0.60 × 0.82 перемножаются в 49.2% площади — это и есть
 *     потолок владельца «не более 50%». Менять любую из них можно только
 *     ПАРОЙ, иначе потолок молча уедет (сторож в dom-smoke.js).
 */

/* P30: модуль не полагается на глобальный `* { box-sizing: border-box }`
   брендов — эталон обязан быть переносимым. Объявляем сами и только у себя. */
.wf-overlay, .wf-overlay *, .wf-overlay *::before, .wf-overlay *::after,
.wf-root, .wf-root *, .wf-root *::before, .wf-root *::after { box-sizing: border-box; }

/* ─────────────────────────── общие переменные ─────────────────────────── */
.wf-root,
.wf-overlay {
    --wf-font: 'Open Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
    /* ПОЛИТИКА СРЕЗА (после жалобы «края колеса не отображаются»):
       — по горизонтали не режем НИКОГДА: кадр ≤ 108vw, а сам круг занимает
         в кадре 1000/1088 = 91.9%, то есть ровно 100vw и ни пикселем больше;
       — по вертикали режем ТОЛЬКО тогда, когда высоты физически не хватило. */

    /* Честная оценка шапки: поля оверлея 12 + клиренс крестика 38 + заголовок
       ~47 + подзаголовок ~27 + подвал (счётчик спинов + таймер) ~46 + вкладки
       FE-CLIENT 44 (.wh-tabs, wheel.css Агента 5). Занижать её нельзя: на
       занижение сцена отвечает не «дырой», а срезом колеса сверх обещанного. */
    --wf-chrome: 208px;

    /* Сколько высоты реально остаётся сцене. max(0px,…) — страховка на экранах,
       где шапка выше самого экрана: отрицательная высота ломает min() ниже. */
    --wf-avail: max(0px, calc(100dvh - var(--wf-chrome)));

    /* N23: --wf-crop — это ПРЕДЕЛ среза, а не постоянный множитель.
       Раньше сцена всегда была ростом size×0.90, то есть 10% круга срезалось
       даже на iPhone 14 Pro Max, где под колесом оставалось ~290px пустого
       фона. Теперь доля 0.90 работает только как нижняя граница: режем ровно
       настолько, насколько не хватило высоты, и не глубже. */
    --wf-crop: 0.90;

    /* Сторона КАДРА колеса (круг = 91.9% от неё). Три ограничителя:
       ширина экрана, высота (через предел среза) и абсолютный потолок. */
    --wf-size: min(108vw, calc(var(--wf-avail) / var(--wf-crop)), 720px);

    /* Высота СЦЕНЫ = сколько круга реально показываем. Пока высоты хватает,
       она равна --wf-size и среза нет вовсе; на низком экране упирается
       в остаток и срезает низ — но не больше, чем разрешает --wf-crop. */
    --wf-view: min(var(--wf-size), var(--wf-avail));
}

/* ═══════════════════════════ оболочка модалки ═════════════════════════ */

/* Блокировка прокрутки под модалкой. position:fixed на body вместо
   overflow:hidden — иначе iOS продолжает тянуть страницу под оверлеем. */
html.wf-lock { overflow: hidden; }
html.wf-lock body {
    position: fixed;
    left: 0;
    right: 0;
    width: 100%;
    overflow: hidden;
}

/* ── Оболочка компактного окна (N29).
 *
 * Узел создаётся в JS ТОЛЬКО при compact:true, поэтому у авторизованного
 * игрока его в разметке нет вовсе — полноэкранный режим не может пострадать
 * от правил, которых он не видит.
 *
 * display:contents — окно исчезает как коробка, и .wf-bg/.wf-rays/.wf-close/
 * .wf-inner снова становятся прямыми детьми оверлея, то есть разметка ровно
 * полноэкранная. Так признак «компактно» сам выключается там, где для окна не
 * хватает экрана, и вызвавшему не нужно ничего знать про размеры устройства.
 *
 * Первое объявление — фолбэк для движков без display:contents (там значение
 * невалидно и правило целиком отбрасывается): окно остаётся блоком и обязано
 * вести себя как сам оверлей, иначе на старом WebKit гость увидел бы съехавшую
 * вёрстку. Позиционирования у фолбэка нет намеренно — абсолютные .wf-bg и
 * .wf-close должны по-прежнему считаться от оверлея. */
.wf-window {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: center;
    width: 100%;
    min-height: 0;
}
.wf-window { display: contents; }

.wf-overlay {
    position: fixed;
    inset: 0;
    z-index: 100060;              /* максимум в client/css сайтов — 10001 */
    display: flex;
    flex-direction: column;
    align-items: center;
    height: 100dvh;
    min-height: 100dvh;
    /* padding-bottom = 0 намеренно: срез колеса должен совпадать с краем
       экрана, иначе «обрезано» читается как «сломано». */
    padding: max(12px, env(safe-area-inset-top)) env(safe-area-inset-right)
             0 env(safe-area-inset-left);
    overflow: hidden;
    overscroll-behavior: contain;
    font-family: var(--wf-font);
    color: var(--wf-ink, #fff);
    opacity: 0;
    visibility: hidden;
    transition: opacity .28s ease, visibility .28s;
    -webkit-tap-highlight-color: transparent;
}
.wf-overlay.is-open { opacity: 1; visibility: visible; }
/* M7: visibility:hidden не останавливает CSS-анимации — лучи и лампы
   продолжали бы жечь GPU до конца сессии. Гасим всё, что крутится. */
.wf-overlay:not(.is-open) *,
.wf-overlay:not(.is-open) *::after,
.wf-overlay:not(.is-open) *::before { animation-play-state: paused !important; }
.wf-overlay:focus { outline: none; }

.wf-bg {
    position: absolute;
    inset: 0;
    background:
        radial-gradient(120% 78% at 50% 6%, rgba(255, 255, 255, .16), rgba(255, 255, 255, 0) 60%),
        linear-gradient(178deg, var(--wf-bg-from, #8B3DFF), var(--wf-bg-to, #3F0F86));
}
.wf-overlay--light .wf-bg {
    background:
        radial-gradient(120% 78% at 50% 4%, #fff, rgba(255, 255, 255, 0) 62%),
        linear-gradient(178deg, var(--wf-bg-from, #F0F9FF), var(--wf-bg-to, #D7ECFB));
}

/* Лучи из-за колеса. Медленное вращение — только transform, слой отдельный. */
.wf-rays {
    /* 190vmax при dpr 3 давало бы 4810 device-px — выше типового
       GL_MAX_TEXTURE_SIZE 4096, слой не промоутится и маскированный
       конический градиент перерисовывается покадрово. Ограничиваем. */
    --wf-rays-size: min(190vmax, 2400px);
    position: absolute;
    left: 50%;
    top: 62%;
    width: var(--wf-rays-size);
    height: var(--wf-rays-size);
    margin: calc(var(--wf-rays-size) / -2) 0 0 calc(var(--wf-rays-size) / -2);
    opacity: .16;
    background: repeating-conic-gradient(from 0deg,
        rgba(255, 255, 255, .9) 0deg 4deg, rgba(255, 255, 255, 0) 4deg 15deg);
    -webkit-mask-image: radial-gradient(closest-side, #000 12%, rgba(0, 0, 0, 0) 72%);
            mask-image: radial-gradient(closest-side, #000 12%, rgba(0, 0, 0, 0) 72%);
    animation: wf-rays 46s linear infinite;
    will-change: transform;
    pointer-events: none;
}
.wf-overlay--light .wf-rays { opacity: .3; mix-blend-mode: multiply; }
@keyframes wf-rays { to { transform: rotate(360deg); } }

.wf-close {
    position: absolute;
    top: max(10px, env(safe-area-inset-top));
    right: max(10px, env(safe-area-inset-right));
    /* B1: крестик обязан быть ВЫШЕ карточки приза (z-index 7), иначе игрок
       заперт в модалке до нажатия «Забрать». */
    z-index: 9;
    width: 44px;
    height: 44px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    background: rgba(0, 0, 0, .28);
    color: #fff;
    cursor: pointer;
    backdrop-filter: blur(6px);
    transition: transform .16s ease, background .16s ease;
}
.wf-close svg { width: 22px; height: 22px; }
.wf-close:hover { background: rgba(0, 0, 0, .44); transform: scale(1.06); }
.wf-close:active { transform: scale(.94); }
/* Кольцо фокуса берёт ОРЕОЛ заголовка (--wf-focus), а не заливку: ореол —
   единственный тон темы, про который в JS доказано, что он читается на фоне
   кампании, и он же чинится страховкой buildTheme при заказном фоне. Прежний
   --wf-title-a на светлом скине давал 2.28:1 к фону (норма 3.0). */
.wf-close:focus-visible { outline: 3px solid var(--wf-focus, #fff); outline-offset: 2px; }
.wf-overlay--light .wf-close { background: rgba(0, 0, 0, .08); color: var(--wf-ink, #1d1d1f); }
.wf-overlay--light .wf-close:hover { background: rgba(0, 0, 0, .16); }

.wf-inner {
    position: relative;
    z-index: 2;
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    align-items: center;
    width: 100%;
    max-width: 720px;
    min-height: 0;
    text-align: center;
    /* Верхняя полоса модалки принадлежит крестику (44px в правом углу).
       Заголовок центрирован, и на телефоне он доезжает до кнопки вплотную:
       ширина «КОЛЕСО ФОРТУНЫ» ≈ 0.82×экрана, то есть правый край строки
       заходит под крестик на любом экране уже 587px. Поэтому клиренс —
       ЖЁСТКИЙ, а не из остатка высоты: остаток на низком экране обнуляется,
       а столкновение с кнопкой не должно от этого зависеть.
       Сверх клиренса — воздух, растущий с высотой экрана: часть запаса,
       который иначе целиком ушёл бы в шов над колесом, лучше отдать шапке —
       она от этого выглядит просторной, а не подвешенной. */
    padding-top: clamp(38px, calc((100dvh - 600px) * .22), 96px);
}

.wf-logo {
    max-width: 46vw;
    max-height: 42px;
    margin: 2px auto 6px;
    object-fit: contain;
}

/* ── «Залитый» объёмный заголовок: ДВА слоя, три материала.
 *
 * N24. Прежняя схема была неверна по существу, а не по цвету. Заливка жила
 * в background-clip:text САМОГО h2, то есть красилась на шаге «фон элемента»,
 * а слой обводки (::before, z-index:-1) — на шаге «потомки с отрицательным
 * z-index», то есть ПОВЕРХ заливки. Обводка 7px закрывала по 3.5px внутрь
 * буквы с каждой стороны; при кегле 40px это шире, чем штрих у большинства
 * знаков — от «КОЛЕСО ФОРТУНЫ» на lime-dark оставалось белое пятно. Обводка
 * обязана отделять букву от фона, а не съедать её.
 *
 * Порядок отрисовки внутри .wf-title (position:relative + z-index:0 — свой
 * контекст наложения):
 *   1. собственный текст h2 — прозрачная заливка + ШИРОКИЙ ореол. Его пара —
 *      «ореол ↔ фон кампании»: он отрывает заголовок от подложки;
 *   2. ::before поверх — градиентная заливка буквы И ТОНКИЙ кант по её краю.
 *      Кант в том же слое намеренно: -webkit-text-stroke кладётся поверх своей
 *      заливки, и это ровно то поведение, которое нужно канту (0.04em внутрь
 *      читаются как контур буквы). Его пара — «кант ↔ заливка».
 * paint-order для HTML-текста не годится: поддержан не везде. Дубль — везде.
 *
 * Толщины — в em, а не в px: заголовок ходит от 20px (ландшафт) до 54px
 * (десктоп), и фиксированные 7px на 20px превращали кант в кляксу. Раньше это
 * лечилось двумя перекрытиями в медиа-запросах — теперь не нужно вовсе. */
.wf-title {
    position: relative;
    padding: 0 12px;
    z-index: 0;
    margin: 2px 0 4px;
    font-size: clamp(26px, 9.4vw, 54px);
    font-weight: 900;
    line-height: 1.02;
    letter-spacing: .5px;
    text-transform: uppercase;
    --wf-title-halo-w: .20em;   /* наружу видно половину: .10em */
    --wf-title-edge-w: .085em;  /* наружу .042em, внутрь буквы столько же */
    color: transparent;
    -webkit-text-stroke: var(--wf-title-halo-w) var(--wf-title-stroke, #fff);
    /* Выдавливание и падающая тень — на самом нижнем слое, под ореолом. */
    text-shadow:
        0 3px 0 rgba(0, 0, 0, .18),
        0 6px 0 rgba(0, 0, 0, .12),
        0 10px 22px var(--wf-title-shadow, rgba(0, 0, 0, .5));
}
.wf-title::before {
    content: attr(data-text);
    position: absolute;
    inset: 0 12px;          /* N17: совпадает с padding самого .wf-title,
                               иначе перенос длинного названия кампании
                               в слое заливки встанет в другом месте */
    z-index: 1;             /* строго ПОВЕРХ ореола */
    background: linear-gradient(180deg, var(--wf-title-a, #FFE066) 18%, var(--wf-title-b, #FFA800) 96%);
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    -webkit-text-stroke: var(--wf-title-edge-w) var(--wf-title-edge, #3A2708);
}
.wf-sub {
    /* Ритм шапки тоже дышит с высотой экрана: на 667px это прежние 6px,
       на 932px — 20px. Ещё немного запаса, изъятого из шва над колесом. */
    margin: 0 0 clamp(6px, 1.6dvh, 20px);
    padding: 0 16px;
    font-size: clamp(12px, 3.6vw, 16px);
    font-weight: 600;
    line-height: 1.35;
    color: var(--wf-ink-dim, rgba(255, 255, 255, .8));
    text-shadow: 0 2px 6px rgba(0, 0, 0, .28);
}
.wf-overlay--light .wf-sub { text-shadow: none; }

/* Искры по бокам заголовка */
.wf-star {
    position: absolute;
    width: 26px;
    height: 26px;
    color: var(--wf-title-a, #FFE066);
    opacity: .9;
    filter: drop-shadow(0 0 8px currentColor);
    animation: wf-twinkle 2.6s ease-in-out infinite;
    pointer-events: none;
}
.wf-star--l { left: 2%; top: 12%; }
.wf-star--r { right: 3%; top: 4%; width: 18px; height: 18px; animation-delay: .9s; }
@keyframes wf-twinkle {
    0%, 100% { transform: scale(.75) rotate(0deg); opacity: .55; }
    50%      { transform: scale(1.12) rotate(28deg); opacity: 1; }
}

.wf-stage {
    position: relative;
    display: flex;
    /* НЕ flex:1 1 auto: с flex-grow сцена забирала бы весь запас высоты,
       height работал бы лишь как базис, и под колесом зияла бы пустота.
       Сцена ровно своей высоты и сжимается только там, где высоты нет. */
    flex: 0 1 auto;
    order: 1;                 /* всегда последний блок в модалке */
    justify-content: center;
    align-items: flex-start;  /* режется низ, а не верх: ступица важнее обода */
    width: 100%;
    min-height: 0;
    height: var(--wf-view);
    overflow: hidden;
}

/* Порядок задаётся через order, а НЕ перестановкой в DOM: FE-CLIENT
   (wheel.js Агента 5) вставляет .wh-tabs перед .wf-stage и .wh-pane перед
   .wf-foot — при перестановке узлов его вкладки уехали бы под панель.
   Дефолтный order:0 у .wh-tabs/.wh-pane ставит их ровно между шапкой и сценой. */
/* N23: РАСПРЕДЕЛЕНИЕ СВОБОДНОЙ ВЫСОТЫ.
   margin-top:auto (прежнее решение) — инструмент «всё в один шов»: flexbox
   отдаёт авто-полям ВЕСЬ остаток и делит его поровну между ними. На 390×844
   это дало приемлемые 2×125px, а на 430×932 остаток вырос и превратился
   в провал между вкладками и колесом — дыра не исчезла, а переехала.
   Здесь остаток делит flex-grow: он работает от ФАКТИЧЕСКОГО запаса, а не от
   того, угадали ли мы --wf-chrome, и веса дают композицию, устойчивую к любой
   высоте. Ниже больше, чем выше (10:11): круг — оптически тяжёлая фигура и
   в геометрическом центре читается как «съехал вниз».
   ::before/::after у flex-контейнера — полноценные flex-элементы, поэтому
   ни одного узла в DOM не добавляется: вставки FE-CLIENT (wheel.js кладёт
   .wh-tabs перед .wf-stage, .wh-pane перед .wf-foot) остаются нетронутыми. */
.wf-inner::before,
.wf-inner::after {
    content: '';
    /* Длинные формы вместо shorthand: unitless-ноль в третьем поле `flex`
       старые WebKit разбирали не всегда, а распорка обязана быть нулевой
       ровно везде — иначе она стала бы видимой дырой на низком экране. */
    flex-basis: 0;
    flex-shrink: 0;
    width: 100%;
    min-height: 0;
    pointer-events: none;
}
.wf-inner::before { order: 1; flex-grow: 10; }   /* воздух над колесом */
.wf-inner::after  { order: 2; flex-grow: 11; }   /* воздух под колесом */

/* На вкладках «История»/«Правила» сцены нет (display:none), а .wh-pane сам
   растягивается (flex:1 1 auto в wheel.css). Распорки с весами 10 и 11
   отобрали бы у панели 21/22 свободной высоты — гасим их на время панели. */
.wh-panel-open .wf-inner::before,
.wh-panel-open .wf-inner::after { display: none; }

.wf-logo  { order: -4; }
.wf-title { order: -3; }
.wf-sub   { order: -2; }
.wf-foot {
    order: -1;                /* счётчик спинов — в шапку: под колесом на
                                 референсе нет ничего, срез = край экрана */
    flex: 0 0 auto;
    padding: 4px 0 2px;
}
.wf-spins {
    font-size: 14px;
    font-weight: 800;
    letter-spacing: .3px;
    color: var(--wf-ink, #fff);
    text-shadow: 0 2px 8px rgba(0, 0, 0, .4);
}
.wf-spins.is-empty { opacity: .6; }
.wf-overlay--light .wf-spins { text-shadow: none; }

/* ═════════════════════════════ само колесо ════════════════════════════ */

.wf-root {
    position: relative;
    width: var(--wf-size);
    max-width: 100%;          /* автономное использование: не рвём чужой контейнер */
    flex: 0 0 auto;
    font-family: var(--wf-font);
}
/* Внутри модалки вылет за боковые края НАМЕРЕННЫЙ, поэтому ограничение
   снимается. Иначе колесо ужалось бы до 100vw, а высота сцены осталась бы
   посчитанной от --wf-size — и под колесом снова появилась бы пустота. */
.wf-stage > .wf-root { max-width: none; }

.wf-wheel {
    position: relative;
    width: 100%;
    aspect-ratio: 1 / 1;
}

.wf-face,
.wf-frame {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* Вращается ОТДЕЛЬНЫЙ svg-элемент: композитор двигает готовый слой,
   а не перерисовывает 16 секторов с текстом на каждом кадре. */
.wf-face {
    transform-origin: 50% 50%;
    will-change: transform;
    backface-visibility: hidden;
}

/* ── ГАРНИТУРА ПОДПИСЕЙ (N28).
 *
 * Прежнее правило «ноль внешних зависимостей» касалось внешних ХОСТОВ, а не
 * шрифтов: файл лежит рядом со статикой бренда и уезжает тем же скриптом
 * раскладки, что и сам компонент. Стек системных узких начертаний правился
 * дважды и оба раза читался как «системный текст» — потому что им и был:
 * на Android это Roboto Condensed, на Windows — Arial Narrow, то есть ровно
 * тот шрифт, которым набран весь остальной интерфейс телефона.
 *
 * Oswald — узкий гротеск семейства Alternate Gothic: родная типографика
 * лотерейного билета, тотализатора и ярмарочного колеса. Три причины
 * выбора, кроме характера:
 *   • прописные высотой 0.810 em против 0.711 у Roboto Condensed (фолбэк
 *     Android). Замер на коридоре 16-секторного колеса: «1000 поинтов»
 *     набирается кеглем 45.3 против 44.1 ед., высота прописной 36.7 против
 *     31.4 — подпись физически КРУПНЕЕ на 17%, а не просто «другая»;
 *   • самая экономная ширина среди кириллических кандидатов с полным
 *     покрытием (замер всех в одинаковых условиях): «1000 поинтов» = 4.79 em
 *     против 5.6–6.1 у геометрических (Onest, Geologica, Rubik) и 7.58 у
 *     Unbounded — на 16 секторах это решает, влезет ли строка;
 *   • вариативная ось веса 400–700 одним файлом: число и единица набираются
 *     РАЗНЫМИ начертаниями без второго запроса и без фальшивого жирного.
 *
 * Лицензия SIL OFL 1.1 (файл лицензии лежит рядом: fonts/OFL-Oswald.txt),
 * без Reserved Font Name — сабсет и размещение у себя разрешены явно.
 * Сабсет: кириллица (вкл. укр./бел./каз.), латиница с расширением A
 * (польский, чешский, турецкий), цифры, пунктуация и блок валют
 * (₽ ₴ ₸ ₺ ₹ ₩ € £ ¥ №). 33 КБ woff2 на игрока, один файл на все темы.
 *
 * ⚠️ ?v=1 в адресе — метка версии САБСЕТА. Пересобрали набор символов —
 * поднимите её, иначе у вернувшегося игрока останется старый файл из кэша.
 */
@font-face {
    font-family: 'Oswald Wheel';
    src: url('../fonts/wheel-oswald.woff2?v=1') format('woff2');
    font-weight: 400 700;          /* вариативная ось wght, весь рабочий диапазон */
    font-style: normal;
    /* swap, а не block: колесо обязано быть читаемым С ПЕРВОГО КАДРА. Пока
       файл едет — работает системный стек ниже, после загрузки JS пересчитывает
       кегль по новым метрикам (_watchFont → _refit), поэтому подмена не может
       вытолкнуть подпись из коридора. */
    font-display: swap;
}

/* Подписи секторов. Первым — своя гарнитура, дальше ТОТ ЖЕ системный стек
   узких начертаний, что работал раньше: Roboto Condensed на Android, Avenir
   Next Condensed на iOS/Mac, Segoe UI Variable Display на Windows 11
   (переменный — реально сжимается через font-stretch), Arial Narrow на
   Windows 10. Все несут кириллицу, поэтому и при неудачной загрузке подпись
   не собирается из двух разных шрифтов, а кегль всё равно подгоняется по
   фактическому замеру — вёрстка не плывёт ни в одном из двух состояний. */
.wf-label {
    font-family: 'Oswald Wheel', 'Roboto Condensed', 'Avenir Next Condensed',
                 'Segoe UI Variable Display', 'Arial Narrow', 'Segoe UI', var(--wf-font);
    font-weight: 700;
    font-stretch: 88%;
    /* Табличные цифры — для системного фолбэка: там они есть и без них «1000»
       и «500» на соседних секторах разной ширины. У Oswald фичи tnum нет
       вовсе (проверено по таблицам GSUB: aalt ccmp dlig frac liga ordn sups
       kern), и это не дефект набора: узкая «1» — часть характера гротеска
       Alternate Gothic. Ширину строки правило не меняет, кегль считается
       замером, поэтому объявление безопасно оставить обоим состояниям. */
    font-variant-numeric: tabular-nums lining-nums;
    letter-spacing: -0.005em;
}
/* Число доминирует, единица измерения — спокойный спутник. Это и отличает
   дорогую подачу от «текста одним кеглем». Обе части в ОДНОЙ строке.
   ⚠️ font-size единицы ДУБЛИРУЕТ константу UNIT_K в wheel-visual.js: по ней
   считается ширина строки до измерения. Менять только парой (сторож в стенде).
   N27: было .62em/600/.82 — спутник оказался слишком тихим и на светлой заливке
   уходил в серую сноску. Иерархия сохранена, но единица снова читается. */
.wf-label__num  { font-weight: 800; letter-spacing: -0.02em; }
.wf-label__unit { font-size: .72em; font-weight: 700; letter-spacing: .04em; opacity: .92; }

/* Светлая тема набирает подписи ТЯЖЕЛЕЕ. На тёмных пресетах массу букве давал
   контраст к яркой заливке — глиф там «горит». На бледно-голубой и белой
   заливке светлого скина этого источника нет, и тот же вес читается как
   системный текст, а не как часть игрового виджета. Добираем весом, а не
   цветом: цвет уже занят страховкой контраста AA (autoInk) и трогать его
   нельзя. Шаг ровно один — 700→800 и 800→900. */
.wf-root--light .wf-label,
.wf-overlay--light .wf-label { font-weight: 800; }
.wf-root--light .wf-label__num,
.wf-overlay--light .wf-label__num { font-weight: 900; }
.wf-root--light .wf-label__unit,
.wf-overlay--light .wf-label__unit { font-weight: 800; opacity: 1; }

/* N28. ВЕСОВАЯ ШКАЛА СВОЕЙ ГАРНИТУРЫ — отдельным блоком, под классом
   .is-wf-oswald, который JS ставит ТОЛЬКО после фактической загрузки файла
   (_watchFont). Смысл разделения: числа 700/800/900 выше подобраны под
   СИСТЕМНЫЕ узкие начертания и дважды приняты владельцем — если файл не
   доехал, шкала обязана остаться ровно той. Своя ось у Oswald кончается на
   700, и те же 800/900 упёрлись бы в потолок: число и единица получили бы
   один вес, а иерархия «число крупно + единица спокойно» держится не только
   кеглем, но и массой.
   Шкала внутри оси: число — на потолке (герой сектора), единица на две
   ступени легче, слово без числа («Пусто», «Подарок») — само себе герой,
   поэтому на ступень выше единицы. Светлый скин добавляет ступень тем, кто
   может её взять: на цветной заливке тёмных тем букву «зажигает» контраст,
   на бледно-голубой и белой заливке этого источника массы нет. */
.wf-root.is-wf-oswald .wf-label       { font-weight: 600; }
.wf-root.is-wf-oswald .wf-label__num  { font-weight: 700; }
.wf-root.is-wf-oswald .wf-label__unit { font-weight: 500; }
.wf-root--light.is-wf-oswald .wf-label       { font-weight: 700; }
.wf-root--light.is-wf-oswald .wf-label__num  { font-weight: 700; }
.wf-root--light.is-wf-oswald .wf-label__unit { font-weight: 600; opacity: 1; }

.wf-lamp { animation: wf-lamp 1.6s ease-in-out infinite; }
@keyframes wf-lamp { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }

/* У указателя НЕТ css-фильтра намеренно: он поворачивается на каждом кадре
   («щелчок» по штырькам), а фильтр заставлял бы браузер пересобирать его
   покадрово. Тень нарисована отдельным путём в _renderFrame(). */

/* ── центральная кнопка */
.wf-hub {
    position: absolute;
    left: 50%;
    top: 50%;
    width: var(--wf-hub-pct, 24.63%);   /* доля кадра, значение ставит JS */
    aspect-ratio: 1 / 1;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    background:
        radial-gradient(circle at 34% 26%, rgba(255, 255, 255, .5), rgba(255, 255, 255, 0) 54%),
        radial-gradient(circle at 50% 74%, rgba(0, 0, 0, .38), rgba(0, 0, 0, 0) 62%),
        var(--wf-hub, #7B2FF7);
    box-shadow:
        0 0 0 5px rgba(255, 255, 255, .18),
        0 0 26px 2px var(--wf-hub-glow, rgba(241, 7, 163, .7)),
        0 10px 22px rgba(0, 0, 0, .45),
        inset 0 -6px 14px rgba(0, 0, 0, .35);
    color: var(--wf-hub-ink, #fff);
    cursor: pointer;
    transition: transform .12s ease, filter .16s ease;
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
}
.wf-hub:hover:not(:disabled) { filter: brightness(1.08); }
.wf-hub:active:not(:disabled),
.wf-hub.is-press:not(:disabled) { transform: translate(-50%, -50%) scale(.94); }
.wf-hub:disabled { cursor: default; }
/* Ступица — единственная кнопка, стоящая на РАЗНОЦВЕТНОЙ подложке: кольцо на
   отступе 4px ложится на лицо колеса, где рядом соседствуют жёлтый сектор и
   почти чёрный. Одиночным тоном это не закрывается ни на одном пресете (замер:
   белое кольцо 1.00…1.57, тёмное 1.17…7.10) — поэтому колец ДВА, вплотную:
   спутник противоположного тона 0–4px и основное кольцо 4–7px. На любой
   подложке видно хотя бы одно; так же устроен и системный фокус браузеров.
   Штатное `0 0 0 5px rgba(255,255,255,.18)` на время фокуса уступает место
   спутнику: полупрозрачный ободок поверх него всё равно не был бы кольцом. */
.wf-hub:focus-visible {
    outline: 3px solid var(--wf-focus, #fff);
    outline-offset: 4px;
    box-shadow:
        0 0 0 4px var(--wf-focus-alt, #0B0B0B),
        0 0 26px 2px var(--wf-hub-glow, rgba(241, 7, 163, .7)),
        0 10px 22px rgba(0, 0, 0, .45),
        inset 0 -6px 14px rgba(0, 0, 0, .35);
}

.wf-hub__label {
    position: relative;
    z-index: 2;
    font-weight: 900;
    line-height: 1;
    letter-spacing: .06em;
    text-transform: uppercase;
    text-shadow: 0 2px 6px rgba(0, 0, 0, .45);
    pointer-events: none;
    user-select: none;
}
.wf-overlay--light .wf-hub__label,
.wf-root--light .wf-hub__label { text-shadow: 0 1px 2px rgba(0, 0, 0, .25); }

/* Индикатор ожидания ответа сервера (setBusy) */
.wf-hub__ring {
    position: absolute;
    inset: 12%;
    border-radius: 50%;
    border: 3px solid transparent;
    border-top-color: currentColor;
    opacity: 0;
    pointer-events: none;
}
.wf-hub.is-busy .wf-hub__ring { opacity: .9; animation: wf-rot .85s linear infinite; }
.wf-hub.is-busy .wf-hub__label { opacity: .45; }
@keyframes wf-rot { to { transform: rotate(360deg); } }

/* Пульс «спины есть» — transform+opacity на псевдоэлементе, без repaint тени */
.wf-hub::after {
    content: '';
    position: absolute;
    inset: -7%;
    border-radius: 50%;
    border: 2px solid currentColor;
    opacity: 0;
    pointer-events: none;
}
.wf-hub.is-ready::after { animation: wf-pulse 1.9s ease-out infinite; }
@keyframes wf-pulse {
    0%   { transform: scale(.92); opacity: .55; }
    70%  { transform: scale(1.22); opacity: 0; }
    100% { transform: scale(1.22); opacity: 0; }
}

/* Список призов для скринридеров: SVG помечен aria-hidden */
.wf-sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* ═══════════════════════════ частицы и вспышка ════════════════════════ */

.wf-fx {
    position: absolute;
    inset: 0;
    z-index: 5;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

.wf-flash {
    position: absolute;
    inset: 0;
    z-index: 4;
    background: radial-gradient(60% 50% at 50% 45%, #fff, rgba(255, 255, 255, 0) 72%);
    opacity: 0;
    pointer-events: none;
}
.wf-flash.is-on { animation: wf-flash .85s ease-out forwards; }
@keyframes wf-flash {
    0%   { opacity: 0; }
    12%  { opacity: .92; }
    100% { opacity: 0; }
}

/* ═════════════════════════ карточка результата ════════════════════════ */

.wf-prize {
    position: absolute;
    inset: 0;
    z-index: 7;
    display: flex;
    align-items: center;
    /* B1: `safe` не даёт обрезать верх карточки, когда она выше экрана
       (ландшафт 740×320 с системной панелью Android). Браузеры без
       поддержки ключевого слова откатятся на align-items:center строкой выше. */
    align-items: safe center;
    justify-content: center;
    padding: 16px;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: rgba(8, 2, 24, .62);
    /* M7: backdrop-filter здесь включался ровно в момент салюта (до 286
       частиц на canvas) — самый дорогой кадр сессии. Плоская подложка. */
    opacity: 0;
    transition: opacity .22s ease;
}
.wf-overlay--light .wf-prize { background: rgba(240, 249, 255, .78); }
.wf-prize.is-on { opacity: 1; }

.wf-prize__card {
    width: min(92vw, 380px);
    max-height: 100%;
    flex: 0 0 auto;
    margin: auto 0;
    padding: 26px 22px 22px;
    border-radius: 24px;
    border: 1px solid rgba(255, 255, 255, .18);
    background: var(--wf-card-bg, linear-gradient(180deg, #7A2BE8, #4B1296));
    color: var(--wf-card-ink, #fff);
    text-align: center;
    box-shadow: 0 24px 60px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .22);
    transform: scale(.86) translateY(12px);
    transition: transform .34s cubic-bezier(.2, 1.5, .4, 1);
}
.wf-prize.is-on .wf-prize__card { transform: none; }
.wf-overlay--light .wf-prize__card { border-color: rgba(0, 0, 0, .08); box-shadow: 0 24px 60px rgba(3, 105, 161, .22); }

/* ── АКЦЕНТ КАРТОЧКИ: --wf-prize-a/-b/-ink, а НЕ пара заголовка (N26).
 *
 * Раньше иконка, сумма и кнопка красились парой --wf-title-a/-b. Это выглядело
 * экономно и было неверно по существу: у буквы заголовка есть кант 0.085em и
 * ореол 0.20em, читаемость держат ОНИ, а цвет заливки может быть каким угодно.
 * Здесь ничего этого нет — сумма лежит на карточке голой заливкой, текст кнопки
 * идёт прямо по заливке. Пороги тоже разные: 3.0 крупному тексту суммы, 4.5
 * тексту кнопки, 3.0 иконке как графическому объекту. Один цвет на три работы
 * с тремя порогами не подбирается, и на светлом скине это вылезло сразу тремя
 * провалами (2.60 / 2.95 / 2.60).
 *
 * Цвет ТЕКСТА кнопки здесь тоже не задаётся константой: прежний #2A1400 был
 * подобран под золотую заливку тёмных пресетов и на синей давал 2.95:1.
 * --wf-prize-ink выводится в JS по ОБОИМ концам заливки (autoInk). */
.wf-prize__icon {
    width: 54px;
    height: 54px;
    margin-bottom: 6px;
    color: var(--wf-prize-a, #FFE066);
    filter: drop-shadow(0 0 12px currentColor);
}
.wf-prize__eyebrow {
    font-size: 13px;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
    opacity: .72;
}
.wf-prize__amount {
    margin: 4px 0 2px;
    font-size: clamp(30px, 9vw, 46px);
    font-weight: 900;
    line-height: 1.05;
    background: linear-gradient(180deg, var(--wf-prize-a, #FFE066), var(--wf-prize-b, #FFA800));
    -webkit-background-clip: text;
            background-clip: text;
    color: transparent;
    text-shadow: 0 6px 18px var(--wf-title-shadow, rgba(0, 0, 0, .45));
}
.wf-prize__label { font-size: 16px; font-weight: 700; }
.wf-prize__note { margin-top: 6px; font-size: 13px; opacity: .72; line-height: 1.4; }

.wf-btn {
    width: 100%;
    margin-top: 18px;
    padding: 14px 18px;
    border: 0;
    border-radius: 14px;
    background: linear-gradient(180deg, var(--wf-prize-a, #FFE066), var(--wf-prize-b, #FFA800));
    color: var(--wf-prize-ink, #2A1400);
    font-family: var(--wf-font);
    font-size: 16px;
    font-weight: 900;
    letter-spacing: .04em;
    text-transform: uppercase;
    cursor: pointer;
    box-shadow: 0 8px 18px rgba(0, 0, 0, .3), inset 0 -3px 0 rgba(0, 0, 0, .18);
    transition: transform .14s ease, filter .14s ease;
    touch-action: manipulation;
}
.wf-btn:hover { filter: brightness(1.06); }
.wf-btn:active { transform: translateY(2px); box-shadow: 0 4px 10px rgba(0, 0, 0, .3); }
/* Кольцо кнопки лежит на карточке приза — подложка известна и одноцветна,
   спутник не нужен. Белая константа тут была слепа к теме: на белой карточке
   светлого скина кольцо давало ровно 1.00:1, то есть отсутствовало. */
.wf-btn:focus-visible { outline: 3px solid var(--wf-focus, #fff); outline-offset: 3px; }

/* ═════════════════════════════ адаптив ════════════════════════════════ */

/* Совсем узкие (320–359px): жмём заголовок, колесо не трогаем — именно оно
   и есть содержание экрана. Крестик НЕ уменьшаем: 44×44 — тач-минимум. */
@media (max-width: 359px) {
    /* Шапка ниже ровно на разницу кеглей заголовка (26 вместо 33.8). */
    .wf-root, .wf-overlay { --wf-chrome: 196px; }
    .wf-title { font-size: 26px; }
    .wf-star--l, .wf-star--r { display: none; }
    .wf-close svg { width: 20px; height: 20px; }
}

/* Ландшафт на телефоне: высоты нет вовсе. Шапка схлопывается до заголовка,
   колесо считается только от высоты и режется сильнее. */
@media (max-height: 520px) and (orientation: landscape) {
    .wf-root, .wf-overlay {
        /* 12 поля + 6 верх шапки + 21 заголовок + 37 подвал + 44 вкладки */
        --wf-chrome: 120px;
        --wf-crop: 0.96;
        --wf-size: min(58vw, calc(var(--wf-avail) / var(--wf-crop)), 360px);
    }
    /* Клиренс крестика в ландшафте не нужен: экран широкий, центрированный
       заголовок до правого угла не достаёт — а 38px тут стоят 10% высоты. */
    .wf-inner { padding-top: 6px; }
    .wf-title { font-size: 20px; margin: 0; }
    .wf-sub, .wf-logo { display: none; }
    .wf-foot { padding: 0 0 2px; }
    .wf-spins { font-size: 12px; }
    /* Карточка приза в ландшафте компактнее: 44px кнопка обязана остаться
       на экране даже при dvh 320 (Android с системной панелью). */
    .wf-prize__card { padding: 14px 16px 14px; border-radius: 18px; }
    .wf-prize__icon { width: 34px; height: 34px; margin-bottom: 2px; }
    .wf-prize__amount { font-size: 26px; }
    .wf-prize__note { display: none; }
    .wf-btn { margin-top: 10px; padding: 11px 16px; }
}

/* Планшет/десктоп: места хватает — колесо целиком.
   --wf-crop:1 означает size ≤ avail, а значит --wf-view всегда равна --wf-size:
   срезать нечего в принципе, поэтому здесь снимается и overflow. */
@media (min-width: 600px) and (min-height: 700px) {
    .wf-root, .wf-overlay {
        --wf-crop: 1;
        --wf-chrome: 200px;
        --wf-size: min(92vw, calc(var(--wf-avail) / var(--wf-crop)), 620px);
    }
    .wf-stage { align-items: center; overflow: visible; }
    /* Заголовок на широком экране до крестика не достаёт — клиренс не нужен,
       воздух сверху даёт композиция целиком. */
    .wf-inner { padding-top: 10px; }
    .wf-star--l { left: 6%; top: 18%; width: 34px; height: 34px; }
    .wf-star--r { right: 7%; top: 6%; width: 24px; height: 24px; }
}

/* Портретный планшет: высоты вагон — колесо крупнее. */
@media (min-width: 600px) and (min-height: 900px) {
    .wf-root, .wf-overlay { --wf-size: min(92vw, calc(var(--wf-avail) / var(--wf-crop)), 760px); }
}

/* ── Широкий экран: ОДНА колонка (прямое требование владельца).
   Двухколоночная раскладка «текст слева, колесо справа» дала бы колесо крупнее,
   но владелец сказал «в 2 колонки не красиво». Значит, единственный способ
   увеличить колесо — освободить высоту: на низких широких экранах шапка
   ужимается (мелкий заголовок, без подзаголовка), и весь остаток уходит колесу. */
@media (min-width: 1100px) and (max-height: 950px) {
    .wf-root, .wf-overlay {
        --wf-crop: 1;
        --wf-chrome: 164px;      /* без подзаголовка и с мелким заголовком */
        --wf-size: min(92vw, calc(var(--wf-avail) / var(--wf-crop)), 980px);
    }
    .wf-title { font-size: clamp(30px, 2.5vw, 46px); }
    .wf-sub { display: none; }
}

/* Высокий десктоп: шапка полноразмерная, колесо и так во весь рост. */
@media (min-width: 1100px) and (min-height: 951px) {
    .wf-root, .wf-overlay { --wf-size: min(92vw, calc(var(--wf-avail) / var(--wf-crop)), 1040px); }
}

/* ════════════════ КОМПАКТНОЕ ОКНО (гостевой режим, N29) ═══════════════
 *
 * ⚠️ БЛОК ОБЯЗАН СТОЯТЬ ПОСЛЕ ВСЕХ АДАПТИВНЫХ МЕДИА-ЗАПРОСОВ ВЫШЕ.
 * У `.wf-overlay--compact` тот же вес селектора, что у `.wf-overlay` в них
 * (один класс), поэтому спор решает порядок в файле: перенесёте выше — на
 * широком экране геометрию компактного окна перепишет полноэкранная.
 *
 * СКОЛЬКО ЭКРАНА ЗАНИМАЕТ ОКНО. Владелец сказал «не более 50%». Половина
 * ПЛОЩАДИ — это 70.7% по каждой стороне, то есть окно 1358×764 на 1920×1080:
 * страница видна вокруг узкой каймой, и от полного экрана это отличается на
 * глаз слабо. Берём 60% ширины × 82% высоты = 49.2% площади: потолок соблюдён
 * буквально, а окно наконец читается как окно. Стороны РАЗНЫЕ намеренно —
 * колесо круглое, а компоновка портретная (заголовок над кругом), поэтому
 * дефицитна именно высота, её и отдаём щедрее.
 * Сверх долей — абсолютные потолки 540×760: карточка не обязана расти вслед
 * за монитором. На 1920×1080 фактическая доля падает до 15.6%, и это верно:
 * промо-окно на большом экране должно оставаться промо-окном.
 *
 * ЧИТАЕМОСТЬ ПОДПИСЕЙ — то, чем этот режим мог сломаться. Кегль подписи
 * считается в единицах viewBox, а на экране он равен size_vb × кадр_px / 1088:
 * уменьшили кадр — уменьшили подпись, и никакой FONT_MIN этого не заметит,
 * потому что он про viewBox, а не про пиксели. Поэтому геометрия окна собрана
 * так, чтобы КАДР КОЛЕСА в компактном окне был не меньше, чем в полноэкранном
 * режиме НА ТЕЛЕФОНЕ (389…464px — размер, на котором подписи и принимались):
 * замер в Chrome даёт 388px на гейте 720×620 и 506px на десктопе, то есть
 * подпись 13.9…18.1 CSS-px против телефонных 14.0…16.7. Гейт подобран из
 * этого равенства, а не «на глаз».
 */
@media (min-width: 720px) and (min-height: 620px) {

    /* Переменные ставятся И на оверлей, И на .wf-root: у .wf-root есть
       собственное объявление тех же переменных, и без второго селектора
       колесо считало бы размер по полноэкранным правилам, а сцена — по
       компактным. Ровно так же устроены адаптивные блоки выше. */
    .wf-overlay--compact,
    .wf-overlay--compact .wf-root {
        --wf-win-w: min(60vw, 540px);
        --wf-win-h: min(82dvh, 760px);
        --wf-win-pad: 16px;
        /* Шапка окна: 14 сверху + заголовок 30×1.02 + 2 + подзаголовок
           13×1.35 + 10 + 16 снизу ≈ 90. Заложено 120: запас в 30px — это
           ровно вторая строка длинного названия кампании. Занижать нельзя:
           здесь занижение оплачивается не «дырой» под колесом, а сжатием
           сцены флексом — то есть колесо мельчает (см. ниже, резать в окне
           нечего). */
        --wf-chrome: 120px;
        --wf-avail: max(0px, calc(var(--wf-win-h) - var(--wf-chrome)));
        /* В окне не режем ВООБЩЕ: высота окна — наша, и при её нехватке
           уменьшается окно, а не круг. Отсюда --wf-view ≡ --wf-size. */
        --wf-crop: 1;
        --wf-size: min(calc(var(--wf-win-w) - var(--wf-win-pad) * 2), var(--wf-avail));
        --wf-view: var(--wf-size);
    }

    /* Подложка. Страница вокруг окна видна — в этом весь смысл режима, поэтому
       никакого backdrop-filter: размытие всей страницы стоит кадра на каждом
       скролле, а даёт ровно то же затемнение. Пятно света в центре собирает
       взгляд на окне, а не размазывает внимание по экрану. */
    .wf-overlay--compact {
        justify-content: center;
        padding: 16px;
        background: radial-gradient(120% 96% at 50% 50%, rgba(6, 8, 14, .52), rgba(3, 4, 9, .78));
    }
    .wf-overlay--compact.wf-overlay--light {
        background: radial-gradient(120% 96% at 50% 50%, rgba(8, 40, 62, .28), rgba(3, 28, 48, .54));
    }

    /* САМО ОКНО.
       ⚠️ Ни z-index, ни transform, ни filter, ни isolation, ни will-change,
       ни contain:paint — см. N29 в шапке файла: любое из них создаёт контекст
       наложения, и крестик (z-index 9) окажется ПОД карточкой приза (z-index 7),
       которая живёт в оверлее. Позиционированный элемент без z-index рисуется
       слоем ниже любого положительного, поэтому фон окна честно уходит под
       .wf-inner (z-index 2) и без всяких ухищрений. */
    .wf-overlay--compact .wf-window {
        position: relative;
        display: flex;
        flex: 0 1 auto;
        flex-direction: column;
        align-items: center;
        width: var(--wf-win-w);
        max-width: 100%;
        max-height: var(--wf-win-h);
        min-height: 0;
        overflow: hidden;              /* лучи и фон не вылезают из скруглений */
        border-radius: 26px;
        border: 1px solid rgba(255, 255, 255, .14);
        box-shadow:
            0 30px 80px rgba(0, 0, 0, .55),
            0 2px 0 rgba(255, 255, 255, .10) inset;
    }
    .wf-overlay--compact.wf-overlay--light .wf-window {
        border-color: rgba(3, 105, 161, .18);
        box-shadow: 0 30px 80px rgba(3, 105, 161, .26);
    }

    /* Лучи считаются от КОЛЕСА, а не от экрана, и держат не «ту же толщину»,
       а ТУ ЖЕ ЗАТУХАЮЩУЮ КАЙМУ. Маска гасит веер к 72% ближней стороны, то
       есть на радиусе 0.36×размера: чтобы веер уходил в ноль у края карточки
       (а не упирался в неё на полной яркости, как при экранных 190vmax),
       размер должен быть около двух диаметров колеса. Проверено снимками:
       при 3.2× карточка была расчерчена клиньями до самых углов, при 2.0×
       за колесом остаётся ореол — ровно то, чем лучи и работают на большом
       экране. Переопределяем НА САМОМ .wf-rays: переменная объявлена там же,
       а собственное объявление элемента сильнее унаследованного. */
    .wf-overlay--compact .wf-rays { --wf-rays-size: calc(var(--wf-size) * 2); }

    /* Крестик — в углу КАРТОЧКИ (окно позиционировано, поэтому пересчитывать
       ничего не нужно). safe-area здесь ни при чём: окно не касается краёв. */
    .wf-overlay--compact .wf-close { top: 8px; right: 8px; }

    .wf-overlay--compact .wf-inner {
        flex: 0 1 auto;
        max-width: none;
        padding: 14px var(--wf-win-pad) var(--wf-win-pad);
    }

    /* КОЛЕСО В ОКНЕ СЖИМАЕТСЯ, А НЕ РЕЖЕТСЯ.
       --wf-chrome — оценка шапки константой, и она обязана однажды не сойтись:
       название кампании оператор пишет сам, и «БОЛЬШОЕ КОЛЕСО УДАЧИ ZABKABET»
       встанет в три строки. В полноэкранном режиме ответ на нехватку высоты —
       срез низа круга (там за кадром экран, и срез читается как край). В окне
       срезать нечего: рамка рядом, и обрезанный круг в ней читается как
       поломка.
       Поэтому здесь колесо ведёт себя иначе: сторону задаёт ВЫСОТА сцены
       (height:100% + aspect-ratio), а ширину подтягивает пропорция. Флекс
       ужал сцену — колесо стало меньше и осталось ЦЕЛЫМ. Ширина при этом не
       превышает сцену никогда: высота сцены и так ≤ --wf-size, а --wf-size
       посчитан от ширины окна. Замер: короткий заголовок — срез 0 на всех
       экранах, трёхстрочный — колесо мельче, но круглое. */
    .wf-overlay--compact .wf-stage { align-items: center; overflow: hidden; }
    .wf-overlay--compact .wf-stage > .wf-root {
        width: auto;
        height: 100%;
        max-width: 100%;
        aspect-ratio: 1 / 1;
    }
    .wf-overlay--compact .wf-wheel { height: 100%; }

    /* Логотип кампании в окне мельче: 42px в карточке высотой ~600 — это
       строка заголовка, отданная картинке. */
    .wf-overlay--compact .wf-logo { max-width: 60%; max-height: 30px; margin: 0 auto 4px; }

    .wf-overlay--compact .wf-title {
        /* Кегль привязан к КАРТОЧКЕ, а не к экрану: 9.4vw полноэкранного
           режима дали бы на 1920 все 54px, то есть полторы строки в окне
           шириной 540.
           Наклон и потолок подобраны ЗАМЕРОМ, а не на глаз: прописная
           кириллица этого начертания идёт по 0.801 em на знак (Open Sans 900
           + разрядка .5px), то есть «КОЛЕСО ФОРТУНЫ» — это 11.2 кегля в
           ширину. Коридор заголовка = ширина карточки − поля 32 − клиренс
           крестика 108. Отсюда 3.5vw и потолок 30px: типовое название встаёт
           в ОДНУ строку на всём диапазоне режима (замер: 284px в коридоре
           292 у гейта, 337 в коридоре 400 на карточке 540). Название длиннее
           ~14 знаков на узкой карточке перенесётся — на это и заложен запас
           в --wf-chrome, поэтому колесо от переноса не мельчает. */
        font-size: clamp(20px, 3.5vw, 30px);
        margin: 0 0 2px;
        /* Клиренс крестика тут БОКОВОЙ, а не верхний: 54px = 8 отступ + 44
           кнопка + 2 воздуха. В полноэкранном режиме заголовок опускают ниже
           кнопки, но в окне высотой 600 полоса пустоты в 38px стоит слишком
           дорого — дешевле сузить строку. */
        padding: 0 54px;
    }
    /* N17: инсет слоя заливки обязан совпадать с padding самого заголовка. */
    .wf-overlay--compact .wf-title::before { inset: 0 54px; }

    /* На широком низком экране полноэкранный режим прячет подзаголовок ради
       высоты колеса. В окне высота своя, и подзаголовок — половина сообщения
       гостю («крути и забирай»), поэтому возвращаем его явно. */
    .wf-overlay--compact .wf-sub {
        display: block;
        margin: 0 0 10px;
        padding: 0 20px;
        font-size: 13px;
    }

    /* Искры — по диагонали от крестика: левый верхний и правый нижний углы
       карточки. Нижний угол — мёртвая зона между кругом и рамкой, так что
       искра не ложится на колесо. */
    .wf-overlay--compact .wf-star--l { left: 14px; top: 12px; width: 20px; height: 20px; }
    .wf-overlay--compact .wf-star--r { right: 16px; top: auto; bottom: 14px; width: 15px; height: 15px; }

    /* У гостя нет ни счётчика спинов, ни таймера, ни вкладок: контента меньше,
       и пустой подвал не имеет права занимать высоту — иначе под колесом
       появится ровно та пустота, за которую макет уже возвращали (N23).
       Отступ включается только тогда, когда в подвале что-то есть. */
    .wf-overlay--compact .wf-foot { padding: 0; }
    .wf-overlay--compact .wf-spins:not(:empty) { display: block; padding: 6px 0 2px; }

    /* Карточка приза остаётся на оверлее (то есть на весь экран) намеренно:
       это момент конверсии — «зарегистрируйтесь и крутите по-настоящему», и
       сужать его до окна незачем. Но подложка кладётся ВТОРЫМ слоем поверх
       подложки окна, поэтому она заметно легче штатной: два затемнения по .62
       давали почти чёрный экран. */
    .wf-overlay--compact .wf-prize { background: rgba(8, 2, 24, .44); }
    .wf-overlay--compact.wf-overlay--light .wf-prize { background: rgba(240, 249, 255, .62); }
}

/* ═══════════════════ уважение к prefers-reduced-motion ════════════════ */
/* Движение выключаем полностью; остаются только смены прозрачности,
   чтобы интерфейс не «телепортировался» без объяснения. */
@media (prefers-reduced-motion: reduce) {
    .wf-rays,
    .wf-lamp,
    .wf-star,
    .wf-hub.is-ready::after { animation: none !important; }
    .wf-hub.is-busy .wf-hub__ring { animation-duration: 2.4s; }
    .wf-prize__card { transition: none; transform: none; }
    .wf-close, .wf-hub, .wf-btn { transition: none; }
    .wf-flash.is-on { animation-duration: .4s; }
}

/* Печать модалку не показывает вовсе. */
@media print { .wf-overlay { display: none !important; } }
