/* =========================================================
   СЛОЙ ФИЛЬМА И ХРОМ

   Один непрерывный ролик живёт под всей страницей.
   Скролл двигает его таймлайн; на «кадрах» фильм идёт,
   на текстовых секциях — замирает (см. film.js).
   ========================================================= */

.film {
  position: fixed;
  inset: 0;
  z-index: var(--z-film);
  overflow: hidden;
  background: var(--abyss) url("../img/poster.jpg") center / cover no-repeat;
  pointer-events: none;
}

.film__video,
.film__frames,
.film__still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Наезд ведёт скрипт: под сплошным текстом кадр чуть приближается,
     на «кадрах» отходит. Размытия здесь нет намеренно — полноэкранный
     blur по видео пересчитывался на каждом кадре перемотки. */
  transform: scale(var(--film-zoom, 1.045));
}

.film__video { opacity: 0; transition: opacity 1.2s var(--ease); }
.is-film-ready .film__video { opacity: 1; }

/* ---- Кадровый движок: телефону вместо видео ----
   Канвас держит собственный размер 540×960 — рисование идёт один к
   одному, без масштабирования, а до размера экрана его растягивает
   композитор. См. js/film.js. */
.film__frames {
  display: none;
  opacity: 0;
  transition: opacity 1.2s var(--ease);
}
.is-frames .film__frames { display: block; }
.is-frames.is-film-ready .film__frames { opacity: 1; }
.is-frames .film__video { display: none; }
.no-film .film__frames { display: none; }

/* стоп-кадры — режим без видео (reduced-motion или ошибка загрузки) */
.film__still { opacity: 0; transition: opacity 1s var(--slow); }
.no-film .film__still.is-on { opacity: 1; }
.no-film .film__video { opacity: 0; }

/* ---- Затемнение: посекционное, интерполируется скриптом ---- */
.film__dim {
  position: absolute;
  inset: 0;
  background: #02050b;
  opacity: 0.5;
  will-change: opacity;
}

/* ---- Вуаль: мягкое пятно тени там, где идёт сплошной текст.
   Живёт на фиксированном слое, а не на секциях: соседние читальные
   секции иначе дают видимую горизонтальную границу подложек.
   Пятно смещено влево — туда, где стоит текстовая колонка, — так что
   справа кадр остаётся открытым и продолжает дышать. ---- */
.film__veil {
  position: absolute;
  inset: 0;
  opacity: 0;
  will-change: opacity;
  background:
    radial-gradient(62% 82% at 32% 50%,
      rgba(2, 5, 11, 0.62) 0%,
      rgba(2, 5, 11, 0.38) 45%,
      rgba(2, 5, 11, 0.06) 78%,
      transparent 100%),
    linear-gradient(180deg,
      rgba(2, 5, 11, 0.46) 0%,
      transparent 24%,
      transparent 76%,
      rgba(2, 5, 11, 0.46) 100%);
}

/* ---- Донный скрим: тип на «кадрах» прижат к низу и должен
   лежать на плотной подложке, пока верх кадра остаётся открытым.
   Живёт здесь, а не на секциях: между двумя соседними «кадрами»
   секционные градиенты давали видимую горизонтальную полосу. ---- */
.film__scrim {
  position: absolute;
  inset: 0;
  opacity: 0;
  will-change: opacity;
  background: linear-gradient(
    180deg,
    rgba(2, 5, 11, 0.50) 0%,
    rgba(2, 5, 11, 0.12) 20%,
    rgba(2, 5, 11, 0.10) 38%,
    rgba(2, 5, 11, 0.48) 62%,
    rgba(2, 5, 11, 0.82) 82%,
    rgba(2, 5, 11, 0.92) 100%);
}

/* ---- Виньетка: собирает кадр и уводит края в тень ---- */
.film__vignette {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(125% 90% at 50% 44%,
      transparent 32%,
      rgba(2, 5, 11, 0.4) 76%,
      rgba(2, 5, 11, 0.8) 100%),
    linear-gradient(180deg,
      rgba(2, 5, 11, 0.5) 0%,
      transparent 20%,
      transparent 76%,
      rgba(2, 5, 11, 0.55) 100%);
}

/* ---- Зерно: плёночная фактура, заодно маскирует полосы на градиентах ---- */
.film__grain {
  position: absolute;
  top: -6%; left: -6%;
  width: 112%; height: 112%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: 0.055;
  will-change: transform;
  animation: grain 6s steps(6) infinite;
}
/* На телефоне зерно стоит на месте.

   Слой зерна занимает 112 % экрана и двигается бесконечно. На десктопе это
   дешёвый композиторский трюк, а на телефоне при плотности 3 это ещё одна
   полноэкранная текстура, которую GPU держит и перекладывает в одной
   очереди с декодером видео. Фактуру оставляем — она и нужна, чтобы
   маскировать полосы на градиентах, — а движение убираем. */
