/* ─────────────────────────────────────────────
   Владимир Храбрый — main stylesheet
   палитра: старая бумага + графит + сургучный красный
───────────────────────────────────────────── */

:root {
  --paper:       #f7edd7;
  --paper-warm:  #f4ead5;
  --paper-deep:  #ebe0c8;

  --ink:         #2a2620;
  --ink-soft:    rgba(42, 38, 32, 0.72);
  --ink-mute:    rgba(42, 38, 32, 0.50);
  --ink-faint:   rgba(42, 38, 32, 0.28);
  --ink-line:    rgba(42, 38, 32, 0.16);

  --wax:         #8b2e2e;
  --wax-deep:    #6b1e20;

  --font-display: 'EB Garamond', 'Times New Roman', serif;
  --font-body:    'Lora', Georgia, serif;
  --font-script:  'Marck Script', cursive;

  --nav-h:        76px;
  --content-max:  1400px;
  --pad-x:        clamp(20px, 4vw, 56px);
}

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

html { scroll-behavior: smooth; }

body {
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  height: 100vh;
  overflow: hidden;
}

a { color: inherit; }

::selection { background: var(--wax); color: var(--paper); }

/* ───── Бумажная текстура (multiply 0.2) поверх всего, что под кнопками ───── */
.paper-overlay {
  position: fixed;
  top: var(--nav-h);
  left: 0;
  right: 0;
  bottom: 0;
  background-image: url('../paper.jpg');
  background-size: auto;
  background-position: top left;
  background-repeat: repeat;
  mix-blend-mode: multiply;
  opacity: 0.2;
  pointer-events: none;
  z-index: 5;
}

/* ───── Верхняя панель ───── */
.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--nav-h);
  background-color: var(--paper);
  z-index: 30;
}

.topbar::after {
  content: "";
  position: absolute;
  left: var(--pad-x);
  right: var(--pad-x);
  bottom: 0;
  height: 1px;
  background: var(--ink-line);
}

.topbar-inner {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 0 var(--pad-x);
  height: 100%;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}

/* Canvas со штриховкой-прогрессом — лежит ПОД текстом, во всю ширину inner'а */
.topbar-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

/* Левая и правая стороны: текст + опциональная стрелка.
   transform: translateY(-4px) включается ТОЛЬКО когда сторона активна
   (под ней подчёркивание — оно смещает оптический центр группы вниз) или
   когда на неё навели (hover на неактивной — превью «как будет»). На голой
   неактивной transform=0, иначе текст без подчёркивания висит криво высоко
   относительно bbox-центра. См. правила в блоке «── Состояния view ──». */
.topbar-side {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 20px;
  color: var(--ink);
  transition: transform 0.25s ease;
}

.topbar-link {
  position: relative;
  text-decoration: none;
  color: inherit;
  white-space: nowrap;
  transition: color 0.25s ease;
}

.topbar-link-text {
  display: inline-block;
}

/* Подчёркивание под активным разделом — чернилами, прижато вплотную к тексту */
.topbar-underline {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--wax);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.35s ease;
}

/* Стрелочки: спрятаны вне своей стороны, выезжают при переключении вида */
.topbar-arrow {
  display: inline-block;
  font-family: var(--font-display);
  font-style: italic;
  font-size: 20px;
  color: var(--ink);
  opacity: 0;
  transition: opacity 0.35s ease, transform 0.35s ease;
}

.topbar-arrow--back {
  transform: translateX(-12px);  /* старт: чуть слева, прячется */
}
.topbar-arrow--forward {
  transform: translateX(12px);   /* старт: чуть справа, прячется */
}

/* Бренд по центру — статичный заголовок. Без подчёркивания — оптического
   смещения нет, оставляем на геометрическом центре шапки (без translateY).
   transition нужен для плавного появления бренда при переключении в city
   (там нет дублирующего hero-заголовка → бренд должен быть видим всегда).
   На скрол-driven phase-обновлении не мешает, так как inline-style
   меняется каждый кадр почти на ту же величину что и transition lerp. */
