:root {
  /* ===== Палитра. Все цвета интерфейса и сцен — только отсюда, оттенки прежние.
     Добавлены два тона тех же оттенков: --steel-100 (вторичный текст светлее, чтобы читался на
     дешёвом экране) и --night-350 (верх карточек — третий, самый светлый уровень фона) ===== */
  /* глубина: тёмно-синий, от самого тёмного */
  --ink: #02070b;
  --ink-rgb: 2, 7, 11;
  --night-900: #050d14;
  --night-850: #08141f;
  --night-830: #081a24;
  --night-800: #0b1c2c;
  --night-790: #0c1a25;
  --night-780: #0d1c28;
  --night-750: #0a2230;
  --night-700: #0e2a38;
  --night-650: #0f2e3d;
  --night-600: #0e3040;
  --night-560: #102536;
  --night-550: #102b3a;
  --night-500: #123445;
  --night-450: #164155;
  --night-440: #1a4254;
  --night-420: #1a4a5c;
  --night-400: #1c4d63;
  --night-350: #21586f;
  /* сталь */
  --steel-700: #2c4652;
  --steel-600: #3d5a66;
  --steel-500: #4d6a76;
  --steel-300: #8aa3ad;
  --steel-200: #93b0be;
  --steel-100: #bdd3dc;
  /* бирюза */
  --teal-950: #062016;
  --teal-900: #06241d;
  --teal-800: #0e5d55;
  --teal-700: #148f73;
  --teal-650: #1a5a4a;
  --teal-600: #1f6b58;
  --teal-500: #1bb887;
  --teal-500-rgb: 27, 184, 135;
  --teal-450: #1fd6a0;
  --teal-400: #2ee6a6;
  --teal-400-rgb: 46, 230, 166;
  --teal-350: #3ddc97;
  --teal-100: #d9fff2;
  /* золото */
  --gold-100: #fff3c4;
  --gold-400: #f0c14a;
  --gold-400-rgb: 240, 193, 74;
  --gold-600: #c49a2a;
  /* светлое и чёрное */
  --snow: #eaf6f3;
  --snow-rgb: 234, 246, 243;
  --white-rgb: 255, 255, 255;
  --black: #000;
  --black-rgb: 0, 0, 0;
  --shadow-rgb: 4, 16, 28;

  /* ===== Роли: правила берут цвета отсюда. Палитру напрямую используют только отдельные детали
     со своими оттенками — кнопка ТАП, пустая монетка сбора, земля и корпуса в сценах ===== */
  /* Фон — три уровня: страница (самый тёмный) → сцена → карточка (светлее) */
  --page: var(--night-800);
  --page-top: var(--night-560);
  --page-bottom: var(--night-850);
  --page-glow: var(--night-420);
  --scene-top: var(--night-450);
  --scene-bottom: var(--night-500);
  --card-top: var(--night-350);
  --card-bottom: var(--night-400);
  --card-border: var(--steel-500);
  /* углубления (полосы, аватар, дорожка шкалы) и плоские поля (кнопки звука, дни бонуса) */
  --well: var(--night-750);
  --field: var(--night-500);
  --field-border: var(--night-400);
  --locked: var(--night-900);
  --locked-border: var(--night-790);
  --silhouette: var(--night-780);
  --shadow: rgba(var(--shadow-rgb), 0.45);
  --overlay: rgba(var(--black-rgb), 0.5);
  /* Текст */
  --text: var(--snow);
  --muted: var(--steel-100);
  --on-action: var(--teal-900);
  /* Золото — только монеты, баланс, цены, монетка сбора */
  --gold: var(--gold-400);
  --gold-dark: var(--gold-600);
  --gold-light: var(--gold-100);
  --gold-rgb: var(--gold-400-rgb);
  /* Яркая бирюза — только то, что можно нажать прямо сейчас (доступный апгрейд, готовая прибыль,
     кнопки, переключатели) */
  --action: var(--teal-400);
  --action-2: var(--teal-350);
  --action-rgb: var(--teal-400-rgb);
  --action-glow: rgba(var(--teal-400-rgb), 0.35);
  /* Приглушённая бирюза — декор: рамки, трубы, ленты, неактивное, значения и подписи */
  --decor: var(--teal-500);
  --decor-rgb: var(--teal-500-rgb);
  --decor-deep: var(--teal-650);
  --decor-deep-2: var(--teal-600);
  /* Сцены: металл, трубы, земля; подсветка ступеней уровня и мелкие блики внутри сцен —
     вид сцен не менялся, это единственное место вне кнопок, где остаётся яркая бирюза */
  --metal: var(--steel-200);
  --pipe-dark: var(--night-600);
  --pipe-light: var(--night-400);
  --pipe-mid: var(--night-500);
  --scene-highlight: var(--teal-400);
  --scene-glow: rgba(var(--teal-400-rgb), 0.35);
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
}

html,
body {
  height: 100%;
  overflow: hidden;
  background:
    radial-gradient(circle at 50% -10%, var(--page-glow) 0%, transparent 42%),
    radial-gradient(circle at 80% 80%, rgba(var(--decor-rgb), 0.08) 0%, transparent 36%),
    linear-gradient(180deg, var(--page-top) 0%, var(--page) 48%, var(--page-bottom) 100%);
  color: var(--text);
  font-family: "Trebuchet MS", "Segoe UI", sans-serif;
}

body {
  display: flex;
  justify-content: center;
  /* цифры одной ширины: счётчики не «прыгают», когда меняется число */
  font-variant-numeric: tabular-nums;
}

.app {
  width: min(100%, 480px);
  height: 100dvh;
  max-height: 100dvh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  padding-bottom: 88px;
}

.screen {
  width: 100%;
  padding: 24px 20px 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  flex: 1;
  min-height: 0;
  position: relative;
  overflow: auto;
}

/* Главный экран: сверху монеты | монет в час (одинаковые блоки), ниже уровень, дальше кнопка ТАП.
   Справа колонка круглых кнопок (профиль, настройки, бонус) на высоту двух верхних строк */
.tap-home {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 52px;
  grid-template-rows: auto auto minmax(0, 1fr);
  align-items: stretch;
  gap: 6px;
  padding: 6px 4px 2px 8px;
  overflow: hidden;
}

.home-coins {
  grid-column: 1;
  grid-row: 1;
}

.home-income {
  grid-column: 2;
  grid-row: 1;
}

.home-dock {
  grid-column: 3;
  grid-row: 1 / span 2;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-end;
  justify-content: flex-start;
  padding-top: 0;
}

.home-dock .sound-btn {
  width: 46px;
  height: 46px;
  font-size: 20px;
}

.tap-home .xp-stat {
  grid-column: 1 / 3;
  grid-row: 2;
  width: auto;
}

.tap-stage {
  grid-column: 1 / -1;
  grid-row: 3;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.tap-coins {
  position: absolute;
  inset: 0;
  pointer-events: none;
  overflow: visible;
  z-index: 3;
}

/* Монетка при тапе: элементы из пула, анимацию включает класс go (снимается, как только доиграла) */
.fly-coin {
  position: absolute;
  left: 0;
  top: 0;
  line-height: 0;
  opacity: 0;
  pointer-events: none;
}

/* will-change держим только на самой монете: 12 монеток в полёте — это лишние слои на телефоне */
.fly-coin.go {
  animation: fly-coin 1s ease-out forwards;
}

@keyframes fly-coin {
  0% {
    opacity: 1;
    transform: translate(-50%, -50%) scale(0.55);
  }
  18% {
    opacity: 1;
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy) * 0.35)) scale(1.05);
  }
  100% {
    opacity: 0;
    transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0.75);
  }
}

.tap-home .stat {
  padding: 8px 10px;
  border-radius: 14px;
}

.tap-home .stat strong {
  font-size: 20px;
  margin-top: 0;
}

.tap-home .home-coins,
.tap-home .home-income {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
}

/* Верхние блоки: сверху иконка и короткая подпись, ниже число во всю ширину блока */
.tap-home .home-coins,
.tap-home .home-income {
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 2px;
  min-width: 0;
}

.home-label {
  display: flex;
  align-items: center;
  gap: 5px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.home-label .home-ico {
  font-size: 13px;
}

.tap-home .home-coins strong,
.tap-home .home-income strong {
  max-width: 100%;
  font-size: 17px;
  font-size: min(17px, 100cqw / (var(--len, 1) * 0.56));
  white-space: nowrap;
}

/* Крупные счётчики монет: число всегда видно полностью. app.js ставит --len (число знаков),
   шрифт уменьшается, только когда число шире блока: 100cqw — ширина блока, 0.56em — ширина цифры
   с запасом. Где cqw не поддерживается, остаётся обычный размер из строки выше */
.tap-home .home-coins,
.tap-home .home-income,
.factory-top .stat {
  container-type: inline-size;
}

.home-ico {
  font-size: 18px;
  line-height: 1;
}

.tap-home .xp-stat .stat-sub {
  margin-top: 2px;
  font-size: 11px;
}

.tap-home .bar {
  margin-top: 4px;
  height: 6px;
}

.home-hint {
  display: none;
}

/* монета — около 70% ширины экрана, но не выше свободного места: экран остаётся без прокрутки */
.tap-home .tap {
  margin: 0;
  width: min(70vw, 46dvh, 320px);
  height: min(70vw, 46dvh, 320px);
  max-height: 100%;
}

.sound-dock {
  display: none;
}

.tap-home.hidden {
  display: none;
}

.sound-btn {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: 1px solid var(--field-border);
  background: var(--field);
  color: var(--text);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  position: relative;
  box-shadow: 0 8px 16px var(--shadow);
}

.sound-btn.muted::after {
  content: "";
  position: absolute;
  left: 18%;
  right: 18%;
  top: 50%;
  height: 2px;
  background: var(--text);
  transform: rotate(-28deg);
  border-radius: 2px;
  pointer-events: none;
}

.hidden {
  display: none;
}

.top {
  text-align: center;
}

.label {
  color: var(--muted);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  font-size: 11px;
}

h1 {
  margin-top: 6px;
  font-size: 48px;
  letter-spacing: 0.16em;
  color: var(--text);
  text-shadow: 0 8px 24px rgba(var(--decor-rgb), 0.18);
}

.cards-title {
  font-size: 36px;
}

.top-actions {
  margin-top: 8px;
  display: flex;
  gap: 8px;
  justify-content: center;
  flex-wrap: wrap;
}

.profile-link {
  margin-top: 0;
  background: var(--field);
  border: 1px solid var(--field-border);
  border-radius: 999px;
  color: var(--action);
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: 0 8px 18px var(--shadow);
  text-decoration: none;
  display: inline-block;
}

.profile-link.bonus-ready,
#open-bonus.bonus-ready {
  color: var(--on-action);
  background: linear-gradient(180deg, var(--action), var(--action-2));
  border-color: var(--action);
  box-shadow: 0 0 0 2px rgba(var(--action-rgb), 0.35), 0 8px 18px rgba(var(--action-rgb), 0.35);
}

.profile-name {
  font-size: 24px;
  font-weight: 800;
}

.profile-user {
  color: var(--muted);
  font-size: 14px;
  min-height: 18px;
}

.stats {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.stat {
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  border: 1px solid var(--card-border);
  border-radius: 22px;
  padding: 14px 16px;
  box-shadow: 0 10px 24px var(--shadow), inset 0 1px 0 rgba(var(--snow-rgb), 0.08);
}

.stat-label {
  display: block;
  color: var(--muted);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.stat strong {
  display: block;
  margin-top: 6px;
  font-size: 28px;
}

.stat-sub {
  margin-top: 4px;
  color: var(--muted);
  font-size: 12px;
}

.coins strong {
  color: var(--gold);
}

.cards-balance,
.xp-stat {
  width: 100%;
}

.xp-stat .stat-label strong {
  display: inline;
  margin: 0;
  font-size: 16px;
  color: var(--text);
}

.bar {
  margin-top: 10px;
  height: 8px;
  background: var(--well);
  border-radius: 999px;
  overflow: hidden;
}

/* полоска уровня: заполнение — transform: scaleX (ставит app.js), не ширина */
.bar-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--decor-deep-2), var(--decor));
  box-shadow: none;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.2s ease;
}

/* Блок уровня: слева «Уровень N», справа что он даёт — бонус к доходу завода и стрелка
   (блок нажимается — открывает окно прогресса уровней) */
.level-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.level-head .stat-label {
  margin-right: auto;
}

.level-open {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.level-open:active {
  transform: scale(0.985);
}

.level-chevron {
  color: var(--muted);
  font-size: 20px;
  font-weight: 700;
  line-height: 0.8;
}

/* === Окно прогресса уровней: панель снизу ===
   Закрыта — за нижним краем экрана (transform) и невидима; открыта — выезжает. Фон — затемнение (opacity).
   Пока панель тянут пальцем, transition выключен (класс dragging), сдвиг ставит app.js */
.sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 45;
  background: var(--overlay);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}

.sheet-backdrop.open {
  opacity: 1;
  pointer-events: auto;
}

.sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 46;
  width: min(100%, 480px);
  max-height: 82dvh;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, var(--night-500) 0%, var(--page) 100%);
  border: 1px solid var(--card-border);
  border-bottom: 0;
  border-radius: 22px 22px 0 0;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  visibility: hidden;
  transform: translate3d(0, 100%, 0);
  transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s linear 0.28s;
}

.sheet.open {
  visibility: visible;
  transform: translate3d(0, 0, 0);
  transition: transform 0.28s cubic-bezier(0.2, 0.8, 0.2, 1), visibility 0s;
}

.sheet.dragging {
  transition: none;
}

/* верх панели: за него тянут вниз */
.sheet-head {
  padding: 8px 16px 8px;
  touch-action: none;
  cursor: grab;
}

.sheet-grip {
  display: block;
  width: 40px;
  height: 4px;
  margin: 0 auto 8px;
  border-radius: 999px;
  background: var(--steel-500);
}

.sheet-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.sheet-title-row h2 {
  font-size: 20px;
  letter-spacing: 0.04em;
}

.sheet-close {
  width: 36px;
  height: 36px;
  border: 1px solid var(--field-border);
  border-radius: 50%;
  background: var(--field);
  color: var(--muted);
  font-size: 15px;
  cursor: pointer;
}

.sheet-progress {
  margin-top: 6px;
  color: var(--muted);
  font-size: 13px;
}

.sheet-bonus {
  margin-top: 4px;
  color: var(--decor);
  font-size: 14px;
  font-weight: 800;
}

/* подписи колонок и строки уровней: одна сетка */
.lvl-cols,
.lvl-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 64px;
  align-items: center;
  gap: 4px 8px;
}

.lvl-cols {
  margin-top: 12px;
  padding: 0 12px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.lvl-cols span:last-child,
.lvl-bonus {
  text-align: right;
}

.lvl-list {
  position: relative;
  flex: 1;
  min-height: 0;
  margin: 0;
  padding: 4px 12px 16px;
  list-style: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.lvl-row {
  padding: 10px 12px;
  border: 1px solid var(--field-border);
  border-radius: 12px;
  background: var(--field);
}

.lvl-num {
  color: var(--text);
  font-size: 15px;
  font-weight: 800;
}

.lvl-check {
  display: none;
  margin-right: 6px;
  color: var(--decor);
}

.lvl-reward {
  color: var(--gold);
  font-weight: 800;
  white-space: nowrap;
}

.lvl-bonus {
  color: var(--decor);
  font-weight: 800;
}

/* круглые уровни (10, 20…50) — заметнее: светлее фон, рамка, номер крупнее */
.lvl-row.milestone {
  padding: 13px 12px;
  border-color: var(--decor-deep-2);
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
}

.lvl-row.milestone .lvl-num {
  font-size: 18px;
}

.lvl-row.milestone .lvl-reward {
  font-size: 16px;
}

/* пройденные — приглушены, с галочкой */
.lvl-row.passed {
  opacity: 0.5;
}

.lvl-row.passed .lvl-check {
  display: inline;
}

/* текущий (следующий к получению) — подсвечен, с полоской прогресса на всю ширину строки */
.lvl-row.current {
  border-color: var(--action);
  box-shadow: 0 0 0 1px rgba(var(--action-rgb), 0.35);
}

.lvl-bar {
  grid-column: 1 / -1;
  height: 6px;
  margin-top: 4px;
  border-radius: 999px;
  background: var(--well);
  overflow: hidden;
}

.lvl-bar-fill {
  height: 100%;
  background: linear-gradient(90deg, var(--action-2), var(--action));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.2s ease;
}

@media (prefers-reduced-motion: reduce) {
  .sheet,
  .sheet.open,
  .sheet-backdrop,
  .lvl-bar-fill {
    transition: none;
  }

  .level-open:active {
    transform: none;
  }
}

.level-bonus {
  color: var(--decor);
  font-size: 12px;
  font-weight: 800;
  white-space: nowrap;
}

.level-bonus.hidden {
  display: none;
}

/* Сумма в уведомлении о новом уровне — золотом */
.toast-coins {
  color: var(--gold);
}

/* Монеты за уровень летят из уведомления к балансу: тот же приём и та же анимация, что у монеток
   при тапе (fly-coin). Точку старта и сдвиг до баланса (--dx, --dy) ставит app.js */
.level-fly {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 61;
  color: var(--gold);
  font-size: 16px;
  font-weight: 800;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
}

.level-fly.go {
  will-change: transform, opacity;
  animation: fly-coin 0.9s ease-in-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  .bar-fill {
    transition: none;
  }

  /* без полёта: сумма и так видна в уведомлении */
  .level-fly.go {
    animation: none;
  }
}

.power-line {
  color: var(--muted);
  font-size: 14px;
}

.power-line strong {
  color: var(--text);
}

/* Монета на главном экране. Кнопка — сама монета: ни фона, ни рамки, только картинка.
   Позади — мягкое статичное свечение в цветах приложения (::before, не анимируется).
   В анимации нажатия — только transform: вдавливание и наклон к точке касания
   (transform ставит app.js, движение ведёт transition ниже) */
.tap {
  position: relative;
  width: min(70vw, 100%);
  max-width: 320px;
  max-height: 100%;
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  user-select: none;
  touch-action: manipulation;
  transform-origin: center;
  /* всё движение монеты — один transition: при частых тапах он не перезапускается,
     а мягко ведёт монету из текущего положения в новое */
  transition: transform 0.14s cubic-bezier(0.25, 0.8, 0.35, 1);
  will-change: transform;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.tap::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 128%;
  height: 128%;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background:
    radial-gradient(circle, rgba(var(--action-rgb), 0.38) 0%, rgba(var(--action-rgb), 0.16) 42%, transparent 68%);
  pointer-events: none;
}

/* монета поверх свечения */
.tap-art {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}

/* нажатие: лёгкое вдавливание и наклон в сторону касания — transform ставит app.js
   (одно обновление за кадр); в лёгком режиме наклона нет */

/* подсказка новичку над монетой: показывается до первого тапа (флаг на сервере) */
.tap-tip {
  position: absolute;
  top: 0;
  left: 50%;
  z-index: 4;
  transform: translateX(-50%);
  padding: 7px 14px;
  border: 1px solid var(--field-border);
  border-radius: 999px;
  background: var(--field);
  color: var(--text);
  font-size: 14px;
  font-weight: 700;
  white-space: nowrap;
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .tap {
    transition: none;
  }
}

.hint {
  min-height: 24px;
  color: var(--muted);
  text-align: center;
  font-size: 14px;
}

.cards-list {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding-bottom: 8px;
}

.upgrade {
  width: 100%;
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  border: 1px solid var(--card-border);
  border-radius: 22px;
  padding: 14px 16px;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 12px;
  align-items: center;
  box-shadow: 0 10px 24px var(--shadow), inset 0 1px 0 rgba(var(--snow-rgb), 0.08);
}

.upgrade h2 {
  font-size: 18px;
  letter-spacing: 0.03em;
}

/* === Выбор языка в настройках: две кнопки с круглыми флагами, выбранный язык в рамке === */
.lang-row {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.lang-title {
  color: var(--muted);
  font-size: 13px;
}

.lang-buttons {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}

.lang-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 9px 10px;
  border: 1px solid var(--field-border);
  border-radius: 14px;
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}

.lang-btn.active {
  border-color: var(--action);
  box-shadow: 0 0 0 1px rgba(var(--action-rgb), 0.45);
}

/* строка «Наш канал» в настройках: иконка Telegram и подпись, открывает канал из конфига */
.channel-row {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--field-border);
  border-radius: 14px;
  background: var(--field);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}

.channel-ico {
  display: block;
  flex-shrink: 0;
  color: var(--action);
}

.channel-ico svg {
  display: block;
  width: 20px;
  height: 20px;
}

.lang-flag {
  display: block;
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  overflow: hidden;
  border-radius: 50%;
  border: 1px solid var(--card-border);
}

.lang-flag svg {
  display: block;
  width: 100%;
  height: 100%;
}

/* === Профиль: шапка, карточка рефералки, статистика 2×3, ID. Всё без прокрутки на 380×650 === */
.profile-screen {
  padding-top: 16px;
  gap: 12px;
}

/* шапка нажимается — открывает окно прогресса уровней */
.pf-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 2px;
  cursor: pointer;
}

