/* Стили игры «Мяч на мяч» */

/* ШРИФТ ИГРЫ — Balsamiq Sans (SIL Open Font License 1.1, коммерческое
   использование разрешено). Рукописный характер под деревянный UI.
   Файлы лежат В АРХИВЕ и грузятся локально: Google Fonts как CDN
   недопустим — сторонний запрос может не пройти у части аудитории
   и задерживает первую отрисовку.
   Два веса — 400 и 700; курсивы не подключены сознательно (не нужны,
   а весят по ~230 КБ). Запросы на 900 браузер сам синтезирует из 700.
   font-display: swap — текст виден сразу системным шрифтом, иначе на
   медленном канале была бы пустая плашка счёта. */
@font-face {
  font-family: 'Balsamiq Sans';
  src: url('../assets/fonts/balsamiq-regular.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Balsamiq Sans';
  src: url('../assets/fonts/balsamiq-bold.woff') format('woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* Три роли оставлены раздельными: сейчас везде одно семейство,
   но через них можно вернуть второй шрифт для текстов одной правкой.
   Системный стек после кавычек — фолбек на случай, если woff не дошёл. */
:root {
  --font-ui: 'Balsamiq Sans', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  --font-text: 'Balsamiq Sans', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
  --font-display: 'Balsamiq Sans', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
}

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

html, body {
  width: 100%;
  height: 100%;
  /* п. 1.10.2 — нет браузерной прокрутки и swipe-to-refresh */
  overflow: hidden;
  overscroll-behavior: none;
  position: fixed;
}

body {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Фон всего окна — поп-арт «взрыв». По умолчанию — горизонтальная
     картинка (ПК/ландшафт); цвет — запасной вариант на время загрузки */
  background: #35c4b5 url('../assets/background-landscape.webp') center / cover no-repeat;
  font-family: var(--font-ui);
  font-weight: 500;
  /* п. 1.6.1.8 и 1.6.2.7 — лонгтап/клик не выделяет поле,
     не показывает меню-каллаут iOS и не подсвечивает тап на Android */
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}

/* п. 1.10.2 — тач-жесты гасим точечно на игровых элементах, а НЕ на body:
   touch-action предков пересекается вниз по дереву, и none на body
   заодно отключало прокрутку подэкранов меню пальцем (справка
   длиннее экрана). Сама страница не скроллится и без этого:
   html/body зафиксированы (position: fixed, overflow: hidden,
   overscroll-behavior: none), так что swipe-to-refresh невозможен. */
#stage,
#score-panel,
#game-wrapper,
#game-wrapper canvas,
#bonus-row,
#game-over,
#pause-overlay,
#bonus-overlay,
#gold-overlay,
#aim-dim {
  touch-action: none;
}

/* А вот прокручиваемым блокам меню вертикальный жест нужен (п. 1.10.3) */
#settings-overlay,
#settings-panel,
#settings-main,
#sub-body {
  touch-action: pan-y;
}

/* Вертикально вытянутый экран (телефон) — портретная картинка */
@media (orientation: portrait) {
  body { background-image: url('../assets/background-portrait.webp'); }
}

/* Сцена фиксированного логического размера, масштабируется
   под доступную область (п. 1.6.2.1, 1.10.1, 1.10.3) */
#stage {
  width: 478px;
  display: flex;
  flex-direction: column;
  align-items: center;
  transform-origin: center center;
}

#score-panel {
  position: relative;
  width: 478px;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  /* Вертикальные паддинги урезаны с 8 до 2 px по той же причине,
     что и margin-top у #game-wrapper: масштаб ограничен высотой сцены. */
  padding: 2px 0;
  gap: 8px;
  font-size: 22px;
  font-weight: bold;
}

/* Счёт и рекорд — нарисованные пилюли (assets/pill_score.webp,
   assets/pill_best.webp). Картинка растягивается целиком через
   background-size: 100% 100%. Раньше использовался border-image со
   срезом торцов 64 px: браузер масштабировал торцы и середину
   независимо, и на стыках были видны две тонкие вертикальные полосы.
   Цена такого решения — при очень длинном числе круглые торцы слегка
   растягиваются в овал; для 6–7 цифр это незаметно. */
/* --- Плашка «Далее» (2026-08-08, по рисунку автора) ---
   Стоит у ЛЕВОГО борта стакана целиком на ��иду: слева подпись
   по центру высоты, справа крупный мяч. Прежний ярлык #next-tab
   (next_tab.webp) убран совсем.
   Точка отсчёта — #score-panel (у него position: relative), а НЕ
   #score-stack: плашка больше не привязана к счёту и не зависит
   от его ширины.
   Картинка растянута целиком (background-size: 100% 100%), как у
   #score-pill: border-image на таких плашках даёт видимые швы.
   Высота 56 px — ровно как у #score-pill (требование автора),
   ширина 136.5 = 56 × 1180/484 — пропорция обрезанного исходника
   (2.438). Менять только пропорционально: при другом соотношении
   сторон круглые торцы капсулы вытянет в овал. */
#next-pill {
  position: absolute;
  /* 13px, а не 6px: #score-panel шире видимого стакана и выходит
     за его борта (замер: левый край панели − 5.4 экранных px).
     При left: 6px плашка вставала в 0 по экрану, то есть левее
     видимого края стакана (6.5). Не переносить сюда 6px от старого
     #next-tab: тот жил в другом контейнере. */
  left: 13px;
  /* Шаг 10: плашка больше НЕ равна по высоте плашке счёта —
     теперь она равна ЗНАЧКУ НАСТРОЕК (прямое требование автора,
     оно отменяет прежнее «толщина как у #score-pill»).
     Сравнивать надо ВИДИМЫЕ края, а не боксы: у icon_settings.webp
     есть своё прозрачное поле, по вертикали 3/128 сверху и снизу,
     то есть видимая высота значка = 44 × 122/128 = 41.9 px,
     а не 44. У pill_socket.webp прозрачных полей нет (замер по
     альфе: видимая доля 1.0 × 1.0), поэтому высота плашки в CSS
     и есть её видимая высота.
     top: значок стоит на 8px + прозрачное поле 44 × 3/128 = 1.03,
     его видимый центр = 8 + 1.03 + 41.9/2 = 30.0;
     отсюда top плашки = 30.0 − 41.9/2 = 9.05 — две плашки стоят
     на одной горизонтальной оси. Прежние 2px (padding #score-panel)
     оставили бы их вразнобой. */
  top: -2px;
  /* Шаг 9: капсула с КРУГЛЫМ ГНЕЗДОМ под мяч (вариант С1),
     подпись «Далее» заменена ЗНАЧКОМ (двойной шеврон, вариант З2),
     и под освободившееся место капсула укорочена.
     Ширина — пятая редакция: 136.5 → 105 → 92 → 106 → 82.
     Просто менять ширину НЕЛЬЗЯ: фон тянется 100% 100%,
     круглые торцы и ГНЕЗДО сжало бы в овал (а тогда мяч в лунку
     уже не сядет). Из полного ассета (136.9) вырезана полоса
     474 px из плоской середины и СТРОГО ЛЕВЕЕ лунки
     (/data/make_pill_socket.py, CSS_W_TARGET).
     pill_socket.webp = 246×168 (1.465 ≈ 82/56).
     Стык склеен не встык, а переходом в 8 столбцов: встык давал
     скачок 19 из 255 в любой из 12 позиций (заливка неоднородна
     и по горизонтали), а широкий переход двоит обводку (38).
     Итог: скачок 5 при естественном шум�� ка��тинки 15.
     Геометрия лунки в этом ассете (при высоте 56): центр 51.1 / 26.5,
     диаметр 33.8, свободное поле слева 34.2, поле справа 14.1.

     ШАГ 10: высота 56 → 41.9 (вровень со значком настроек).
     Ассет перерезать НЕ понадобилось: меняется вся плашка целиком,
     пропорция 1.465 сохранена, поэтому ширина обязана уменьшиться
     в том же отношении: 41.9 × 1.4649 = 61.4.
     Коэффициент пересчёта всей внутренней геометрии k = 41.9/56 = 0.7482;
     через него получены координаты значка и мяча ниже.
     Лунка после пересчёта: центр 38.2 / 19.8, диаметр 25.3,
     свободное поле слева 25.6 (центр 12.8). */
  /* ШАГ 11: возврат СТАРОЙ плашки pill_next.webp (требование автора).
     Она без ЛУНКИ и более прямоугольная, чем pill_socket.
     Пропорция файла другая: 281×171 = 1.6433 (было 246×168 = 1.4649),
     поэтому при той же высоте 41.9 ширина ОБЯЗАНА стать
     41.9 × 1.6433 = 68.9 (было 61.4). Фон тянется 100% 100%,
     та�� что любое отклонение от 1.6433 вытянет торцы в овал.

     Внутреннее поле обмерено численно (профиль яркости по
     центральной строке/столбцу, медиана заливки 227):
     x 8…266 из 281, y 13…146 из 171. Поля НЕСИММЕТРИЧНЫ —
     справа и снизу шире (нарисованная тень), поэтому содержимое
     центруется по ВНУТРЕННЕМУ ПОЛЮ, а НЕ по габариту:
     в CSS-координатах поле = x 2.0…65.2, y 3.2…35.8,
     его вертикальный центр 19.5 (а середина габарита — 20.95). */
  /* ШАГ 12: автор прислал НОВУЮ КВАДРАТНУЮ плашку с нарисованным
     ГНЕЗДОМ (assets/pill_square.webp). Она заменяет и pill_next,
     и pill_socket.
     Пропорция файла 845×940 (обрезка по видимым краям) = 0.8989,
     поэтому при высоте 41.9 ширина = 41.9 × 0.8989 = 37.7.
     Плашка стала УЖЕ втрое против шага 11 (68.9), потому что
     она почти квадратная. Менять ширину отдельно нельзя:
     фон тянется 100% 100%, скруглённые углы уйдут в эллипс.

     Обмер гнезда (профили яркости через центр, круглость 0.992 —
     обмеру можно доверять): гнездо 509 × 513 px в файле,
     центр 0.4994 ширины и 0.5564 высоты, диаметр 0.5415 высоты.
     Гнездо сдвинуто ВНИЗ от середины (0.5564, а не 0.5) —
     снизу у плашки более широкий бортик (73 против 36 сверху). */
  /* ШАГ 13 (требование автора): плашка УВЕЛИЧЕНА, и на ней
     снова написано «Далее». Высота 41.9 → 64.
     Причина именно такого роста: чистое поле над гнездом занимает
     0.2509 высоты ассета, то есть при 41.9 это всего 10.5 px — текст
     туда не влезал, и на шаге 12 надпись пришлось спрятать.
     При 64 поле = 16.1 px — надпись 10 px стоит свободно.
     Ширина НЕ ПРОИЗВОЛЬНАЯ: фон тянется 100% 100%, поэтому
     она жёстко = 64 × 0.8989 = 57.5, иначе скруглённые углы и круглое
     гнездо уйдут в эллипс и мяч в лунку не сядет.
     Побочный эффект, на этот раз желательный: мяч в гнезде
     вырос 22.7 → 34.5. Прежнее равенство высоты со значком настроек
     (шаг 10) больше НЕ действует — оно отменено этим требованием. */
  /* ШАГ 14 (требование автора): плашка ПОДНЯТА и выровнена
     ПО ЦЕНТРУ с плашкой счёта, а не по верхнему краю (шаг 13).
     top: 9.05 → -2. Число СЧИТАНО, а не подобрано:
     offsetParent плашки — #score-panel высотой 60 (56 + паддинги 2×2),
     его вертикальный центр = 30, и там же сидят центры
     #score-stack и значка настроек (замер: все три cy совпали).
     Отсюда top = 30 − 64/2 = -2.
     **ОТРИЦАТЕЛЬНЫЙ top — НЕ ОПЕЧАТКА:** плашка (64) выше
     своей панели (60), поэтому при центрировании она ОБЯЗАНА
     выступать на 2 px вверх и на 2 px вниз. У #score-panel нет overflow,
     так что ничего не обрезается. Не "чинить" это на 0.
     Замена на top: 50% + translateY(-50%) НЕ подойдёт: transform
     на #next-pill создаст новый containing block для вложенных
     #next-label / #next-balls, и их абсолютные координаты собьются. */
  width: 57.5px;
  height: 64px;
  background: url('../assets/pill_square.webp') center / 100% 100% no-repeat;
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, .18));
  pointer-events: none;
}