.topbar-brand {
  position: relative;
  z-index: 1;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 26px;
  letter-spacing: -0.005em;
  color: var(--ink);
  margin: 0;
  white-space: nowrap;
  transition: opacity 0.4s ease, transform 0.4s ease;
}

/* В city-view нет hero-заголовка → бренд должен быть видим, иначе
   шапка голая. !important перекрывает inline-style, который непрерывно
   пишет JS из applyPhase (он привязан к phase истории). */
body.view-city .topbar-brand {
  opacity: 1 !important;
  transform: translateY(0%) translateY(-2px) !important;
}

.topbar-brand-italic {
  font-style: italic;
  font-weight: 500;
}

/* ── Состояния view ───────────────────────────
   .view-history (default): левый ссылка активна → подчёркнута, левая стрелка
                            ←  скрыта; правая стрелка → видна, правая ссылка
                            не подчёркнута (= пригашённый цвет).
   .view-city:              зеркально: правая ссылка подчёркнута, → скрыта;
                            левая ссылка пригашена, ← выезжает.
─────────────────────────────────────────────── */
body.view-history .topbar-side--left  .topbar-underline,
body.view-city    .topbar-side--right .topbar-underline {
  transform: scaleX(1);
}

body.view-history .topbar-side--right .topbar-arrow--forward,
body.view-city    .topbar-side--left  .topbar-arrow--back {
  opacity: 1;
  transform: translateX(0);
}

/* Поднятие активной стороны на 4px — компенсирует оптическое смещение от
   подчёркивания. Inactive-сторона на hover поднимается так же (превью «как
   будет»), плавно через .topbar-side { transition: transform }. */
body.view-history .topbar-side--left,
body.view-city    .topbar-side--right,
body.view-history .topbar-side--right:hover,
body.view-city    .topbar-side--left:hover {
  transform: translateY(-4px);
}

/* Inactive-сторона: пригашённый цвет ссылки, чтобы было видно что она link */
body.view-history .topbar-side--right .topbar-link,
body.view-city    .topbar-side--left  .topbar-link {
  color: var(--ink-mute);
}

body.view-history .topbar-side--right .topbar-link:hover,
body.view-city    .topbar-side--left  .topbar-link:hover {
  color: var(--ink);
}

/* Active-сторона: курсор default (всё-равно navigate триггерит, но pointer
   на активной странице сбивает с толку) */
body.view-history .topbar-side--left  .topbar-link,
body.view-city    .topbar-side--right .topbar-link {
  cursor: default;
}

/* ── Fade между hero (view-history) и city-view (view-city) ──
   Обе секции ABS-позиционированы, накладываются друг на друга.
   Phase-state в JS не сбрасывается при переключении — пользователь возвра-
   щается на тот же скролл-момент.
─────────────────────────────────────────────── */
.hero,
.city-view {
  transition: opacity 0.5s ease;
}

body.view-city .hero {
  opacity: 0;
  pointer-events: none;
}

.city-view {
  position: absolute;
  inset: var(--nav-h) 0 0 0;
  opacity: 0;
  pointer-events: none;

  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(12px, 1.5vw, 24px) var(--pad-x);

  container-type: size;

  /* aspect карты × 1.5 (карта + 0.5 на текст), как у battle-stage */
  --city-map-aspect: calc(1500 / 1095);
  --city-total-aspect: calc(var(--city-map-aspect) * 1.5);
}

body.view-city .city-view {
  opacity: 1;
  pointer-events: auto;
}

/* ───── City inner: карта слева + текст справа ─────
   Та же логика «самый большой прямоугольник нужного аспекта», что у
   .battle-inner — width = min(вся ширина, доступная высота × аспект). */
.city-inner {
  width: min(100cqw, calc(100cqh * var(--city-total-aspect)));
  aspect-ratio: var(--city-total-aspect);
  display: flex;
  align-items: stretch;
}

.city-map {
  position: relative;
  flex: none;
  height: 100%;
  aspect-ratio: var(--city-map-aspect);
  overflow: visible;       /* пины могут чуть свешиваться за границу карты */
  container-type: inline-size;
}

