/*
  Стили мини-приложения "Награда дня" — коробки, категории, "Мои награды",
  монеты. Общие для двух хостов: отдельной страницы /reward/ (reward_home.html)
  и попапа поверх каталога (catalog.html). Цвета/иконки категорий берутся из
  catalog.css (--cat-*, --icon-*, .cat-icon-circle/.cat-glyph) — обе страницы
  уже подключают catalog.css, здесь эти классы не переопределяются.
*/
:root {
  /* Фон страницы "Награда дня" - тот же тёмный, что и внутренние
     поверхности виджета ниже. Не меняем набор тем - просто одно место
     вместо дублирующихся объявлений на хосте (см. reward_home.html). */
  --rw-page-bg: #0b0f19;
  --rw-muted: #94a3b8;
}
.rw-widget {
  color: #e2e8f0;
  /* язык движения v2 - docs-project/MOTION_LANGUAGE.md, раздел 7/9 */
  --motion-instant: 100ms;
  --motion-fast: 200ms;
  --motion-base: 300ms;
  --motion-slow: 450ms;
  --motion-celebration: 700ms;
  --ease-standard: cubic-bezier(.4, 0, .2, 1);
  --ease-out: cubic-bezier(0, 0, .2, 1);
  --ease-in: cubic-bezier(.4, 0, 1, 1);
  --ease-spring: cubic-bezier(.34, 1.56, .64, 1);
}

/*
  Базовое правило скрытия для class="hidden", используемого и на попапе
  (reward-modal-backdrop в catalog.html), и на экранах внутри самого
  виджета (RewardWidget: view-loading/view-gate/... в reward-widget.js).
  На reward_home.html это правило раньше давал Tailwind CDN (<script
  src="https://cdn.tailwindcss.com">) — на catalog.html такого скрипта
  нет, поэтому .hidden там не скрывал ничего и попап-оверлей был виден
  сразу при загрузке страницы. Правило теперь живёт здесь — в общем для
  обоих хостов файле, без зависимости от Tailwind.
*/
.hidden { display: none !important; }

.rw-boxes-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; position: relative; }
.rw-boxes-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }

/*
  Коробка "Награды дня" - нарисована векторно (SVG), без эмодзи (docs-project/
  MOTION_LANGUAGE.md, раздел 9.1). .rw-box-slot - ячейка грида (покой,
  покачивание). .rw-box-tilt - обёртка, которой управляет ТОЛЬКО JS (наклон
  под палец/гироскоп, раздел 9.6) - её transform никогда не трогают keyframes,
  чтобы не конфликтовать с .rw-box ниже (upreждение/открытие).
*/
.rw-box-slot { position: relative; aspect-ratio: 1/1; }
.rw-box-tilt { width: 100%; height: 100%; transform-style: preserve-3d; }
.rw-box {
  position: relative; width: 100%; height: 100%; cursor: pointer;
  display: flex; align-items: center; justify-content: center; user-select: none;
}
.rw-box svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.rw-box.is-dimmed { opacity: .35; }

.rw-box-shadow-ellipse { transition: none; }
.rw-box-lid-group { transform-box: fill-box; transform-origin: 50% 90%; }
.rw-box-glow-core { transform-box: fill-box; transform-origin: center; opacity: .12; }
.rw-box-beam { transform-box: fill-box; transform-origin: 50% 100%; transform: scaleY(0); opacity: 0; }
.rw-box-flash { transform-box: fill-box; transform-origin: center; transform: scale(0); opacity: 0; }

/*
  Блик обёрнут в .rw-box-sheen-clip: -20% inset самого блика по горизонтали
  у крайней (первой/третьей) коробки выходил за край страницы (обнаружено
  docs-project/streak-lane, 6-7px на 375/360 - .rw-burst-layer рядом не
  участвует, его не трогаем). overflow-x: clip обрезает только горизонталь
  (единственная ось, влияющая на document.scrollWidth), overflow-y: visible
  сохраняет вертикальный вылет блика как был - сам .rw-box-sheen (размер,
  градиент, центр, transform из JS) не менялся вообще.
*/
.rw-box-sheen-clip { position: absolute; inset: 0; overflow-x: clip; overflow-y: visible; pointer-events: none; }
/* блик, едущий по поверхности при наклоне - отдельный слой, двигается
   только через translate из JS, никогда через background-position. */
.rw-box-sheen {
  position: absolute; inset: -20%; pointer-events: none; border-radius: 20%;
  background: radial-gradient(circle at 50% 30%, rgba(255,255,255,.16), transparent 55%);
  mix-blend-mode: screen; will-change: transform;
}