/* Шаг 9: вместо слова «ДАЛЕЕ» — ЗНАЧОК (двойной шеврон).
   Зачем: слово занимало 39.7 экранных px из 58 свободных, значок — 18,
   и капсулу удалось укоротить с 106 до 82.

   Рисуем МАСКОЙ, а не тегом <svg> в разметке, намеренно:
   1) текст «Далее / Next» остаётся в DOM и в I18N, его по-прежнему
      ставит applyLang — ни game.js, ни i18n.js править не пришлось;
   2) откат к слову — удаление одного правила CSS;
   3) внешний файл не нужен — одним запросом меньше.
   Сам текст скрыт через font-size: 0 (не display: none и не
   visibility: hidden — иначе ушла бы и маска вместе с элементом).
   Доступность держится на aria-label у #next-pill — его не убирать.

   Посадка: центр свободного ��о����я слева от лунки, по вертикали —
   ровно по центру капсулы (центр лунки практически совпадает
   с серединой по высоте).
   Шаг 10 (высота 41.9): поле слева сузилось 34.2 → 25.6,
   его центр 17.1 → 12.8, сам значок уменьшен 20 → 15
   в том же отношении k = 0.7482 — иначе он занял бы почти всё
   поле и упёрся бы в лунку. */
#next-label {
  position: absolute;
  /* ШАГ 13: вместо значка-шеврона (шаг 9) и полного сокрытия
     (шаг 12) на плашке снова СЛОВО «Далее» / «Next».
     Текст берётся из I18N (L.next) — его ставят applyLang() и
     renderNextTab(), поэтому правки в game.js / i18n.js не нужны.
     Сняты: display: none, font-size: 0, маска SVG и background-color
     (он был цветом маски, а не текста; оставь его — был бы
     коричневый прямоугольник под буквами).

     Посадка — по обмеру ассета, а не «на глаз»: центр чистого поля
     между верхом заливки (0.0375 × 64 = 2.4) и верхом гнезда
     (0.2866 × 64 = 18.3), то есть 10.4; по горизонтали — центр гнезда
     0.4979 × 57.5 = 28.6, чтобы слово стояло ровно над мячом.
     top задан ЧИСЛОМ, а не процентом: гнездо смещено вниз
     (0.5562, а не 0.5), и от середины габарита считать нельзя.
     Кегль 10 px подобран под ширину плашки: в RU «ДАЛЕЕ» и в EN
     «NEXT» слово обязано умещаться в видимое поле (≈ 51 px).
     Цвет и начертание — как у #score-label: плашки стоят рядом.
     Откат к значку-шеврону — в истории AGENTS.md (шаг 9). */
  left: 28.6px;
  top: 10.4px;
  transform: translate(-50%, -50%);
  font-family: var(--font-ui);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: .06em;
  text-transform: uppercase;
  white-space: nowrap;
  color: #9a5f22;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .5);
  pointer-events: none;
}

/* Шаг 8. Обёртка больше НЕ центрует пару через flex.
   Мяч должен встать ТОЧНО в гнездо, нарисованное на ассете,
   а его центр задан картинкой (шаг 9: 51.1 / 26.5), а не серединой плашки.
   Поэтому обёртка — просто система координат размером в плашку,
   а мяч позиционируется абсолютно.
   ВАЖНО: right/bottom сброшены в auto — без этого прежние left:0/right:0
   растянули бы блок и сбили посадку. */
#next-balls {
  position: absolute;
  left: 0;
  top: 0;
  right: auto;
  bottom: auto;
  /* Шаг 12: размер обёртки обязан совпадать с плашкой (37.7 × 41.9),
     это система координат для посадки мяча. */
  /* Шаг 13: размер обязан совпадать с плашкой (57.5 × 64). */
  width: 57.5px;
  height: 64px;
  display: block;
  pointer-events: none;
}

/* Мяч СИДИТ В ЛУНКЕ и РАВЕН ЕЙ ПО РАЗМЕРУ (требование автора).
   Диаметр лунки по обмеру ассета — 33.8 px, поэтому мяч 34 px
   (отношение 1.006). Было 28 px — мяч болтался в гнезде.
   Координаты — центр лунки минус половина диаметра.
   Шаг 10 (высота 41.9, ширина 61.4): вся геометрия ассета
   пересчитана через k = 41.9/56 = 0.7482, поэтому лунка
   стала 25.3, а её центр — 38.2 / 19.8;
   отсюда left = 38.2 − 12.65 = 25.6, top = 19.8 − 12.65 = 7.2.
   ПОБОЧНЫЙ ЭФФЕКТ, о котором надо помнить: мяч неизбежно
   уменьшился 34 → 25.3. Крупнее его сделать нельзя, не нарушив
   требование «мяч размером с лунку»: лунка нарисована на ассете
   и занимает 0.603 его высоты, так что при высоте 41.9
   она физически не может быть больше 25.3.
   История: шаг 9 (82 × 56) — 34 px в 34.1 / 9.5;
   шаг 8 (106 × 56) — 34 px в 58 / 9.5.
   Менять размер или координаты только вместе с переобмером ассета. */