.city-map-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* контейнер пинов — поверх canvas, события клика только у самих пинов */
.city-pins {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Пин-метка на карте. left/top ставит JS из пиксельных координат
   исходной карты (4686×3422). transform центрирует по точке.
   На load opacity=0 → JS лерпит в 1 при пройденной reveal-анимации карты.
   У эмблемы свой aspect 470/604; ширина в % от карты. */
/* Пин: серая нарисованная эмблема (img). Активная метка — красный tint
   поверх через mix-blend-mode: multiply. Multiply на белом бумажном
   фоне эмблемы даёт красный, а тёмный павлин остаётся тёмным —
   форма читается, метка становится визуально «красной». */
/* Двухуровневая структура:
   • .city-pin (outer) — позиционирование + hover/active scale.
     transform управляет ТОЛЬКО hover/active изменением размера, чтобы
     transition сглаживал. Pulse сюда не лезет.
   • .city-pin-inner (inner) — pulse-анимация. Independent transform.

   Без разделения animation pulse и hover/active transform конфликтуют
   по свойству — animation:none даёт мгновенный прыжок на base scale. */
.city-pin {
  position: absolute;
  width: 6.5%;
  aspect-ratio: 470 / 604;
  transform: translate(-50%, -100%) scale(0.8);
  transform-origin: 50% 100%;
  pointer-events: auto;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.4s ease, transform 0.35s ease;
  filter: drop-shadow(0 2px 4px rgba(42, 38, 32, 0.25));
  will-change: opacity, transform;
}

/* Размытая лежащая тень под эмблемой. Прямоугольник со скруглением,
   blur + multiply — даёт ощущение что метка лежит на бумажной карте,
   а не парит сама по себе. На outer.transform скалится с метой при
   hover/active (что добавляет немного объёма к движению), pulse не
   затрагивает её (он на inner). */
.city-pin::before {
  content: '';
  position: absolute;
  left: 50%;
  bottom: -4%;
  width: 82%;
  height: 14%;
  transform: translateX(-50%);
  background-color: rgba(42, 38, 32, 0.85);
  border-radius: 50%;
  filter: blur(6px);
  mix-blend-mode: multiply;
  pointer-events: none;
  z-index: -1;
}

/* Inner — собственная transform-цепочка только для пульсации.
   Амплитуда 0.06 (50% от исходных 0.12: 1.0 ↔ 1.075). transform-origin
   тоже на нижней середине — не двигает «якорь» эмблемы. */
.city-pin-inner {
  width: 100%;
  height: 100%;
  transform-origin: 50% 100%;
  animation: cityPinPulse 1.6s ease-in-out infinite;
  transition: transform 0.35s ease;
  will-change: transform;
}

.city-pin-inner img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
}

@keyframes cityPinPulse {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.075); }
}

/* hover/active останавливают pulse у inner и фиксируют scale(1).
   Outer тем временем плавно растёт до 1.08 через свой transition. */
.city-pin:hover .city-pin-inner,
.city-pin.is-active .city-pin-inner {
  animation: none;
  transform: scale(1);
}

/* hover/active обновляют outer transform — transition transform 0.35s
   на .city-pin сглаживает переход с текущего pulse-значения на 1.08. */
.city-pin:hover {
  transform: translate(-50%, -100%) scale(1.08);
}

.city-pin.is-active {
  transform: translate(-50%, -100%) scale(1.08);
}

/* красный tint-слой: mask по эмблеме обрезает прямоугольник overlay'а
   до формы-щита, multiply красит белый paper-фон эмблемы, оставляя
   тёмные линии павлина и медальона видимыми. Цвет — чистый красный
   (не сургучный wax — тот даёт коричневатый оттенок при multiply). */
.city-pin::after {
  content: '';
  position: absolute;
  inset: 0;
  background-color: #d62828;
  -webkit-mask-image: url('../emblem.png');
          mask-image: url('../emblem.png');
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-size: contain;
          mask-size: contain;
  mix-blend-mode: multiply;
  opacity: 0;
  pointer-events: none;
  /* плавный спад красного при де-активации: keyframes снимаются вместе
   с .is-active, opacity возвращается в base 0 через transition. */
  transition: opacity 0.5s ease;
}

