/* ==========================================================================
   The Body Edit — залипающая сцена (hero + hinnat), локап, полёт в шапку
   Анимируются только transform / opacity / filter.
   Шрифт, токены, базовая типографика, сама шапка, переключатель языка
   и .cta — в base.css, он подключён первым. Здесь только то, чего нет
   на страницах без сцены.

   Раскладка:
     .stage__pin лежит в потоке и сам даёт свои 100svh,
     рельсы идут следом и задают ход прогресса.

       десктоп:  100 + 220 + 260 = 580svh,  ход панели 480svh
       <720px:   100 + 140 + 150 = 390svh,  ход панели 290svh

   Стартовое состояние p=0 прописано здесь же, чтобы до первого кадра rAF
   ничего не моргнуло. Числа выведены из формулы позиционирования:

     tx = contW*(f - 0.5) - imgW*S*(T - 0.5),   imgW = contH * AR

   side  @ p=0:  AR 2.910204, S 1.30, T 0.754, f 0.80
                 tx = 0.30*100vw - (2.910204*1.30*0.254)*100svh
                    = 30vw - 96.0949svh
   front @ p=0:  AR 2.896879, S 1.40, T 0.495, f 0.50
                 tx = 0 - (2.896879*1.40*(-0.005))*100svh = +2.0278svh
   ========================================================================== */

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   Сцена
   Ни один предок .stage__pin не должен получать transform / filter /
   will-change — position: sticky от этого перестаёт работать.
   svh, а не dvh: dvh пересчитывается при сворачивании адресной строки
   на мобильных, и прогресс дёргается прямо посреди жеста.
   -------------------------------------------------------------------------- */

.stage { position: relative; }

.stage__pin {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  background: var(--shadow);
}

.rail--hero   { height: 220vh; height: 220svh; }
.rail--prices { height: 260vh; height: 260svh; }

/* --narrow читает scene.js: порог 719px записан здесь один раз, в JS его нет.
   По нему переключаются числа треков — раскладка на телефоне та же, что
   на десктопе, отличаются только якорь стола, масштабы и размах панорамы. */
.stage__pin { --narrow: 0; }

@media (max-width: 719px) {
  .stage__pin   { --narrow: 1; }
  .rail--hero   { height: 140vh; height: 140svh; }
  .rail--prices { height: 150vh; height: 150svh; }
}

/* --------------------------------------------------------------------------
   Слой кадров
   Блюр висит на обёртке, а не на каждой картинке: размытие линейно,
   кроссфейд — линейная комбинация, они коммутируют, поэтому
   blur(a*A + b*B) === a*blur(A) + b*blur(B). Результат тот же,
   проходов размытия вдвое меньше.
   Свойство filter выставляется из JS только внутри окна перехода
   и снимается за его пределами — blur(0px) всё равно стоит одного
   лишнего прохода композитора.
   -------------------------------------------------------------------------- */

.scene__frames { position: absolute; inset: 0; }
.scene__frames.is-blurring { will-change: filter; }

/* (Затухание внизу кадра вынесено ниже, в отдельный слой .scene__fade —
   оно обязано лежать ПОВЕРХ вуали, см. комментарий там.) */

.scene__img {
  position: absolute;
  top: 0;
  left: 50%;
  height: 100%;
  width: auto;
  max-width: none;
  transform-origin: center center;
  will-change: transform, opacity;   /* 1, 2 из трёх — кадры едут непрерывно */
  user-select: none;
  -webkit-user-drag: none;
}

.scene__img--side {
  opacity: 1;
  transform: translateX(-50%) translateX(calc(30vw - 96.0949vh))  scale(1.30);
  transform: translateX(-50%) translateX(calc(30vw - 96.0949svh)) scale(1.30);
}

.scene__img--front {
  opacity: 0;
  transform: translateX(-50%) translateX(2.0278vh)  scale(1.40);
  transform: translateX(-50%) translateX(2.0278svh) scale(1.40);
}

/* --------------------------------------------------------------------------
   Вуаль
   Радиальный градиент, а не заливка: в центре альфа 0.90 против 1.00
   по краям. Разница множится на текущую opacity слоя, поэтому на пике
   перехода (0.88) центр светлее краёв на 0.88 * 0.10 ≈ 0.088, а на старте
   (0.15) разница 0.015 и незаметна. Кино-затемнение вместо выключенного
   экрана ровно там, где оно нужно.
   -------------------------------------------------------------------------- */

.scene__veil {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: .15;
  background: radial-gradient(ellipse 78% 62% at 50% 54%,
    rgb(var(--veil-rgb) / .90)  0%,
    rgb(var(--veil-rgb) / .935) 45%,
    rgb(var(--veil-rgb) / 1)   100%);
}

