/* =========================================================
   СЛОЙ ФИЛЬМА И ХРОМ

   Один непрерывный ролик живёт под всей страницей.
   Скролл двигает его таймлайн; на «кадрах» фильм идёт,
   на текстовых секциях — замирает (см. film.js).
   ========================================================= */

.film {
  position: fixed;
  inset: 0;
  z-index: var(--z-film);
  overflow: hidden;
  background: var(--void) url("../img/poster.webp?v=10") 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: var(--ink, #04031a);
  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(4, 3, 26, 0.62) 0%,
      rgba(4, 3, 26, 0.38) 45%,
      rgba(4, 3, 26, 0.06) 78%,
      transparent 100%),
    linear-gradient(180deg,
      rgba(4, 3, 26, 0.46) 0%,
      transparent 24%,
      transparent 76%,
      rgba(4, 3, 26, 0.46) 100%);
}

/* ---- Донный скрим: тип на «кадрах» прижат к низу и должен
   лежать на плотной подложке, пока верх кадра остаётся открытым.
   Живёт здесь, а не на секциях: между двумя соседними «кадрами»
   секционные градиенты давали видимую горизонтальную полосу. ---- */
.film__scrim {
  position: absolute;
  inset: 0;
  opacity: 0;
  will-change: opacity;
  background: linear-gradient(
    180deg,
    rgba(4, 3, 26, 0.50) 0%,
    rgba(4, 3, 26, 0.12) 20%,
    rgba(4, 3, 26, 0.10) 38%,
    rgba(4, 3, 26, 0.48) 62%,
    rgba(4, 3, 26, 0.82) 82%,
    rgba(4, 3, 26, 0.92) 100%);
}

/* ---- Виньетка: собирает кадр и уводит края в тень ---- */
.film__vignette {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(125% 90% at 50% 44%,
      transparent 32%,
      rgba(4, 3, 26, 0.4) 76%,
      rgba(4, 3, 26, 0.8) 100%),
    linear-gradient(180deg,
      rgba(4, 3, 26, 0.5) 0%,
      transparent 20%,
      transparent 76%,
      rgba(4, 3, 26, 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; }

  /* ...и её работу забирает скрим.

     Замер на iPhone-ширине 390: без вуали контраст белого текста к
     плёнке падал на первом экране до 2.9:1 при норме 4.5. Причина не в
     затемнении, а в раскладке: на телефоне колонка одна и текст идёт во
     всю ширину, то есть ложится и на самое светлое место кадра. На
     широком экране этого не бывает — там текст стоит в своей колонке, а
     вуаль накрывает именно её.

     Поднимать data-dim нельзя: он общий для обоих экранов, и на широком
     от плёнки перестало бы оставаться что-либо. Поэтому меняется только
     градиент скрима — слой тот же самый, лишней поверхности для
     видеокарты не появляется, а плотность в середине кадра, где и стоит
     текст, вырастает вдвое. */
  .film__scrim {
    background: linear-gradient(
      180deg,
      rgba(4, 3, 26, 0.92) 0%,
      rgba(4, 3, 26, 0.86) 18%,
      rgba(4, 3, 26, 0.70) 34%,
      rgba(4, 3, 26, 0.40) 56%,
      rgba(4, 3, 26, 0.52) 74%,
      rgba(4, 3, 26, 0.86) 92%,
      rgba(4, 3, 26, 0.94) 100%);
  }
}

@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); }
}

/* На этом файл заканчивается намеренно.

   В оригинале, на лендинге ретрита, дальше шли ещё четыреста строк:
   заставка, шапка, кнопка возврата, рельс глав. Там они и должны быть —
   у того проекта нет отдельного файла под интерфейс.

   Здесь весь этот хром уже описан в css/base.css, причём в индиговой
   палитре и со своей раскладкой. Оставить копии из ретрита значило бы
   получить два набора правил на одни и те же классы: они переопределяли
   друг друга по порядку подключения и тянули за собой переменные
   --champ и --abyss, которых в этом проекте нет вовсе. Кнопка возврата
   уехала в правый нижний угол, у стрелки отвалилась половина, рельс
   потерял засечки — всё это было именно оттуда.

   Этот файл отвечает ровно за слой фильма и ни за что больше. */