.city-pin.is-active::after {
  animation: cityPinBlink 2s ease-in-out infinite;
}

@keyframes cityPinBlink {
  0%, 100% { opacity: 0.3; }
  50%      { opacity: 0.05; }
}

/* ───── Текстовая колонка city ─────
   .city-text использует те же стили что .battle-text (aspect, container-type),
   .city-text-inner — то же design-width 500px со scale-трансформом,
   но layout у inner'а свой: title → landmark → body. */
.city-text {
  flex: none;
  height: 100%;
  aspect-ratio: calc(var(--city-map-aspect) / 2);
  position: relative;
  overflow: hidden;
  container-type: inline-size;
}

/* Композированный селектор (.city-text-inner.battle-text-inner) поднимает
   специфичность до 0,2,0 — иначе .battle-text-inner ниже по файлу
   перекрывает наши city-значения font-size/line-height. */
.city-text-inner.battle-text-inner {
  --design-w: 500;
  width:  calc(var(--design-w) * 1px);
  height: calc(var(--design-w) * 1px / (var(--city-map-aspect) / 2));

  transform-origin: top left;
  transform: scale(calc(100cqw / var(--design-w) / 1px));

  padding-left: 30px;
  display: flex;
  flex-direction: column;

  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.55;
  color: var(--ink);
}

/* Перекрываем размеры и разрешаем перенос на 2 строки (white-space:normal
   вместо pre). Высота 88px (= ~2 × 36 × 1.2 line-height) фиксирована, чтобы
   layout не прыгал между точками с разной длиной заголовка. Cursor
   родителя отключаем — он становится отдельным flex-item'ом и уезжает
   вправо на 2-строчных заголовках; cursor рисуется на inner-span'е (см. ниже). */
.city-title.battle-title {
  flex: 0 0 auto;
  height: 88px;
  font-size: 36px;
  white-space: normal;
  text-align: center;
}

.city-title.battle-title::after {
  display: none;
}

/* inner-span внутри city-title — обёртка под текст. ::after-cursor лежит
   inline после последней буквы независимо от количества строк. */
.city-title-inner {
  display: inline;
}

.city-title-inner::after {
  content: '|';
  display: inline-block;
  margin-left: 2px;
  color: var(--wax);
  font-weight: 400;
  animation: titleCursor 0.85s linear infinite;
}

/* блок под landmark-картинку. На всю ширину inner'а (с учётом
   padding-left у inner'а). aspect-ratio ставится из JS на основе
   natural размеров активной картинки — у разных точек могут быть
   разные пропорции картинок. Дефолт нужен на случай инициализации
   до loadActiveImage(). */
.city-image {
  flex: 0 0 auto;
  width: 100%;
  margin: 6px 0 16px;
  aspect-ratio: 4 / 3;
  position: relative;
  overflow: hidden;
}