#next-ball {
  position: absolute;
  /* ШАГ 12: на квадратной плашке СНОВА ЕСТЬ ГНЕЗДО,
     поэтому мяч опять привязан к нему, а не к высоте поля
     (правило «мяч размером с лунку» снова действует).
     Доли ассета (круглость обмера 0.992):
       диаметр = 0.5415 высоты → 41.9 × 0.5415 = 22.7;
       центр   = 0.4994 ширины и 0.5564 высоты → 18.8 / 23.3;
       отсюда left = 18.8 − 11.35 = 7.5, top = 23.3 − 11.35 = 12.0.
     ПОБОЧНЫЙ ЭФФЕКТ: мяч снова уменьшился, 30 → 22.7.
     Крупнее его не сделать, не выйдя за края гнезда: гнездо
     нарисовано на ассете и занимает 0.5415 высоты.
     Единственный способ увеличить мяч — поднять всю плашку
     выше 41.9, а это нарушит равенств�� со значком настроек. */
  /* Шаг 13 (высота 64, ширина 57.5), доли ассета перемерены
     заливкой от центра (гнездо 144×144, круглость 1.000):
       диаметр = 0.5393 высоты → 64 × 0.5393 = 34.5;
       центр   = 0.4979 ширины и 0.5562 высоты → 28.6 / 35.6;
       отсюда left = 28.6 − 17.25 = 11.4, top = 35.6 − 17.25 = 18.4. */
  /* 2026-08-11: у спрайтов мячей появилось по 3 % прозрачного поля,
     поэтому БОКС больше гнезда: 34.5 / 0.94 (CONFIG.SPRITE_FILL) = 36.7,
     а ВИДИМЫЙ кружок как и раньше 34.5 — ровно в размер гнезда.
     Координаты — центр гнезда минус половина бокса:
     left = 28.6 − 18.35 = 10.25, top = 35.6 − 18.35 = 17.25.
     Менять только вместе с CONFIG.SPRITE_FILL. */
  left: 10.25px;
  top: 17.25px;
  width: 36.7px;
  height: 36.7px;
  object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .25));
}

/* Второго мяча в варианте С1 НЕТ: гнездо на ассете одно,
   и второй мяч рядом с лункой читался бы как выпавший из неё.
   Сам элемент и логика nextLevel2 в game.js ОСТАВЛЕНЫ на месте:
   очередь из двух продолжает считаться, просто не показывается,
   чтобы вернуть её можно было одной строкой CSS. */
#next-ball-2 {
  display: none;
}

#score-stack {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}

#score-pill {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-width: 150px;
  height: 56px;
  padding: 0 10px;
  /* Одна картинка, растянутая на всю пла��ку. border-image масштабировал
     торцы и середину независимо, и на стыках слайсов (28 px от каждого
     края) были видны две тонкие вертикальные полосы. Здесь ресемпл��нг один. */
  background: url('../assets/pill_score.webp') center / 100% 100% no-repeat;
}

#score-label {
  font-family: var(--font-text);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  line-height: 1;
  color: #9a5f22;
}

#score {
  font-size: 26px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  color: #4a2200;
  text-shadow: 0 1px 0 rgba(255,255,255,.55);
}

/* --- Одна плашка на две грани: счёт и рекорд ---
   Отдельной плашки рекорда под счётом больше нет. #score-flip
   вращается вокруг горизонтальной оси: лицевая грань — #score-pill,
   обратная — #best-pill. Класс .flipped ставит game.js по простою
   и снимает при любом действии игрока (CONFIG.PILL_FLIP_MS). */
#score-stack { perspective: 700px; }

#score-flip {
  position: relative;
  transform-style: preserve-3d;
  transition: transform .55s cubic-bezier(.4, .05, .2, 1);
}

#score-flip.flipped { transform: rotateX(180deg); }

/* Без backface-visibility сквозь плашку видно зеркальный текст */
#score-pill,
#best-pill {
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

#best-pill {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  transform: rotateX(180deg);
  /* Та же картинка рекорда, что и раньше, одним куском без border-image */
  background: url('../assets/pill_best.webp') center / 100% 100% no-repeat;
}

/* Подпись и число на обратной грани — такие же, как у счёта */
#best-label {
  font-family: var(--font-text);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  line-height: 1;
  color: #9a5f22;
}

#best {
  font-size: 26px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
  color: #4a2200;
  text-shadow: 0 1px 0 rgba(255,255,255,.55);
}

/* Кнопка настроек — готовая иконка assets/icon_settings.webp,
   своего фона кнопка не имеет (кружок уже нарисован),
   от паркета отбивает drop-shadow — так же, как у кнопок бонусов */
#settings {
  position: absolute;
  /* ШАГ 15 (требование автора): шестерёнка стоит НА ТАКОЙ ЖЕ
     ПЛАШКЕ, как "Далее" — тот же ассет pill_square.webp,
     тот же размер 57.5 × 64 и тот же top: -2px, то есть обе плашки
     и плашка счёта сидят центрами на одной оси (шаг 14).
     Своёго фона у кнопки больше нет смысла держать пустым:
     фон — сама плашка, а шестерёнка садится В ГНЕЗДО (см. #settings img).

     right: 13px — ЗЕРКАЛО left: 13px у #next-pill, и теперь это честное
     зеркало: у pill_square.webp НЕТ прозрачных полей (видимая доля 1.0),
     поэтому видимый край совпадает с боксом. Края стакана 6.33 и 471.67:
     слева зазор 13 − 6.33 = 6.67, справа 471.67 − (478 − 13) = 6.67.
     Прежние 9.4px (шаг от 2026-08-06) были поправкой НА ПРОЗРАЧНОЕ
     ПОЛЕ САМОЙ ИКОНКИ (6/128 = 4.69 %); теперь с краем стакана
     сравнивается плашка, а не иконка, и поправка больше не нужна.
     Правило "считать по видимым краям спрайтов" ОСТАЁТСЯ в силе. */
  right: 13px;
  top: -2px;
  width: 57.5px;
  height: 64px;
  background: url('../assets/pill_square.webp') center / 100% 100% no-repeat;
  /* Та же тень, что у #next-pill — плашки обязаны отбиваться
     от паркета одинаково, иначе одна выглядит "наклеенной". */
  filter: drop-shadow(0 2px 2px rgba(0, 0, 0, .18));
  padding: 0;
  border: none;
  cursor: pointer;
  line-height: 0;
}

/* (ИСТОРИЯ, до шага 15 кнопка была голой иконкой 44 × 44
   без фона, top: 8px, right: 9.4px.)
  ТОЧКА ОТКАТА — старые правила целиком:
  #settings { position: absolute; right: 9.4px; top: 8px; width: 44px;
              height: 44px; padding: 0; border: none; background: none;
              cursor: pointer; line-height: 0 }
  #settings img { width: 100%; height: 100%;
                  filter: drop-shadow(0 2px 3px rgba(0,0,0,.45)) }

  Обоснование старого right: 9.4px было таким: зазор до края стакана
  зеркалит левую сторону:
     края стакана 6.33 и 471.67 (замер по альфе канваса), видимый левый
     край ярлыка "Далее" 11.48 -> зазор 5.15 px. У icon_settings.webp есть
     своё прозрачное поле 6/128 = 4.69 % (при 44 px это 2.06 px), поэтому
     ��окс кнопки заходит правее видимого края: 471.67 - 5.15 + 2.06 = 468.58,
     отсюда right = 478 - 468.58 = 9.4px. */

/* ШАГ 15: иконка больше НЕ равна кнопке (было 100 % / 100 %) —
   кнопка теперь плашка, а шестерёнка садится В ГНЕЗДО,
   ровно как мяч в плашке "Далее" (#next-ball).
   Доли гнезда у pill_square.webp: центр 0.4979 ширины и 0.5562 высоты,
   диаметр 0.5393 высоты → центр 28.6 / 35.6 и диаметр 34.5 при 57.5 × 64.

   НО БОКС ИКОНКИ БОЛЬШЕ 34.5, и это НЕ ошибка: у icon_settings.webp
   есть собственное прозрачное поле (альфа-bbox 6…122 из 128, то есть
   видимые 116/128 = 90.6 %). Чтобы ВИДИМЫЙ кружок был ровно 34.5
   (в размер гнезда, как у мяча), бокс = 34.5 × 128/116 = 38.1,
   а координаты считаются от его центра: 28.6 − 19.05 = 9.6
   и 35.6 − 19.05 = 16.6. Альфа-bbox симметричен (центр 64/64 из 128),
   поэтому дополнительного смещения не требуется.
   Поставишь 34.5 боксом — шестерёнка окажется мельче мяча на 9 %.

   Собственная тень иконки УБРАНА: раньше она отбивала иконку
   от паркета, а в гнезде она давала вторую тень поверх нарисованной.
   У мяча в гнезде тень слабая (0 1px 2px / .25) — такая же здесь. */