.pf-head .level-chevron {
  margin-left: auto;
}

.pf-avatar {
  position: relative;
  flex-shrink: 0;
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 50%;
  border: 2px solid var(--card-border);
  background: linear-gradient(135deg, var(--decor) 0%, var(--teal-700) 100%);
}

.pf-photo {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.pf-letter {
  color: var(--text);
  font-size: 24px;
  font-weight: 800;
}

.pf-who {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.pf-name {
  overflow: hidden;
  font-size: 20px;
  font-weight: 800;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.pf-level {
  color: var(--decor);
  font-size: 13px;
  font-weight: 800;
}

/* карточка рефералки — самый заметный блок экрана: рамка и подсветка яркой бирюзой (статичные) */
.pf-ref {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  border: 1px solid var(--action);
  border-radius: 20px;
  background: linear-gradient(180deg, rgba(var(--action-rgb), 0.16) 0%, var(--card-bottom) 100%);
  box-shadow: 0 0 0 1px rgba(var(--action-rgb), 0.3), 0 10px 26px rgba(var(--action-rgb), 0.18);
  cursor: pointer;
}

.pf-ref-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
}

.pf-ref-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* заголовок заметнее: ярче обычного текста, с лёгкой подсветкой сзади (статичной) */
.referral-title {
  color: var(--teal-100);
  font-size: 19px;
  font-weight: 800;
  text-shadow: 0 0 10px rgba(var(--action-rgb), 0.45);
}

.pf-ref-reward {
  color: var(--muted);
  font-size: 14px;
}

.pf-ref-reward b {
  color: var(--gold);
  font-size: 16px;
}

.pf-ref-count {
  flex-shrink: 0;
  padding: 6px 10px;
  border-radius: 999px;
  background: var(--well);
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
}

.pf-ref-btn {
  width: 100%;
  grid-row: auto;
  padding: 13px 14px;
  font-size: 16px;
}

/* строка «Достижения · 7 из 23» — вход в экран достижений */
.pf-ach {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border: 1px solid var(--card-border);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.pf-ach-ico {
  width: 24px;
  height: 24px;
  color: var(--gold);
}

.pf-ach-text {
  font-size: 15px;
  font-weight: 700;
}

.pf-ach-text b {
  color: var(--muted);
  font-weight: 700;
}

.pf-ach .level-chevron {
  margin-left: auto;
}

/* === Экран достижений: заголовок со счётчиком, группы, сетка карточек в три колонки ===
   Полученное — иконка цветная, дата; не полученное — иконка серая, полоска прогресса (transform) */
.ach-screen {
  padding-top: 12px;
  gap: 14px;
}

.ach-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
}

.ach-back {
  width: 36px;
  height: 36px;
  flex-shrink: 0;
  border: 1px solid var(--field-border);
  border-radius: 50%;
  background: var(--field);
  color: var(--text);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}

.ach-title {
  margin: 0;
  font-size: 22px;
  letter-spacing: 0.04em;
}

.ach-count {
  margin-left: auto;
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--well);
  color: var(--text);
  font-size: 13px;
  font-weight: 700;
  white-space: nowrap;
}

.ach-groups {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding-bottom: 8px;
}

.ach-group-title {
  margin-bottom: 8px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.ach-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.ach-card {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 10px 6px;
  border: 1px solid var(--field-border);
  border-radius: 14px;
  background: var(--field);
  color: var(--text);
  font: inherit;
  text-align: center;
  cursor: pointer;
}

.ach-ico {
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 9px;
  border: 1px solid var(--field-border);
  border-radius: 50%;
  background: var(--well);
  color: var(--steel-500);
}

.ach-ico svg {
  width: 100%;
  height: 100%;
}

.ach-card.got {
  border-color: var(--decor-deep-2);
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
}

.ach-card.got .ach-ico {
  border-color: var(--gold);
  background: rgba(var(--gold-rgb), 0.14);
  color: var(--gold);
}

.ach-name {
  display: -webkit-box;
  overflow: hidden;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.25;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.ach-card:not(.got) .ach-name {
  color: var(--muted);
}

.ach-card.hidden-secret .ach-name {
  letter-spacing: 0.2em;
}

.ach-date {
  color: var(--muted);
  font-size: 10px;
}

.ach-bar {
  width: 100%;
  height: 4px;
  overflow: hidden;
  border-radius: 999px;
  background: var(--well);
}

.ach-bar i {
  display: block;
  height: 100%;
  background: var(--decor);
  transform-origin: left center;
}

.ach-num {
  color: var(--muted);
  font-size: 10px;
  line-height: 1.2;
}

/* иконка в уведомлении о достижении */
.toast-ach-ico {
  display: inline-block;
  width: 16px;
  height: 16px;
  margin-right: 6px;
  vertical-align: -3px;
  color: var(--gold);
}

.toast-ach-ico svg {
  width: 100%;
  height: 100%;
}

.pf-stats {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.pf-tile {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  border: 1px solid var(--card-border);
  border-radius: 14px;
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
}

.pf-tile span {
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.pf-tile strong {
  overflow: hidden;
  font-size: 19px;
  font-weight: 800;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.pf-tile .pf-gold {
  color: var(--gold);
}

.pf-id {
  color: var(--muted);
  font-size: 12px;
  opacity: 0.7;
  text-align: center;
}

/* === Окно рефералки: панель снизу (оформление панели — .sheet, общее с окном уровней).
   Три шага с наградами, счётчики, кнопка «Пригласить». Всё без прокрутки на 380×650 === */
.ref-body {
  padding: 4px 16px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.ref-steps {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.ref-step {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 12px;
  border: 1px solid var(--field-border);
  border-radius: 14px;
  background: var(--field);
}

.ref-icon {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--well);
  font-size: 19px;
}

.ref-text {
  color: var(--text);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.25;
}

.ref-text small {
  display: block;
  margin-top: 3px;
  color: var(--muted);
  font-size: 12px;
  font-weight: 400;
}

.ref-prize {
  text-align: right;
  white-space: nowrap;
}

.ref-prize b {
  display: block;
  color: var(--gold);
  font-size: 17px;
  font-weight: 800;
}

.ref-prize small {
  color: var(--muted);
  font-size: 12px;
}

.ref-stats {
  color: var(--muted);
  font-size: 14px;
  text-align: center;
}

.ref-stats b {
  color: var(--gold);
}

.ref-invite {
  width: 100%;
  grid-row: auto;
  padding: 15px 14px;
  font-size: 17px;
}

.upgrade .effect,
.upgrade .meta {
  color: var(--muted);
  font-size: 13px;
}

.upgrade .price {
  color: var(--gold);
  font-weight: 700;
}

.buy {
  grid-row: 1 / span 3;
  min-width: 92px;
  background: linear-gradient(180deg, var(--action), var(--action-2));
  border: 0;
  border-radius: 14px;
  padding: 12px 14px;
  color: var(--on-action);
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
  box-shadow: 0 8px 16px rgba(var(--action-rgb), 0.22);
}

.buy:active {
  transform: translateY(2px);
}

.buy:disabled {
  opacity: 0.55;
  cursor: default;
}

/* === Задания: переключатель «Ежедневные / Задания» и список карточек ===
   Карточка: название и награда золотом, полоска прогресса, «сделано / цель» и кнопка «Забрать».
   ready — можно забрать (рамка и кнопка яркой бирюзой), done — забрано (приглушена).
   Полоска — transform: scaleX, награда взлетает — transform/opacity */
.tasks-switch {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  padding: 4px;
  background: var(--well);
  border: 1px solid var(--field-border);
  border-radius: 16px;
}

.tasks-switch button {
  position: relative;
  padding: 11px 8px;
  border: 0;
  border-radius: 12px;
  background: transparent;
  color: var(--muted);
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
}

/* выбранный раздел — как текущая вкладка в нижней панели: рамка приглушённой бирюзой */
.tasks-switch button.active {
  color: var(--text);
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  box-shadow: inset 0 0 0 1px var(--decor);
}

/* Точка «есть невзятая награда»: на разделе и на вкладке «Задания» внизу */
.task-dot {
  display: none;
  position: absolute;
  top: 6px;
  right: 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--action);
  box-shadow: 0 0 0 2px var(--page);
}

.has-dot > .task-dot {
  display: block;
}

#nav-tasks {
  position: relative;
}

.tasks-list {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-bottom: 8px;
}

.tasks-list.hidden,
.task-card.hidden {
  display: none;
}

.tasks-empty {
  padding: 24px 0;
  color: var(--muted);
  text-align: center;
  font-size: 14px;
}

.task-card {
  position: relative;
  width: 100%;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "step step"
    "name reward"
    "bar bar"
    "progress action"
    "note note";
  gap: 8px 12px;
  align-items: center;
  padding: 14px 16px;
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  border: 1px solid var(--card-border);
  border-radius: 20px;
  box-shadow: 0 10px 24px var(--shadow), inset 0 1px 0 rgba(var(--snow-rgb), 0.08);
}

.task-step {
  grid-area: step;
  color: var(--muted);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.task-name {
  grid-area: name;
  font-size: 16px;
  line-height: 1.25;
}

.task-reward {
  grid-area: reward;
  justify-self: end;
  color: var(--gold);
  font-size: 16px;
  font-weight: 800;
  white-space: nowrap;
}

.task-bar {
  grid-area: bar;
  height: 8px;
  background: var(--well);
  border-radius: 999px;
  overflow: hidden;
}

.task-bar-fill {
  height: 100%;
  width: 100%;
  background: linear-gradient(90deg, var(--decor-deep-2), var(--decor));
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.35s ease-out;
}

/* высота строки — по кнопке, чтобы карточка не прыгала, когда кнопка появляется */
.task-progress {
  grid-area: progress;
  min-height: 36px;
  display: flex;
  align-items: center;
  color: var(--muted);
  font-size: 13px;
}

.task-claim,
.task-done {
  grid-area: action;
  justify-self: end;
  display: none;
}

.task-claim {
  min-width: 104px;
  padding: 9px 14px;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(180deg, var(--action), var(--action-2));
  color: var(--on-action);
  font-size: 14px;
  font-weight: 800;
  cursor: pointer;
  box-shadow: 0 6px 14px rgba(var(--action-rgb), 0.3);
}

.task-claim:active {
  transform: translateY(2px);
}

.task-done {
  color: var(--muted);
  font-size: 13px;
  font-weight: 800;
}

.task-card.ready .task-claim,
.task-card.done .task-done {
  display: block;
}

/* Задание «Подпишись на канал»: прогресса нет, кнопка меняет роль — «Подписаться», «Проверить»,
   «Забрать». Короткое сообщение о проверке — строкой под кнопкой */
.task-card.channel .task-bar,
.task-card.channel .task-progress {
  display: none;
}

.task-card.channel .task-claim {
  display: block;
}

.task-card.channel.done .task-claim {
  display: none;
}

.task-note {
  grid-area: note;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.3;
}

.task-card.ready {
  border-color: var(--action);
  box-shadow: 0 0 0 1px rgba(var(--action-rgb), 0.35), 0 10px 24px var(--shadow);
}

.task-card.ready .task-bar-fill {
  background: linear-gradient(90deg, var(--action-2), var(--action));
}

/* забрано: приглушено всё, кроме взлетающей награды */
.task-card.done {
  border-color: var(--field-border);
  box-shadow: none;
}

.task-card.done > :not(.task-float) {
  opacity: 0.5;
}

.task-float {
  position: absolute;
  top: 10px;
  right: 16px;
  z-index: 2;
  color: var(--gold);
  font-size: 18px;
  font-weight: 800;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
}

.task-float.go {
  will-change: transform, opacity;
  animation: collect-float 1s ease-out forwards;
}

@media (prefers-reduced-motion: reduce) {
  .task-bar-fill {
    transition: none;
  }

  .task-float.go {
    animation-name: collect-float-still;
  }

  .task-claim:active {
    transform: none;
  }
}

.notice-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: var(--overlay);
}

.notice-overlay.hidden {
  display: none;
}

.app-notice {
  position: relative;
  width: min(100%, 360px);
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  border: 1px solid var(--card-border);
  border-radius: 22px;
  padding: 22px 18px 16px;
  text-align: center;
  color: var(--text);
  box-shadow: 0 18px 40px var(--shadow);
}

.app-notice p {
  margin-bottom: 16px;
  line-height: 1.4;
}

.settings-mutes {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-bottom: 14px;
}

.settings-panel {
  text-align: left;
}

.settings-panel h2 {
  text-align: center;
  margin-bottom: 16px;
  font-size: 22px;
  letter-spacing: 0.06em;
}

.slider-row {
  display: grid;
  grid-template-columns: 110px 1fr 48px;
  gap: 8px;
  align-items: center;
  margin-bottom: 14px;
  color: var(--muted);
  font-size: 14px;
}

.slider-row input[type="range"] {
  width: 100%;
  accent-color: var(--action);
}

.slider-row strong {
  color: var(--text);
  font-size: 13px;
  text-align: right;
}

.settings-panel .buy,
.bonus-panel .buy {
  width: 100%;
  grid-row: auto;
  margin-top: 8px;
}

.bonus-panel h2 {
  text-align: center;
  margin-bottom: 8px;
  font-size: 22px;
  letter-spacing: 0.06em;
}

.bonus-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 8px;
  margin: 12px 0 8px;
}

.bonus-day {
  background: var(--field);
  border: 1px solid var(--field-border);
  border-radius: 14px;
  padding: 10px 6px;
  color: var(--text);
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  cursor: default;
}

.bonus-day span {
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
}

.bonus-day strong {
  color: var(--gold);
  font-size: 16px;
}

.bonus-day.available {
  border-color: var(--action);
  box-shadow: 0 0 0 1px rgba(var(--action-rgb), 0.4), 0 8px 16px var(--shadow);
  cursor: pointer;
}

.bonus-day.claimed {
  opacity: 0.5;
}

/* День 7 — главная награда: занимает две клетки последнего ряда, золотая рамка и фон, число крупнее */
.bonus-day.grand {
  grid-column: span 2;
  justify-content: center;
  border-color: var(--gold);
  background: linear-gradient(180deg, rgba(var(--gold-rgb), 0.2) 0%, rgba(var(--gold-rgb), 0.06) 100%), var(--field);
  box-shadow: 0 0 0 1px rgba(var(--gold-rgb), 0.35), 0 8px 16px var(--shadow);
}

.bonus-day.grand span {
  color: var(--gold);
  font-weight: 700;
}

.bonus-day.grand strong {
  font-size: 22px;
}

.bonus-day.grand.available {
  border-color: var(--action);
  box-shadow: 0 0 0 2px rgba(var(--action-rgb), 0.55), 0 8px 16px var(--shadow);
}

.app-notice .buy {
  width: 100%;
  grid-row: auto;
}

.bottom-nav {
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  width: min(100%, 480px);
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 6px;
  background: transparent;
  border: 0;
  box-shadow: none;
  padding: 6px 8px calc(8px + env(safe-area-inset-bottom));
}

.bottom-nav button {
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  border: 1px solid var(--card-border);
  color: var(--muted);
  padding: 9px 2px 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  cursor: pointer;
  border-radius: 14px;
  box-shadow: 0 8px 16px var(--shadow), inset 0 1px 0 rgba(var(--snow-rgb), 0.08);
}

/* иконка вкладки: обводка currentColor, поэтому гаснет и загорается вместе с подписью */
.nav-ico {
  display: block;
  line-height: 0;
  opacity: 0.75;
}

.nav-ico svg {
  display: block;
  width: 22px;
  height: 22px;
}

/* текущий раздел — не действие: рамка приглушённой бирюзой, подпись основным цветом */
.bottom-nav button.active {
  color: var(--text);
  border-color: var(--decor);
  background: linear-gradient(180deg, var(--decor-deep) 0%, var(--scene-bottom) 100%);
  box-shadow: 0 0 0 1px rgba(var(--decor-rgb), 0.28), 0 8px 16px var(--shadow);
}

.bottom-nav button.active .nav-ico {
  color: var(--action);
  opacity: 1;
}

/* Заглушка «Совсем скоро» (мини-игры, рейтинг): тёмная карточка по центру экрана, приглушённая иконка
   и надпись */
.soon-card {
  width: 100%;
  margin: auto 0;
  padding: 34px 20px 30px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  border: 1px solid var(--card-border);
  border-radius: 22px;
  box-shadow: 0 10px 24px var(--shadow), inset 0 1px 0 rgba(var(--snow-rgb), 0.08);
}

.soon-icon {
  width: 72px;
  height: 45px;
  color: var(--muted);
  opacity: 0.45;
}

.soon-card h2 {
  font-size: 20px;
  letter-spacing: 0.04em;
  color: var(--text);
}

.soon-card p {
  color: var(--muted);
  font-size: 14px;
}

.soon-card.hidden {
  display: none;
}

/* содержимое рейтинга в обёртке, которая сама места не занимает — раскладка экрана прежняя */
.rating-live {
  display: contents;
}

.rating-live.hidden {
  display: none;
}

.rating-list {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-bottom: 8px;
}

.rating-row {
  width: 100%;
  display: grid;
  grid-template-columns: 36px 40px 1fr auto;
  gap: 10px;
  align-items: center;
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  border: 1px solid var(--card-border);
  border-radius: 16px;
  padding: 10px 12px;
  box-shadow: 0 8px 16px var(--shadow);
}

.rating-row.is-me {
  border-color: var(--decor);
  box-shadow: 0 0 0 1px rgba(var(--decor-rgb), 0.35), 0 8px 16px var(--shadow);
}

.rating-row.me-row {
  margin-top: 8px;
}

.rating-place {
  color: var(--text);
  font-weight: 800;
  font-size: 14px;
}

.lb-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--well);
}

.lb-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--decor);
  font-weight: 800;
  background: var(--well);
  border: 1px solid var(--field-border);
}