.city-image-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.city-body {
  flex: 1 1 auto;
  position: relative;
  min-height: 0;

  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

.city-body p {
  margin-bottom: 10px;
  text-align: justify;
  hyphens: auto;
}
.city-body p:last-child { margin-bottom: 0; }

/* буквица — float:left высотой ровно в 2 строки текста.
   Используем initial-letter где поддерживается (Chromium, Safari) —
   браузер сам подгонит размер. Float-fallback с уменьшенным font-size
   на случай Firefox / старых браузеров (3.6em × коэф ~1.38 = 3+ строк,
   поэтому ужимаем до 2.4em — визуальный bbox влезает в 2 строки). */
.city-body p:first-of-type::first-letter {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 2.4em;
  line-height: 0.9;
  color: var(--wax);
  float: left;
  margin: 0.05em 0.12em 0 0;
  -webkit-initial-letter: 2;
          initial-letter: 2;
}

.city-body strong {
  font-weight: 700;
  color: var(--ink);
}
.city-body em {
  font-style: italic;
  color: var(--wax);
}

/* ───── Полотно страницы ───── */
.page {
  position: relative;
  z-index: 2;
  padding-top: var(--nav-h);
}

/* ───── Hero ───── */
/* hero на всю ширину окна — модель должна доходить до краёв.
   horizontal-обвязку держит .hero-text, у неё свой max-width и боковые отступы */
.hero {
  padding: clamp(12px, 1.5vw, 24px) 0 0;
  height: calc(100vh - var(--nav-h));
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  position: relative;
  overflow: hidden;
}

/* Стек двух заголовков (initial + history) — оба занимают одну
   grid-ячейку, поэтому накладываются друг на друга. JS сдвигает
   их по Y в зависимости от phase, давая «карусель» при прокрутке. */
.hero-stack {
  display: grid;
  /* minmax(0, 1fr) вместо 1fr — снимает auto-min на row-track, иначе грид
     залипает в max(min-content) всех .hero-text item'ов (= в 207px от большого
     initial-заголовка), и items стретчатся до 207 даже когда .hero-stack
     style.height установлено в 96. С minmax(0,1fr) track корректно ужимается
     под explicit container height. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  position: relative;
  z-index: 5;
  /* высота лерпится из JS под текущий заголовок. Чтобы это не пропагировало
     reflow на .hero-content (= map-canvas resize-thrash → лаги),
     .hero-content вынесен в position: absolute (см. ниже) — он стоит
     отдельно и его высота не зависит от .hero-stack. */
}

.hero-text {
  grid-area: 1 / 1;
  text-align: center;
  max-width: 1400px;
  width: 100%;
  margin: 0 auto;
  padding: 0 var(--pad-x);
  /* по дефолту скрыта пока phase=0 не активирована — JS подмешает */
  will-change: transform, opacity;
}

.hero-text--history {
  /* стартовое положение — выше экрана; JS опустит при росте phase */
  transform: translateY(-150%);
  opacity: 0;
}

.hero-text--serpukhov,
.hero-text--coin {
  /* стартовое положение — выше экрана (как и --history); JS опустит вниз
     на место уезжающего предыдущего заголовка по своему titleSwap-time */
  transform: translateY(-150%);
  opacity: 0;
}

/* compound-селектор (.hero-title.hero-title--alt) даёт более высокую
   специфичность чем одиночный .hero-title ниже по файлу — иначе тот
   перекрывал бы наш font-size */
.hero-title.hero-title--alt {
  font-size: clamp(20px, 3vw, 42px);     /* −30% от изначального clamp(28,4.4vw,60) */
  line-height: 1.05;
  white-space: normal;
  font-weight: 700;
  font-style: normal;
}

/* компактные отступы для не-initial слайдов */
.hero-text--history .hero-ornament,
.hero-text--serpukhov .hero-ornament,
.hero-text--coin .hero-ornament {
  margin: 6px auto 3px;
}

.hero-text--history .hero-dates,
.hero-text--serpukhov .hero-dates,
.hero-text--coin .hero-dates {
  font-size: clamp(22px, 2.6vw, 32px);
}

.hero-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(40px, 7.6vw, 120px);
  line-height: 1;
  letter-spacing: -0.015em;
  color: var(--ink);
  white-space: nowrap;
}

.title-line--italic {
  font-style: italic;
  font-weight: 500;
}

.hero-ornament {
  margin: 22px auto 14px;
  color: var(--wax);
  display: flex;
  justify-content: center;
}

.hero-subtitle {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  justify-content: center;
}

.hero-role {
  font-family: var(--font-display);
  font-style: italic;
  font-size: clamp(18px, 1.9vw, 24px);
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}

.hero-bullet {
  font-size: clamp(18px, 1.9vw, 24px);
  color: var(--wax);
  line-height: 1;
}

.hero-dates {
  font-family: var(--font-script);
  font-size: clamp(28px, 3.4vw, 42px);
  color: var(--wax);
  line-height: 1;
}

.hero-dates .dash {
  margin: 0 0.06em;
  font-family: var(--font-display);
}