/* 2026-08-11 (требование автора): шестерёнка КРУПНЕЕ и стоит
   ПО ЦЕНТРУ ПЛАШКИ, а не в нарисованном гнезде.
   Центр плашки = 57.5/2 и 64/2 = 28.75 / 32 (раньше был центр гнезда
   28.6 / 35.6, то е��ть иконка сидела на 3.6 px ниже середины).
   Видимый диаметр 34.5 → 42 px. У icon_settings.webp своё прозрачное
   поле (альфа-bbox 6…122 из 128, видимые 116/128 = 90.6 %), поэтому
   бокс = 42 × 128/116 = 46.3, а координаты — от его центра:
   left = 28.75 − 23.15 = 5.6, top = 32 − 23.15 = 8.85.
   Правило «считать по видимым краям спрайтов» остаётся в силе. */
/* 12.08.2026 (требование автора): шестерёнка немного меньше — 46.3 → 42.5 px (−8 %).
   ЦЕНТР НЕ ДВИГАЕТСЯ: он задан гнездом плашки (28.75; 32.0),
   поэтому left/top пересчитаны от него, а не оставлены как были:
   left = 28.75 − 42.5/2 = 7.5, top = 32.0 − 42.5/2 = 10.75.
   Меняете размер снова — считайте по той же формуле, иначе иконка
   уедет из гнезда и разойдётся с мячом в плашке «Далее». */
#settings img {
  position: absolute;
  left: 7.5px;
  top: 10.75px;
  width: 42.5px;
  height: 42.5px;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .25));
  transition: transform .12s ease;
}

#settings:hover img { transform: scale(1.06); }
#settings:active img { transform: scale(0.96); }

/* ================================================================
   МЕНЮ НАСТРОЕК
   Вся графика — готовые PNG, CSS только растягивает их 9-slice.
   ��икаких цветовых подложек и border-radius вместо арта!
   slice посчитаны по замерам исходников (радиус угла + рамка):
   panel 85, title_plate 0 60, row_button 34, row_button_danger 40.
   Заменил картинку — пересчитай slice и border-width вместе с ней.
   Затемне��ие — без backdrop-filter: blur() (режет fps на Android).
   ================================================================ */
#settings-overlay {
  position: fixed;
  inset: 0;
  gap: 0;
  z-index: 60;
  background: rgba(0, 0, 0, .6);
  animation: set-fade-in .15s ease both;
}
#settings-overlay.closing { animation: set-fade-out .15s ease both; }

@keyframes set-fade-in { from { opacity: 0 } to { opacity: 1 } }
@keyframes set-fade-out { from { opacity: 1 } to { opacity: 0 } }

/* Панель: 64 % экрана, но не больше 360 px, высота по контенту.
   Рамка 30 px сохраняет рисунок и возвращает место содержимому
   на узких телефонах. panel.webp остаётся 9-slice со slice 85. */
#settings-panel {
  position: relative;
  box-sizing: border-box;
  width: 64%;
  min-width: 250px; /* на 360 px иначе строка тумблера переполняется */
  max-width: 360px;
  padding: 24px 10px 14px;
  border: 30px solid transparent;
  border-image: url('../assets/panel.webp') 85 fill / 30px stretch;
  color: #4a2c10;
  text-align: left;
  animation: set-pop-in .2s cubic-bezier(.3, 1.5, .6, 1) both;
}
#settings-overlay.closing #settings-panel { animation: set-pop-out .15s ease both; }

/* Появление с лёгким overshoot (его даёт cubic-bezier),
   закрытие — ровное, без перелёта */
@keyframes set-pop-in { from { transform: scale(.9) } to { transform: scale(1) } }
@keyframes set-pop-out { from { transform: scale(1) } to { transform: scale(.9) } }

/* Шестерёнка проворачивается на 90°, пока меню открыто */
#settings.open img { transform: rotate(90deg); }
#settings.open:hover img { transform: rotate(90deg) scale(1.06); }

/* Заголовок — нарисованная золотая плашка внахлёст на верхний
   край панели; тянется только по горизонтали, торцы не сплющиваются.
   Текст рису��т браузер */
#settings-title {
  position: absolute;
  /* Отсчёт идёт от padding-box панели, а он вдавлен на всю
     толщину рамки (30 px), поэтому для нахлёста в 27 px нужно 30 + 27 */
  left: 50%;
  top: -57px;
  transform: translateX(-50%);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 176px;
  max-width: 92%;
  height: 54px;
  /* Картинка рисуется целиком, без резки на 9 частей:
     раньше торцы сжимались вдвое, а середина тянулась, и на
     стыках вертикального градиента были видны полосы.
     Заголовки короткие, поэтому ровное масштабирование безопасно;
     для плашек счёта и строк-кнопок так делать НЕЛЬЗЯ. */
  padding: 0 18px;
  background: center / 100% 100% no-repeat url('../assets/title_plate.webp');
  font-family: var(--font-display);
  font-size: 19px;
  font-weight: 600;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: .5px;
  white-space: nowrap;
  color: #5a2f0c;
  filter: drop-shadow(0 3px 4px rgba(0, 0, 0, .3));
}

/* Крестик: диаметр как у кнопки настроек, внахлёст на угол */
#settings-close {
  position: absolute;
  /* так же, как у заголовка: 30 px рамки + 13 px нахлёста */
  right: -43px;
  top: -43px;
  width: 46px;
  height: 46px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  z-index: 2;
}
#settings-close img {
  width: 100%;
  height: 100%;
  display: block;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .35));
  transition: transform .12s ease;
}
#settings-close:hover img { transform: scale(1.08); }
#settings-close:active img { transform: scale(0.94); }

/* Строка тумблера: кликабельна целиком, минимум 56 px */
.set-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 56px;
  padding: 0 4px;
  border-radius: 14px;
  cursor: pointer;
  user-select: none;
}
.set-row:hover { background: rgba(90, 50, 15, .05); }
.set-label { flex: 1; font-size: 16px; font-weight: bold; color: #5a3413; }

/* Иконки строк — нарисованные PNG, а не эмодзи: эмодзи
   выглядят по-разному на Android, iOS и десктопе */
.set-ico {
  flex: none;
  width: 26px;
  height: 26px;
  background: center / contain no-repeat;
}
.ico-sound { background-image: url('../assets/icon_sound.webp'); }
.ico-music { background-image: url('../assets/icon_music.webp'); }
.ico-help { background-image: url('../assets/icon_help.webp'); }
.ico-records { background-image: url('../assets/icon_records.webp'); }
.ico-restart { background-image: url('../assets/icon_restart.webp'); }
.ico-mail { background-image: url('../assets/icon_mail.webp'); }

/* Шеврон справа; у кнопки «Назад» — тот же файл зеркально */
.chev {
  flex: none;
  width: 18px;
  height: 18px;
  opacity: .75;
  background: center / contain no-repeat url('../assets/icon_chevron.webp');
}
.chev.back { transform: scaleX(-1); }

/* Тумблер: паз и полз��нок — готовые картинки в двух состояниях.
   Укороченный экранный размер 54×22: по просьбе пользователя
   паз заметно короче исходного, поэтому растягивается не строго
   пропорционально. Ползунок 26×26 выступает на 2 px. Вся строка
   остаётся кликабельной высотой 56 px — тач-цель не уменьшается. */
.toggle {
  position: relative;
  flex: none;
  width: 54px;
  height: 22px;
  background: no-repeat url('../assets/toggle_track_off.webp');
  background-size: 100% 100%;
}
.toggle i {
  position: absolute;
  top: -2px;
  left: -2px;
  width: 26px;
  height: 26px;
  background: center / contain no-repeat url('../assets/toggle_knob_off.webp');
  transition: left .15s ease;
}
.set-row.on .toggle { background-image: url('../assets/toggle_track_on.webp'); }
.set-row.on .toggle i {
  left: 30px;
  background-image: url('../assets/toggle_knob_on.webp');
}

.set-sep {
  height: 1px;
  margin: 12px 6px;
  background: rgba(90, 50, 15, .18);
}

/* Строки-кнопки — нарисованная бежевая подложка, НЕ золотые пилюли:
   иначе меню превращается в ёлку. Акцентная только .danger */
.set-link {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 56px;
  margin: 8px 0 0;
  padding: 0 6px;
  border: 15px solid transparent;
  border-image: url('../assets/row_button.webp') 34 fill / 15px stretch;
  background: none;
  color: #5a3413;
  font-size: 16px;
  font-weight: bold;
  text-align: left;
  cursor: pointer;
  transition: filter .12s ease, transform .08s ease;
}
.set-text { flex: 1; }
.set-link:hover { filter: brightness(1.04); }
.set-link:active { transform: scale(.985); }

/* «Начать заново»: своя картинка и светлые иконки поверх неё;
   .confirm — состояние «ждём второй тап», класс вешает game.js */
.set-link.danger {
  margin-top: 14px;
  border-width: 16px;
  border-image: url('../assets/row_button_danger.webp') 40 fill / 16px stretch;
  color: #fff5ec;
}
.set-link.danger .set-ico,
.set-link.danger .chev { filter: brightness(0) invert(1); opacity: .9; }
.set-link.danger.confirm { filter: saturate(1.35) brightness(.9); }


/* Подэкраны живут в той же панели: класс .sub меняет страницу */
#settings-sub { display: none; }
#settings-panel.sub #settings-main { display: none; }
#settings-panel.sub #settings-sub { display: block; }

/* 12.08.2026 (требование автора): на ПОДЭКРАНАХ крестика нет.
   Выход там безвариантный — кнопка «Назад» внизу, а два разных
   элемента закрытия рядом заставляют гадать, куда ведёт каждый:
   крестик закрывал всё меню целиком, а «Назад» возвращал на список.
   В главном меню крестик ОСТАЁТСЯ: там он единственный выход.
   visibility вместо display: none — чтобы при возврате кнопка не пересобиралась
   и не мигала; с клавиатуры и для скринридеров она тоже пропадает. */
#settings-panel.sub #settings-close {
  visibility: hidden;
  pointer-events: none;
}
#sub-body {
  font-family: var(--font-text);
  font-size: 16px;
  line-height: 1.5;
  color: #5a3413;
}