.rating-name {
  font-size: 15px;
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rating-coins {
  color: var(--gold);
  font-weight: 800;
  font-size: 14px;
  white-space: nowrap;
}

/* --- Завод --- */

.factory {
  gap: 8px;
  padding: 10px 10px 6px;
}

.factory-top {
  width: 100%;
  display: grid;
  grid-template-columns: 1.5fr 1fr;
  gap: 8px;
}

.factory-top .stat {
  padding: 10px 12px;
  border-radius: 16px;
  min-width: 0;
}

/* суммы монет — золотом (доход в час и в минуту) */
.factory-income strong {
  margin-top: 2px;
  font-size: 30px;
  font-size: min(30px, 100cqw / (var(--len, 1) * 0.56));
  white-space: nowrap;
  color: var(--gold);
  line-height: 1.1;
}

.factory-income .stat-sub {
  font-size: 11px;
  color: var(--gold);
}

.factory-coins strong {
  margin-top: 4px;
  font-size: 20px;
  font-size: min(20px, 100cqw / (var(--len, 1) * 0.56));
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* --- Сцена завода, вид сбоку: труба подачи → лента на колёсиках → колпак-штамп → мешок ---
   Вся сцена нарисована в своей системе координат 600×190 (.scene) и целиком масштабируется
   под ширину карточки через transform: scale() (коэффициент выставляет JS при изменении размера).
   Поэтому на любом экране она выглядит одинаково. Все px внутри .scene — это px этой системы.
   Для старых WebView внутри сцены не используется inset, а у aspect-ratio есть запасной вариант.

   Ключевые координаты:
     лента: верх полотна y=118; колёсики — центры x=95, 260, 425
     монета 34×34, в покое стоит на ленте (центр y=101); путь по ленте x=95 → 425 (330px)
     колпак над x=260 (середина пути), опускается на 44px до ленты
     мешок x=470…560, дно y=182 */
.factory-line {
  width: 100%;
  aspect-ratio: 600 / 190;
  flex-shrink: 0;
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--scene-top) 0%, var(--scene-bottom) 100%);
  border: 1px solid var(--field-border);
  border-radius: 18px;
  box-shadow: 0 10px 24px var(--shadow), inset 0 1px 0 rgba(var(--snow-rgb), 0.08);
}

/* Старые WebView без aspect-ratio: те же пропорции через padding */
@supports not (aspect-ratio: 1 / 1) {
  .factory-line {
    height: 0;
    padding-top: 31.667%;
  }
}

/* === Уведомление сверху ===
   Одна строка, без модального окна. Появляется и прячется переходом opacity + transform,
   само исчезает через несколько секунд (таймер в app.js). Нажатие — переход на экран завода */
.toast {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 10px);
  left: 50%;
  z-index: 60;
  width: max-content;
  max-width: min(460px, calc(100% - 24px));
  padding: 9px 14px;
  border-radius: 14px;
  border: 1px solid var(--action);
  background: linear-gradient(180deg, var(--night-440) 0%, var(--night-550) 100%);
  box-shadow: 0 8px 20px var(--shadow);
  color: var(--action);
  font: inherit;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.3;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transform: translate3d(-50%, -12px, 0);
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.toast.show {
  opacity: 1;
  pointer-events: auto;
  transform: translate3d(-50%, 0, 0);
}

@media (prefers-reduced-motion: reduce) {
  .toast,
  .toast.show {
    transform: translate3d(-50%, 0, 0);
  }
}

/* === Монетка сбора хранилища этапа ===
   Поверх сцены в правом верхнем углу: там на всех сценах пусто (механизмы ниже и левее).
   Состояния (класс ставит app.js по данным сервера):
     empty — пусто: тусклая, серая, нажатие выключено
     ready — есть что забрать: золотая, со статичной подсветкой
     full  — хранилище заполнено, производство стоит: бирюзовое кольцо, подсветка сильнее, «!»
   Подсветка статичная; в анимации только transform (нажатие) и opacity (смена состояния) */
.scene-wrap {
  width: 100%;
  position: relative;
  flex-shrink: 0;
}

.collect-coin {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 2;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid var(--gold-dark);
  background:
    url("/coin-logo.svg") center / 58% no-repeat,
    radial-gradient(circle at 35% 30%, var(--gold-light) 0%, var(--gold) 45%, var(--gold-dark) 100%);
  cursor: pointer;
  transition: transform 0.12s ease, opacity 0.2s ease;
  -webkit-tap-highlight-color: transparent;
}

.collect-coin.ready {
  box-shadow: 0 0 10px rgba(var(--gold-rgb), 0.55);
}

.collect-coin.full {
  border: 3px solid var(--action);
  box-shadow: 0 0 0 3px rgba(var(--action-rgb), 0.35), 0 0 16px rgba(var(--gold-rgb), 0.85);
}

/* «!» — производство встало */
.collect-coin.full::after {
  content: "!";
  position: absolute;
  top: -7px;
  right: -7px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--action);
  color: var(--teal-950);
  font-size: 12px;
  font-weight: 900;
  line-height: 16px;
  text-align: center;
}

.collect-coin.empty {
  opacity: 0.45;
  border-color: var(--steel-600);
  background:
    url("/coin-logo.svg") center / 58% no-repeat,
    radial-gradient(circle at 35% 30%, var(--steel-300) 0%, var(--steel-500) 60%, var(--steel-700) 100%);
  cursor: default;
}

/* Нажатие — монетка слегка сжимается */
.collect-coin:not(:disabled):active {
  transform: scale(0.86);
}

/* Лёгкий режим: без свечения, кольцо заполненного хранилища остаётся (это не свечение) */
.factory.lite .collect-coin.ready {
  box-shadow: none;
}

.factory.lite .collect-coin.full {
  box-shadow: 0 0 0 3px rgba(var(--action-rgb), 0.45);
}

/* Сумма сбора: от монетки вверх и растворяется (тот же приём, что у монетки при тапе).
   Прижата к правому краю, чтобы длинное число не вылезало за блок */
.collect-float {
  position: absolute;
  top: 12px;
  right: 6px;
  z-index: 3;
  color: var(--gold);
  font-size: 15px;
  font-weight: 800;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
}

.collect-float.go {
  will-change: transform, opacity;
  animation: collect-float 1s ease-out forwards;
}

@keyframes collect-float {
  0% {
    opacity: 0;
    transform: translate3d(0, 8px, 0) scale(0.8);
  }
  15% {
    opacity: 1;
    transform: translate3d(0, -4px, 0) scale(1.05);
  }
  100% {
    opacity: 0;
    transform: translate3d(0, -48px, 0) scale(0.9);
  }
}

/* Меньше движения: сумма просто появляется и гаснет на месте */
@keyframes collect-float-still {
  0%,
  100% {
    opacity: 0;
  }
  15%,
  60% {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* общее правило ниже ставит всё внутри этапа на паузу; разовой подписи после нажатия — можно
     (она только появляется и гаснет, без движения) */
  .factory .stage .collect-float.go {
    animation-name: collect-float-still;
    animation-play-state: running !important;
  }

  .collect-coin:not(:disabled):active {
    transform: none;
  }
}

.scene {
  position: absolute;
  left: 0;
  top: 0;
  width: 600px;
  height: 190px;
  transform-origin: left top;
  transform: scale(0.567);
}

/* === Лента — общий компонент для всех сцен ===
   В сцене пишется один тег: <div class="conveyor" style="left:…; top:…; --belt-len: 380px"></div>,
   внутренности (две ветви с узором и три колёсика) создаёт app.js один раз при постройке сцены.
   Параметры:
     --belt-len        длина ленты, кратна тайлу узора (20px); высота всегда 50px
     --belt-tile-time  скорость: за сколько лента проходит один тайл (20px). Задаёт сцена так,
                       чтобы лента шла со скоростью того, что на ней едет
     класс .reverse    лента едет влево (по умолчанию — вправо)
   Верх полотна — верхний край ленты (top). Колёсики — у краёв и посередине. */
.conveyor {
  position: absolute;
  width: var(--belt-len, 380px);
  height: 50px;
  border-radius: 25px;
  background: var(--well);
  border: 3px solid var(--field-border);
  /* ветви ленты доходят до самых краёв петли и обрезаются по её скруглению */
  overflow: hidden;
}

/* Лента — замкнутая петля толщиной 8px: верхняя ветвь → полукольцо вокруг правого колёсика →
   нижняя ветвь обратно → полукольцо вокруг левого. Координаты — внутри рамки (44px по высоте),
   центры крайних колёсиков — (22, 22) и (длина − 28, 22), радиус петли 22px снаружи и 14px внутри.
   Узор везде один: полоса 8px через каждые 20px, считая по средней линии петли. На полукольцах
   средней линией берём радиус 60/π ≈ 19.1px: полукольцо = ровно 3 тайла, тайл = ровно 60°,
   поэтому вся петля (2 × (длина − 50) + 120) кратна тайлу и узор нигде не рвётся.
   Всё движется от одной переменной --belt-tile-time: прямые — translateX на тайл (20px),
   полукольца — rotate на 60° за то же время (угловая скорость = линейная ÷ 19.1 × 57.3 = 3 × линейная).
   Фазы подобраны так, что на всех четырёх стыках узор совпадает: верхняя ветвь — узор с −20px,
   нижняя (зеркально, едет обратно) — с −8px, полукольца — с 0° и 30° (для длины, кратной 20px).
   Ветви лежат выше колёсиков, поэтому те их не перекрывают */
.belt-band {
  position: absolute;
  left: 22px;
  width: calc(var(--belt-len, 380px) - 50px);
  height: 8px;
  overflow: hidden;
  z-index: 1;
}

.belt-band.top {
  top: 0;
}

.belt-band.bottom {
  bottom: 0;
}

.belt-run {
  position: absolute;
  top: 0;
  left: -20px;
  width: calc(var(--belt-len, 380px) + 40px);
  height: 8px;
  background: repeating-linear-gradient(
    90deg,
    var(--decor) 0,
    var(--decor) 8px,
    transparent 8px,
    transparent 20px
  );
}

/* нижняя ветвь едет в обратную сторону — её узор сдвинут так, чтобы совпасть с полукольцами */
.belt-band.bottom .belt-run {
  left: -8px;
}

/* Полукольца вокруг крайних колёсиков. Неподвижная рамка (половина круга + 1px нахлёста на прямую,
   чтобы при масштабе сцены не было щели) показывает свою половину вращающегося кольца.
   Кольцо — круг 44px с тем же узором по кругу (полоса 24° через каждые 60°), дырка до радиуса 14px */
.belt-end {
  position: absolute;
  top: 0;
  width: 23px;
  height: 44px;
  overflow: hidden;
  z-index: 1;
}

.belt-end.left {
  left: 0;
  --arc-from: 0deg;
}

.belt-end.right {
  left: calc(var(--belt-len, 380px) - 29px);
  --arc-from: 30deg;
}

.belt-arc {
  position: absolute;
  top: 0;
  left: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: repeating-conic-gradient(
    from var(--arc-from, 0deg),
    var(--decor) 0deg 24deg,
    transparent 24deg 60deg
  );
  -webkit-mask: radial-gradient(circle, transparent 13.5px, #000 14px);
  mask: radial-gradient(circle, transparent 13.5px, #000 14px);
}

.belt-end.right .belt-arc {
  left: -21px;
}

.roller {
  position: absolute;
  top: 3px;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: var(--well);
  border: 3px solid var(--decor);
}

.roller.r1 {
  left: 3px;
}

.roller.r2 {
  left: calc((var(--belt-len, 380px) - 6px) / 2 - 19px);
}

.roller.r3 {
  left: calc(var(--belt-len, 380px) - 47px);
}

/* Движение ленты. Верхняя ветвь едет по ходу ленты, нижняя — обратно, колёсики крутятся
   с той же линейной скоростью (окружность ≈ 132px = 6.6 тайла) */
.conveyor .belt-run {
  animation: belt-shift var(--belt-tile-time, 0.35s) linear infinite;
}

.conveyor .belt-band.top .belt-run,
.conveyor.reverse .belt-band.bottom .belt-run {
  animation-direction: reverse;
}

.conveyor.reverse .belt-band.top .belt-run {
  animation-direction: normal;
}

/* Полукольца: по ходу ленты петля идёт по часовой стрелке, 60° (один тайл) за --belt-tile-time */
.conveyor .belt-arc {
  animation: belt-arc-spin var(--belt-tile-time, 0.35s) linear infinite;
}

.conveyor.reverse .belt-arc {
  animation-direction: reverse;
}

.conveyor .roller {
  animation: roller-spin calc(var(--belt-tile-time, 0.35s) * 6.6) linear infinite;
}

.conveyor.reverse .roller {
  animation-direction: reverse;
}

/* Спицы — чтобы вращение было видно */
.roller::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 2px;
  right: 2px;
  bottom: 2px;
  border-radius: 50%;
  background:
    linear-gradient(var(--field-border), var(--field-border)) center / 4px 100% no-repeat,
    linear-gradient(var(--field-border), var(--field-border)) center / 100% 4px no-repeat;
}

.roller::after {
  content: "";
  position: absolute;
  left: 12px;
  top: 12px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--scene-highlight);
}

/* Монеты. В покое монета стоит в начале пути: центр (95, 101), низ на полотне ленты.
   Внутри два слоя — обычная и отчеканенная, оба на месте с самого начала */
.coin-pool {
  position: absolute;
  left: 0;
  top: 0;
  width: 600px;
  height: 190px;
  z-index: 2;
}

.coin {
  position: absolute;
  left: 78px;
  top: 84px;
  width: 34px;
  height: 34px;
  pointer-events: none;
}

.coin.idle {
  display: none;
}

.coin-face {
  position: absolute;
  left: 0;
  top: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, var(--gold-light) 0%, var(--gold) 45%, var(--gold-dark) 100%);
  border: 2px solid var(--gold-dark);
  box-shadow: 0 0 8px rgba(var(--gold-rgb), 0.5);
}

/* === Картинка монеты (web/img/castor-coin-*.webp) ===
   Лежит поверх монеты: слоя с логотипом на ленте, монетки сбора, монетки при тапе и сумм наград.
   Под ней остаётся прежний логотип-заглушка фоном (/coin-logo.svg) — если картинка не загрузится,
   всё выглядит как раньше. Размер в процентах, чтобы совпадал с прежним логотипом; анимации не трогаем */
.coin-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
}


/* иконка монеты рядом с суммой: в строке текста */
.coin-ico {
  object-fit: contain;
  vertical-align: -3px;
}

/* монетка при тапе (одна картинка, без текста) и суммы наград (картинка перед числом) */
.fly-coin .coin-art,
.collect-float .coin-art,
.task-float .coin-art,
.level-fly .coin-art {
  position: static;
  width: 1em;
  height: 1em;
  margin-right: 4px;
  transform: none;
  vertical-align: -2px;
}