/* ───── Контейнер для модели и битвы ─────
   .hero-content раньше был flex-дитём .hero (брал всё оставшееся место под
   заголовками). Сейчас он position: absolute с фиксированной высотой
   (= hero - padTop - initialTitleH, ставится из JS). Это развязывает его
   от .hero-stack: лерп высоты стека больше НЕ меняет размер .hero-content,
   а значит не дёргает container-queries у .battle-/.serpukhov-/.coin-stage
   и не вызывает resize-thrash на map-canvas'ах внутри (= нет лагов).

   Внутри — model-stage (фаза 0..1), battle-stage (фаза 1..6) и т.д.,
   все position: absolute; inset: 0 — накладываются друг на друга.
─────────────────────────────────────────── */
.hero-content {
  flex: 1 1 auto;
  position: relative;
  min-height: 0;
}

/* ───── Сцена под 3D-модель ─────
   align-items: flex-end — .model-frame (с пинованной из JS высотой) лепится
   к НИЗУ .model-stage. Низ .model-stage = низ .hero-content = низ .hero,
   фиксированная Y-точка. При сжатии .hero-stack в фазе 0..1 .hero-content
   растёт (flex: 1 1 auto), .model-stage (inset: 0) тоже растёт, но фрейм
   остаётся прибитым к низу с зафиксированной высотой → модель не двигается
   и не ресайзится во время стирания. Пустое сверху от фрейма — paper bg.
─────────────────────────────────────────── */
.model-stage {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: flex-end;
  justify-content: stretch;
}

.model-frame {
  width: 100%;
  position: relative;
  display: flex;
  align-items: stretch;
  justify-content: stretch;
}

.model-canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  z-index: 1;
  /* пока модель не загрузилась — canvas прозрачный (видна paper-страница).
     На load JS ставит opacity: 1 → плавный fade-in вместо резкого появления. */
  opacity: 0;
  transition: opacity 0.6s ease;
}

/* ───── Сцена под раздел Куликовской битвы ─────
   Активируется при phase > 1. Геометрия:
     • большой блок фиксированного аспекта (= map_aspect × 1.5):
       карта в полную ширину + текстовая колонка шириной с половину карты.
     • блок центрируется по обеим осям внутри stage'а — т.е. между низом
       заголовка «Куликовская битва» и низом окна.
     • при изменении пропорций окна блок масштабируется по принципу
       «самый большой прямоугольник нужного аспекта, который влезает».
   container-type: size превращает stage в размерный контейнер, чтобы
   дети могли считать ширину через cqw/cqh.
─────────────────────────────────────────────── */
.battle-stage,
.serpukhov-stage,
.coin-stage {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;         /* вертикальное центрирование inner */
  justify-content: center;     /* горизонтальное центрирование inner */
  padding: 0 var(--pad-x);     /* без вертикальных отступов: края рисунка сами растворяются */
  opacity: 0;                  /* до фазы ~1 невидим — иначе paper-фон canvas'а закрыл бы 3D-модель */
  will-change: opacity;
  z-index: 2;                  /* поверх model-stage и его canvas (z-index: 1) */

  container-type: size;        /* нужен для cqw/cqh у .battle-inner */

  /* total-aspect = left-block-aspect * 1.5 (left + half на текст) */
  --total-aspect: calc(var(--map-aspect) * 1.5);
}

/* у Куликова и Серпухова левый блок = карта с её натуральным аспектом */
.battle-stage,
.serpukhov-stage {
  --map-aspect: calc(2479 / 1949);
}

/* у Coin левый блок — квадратная монета 1:1 */
.coin-stage {
  --map-aspect: 1;
}

/* «Самый большой прямоугольник нужного аспекта, который влезает в stage».
   width = min(вся доступная ширина, доступная_высота × аспект).
   высота выводится автоматически из aspect-ratio. */
.battle-inner {
  width: min(100cqw, calc(100cqh * var(--total-aspect)));
  aspect-ratio: var(--total-aspect);

  display: flex;
  align-items: stretch;
}