/* 12.08.2026: ПАНЕЛЬ НЕ ДОЛЖНА ВЫЛЕЗАТЬ ЗА ЭКРАН.
   Высота панели идёт по контенту, и длинный текст «Как играть» (9 абзацев)
   уводил низ панели вместе с кнопкой «Назад» под нижний край экрана
   (на 390 × 664 низ был на 1128 px), а прокрутки ни у кого не было.
   Прокручивается ИМЕННО СОДЕРЖИМОЕ, а НЕ сама панель: у панели
   заголовок (top: -57px) и крестик (-43px) торчат ЗА её границами
   и любой overflow на #settings-panel их бы обрезал.
   Кнопка «Назад» лежит ВНЕ #sub-body — поэтому она всегда на экране,
   а не в конце прокрутки: иначе единственный выход с подэкрана
   (крестика там больше нет) пришлось бы ещё и искать.
   Вычитаем 250px: заголовок 57 + рамки 60 + padding 38 + «Назад» ~62 + поля.
   dvh вторым правилом — из-за схлопывающейся адресной строки на мобильных;
   старые браузеры просто останутся на vh. */
#settings-main,
#sub-body {
  max-height: calc(100vh - 220px);
  max-height: calc(100dvh - 220px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* 13.08.2026: НА ПОДЭКРАНАХ ПАНЕЛЬ ШИРЕ.
   Замер показал корень проблемы с длиной справки: при width: 64 %
   на экране 390 px панель = 250 px, из них рамка берёт 60 и padding 20 —
   тексту оставалось ВСЕГО 170 px, то есть ~22 символа в строке.
   Каждый пункт расползался на 3–5 строк (814 px на 7 пунктов), и никакое
   сокращение формулировок это не лечит — лечит только ширина.
   Шире делаем ТОЛЬКО подэкраны (селектор .sub): главное меню с тумблерами
   остаётся ровно таким, каким его утвердили — там текста нет и ширина не мешает.
   Переход плавный, иначе панель дёргается при входе в раздел. */
#settings-panel {
  transition: width .16s ease;
}
#settings-panel.sub {
  width: 92%;
}

/* Текст справки чуть мельче и плотнее — вместе с шириной это убирает
   прокрутку совсем: раздел должен читаться с одного взгляда. */
#sub-body {
  font-size: 15px;
  line-height: 1.42;
}
#sub-body p { margin: 0 0 8px; }

/* Скроллбар тонкий и в тон дереву: системный серый брусок
   посреди рисованной панели выглядит чужеродно. */
#settings-main,
#sub-body {
  scrollbar-width: thin;
  scrollbar-color: rgba(106, 53, 29, .35) transparent;
}
#settings-main::-webkit-scrollbar,
#sub-body::-webkit-scrollbar { width: 6px; }
#settings-main::-webkit-scrollbar-thumb,
#sub-body::-webkit-scrollbar-thumb {
  border-radius: 3px;
  background: rgba(106, 53, 29, .35);
}
#sub-body p { margin: 0 0 10px; }

/* Таблица лидеров на подэкране «Рекорды» (11.08.2026).
   Три колонки с фиксированными краями: место и счёт моноширинные
   (tabular-nums), имя тянется и обрезается многоточием: имена
   у Яндекса бывают очень длинными и ломают строку. */
.lb-head {
  margin: 2px 0 6px;
  font-size: 13px;
  font-weight: 700;
  opacity: .75;
}
.lb {
  margin: 0 0 10px;
  padding: 0;
  list-style: none;
}
.lb-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px;
  border-radius: 9px;
  font-size: 14px;
}
.lb-row:nth-child(odd) { background: rgba(106, 53, 29, .06); }
/* Своя строка — золотая: в длинном списке игрок ищет именно её */
.lb-row.me {
  background: #ffe9a8;
  font-weight: 700;
}
.lb-rank {
  min-width: 22px;
  font-variant-numeric: tabular-nums;
  opacity: .7;
}
.lb-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}
.lb-score {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}
.lb-note {
  font-size: 13px;
  opacity: .8;
}
#sub-body .big {
  font-family: var(--font-ui);
  font-size: 30px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
#sub-back { margin: 14px 0 0; }


#game-wrapper {
  position: relative;
  /* Отступ от панели счёта. Держать минимальным: масштаб сцены
     на телефонах упирается именно в высоту, и каждый пустой px
     здесь отнимается от размера стакана. Своё пустое место сверху
     у канваса уже есть: дерево начинается с y≈63, там же летит мяч. */
  margin-top: 6px;
  /* Размер канваса = весь нарисованный стакан (CONFIG.WIDTH/HEIGHT) */
  width: 478px;
  height: 632px;
  /* Бел��го кольца и тени больше нет: рамку рисует сам стакан
     (assets/cup.webp поверх мячей, game.js), а прямоугольная обводка
     торчала бы вокруг U-образного силуэта */
  /* Поле остаётся над общим затенением #aim-dim (своё затенение
     поля рисуется в канвасе, game.js afterRender) */
  z-index: 5;
}

/* Интерьер стакана. Геометрия повторяет внутренний контур
   рисунка (CUP в js/config.js: стенки 33/446, дно 544, радиусы 119×106).
   ��ежит под канвасом, поэтому мячи и рамка рисуются поверх.

   Заливка — ТЁПЛОЕ затемнение (2026-08-03): вместо текстуры
   досок — коричневый градиент плотностью 74% у горловины
   и 94% у дна. Сначала был чёрный градиент 34–68%, но чёрный
   гасит яркость, не меняя тона, и бирюзовый фон давал грязно-
   зелёный оттенок. Светлее ~0.85 зелень возвращается — это
   нижняя граница для плотности. Геометрия, внутренние тени
   и маска верхнего края не менялись: без них стакан плоский.
   Прежний вариант — швы досок каждые 43 px + коричневый
   градиент #6d4b28 → #48301a */
#game-wrapper::before {
  content: '';
  position: absolute;
  left: 33px;
  right: 32px; /* 478 − 446 */
  top: 0;
  bottom: 88px; /* 632 − 544 */
  border-radius: 0 0 119px 119px / 0 0 106px 106px;
  background:
    /* СЛОЙ 1 — ДИЗЕР. Градиент ниже пологий и длинный (~580 px
       на всего ~20 уровней яркости), и в 8 битах он распадается
       на горизонтальные ступени шириной до 24 px (banding).
       Браузер градиенты не дизерит, поэтому шум подмешиваем
       сами: он размывает границы ступеней. Генерится в CSS
       (SVG feTurbulence), отдельный файл в архив не нужен */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='120' height='120' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E"),
    /* СЛОЙ 2 — ЗАТЕМНЕНИЕ вместо текстуры досок. Затемнитель
       ТЁПЛЫЙ, а не чёрный: нейтральный чёрный гасит яркость,
       но не меняет тон, и бирюзовый фон снизу давал грязно-зелёный
       оттенок. Плотность 74–94% — чистый коричневый начинается
       при��ерно с 0.85, ниже этого фон снова тянет цвет в зелень */
    linear-gradient(to bottom,
      rgba(70, 38, 14, .74) 0,
      rgba(54, 29, 11, .86) 55%,
      rgba(40, 21, 8, .94) 100%);
  /* Шум работает только в��есте с overlay: без режима смешивания
     он просто ляжет серой плёнкой поверх цвета */
  background-blend-mode: overlay, normal;
  /* Объём: внутренняя тень от стенок и дна — мячи читаются
     лежащими ВНУТРИ ёмкости, а не на плоской текстуре */
  box-shadow:
    inset 16px 0 26px -10px rgba(26, 13, 3, .8),
    inset -16px 0 26px -10px rgba(26, 13, 3, .8),
    inset 0 -20px 30px -12px rgba(26, 13, 3, .75),
    inset 0 30px 34px -20px rgba(0, 0, 0, .55);
  /* Мягкое р��створение у открытого края вместо жёсткого среза:
     ручки стакана начинаются с y≈63, и прямоугольная кромка
     торчала бы над рамкой. Зона броска (DROP_Y) — открытый воздух.
     Маска гасит и тень, и заливку одновременно */
  -webkit-mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0) 0, #000 104px, #000 100%);
  mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0) 0, #000 104px, #000 100%);
}