.fly-coin .coin-art {
  width: 26px;
  height: 26px;
  margin-right: 0;
  vertical-align: top;
}

/* ЛОГОТИП: заглушка лежит в web/coin-logo.svg — чтобы заменить, достаточно подменить этот файл */
.coin-face.minted {
  opacity: 0;
  background:
    url("/coin-logo.svg") center / 68% no-repeat,
    radial-gradient(circle at 35% 30%, var(--gold-light) 0%, var(--gold) 45%, var(--gold-dark) 100%);
  border: 3px solid var(--mint-rim, var(--gold-dark));
  box-shadow: 0 0 8px rgba(var(--gold-rgb), 0.5), var(--mint-glow, 0 0 0 transparent);
}

/* Ступени уровня узла-«номинала» этапа (data-mint выставляет app.js): 1 — ур. 1–3, 2 — ур. 4–6,
   3 — ур. 7–9, 4 — ур. 10. На ступенях 2 и 3 меняется механизм этапа (см. блок «Видимый
   прогресс уровней»), на ступени 4 у изделия этапа появляется обводка — общая для всех сцен */
.factory-line[data-mint="4"] {
  --mint-rim: var(--scene-highlight);
  --mint-glow: 0 0 0 2px rgba(var(--teal-400-rgb), 0.45);
}

/* Труба подачи: прямая и широкая (64px при монете 34px), с фланцем на конце.
   Стоит выше монет по слоям — монета выезжает из-под неё */
.feed-pipe {
  position: absolute;
  left: 63px;
  top: -10px;
  width: 64px;
  height: 70px;
  z-index: 3;
  background: linear-gradient(90deg, var(--pipe-dark) 0%, var(--pipe-light) 45%, var(--pipe-mid) 100%);
  border-left: 2px solid var(--field-border);
  border-right: 2px solid var(--field-border);
}

/* Труба вниз справа — общая для всех сцен: вертикальная, вход сразу за концом ленты, чуть ниже
   её уровня (высоту задаёт сцена: style="top: …" — верх трубы, раструб на 8px выше).
   Предмет доезжает до края ленты, срывается и падает в неё по дуге вниз, уходит за её стенку
   (труба выше по слоям) и исчезает внутри. Центр x=505 (84.17% ширины) — на той же вертикали
   начинается труба между этапами */
.out-pipe {
  position: absolute;
  left: 478px;
  top: 112px;
  width: 54px;
  height: 90px;
  z-index: 3;
  background: linear-gradient(90deg, var(--pipe-dark) 0%, var(--pipe-light) 45%, var(--pipe-mid) 100%);
  border-left: 2px solid var(--field-border);
  border-right: 2px solid var(--field-border);
}

.out-pipe::before {
  content: "";
  position: absolute;
  left: -10px;
  right: -10px;
  top: -8px;
  height: 12px;
  border-radius: 4px;
  background: var(--field-border);
}

.feed-pipe::after {
  content: "";
  position: absolute;
  left: -10px;
  right: -10px;
  bottom: 0;
  height: 12px;
  border-radius: 4px;
  background: var(--field-border);
}

/* Штамповочный узел: короткий колпак на стойке. В покое поднят (низ колпака y=74,
   над монетой 10px); опускаясь на 44px, накрывает монету целиком. Двигается только .press-ram */
.press-ram {
  position: absolute;
  left: 231px;
  top: 26px;
  width: 58px;
  height: 48px;
  z-index: 3;
}

/* Стойка: уходит вверх под кронштейн */
.press-ram::before {
  content: "";
  position: absolute;
  left: 25px;
  bottom: 46px;
  width: 8px;
  height: 110px;
  background: var(--metal);
}

.press-cap {
  position: absolute;
  left: 0;
  top: 0;
  width: 58px;
  height: 48px;
  border-radius: 50% 50% 5px 5px / 70% 70% 5px 5px;
  background: linear-gradient(180deg, var(--decor) 0%, var(--decor-deep) 100%);
  border: 2px solid var(--decor);
}

/* Юбка колпака */
.press-cap::after {
  content: "";
  position: absolute;
  left: -6px;
  right: -6px;
  bottom: -2px;
  height: 7px;
  border-radius: 3px;
  background: var(--scene-highlight);
}

/* Кронштейн под потолком сцены, в нём скрывается стойка */
.press-mount {
  position: absolute;
  left: 244px;
  top: 0;
  width: 32px;
  height: 18px;
  z-index: 4;
  border-radius: 0 0 7px 7px;
  background: linear-gradient(90deg, var(--pipe-dark) 0%, var(--pipe-light) 45%, var(--pipe-mid) 100%);
  border: 2px solid var(--field-border);
  border-top: 0;
}

/* Мешок стоит на полу сцены справа от ленты; монета падает в горловину и прячется за ним */
.bag {
  position: absolute;
  left: 470px;
  top: 78px;
  width: 90px;
  height: 104px;
  z-index: 3;
  transform-origin: 50% 100%;
}

/* Полнота мешка зависит от уровня приёмника (--bag-sx / --bag-sy выставляет JS) */
.bag-body {
  position: absolute;
  left: 0;
  top: 0;
  width: 90px;
  height: 104px;
  transform: scale(var(--bag-sx, 0.83), var(--bag-sy, 0.8));
  transform-origin: 50% 100%;
}

.bag-sack {
  position: absolute;
  left: 0;
  top: 25px;
  width: 90px;
  height: 79px;
  border-radius: 45% 45% 40% 40% / 35% 35% 65% 65%;
  background: radial-gradient(circle at 35% 30%, var(--decor-deep-2) 0%, var(--decor-deep) 50%, var(--field) 100%);
  border: 2px solid var(--decor);
}

.bag-neck {
  position: absolute;
  left: 26px;
  top: 6px;
  width: 38px;
  height: 25px;
  border-radius: 8px 8px 3px 3px;
  background: var(--decor-deep);
  border: 2px solid var(--decor);
}

.bag-neck::before {
  content: "";
  position: absolute;
  left: 3px;
  right: 3px;
  top: 2px;
  height: 8px;
  border-radius: 50%;
  background: var(--well);
}

.bag-tie {
  position: absolute;
  left: 23px;
  top: 26px;
  width: 44px;
  height: 6px;
  border-radius: 3px;
  background: var(--gold-dark);
}

/* Монеты, выглядывающие из полного мешка */
.bag-peek {
  position: absolute;
  left: 23px;
  top: -2px;
  width: 44px;
  height: 16px;
  opacity: 0;
  background:
    radial-gradient(circle at 30% 60%, var(--gold) 0, var(--gold) 6px, transparent 6.5px),
    radial-gradient(circle at 70% 50%, var(--gold) 0, var(--gold) 6px, transparent 6.5px);
}

.factory-line[data-bag="2"] .bag-peek,
.factory-line[data-bag="3"] .bag-peek {
  opacity: 1;
}

.factory-line[data-bag="3"] .bag-peek {
  background:
    radial-gradient(circle at 20% 60%, var(--gold) 0, var(--gold) 6px, transparent 6.5px),
    radial-gradient(circle at 50% 35%, var(--gold) 0, var(--gold) 6px, transparent 6.5px),
    radial-gradient(circle at 80% 60%, var(--gold) 0, var(--gold) 6px, transparent 6.5px);
}

/* Лёгкий режим (класс lite на экране завода): никакого свечения */
.factory.lite .coin-face,
.factory.lite .coin-face.minted {
  box-shadow: none;
}

/* --- Движение сцены ---
   Один общий ритм: --coin-interval (раз в столько секунд из трубы падает монета).
   Всё — чистый CSS, только transform и opacity. JS лишь меняет --coin-interval при апгрейде
   ленты (и --coin-count в лёгком режиме); анимации при этом не пересоздаются и не перезапускаются.
   В ключевых кадрах нет var() — только числа системы координат сцены.

   Цикл монеты = interval × count. Доли цикла (см. @keyframes coin-ride):
     0–6%    выезжает из-под трубы и падает на ленту
     6–66%   едет по ленте 330px, середина (колпак) — ровно в 36%
     66–69%  доезжает до края ленты
     69–81%  срывается и падает по дуге в трубу вниз, прячется за её стенкой
     81–100% спрятана, потом снова в трубе слева
   Монета i запускается с задержкой -(i × interval): на старте лента уже заполнена.

   Фазы колпака и мешка — константы, от скорости не зависят. Если монета проходит момент X
   своего цикла, то анимация с периодом interval должна иметь задержку
     interval × (дробная часть (X × count − момент_в_своём_цикле)) − interval.
   Колпак закрыт в середине своего цикла (0.5), монета под ним в 0.36:
     count 6 → 0.36×6 − 0.5 = 1.66 → −0.34;  count 3 → 0.36×3 − 0.5 = 0.58 → −0.42.
   Мешок дёргается в начале своего цикла (0), монета в горловине в 0.78:
     count 6 → 0.78×6 = 4.68 → −0.32.

   Скорость ленты совпадает с монетой: монета едет 330px за 0.6 цикла, значит тайл 20px
   лента проходит за 20 / 330 × 0.6 = 0.0364 цикла; колёсико (r≈21) — оборот за 0.24 цикла */
.factory-line {
  --coin-count: 6;
  --cap-phase: -0.34;
  --bag-phase: -0.32;
}

.factory.lite .factory-line {
  --cap-phase: -0.42;
}

/* Лента чеканки (общий компонент): монета едет 330px за 0.6 цикла → тайл 20px за 0.0364 цикла */
.scene-minting .conveyor {
  --belt-tile-time: calc(var(--coin-interval, 1.6s) * var(--coin-count) * 0.0364);
}

/* Полоса ленты сдвигается ровно на один тайл: translateX(0) → translateX(-20px), затем в ноль */
@keyframes belt-shift {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-20px, 0, 0);
  }
}

@keyframes roller-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

/* Полукольцо ленты поворачивается ровно на один тайл узора (60°) и мгновенно в ноль — бесшовно */
@keyframes belt-arc-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(60deg);
  }
}

/* Монета: одна анимация проезда на весь путь */
.coin {
  animation: coin-ride calc(var(--coin-interval, 1.6s) * var(--coin-count)) linear infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * var(--i, 0) * -1);
}

@keyframes coin-ride {
  0% {
    opacity: 1;
    transform: translate3d(0, -62px, 0);
    animation-timing-function: ease-in;
  }
  6% {
    transform: translate3d(0, 0, 0);
    animation-timing-function: linear;
  }
  66% {
    transform: translate3d(330px, 0, 0);
    animation-timing-function: linear;
  }
  /* край ленты (x=440): дальше монета срывается и падает в трубу. Горизонтально — та же
     скорость (5.5px на 1% цикла), вертикально — с ускорением: 5 точек через равное время,
     y растёт как t² (0 → 3 → 13 → 29 → 51). Гаснет, уже целиком за стенкой трубы */
  68.7% {
    transform: translate3d(345px, 0, 0);
  }
  71.65% {
    transform: translate3d(361.25px, 3.19px, 0);
  }
  74.6% {
    transform: translate3d(377.5px, 12.75px, 0);
  }
  77.55% {
    transform: translate3d(393.75px, 28.69px, 0);
  }
  80.5% {
    opacity: 1;
    transform: translate3d(410px, 51px, 0);
  }
  81%,
  100% {
    opacity: 0;
    transform: translate3d(410px, 51px, 0);
  }
}

/* Превращение: слой с логотипом включается ровно в 36% цикла — монета под закрытым колпаком.
   Та же длительность и задержка, что у проезда, поэтому момент совпадает всегда.
   Выключается, когда монета уже в трубе */
.coin-face.minted {
  animation: coin-mint calc(var(--coin-interval, 1.6s) * var(--coin-count)) linear infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * var(--i, 0) * -1);
}

@keyframes coin-mint {
  0% {
    opacity: 0;
    animation-timing-function: steps(1, end);
  }
  36% {
    opacity: 1;
    animation-timing-function: steps(1, end);
  }
  84%,
  100% {
    opacity: 0;
  }
}

/* Колпак: опускается, накрывает монету целиком (45–55% своего цикла, середина — 50%) и поднимается */
.press-ram {
  animation: cap-stamp var(--coin-interval, 1.6s) linear infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * var(--cap-phase));
}

@keyframes cap-stamp {
  0%,
  30% {
    transform: translate3d(0, 0, 0);
    animation-timing-function: ease-in;
  }
  45%,
  55% {
    transform: translate3d(0, 44px, 0);
    animation-timing-function: ease-out;
  }
  70%,
  100% {
    transform: translate3d(0, 0, 0);
  }
}

/* Мешок слегка дёргается, когда в него падает монета */
.bag {
  animation: bag-catch var(--coin-interval, 1.6s) linear infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * var(--bag-phase));
}

@keyframes bag-catch {
  0% {
    transform: scale(1.05, 0.94);
  }
  18% {
    transform: scale(0.98, 1.03);
  }
  36%,
  100% {
    transform: scale(1, 1);
  }
}

/* Лёгкий режим: мешок не дёргается (колпак и колёсики работают как обычно) */
.factory.lite .bag {
  animation: none;
}

/* === Сцена плавки (этап 1), координаты 600×190 ===
   Слева направо: сопло сверху (центр x=100) — тонкая струя — форма под ним (x=66…134,
   y=88…122, наклоняется вокруг правого нижнего угла) — лента x=150…450, верх полотна y=128 —
   труба-отвод, вход x=460, y=86…136.
   Заготовка 34×34: в покое её центр (118, 90) — там, где залитое золото в наклонённой форме. */
.nozzle {
  position: absolute;
  left: 85px;
  top: -10px;
  width: 30px;
  height: 50px;
  z-index: 4;
  background: linear-gradient(90deg, var(--pipe-dark) 0%, var(--pipe-light) 45%, var(--pipe-mid) 100%);
  border-left: 2px solid var(--field-border);
  border-right: 2px solid var(--field-border);
}

/* носик сопла */
.nozzle::after {
  content: "";
  position: absolute;
  left: 5px;
  bottom: -10px;
  width: 16px;
  height: 10px;
  border-radius: 0 0 4px 4px;
  background: var(--field-border);
}

/* Струя: тонкая и непрерывная, внутри бегут блики вниз (полоса 90px = 9 тайлов по 10px
   при видимой части 70px — длиннее на два тайла) */
.stream {
  position: absolute;
  left: 97px;
  top: 50px;
  width: 6px;
  height: 70px;
  z-index: 1;
  overflow: hidden;
  border-radius: 3px;
}

.stream-run {
  position: absolute;
  left: 0;
  top: -10px;
  width: 6px;
  height: 90px;
  background: repeating-linear-gradient(
    180deg,
    var(--gold-light) 0,
    var(--gold) 5px,
    var(--gold-dark) 8px,
    var(--gold-light) 10px
  );
}

/* Форма: открытая сверху ёмкость. Заливка растёт снизу (scaleY), форма наклоняется вправо.
   Заливка — отдельный элемент поверх формы, не внутри неё: вложенные анимации transform заставляют
   Safari делать слоями все блоки завода ниже, и за экраном он их не рисует до прокрутки */
.mold {
  position: absolute;
  left: 66px;
  top: 88px;
  width: 68px;
  height: 34px;
  z-index: 3;
  transform-origin: 100% 100%;
  background: var(--well);
  border: 4px solid var(--field-border);
  border-top: 0;
  border-radius: 0 0 12px 12px;
  overflow: hidden;
}

/* Заливка стоит там же, где стояла бы внутри формы: внутренняя область формы начинается на (4, 0)
   от её угла, заливка — нижние 26px из 30 → (66 + 4, 88 + 4), 60×26. Низ скруглён по внутреннему
   радиусу формы (12 − 4 = 8px) — раньше это делала обрезка формой.
   Центр преобразования — середина низа заливки (30, 26); петля формы (её правый нижний угол)
   в координатах заливки — (64, 30), то есть на (34, 4) от центра */
.mold-fill {
  position: absolute;
  left: 70px;
  top: 92px;
  width: 60px;
  height: 26px;
  z-index: 3;
  border-radius: 0 0 8px 8px;
  transform-origin: 30px 26px;
  background: linear-gradient(180deg, var(--gold-light) 0%, var(--gold) 45%, var(--gold-dark) 100%);
}

/* Стойка под шарниром формы */
.mold-stand {
  position: absolute;
  left: 120px;
  top: 118px;
  width: 14px;
  height: 64px;
  z-index: 2;
  border-radius: 7px 7px 2px 2px;
  background: var(--field-border);
}

/* Заготовка: гладкая, без логотипа. Уровень «Тигля» меняет обводку — ступени data-mint */
.blank {
  position: absolute;
  left: 101px;
  top: 73px;
  width: 34px;
  height: 34px;
  pointer-events: none;
}

.blank.idle {
  display: none;
}

.blank-face {
  position: absolute;
  left: 0;
  top: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
}

.blank-face.solid {
  background: radial-gradient(circle at 35% 30%, var(--gold-light) 0%, var(--gold) 45%, var(--gold-dark) 100%);
  border: 3px solid var(--mint-rim, var(--gold-dark));
  box-shadow: var(--mint-glow, 0 0 0 transparent);
}

.factory.lite .blank-face {
  box-shadow: none;
}

/* --- Движение плавки ---
   Один период --coin-interval (u). Цикл формы = u: заливка → наклон → выдача заготовки → возврат.
     0–66%   форма наполняется (заливка растёт)
     68–76%  струя прерывается, форма наклоняется; в 76% — наибольший наклон, заготовка выходит
     76–86%  форма возвращается, струя снова льётся в пустую форму
   Заготовки: цикл u × count, заготовка i с задержкой -(i × u) — выходят через каждые u.
   Заготовка выходит в 0% своего цикла, а в форме это 76% → сдвиг формы −0.76u
   (0 × count = 0 — от числа заготовок не зависит, в лёгком режиме тот же).
   Доли цикла заготовки:
     0–5%    выкатывается из наклонённой формы на ленту
     5–45%   едет по ленте до её края
     45–54%  срывается и падает по дуге в трубу вниз, прячется за её стенкой
     55–100% спрятана
   Лента идёт со скоростью заготовки: 340px за 0.5 цикла → тайл 20px за 0.0294 цикла */