/* Карта (или монета у coin-слайда) — колонка с собственным аспектом.
   На coin-стейдже --map-aspect=1, поэтому coin-frame получится квадратным. */
.battle-map,
.coin-frame {
  position: relative;
  flex: none;
  height: 100%;
  aspect-ratio: var(--map-aspect);
  overflow: hidden;
}

/* canvas с послойным reveal'ом карт (map-reveal.js рисует слои PNG'шек
   с paper-фоном в multiply-режиме). */
.battle-map-canvas,
.serpukhov-map-canvas,
.coin-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ───── Текстовая колонка ─────
   .battle-text — внешний бокс с fixed аспектом (= map_aspect / 2 = 0.636).
   .battle-text-inner — контейнер фиксированных design-размеров (500×786px).
     Внутри ВСЁ в фиксированных px при design-ширине 500. При изменении
     размера .battle-text трансформ scale() масштабирует контент как
     картинку — без reflow'а текста.

   Структура:
     .battle-text-inner
     ├── .battle-title  — фикс-высота 90px (не дёргается при пустом title)
     └── .battle-body   — relative, заполняет остаток
         └── .stage-body[N]  — absolute, opacity-crossfade + soft mask reveal
─────────────────────────────────────── */

.battle-text,
.serpukhov-text,
.coin-text {
  flex: none;
  height: 100%;
  aspect-ratio: calc(var(--map-aspect) / 2);

  position: relative;
  overflow: hidden;
  container-type: inline-size;   /* для cqw в .battle-text-inner */
}

.battle-text-inner {
  --design-w: 500;
  width:  calc(var(--design-w) * 1px);
  height: calc(var(--design-w) * 1px / (var(--map-aspect) / 2));

  /* масштабируем содержимое как картинку: scale = текущая ширина / design.
     length / length в современном calc даёт unitless — скейлу подходит */
  transform-origin: top left;
  transform: scale(calc(100cqw / var(--design-w) / 1px));

  padding-left: 30px;

  display: flex;
  flex-direction: column;

  font-family: var(--font-body);
  font-size: 23px;
  line-height: 1.7;
  color: var(--ink);
}

.battle-title {
  flex: 0 0 auto;
  height: 90px;                  /* фикс — empty title всё равно занимает свою высоту */

  display: flex;
  align-items: center;
  justify-content: center;

  font-family: var(--font-display);
  font-size: 40px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.01em;
  color: var(--ink);
  white-space: pre;              /* сохраняет хвостовые пробелы во время typewriter */
}

/* мигающий курсор; скрывается когда title пустой */
.battle-title::after {
  content: '|';
  display: inline-block;
  margin-left: 2px;
  color: var(--wax);
  font-weight: 400;
  animation: titleCursor 0.85s linear infinite;
}
.battle-title:empty::after { display: none; }

@keyframes titleCursor {
  0%, 49%   { opacity: 1; }
  50%, 100% { opacity: 0; }
}

.battle-body {
  flex: 1 1 auto;
  position: relative;
  min-height: 0;
}

.stage-body {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;

  opacity: 0;
  pointer-events: none;
  transition: opacity 0.35s ease;

  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

.stage-body.is-active {
  opacity: 1;
  pointer-events: auto;
}

.stage-body p {
  margin-bottom: 12px;
  text-align: justify;
  hyphens: auto;
}
.stage-body p:last-child { margin-bottom: 0; }

.stage-body strong {
  font-weight: 700;
  color: var(--ink);
}
.stage-body em {
  font-style: italic;
  color: var(--wax);
}

/* ───── Накапливающиеся абзацы (Серпухов-слайд) ─────
   Лежат прямо в .battle-text-inner внутри .serpukhov-text.
   Распределяются по высоте через space-between, чтобы 3 абзаца
   полностью заполнили колонку. Каждый — со своей soft-mask reveal.
   Markdown bold/italic поддержаны через тот же стиль что и .stage-body. */

/* у Серпуховского inner'а абзацы прижимаются к верху с компактным gap'ом —
   editorial/magazine feel вместо равномерного распределения */
.serpukhov-text .battle-text-inner {
  justify-content: flex-start;
  gap: 1.1em;
  padding-top: 8px;
}

/* у Coin-слайда несколько абзацев — кластером по центру по вертикали для
   баланса с квадратной монетой слева. gap даёт расстояние между абзацами */
.coin-text .battle-text-inner {
  justify-content: center;
  gap: 1em;
}

/* ───── CTA-блок последнего слайда ─────
   Подводка-вопрос + кнопка перехода в раздел «Серпухов сегодня». Лежит
   в .coin-text-inner после абзацев, под общим reveal-mask'ом текста.
   Стиль editorial: тонкая рамка цвета ink-line, italic шрифт, hover в
   сургучный. По дизайн-ширине 500px — кнопка центрирована, лид сужен.
─────────────────────────────────────── */
.coin-cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 18px;
  margin-top: 14px;
  text-align: center;
}

