/* =========================================================
   СЛОЙ ФИЛЬМА И ХРОМ

   Один непрерывный ролик живёт под всей страницей.
   Скролл двигает его таймлайн; на «кадрах» фильм идёт,
   на текстовых секциях — замирает (см. film.js).
   ========================================================= */

.film {
  position: fixed;
  inset: 0;
  z-index: var(--z-film);
  overflow: hidden;
  background: var(--abyss) url("../img/poster.webp?v=115") 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.1rem;
  padding: var(--gut);
  text-align: center;
  background: var(--abyss);
  transition: opacity 1s var(--ease), visibility 1s;
}
.is-loaded .loader { opacity: 0; visibility: hidden; }

.loader__name {
  font-family: var(--display);
  font-size: clamp(1.5rem, 1.1rem + 2vw, 2.4rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--bone);
  margin: 0;
}

.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);
  padding-right: calc(var(--lang-cell) * 2 + clamp(1rem, 2.4vw, 2.2rem));
}

.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: #03070f;
  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);
}

/* Кнопка уехала направо.

   Она висит поверх страницы, и текст под ней просто продолжается — на
   скриншотах заказчика чёрный прямоугольник лёг то на подпись под ценой,
   то на строку про маршрут, и это читается как брак вёрстки.

   Слева этого не избежать: там начинается КАЖДАЯ строка текста. Справа
   строки заканчиваются вразбежку, и до правого края добирается едва
   каждая третья — перекрытие становится редким исключением вместо
   правила. Запас снизу от домашней полосы айфона добавляем там же. */
.totop {
  left: auto;
  right: clamp(0.9rem, 1.8vw, 2rem);
  bottom: calc(clamp(1rem, calc(var(--vh, 1svh) * 3), 2rem) + env(safe-area-inset-bottom, 0px));
}
.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 {
    padding: 0.7rem 0.9rem;
    gap: 0.5rem;
    letter-spacing: 0.14em;
  }
}

/* =========================================================
   РЕЛЬС — скраббер плёнки
   ========================================================= */

.rail {
  position: fixed;
  top: 50%;
  right: clamp(0.9rem, 1.8vw, 2rem);
  z-index: var(--z-rail);
  transform: translateY(-50%);
  display: grid;
  opacity: 0;
  /* Прозрачности мало: прозрачная кнопка всё ещё ловит фокус. Рельс стоит
     в разметке ДО первого экрана, и человек с клавиатурой десять раз
     подряд терял курсор в невидимых кнопках, прежде чем добирался до
     «Забронировать место» — двенадцать нажатий Tab до первого действия.
     visibility убирает рельс и из порядка обхода тоже. */
  visibility: hidden;
  transition: opacity 0.8s var(--ease), visibility 0.8s;
}
/* рельс живёт своей жизнью: шапка может прятаться, главы — нет */
.is-past-hero .rail { opacity: 1; visibility: visible; }

.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__mark { display: none; }

  /* Мягкая растяжка от 94 % до прозрачного задумывалась как дымка над
     кадром. На широком экране так и читается. На телефоне шапка низкая, а
     заголовки крупные — и заголовок проступал ПРЯМО СКВОЗЬ цену и кнопку,
     как будто страница не дорисовалась. Поэтому здесь шапка держит
     плотность на всю свою высоту и растворяется только у самого края. */
  .header {
    background: linear-gradient(180deg,
      rgba(3, 7, 15, 0.97) 0%,
      rgba(3, 7, 15, 0.97) 72%,
      rgba(3, 7, 15, 0.86) 88%,
      transparent 100%);
  }
}

/* Название стало длиннее («Ретрит «Новое состояние»»), и на узких
   андроидах оно выдавливало кнопку за экран. Даём марке ужиматься,
   а кнопке — не переносить свою надпись. */
@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; }
}