@media (pointer: coarse) {
  /* СЛОЙ ФИЛЬМА НЕ ДЫШИТ ВМЕСТЕ С ОКНОМ.

     На телефоне высота окна меняется постоянно и не по воле человека:
     прячется и возвращается адресная строка, вылезает баннер уведомления,
     поднимается клавиатура. Слой на inset: 0 при каждом таком движении
     менял размер, а кадр внутри пересчитывал обрезку по object-fit: cover
     и заново растеризовался на новый размер. Со стороны это выглядело
     ровно так, как описал заказчик: картинку то приближает, то отдаляет,
     и всё подтормаживает — хотя ни один кадр при этом не перезагружается.

     Поэтому высоту слоя задаём по САМОМУ БОЛЬШОМУ окну (lvh — это высота
     при спрятанной панели браузера) и больше не трогаем никогда. Что бы
     ни вылезло сверху или снизу, кадр стоит на месте: он просто чуть
     заходит за нижний край, пока панель видна. Этого не видно — низ кадра
     и так лежит под затемнением и виньеткой.

     Ровно то приближение, которое человек видит при спрятанной панели,
     и становится единственным. */
  .film {
    bottom: auto;
    /* С запасом в двенадцать процентов. Высота --vh снята при загрузке,
       когда панель браузера ещё на месте; когда она уезжает, окно
       становится выше, и слой ровно в сто высот оставил бы внизу полосу
       голого фона. Запас перекрывает любую панель, а лишнее просто
       уходит за нижний край — кадр и так обрезается по cover. */
    height: calc(var(--vh, 1lvh) * 112);
  }

  .film__grain {
    top: 0; left: 0;
    width: 100%; height: 100%;
    will-change: auto;
    animation: none;
  }

  /* Вуали на телефоне нет.

     Её пятно намеренно смещено влево — туда, где на широком экране стоит
     текстовая колонка, — чтобы справа кадр оставался открытым. На
     телефоне колонка одна и занимает весь экран: смещение бессмысленно, а
     слой остаётся — ещё одна полноэкранная поверхность, которую GPU
     смешивает на каждом кадре прокрутки поверх видео, затемнения, скрима,
     виньетки и зерна. Читаемость держат скрим и затемнение. */
  .film__veil { display: none; }
}