@media (prefers-reduced-motion: no-preference) {
  .rw-box-slot.motion-idle-sway .rw-box { animation: rw-idle-sway 2.6s ease-in-out infinite; }
  @keyframes rw-idle-sway {
    0%, 100% { transform: rotate(0deg) translateY(0); }
    50% { transform: rotate(1.4deg) translateY(-2px); }
  }
  /* Пауза перед развязкой (MOTION_LANGUAGE.md, раздел 9.5): присесть ->
     короткий замер -> дрожь нарастающего ожидания. 460мс, обоснование
     значения - там же. */
  .rw-box.is-anticipating { animation: rw-box-anticipate 460ms var(--ease-standard) 1 both; }
  @keyframes rw-box-anticipate {
    0%   { transform: scale(1) translateY(0) rotate(0deg); }
    18%  { transform: scale(.95) translateY(2px) rotate(0deg); }
    30%  { transform: scale(.95) translateY(2px) rotate(0deg); }
    40%  { transform: scale(.96) translateY(1px) rotate(-2.4deg); }
    50%  { transform: scale(.96) translateY(1px) rotate(2.2deg); }
    60%  { transform: scale(.97) translateY(1px) rotate(-1.8deg); }
    70%  { transform: scale(.97) translateY(1px) rotate(1.4deg); }
    80%  { transform: scale(.97) translateY(1px) rotate(-0.8deg); }
    92%  { transform: scale(.98) translateY(0px) rotate(0deg); }
    100% { transform: scale(1) translateY(0) rotate(0deg); }
  }
  .rw-box.is-opening-small { animation: rw-box-open-small 380ms var(--ease-standard) 1 both; }
  @keyframes rw-box-open-small {
    0%   { transform: scale(1) translateY(0); }
    45%  { transform: scale(1.05) translateY(-7px); }
    100% { transform: scale(1) translateY(0); }
  }
  .rw-box.is-opening-big { animation: rw-box-open-big 620ms var(--ease-standard) 1 both; }
  @keyframes rw-box-open-big {
    0%   { transform: scale(1) translateY(0); }
    35%  { transform: scale(1.1) translateY(-14px); }
    60%  { transform: scale(1.02) translateY(-4px); }
    100% { transform: scale(1) translateY(0); }
  }

  .rw-box-glow-core.is-charging { animation: rw-glow-charge 460ms var(--ease-in) 1 both; }
  @keyframes rw-glow-charge {
    0%   { opacity: .12; transform: scale(1); }
    100% { opacity: .68; transform: scale(1.18); }
  }
  .rw-box-glow-core.is-flash-small { animation: rw-glow-flash-small 380ms var(--ease-out) 1 both; }
  @keyframes rw-glow-flash-small {
    0%   { opacity: .68; transform: scale(1.18); }
    25%  { opacity: .95; transform: scale(1.5); }
    100% { opacity: 0; transform: scale(1.8); }
  }
  .rw-box-glow-core.is-flash-big { animation: rw-glow-flash-big 560ms var(--ease-out) 1 both; }
  @keyframes rw-glow-flash-big {
    0%   { opacity: .68; transform: scale(1.18); }
    20%  { opacity: 1; transform: scale(1.9); }
    55%  { opacity: .55; transform: scale(1.6); }
    100% { opacity: 0; transform: scale(2.1); }
  }

  .rw-box-flash.is-pop-small { animation: rw-flash-pop-small 260ms var(--ease-out) 1 both; }
  @keyframes rw-flash-pop-small {
    0%   { transform: scale(.3); opacity: 0; }
    30%  { transform: scale(1.5); opacity: .55; }
    100% { transform: scale(2.1); opacity: 0; }
  }
  .rw-box-flash.is-pop-big { animation: rw-flash-pop-big 380ms var(--ease-out) 1 both; }
  @keyframes rw-flash-pop-big {
    0%   { transform: scale(.3); opacity: 0; }
    28%  { transform: scale(2.1); opacity: .78; }
    100% { transform: scale(3); opacity: 0; }
  }

  .rw-box-beam.is-shoot-small { animation: rw-beam-shoot-small 340ms var(--ease-out) 1 both; }
  @keyframes rw-beam-shoot-small {
    0%   { transform: scaleY(0); opacity: 0; }
    25%  { opacity: .45; }
    100% { transform: scaleY(.65); opacity: 0; }
  }
  .rw-box-beam.is-shoot-big { animation: rw-beam-shoot-big 520ms var(--ease-out) 1 both; }
  @keyframes rw-beam-shoot-big {
    0%   { transform: scaleY(0); opacity: 0; }
    22%  { opacity: .65; }
    100% { transform: scaleY(1); opacity: 0; }
  }

  .rw-box-lid-group.is-flying-small { animation: rw-lid-fly-small 260ms var(--ease-spring) 20ms 1 both; }
  @keyframes rw-lid-fly-small {
    0%   { transform: translate(0,0) rotate(0deg); opacity: 1; }
    100% { transform: translate(10px,-30px) rotate(-24deg); opacity: 0; }
  }
  .rw-box-lid-group.is-flying-big { animation: rw-lid-fly-big 340ms var(--ease-spring) 30ms 1 both; }
  @keyframes rw-lid-fly-big {
    0%   { transform: translate(0,0) rotate(0deg); opacity: 1; }
    100% { transform: translate(20px,-54px) rotate(-46deg); opacity: 0; }
  }

  /*
    Рубеж серии дней (docs-project/LOYALTY_ECONOMY.md, раздел 2) - отдельная
    фаза ПОСЛЕ обычного вскрытия коробки (reward-widget.js,
    playStreakMilestone), крупнее/длиннее "big" и с акцентным градиентом
    амбер+фиолетовый (rwMilestone*Grad в SVG_DEFS), чтобы читаться как
    категорически другое событие, а не просто более крупный приз (раздел 9.4).
  */
  .rw-box.is-opening-streak { animation: rw-box-open-streak 780ms var(--ease-standard) 1 both; }
  @keyframes rw-box-open-streak {
    0%   { transform: scale(1) translateY(0); }
    30%  { transform: scale(1.14) translateY(-18px); }
    55%  { transform: scale(1.04) translateY(-6px); }
    100% { transform: scale(1) translateY(0); }
  }

  .rw-box-glow-core.is-charging-streak { animation: rw-glow-charge-streak 260ms var(--ease-in) 1 both; fill: url(#rwMilestoneGlowGrad); }
  @keyframes rw-glow-charge-streak {
    0%   { opacity: .2; transform: scale(1.05); }
    100% { opacity: .8; transform: scale(1.3); }
  }
  .rw-box-glow-core.is-flash-streak { animation: rw-glow-flash-streak 700ms var(--ease-out) 1 both; fill: url(#rwMilestoneGlowGrad); }
  @keyframes rw-glow-flash-streak {
    0%   { opacity: .8; transform: scale(1.3); }
    22%  { opacity: 1; transform: scale(2.1); }
    55%  { opacity: .6; transform: scale(1.8); }
    100% { opacity: 0; transform: scale(2.4); }
  }

  .rw-box-flash.is-pop-streak { animation: rw-flash-pop-streak 460ms var(--ease-out) 1 both; fill: url(#rwMilestoneFlashGrad); }
  @keyframes rw-flash-pop-streak {
    0%   { transform: scale(.3); opacity: 0; }
    26%  { transform: scale(2.4); opacity: .85; }
    100% { transform: scale(3.4); opacity: 0; }
  }

  .rw-box-beam.is-shoot-streak { animation: rw-beam-shoot-streak 620ms var(--ease-out) 1 both; fill: url(#rwMilestoneBeamGrad); }
  @keyframes rw-beam-shoot-streak {
    0%   { transform: scaleY(0); opacity: 0; }
    20%  { opacity: .7; }
    100% { transform: scaleY(1.15); opacity: 0; }
  }

  .rw-box-lid-group.is-flying-streak { animation: rw-lid-fly-streak 380ms var(--ease-spring) 30ms 1 both; }
  @keyframes rw-lid-fly-streak {
    0%   { transform: translate(0,0) rotate(0deg); opacity: 1; }
    100% { transform: translate(26px,-70px) rotate(-58deg); opacity: 0; }
  }
}
@media (prefers-reduced-motion: reduce) {
  .rw-box.is-anticipating, .rw-box.is-opening-small, .rw-box.is-opening-big, .rw-box.is-opening-streak { transform: none; }
  .rw-box-glow-core, .rw-box-flash, .rw-box-beam { opacity: 0 !important; }
  .rw-box-lid-group.is-flying-small, .rw-box-lid-group.is-flying-big, .rw-box-lid-group.is-flying-streak { opacity: 0; }
}

/* --- слои частиц: крупные/средние/мелкие + конфетти (раздел 9.3 докса) --- */
.rw-burst-layer { position: absolute; inset: -30% -10% -10%; pointer-events: none; z-index: 5; overflow: visible; }
.rw-burst-large, .rw-burst-medium, .rw-burst-small, .rw-burst-confetti { position: absolute; inset: 0; }
.rw-burst-small { filter: blur(.4px); } /* статичное размытие слоя = ощущение глубины, не анимируется */

.rw-particle-coin { position: absolute; top: 50%; left: 50%; border-radius: 50%; will-change: transform, opacity; }
.rw-layer-large .rw-particle-coin { width: 20px; height: 20px; margin: -10px; }
.rw-layer-medium .rw-particle-coin { width: 14px; height: 14px; margin: -7px; }
.rw-layer-small .rw-particle-coin { width: 9px; height: 9px; margin: -4.5px; }

.rw-particle-confetti {
  position: absolute; top: 50%; left: 50%; width: 7px; height: 10px; margin: -5px -3.5px;
  will-change: transform, opacity; border-radius: 1px;
}

@media (prefers-reduced-motion: no-preference) {
  .rw-layer-large .rw-particle-coin { animation: rw-burst-large var(--dur, 620ms) var(--ease-out) both; }
  .rw-layer-medium .rw-particle-coin { animation: rw-burst-medium var(--dur, 560ms) var(--ease-out) both; }
  .rw-layer-small .rw-particle-coin { animation: rw-burst-small var(--dur, 500ms) var(--ease-out) both; }
  .rw-particle-confetti { animation: rw-burst-confetti var(--dur, 700ms) linear both; }

  @keyframes rw-burst-large {
    0%   { transform: translate(0,0) rotate(0deg) scale(.4); opacity: 0; }
    10%  { opacity: 1; }
    42%  { transform: translate(var(--dx), var(--dy)) rotate(var(--dr)) scale(1); }
    68%  { transform: translate(calc(var(--dx) * .94), calc(var(--dy) * .35 + 30px)) rotate(var(--dr)) scale(.94); }
    84%  { transform: translate(calc(var(--dx) * .9), calc(var(--dy) * .35 + 10px)) rotate(var(--dr)) scale(.97); }
    100% { transform: translate(calc(var(--dx) * .9), calc(var(--dy) * .35 + 24px)) rotate(var(--dr)) scale(.85); opacity: 0; }
  }
  @keyframes rw-burst-medium {
    0%   { transform: translate(0,0) rotate(0deg) scale(.4); opacity: 0; }
    10%  { opacity: .92; }
    40%  { transform: translate(var(--dx), var(--dy)) rotate(var(--dr)) scale(.9); }
    70%  { transform: translate(calc(var(--dx) * .92), calc(var(--dy) * .3 + 24px)) rotate(var(--dr)) scale(.82); }
    100% { transform: translate(calc(var(--dx) * .88), calc(var(--dy) * .3 + 18px)) rotate(var(--dr)) scale(.72); opacity: 0; }
  }
  @keyframes rw-burst-small {
    0%   { transform: translate(0,0) scale(.4); opacity: 0; }
    10%  { opacity: .7; }
    38%  { transform: translate(var(--dx), var(--dy)) scale(.75); }
    100% { transform: translate(calc(var(--dx) * .85), calc(var(--dy) * .3 + 16px)) scale(.6); opacity: 0; }
  }
  @keyframes rw-burst-confetti {
    0%   { transform: translate(0,0) rotate(0deg) scale(.7); opacity: 0; }
    8%   { opacity: 1; }
    55%  { transform: translate(var(--dx), calc(var(--dy) * .5)) rotate(calc(var(--dr) * .6)) scale(1); }
    100% { transform: translate(calc(var(--dx) * 1.05), calc(var(--dy) * .5 + 64px)) rotate(var(--dr)) scale(.9); opacity: 0; }
  }
}

/* --- карточка результата коробки (монеты): малый/крупный исход выглядят
   по-разному без чтения текста (раздел 9.4 докса) --- */
.rw-result-card {
  display: inline-block; padding: 18px 22px; border-radius: 16px;
  background: linear-gradient(160deg, #1e293b, #0f172a); border: 1px solid rgba(255,255,255,.08);
}
.rw-result-card.rw-result-big { border-color: rgba(245,158,11,.55); box-shadow: 0 0 0 1px rgba(245,158,11,.15), 0 10px 30px rgba(245,158,11,.12); }
.rw-result-coin-icon { width: 52px; height: 52px; display: block; margin: 0 auto 10px; }
@media (prefers-reduced-motion: no-preference) {
  .rw-result-card, .rw-milestone-card { animation: rw-pop-in var(--motion-base) var(--ease-spring) both; }
  @keyframes rw-pop-in { 0% { opacity: 0; transform: translateY(10px) scale(.94); } 100% { opacity: 1; transform: translateY(0) scale(1); } }
}

/*
  Карточка рубежа серии (reward-widget.js, renderResult) - визуально
  отличается от обычной карточки монет/приза (амбер+фиолетовый акцент,
  тот же язык, что и у вспышки/луча фазы рубежа выше), а не просто крупнее.
*/
.rw-milestone-card {
  display: inline-block; padding: 20px 24px; border-radius: 18px;
  background: linear-gradient(160deg, #241f38, #171227);
  border: 1px solid rgba(196,181,253,.4);
  box-shadow: 0 0 0 1px rgba(251,191,36,.12), 0 12px 32px rgba(124,58,237,.18);
}
.rw-milestone-icon-wrap { width: 44px; height: 44px; margin: 0 auto 10px; }
.rw-milestone-icon-wrap svg { width: 100%; height: 100%; display: block; }
.rw-milestone-bonus {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 1.1rem; font-weight: 700; color: #fde68a; margin-bottom: 12px;
}
.rw-milestone-bonus .rw-coin-icon { width: 18px; height: 18px; }
.rw-milestone-coin-bar { margin-bottom: 0; }

/* Сброс серии - спокойная подача, намеренно без красного/тревожных
   акцентов (см. задание: "не карающая"). */
.rw-streak-reset-note { color: var(--rw-muted); font-size: .85rem; }

/*
  Фаза награды "Исследователя" (этап 5, view-explorer-milestone) - визуально
  ДРУГАЯ и от коробки (rw-box-*), и от рубежа серии (rw-box-*-streak): своя
  сцена (.rwx-stage), не завязана на разметку/классы коробки вообще - см.
  docs-project/MOTION_LANGUAGE.md, раздел 9.4. Города (rwx-stage-city через
  саму разметку - цвет задаётся JS через fill на элементах, не отдельным
  классом сцены) крупнее/дольше площадок - "заметнее" (постановка задачи).
*/
.rwx-stage { position: relative; width: 132px; height: 132px; margin: 0 auto 14px; }
.rwx-stage-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.rwx-glow { transform-box: fill-box; transform-origin: center; opacity: .16; }
.rwx-beam { transform-box: fill-box; transform-origin: 50% 100%; transform: scaleY(0); opacity: 0; }
.rwx-flash { transform-box: fill-box; transform-origin: center; transform: scale(0); opacity: 0; }
.rwx-badge-icon { position: absolute; inset: 0; margin: auto; width: 46px; height: 46px; z-index: 2; }
.rwx-badge-icon svg { width: 100%; height: 100%; display: block; filter: drop-shadow(0 2px 6px rgba(0,0,0,.35)); }
.rwx-bonus {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 1.1rem; font-weight: 700; color: #fde68a; margin-bottom: 12px;
}
.rwx-bonus .rw-coin-icon { width: 18px; height: 18px; }
.rwx-result-coin-bar { display: inline-flex; margin: 0 auto 18px; }

@media (prefers-reduced-motion: no-preference) {
  .rwx-glow.is-charging { animation: rwx-glow-charge 220ms var(--ease-in) 1 both; }
  @keyframes rwx-glow-charge {
    0%   { opacity: .16; transform: scale(1); }
    100% { opacity: .75; transform: scale(1.25); }
  }
  .rwx-glow.is-flash { animation: rwx-glow-flash 680ms var(--ease-out) 1 both; }
  @keyframes rwx-glow-flash {
    0%   { opacity: .75; transform: scale(1.25); }
    22%  { opacity: 1; transform: scale(2); }
    55%  { opacity: .55; transform: scale(1.75); }
    100% { opacity: 0; transform: scale(2.3); }
  }
  .rwx-flash.is-pop { animation: rwx-flash-pop 440ms var(--ease-out) 1 both; }
  @keyframes rwx-flash-pop {
    0%   { transform: scale(.3); opacity: 0; }
    26%  { transform: scale(2.2); opacity: .8; }
    100% { transform: scale(3.1); opacity: 0; }
  }
  .rwx-beam.is-shoot { animation: rwx-beam-shoot 600ms var(--ease-out) 1 both; }
  @keyframes rwx-beam-shoot {
    0%   { transform: scaleY(0); opacity: 0; }
    20%  { opacity: .7; }
    100% { transform: scaleY(1.1); opacity: 0; }
  }
  #view-explorer-milestone { animation: rw-pop-in var(--motion-base) var(--ease-spring) both; }
}
@media (prefers-reduced-motion: reduce) {
  .rwx-glow, .rwx-flash, .rwx-beam { opacity: 0 !important; }
}

/* --- частицы фазы "Исследователя" - отдельные контейнеры (не rw-burst-*),
   переиспользуют только форму/размер частиц (.rw-particle-coin/.rw-particle-
   confetti) и уже существующие keyframes веера (rw-burst-large/medium/
   small/confetti определены выше, в секции коробки) - только позиционирование
   контейнера здесь своё. --- */
.rwx-burst-layer { position: absolute; inset: -20% -10% -10%; pointer-events: none; z-index: 1; overflow: visible; }
.rwx-layer-large, .rwx-layer-medium, .rwx-layer-small, .rwx-confetti { position: absolute; inset: 0; }
.rwx-layer-small { filter: blur(.4px); }
/* Размер/анимация частиц по слою - те же имена keyframes, что у коробки
   (rw-burst-large/medium/small выше), только контейнер-родитель другой
   класс (.rwx-layer-* вместо .rw-layer-*), поэтому нужны отдельные
   селекторы; .rw-particle-confetti уже без такой привязки к родителю. */
.rwx-layer-large .rw-particle-coin { width: 20px; height: 20px; margin: -10px; }
.rwx-layer-medium .rw-particle-coin { width: 14px; height: 14px; margin: -7px; }
.rwx-layer-small .rw-particle-coin { width: 9px; height: 9px; margin: -4.5px; }
@media (prefers-reduced-motion: no-preference) {
  .rwx-layer-large .rw-particle-coin { animation: rw-burst-large var(--dur, 620ms) var(--ease-out) both; }
  .rwx-layer-medium .rw-particle-coin { animation: rw-burst-medium var(--dur, 560ms) var(--ease-out) both; }
  .rwx-layer-small .rw-particle-coin { animation: rw-burst-small var(--dur, 500ms) var(--ease-out) both; }
}

/*
  Лента дней серии (1..30) - визуальная замена прежней текстовой строки
  прогресса (docs-project/streak-lane), без эмодзи, та же амбер-гамма, что
  и монеты/коробка. Общая для view-attempted/view-boxes (reward-widget.js,
  streakBarHtml()/renderStreak()). Скроллится только сама лента - overflow-x
  и scrollLeft из JS (scrollStreakLanesToToday) - на ОДНОМ и том же элементе
  (.rw-streak-lane): раньше overflow был на отдельном враппере, а scrollLeft
  JS выставлял ячейкам-родителю без своего скролла - прокрутка тихо не
  работала. Ширина по контенту (30 ячеек) шире контейнера - именно это и
  включает overflow-x:auto, страница целиком по горизонтали не двигается
  (проверено Playwright, см. docs-project/streak-lane/RESULTS.md).
*/
.rw-streak-bar { margin-bottom: 14px; }
.rw-streak-lane {
  display: flex; align-items: flex-end; gap: 10px;
  overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
  margin: 0 -4px 10px; padding: 4px 4px 6px; scrollbar-width: none;
}
.rw-streak-lane::-webkit-scrollbar { display: none; }

/* Колонка = ячейка + подпись словами под ней (.rw-lane-label), это и есть
   флекс-элемент ленты - align-items:flex-end на .rw-streak-lane выше
   выравнивает подписи в одну строку, а более высокие рубежные ячейки
   поднимаются над обычными (задание, п.3-4). */
.rw-lane-col { position: relative; flex: none; display: flex; flex-direction: column; align-items: center; gap: 6px; }

/* Обычная ячейка - около 64px (задание, п.1: "мелкие, цифра и иконка почти
   не читаются на телефоне"), день + маленькая коробка. */
.rw-lane-cell {
  position: relative; width: 64px; height: 72px; border-radius: 16px;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 5px;
  background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.06);
}
.rw-lane-day { font-size: .82rem; font-weight: 600; color: var(--rw-muted); }
.rw-lane-box-icon { width: 28px; height: 28px; display: flex; }
.rw-lane-box-icon svg, .rw-lane-check svg, .rw-lane-milestone-icon svg { width: 100%; height: 100%; display: block; }
.rw-lane-label { font-size: .68rem; color: var(--rw-muted); text-align: center; white-space: nowrap; }

/* Пройден/будущее - различаются без чтения текста только приглушённостью
   (см. задание, п.2); пройден дополнительно несёт метку-галочку. */
.rw-lane-cell.is-future { opacity: .4; }
.rw-lane-cell.is-passed { opacity: .55; }
/* Доводка theme-guest, п.4: было #64748b (slate-500) - 3.71:1 на фоне
   ячейки, ниже 4.5:1. slate-400 (тот же тон, что --rw-muted) - 6.88:1,
   разница "пройден/обычный" по-прежнему видна через opacity ячейки
   (.rw-lane-cell.is-passed{opacity:.55} выше), не только цветом подписи. */
.rw-lane-cell.is-passed .rw-lane-day { color: #94a3b8; }
.rw-lane-col.is-passed .rw-lane-label { color: #94a3b8; }
.rw-lane-col.is-future .rw-lane-label { opacity: .55; }
.rw-lane-check { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; }

/* Сегодня - подсвечена и явно выделена (задание, п.2): собственный фон и
   рамка амбер, без анимации-мигания - единственная анимация появления
   одноразовая (см. @keyframes rw-lane-today-in ниже). Подпись под сегодня
   тоже ярче обычной - подсветка читается и по ячейке, и по слову под ней. */
.rw-lane-cell.is-today {
  background: linear-gradient(160deg, rgba(245,158,11,.22), rgba(245,158,11,.06));
  border-color: rgba(245,158,11,.65);
  box-shadow: 0 0 0 1px rgba(245,158,11,.18), 0 6px 16px rgba(245,158,11,.16);
}
.rw-lane-cell.is-today .rw-lane-day { color: #fde68a; }
.rw-lane-col.is-today .rw-lane-label { color: #fde68a; font-weight: 600; }

/* Рубежные дни (3/7/14/30) - заметно крупнее и богаче обычных ячеек
   (задание, п.1/3): номинал монетами читается сразу, без клика. */
.rw-lane-cell-milestone {
  width: 84px; height: 92px; border-radius: 20px; gap: 3px;
  background: linear-gradient(160deg, rgba(245,158,11,.16), rgba(180,83,9,.06));
  border-color: rgba(245,158,11,.35);
}
.rw-lane-cell-milestone.is-today {
  background: linear-gradient(160deg, rgba(245,158,11,.32), rgba(245,158,11,.1));
  border-color: rgba(245,158,11,.85);
  box-shadow: 0 0 0 1px rgba(245,158,11,.28), 0 8px 22px rgba(245,158,11,.24);
}
.rw-lane-cell-milestone.is-future { opacity: .55; }
.rw-lane-cell-milestone.is-passed { opacity: .7; }
.rw-lane-milestone-icon { width: 34px; height: 34px; display: flex; }
.rw-lane-milestone-bonus { font-size: .88rem; font-weight: 700; color: #fde68a; }
.rw-lane-cell-milestone.is-passed .rw-lane-milestone-bonus,
.rw-lane-cell-milestone.is-future .rw-lane-milestone-bonus { color: #cbb676; }

.rw-streak-summary { margin: 0; font-size: .8rem; line-height: 1.4; color: #cbd5e1; }
/* Призыв под строкой серии - чуть заметнее её (задание, п.3): амбер-акцент
   и вес пожирнее вместо простого приглушённого текста, но без прописных
   букв целиком и без восклицательного знака - не кричит. */
.rw-streak-cta { margin: 4px 0 0; font-size: .82rem; font-weight: 600; line-height: 1.4; color: #fde68a; }

@media (prefers-reduced-motion: no-preference) {
  .rw-streak-lane { animation: rw-pop-in var(--motion-base) var(--ease-spring) both; }
  .rw-lane-cell.is-today { animation: rw-lane-today-in var(--motion-base) var(--ease-spring) both; }
  @keyframes rw-lane-today-in {
    0% { transform: scale(.85); opacity: .7; }
    100% { transform: scale(1); opacity: 1; }
  }
}

/*
  Заголовок и пояснение строки прогресса "Исследователя" (view-attempted
  only - см. explorerBarHtml() в reward-widget.js, variant "attempted") -
  у серии дней своя подпись под лентой (.rw-streak-summary выше), эти
  классы теперь только у "Исследователя".
*/
.rwx-row-title { font-size: .78rem; font-weight: 600; color: #cbd5e1; margin-bottom: 4px; }
.rwx-row-desc { margin: 6px 0 0; font-size: .74rem; line-height: 1.4; color: var(--rw-muted); }

/*
  Блок прогресса "Исследователя" (этап 5, docs-project/LOYALTY_ECONOMY.md) -
  та же дисциплина, что и у полосы серии выше (rw-streak-*): заголовок +
  трек + заливка, на тех же экранах (view-attempted/view-boxes), сразу под
  полосой серии. Две строки - площадки (бирюза) и города (роза) - города
  визуально акцентнее (rwx-row-city), т.к. рубеж по городам реже.
*/
.rwx-bar { margin-bottom: 14px; display: flex; flex-direction: column; gap: 8px; }
.rwx-row { display: flex; align-items: center; gap: 8px; }
.rwx-row-icon { flex: none; width: 18px; height: 18px; display: flex; }
.rwx-row-icon svg { width: 100%; height: 100%; display: block; }
.rwx-row-body { flex: 1; min-width: 0; }
.rwx-row-info { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.rwx-row-current { font-size: .85rem; font-weight: 600; color: #e2e8f0; }
.rwx-row-next { font-size: .72rem; color: var(--rw-muted); }
.rwx-row-track { height: 5px; border-radius: 999px; background: rgba(255,255,255,.08); overflow: hidden; }
.rwx-row-fill { height: 100%; border-radius: 999px; width: 0%; background: linear-gradient(90deg, #0d9488, #5eead4); }
.rwx-row-fill-city { background: linear-gradient(90deg, #e11d48, #fb7185); }
.rwx-row-city .rwx-row-current { color: #fecdd3; }
.rwx-cap-note { margin: 2px 0 0; color: var(--rw-muted); font-size: .78rem; }
@media (prefers-reduced-motion: no-preference) {
  .rwx-row-fill { transition: width var(--motion-slow) var(--ease-out); }
}

/* --- переключатель звука (раздел 9.8 докса): по умолчанию тихо включён,
   явная кнопка выключения, выбор запоминается в localStorage. Рядом с
   заголовком экрана коробок, не поверх всей страницы - виджет встраивается
   в разные хосты (попап/страница) с разной раскладкой. --- */
.rw-sound-toggle {
  flex: none; width: 30px; height: 30px; border-radius: 50%; margin-top: 2px;
  border: 1px solid rgba(255,255,255,.14); background: rgba(15,20,32,.9); color: #cbd5e1;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.rw-sound-toggle svg { width: 15px; height: 15px; }

/* Векторные замены прежних эмодзи (гейт/использовано/результат) - тот же
   стиль градиентов, что и у коробки/монеты (см. reward-widget.js, SVG_DEFS). */
.rw-gate-icon-wrap, .rw-attempted-icon-wrap { width: 64px; height: 64px; margin: 0 auto; }
.rw-gate-icon, .rw-attempted-icon { width: 100%; height: 100%; display: block; }
.rw-result-icon-wrap { width: 40px; height: 40px; margin: 0 auto; }
.rw-neutral-icon { width: 100%; height: 100%; display: block; }
.rw-celebration-icon-wrap { width: 48px; height: 48px; margin: 0 auto; }
.rw-celebration-icon { width: 100%; height: 100%; display: block; }

.rw-chip {
  padding: .5rem 1rem; border-radius: 9999px; font-size: .85rem;
  border: 1px solid rgba(255,255,255,.1); background: rgba(255,255,255,.05); color: inherit;
  cursor: pointer;
}

.rw-cat-grid { display: flex; flex-direction: column; gap: 10px; margin-bottom: 16px; }
.rw-cat-card {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 14px; border-radius: 16px;
  text-align: left; cursor: pointer; background: linear-gradient(160deg, #1e293b, #0f172a);
  border: 1px solid rgba(255,255,255,.08); transition: transform .15s ease, border-color .15s ease;
  color: inherit; font: inherit;
}
.rw-cat-card:active { transform: scale(0.98); }
.rw-cat-card:not(.is-blocked):hover { border-color: rgba(255,255,255,.22); }
.rw-cat-card.is-blocked { cursor: default; }
.rw-cat-icon { width: 44px; height: 44px; flex: none; }
.rw-cat-icon .cat-glyph { width: 22px; height: 22px; }
.rw-cat-card.is-blocked .rw-cat-icon { filter: grayscale(1); opacity: .5; }
.rw-cat-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rw-cat-arrow { flex: none; font-size: 1.1rem; }

.rw-myrewards-entry {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 14px; border-radius: 16px;
  text-align: left; cursor: pointer; background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.1);
  color: inherit; font: inherit; margin-top: 16px;
}
.rw-myrewards-icon { width: 1.4rem; height: 1.4rem; flex: none; display: flex; }
.rw-myrewards-icon svg { width: 100%; height: 100%; display: block; }
.rw-myrewards-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.rw-myrewards-label { font-weight: 700; font-size: .9rem; color: #f1f5f9; }
.rw-myrewards-sub { font-size: .76rem; color: var(--rw-muted); }
.rw-myrewards-badge {
  flex: none; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 999px;
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: .75rem;
  background: #f97316; color: #020617;
}
/*
  Вариант для входа в "Мои награды" на экране "попытка уже использована"
  (view-attempted, myRewardsEntryHtml(..., {amber:true}) в reward-widget.js) -
  раньше там была мелкая серая .rw-chip кнопка, терявшаяся внизу экрана
  (отчёт по задаче "кнопка Мои награды"). Тот же янтарный бренд-оттенок,
  что у .rw-coin-bar (rgba(250,204,21,...)), но заметно приглушённее сплошной
  заливки .rw-extra-btn выше по экрану - кнопка не должна перетягивать
  внимание с "Открыть ещё одну коробку".
*/
.rw-myrewards-entry-amber {
  background: linear-gradient(135deg, rgba(250,204,21,.16), rgba(250,204,21,.05));
  border-color: rgba(250,204,21,.32);
  margin-top: 24px;
}
.rw-myrewards-entry-amber .rw-myrewards-label { color: #fde68a; }
.rw-myrewards-entry-amber:active { transform: scale(.99); }

/* Монеты: баланс + доп. открытие */
.rw-coin-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 10px 14px; border-radius: 14px; margin-bottom: 14px;
  background: linear-gradient(135deg, rgba(250,204,21,.14), rgba(250,204,21,.04));
  border: 1px solid rgba(250,204,21,.25);
}
/* Запасной размер для .rw-coin-icon везде, где нет более специфичного
   правила (.rw-coin-pill/.rw-milestone-bonus/...) - без него голый <svg>
   без width/height рисуется браузером как подменяемый элемент по
   умолчанию (~300x150px). Раньше монета так использовалась внутри текста
   кнопки доп. открытия ("огромная монета на весь экран" из отчёта по
   задаче про экран "уже использовано") - сама кнопка теперь без иконки
   (см. reward-widget.js, renderCoinBalance), но правило остаётся здесь на
   случай другого будущего использования той же иконки без обёртки. */
.rw-coin-icon { width: 17px; height: 17px; }
.rw-coin-pill { display: flex; align-items: center; gap: 6px; font-weight: 700; font-size: .95rem; color: #fde68a; }
.rw-coin-pill .rw-coin-icon { width: 17px; height: 17px; flex: none; }
.rw-extra-banner .rw-coin-icon { width: 14px; height: 14px; flex: none; vertical-align: -2px; }

/*
  Доп. открытие (view-attempted) - маленькая статичная коробка (тот же
  векторный стиль, что и в игре, EXTRA_BOX_ICON_SVG) + кнопка с ценой в
  тексте + одна поясняющая строка под ней, которая меняет содержание по
  состоянию (renderCoinBalance): хватает монет - как это работает;
  не хватает - сколько не хватает. Не заголовок экрана - блок компактный,
  укладывается в треть экрана 360x640 (проверено Playwright).
*/
.rw-extra-open { margin-top: 14px; padding: 12px 14px; border-radius: 16px; background: rgba(255,255,255,.04); border: 1px dashed rgba(255,255,255,.16); text-align: center; }
.rw-extra-open-icon { width: 34px; height: 34px; margin: 0 auto 8px; }
.rw-extra-open-icon svg { width: 100%; height: 100%; display: block; }
.rw-extra-open-sub { font-size: .76rem; color: var(--rw-muted); line-height: 1.4; margin: 8px 0 0; }
.rw-extra-btn {
  width: 100%; padding: .65rem .9rem; border-radius: 12px; font-weight: 600; font-size: .85rem;
  background: #f59e0b; color: #1c1917; border: none; cursor: pointer;
}
/* Текст был #64748b (slate-500) - 3.33:1 на этом фоне, ниже 4.5:1 (сквозное
   правило задачи theme-guest распространяется и на disabled). Заменён на
   var(--rw-muted) (slate-400, тот же приглушённый тон, что и остальные
   второстепенные подписи виджета) - 6.19:1, без красного/тревожных
   акцентов, как и требовал комментарий выше ("не карающая" подача). */
.rw-extra-btn:disabled { background: rgba(255,255,255,.08); color: var(--rw-muted); cursor: not-allowed; }
/*
  Состояние "монет не хватает" - весь блок приглушается (не должен быть
  самым заметным элементом экрана, см. задание), кнопка и так неактивна
  визуально через :disabled выше. Без красного/тревожных акцентов - тот
  же принцип "не карающей" подачи, что и у сброса серии (rw-streak-reset-
  note выше по файлу).
*/
.rw-extra-open.rw-extra-open-disabled { opacity: .55; }
.rw-extra-banner {
  display: inline-block; margin-bottom: 12px; padding: 6px 12px; border-radius: 999px;
  background: rgba(250,204,21,.15); color: #fde68a; font-size: .78rem; font-weight: 600;
}

/*
  Хостинг попапом поверх каталога. z-index заведомо выше слоёв Leaflet
  (.leaflet-pane/.leaflet-control из catalog.js — контролы там ~1000,
  попапы маркеров ~700), иначе зум-кнопки карты и плашка атрибуции
  пробивают оверлей насквозь. Было: 1000 (оверлей), явного z-index на
  панели не было (auto). Стало: 10000 / 10001.
*/
.rw-modal-backdrop {
  position: fixed; inset: 0; background: rgba(5, 8, 15, .6); backdrop-filter: blur(2px);
  z-index: 10000; display: flex; align-items: center; justify-content: center;
  padding: 16px; box-sizing: border-box;
}
.rw-modal-panel {
  position: relative; z-index: 10001; width: 100%; max-width: 460px; max-height: 100%;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  background: var(--rw-page-bg); border-radius: 20px; padding: 20px 18px calc(20px + env(safe-area-inset-bottom));
  box-shadow: 0 20px 60px rgba(0,0,0,.5);
}
.rw-modal-close {
  position: absolute; top: 12px; right: 12px; width: 32px; height: 32px; border-radius: 999px;
  background: rgba(255,255,255,.08); border: none; color: #e2e8f0; font-size: 1.1rem; line-height: 1;
  cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 2;
}
.rw-modal-close:hover { background: rgba(255,255,255,.16); }

body.rw-scroll-lock { overflow: hidden; }

/*
  Сертификат награды (PNG, рендерится сервером через Pillow — см.
  loyalty/certificates.py). Чисто витринная картинка: на экране результата
  крупнее, в "Моих наградах" — миниатюра поверх каждой карточки.
*/
.rw-cert-img {
  display: block; width: 100%; max-width: 320px; margin: 0 auto 16px;
  border-radius: 16px; box-shadow: 0 12px 30px rgba(0,0,0,.45);
}
.rw-cert-thumb {
  display: block; width: 100%; max-width: 220px; margin: 0 auto 10px;
  border-radius: 12px;
}

/*
  Остальные Tailwind-утилиты из разметки виджета (reward-widget.js) — тоже
  без эквивалента здесь до сих пор, тоже ломались в попапе catalog.html
  (Tailwind CDN туда специально не подключаем — лишний внешний скрипт в
  вебвью MAX). Значения не выдуманы — сняты через getComputedStyle с
  живого /reward/ (там Tailwind CDN есть), см. отчёт по задаче. Селектор
  через .rw-widget, чтобы не тащить эти правила куда-то ещё на странице.
*/
.rw-widget .flex { display: flex; }
.rw-widget .inline-block { display: inline-block; }
.rw-widget .items-center { align-items: center; }
.rw-widget .justify-between { justify-content: space-between; }
.rw-widget .text-center { text-align: center; }
.rw-widget .text-left { text-align: left; }

.rw-widget .text-xs { font-size: 12px; line-height: 16px; }
.rw-widget .text-sm { font-size: 14px; line-height: 20px; }
.rw-widget .text-lg { font-size: 18px; line-height: 28px; }
.rw-widget .text-xl { font-size: 20px; line-height: 28px; }
.rw-widget .text-2xl { font-size: 24px; line-height: 32px; }
.rw-widget .text-3xl { font-size: 30px; line-height: 36px; }
.rw-widget .text-4xl { font-size: 36px; line-height: 40px; }

.rw-widget .font-medium { font-weight: 500; }
.rw-widget .font-semibold { font-weight: 600; }
.rw-widget .font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }

.rw-widget .text-slate-100 { color: rgb(241, 245, 249); }
.rw-widget .text-slate-300 { color: rgb(203, 213, 225); }
.rw-widget .text-slate-400 { color: rgb(148, 163, 184); }
/* Доводка theme-guest, п.4: text-slate-500 давал 4.02:1, text-slate-600 -
   2.53:1 на тёмной странице (оба сняты через getComputedStyle со светлой
   /reward/-разметки Tailwind, см. комментарий выше по файлу - там же эти
   значения читались на белом фоне, здесь фон всегда тёмный). Оба сведены
   к тому же тону, что --rw-muted/text-slate-400 - 7.47:1. */
.rw-widget .text-slate-500 { color: rgb(148, 163, 184); }
.rw-widget .text-slate-600 { color: rgb(148, 163, 184); }
.rw-widget .text-slate-950 { color: rgb(2, 6, 23); }
.rw-widget .text-orange-400 { color: rgb(251, 146, 60); }
.rw-widget .text-emerald-400 { color: rgb(52, 211, 153); }
.rw-widget .text-rose-400 { color: rgb(251, 113, 133); }

.rw-widget .bg-orange-500 { background-color: rgb(249, 115, 22); }
.rw-widget .bg-white\/5 { background-color: rgba(255, 255, 255, 0.05); }

.rw-widget .border { border-width: 1px; border-style: solid; border-color: rgb(229, 231, 235); }
.rw-widget .border-white\/10 { border-color: rgba(255, 255, 255, 0.1); }
.rw-widget .rounded-lg { border-radius: 8px; }

.rw-widget .mb-1 { margin-bottom: 4px; }
.rw-widget .mb-1\.5 { margin-bottom: 6px; }
.rw-widget .mb-2 { margin-bottom: 8px; }
.rw-widget .mb-3 { margin-bottom: 12px; }
.rw-widget .mb-4 { margin-bottom: 16px; }
.rw-widget .mb-6 { margin-bottom: 24px; }

.rw-widget .mt-4 { margin-top: 16px; }
.rw-widget .mt-5 { margin-top: 20px; }
.rw-widget .mt-6 { margin-top: 24px; }
.rw-widget .mt-8 { margin-top: 32px; }

.rw-widget .py-2 { padding-top: 8px; padding-bottom: 8px; }
.rw-widget .py-2\.5 { padding-top: 10px; padding-bottom: 10px; }
.rw-widget .py-10 { padding-top: 40px; padding-bottom: 40px; }
.rw-widget .py-16 { padding-top: 64px; padding-bottom: 64px; }
.rw-widget .py-24 { padding-top: 96px; padding-bottom: 96px; }

.rw-widget .px-3 { padding-left: 12px; padding-right: 12px; }
.rw-widget .px-5 { padding-left: 20px; padding-right: 20px; }