.scene-smelting .conveyor {
  --belt-tile-time: calc(var(--coin-interval, 1.6s) * var(--coin-count, 6) * 0.0294);
}

.mold,
.mold-fill,
.stream {
  animation-duration: var(--coin-interval, 1.6s);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * -0.76);
}

.mold {
  animation-name: mold-tilt;
}

.mold-fill {
  animation-name: mold-fill-move;
}

.stream {
  animation-name: stream-pour;
}

@keyframes mold-tilt {
  0%,
  68% {
    transform: rotate(0deg);
    animation-timing-function: ease-in;
  }
  76% {
    transform: rotate(38deg);
    animation-timing-function: ease-in-out;
  }
  86%,
  100% {
    transform: rotate(0deg);
  }
}

/* Заливка: наклон формы (mold-tilt) и собственный рост — в одной анимации. Список функций во всех
   кадрах один: translate(к петле) rotate(угол формы) translate(обратно) scale(1, рост), поэтому
   браузер интерполирует их попарно, как раньше вложенные слои. Кадры посчитаны из прежних
   (scratchpad flatten_anims.py): 0–66% растёт, 68–76% наклон с формой (ease-in), 76–86% форма
   возвращается (точки через 0.2%, в 80–80.5% заливка гаснет), 86.5% — снова тонкий слой.
   Отклонение от прежней траектории — не больше 0.01px сцены */
@keyframes mold-fill-move {
  0% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(0deg) translate(-34px, -4px) scale(1, 0.12);
  }
  66% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(0deg) translate(-34px, -4px) scale(1, 1);
  }
  68% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(0deg) translate(-34px, -4px) scale(1, 1);
    animation-timing-function: ease-in;
  }
  76% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(38deg) translate(-34px, -4px) scale(1, 1);
  }
  76.2% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(37.971deg) translate(-34px, -4px) scale(1, 1);
  }
  76.4% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(37.883deg) translate(-34px, -4px) scale(1, 1);
  }
  76.6% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(37.735deg) translate(-34px, -4px) scale(1, 1);
  }
  76.8% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(37.524deg) translate(-34px, -4px) scale(1, 1);
  }
  77% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(37.251deg) translate(-34px, -4px) scale(1, 1);
  }
  77.2% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(36.912deg) translate(-34px, -4px) scale(1, 1);
  }
  77.4% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(36.509deg) translate(-34px, -4px) scale(1, 1);
  }
  77.6% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(36.039deg) translate(-34px, -4px) scale(1, 1);
  }
  77.8% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(35.501deg) translate(-34px, -4px) scale(1, 1);
  }
  78% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(34.897deg) translate(-34px, -4px) scale(1, 1);
  }
  78.2% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(34.225deg) translate(-34px, -4px) scale(1, 1);
  }
  78.4% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(33.486deg) translate(-34px, -4px) scale(1, 1);
  }
  78.6% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(32.681deg) translate(-34px, -4px) scale(1, 1);
  }
  78.8% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(31.811deg) translate(-34px, -4px) scale(1, 1);
  }
  79% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(30.879deg) translate(-34px, -4px) scale(1, 1);
  }
  79.2% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(29.886deg) translate(-34px, -4px) scale(1, 1);
  }
  79.4% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(28.837deg) translate(-34px, -4px) scale(1, 1);
  }
  79.6% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(27.734deg) translate(-34px, -4px) scale(1, 1);
  }
  79.8% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(26.583deg) translate(-34px, -4px) scale(1, 1);
  }
  80% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(25.388deg) translate(-34px, -4px) scale(1, 1);
  }
  80.167% {
    opacity: 0.667;
    transform: translate(34px, 4px) rotate(24.365deg) translate(-34px, -4px) scale(1, 1);
  }
  80.333% {
    opacity: 0.333;
    transform: translate(34px, 4px) rotate(23.319deg) translate(-34px, -4px) scale(1, 1);
  }
  80.5% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(22.255deg) translate(-34px, -4px) scale(1, 1);
  }
  80.696% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(20.984deg) translate(-34px, -4px) scale(1, 0.965);
  }
  80.893% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(19.702deg) translate(-34px, -4px) scale(1, 0.93);
  }
  81.089% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(18.415deg) translate(-34px, -4px) scale(1, 0.895);
  }
  81.286% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(17.132deg) translate(-34px, -4px) scale(1, 0.86);
  }
  81.482% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(15.86deg) translate(-34px, -4px) scale(1, 0.825);
  }
  81.679% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(14.606deg) translate(-34px, -4px) scale(1, 0.79);
  }
  81.875% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(13.377deg) translate(-34px, -4px) scale(1, 0.755);
  }
  82.071% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(12.18deg) translate(-34px, -4px) scale(1, 0.72);
  }
  82.268% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(11.022deg) translate(-34px, -4px) scale(1, 0.685);
  }
  82.464% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(9.906deg) translate(-34px, -4px) scale(1, 0.65);
  }
  82.661% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(8.839deg) translate(-34px, -4px) scale(1, 0.615);
  }
  82.857% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(7.824deg) translate(-34px, -4px) scale(1, 0.58);
  }
  83.054% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(6.865deg) translate(-34px, -4px) scale(1, 0.545);
  }
  83.25% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(5.965deg) translate(-34px, -4px) scale(1, 0.51);
  }
  83.446% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(5.126deg) translate(-34px, -4px) scale(1, 0.475);
  }
  83.643% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(4.35deg) translate(-34px, -4px) scale(1, 0.44);
  }
  83.839% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(3.638deg) translate(-34px, -4px) scale(1, 0.405);
  }
  84.036% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(2.99deg) translate(-34px, -4px) scale(1, 0.37);
  }
  84.232% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(2.408deg) translate(-34px, -4px) scale(1, 0.335);
  }
  84.429% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(1.89deg) translate(-34px, -4px) scale(1, 0.3);
  }
  84.625% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(1.437deg) translate(-34px, -4px) scale(1, 0.265);
  }
  84.821% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(1.048deg) translate(-34px, -4px) scale(1, 0.23);
  }
  85.018% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(0.722deg) translate(-34px, -4px) scale(1, 0.195);
  }
  85.214% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(0.459deg) translate(-34px, -4px) scale(1, 0.16);
  }
  85.411% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(0.256deg) translate(-34px, -4px) scale(1, 0.125);
  }
  85.607% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(0.113deg) translate(-34px, -4px) scale(1, 0.09);
  }
  85.804% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(0.028deg) translate(-34px, -4px) scale(1, 0.055);
  }
  86% {
    opacity: 0;
    transform: translate(34px, 4px) rotate(0deg) translate(-34px, -4px) scale(1, 0.02);
  }
  86.5% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(0deg) translate(-34px, -4px) scale(1, 0.02);
  }
  100% {
    opacity: 1;
    transform: translate(34px, 4px) rotate(0deg) translate(-34px, -4px) scale(1, 0.12);
  }
}

/* Струя на мгновение прерывается, пока форма наклонена */
@keyframes stream-pour {
  0%,
  68% {
    opacity: 1;
  }
  71%,
  83% {
    opacity: 0;
  }
  86.5%,
  100% {
    opacity: 1;
  }
}

.stream-run {
  animation: stream-flow calc(var(--coin-interval, 1.6s) * 0.08) linear infinite;
}

@keyframes stream-flow {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(0, 10px, 0);
  }
}

.blank {
  animation: blank-ride calc(var(--coin-interval, 1.6s) * var(--coin-count, 6)) linear infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * var(--i, 0) * -1);
}

@keyframes blank-ride {
  0% {
    opacity: 1;
    transform: translate3d(0, 0, 0) rotate(0deg);
  }
  2.5% {
    transform: translate3d(37px, 5px, 0) rotate(120deg);
    animation-timing-function: ease-in;
  }
  5% {
    transform: translate3d(52px, 21px, 0) rotate(180deg);
    animation-timing-function: linear;
  }
  /* край ленты (x=440): срывается и падает в трубу по дуге — та же горизонтальная скорость
     (6.8px на 1% цикла), вертикально y ∝ t² (0 → 3 → 13 → 29 → 51) */
  44.7% {
    transform: translate3d(322px, 21px, 0) rotate(180deg);
  }
  47.09% {
    transform: translate3d(338.25px, 24.19px, 0) rotate(180deg);
  }
  49.48% {
    transform: translate3d(354.5px, 33.75px, 0) rotate(180deg);
  }
  51.87% {
    transform: translate3d(370.75px, 49.69px, 0) rotate(180deg);
  }
  54.26% {
    opacity: 1;
    transform: translate3d(387px, 72px, 0) rotate(180deg);
  }
  54.76%,
  100% {
    opacity: 0;
    transform: translate3d(387px, 72px, 0) rotate(180deg);
  }
}

/* === Сцена упаковки, координаты 600×190 ===
   Слева направо: труба сверху (центр x=95, здесь короче — до y=30) — под ней площадка
   x=40…150, верх y=124, на ней открытый чемоданчик x=63…127, y=94…124 — толкатель слева —
   лента x=150…450, верх y=124 — труба вниз (шире обычной, чемоданчик шире монеты):
   корпус x=460…548, раструб x=450…558, y=144. Дуга падения подобрана так, что задний нижний
   угол чемоданчика опускается ниже раструба, только когда уже прошёл его левый край.
   Монета 34×34: в покое её центр в трубе (95, 13), падает в чемоданчик до (95, 111). */
.scene-packing .feed-pipe {
  height: 40px;
}

.scene-packing .out-pipe {
  left: 460px;
  width: 88px;
}

/* Монета с логотипом, приходит от чеканки */
.pcoin {
  position: absolute;
  left: 78px;
  top: -4px;
  width: 34px;
  height: 34px;
  pointer-events: none;
}

.pcoin.idle {
  display: none;
}

.pcoin-face {
  position: absolute;
  left: 0;
  top: 0;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background:
    url("/coin-logo.svg") center / 68% no-repeat,
    radial-gradient(circle at 35% 30%, var(--gold-light) 0%, var(--gold) 45%, var(--gold-dark) 100%);
  border: 3px solid var(--gold-dark);
}

/* Площадка под трубой */
.platform {
  position: absolute;
  left: 40px;
  top: 124px;
  width: 110px;
  height: 10px;
  z-index: 3;
  border-radius: 3px;
  background: var(--field-border);
}

.platform::after {
  content: "";
  position: absolute;
  left: 42px;
  top: 10px;
  width: 12px;
  height: 56px;
  background: linear-gradient(90deg, var(--pipe-dark) 0%, var(--pipe-light) 45%, var(--pipe-mid) 100%);
}

/* Толкатель: пластина слева от чемоданчика, шток уходит в корпус у левого края сцены */
.pusher {
  position: absolute;
  left: 55px;
  top: 96px;
  width: 8px;
  height: 26px;
  z-index: 3;
  border-radius: 3px;
  background: var(--decor);
}

.pusher::before {
  content: "";
  position: absolute;
  right: 8px;
  top: 10px;
  width: 130px;
  height: 6px;
  background: var(--metal);
}

.pusher-housing {
  position: absolute;
  left: 0;
  top: 92px;
  width: 34px;
  height: 34px;
  z-index: 4;
  border-radius: 0 6px 6px 0;
  background: linear-gradient(180deg, var(--pipe-light) 0%, var(--pipe-mid) 100%);
  border: 2px solid var(--field-border);
  border-left: 0;
}

/* Чемоданчик: корпус (открытый сверху, внутри видна стопка) и крышка на петле слева.
   Уровень «Упаковщика» меняет вид — обводка по ступеням data-mint, как у других этапов */
.case {
  position: absolute;
  width: 64px;
  height: 30px;
  transform-origin: 50% 100%;
}

.case.on-platform {
  left: 63px;
  top: 94px;
  z-index: 3;
}

.case-box {
  position: absolute;
  left: 0;
  top: 0;
  width: 64px;
  height: 30px;
  overflow: hidden;
  border-radius: 0 0 6px 6px;
  background: var(--well);
  border: 4px solid var(--mint-rim, var(--field-border));
  border-top: 0;
  box-shadow: var(--mint-glow, 0 0 0 transparent);
}

/* Монеты в стопке — вид сбоку, плоские; снизу вверх */
.case-disc {
  position: absolute;
  left: 6px;
  width: 44px;
  height: 8px;
  border-radius: 4px;
  background: linear-gradient(180deg, var(--gold-light) 0%, var(--gold) 45%, var(--gold-dark) 100%);
  border: 1px solid var(--gold-dark);
}

.case-disc.d0 {
  bottom: 0;
}

.case-disc.d1 {
  bottom: 8px;
}

.case-disc.d2 {
  bottom: 16px;
}

.case-lid {
  position: absolute;
  left: 0;
  top: -6px;
  width: 64px;
  height: 7px;
  transform-origin: 3px 50%;
  border-radius: 3px;
  background: linear-gradient(180deg, var(--decor-deep-2) 0%, var(--decor-deep) 100%);
  border: 2px solid var(--mint-rim, var(--field-border));
}

/* ручка на крышке — точно по центру: left считается от внутренней области крышки (без рамок),
   её половина минус половина ширины ручки. box-sizing явно: псевдоэлементы не попадают под «*» */
.case-lid::after {
  content: "";
  position: absolute;
  box-sizing: border-box;
  left: calc(50% - 9px);
  top: -7px;
  width: 18px;
  height: 8px;
  border: 2px solid var(--mint-rim, var(--field-border));
  border-bottom: 0;
  border-radius: 4px 4px 0 0;
}

/* Крышка чемоданчика на площадке — отдельный элемент поверх него, не внутри: вложенная в движущийся
   чемоданчик анимация крышки (transform в transform) заставляла Safari делать отдельными слоями все
   блоки завода ниже, а такие слои за экраном он не рисует до прокрутки.
   Стоит там же, где стояла бы в чемоданчике (63 + 0, 94 − 6). Центр преобразования — центр чемоданчика
   снизу (32, 30) в координатах крышки: (32, 36); петля крышки (3, 3.5) — это (−29, −32.5) от него */
.case-lid.platform-lid {
  left: 63px;
  top: 88px;
  z-index: 3;
  transform-origin: 32px 36px;
}

.factory.lite .case-box {
  box-shadow: none;
}

/* Чемоданчик на ленте: всегда закрыт, стопка внутри. В покое стоит в начале ленты —
   ровно там, куда его сталкивает толкатель (x=155…219) */
.shipped-layer {
  position: absolute;
  left: 0;
  top: 0;
  width: 600px;
  height: 190px;
  z-index: 2;
}

.case.shipped {
  left: 155px;
  top: 94px;
  /* при падении чуть наклоняется вокруг своего центра */
  transform-origin: 50% 50%;
}

/* --- Движение упаковки ---
   Монеты падают с периодом --coin-interval (u). Цикл чемоданчика — ровно 3u (три монеты).
   Все его фазы — части одного цикла с одной фазой --case-phase: чемоданчик на площадке,
   крышка, стопка, толкатель, чемоданчик на ленте. Цикл кратен периоду монет — всё совпадает
   само, без подсчёта монет в коде.
   Доли цикла чемоданчика (3u):
     0%, 33.3%, 66.7%  падает монета — в стопке появляется очередной слой
     70–76.7%          крышка закрывается
     76.7–83.3%        толкатель выдвигается и сталкивает чемоданчик на ленту (92px);
                       в 83.3% он передаётся чемоданчику на ленте — то же место
     83.3–90%          толкатель возвращается
     90–96.7%          под трубой появляется новый открытый чемоданчик (0.6 → 1)
     83.3% → 36.7% следующего цикла  чемоданчик едет по ленте до её края (263px)
     36.7–48.1%        срывается с края и падает в трубу по дуге, чуть наклоняясь, исчезает
   Монеты: цикл u × count, монета i с задержкой -(i × u): 0–5% падает из трубы в чемоданчик.
   Фаза чемоданчика: монета падает в 5% своего цикла, слой появляется в начале доли u:
     0.05 × count, дробная часть − 1:  count 6 → 0.3 → −0.7;  count 3 → 0.15 → −0.85.
   Лента идёт со скоростью чемоданчика: 263px за 1.6u → тайл 20px за 0.1217u */
.factory-line {
  --case-phase: -0.7;
}

.factory.lite .factory-line {
  --case-phase: -0.85;
}

.scene-packing .conveyor {
  --belt-tile-time: calc(var(--coin-interval, 1.6s) * 0.1217);
}

.pcoin {
  animation: pcoin-drop calc(var(--coin-interval, 1.6s) * var(--coin-count, 6)) linear infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * var(--i, 0) * -1);
}

/* Из трубы прямо вниз в чемоданчик: оба конца спрятаны (в трубе и за стенкой чемоданчика) */
@keyframes pcoin-drop {
  0% {
    opacity: 1;
    transform: translate3d(0, 0, 0);
    animation-timing-function: ease-in;
  }
  5% {
    opacity: 1;
    transform: translate3d(0, 98px, 0);
  }
  5.05%,
  100% {
    opacity: 0;
    transform: translate3d(0, 98px, 0);
  }
}

.case.on-platform,
.platform-lid,
.case.on-platform .case-disc,
.pusher,
.case.shipped {
  animation-duration: calc(var(--coin-interval, 1.6s) * 3);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * var(--case-phase));
}

.case.on-platform {
  animation-name: case-platform;
}

.platform-lid {
  animation-name: platform-lid;
}

.case.on-platform .case-disc.d0 {
  animation-name: case-disc-0;
}

.case.on-platform .case-disc.d1 {
  animation-name: case-disc-1;
}

.case.on-platform .case-disc.d2 {
  animation-name: case-disc-2;
}

.pusher {
  animation-name: pusher-push;
}

.case.shipped {
  animation-name: case-ride;
}