canvas { display: block; position: relative; z-index: 1; cursor: crosshair; }

/* ===== Заставка загрузки (п. 1.19.2) =====
   Перехватывает ВСЕ клики, тапы и наведения до вызова LoadingAPI.ready():
   игра не должна отвечать на действия игрока раньше сигнала Game Ready.
   z-index выше любых игровых оверлеев (у .overlay — 10). */
#boot-gate {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Тот же запасной цвет, что у body: заставка не мигает другим
     оттенком, пока грузится картинка фона окна */
  background: #35c4b5;
  touch-action: none;
  cursor: default;
}
/* Класс ставит game.js сразу после YSDK.loadingReady() */
body.booted #boot-gate { display: none; }

#boot-spinner {
  width: 46px;
  height: 46px;
  border: 4px solid rgba(255,240,214,.3);
  border-top-color: #ffcc33;
  border-radius: 50%;
  animation: boot-spin .9s linear infinite;
}
@keyframes boot-spin { to { transform: rotate(360deg); } }
/* Уважаем системную настройку «меньше движения» */
@media (prefers-reduced-motion: reduce) {
  #boot-spinner { animation-duration: 2.4s; }
}

.overlay {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,.72);
  color: #fff;
  text-align: center;
  gap: 14px;
  z-index: 10;
}

.overlay h2 {
  font-family: var(--font-display);
  font-size: 32px;
  font-weight: 600;
  line-height: 1.15;
}

.btn {
  padding: 10px 28px;
  font-size: 18px;
  font-weight: bold;
  border: none;
  border-radius: 24px;
  background: #ffcc33;
  color: #5a3800;
  cursor: pointer;
}
.btn:hover { background: #ffd966; }

/* ================== ЭКРАН ПРОИГРЫША (11.08.2026) ==================
   Было: чёрная плёнка rgba(0,0,0,.72) на весь прямоугольник #game-wrapper
   (478×632) — она накрывала и прозрачные поля вокруг нарисованного
   стакана, то есть темнила фон и саму деревянную рамку.
   Стало: затеняется ТОЛЬКО игровое поле — тот же внутренний контур,
   что у #game-wrapper::before (CUP в js/config.js: стенки 33/446, дно 544,
   радиусы 119×106), с той же маской у горловины. Геометрию править
   СРАЗУ В ДВУХ местах, иначе затенение разъедется с интерьером стакана.
   Цвет тёплый коричневый, а не чёрный: нейтральный чёрный гасит яркость,
   но не меняет тон, и поверх тёплого интерьера давал грязный серый налết.
   ================================================================ */
#game-over {
  /* Своей заливки у оверлея нет — её рисует ::before по контуру поля */
  background: none;
  /* Клики мимо карточки всё равно не должны уходить в канвас:
     иначе после проигрыша можно было бы «бросить» мяч сквозь плашку */
  gap: 0;
}
#game-over::before {
  content: '';
  position: absolute; /* абсолютный — значит не участвует в flex-раскладке оверлея */
  left: 33px;
  right: 32px;  /* 478 − 446 */
  top: 0;
  bottom: 88px; /* 632 − 544 */
  border-radius: 0 0 119px 119px / 0 0 106px 106px;
  background: linear-gradient(to bottom,
    rgba(58, 31, 12, .82) 0,
    rgba(48, 25, 9, .88) 55%,
    rgba(36, 18, 6, .92) 100%);
  /* Та же мягкая кромка у гор��овины, что и у интерьера стакана:
     без неё затенение срезалось бы прямой линией поверх ручек */
  -webkit-mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0) 0, #000 104px, #000 100%);
  mask-image: linear-gradient(to bottom,
    rgba(0, 0, 0, 0) 0, #000 104px, #000 100%);
}

/* Карточка с итогами. Собирает заголовок, счёт, действия и предложение
   войти в один блок с общим ритмом отступов. Фон едва заметный —
   плашка внутри тёмного стакана не должна читаться как второе окно. */
#over-card {
  position: relative; /* поверх ::before */
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: 84%;
  max-width: 320px;
  padding: 20px 18px 22px;
  border-radius: 24px;
  background: rgba(28, 14, 5, .42);
  border: 1px solid rgba(255, 226, 176, .14);
  box-shadow: 0 12px 30px rgba(0, 0, 0, .34);
}
#over-card h2 {
  margin: 0;
  font-size: 27px;
  color: #ffe6b4;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .35);
}

/* Счёт: подпись мелким разряженным капсом, число — крупно и золотом.
   Главное на экране — результат, а не слово «счёт». */
#final-line {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 22px 10px;
  border-radius: 16px;
  background: rgba(0, 0, 0, .22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
}
#final-label {
  font-size: 12px;
  letter-spacing: .09em;
  text-transform: uppercase;
  opacity: .72;
}
#final-score {
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 1.05;
  color: #ffcf40;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 0 rgba(0, 0, 0, .38);
}

/* Действия одной колонкой во всю ширину карточки: две кнопки
   разной ширины по центру и были главной причиной ощущения «навалом». */
#over-actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  width: 100%;
}
#over-actions .btn { width: 100%; }

/* Кнопка спасения за рекламу на экране проигрыша.
   Зелёная, а не жёлтая как «Заново»: две одинаковые кнопки рядом
   читаются как одна и та же, а выбор здесь разный: продолжить
   партию или начать с нуля. display ставит game.js (по умолчанию скрыта) */
#rescue {
  display: none;
  align-items: center;
  gap: 10px;
  padding: 10px 22px;
  background: #7ac943;
  color: #17400a;
}
#rescue:hover { background: #8fdc55; }
#rescue img {
  width: 26px;
  height: 26px;
  /* Пиктограмма бонуса светлая — на зелёном ей нужна тень */
  filter: drop-shadow(0 1px 1px rgba(0,0,0,.35));
}

/* Пояснение под кнопкой: что именно уберётся и что счёт цел.
   На узких экранах текст длинный, поэтому ограничиваем ширину */
/* Мягкое предложение войти на экране проигрыша. Стоит после
   «Заново» и оформлено тише кнопок: это подсказка, а не требование.
   display ставит game.js */
#auth-prompt {
  display: none;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin-top: 2px;
  padding-top: 12px;
  border-top: 1px solid rgba(255, 226, 176, .16);
}
#auth-note {
  margin: 0;
  font-size: 13px;
  line-height: 1.3;
  opacity: .85;
}
#auth-go {
  padding: 7px 22px;
  font-size: 15px;
  background: #ffe9a8;
}
#auth-go:hover { background: #fff3c9; }

/* Сноска под кнопкой спасения: что именно уберётся и что счёт цел.
   Прижата к своей кнопке (margin-top отрицательный против gap группы),
   чтобы читалась как пояснение к ней, а не как отдельная строка. */
#rescue-note {
  display: none;
  max-width: 100%;
  margin: -3px 0 2px;
  font-size: 12.5px;
  line-height: 1.35;
  opacity: .8;
}


/* Тестовые бонусы — доступны и без SDK, в��дны всегда.
   Кнопки-иконки (assets/bonus_*.webp) с бейджем-счётчиком в углу */
#bonus-row {
  /* Отрицательный отступ: канвас заканчивается не на дне стакана —
     на рисунке под деревом ещё ~63 px пустоты (y 1114…1216 × 0.6215).
     Поднимаем бонусы в э��у пустоту, оставляя ~17 px воздуха под дном.
     Кнопки остаются кликабельны: ряд идёт после #game-wrapper в DOM
     при том же z-index, а перекрываемая часть канваса пустая. */
  margin-top: -46px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 20px;
  width: 100%;
}
#shake, #clear-small, #pop-ball {
  position: relative;
  width: 76px;
  height: 76px;
  padding: 4px;
  border: 3px solid #6a351d;
  border-radius: 50%;
  background: linear-gradient(145deg, #fff8df 0%, #f3dfb7 72%, #dfba7a 100%);
  box-shadow: 0 3px 5px rgba(0, 0, 0, .38), inset 0 2px 0 rgba(255,255,255,.7);
  cursor: pointer;
}
#shake img, #clear-small img, #pop-ball img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
#shake:hover, #clear-small:hover, #pop-ball:hover { transform: scale(1.06); }
/* Кно��ки с нулёвым запасом НЕ затеняются и не блокируются:
   по клику по ним будет реклама за вознаграждение.
   Не возвращать правило :disabled без прямого запроса. */