/* Подложка под текст. Локальное пятно вместо подъёма общей вуали:
   радиус 45% ширины, плотность 0.35 в центре, края в ноль. */
.scene__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(ellipse max(45vw, 19rem) max(45vw, 19rem) at 50% 48%,
    rgb(var(--veil-rgb) / .35)  0%,
    rgb(var(--veil-rgb) / .252) 55%,
    rgb(var(--veil-rgb) / 0)   100%);
}

/* Затухание внизу панели.
   Стык сцены с плоским фоном расходился по тону: нижняя кромка снимка —
   тёплый деревянный пол, сырой пиксель rgb(41,15,2), и вуаль его в зелёный
   не уводит. Полоса растворяет фотографию в фоне вместо обрыва линией.

   Порядок слоёв критичен и стоил одной итерации. Полоса обязана лежать:
     ПОВЕРХ вуали — иначе вуаль (0.66 от --veil) перекрашивает уже затухший
       низ обратно в свой тон, и при --veil темнее --shadow стык возвращается;
     ПОД текстом — на высоте панели 420px вывод «Hinta perustuu…» стоит
       в 24px от нижнего края, целиком внутри полосы, и сверху она смыла бы его.
   Отсюда позиция в DOM: после .scene__veil и .scene__scrim, до .layer.
   Отдельным элементом, а не псевдоэлементом вуали: у вуали opacity ведётся
   из JS, и полоса унаследовала бы её прозрачность. */
.scene__fade {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 120px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgb(var(--shadow-rgb) / 0), var(--shadow));
}

/* --------------------------------------------------------------------------
   Текстовые слои — вне .scene__frames, поэтому блюр их не задевает
   -------------------------------------------------------------------------- */

.layer {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 0 6vw;
  pointer-events: none;
}

/* --- открывающая сцена ---------------------------------------------------- */

.hero__lead {
  position: relative;
  text-align: center;
  transform: translateY(0);
}

.hero__slogan {
  margin: 0;
  opacity: 0;
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.75rem, 5.6vw, 4rem);
  line-height: 1.18;
  letter-spacing: .005em;
  transform: translateY(18px);
}

.hero__slogan span { color: var(--gold); }


/* --- цены ----------------------------------------------------------------- */

.prices__inner {
  position: relative;
  width: min(34rem, 82vw);
  transform: translateY(0);
  will-change: transform;            /* 3 из трёх */
}

.prices { margin: 0; }

/* Регистрируем --rule числом, чтобы значение было типизированным.
   inherits ОБЯЗАН быть true: линию рисует ::after, а псевдоэлемент
   получает пользовательское свойство только по наследованию от своего
   элемента. При inherits:false var(--rule) внутри ::after резолвится
   в initial-value, то есть в ноль, и линия не появляется никогда. */
@property --rule {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}

.price {
  --rule: 0;                         /* если @property не поддержан, свойство
                                        остаётся незарегистрированным и до
                                        первой записи из JS было бы пустым */
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1em;
  padding-bottom: .62em;
  margin-bottom: 1.15em;
  opacity: 0;
  transform: translateY(18px);
  font-family: var(--serif);
  font-size: clamp(1.15rem, 3vw, 1.7rem);
  line-height: 1.2;
}

.price dt { letter-spacing: .02em; }
.price dd { margin: 0; }

.price::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--gold);
  opacity: .4;
  transform: scaleX(var(--rule));
  transform-origin: left center;
}

.verdict {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin: 1.6em 0 0;
  opacity: 0;
  color: var(--gold);
  font-family: var(--serif);
  font-size: clamp(1.35rem, 3.6vw, 2.1rem);
  line-height: 1.28;
  text-align: center;
  transform: translateY(18px);
}

/* --------------------------------------------------------------------------
   Шапка — сама полоса, знак и слово в ней описаны в base.css.
   Здесь только то, что нужно полёту и страховкам сцены.
   -------------------------------------------------------------------------- */

/* Снимается только на время замера целей полёта: getBoundingClientRect()
   у сдвинутой шапки вернул бы смещённые координаты. transition тоже гасим —
   замер форсирует пересчёт стилей, и без этого снятие класса запустило бы
   анимацию обратно. */
.topbar.is-measuring {
  transform: none !important;
  transition: none !important;
}