@keyframes case-platform {
  0%,
  76.7% {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
    animation-timing-function: ease-in-out;
  }
  83.3% {
    opacity: 1;
    transform: translate3d(92px, 0, 0) scale(1);
  }
  /* гаснет одновременно с появлением такого же чемоданчика на ленте */
  83.35% {
    opacity: 0;
    transform: translate3d(92px, 0, 0) scale(1);
  }
  90% {
    opacity: 0;
    transform: translate3d(0, 0, 0) scale(0.6);
    animation-timing-function: ease-out;
  }
  96.7%,
  100% {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
  }
}

/* Крышка: движение чемоданчика (case-platform: сдвиг толкателем, исчезновение, рост 0.6 → 1)
   и её собственный поворот на петле — в одной анимации. Список функций во всех кадрах один:
   translate3d(сдвиг) scale(рост) translate(к петле) rotate(угол) translate(обратно) — поэтому
   браузер интерполирует их попарно, ровно как раньше вложенные слои. Кривые времени те же:
   70–76.7% закрывается (ease-in), 76.7–83.3% едет с чемоданчиком (ease-in-out), 90–96.7% растёт
   вместе с новым (ease-out). Сбрасывается в 83.4–83.5%, когда чемоданчик уже невидим */
@keyframes platform-lid {
  0%,
  70% {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1) translate(-29px, -32.5px) rotate(-120deg) translate(29px, 32.5px);
    animation-timing-function: ease-in;
  }
  76.7% {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1) translate(-29px, -32.5px) rotate(0deg) translate(29px, 32.5px);
    animation-timing-function: ease-in-out;
  }
  83.3% {
    opacity: 1;
    transform: translate3d(92px, 0, 0) scale(1) translate(-29px, -32.5px) rotate(0deg) translate(29px, 32.5px);
  }
  83.35% {
    opacity: 0;
    transform: translate3d(92px, 0, 0) scale(1) translate(-29px, -32.5px) rotate(0deg) translate(29px, 32.5px);
  }
  83.4% {
    opacity: 0;
    transform: translate3d(91.31px, 0, 0) scale(0.997) translate(-29px, -32.5px) rotate(0deg) translate(29px, 32.5px);
  }
  83.5% {
    opacity: 0;
    transform: translate3d(89.93px, 0, 0) scale(0.991) translate(-29px, -32.5px) rotate(-120deg) translate(29px, 32.5px);
  }
  90% {
    opacity: 0;
    transform: translate3d(0, 0, 0) scale(0.6) translate(-29px, -32.5px) rotate(-120deg) translate(29px, 32.5px);
    animation-timing-function: ease-out;
  }
  96.7%,
  100% {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1) translate(-29px, -32.5px) rotate(-120deg) translate(29px, 32.5px);
  }
}

@keyframes case-disc-0 {
  0%,
  83.4% {
    opacity: 1;
  }
  83.5%,
  99.9% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

@keyframes case-disc-1 {
  0%,
  33.32% {
    opacity: 0;
  }
  33.34%,
  83.4% {
    opacity: 1;
  }
  83.5%,
  100% {
    opacity: 0;
  }
}

@keyframes case-disc-2 {
  0%,
  66.65% {
    opacity: 0;
  }
  66.67%,
  83.4% {
    opacity: 1;
  }
  83.5%,
  100% {
    opacity: 0;
  }
}

@keyframes pusher-push {
  0%,
  76.7% {
    transform: translate3d(0, 0, 0);
    animation-timing-function: ease-in-out;
  }
  83.3% {
    transform: translate3d(92px, 0, 0);
    animation-timing-function: ease-in-out;
  }
  90%,
  100% {
    transform: translate3d(0, 0, 0);
  }
}

/* Проезд и падение. Горизонтально — одна скорость (≈4.93px на 1% цикла) и на ленте, и в
   падении; вертикально в падении y ∝ t² (0 → 4 → 15 → 34 → 60) через равные промежутки */
@keyframes case-ride {
  0% {
    opacity: 1;
    transform: translate3d(82.1px, 0, 0) rotate(0deg);
  }
  36.7% {
    transform: translate3d(263px, 0, 0) rotate(0deg);
  }
  39.55% {
    transform: translate3d(277.05px, 3.76px, 0) rotate(3.5deg);
  }
  42.4% {
    transform: translate3d(291.1px, 15.02px, 0) rotate(7deg);
  }
  45.25% {
    transform: translate3d(305.14px, 33.8px, 0) rotate(10.5deg);
  }
  /* здесь чемоданчик целиком за трубой — гаснет невидимо */
  48.1% {
    opacity: 1;
    transform: translate3d(319.19px, 60.09px, 0) rotate(14deg);
  }
  48.6%,
  83.25% {
    opacity: 0;
    transform: translate3d(319.19px, 60.09px, 0) rotate(14deg);
  }
  83.3% {
    opacity: 0;
    transform: translate3d(0, 0, 0) rotate(0deg);
  }
  83.35% {
    opacity: 1;
    transform: translate3d(0, 0, 0) rotate(0deg);
  }
  100% {
    opacity: 1;
    transform: translate3d(82.1px, 0, 0) rotate(0deg);
  }
}

/* === Сцена транспорта, координаты 600×190 ===
   Общее правило завода: вход сверху слева, выход вниз справа.
   Слева направо: труба сверху (x=74…146, центр 110, низ y=24) — под ней место погрузки: вагонетка
   на рельсах (x=74…146, колёса на рельсах y=110) — рельсы до x≈340, дальше загиб вниз и обрыв у x=364
   (земля и край обрыва x=344) — тёмная пропасть x=364…441 — труба вниз (корпус x=451…559,
   раструб x=441…569, y=146).
   Вагонеток две, они чередуются: пока одна едет к обрыву, вторая стоит под трубой и грузится.
   Чемоданчик 44×19 (крышка с ручкой, корпус 15). В покое спрятан в трубе (верх y=4), падает прямо
   вниз в вагонетку. Стопка из трёх: верх чемоданов y=76, 57, 38 (корпуса 80, 61, 42),
   дно кузова изнутри y=95, верх ручки верхнего чемодана y=34 */
.scene-transport .feed-pipe {
  left: 74px;
  width: 72px;
  height: 34px;
}

.scene-transport .out-pipe {
  left: 451px;
  width: 108px;
  z-index: 4;
}

/* Земля под рельсами до края обрыва. Выше вагонетки по слоям: сорвавшаяся вагонетка уходит за край */
.ground {
  position: absolute;
  left: 0;
  top: 122px;
  width: 344px;
  height: 68px;
  z-index: 4;
  background: linear-gradient(180deg, var(--night-700) 0%, var(--night-830) 100%);
  border-right: 2px solid var(--field-border);
  border-radius: 0 0 4px 0;
}

/* Пропасть за обрывом — темнеет книзу */
.abyss {
  position: absolute;
  left: 344px;
  top: 110px;
  width: 256px;
  height: 80px;
  background: linear-gradient(180deg, rgba(var(--ink-rgb), 0) 0%, rgba(var(--ink-rgb), 0.95) 100%);
}

/* Рельсы: полоса со шпалами до x=340, дальше плавный загиб вниз (дуга) и обрыв на y≈138 */
.rails {
  position: absolute;
  left: 0;
  top: 110px;
  width: 340px;
  height: 12px;
  z-index: 1;
  background:
    linear-gradient(var(--metal), var(--metal)) top / 100% 4px no-repeat,
    repeating-linear-gradient(90deg, var(--field-border) 0 10px, transparent 10px 26px) bottom / 100% 8px no-repeat;
}

.rails-bend {
  position: absolute;
  left: 320px;
  top: 110px;
  width: 44px;
  height: 28px;
  z-index: 1;
  border-top: 4px solid var(--metal);
  border-right: 4px solid var(--metal);
  border-top-right-radius: 26px;
}

/* Чемоданчик из трубы (пул app.js) */
.tcase {
  position: absolute;
  left: 88px;
  top: 4px;
  width: 44px;
  height: 19px;
  pointer-events: none;
}

.tcase.idle {
  display: none;
}

/* Закрытый чемоданчик — один вид и в полёте из трубы, и в вагонетке: корпус, крышка, ручка по центру */
.tcase-face,
.cart-load {
  position: absolute;
  left: 0;
  top: 4px;
  width: 44px;
  height: 15px;
  border: 3px solid var(--mint-rim, var(--decor));
  border-top: 0;
  border-radius: 0 0 4px 4px;
  background: linear-gradient(180deg, var(--night-650) 0%, var(--well) 100%);
  box-shadow: var(--mint-glow, 0 0 0 transparent);
}

/* Псевдоэлементы не попадают под общее правило «* { box-sizing: border-box }» — задаём явно,
   иначе рамка прибавляется к ширине и крышка выпирает за корпус */
.tcase-face::before,
.cart-load::before {
  content: "";
  position: absolute;
  box-sizing: border-box;
  left: -3px;
  top: -4px;
  width: 44px;
  height: 9px;
  border-radius: 3px;
  background: linear-gradient(180deg, var(--decor-deep-2) 0%, var(--decor-deep) 100%);
  border: 2px solid var(--mint-rim, var(--decor));
}

.tcase-face::after,
.cart-load::after {
  content: "";
  position: absolute;
  box-sizing: border-box;
  left: calc(50% - 8px);
  top: -8px;
  width: 16px;
  height: 7px;
  border: 2px solid var(--mint-rim, var(--decor));
  border-bottom: 0;
  border-radius: 4px 4px 0 0;
}

/* Монета внутри чемоданчика: одна золотая, по бокам остаётся тёмный зазор (внутренность корпуса x=3…41,
   монета x=7…37). Тонкая: крышка закрывает верх корпуса до y=5, нижняя рамка с y=12 — монета y=6…11,
   с зазором 1px от обеих, на текстуры не заходит. У чемоданчика из трубы — второй слой .tcase-face.gold (пул app.js
   по data-faces; не «coin» — этот класс занят монетой чеканки с её анимацией), у груза в вагонетке —
   .case-coin внутри яруса */
.tcase-face.gold,
.case-coin {
  position: absolute;
  box-sizing: border-box;
  width: 30px;
  height: 5px;
  border: 1px solid var(--gold-dark);
  border-radius: 3px;
  background: linear-gradient(180deg, var(--gold-light) 0%, var(--gold) 45%, var(--gold-dark) 100%);
  box-shadow: none;
}

/* у чемоданчика из трубы корпус начинается на 4px ниже верха элемента */
.tcase-face.gold {
  left: 7px;
  top: 10px;
}

.tcase-face.gold::before,
.tcase-face.gold::after {
  content: none;
}

/* от внутреннего края яруса (он сам — корпус, рамка слева 3px) */
.case-coin {
  left: 4px;
  top: 6px;
}

/* Груз вагонетки: один слой, у обрыва летит дальше по дуге (кадры load-move).
   Стоит в вагонетке за передней стенкой кузова (вагонетка выше по слоям). У каждой вагонетки свой груз */
.load {
  position: absolute;
  left: 88px;
  top: 0;
  width: 44px;
  height: 190px;
  z-index: 2;
}

/* сам ярус — это корпус (крышка и ручка выше него): корпус на 4px ниже верха чемоданчика из трубы */
.cart-load.l0 {
  top: 80px;
}

.cart-load.l1 {
  top: 61px;
}

.cart-load.l2 {
  top: 42px;
}

/* Вагонетка: один слой с одной анимацией (cart-move) — вперёд, вниз с появлением (масштаб от середины
   низа) и наклон у обрыва (поворот вокруг переднего нижнего угла). Центр преобразования — середина
   низа (36, 36); наклон — через translate(36px) rotate translate(−36px), то есть вокруг угла (72, 36) */
.cart {
  position: absolute;
  left: 74px;
  top: 74px;
  width: 72px;
  height: 36px;
  z-index: 3;
  transform-origin: 36px 36px;
}

.cart-body {
  position: absolute;
  left: 0;
  top: 8px;
  width: 72px;
  height: 16px;
  border-radius: 2px 2px 6px 6px;
  background: linear-gradient(180deg, var(--pipe-light) 0%, var(--pipe-mid) 100%);
  border: 3px solid var(--decor);
}

.cart-wheel {
  position: absolute;
  top: 22px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--well);
  border: 3px solid var(--metal);
}

.cart-wheel.w0 {
  left: 10px;
}

.cart-wheel.w1 {
  left: 48px;
}

/* --- Движение транспорта ---
   Чемоданчики падают из трубы с периодом --coin-interval (T). Вагонеток две; цикл каждой — 6T:
   первая половина — стоит под трубой и принимает три чемодана, вторая — едет к обрыву, срывается,
   невидимой возвращается на старт. Вторая вагонетка — та же анимация со сдвигом на 3T (класс b).
   Всё — одна анимация с периодом 6T и фазой --cart-phase; в коде ничего не считается.
   Доли цикла вагонетки (6T, T = 16.67%):
     12 → 14.5%          появляется под трубой (0.6 → 1): предыдущая уже отъехала на 80px и место свободно
     16.17%, 32.83%, 49.5%   падает чемоданчик — появляется очередной ярус (посадка на 0.97T от начала окна,
                         фаза падения сдвинута: зазор между чемоданами всегда T, и уехавшая вагонетка
                         должна успеть освободить место до первого чемодана следующей)
     50 → 69.81%         трогается и равноускоренно разгоняется: x = 218·t² (к 62% отъехала на 80px)
     69.81%              перед у края обрыва (x=364), скорость 22 px на 1% цикла
     69.81 → 79.07%      чемоданы по инерции: вперёд равномерно с той же скоростью (+203.9), вниз
                         y = 113·t² — парабола через пропасть в трубу, уходят за её стенку
     69.81 → 74.5%       вагонетка срывается в пропасть: вниз с ускорением, клюёт носом, гаснет в темноте
     74.6 → 112% (след. цикл)  невидимой стоит на старте и ждёт своего окна
   Чемоданчик из трубы: цикл T × count, чемоданчик i с задержкой −(i × T), касается груза в 7% цикла.
   На какой ярус он падает — по номеру элемента: i = 0, 3 — ярус 0, i = 2, 5 — ярус 1, i = 1, 4 — ярус 2.
   Фаза: чемоданчик 0 касается в 0.07 × count × T — это 0.97T от начала окна погрузки:
     count 6 → 0.42 − 0.97 = −0.55;  count 3 → 0.21 − 0.97 = −0.76.
   Кривая cubic-bezier(0.333, 0, 0.667, 0.333) — это ровно t² (равноускоренное движение из покоя) */
.factory-line {
  --cart-phase: -0.55;
}

.factory.lite .factory-line {
  --cart-phase: -0.76;
}

.tcase {
  animation: tcase-drop-0 calc(var(--coin-interval, 1.6s) * var(--coin-count, 6)) linear infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * var(--i, 0) * -1);
}

.tcase:nth-child(3n + 2) {
  animation-name: tcase-drop-2;
}

.tcase:nth-child(3n + 3) {
  animation-name: tcase-drop-1;
}

/* Падение по параболе (t²). Старт у яруса повыше позже: время ∝ √высоты, касание у всех в 7% */
@keyframes tcase-drop-0 {
  0% {
    opacity: 1;
    transform: translate3d(0, 0, 0);
    animation-timing-function: cubic-bezier(0.333, 0, 0.667, 0.333);
  }
  7% {
    opacity: 1;
    transform: translate3d(0, 72px, 0);
  }
  7.05%,
  100% {
    opacity: 0;
    transform: translate3d(0, 72px, 0);
  }
}

@keyframes tcase-drop-1 {
  0%,
  0.99% {
    opacity: 1;
    transform: translate3d(0, 0, 0);
    animation-timing-function: cubic-bezier(0.333, 0, 0.667, 0.333);
  }
  7% {
    opacity: 1;
    transform: translate3d(0, 53px, 0);
  }
  7.05%,
  100% {
    opacity: 0;
    transform: translate3d(0, 53px, 0);
  }
}

@keyframes tcase-drop-2 {
  0%,
  2.19% {
    opacity: 1;
    transform: translate3d(0, 0, 0);
    animation-timing-function: cubic-bezier(0.333, 0, 0.667, 0.333);
  }
  7% {
    opacity: 1;
    transform: translate3d(0, 34px, 0);
  }
  7.05%,
  100% {
    opacity: 0;
    transform: translate3d(0, 34px, 0);
  }
}

.cart,
.load,
.cart-load {
  animation-duration: calc(var(--coin-interval, 1.6s) * 6);
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-delay: calc(var(--coin-interval, 1.6s) * var(--cart-phase));
}

/* Вторая вагонетка и её груз — та же анимация, сдвиг на полцикла (3T) */
.cart.b,
.load.b,
.load.b .cart-load {
  animation-delay: calc(var(--coin-interval, 1.6s) * (var(--cart-phase) - 3));
}

.cart {
  animation-name: cart-move;
}

.load {
  animation-name: load-move;
}

.cart-load.l0 {
  animation-name: cart-load-0;
}

.cart-load.l1 {
  animation-name: cart-load-1;
}

.cart-load.l2 {
  animation-name: cart-load-2;
}

/* Вагонетка и груз — по одной анимации на слой (раньше: вложенные слои cart-x ⊃ cart-y ⊃ cart-tilt
   и load-x ⊃ load-y). Вложенные анимации transform нельзя: WebKit теряет границы движения, выключает
   проверку перекрытий и делает отдельным слоем каждый блок завода ниже — такие слои Safari за экраном
   не рисует, этапы «появлялись» только при прокрутке.
   Кадры посчитаны из прежних (scratchpad flatten_anims.py): где меняется одна ось — её точная кривая
   времени, где оси меняются по-разному (срыв с обрыва) — точки через 0.2% цикла. Отклонение от прежней
   траектории — не больше 0.06px сцены.
   Вагонетка: стоит невидимой → появляется с ростом (12–14.5%) → стоит и принимает груз → разгон t²
   до края (50–69.81%) → срыв в пропасть с наклоном и в темноту (69.81–74.5%) → невидимо на старт.
   Груз: едет с вагонеткой, с края летит по инерции по дуге (69.81–79.07%) и уходит за трубу */