.bonus-count {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 14px;
  padding: 2px 5px;
  border-radius: 11px;
  background: #ffcf40;
  color: #5b2d00;
  font-size: 12px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.4;
  box-shadow: 0 1px 3px rgba(0,0,0,.45);
  pointer-events: none;
}
/* Отклик бейджа в момент, когда до него долетела иконка бонуса */
.bonus-count.bump { animation: badge-bump .32s ease; }
@keyframes badge-bump {
  0%   { transform: scale(1); }
  45%  { transform: scale(1.5); }
  100% { transform: scale(1); }
}

/* Иконка бонуса, летящая от места слияния на свою кнопку.
   position: fixed — чтобы transform: scale() у #stage не сбивал
   координаты: летим в экранных px.
   Размер и длительность выставляет flyBonusIcon(): ширина
   равна реальному размеру кнопки-цели, тайминги берутся из
   CONFIG.FLY_BONUS_*. Значения ни��е — только запасные. */
.fly-bonus {
  position: fixed;
  left: 0;
  top: 0;
  width: 76px;
  height: 76px;
  transform: translate(-50%, -50%);
  transition: transform .62s cubic-bezier(.4, .05, .35, 1), opacity .18s ease-out;
  filter: drop-shadow(0 4px 7px rgba(0, 0, 0, .5));
  pointer-events: none;
  z-index: 70;
}

/* Множитель комбо (×2, ×3…), летящий от последнего
   слияния на плашку счёта. Как и .fly-bonus — position: fixed,
   чтобы transform: scale() у #stage не сбивал координаты.
   Центро��ка живёт в transform из flyCombo(), потому что
   ширина зависит от числа знаков. Тайминги — CONFIG.FLY_COMBO_*,
   значения ниже только запасные. */
.fly-combo {
  position: fixed;
  left: 0;
  top: 0;
  font-size: 54px;
  font-weight: 800;
  line-height: 1;
  white-space: nowrap;
  color: #ffd75e;
  /* Обводка поверх заливки — paint-order держит тонкие
     штрихи цифр читаемыми на пёстром поле */
  -webkit-text-stroke: 3px #71360f;
  paint-order: stroke fill;
  text-shadow: 0 3px 6px rgba(0, 0, 0, .45);
  transition: transform .64s cubic-bezier(.4, .05, .35, 1), opacity .2s ease-out;
  pointer-events: none;
  z-index: 70;
}

/* Отклик счёта, когда до него долетел множитель.
   Специально та же анимация, что у бейджей бонусов:
   второй язык для «прилетело и засчиталось» не заводим.
   Анимируется #score, а не #score-pill: у плашки свой
   transform под переворот ��раней, и они бы перебили друг друга. */
#score.bump {
  display: inline-block;
  animation: badge-bump .32s ease;
}

/* ============================================================
   ПЛАШКА «БОНУС»
   Показывается один раз на каждый вид бонуса. Нарочно
   повторяет визуал меню настроек: та же рамка panel.webp,
   та же золотая табличка заголовка и те же анимации.
   Второй визуальный язы�� для модалок не заводить.
   ============================================================ */
#bonus-overlay {
  position: fixed;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, .6);
  z-index: 60;
  animation: set-fade-in .18s ease both;
}
#bonus-overlay.closing { animation: set-fade-out .15s ease both; }
#bonus-panel {
  position: relative;
  width: 64%;
  min-width: 250px;
  max-width: 360px;
  padding: 26px 10px 14px;
  border: 30px solid transparent;
  border-image: url('../assets/panel.webp') 85 fill / 30px stretch;
  text-align: center;
  animation: set-pop-in .2s cubic-bezier(.3, 1.5, .6, 1) both;
}
#bonus-overlay.closing #bonus-panel { animation: set-pop-out .15s ease both; }

/* ---- Плашка золотой коллекции ----
   Повторяет геометрию плашки бонуса (та же панель 9-slice
   и та же золотая шапка), но лежит выше по z-index:
   её ничто не должно перекрывать. */
#gold-overlay {
  position: fixed;
  inset: 0;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, .6);
  z-index: 70;
  animation: set-fade-in .18s ease both;
}
#gold-overlay.closing { animation: set-fade-out .15s ease both; }
#gold-panel {
  position: relative;
  width: 64%;
  min-width: 250px;
  max-width: 360px;
  padding: 26px 12px 16px;
  border: 30px solid transparent;
  border-image: url('../assets/panel.webp') 85 fill / 30px stretch;
  text-align: center;
  animation: set-pop-in .2s cubic-bezier(.3, 1.5, .6, 1) both;
}
#gold-overlay.closing #gold-panel { animation: set-pop-out .15s ease both; }
#gold-title {
  position: absolute;
  left: 50%;
  top: -57px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 74%;
  height: 54px;
  padding: 0 22px 4px;
  background: url('../assets/title_plate.webp') center / 100% 100% no-repeat;
  font-family: var(--font-display);
  font-size: 22px;
  color: #7a3c0d;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .5);
  white-space: nowrap;
}
/* Сияние за мячом — гр��диент, а не картинка: лишний файл
   ради блика не нужен. Пульсация медленная, чтобы не рябить. */
#gold-shine {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 6px auto 2px;
  width: 132px;
  height: 132px;
  background: radial-gradient(circle, rgba(255, 228, 140, .95) 0%, rgba(255, 205, 70, .45) 45%, rgba(255, 205, 70, 0) 70%);
  animation: gold-pulse 1.8s ease-in-out infinite;
}
/* Бокс учитывает 3 % прозрачные поля спрайта (2026-08-11):
   104 / 0.94 = 110.6, видимый мяч о��тался 104 px */
#gold-ball {
  width: 110.6px;
  height: 110.6px;
  object-fit: contain;
  filter: drop-shadow(0 6px 10px rgba(120, 70, 0, .35));
}
#gold-text {
  margin: 8px 6px 14px;
  font-size: 15px;
  line-height: 1.55;
  color: #5c3a1a;
}
#gold-ok {
  padding: 9px 30px 11px;
  border: none;
  border-radius: 22px;
  background: linear-gradient(#ffd75e, #f5a623);
  box-shadow: 0 3px 0 #c8791a;
  font-family: var(--font-display);
  font-size: 19px;
  color: #6b3a05;
  cursor: pointer;
}
#gold-ok:active { transform: translateY(2px); box-shadow: 0 1px 0 #c8791a; }

@keyframes gold-pulse {
  0%, 100% { transform: scale(1); opacity: .9; }
  50%      { transform: scale(1.08); opacity: 1; }
}

/* ---- Конфетти ----
   Простые <i>-бумажки: канвас ради трёх секунд анимации
   держать незачем, а transform/opacity браузер считает на GPU.
   Координаты, цвет и задержку ставит game.js. */
#confetti {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
#confetti i {
  position: absolute;
  top: -24px;
  width: 9px;
  height: 14px;
  border-radius: 2px;
  opacity: 0;
  animation: confetti-fall linear forwards;
}

@keyframes confetti-fall {
  0%   { opacity: 0; transform: translateY(0) rotate(0deg); }
  8%   { opacity: 1; }
  100% { opacity: 1; transform: translateY(105vh) rotate(720deg); }
}
#bonus-title {
  position: absolute;
  left: 50%;
  top: -57px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 74%;
  height: 54px;
  padding: 0 22px 4px;
  /* Одна картинка, растянутая на всю плашку — как у #score-panel.
     border-image со слайсом 46 px в рамке 16 px сжимал торцы в 2.9 раза,
     а середину растягивал: ресемплинг разный, и на двух стыка�� были
     видны тонкие вертикальные полосы. Здесь ресемплинг один. */
  background: url('../assets/title_plate.webp') center / 100% 100% no-repeat;
  font-family: var(--font-display);
  font-size: 22px;
  color: #7a3c0d;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .5);
  white-space: nowrap;
}
#bonus-icon {
  display: block;
  width: 88px;
  height: 88px;
  margin: 4px 0 6px;
  object-fit: contain;
}

/* Строка награды: иконка бонуса и крупное "×N" рядом.
   Множитель показывается только в рекламном режиме (класс with-mult) */
#bonus-reward {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
}
#bonus-mult {
  display: none;
  font-family: var(--font-display);
  font-size: 46px;
  font-weight: 800;
  line-height: 1;
  color: #ffcf40;
  -webkit-text-stroke: 3px #7a3c0d;
  paint-order: stroke fill;
  text-shadow: 0 3px 0 rgba(122, 60, 13, .3);
}
#bonus-reward.with-mult #bonus-mult { display: block; }

/* Крестик плашки бонуса — тот же вид и та же посадка,
   что у меню настроек: второй визуальный язык для модалок не заводим */
#bonus-close {
  position: absolute;
  right: -43px;
  top: -43px;
  width: 46px;
  height: 46px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  z-index: 2;
}
#bonus-close img {
  width: 100%;
  height: 100%;
  display: block;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .35));
  transition: transform .12s ease;
}
#bonus-close:hover img { transform: scale(1.08); }
#bonus-close:active img { transform: scale(0.94); }