/* --------------------------------------------------------------------------
   Страховка: сцены нет (классы no-js и no-scene, см. подробности в content.css)
   no-js — скриптов нет вовсе; no-scene — не доехал именно scene.js.
   Правила общие, потому что видимый результат один и тот же.
   ВАЖНО: сюда нельзя добавлять no-reveal. Он означает «не доехал content.js»,
   а scene.js в этот момент жив и считает прогресс по высотам рельсов —
   спрятанные рельсы дали бы ему нулевой ход и сломали сцену.

   Одной видимости текста мало — без scene.js ломается ещё три вещи, и каждая
   по-своему:
     .flight — фиксированный слой во весь экран, скрывает его JS в момент
       подмены. Без JS знак и слово «Ventell» висят посреди экрана поверх
       ВСЕЙ страницы на любой прокрутке;
     .rail — задают ход прогресса, которого без JS нет. 480svh пустой
       прокрутки превращают сцену в пять экранов одной фотографии;
     .topbar — базово opacity 0 и pointer-events: none, её выводит scene.js.
       Без него недоступны и кнопка записи, и переключатель языка.
   Шапку приводим к тому же виду, что на страницах политики, — правило
   .topbar--static в base.css.
   -------------------------------------------------------------------------- */

.no-js .flight,
.no-scene .flight { display: none; }

.no-js .rail,
.no-scene .rail { display: none; }

.no-js .topbar,
.no-scene .topbar {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.no-js .topbar__brand,
.no-scene .topbar__brand {
  opacity: 1;
  visibility: visible;
}

/* Подчинённая: без рамки и заливки — обведённая кнопка в шапке остаётся одна.
   Кегль тот же, что у слова Ventell. */
.topbar__link {
  position: relative;
  color: var(--gold);
  font-size: .78rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
}

/* Зона нажатия — псевдоэлементом, как у .langs__item ниже. Паддингом нельзя:
   он увёл бы подчёркивание вниз от текста, а оно здесь и есть индикатор
   наведения. Замерено до правки: 88.6×19.3 при минимуме 24×24 по высоте.
   Соседей рядом нет ближе 1.75rem (зазор .topbar__nav), поэтому -4px
   по вертикали ни с чем не сливаются.
   На узкой ветке ссылка скрыта целиком, там это правило не работает. */
.topbar__link::after {
  content: '';
  position: absolute;
  inset: -4px 0;
}

.topbar__link:hover,
.topbar__link:focus-visible {
  border-bottom-color: currentColor;
}

/* --------------------------------------------------------------------------
   Слой полёта — фиксированный, поверх шапки.
   Панель залипшая на всём протяжении p, поэтому центрирование во вьюпорте
   даёт ту же точку, что и центрирование в панели.
   -------------------------------------------------------------------------- */

.flight {
  position: fixed;
  inset: 0;
  z-index: 101;
  display: grid;
  place-items: center;
  padding: 0 6vw;
  pointer-events: none;
  text-align: center;
}

/* Без собственного transform: дельта летящих детей считается в координатах
   вьюпорта, и трансформ на родителе увёл бы её в другую систему. */
.flight__lockup { transform: none; }

/* Класс на самом <svg>, а не на обёртке: у блочной обёртки прямоугольник
   равен ширине локапа, и замер старта дал бы не знак, а контейнер. */
.flight__mark {
  display: block;
  /* 42svh — страховка для низких экранов: знак 300px даёт 245px высоты,
     и со словом и подписью локап переставал помещаться в панель 420px. */
  width: min(300px, 34vw, 42svh);
  height: auto;
  margin: 0 auto;
  color: var(--gold);
  fill: currentColor;
}

.flight__word {
  margin-top: 1.6rem;
  font-size: 1.5rem;   /* цель 12.48px, значит масштаб полёта 0.520 */
}

.flight__sub {
  margin: 1.1rem 0 0;
  color: var(--ink);
  font-size: clamp(.72rem, 1.9vw, .9rem);
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: .16em;
  text-transform: uppercase;
}

/* Узкая ветка шапки (слово «Ventell», кегли, паддинги) — в base.css.
   Здесь только ссылка на секцию, которой на страницах без сцены нет. */
@media (max-width: 719px) {
  /* Ссылка на секцию уходит: с переключателем языка правая группа
     перестала помещаться. Замерено при 11px и зазоре 1rem — знак 29.4
     + FI/EN 51.7 + кнопка 137.8 + паддинги дают 283px, а со ссылкой
     «For Companies» — 400px при экране 360. Секция остаётся достижимой
     прокруткой, кнопка записи и переключатель важнее.
     Убрана в ОБЕИХ версиях: состав шапки не должен зависеть от языка. */
  .topbar__link { display: none; }
}

/* --------------------------------------------------------------------------
   prefers-reduced-motion: без сглаживания (это в JS) и без блюра.
   (Переходы .cta и .topbar снимает base.css.)
   Здесь страхуемся на случай, если filter успел выставиться до того,
   как JS прочитал настройку.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .scene__frames { filter: none !important; }
}