.coin-cta-button {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 600;
  font-size: 24px;
  color: var(--ink);
  text-decoration: none;
  padding: 12px 28px;
  border: 1px solid var(--ink-line);
  background: transparent;
  cursor: pointer;
  transition: color 0.25s ease, border-color 0.25s ease, background 0.25s ease;
}

.coin-cta-button:hover {
  color: var(--wax);
  border-color: var(--wax);
  background: rgba(139, 46, 46, 0.06);
}

.coin-cta-button-arrow {
  font-style: normal;
  font-size: 22px;
  transition: transform 0.25s ease;
}

.coin-cta-button:hover .coin-cta-button-arrow {
  transform: translateX(4px);
}

.cumulative-paragraph {
  text-align: justify;
  hyphens: auto;
  margin: 0;

  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}

/* буквица — крупная инициальная буква в первом абзаце, оборачиваемая текстом.
   Применяется одинаково и к Серпуховским cumulative-абзацам, и к первому
   параграфу каждой стадии Куликова. */
.cumulative-paragraph:first-of-type::first-letter,
.stage-body p:first-of-type::first-letter {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 3.6em;
  line-height: 0.82;
  color: var(--wax);
  float: left;
  margin: 0.06em 0.1em 0 0;
}

.cumulative-paragraph strong {
  font-weight: 700;
  color: var(--ink);
}
.cumulative-paragraph em {
  font-style: italic;
  color: var(--wax);
}

/* ───── Подсказка-шеврон под холстом 3D ───── */
.hero-hint {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  display: inline-flex;
  color: var(--ink-soft);
  pointer-events: none;
  z-index: 4;
  will-change: transform, opacity;
}

/* bob-анимация на inner-svg, чтобы внешний transform от JS-фазы не конфликтовал */
.hero-hint-chevron {
  display: block;
  animation: hintBob 2.2s ease-in-out infinite;
}

@keyframes hintBob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(5px); }
}

/* ───── Разделитель ───── */
.section-divider {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 56px var(--pad-x) 24px;
  display: flex;
  justify-content: center;
  color: var(--ink);
}

/* ───── Контент-секция ───── */
.content-section {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: 36px var(--pad-x) 140px;
}

.section-head {
  max-width: 760px;
  text-align: center;
  margin: 0 auto;
}

.section-eyebrow {
  display: inline-block;
  font-family: var(--font-script);
  font-size: 22px;
  letter-spacing: 0.04em;
  color: var(--wax);
  margin-bottom: 12px;
}

.section-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(36px, 5vw, 60px);
  line-height: 1.05;
  color: var(--ink);
  margin-bottom: 18px;
  letter-spacing: -0.005em;
}

.section-lead {
  font-family: var(--font-body);
  font-size: 18px;
  font-style: italic;
  color: var(--ink-soft);
}

/* ───── Адаптив ───── */
@media (max-width: 860px) {
  .topbar-brand { font-size: 20px; }
}

@media (max-width: 640px) {
  :root { --nav-h: 64px; }
  .topbar-brand { font-size: 18px; }
  .topbar-side { font-size: 13px; }
  .hero-subtitle { gap: 10px; }
}