@keyframes cart-move {
  0% {
    opacity: 0;
    transform: translate3d(0px, 0px, 0) scale(0.6) translate(36px, 0) rotate(0deg) translate(-36px, 0);
  }
  12% {
    opacity: 0;
    transform: translate3d(0px, 0px, 0) scale(0.6) translate(36px, 0) rotate(0deg) translate(-36px, 0);
    animation-timing-function: ease-out;
  }
  14.5% {
    opacity: 1;
    transform: translate3d(0px, 0px, 0) scale(1) translate(36px, 0) rotate(0deg) translate(-36px, 0);
  }
  50% {
    opacity: 1;
    transform: translate3d(0px, 0px, 0) scale(1) translate(36px, 0) rotate(0deg) translate(-36px, 0);
    animation-timing-function: cubic-bezier(0.333, 0, 0.667, 0.333);
  }
  69.81% {
    opacity: 1;
    transform: translate3d(218px, 0px, 0) scale(1) translate(36px, 0) rotate(0deg) translate(-36px, 0);
  }
  70.009% {
    opacity: 1;
    transform: translate3d(219.28px, 0.22px, 0) scale(1) translate(36px, 0) rotate(0.147deg) translate(-36px, 0);
  }
  70.209% {
    opacity: 1;
    transform: translate3d(220.55px, 0.87px, 0) scale(1) translate(36px, 0) rotate(0.562deg) translate(-36px, 0);
  }
  70.408% {
    opacity: 1;
    transform: translate3d(221.83px, 1.95px, 0) scale(1) translate(36px, 0) rotate(1.214deg) translate(-36px, 0);
  }
  70.608% {
    opacity: 1;
    transform: translate3d(223.1px, 3.47px, 0) scale(1) translate(36px, 0) rotate(2.078deg) translate(-36px, 0);
  }
  70.807% {
    opacity: 1;
    transform: translate3d(224.38px, 5.42px, 0) scale(1) translate(36px, 0) rotate(3.133deg) translate(-36px, 0);
  }
  71.006% {
    opacity: 1;
    transform: translate3d(225.65px, 7.8px, 0) scale(1) translate(36px, 0) rotate(4.361deg) translate(-36px, 0);
  }
  71.206% {
    opacity: 1;
    transform: translate3d(226.93px, 10.62px, 0) scale(1) translate(36px, 0) rotate(5.747deg) translate(-36px, 0);
  }
  71.405% {
    opacity: 1;
    transform: translate3d(228.2px, 13.87px, 0) scale(1) translate(36px, 0) rotate(7.28deg) translate(-36px, 0);
  }
  71.604% {
    opacity: 1;
    transform: translate3d(229.48px, 17.55px, 0) scale(1) translate(36px, 0) rotate(8.949deg) translate(-36px, 0);
  }
  71.804% {
    opacity: 1;
    transform: translate3d(230.75px, 21.67px, 0) scale(1) translate(36px, 0) rotate(10.744deg) translate(-36px, 0);
  }
  72.003% {
    opacity: 1;
    transform: translate3d(232.03px, 26.22px, 0) scale(1) translate(36px, 0) rotate(12.658deg) translate(-36px, 0);
  }
  72.203% {
    opacity: 1;
    transform: translate3d(233.3px, 31.2px, 0) scale(1) translate(36px, 0) rotate(14.684deg) translate(-36px, 0);
  }
  72.402% {
    opacity: 1;
    transform: translate3d(234.58px, 36.62px, 0) scale(1) translate(36px, 0) rotate(16.816deg) translate(-36px, 0);
  }
  72.601% {
    opacity: 1;
    transform: translate3d(235.85px, 42.48px, 0) scale(1) translate(36px, 0) rotate(19.049deg) translate(-36px, 0);
  }
  72.801% {
    opacity: 1;
    transform: translate3d(237.13px, 48.77px, 0) scale(1) translate(36px, 0) rotate(21.38deg) translate(-36px, 0);
  }
  73% {
    opacity: 1;
    transform: translate3d(238.41px, 55.5px, 0) scale(1) translate(36px, 0) rotate(23.804deg) translate(-36px, 0);
  }
  73.188% {
    opacity: 0.875;
    transform: translate3d(239.6px, 63.56px, 0) scale(1) translate(36px, 0) rotate(26.168deg) translate(-36px, 0);
  }
  73.375% {
    opacity: 0.75;
    transform: translate3d(240.8px, 71.63px, 0) scale(1) translate(36px, 0) rotate(28.61deg) translate(-36px, 0);
  }
  73.562% {
    opacity: 0.625;
    transform: translate3d(242px, 79.69px, 0) scale(1) translate(36px, 0) rotate(31.13deg) translate(-36px, 0);
  }
  73.75% {
    opacity: 0.5;
    transform: translate3d(243.2px, 87.75px, 0) scale(1) translate(36px, 0) rotate(33.729deg) translate(-36px, 0);
  }
  73.938% {
    opacity: 0.375;
    transform: translate3d(244.4px, 95.81px, 0) scale(1) translate(36px, 0) rotate(36.408deg) translate(-36px, 0);
  }
  74.125% {
    opacity: 0.25;
    transform: translate3d(245.6px, 103.87px, 0) scale(1) translate(36px, 0) rotate(39.17deg) translate(-36px, 0);
  }
  74.312% {
    opacity: 0.125;
    transform: translate3d(246.8px, 111.94px, 0) scale(1) translate(36px, 0) rotate(42.024deg) translate(-36px, 0);
  }
  74.5% {
    opacity: 0;
    transform: translate3d(248px, 120px, 0) scale(1) translate(36px, 0) rotate(45deg) translate(-36px, 0);
  }
  74.6% {
    opacity: 0;
    transform: translate3d(0px, 0px, 0) scale(0.6) translate(36px, 0) rotate(0deg) translate(-36px, 0);
  }
  100% {
    opacity: 0;
    transform: translate3d(0px, 0px, 0) scale(0.6) translate(36px, 0) rotate(0deg) translate(-36px, 0);
  }
}

@keyframes load-move {
  0% {
    transform: translate3d(0px, 0px, 0);
  }
  50% {
    transform: translate3d(0px, 0px, 0);
    animation-timing-function: cubic-bezier(0.333, 0, 0.667, 0.333);
  }
  69.81% {
    transform: translate3d(218px, 0px, 0);
  }
  70.007% {
    transform: translate3d(222.34px, 0.05px, 0);
  }
  70.204% {
    transform: translate3d(226.68px, 0.2px, 0);
  }
  70.401% {
    transform: translate3d(231.01px, 0.46px, 0);
  }
  70.598% {
    transform: translate3d(235.35px, 0.82px, 0);
  }
  70.795% {
    transform: translate3d(239.69px, 1.28px, 0);
  }
  70.992% {
    transform: translate3d(244.03px, 1.84px, 0);
  }
  71.189% {
    transform: translate3d(248.37px, 2.51px, 0);
  }
  71.386% {
    transform: translate3d(252.71px, 3.27px, 0);
  }
  71.583% {
    transform: translate3d(257.04px, 4.14px, 0);
  }
  71.78% {
    transform: translate3d(261.38px, 5.12px, 0);
  }
  71.977% {
    transform: translate3d(265.72px, 6.19px, 0);
  }
  72.174% {
    transform: translate3d(270.06px, 7.37px, 0);
  }
  72.371% {
    transform: translate3d(274.4px, 8.64px, 0);
  }
  72.568% {
    transform: translate3d(278.74px, 10.02px, 0);
  }
  72.765% {
    transform: translate3d(283.07px, 11.51px, 0);
  }
  72.962% {
    transform: translate3d(287.41px, 13.09px, 0);
  }
  73.159% {
    transform: translate3d(291.75px, 14.78px, 0);
  }
  73.356% {
    transform: translate3d(296.09px, 16.57px, 0);
  }
  73.553% {
    transform: translate3d(300.43px, 18.46px, 0);
  }
  73.75% {
    transform: translate3d(304.77px, 20.45px, 0);
  }
  73.947% {
    transform: translate3d(309.1px, 22.55px, 0);
  }
  74.144% {
    transform: translate3d(313.44px, 24.75px, 0);
  }
  74.341% {
    transform: translate3d(317.78px, 27.05px, 0);
  }
  74.539% {
    transform: translate3d(322.12px, 29.45px, 0);
  }
  74.736% {
    transform: translate3d(326.46px, 31.95px, 0);
  }
  74.933% {
    transform: translate3d(330.8px, 34.56px, 0);
  }
  75.13% {
    transform: translate3d(335.13px, 37.27px, 0);
  }
  75.327% {
    transform: translate3d(339.47px, 40.08px, 0);
  }
  75.524% {
    transform: translate3d(343.81px, 43px, 0);
  }
  75.721% {
    transform: translate3d(348.15px, 46.01px, 0);
  }
  75.918% {
    transform: translate3d(352.49px, 49.13px, 0);
  }
  76.115% {
    transform: translate3d(356.83px, 52.35px, 0);
  }
  76.312% {
    transform: translate3d(361.16px, 55.68px, 0);
  }
  76.509% {
    transform: translate3d(365.5px, 59.1px, 0);
  }
  76.706% {
    transform: translate3d(369.84px, 62.63px, 0);
  }
  76.903% {
    transform: translate3d(374.18px, 66.26px, 0);
  }
  77.1% {
    transform: translate3d(378.52px, 70px, 0);
  }
  77.297% {
    transform: translate3d(382.86px, 73.84px, 0);
  }
  77.494% {
    transform: translate3d(387.19px, 77.78px, 0);
  }
  77.691% {
    transform: translate3d(391.53px, 81.82px, 0);
  }
  77.888% {
    transform: translate3d(395.87px, 85.96px, 0);
  }
  78.085% {
    transform: translate3d(400.21px, 90.21px, 0);
  }
  78.282% {
    transform: translate3d(404.55px, 94.56px, 0);
  }
  78.479% {
    transform: translate3d(408.89px, 99.02px, 0);
  }
  78.676% {
    transform: translate3d(413.22px, 103.58px, 0);
  }
  78.873% {
    transform: translate3d(417.56px, 108.24px, 0);
  }
  79.07% {
    transform: translate3d(421.9px, 113px, 0);
  }
  79.2% {
    transform: translate3d(421.9px, 113px, 0);
  }
  79.25% {
    transform: translate3d(0px, 0px, 0);
  }
  100% {
    transform: translate3d(0px, 0px, 0);
  }
}

/* Ярусы груза: появляются в момент касания, гаснут, когда весь груз уже за трубой (79.07–79.12%) */
@keyframes cart-load-0 {
  0%,
  16.16% {
    opacity: 0;
  }
  16.17%,
  79.07% {
    opacity: 1;
  }
  79.12%,
  100% {
    opacity: 0;
  }
}

@keyframes cart-load-1 {
  0%,
  32.82% {
    opacity: 0;
  }
  32.84%,
  79.07% {
    opacity: 1;
  }
  79.12%,
  100% {
    opacity: 0;
  }
}

@keyframes cart-load-2 {
  0%,
  49.49% {
    opacity: 0;
  }
  49.5%,
  79.07% {
    opacity: 1;
  }
  79.12%,
  100% {
    opacity: 0;
  }
}

/* === Видимый прогресс уровней ===
   Каждые три уровня узла-«номинала» на сцене меняется что-то заметное — только статично,
   без новой анимации. По ступеням data-mint (изменения копятся):
     ур. 4 (ступень 2+)  механизм этапа с подсветкой
     ур. 7 (ступень 3+)  механизм массивнее
     ур. 10 (ступень 4)  обводка у изделия — общее правило --mint-rim / --mint-glow выше
   Механизм: чеканка — колпак пресса, упаковка — толкатель, транспорт — вагонетка.
   Размеры меняются так, что точки касания и сцепки в анимациях остаются на месте.
   Свечение (box-shadow) — статичное и только в обычном режиме, в лёгком его нет */

/* Чеканка: колпак */
.factory-line:not([data-mint="1"]) .press-cap {
  background: linear-gradient(180deg, var(--scene-highlight) 0%, var(--decor) 100%);
  border-color: var(--scene-highlight);
  box-shadow: 0 0 12px var(--scene-glow);
}

/* блик на куполе */
.factory-line:not([data-mint="1"]) .press-cap::before {
  content: "";
  position: absolute;
  left: 10px;
  top: 7px;
  width: 16px;
  height: 9px;
  border-radius: 50%;
  background: rgba(var(--white-rgb), 0.4);
}

/* массивнее: шире и толще, низ колпака на прежней высоте — монету накрывает так же */
.factory-line[data-mint="3"] .press-cap,
.factory-line[data-mint="4"] .press-cap {
  left: -5px;
  width: 68px;
  border-width: 3px;
}

.factory-line[data-mint="3"] .press-cap::after,
.factory-line[data-mint="4"] .press-cap::after {
  left: -9px;
  right: -9px;
  bottom: -3px;
  height: 10px;
}

.factory-line[data-mint="3"] .press-ram::before,
.factory-line[data-mint="4"] .press-ram::before {
  left: 22px;
  width: 14px;
}

/* Упаковка: толкатель */
.factory-line:not([data-mint="1"]) .pusher {
  background: linear-gradient(90deg, var(--decor) 0%, var(--scene-highlight) 100%);
  box-shadow: 0 0 10px var(--scene-glow);
}

.factory-line:not([data-mint="1"]) .pusher::before {
  background: var(--decor);
}

/* сигнальная лампа на корпусе толкателя */
.factory-line:not([data-mint="1"]) .pusher-housing::after {
  content: "";
  position: absolute;
  left: 9px;
  top: -9px;
  width: 12px;
  height: 8px;
  border-radius: 6px 6px 0 0;
  background: var(--gold);
  box-shadow: 0 0 8px rgba(var(--gold-rgb), 0.7);
}

/* массивнее: пластина толще и выше, шток толще; правый край пластины там же (x=63) */
.factory-line[data-mint="3"] .pusher,
.factory-line[data-mint="4"] .pusher {
  left: 50px;
  top: 94px;
  width: 13px;
  height: 30px;
}

.factory-line[data-mint="3"] .pusher::before,
.factory-line[data-mint="4"] .pusher::before {
  right: 13px;
  top: 10px;
  height: 10px;
}

/* Транспорт: вагонетка (узел «Погрузчик»). Ур. 4 — рама кузова светлая бирюзовая */
.factory-line:not([data-mint="1"]) .scene-transport .cart-body {
  border-color: var(--scene-highlight);
}

/* ур. 7 — массивнее: рама кузова толще (размер вагонетки и точки касания рельсов те же) */
.factory-line[data-mint="3"] .scene-transport .cart-body,
.factory-line[data-mint="4"] .scene-transport .cart-body {
  border-width: 4px;
}

.factory.lite .press-cap,
.factory.lite .pusher,
.factory.lite .pusher-housing::after {
  box-shadow: none !important;
}

/* Вкладка скрыта (класс paused на экране): анимации на паузе, при возврате продолжают с того же места.
   !important — потому что правила анимаций (например, потока в трубах) задают сокращённое
   animation с более точным селектором, а оно сбрасывает play-state обратно в running */
.factory.paused .stage *,
.factory.paused .pipe-link * {
  animation-play-state: paused !important;
}

/* Этап или труба за экраном (is-offscreen ставит IntersectionObserver): анимации сняты совсем —
   меньше слоёв и работы, этап стоит неподвижно и виден. На экране анимации запускаются заново —
   все элементы этапа в одном кадре, поэтому синхронизация внутри сцены та же.

   ВАЖНО для Safari (почему блоки завода «появлялись только при прокрутке»). В сценах нельзя вкладывать
   анимацию transform в элемент, который сам анимирует transform (например, крышка внутри едущего
   чемоданчика, ось Y внутри оси X). WebKit в таком случае считает границы движения неизвестными,
   выключает проверку перекрытий и делает отдельным композитным слоем КАЖДЫЙ блок страницы после
   этой сцены — трубы, закрытые этапы, силуэты. А композитные слои за пределами видимой области
   Safari не рисует (backing store отсоединён), пока до них не доскроллишь. Поэтому движения, которые
   складываются из нескольких, собраны в одну анимацию на одном элементе (platform-lid, cart-move,
   load-move, mold-fill-move). Вложенная анимация только opacity — можно */
.stage.is-offscreen *,
.pipe-link.is-offscreen * {
  animation: none !important;
}

/* Пользователь попросил меньше движения — сцены стоят, монеты на своих местах, трубы без потока */
@media (prefers-reduced-motion: reduce) {
  .factory .stage *,
  .factory .pipe-link * {
    animation-play-state: paused !important;
  }
}

.factory-hint {
  min-height: 18px;
  font-size: 13px;
}

/* пока сообщения нет — строки нет совсем, без пустого места */
.factory-hint:empty {
  display: none;
}

/* --- Экран завода: этапы друг под другом, экран прокручивается ---
   Открытый этап: заголовок, сцена, три карточки узлов. Закрытый: тёмный блок с силуэтом.
   Между этапами — трубы, после последнего — труба в темноту. Блоки строит app.js по списку
   этапов с сервера. */
.factory-stages {
  width: 100%;
  display: flex;
  flex-direction: column;
}

.stage {
  width: 100%;
  position: relative;
}