/* =========================================================
   ВЫБОР ЯЗЫКА — виден с первого кадра

   Переключатель жил в шапке, а шапка приезжает только после первого
   экрана. Для человека, открывшего русскую страницу без русского языка,
   это значит, что языка нет вовсе: он решает уйти в первые же секунды,
   а листать двадцать тысяч пикселей в поисках кнопки не станет. Заказчик
   об этом сказал прямо: «вверху надо, чтобы СРАЗУ была возможность
   выбрать язык, а не потом при листании».

   Поэтому переключатель вынесен из шапки в отдельный слой поверх всего,
   включая заставку: выбор есть в ту же секунду, когда страница
   открылась, ещё до того как догрузился фильм.

   Показаны оба языка, а не одна кнопка «EN». Одна буква не отвечает на
   вопрос, что она значит — текущий язык или переход; пара с отмеченным
   текущим отвечает. Золотом здесь покрашено то, что можно нажать: на
   этой странице золото и так значит «действие».

   Поля у переключателя те же, что у шапки, а клетка ростом с кнопку
   брони. Поэтому, когда шапка приезжает, переключатель оказывается
   ровно в её строке, а не рядом с ней. Место под него шапка держит
   отступом справа — см. .header__right ниже.
   ========================================================= */

.langbar {
  position: fixed;
  top: 0;
  right: 0;
  z-index: var(--z-lang);
  display: flex;
  align-items: center;
  padding: 1rem var(--gut);
  /* Поля переключателя шире его самого на целую колонку, и невидимая
     рамка ложилась поверх правого края кнопки «Забронировать» — нажатие
     туда не доходило ни до чего. Ловят нажатия только сами клетки. */
  pointer-events: none;
}
.langbar__opt { pointer-events: auto; }

.langbar__opt {
  display: inline-grid;
  place-items: center;
  min-width: var(--lang-cell);
  min-height: var(--lang-h);
  padding: 0 0.6rem;
  border: 1px solid var(--hair-warm);
  font-family: var(--ui);
  font-size: var(--t-label);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-decoration: none;
  /* подложка своя: переключатель висит поверх кадра, который всё время
     меняется, и на светлых кадрах буквы иначе пропадают */
  background: rgba(3, 7, 15, 0.66);
  color: var(--champ-hi);
  transition: color 0.4s var(--ease), border-color 0.4s var(--ease),
              background 0.4s var(--ease);
}
/* две клетки — одна деталь, а не две кнопки рядом */
.langbar__opt + .langbar__opt { border-left: 0; }

.langbar__opt:hover { background: rgba(216, 185, 143, 0.18); border-color: var(--champ); }

/* Текущий язык — не кнопка, и нажимать в нём нечего. Он приглушён
   намеренно: внимания требует не он, а вторая клетка. */
.langbar__opt.is-on {
  background: rgba(240, 232, 218, 0.1);
  border-color: var(--hair);
  color: var(--bone);
  cursor: default;
}

/* ---- Шапка и переключатель языка на телефоне ----

   Заказчик: «переключатель языков уплывает к строке и невозможно нажать».
   Так и есть. Шапка прибита к top: 0, а встроенный браузер Телеграма и
   свежий сафари рисуют адресную строку ПЛАВАЮЩЕЙ таблеткой поверх
   страницы — она ложится ровно на первые сорок-пятьдесят пикселей. Узнать
   о ней из стилей нельзя: для страницы её не существует.

   Значит, туда просто нельзя ничего класть. Отодвигаем вниз на вырез плюс
   запас и шапку, и переключатель — под таблеткой остаётся только фон. */
@media (pointer: coarse), (hover: none) {
  .header,
  .langbar {
    padding-top: calc(env(safe-area-inset-top, 0px) + 3.1rem);
    padding-bottom: 0.9rem;
  }

  /* на тач-экране приём наведения не сработает — рамка живая сразу */
  .langbar__opt { border-color: rgba(216, 185, 143, 0.62); }
}