/* Стопка награды: N иконок ложатся колодой и по одной
   слистываются в бейдж своей кнопки. Позиция фиксированная —
   transform: scale() у #stage не должен сбивать координаты.
   Длительность transform обязана совпадать с CARD_FLY_MS в game.js */
.reward-card {
  position: fixed;
  left: 0;
  top: 0;
  /* Размер обяза�� совпадать с CARD_SIZE в game.js — по нему считается
     центровка стопки и точка прицела в бейдж */
  box-sizing: border-box;
  width: 94px;
  height: 94px;
  padding: 5px;
  /* Карта повторяет саму кнопку бонуса (#shake и соседи):
     тот же кружок, рамка и градиент, чтобы игрок узнавал,
     что именно летит в его ячейку. Менять вместе с кнопками. */
  border: 3px solid #6a351d;
  border-radius: 50%;
  background: linear-gradient(145deg, #fff8df 0%, #f3dfb7 72%, #dfba7a 100%);
  box-shadow: 0 3px 5px rgba(0, 0, 0, .38), inset 0 2px 0 rgba(255, 255, 255, .7);
  opacity: 0;
  filter: drop-shadow(0 5px 8px rgba(0, 0, 0, .5));
  transition: transform .52s cubic-bezier(.4, .05, .35, 1), opacity .22s ease;
  pointer-events: none;
  z-index: 71;
}
.reward-card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
#bonus-name {
  font-family: var(--font-display);
  font-size: 21px;
  color: #6a3410;
}
#bonus-desc {
  margin: 10px 4px 14px;
  font-family: var(--font-text);
  font-size: 15px;
  line-height: 1.6;
  color: #5c3a1a;
  text-align: left;
}
/* Мяч прямо в тексте описания — вместо слов «7-й мяч».
   Исходник мяча — 512 px, поэтому размер дублируется инлайном из
   game.js (CONFIG.BONUS_DESC_BALL_PX): если браузер отдаст старый
   style.css из кэша, картинка всё равно остан��тся маленькой.
   Значения менять вместе с константой в config.js. */
#bonus-desc .desc-ball {
  display: inline-block;
  /* 21 = 20 / 0.94 с учётом прозрачных полей спрайта (2026-08-11);
     дублируется из CONFIG.BONUS_DESC_BALL_PX — менять парой */
  width: 21px;
  height: 21px;
  max-width: 21px;
  max-height: 21px;
  margin: 0 2px;
  vertical-align: -5px;
  object-fit: contain;
}
/* Согласие — кнопка с ПОЗИТИВНОЙ картинкой (row_button.webp).
   Красный row_button_danger.webp зарезервирован за разрушающим
   действием («Начать заново», сброс прогресса): на кнопке
   согласия красный читается как «отмена/опасно» и сажает CTR
   рекламного плейсмента. Цифры border/border-image — из 9-slice таблицы
   выше (row_button 34), темный текст — как у остальных светлых кнопок */
#bonus-ok {
  width: 100%;
  min-height: 56px;
  border: 15px solid transparent;
  border-image: url('../assets/row_button.webp') 34 fill / 15px stretch;
  background: none;
  font-family: var(--font-ui);
  font-size: 17px;
  font-weight: 700;
  color: #5a3413;
  cursor: pointer;
}
#bonus-ok:hover { filter: brightness(1.06); }
#bonus-ok:active { transform: translateY(1px); }

/* Режим прицела бонуса «Удалить мяч» — ��ёлтое свечение вокруг иконки */
#pop-ball.armed { box-shadow: 0 0 0 3px #ffc400, 0 0 14px 4px rgba(255,196,0,.7); }
/* Затенение всего окна в режиме прицела: поверх фона и панелей,
   но под игровым полем (z-index 5) и рядом бонусов; клики пропускает */
#aim-dim {
  position: fixed;
  inset: 0;
  display: none;
  background: rgba(0,0,0,.45);
  pointer-events: none;
  z-index: 4;
}
body.pop-aim-mode #aim-dim { display: block; }
/* Ряд бонусов не затеняется — видно активную кнопку и отмену */
#bonus-row { position: relative; z-index: 5; }

/* Курсор в режиме прицела — встроенная SVG-иконка (без внешних ресурсов),
   запасной вариант — crosshair. Затенение поля и красная обводка шаров
   рисуются в game.js (afterRender) */
canvas.pop-aim {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='10' fill='none' stroke='red' stroke-width='2.5'/%3E%3Ccircle cx='16' cy='16' r='2.5' fill='red'/%3E%3Cline x1='16' y1='2' x2='16' y2='9' stroke='red' stroke-width='2.5'/%3E%3Cline x1='16' y1='23' x2='16' y2='30' stroke='red' stroke-width='2.5'/%3E%3Cline x1='2' y1='16' x2='9' y2='16' stroke='red' stroke-width='2.5'/%3E%3Cline x1='23' y1='16' x2='30' y2='16' stroke='red' stroke-width='2.5'/%3E%3C/svg%3E") 16 16, crosshair;
}


/* Подэкран «Обратная связь»: адрес почты и кнопка копирования.
   Адрес ломается по символам (word-break): панель узкая, а почта
   может быть длиннее текущей — иначе она вылезет за край. */
.mail-link {
  display: inline-block;
  color: #7a4a16;
  font-size: 17px;
  font-weight: bold;
  text-decoration: underline;
  word-break: break-all;
}
.mail-copy {
  margin: 2px 0 0;
  padding: 8px 15px;
  border: 0;
  border-radius: 12px;
  background: rgba(90, 52, 19, .13);
  color: #5a3413;
  font-family: inherit;
  font-size: 14px;
  font-weight: bold;
  cursor: pointer;
}
.mail-copy:active { transform: scale(.985); }

/* 2026-09: окна ограничены реальной высотой viewport. Шапка и крестик
   остаются вне скролла, нижняя кнопка — всегда доступна. */
#bonus-overlay, #gold-overlay, #settings-overlay {
  box-sizing: border-box;
  padding: 44px 24px 18px;
  padding-top: max(44px, env(safe-area-inset-top));
  padding-bottom: max(18px, env(safe-area-inset-bottom));
}
#bonus-panel, #gold-panel {
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  min-width: 0;
  width: 360px;
  max-width: 100%;
  max-height: calc(100vh - 80px);
  max-height: calc(100dvh - 80px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  animation-timing-function: ease-out;
}
.modal-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-y;
}
#bonus-ok, #gold-ok { flex: 0 0 auto; align-self: center; min-height: 44px; }
#bonus-title, #gold-title { width: calc(100% + 8px); padding-inline: 12px; }
#bonus-close { right: -38px; }
#boot-gate { flex-direction: column; gap: 16px; padding: 24px; box-sizing: border-box; }
#boot-status { max-width: 440px; margin: 0; text-align: center; color: #183a35; font: 16px/1.5 system-ui, sans-serif; }
#boot-retry { min-height: 44px; padding: 10px 20px; background: white; color: #183a35; border: 2px solid #183a35; border-radius: 8px; cursor: pointer; }
#boot-retry[hidden] { display: none; }
#mail-link { user-select: text; -webkit-user-select: text; overflow-wrap: anywhere; }
button:focus-visible { outline: 3px solid #205990; outline-offset: 3px; }
@media (max-height: 450px) {
  #bonus-panel, #gold-panel { padding: 8px 8px 6px; border-width: 22px; border-image-width: 22px; }
  #bonus-title, #gold-title { top: -49px; font-size: 20px; height: 48px; }
  #bonus-close { top: -35px; right: -30px; }
  #bonus-icon { width: 56px; height: 56px; margin: 0; }
  #bonus-desc, #gold-text { margin: 6px 2px 10px; line-height: 1.4; }
  #gold-shine { width: 80px; height: 80px; }
  #gold-ball { width: 72px; height: 72px; }
  #bonus-ok, #gold-ok { padding-block: 6px; }
}
@media (prefers-reduced-motion: reduce) {
  #bonus-panel, #gold-panel, #settings-panel { animation: none; }
}

/* touch-action учитывается по всей цепочке предков скроллера. */
#bonus-overlay, #gold-overlay { touch-action: pan-y; }
.modal-scroll { scrollbar-width: thin; scrollbar-color: #9a683b transparent; margin-bottom: 8px; }
.modal-scroll::-webkit-scrollbar { width: 6px; }
.modal-scroll::-webkit-scrollbar-thumb { background: #9a683b; border-radius: 3px; }
@media (max-height: 450px) {
  #bonus-panel, #gold-panel {
    max-height: calc(100vh - 70px);
    max-height: calc(100dvh - 70px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
  }
  #bonus-icon { width: 48px; height: 48px; }
  #bonus-ok { border-width: 12px; border-image-width: 12px; padding-block: 0; min-height: 48px; }
}