.stage.open {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Над сценой одна строка: доход этапа в час (колонка фиксированной ширины — самый длинный доход
   «1 530 000 000/ч» ≈93px) и шкала хранилища. Названия этапа нет. Колонки фиксированы, поэтому
   дорожка шкалы одинаковой длины у всех этапов */
.stage-head {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  align-items: center;
  column-gap: 8px;
  padding: 0 4px;
}

/* Шкала заполнения хранилища — справа от дохода в час.
   Заливка — transform: scaleX (от левого края); плавный переход на время до заполнения ставит app.js.
   Дорожка и подпись стоят на месте всегда: сетка из двух колонок — дорожка на всё остальное место
   (одинаковая у всех этапов), подпись в колонке фиксированной ширины справа. Текст подписи на ширину
   не влияет. Колонка 64px: дорожка доходит примерно до начала дохода в час над ней; самая длинная
   подпись («999,9 млрд») ≈61px, «Заполнено» ≈57px */
.stage-storage {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 64px;
  align-items: center;
  column-gap: 8px;
  min-width: 0;
}

.storage-bar {
  position: relative;
  height: 6px;
  overflow: hidden;
  border-radius: 3px;
  background: var(--well);
  border: 1px solid var(--field-border);
}

.storage-fill {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  border-radius: 3px;
  background: linear-gradient(90deg, var(--decor-deep-2) 0%, var(--decor) 100%);
  transform: scaleX(0);
  transform-origin: left center;
}

/* цифры одинаковой ширины — число не дёргается, пока тикает; если всё же длиннее колонки —
   обрезается многоточием, но колонку не раздвигает */
/* накоплено в хранилище — сумма монет, золотом («Заполнено» — яркой бирюзой, см. ниже) */
.storage-label {
  min-width: 0;
  overflow: hidden;
  color: var(--gold);
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: right;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Заполнено (готовая прибыль): шкала и подпись яркой бирюзой, рамка ярче. Подсветка статичная */
.stage-storage.full .storage-bar {
  border-color: var(--action);
  box-shadow: 0 0 8px var(--action-glow);
}

.stage-storage.full .storage-fill {
  background: linear-gradient(90deg, var(--decor) 0%, var(--action) 100%);
}

.stage-storage.full .storage-label {
  color: var(--action);
}

.factory.lite .stage-storage.full .storage-bar {
  box-shadow: none;
}

.stage-income {
  color: var(--gold);
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}

/* Три карточки узлов в один ряд, одинаковой ширины; на широком экране блок не шире 480px */
.stage-nodes {
  width: 100%;
  max-width: 480px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}

/* Закрытый этап: блок того же размера, что сцена, полностью затемнён.
   Внутри только силуэт — общая форма без деталей. Ни текста, ни анимации */
.stage-locked {
  width: 100%;
  aspect-ratio: 600 / 190;
  position: relative;
  overflow: hidden;
  border-radius: 18px;
  background: var(--locked);
  border: 1px solid var(--locked-border);
}

@supports not (aspect-ratio: 1 / 1) {
  .stage-locked {
    height: 0;
    padding-top: 31.667%;
  }
}

/* Надпись на закрытом этапе: по центру, спокойная и приглушённая; силуэт не перекрывает (фона нет) */
.stage-lock-note {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  z-index: 1;
  color: var(--muted);
  opacity: 0.7;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  pointer-events: none;
}

.silhouette {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}

.sil {
  position: absolute;
  background: var(--silhouette);
}

/* силуэт по умолчанию (для этапов без своего шаблона) */
.sil-a {
  left: 14%;
  right: 24%;
  bottom: 14%;
  height: 18%;
  border-radius: 999px;
}

.sil-b {
  left: 40%;
  top: 22%;
  width: 18%;
  height: 34%;
  border-radius: 10px;
}

/* чеканка: лента и колпак */
.sil-belt {
  left: 12%;
  right: 24%;
  bottom: 12%;
  height: 26%;
  border-radius: 999px;
}

.sil-dome {
  left: 38.5%;
  top: 20%;
  width: 10%;
  height: 25%;
  border-radius: 50% 50% 6px 6px / 70% 70% 6px 6px;
}

/* упаковка: столбики */
.sil-stack {
  bottom: 12%;
  width: 12%;
  border-radius: 6px;
}

.sil-stack.s1 {
  left: 24%;
  height: 40%;
}

.sil-stack.s2 {
  left: 42%;
  height: 58%;
}

.sil-stack.s3 {
  left: 60%;
  height: 30%;
}

/* транспорт: крупные блоки */
.sil-block {
  left: 16%;
  bottom: 16%;
  width: 48%;
  height: 46%;
  border-radius: 10px;
}

.sil-block-low {
  left: 62%;
  bottom: 16%;
  width: 22%;
  height: 28%;
  border-radius: 8px;
}

/* хранилище (последний этап): заметно крупнее остальных, бесформенная масса */
.sil-mass {
  left: 7%;
  right: 9%;
  top: 7%;
  bottom: 18%;
  border-radius: 42% 58% 18px 24px / 55% 72% 18px 18px;
}

.sil-mass-base {
  left: 3%;
  right: 4%;
  bottom: 5%;
  height: 18%;
  border-radius: 10px 14px 8px 8px;
}

/* Труба между этапами — поверх промежутка, не внутри блоков. Выходит из правого нижнего угла
   блока (на вертикали трубы-выхода сцены — 84.17% ширины), идёт вниз, поперёк и заходит
   в левый верхний угол следующего блока (на вертикали трубы подачи — 15.83%) */
.pipe-link {
  position: relative;
  width: 100%;
  height: 48px;
  z-index: 2;
}

.pipe-seg {
  position: absolute;
  overflow: hidden;
  background: var(--pipe-mid);
  border: 2px solid var(--field-border);
}

.seg-out {
  left: calc(84.17% - 8px);
  top: 0;
  width: 16px;
  height: 32px;
}

.seg-across {
  left: calc(15.83% - 8px);
  right: calc(15.83% - 8px);
  top: 16px;
  height: 16px;
}

.seg-in {
  left: calc(15.83% - 8px);
  top: 16px;
  width: 16px;
  height: 32px;
}

/* Штрихи потока. Полоса длиннее видимой части минимум на два тайла (тайл 10px) и кратна тайлу;
   сдвиг ровно на один тайл и мгновенный возврат в ноль. Только transform */
.pipe-flow {
  position: absolute;
  opacity: 0.35;
}

.seg-out .pipe-flow,
.seg-in .pipe-flow {
  left: 0;
  right: 0;
  top: -10px;
  height: 60px;
  background: repeating-linear-gradient(
    180deg,
    var(--decor) 0,
    var(--decor) 4px,
    transparent 4px,
    transparent 10px
  );
}

.seg-across .pipe-flow {
  top: 0;
  bottom: 0;
  left: -10px;
  width: 1000px;
  background: repeating-linear-gradient(
    90deg,
    var(--decor) 0,
    var(--decor) 4px,
    transparent 4px,
    transparent 10px
  );
}

/* Поток есть, только если следующий этап открыт */
.pipe-link.flowing .pipe-flow {
  opacity: 1;
}

.pipe-link.flowing .seg-out .pipe-flow,
.pipe-link.flowing .seg-in .pipe-flow {
  animation: pipe-down 0.4s linear infinite;
}

.pipe-link.flowing .seg-across .pipe-flow {
  animation: pipe-left 0.4s linear infinite;
}

@keyframes pipe-down {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(0, 10px, 0);
  }
}

@keyframes pipe-left {
  from {
    transform: translate3d(0, 0, 0);
  }
  to {
    transform: translate3d(-10px, 0, 0);
  }
}

/* Лёгкий режим: трубы не анимируются вообще */
.factory.lite .pipe-flow {
  animation: none !important;
}

/* После последнего этапа: труба уходит вниз в темноту и там исчезает.
   Прокрутка заходит в темноту и упирается */
.factory-abyss {
  position: relative;
  width: 100%;
  height: 150px;
  flex-shrink: 0;
}

.seg-tail .pipe-flow {
  height: 170px;
}

.seg-tail {
  height: 150px;
  -webkit-mask-image: linear-gradient(to bottom, var(--black) 0%, transparent 80%);
  mask-image: linear-gradient(to bottom, var(--black) 0%, transparent 80%);
}

.factory-abyss::after {
  content: "";
  position: absolute;
  left: -10px;
  right: -10px;
  top: 0;
  bottom: 0;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(var(--ink-rgb), 0) 0%, rgba(var(--ink-rgb), 0.92) 100%);
}

/* Над первым этапом: труба приходит сверху из темноты — зеркально трубе в темноту внизу.
   Та же вертикаль, что у трубы подачи в сцене (15.83%). Это обычная труба-связка
   (.pipe-link.flowing): пауза вне экрана, лёгкий режим и reduced-motion работают так же */
.pipe-link.pipe-source {
  height: 90px;
}

.pipe-source .seg-in {
  top: 0;
  height: 90px;
  -webkit-mask-image: linear-gradient(to top, var(--black) 0%, transparent 85%);
  mask-image: linear-gradient(to top, var(--black) 0%, transparent 85%);
}

.pipe-source .seg-in .pipe-flow {
  height: 110px;
}

/* Карточка узла: три в ряд под сценой (раскладку задаёт .stage-nodes), вся карточка — кнопка.
   На 380px ширина карточки ≈115px: мелкие шрифты, короткие подписи, числа не рвутся
   (в них неразрывные пробелы), строка значений при нехватке места переносится у стрелки */
.node-card {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  padding: 8px 7px 7px;
  border-radius: 14px;
  border: 1px solid var(--card-border);
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  color: var(--text);
  font: inherit;
  text-align: left;
  line-height: 1.2;
  cursor: pointer;
  transition: transform 0.12s ease;
  -webkit-user-select: none;
  user-select: none;
}

.node-card > span {
  display: block;
  min-width: 0;
}

/* название узла одинаковое на всех этапах, с иконкой роли */
.node-name {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.01em;
}

.node-ico {
  display: block;
  flex-shrink: 0;
  width: 14px;
  height: 14px;
  color: var(--decor);
}

.node-ico svg {
  width: 100%;
  height: 100%;
}

.node-title {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.node-level {
  color: var(--muted);
  font-size: 11px;
  font-weight: 800;
}

.node-values {
  margin-top: 2px;
  color: var(--muted);
  font-size: 11px;
}

/* текущее значение — вторичным цветом, следующее — основным: видно, к чему ведёт апгрейд */
.node-values b {
  color: var(--muted);
  white-space: nowrap;
}

.node-values b.next {
  color: var(--text);
}

.node-values small {
  font-size: 10px;
  white-space: nowrap;
}

.node-price {
  color: var(--gold);
  font-size: 12px;
  font-weight: 800;
  white-space: nowrap;
}

/* монетка перед ценой */
.node-price::before {
  content: "";
  display: inline-block;
  width: 8px;
  height: 8px;
  margin-right: 4px;
  border-radius: 50%;
  background: var(--gold);
  vertical-align: 1px;
}

.node-price:empty {
  display: none;
}

/* Состояние прижато к низу карточки: у соседних карточек строки состояния на одном уровне */
.node-state {
  margin-top: auto;
  padding-top: 4px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.node-sub {
  font-size: 10px;
  font-weight: 700;
  white-space: nowrap;
}

.node-sub:empty {
  display: none;
}

/* Можно купить: рамка и состояние цветом акцента */
.node-card.ready {
  border-color: var(--action);
  background: linear-gradient(180deg, var(--decor-deep) 0%, var(--card-bottom) 100%);
  box-shadow: inset 0 0 0 1px var(--action);
}

.node-card.ready .node-state {
  color: var(--action);
}

/* прибавка дохода «+N/ч» и «сколько не хватает» — суммы монет, золотом */
.node-card .node-sub {
  color: var(--gold);
}

/* Нажатие — короткое «вдавливание» */
.node-card.ready:active {
  transform: scale(0.95);
}

/* Не хватает монет: приглушена, нажатие выключено (disabled) */
.node-card.poor {
  background: var(--scene-bottom);
  border-color: var(--field-border);
}

.node-card.poor .node-name,
.node-card.poor .node-values,
.node-card.poor .node-price {
  opacity: 0.72;
}

.node-card.poor .node-state {
  color: var(--muted);
}

/* Максимум: нейтральная, без подсветки */
/* Максимум: обычная карточка без подсветки, рамка приглушённой бирюзой — «завершено», не действие */
.node-card.maxed {
  border-color: var(--decor-deep-2);
}

.node-card.maxed .node-state {
  color: var(--muted);
}

.node-card:disabled {
  cursor: default;
}

/* Узкие телефоны: верхний блок завода не должен переноситься */
@media (max-width: 359px) {
  .factory-income strong {
    font-size: 25px;
    font-size: min(25px, 100cqw / (var(--len, 1) * 0.56));
  }

  .factory-coins strong {
    font-size: 15px;
    font-size: min(15px, 100cqw / (var(--len, 1) * 0.56));
  }

  .node-card {
    padding: 7px 5px 6px;
  }

  .node-name {
    font-size: 11px;
  }
}


.toggle-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px;
  align-items: center;
  margin-bottom: 14px;
  color: var(--muted);
  font-size: 14px;
}

.toggle-row small {
  display: block;
  margin-top: 2px;
  font-size: 11px;
}

.toggle-row input[type="checkbox"] {
  width: 22px;
  height: 22px;
  accent-color: var(--action);
}

/* === Монета-картинка во весь размер ===
   Там, где лежит картинка монеты (has-art ставит app.js), монета нарисована на самой картинке:
   золотой круг с рамкой из-под неё убираем. Правила в конце файла — чтобы перебить фон и рамку
   из описаний .coin-face.minted, .pcoin-face и .collect-coin выше. Подсветка и кольцо состояния
   рисуются снаружи, поэтому остаются */
.coin-face.has-art,
.pcoin-face.has-art,
.collect-coin.has-art {
  background: none;
  border: 0;
}

/* заполненное хранилище: бирюзовое кольцо было рамкой — теперь рисуем его вокруг картинки */
.collect-coin.full.has-art {
  box-shadow: 0 0 0 2px var(--action), 0 0 16px rgba(var(--gold-rgb), 0.85);
}

.factory.lite .collect-coin.full.has-art {
  box-shadow: 0 0 0 2px var(--action);
}

/* пустое хранилище: монета приглушена, как и была */
.collect-coin.empty.has-art {
  opacity: 0.4;
}

/* === Значок «Первопроходец» ===
   Маленький — рядом с именем в шапке профиля; крупный — в окне значка; карточка — первой в достижениях
   (только у получивших). Значок секретный: у остальных нигде не упоминается. SVG из app.js в цветах
   приложения (золото, бирюзовая лента). Без анимации */
.pf-name-row {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}

.pf-pioneer {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  background: none;
  line-height: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.pf-pioneer svg,
.pioneer-big svg,
.pioneer-ico svg {
  display: block;
  width: 100%;
  height: 100%;
}

.pioneer-sheet .sheet-title-row {
  justify-content: flex-end;
}

.pioneer-body {
  padding: 0 24px 28px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  text-align: center;
}

/* крупный значок на мягкой статичной подсветке */
.pioneer-big {
  width: 112px;
  height: 112px;
  padding: 10px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(var(--gold-rgb), 0.22) 0%, rgba(var(--gold-rgb), 0.06) 55%, transparent 72%);
}

.pioneer-title {
  color: var(--gold);
  font-size: 24px;
  letter-spacing: 0.04em;
}

.pioneer-text {
  max-width: 330px;
  color: var(--text);
  font-size: 15px;
  line-height: 1.45;
}

.pioneer-since {
  color: var(--muted);
  font-size: 13px;
  font-weight: 700;
}

/* достижения: отдельная широкая карточка сверху */
.ach-card.pioneer-card {
  width: 100%;
  flex-direction: row;
  justify-content: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border-color: var(--gold-dark);
  text-align: left;
}

.ach-card.pioneer-card .pioneer-ico {
  flex-shrink: 0;
  width: 52px;
  height: 52px;
  padding: 5px;
}

.pioneer-card-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.pioneer-card .ach-name {
  color: var(--gold);
  font-size: 15px;
}

.pioneer-card .ach-date {
  font-size: 12px;
}

/* === Обучение нового игрока ===
   Затемнение — тень рамки подсветки (статичная), вокруг рамки прозрачные блоки: нажать можно только
   подсвеченный элемент (и только на шагах, где он нужен — класс passthrough). Подсказку раскладывает app.js:
   не выходит за экран и не заходит под системные панели. Появление — только opacity и transform */
.tut {
  position: fixed;
  inset: 0;
  z-index: 70;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease-out;
}

.tut.show {
  opacity: 1;
}

.tut-hole {
  position: fixed;
  border-radius: 18px;
  box-shadow: 0 0 0 200vmax rgba(3, 10, 14, 0.62);
  outline: 2px solid rgba(var(--action-rgb), 0.95);
  pointer-events: auto;
}

.tut-hole.round {
  border-radius: 50%;
}

.tut-hole.passthrough {
  pointer-events: none;
}

.tut-block {
  position: fixed;
  pointer-events: auto;
}

/* элемент шага ещё не на экране (например, завод раскладывается) — подсказку не показываем */
.tut.waiting .tut-tip,
.tut.waiting .tut-hole {
  visibility: hidden;
}

.tut-tip {
  position: fixed;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  padding: 14px 16px 8px;
  border: 1px solid var(--action);
  border-radius: 16px;
  background: linear-gradient(180deg, var(--card-top) 0%, var(--card-bottom) 100%);
  box-shadow: 0 12px 28px var(--shadow);
  text-align: center;
  pointer-events: auto;
}

.tut-text {
  color: var(--text);
  font-size: 15px;
  font-weight: 700;
  line-height: 1.35;
}

.tut-ok {
  width: 100%;
  padding: 10px 14px;
  font-size: 15px;
}

.tut-skip {
  align-self: center;
  padding: 4px 8px;
  border: 0;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: 12px;
  text-decoration: underline;
  cursor: pointer;
}

/* стрелка к подсвеченному элементу: сверху подсказки или снизу (arrow-down), по центру элемента */
.tut-arrow {
  position: absolute;
  top: -8px;
  width: 14px;
  height: 14px;
  margin-left: -7px;
  border-top: 1px solid var(--action);
  border-left: 1px solid var(--action);
  background: var(--card-top);
  transform: rotate(45deg);
}

.tut-tip.arrow-down .tut-arrow {
  top: auto;
  bottom: -8px;
  background: var(--card-bottom);
  transform: rotate(225deg);
}

@media (prefers-reduced-motion: reduce) {
  .tut {
    transition: none;
  }
}