@keyframes grain {
  0%   { transform: translate3d(0, 0, 0); }
  16%  { transform: translate3d(-3%, -4%, 0); }
  33%  { transform: translate3d(2%, -2%, 0); }
  50%  { transform: translate3d(-2%, 3%, 0); }
  66%  { transform: translate3d(4%, 1%, 0); }
  83%  { transform: translate3d(-1%, -3%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* =========================================================
   ЗАСТАВКА
   Ждём, пока фильм действительно готов играть — без этого
   первые экраны выглядят как статичные картинки.
   ========================================================= */

.loader {
  position: fixed;
  inset: 0;
  z-index: var(--z-load);
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 1.8rem;
  padding: var(--gut);
  text-align: center;
  background: var(--abyss);
  transition: opacity 1s var(--ease), visibility 1s;
}
.is-loaded .loader { opacity: 0; visibility: hidden; }

.loader__bar {
  width: min(280px, 58vw);
  height: 1px;
  background: var(--hair);
  overflow: hidden;
}
.loader__fill {
  display: block;
  width: 0; height: 100%;
  background: var(--champ);
  transition: width 0.5s var(--slow);
}

.loader__pct {
  font-family: var(--ui);
  font-size: var(--t-label);
  letter-spacing: 0.3em;
  color: var(--bone-dim);
  font-variant-numeric: tabular-nums;
}

/* =========================================================
   ШАПКА — прячется при движении вниз, возвращается при движении вверх
   ========================================================= */

.header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-head);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem var(--gut);
  background: linear-gradient(180deg,
    rgba(3, 7, 15, 0.94) 0%,
    rgba(3, 7, 15, 0.84) 58%,
    rgba(3, 7, 15, 0.44) 84%,
    transparent 100%);
  border-bottom: 1px solid transparent;
  transform: translateY(-102%);
  transition: transform 0.7s var(--ease), border-color 0.7s var(--ease);
}
.is-header-on .header { transform: translateY(0); border-bottom-color: var(--hair); }

.header__mark {
  font-family: var(--display);
  font-size: 1.02rem;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.header__right { display: flex; align-items: center; gap: clamp(1rem, 2.4vw, 2.2rem); }

.header__price {
  font-family: var(--display);
  font-size: 1.1rem;
  color: var(--champ-hi);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.header .btn { padding: 0.72rem 1.5rem; letter-spacing: 0.24em; }

/* =========================================================
   ВОЗВРАТ В НАЧАЛО
   Появляется во второй половине страницы: кнопка брони уносит
   в самый низ, и вернуться было нечем.
   ========================================================= */

.totop {
  position: fixed;
  left: clamp(0.9rem, 1.8vw, 2rem);
  bottom: clamp(1rem, calc(var(--vh, 1svh) * 3), 2rem);
  z-index: var(--z-head);
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.75rem 1.2rem;
  border: 1px solid var(--hair-warm);
  /* без backdrop-filter: он лежит поверх кадра, который меняется
     каждый тик прокрутки, и размытие пересчитывалось бы бесконечно */
  background: rgba(3, 7, 15, 0.9);
  color: var(--champ-hi);
  font-family: var(--ui);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  text-decoration: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px);
  transition: opacity 0.5s var(--ease), transform 0.5s var(--ease), visibility 0.5s,
              background 0.4s var(--ease);
}
.is-deep .totop { opacity: 1; visibility: visible; transform: translateY(0); }
.totop:hover { background: rgba(216, 185, 143, 0.16); }

.totop__arrow {
  width: 1px; height: 1.1em;
  background: currentColor;
  position: relative;
}
.totop__arrow::before,
.totop__arrow::after {
  content: "";
  position: absolute;
  top: 0; left: 0;
  width: 7px; height: 1px;
  background: currentColor;
  transform-origin: left center;
}
.totop__arrow::before { transform: rotate(-45deg); }
.totop__arrow::after  { transform: rotate(-135deg); }

@media (max-width: 560px) {
  .totop span { display: none; }
  .totop { padding: 0.8rem; }
}

/* =========================================================
   РЕЛЬС — скраббер плёнки
   ========================================================= */

.rail {
  position: fixed;
  top: 50%;
  right: clamp(0.9rem, 1.8vw, 2rem);
  z-index: var(--z-rail);
  transform: translateY(-50%);
  display: grid;
  opacity: 0;
  transition: opacity 0.8s var(--ease);
}
/* рельс живёт своей жизнью: шапка может прятаться, главы — нет */
.is-past-hero .rail { opacity: 1; }

.rail__item {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.8rem;
  padding: 0.45rem 0;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--bone-dim);
  font-family: var(--ui);
  font-size: var(--t-label);
  letter-spacing: 0.26em;
  text-transform: uppercase;
  transition: color 0.45s var(--ease);
}

.rail__label {
  opacity: 0;
  transform: translateX(8px);
  white-space: nowrap;
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
  text-shadow: 0 1px 14px rgba(2, 5, 11, 0.95);
}

.rail__tick {
  width: 20px; height: 1px;
  background: currentColor;
  opacity: 0.45;
  transition: width 0.5s var(--ease), opacity 0.45s var(--ease), background 0.45s var(--ease);
}

.rail__item:hover,
.rail__item[aria-current="true"] { color: var(--champ-hi); }

/* подпись — только при наведении: иначе налезает на текст */
.rail__item:hover .rail__label { opacity: 1; transform: translateX(0); }
.rail__item:hover .rail__tick { width: 32px; opacity: 1; }

.rail__item[aria-current="true"] .rail__tick { width: 38px; opacity: 1; background: var(--champ); }

@media (min-width: 1400px) {
  .rail__item[aria-current="true"] .rail__label { opacity: 1; transform: translateX(0); }
}

/* рельс — часть раскладки, а не наклейка поверх: отдаём ему полосу справа */
@media (min-width: 901px) {
  .page { padding-right: clamp(2.5rem, 4vw, 4.5rem); }
}

/* =========================================================
   АДАПТИВ / ДОСТУПНОСТЬ
   ========================================================= */

@media (max-width: 900px) {
  .rail { display: none; }
  .header__price { display: none; }
}

/* Название стало длиннее («Ретрит «Новое состояние»»), и на узких
   андроидах оно выдавливало кнопку за экран. Даём марке ужиматься,
   а кнопке — не переносить свою надпись. */
@media (max-width: 620px) {
  .header { gap: 0.6rem; padding-inline: 1rem; }
  .header__mark {
    font-size: 0.82rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .header .btn {
    flex: 0 0 auto;
    padding: 0.6rem 0.85rem;
    letter-spacing: 0.12em;
    gap: 0.6em;
  }
  .header .btn__arrow { width: 1em; }
}

@media (prefers-reduced-motion: reduce) {
  .film__grain { animation: none; }
  .film__video { display: none; }
}
