/* ============================================================
   ПЛЁНКА — раскладка

   Логика в assets/film.js. Здесь только то, как плёнка занимает место на
   странице.

   Устройство одно на все шесть сцен: обёртка `.film` держит запас
   прокрутки, внутри неё залипающий экран `.film-viewport` на всю высоту
   окна, в нём холст и подписи. Экран не уезжает, пока плёнка не
   кончится, — поэтому и нет ощущения, что листаешь страницу: меняется
   только то, что нарисовано.

   Базовое состояние (без класса film-on) — обычный поток: если скрипт не
   выполнился, страница остаётся читаемой страницей, а холст не занимает
   места.
   ============================================================ */

.film { position: relative; }
.film-canvas { display: none; }

/* ВЫСОТА КАДРА — lvh, А НЕ svh.

   На телефоне у окна две высоты: маленькая (адресная строка развёрнута)
   и большая (свёрнута). svh — это маленькая, и залипающий кадр ростом в
   100svh на прокрутке, когда строка прячется, оказывается НИЖЕ экрана:
   под ним вылезает полоса фона страницы. Ровно она и была видна внизу
   на всех сценах плёнки.

   lvh — большая высота: кадр всегда закрывает экран целиком. Плата за
   это — со развёрнутой адресной строкой нижняя полоса кадра уходит под
   неё. Это меньшее зло: предмет сцены стоит по центру, а пустая полоса
   бумаги посреди тёмной сцены читается сломанной вёрсткой.

   Обе величины СТАТИЧНЫЕ, к текущему состоянию адресной строки не
   привязаны, — то есть resize во время жеста плёнку по-прежнему не
   дёргает (см. onResize в film.js). dvh здесь брать нельзя ровно
   поэтому: он меняется на каждом кадре сворачивания строки.

   100vh в запасной строке — для браузеров без lvh; на iOS Safari он и
   так равен большой высоте. */
html.film-on .film {
  /* ЗАПАСНОЕ ЗНАЧЕНИЕ. Настоящую высоту ставит film.js на init: она
     считается из весов сцен (FILM_SCROLL = сумма весов × 37,5 пикселя на
     единицу) и потому не может разойтись с ними — а разошлась однажды
     именно потому, что число жило здесь и правилось руками.

     Здесь остаётся то же самое число на случай, если скрипт не доехал:
     238 единиц × 37,5 = 8925. Плёнка без скрипта всё равно не идёт, но
     высота полосы под ней остаётся правильной. */
  height: calc(100vh + 8925px);
  height: calc(100lvh + 8925px);
}

html.film-on .film-viewport {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100lvh;
  overflow: hidden;
  /* Фон ведёт film.js: тёмный мир телефона светлеет до бумаги внутри
     одного экрана, а не стыком двух секций.

     ЗАПАСНОЕ ЗНАЧЕНИЕ — ЧЕРНИЛА, А НЕ БУМАГА. Плёнка открывает страницу
     и первый её кадр всегда тёмный. Пока стояла бумага, первая отрисовка
     шла светлой и перекрашивалась в тёмное ровно тогда, когда до сцены
     доходила очередь скрипта, — это и читалось как «чёрное появляется
     резко». Теперь первый же кадр рисуется тем цветом, которым сцена и
     начинается, и перекрашивать нечего.

     Переход сюда добавлять нельзя: дальше этот же фон ведёт прокрутку
     кадр в кадр, и любая анимация цвета превратилась бы в отставание
     фона от сцены на всём протяжении плёнки. */
  background: var(--stage-bg, var(--ink));
}

/* ---------- колонка текста и кадр ----------
   Слева текст, справа объект. Полосами сверху и снизу текст ложился
   поверх объекта и терялся у нижнего края экрана; колонкой они не
   пересекаются вовсе, и читать можно не отрываясь от анимации.

   Холст при этом остаётся во всю ширину экрана, а не занимает правую
   ячейку сетки. Причина одна: точка рождается на месте кружка из
   заголовка, а заголовок стоит слева — будь холст только справа, точке
   негде было бы появиться. Объект вместо этого сдвинут вправо внутри
   самого кадра (SC в film.js), поэтому с колонкой он не пересекается. */

html.film-on .film-canvas {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Один столбец на сцену: заголовок и содержание стоят в нём друг под
   другом и центрируются как одно целое.

   Раньше это были две независимые подписи с фиксированными местами —
   заголовок на 30% высоты, содержание прижато к низу. Расстояние между
   ними тогда зависело от длины текста, и от сцены к сцене блок стоял
   каждый раз по-разному: в одной пусто в середине, в другой текст
   почти во весь экран. Теперь по вертикали центрируется собранный блок,
   и все шесть сцен держат одну композицию.

   Высота блока по ходу сцены не меняется: абзацы, которые копятся,
   занимают своё место с самого начала и только проявляются. Поэтому
   центр стоит намертво и текст не подпрыгивает.

   Коробка повторяет .grid из index.html — та же максимальная ширина и те
   же поля. Левый край текста встаёт ровно на левый край логотипа в
   шапке, и вертикаль страницы не ломается на плёнке. */
html.film-on .cap-col {
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 40px;
  z-index: 2;
  pointer-events: none;
  transform: translateY(-50%);
}

html.film-on .cap {
  width: min(46%, 620px);
  opacity: 0;
  /* --cap-y ведёт film.js: подпись приходит снизу и уходит вверх, по ходу
     времени плёнки. */
  transform: translateY(var(--cap-y, 0px));
}
html.film-on .cap a,
html.film-on .cap button { pointer-events: auto; }

/* Отбивка между заголовком сцены и её содержанием. Единственное, что
   отделяет их друг от друга, — поэтому она одна на все сцены. */
html.film-on .cap-top + .cap-bottom { margin-top: clamp(26px, 3.6vh, 46px); }

/* ---------- подсказка листать ----------
   Стоит по центру нижнего края кадра, а не в колонке текста: жест
   относится ко всей странице, а не к тому, что написано слева.
   Показ включает film.js классом is-on — через три секунды после того,
   как кадр перестал двигаться (applyScrollHint). Прозрачность здесь, а
   не инлайном из скрипта: появление подсказки не привязано к прокрутке,
   у него своя длительность, и переход ей задаёт CSS.

   Без film-on её нет вовсе: не выполнился скрипт — страница обычная,
   листать подсказывать нечему. */
.scroll-hint { display: none; }
html.film-on .scroll-hint {
  position: absolute;
  left: 0;
  right: 0;
  /* Выше нижнего края: у самой кромки подсказка читается служебной
     строкой браузера, а не частью кадра. */
  bottom: clamp(48px, 9vh, 104px);
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  opacity: 0;
  pointer-events: none;
  /* Акцентным: это единственное на первом экране, что просит действия, —
     и единственное, чему на странице положен акцентный цвет. */
  color: var(--route);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  /* Появление мягче исчезновения: подсказка приходит сама и не должна
     выпрыгивать, а уходит по действию человека — там задержка читалась
     бы как «не среагировало». */
  transition: opacity .45s ease, transform .45s ease;
  transform: translateY(6px);
}
html.film-on .scroll-hint.is-on {
  opacity: 1;
  transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
  html.film-on .scroll-hint { transition: opacity .2s linear; transform: none; }
  html.film-on .scroll-hint.is-on { transform: none; }
}
html.film-on .scroll-hint svg {
  width: 12px;
  height: 22px;
  /* Стрелка подталкивает сама: неподвижная читается как значок, а не как
     приглашение к действию. Ход маленький — рядом ничего не движется, и
     большего не нужно. */
  animation: scroll-hint-nudge 1.9s ease-in-out infinite;
}
@keyframes scroll-hint-nudge {
  0%, 100% { transform: translateY(0); opacity: .5; }
  50% { transform: translateY(5px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  html.film-on .scroll-hint svg { animation: none; opacity: .8; }
}

/* ---------- цвета подписей ----------
   Идут от темы сцены и меняются вместе с фоном: тёмный мир телефона
   светлеет до бумаги внутри одного экрана. */
.film-viewport .section-title,
.film-viewport h1 { color: var(--stage-fg, var(--ink)); }
.film-viewport .section-sub,
.film-viewport .cap-lead,
.film-viewport .cap-aside,
.film-viewport .hero-sub,
.film-viewport .hero-note { color: var(--stage-fg-dim, var(--ink-70)); }
.film-viewport .cap-lead b:not(.mark) { color: var(--stage-fg, var(--ink)); }
/* Акцент на тёмном берём светлый: --route-ink рассчитан на контраст с
   бумагой и на чернилах уходит в нечитаемое. */
.film-viewport.is-dark .accent,
.film-viewport.is-dark .mark { color: var(--route); }
/* На тёмном ЗАГОЛОВОК И ВЫДЕЛЕНИЯ ИДУТ АКЦЕНТОМ.

   На бумаге у блока три ступени тона: чернильный заголовок, чернильный
   текст, акцентные выделения. На чернилах ступеней оставалось две —
   бумага и бумага, — и блок читался одной сплошной серой массой:
   заголовок ничем не отличался от абзаца под ним.

   Оранжевый на чернилах даёт 5,2:1 — крупному начертанию с запасом.
   Мелкий текст в акцент не уводим: там запаса уже нет. */
.film-viewport.is-dark .section-title { color: var(--route); }
.film-viewport.is-dark .cap-lead b:not(.mark) { color: var(--route); }
.film-viewport.is-dark .btn { background: var(--route); color: var(--ink); }
.film-viewport.is-dark .btn:hover,
.film-viewport.is-dark .btn:focus-visible { background: var(--paper); color: var(--ink); }

/* ---------- последняя сцена: акцентный фон ----------
   Крышка тубуса раскрутилась и заполнила кадр — дальше всё лежит на
   оранжевом, и весь текст на нём ВЫВОРОТНЫЙ.

   Раньше здесь была смешанная раскладка: заголовок бумагой, остальное
   чернилами. Считалась она по контрасту — чернила на #E0562C дают ровно
   4,5:1, бумага 3,16:1, — но в кадре читалась поломкой. Блок приходит
   единым оранжевым полем, и текст на нём обязан быть одного цвета:
   разнобой выглядит так, будто часть строк не успела перекраситься.

   Что это значит для контраста, прямо: 3,16:1 проходит норму для
   крупного начертания (заголовок, полужирные названия пунктов) и не
   дотягивает до 4,5:1 на мелких пояснениях. Держим их поэтому в полную
   плотность, без приглушения (--stage-fg-dim к концу уходит в 1), и не
   уводим ни в какую прозрачность.

   Акцентного ЦВЕТА на акцентном фоне быть не может — оранжевый по
   оранжевому не виден. Но ступени тона нужны и здесь, иначе блок опять
   читается сплошной массой. Роль акцента берут ЧЕРНИЛА: на оранжевом
   это самый плотный из доступных тонов, 4,5:1, и он же единственный,
   который на этом фоне вообще работает.

   Отсюда раскладка: выворотная бумага — названия и основной текст,
   чернила — выделенная часть заголовка и пояснения под названиями.

   Пояснение чернилами, а название бумагой — это не перевёрнутая
   иерархия. Иерархию здесь несёт НАЧЕРТАНИЕ: название полужирное и на
   кегль крупнее, и оно остаётся первым, что читают. Чернила достаются
   строке-пояснению как второй голос: он тише по смыслу, но по цвету
   отделён — так пара «что» и «почему» перестаёт слипаться в один серый
   абзац. */
.film-viewport.is-route .section-title,
.film-viewport.is-route .mark,
.film-viewport.is-route .stack-list b { color: var(--paper); }
.film-viewport.is-route .section-title .accent,
.film-viewport.is-route .accent,
.film-viewport.is-route .stack-list span,
.film-viewport.is-route .stack-list li::before { color: var(--ink); }
.film-viewport.is-route .btn { background: var(--ink); color: var(--paper); }
.film-viewport.is-route .btn:hover,
.film-viewport.is-route .btn:focus-visible { background: var(--paper); color: var(--ink); }

/* ---------- типографика подписей ---------- */
.cap-hero h1 {
  font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1.02;
  font-size: clamp(30px, 3.4vw, 54px);
  margin: 0 0 22px;
  text-wrap: balance;
}
.cap-hero .hero-sub { font-size: clamp(15px, 1.2vw, 18px); margin: 0 0 26px; }
.cap-hero .hero-ctas { display: flex; gap: 18px; flex-wrap: wrap; }
.cap-hero .hero-note {
  margin: 16px 0 0;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 12px; line-height: 1.5;
}
/* Кегль наследуется общий — тот же, что у заголовков секций на
   странице. Свой размер здесь был (24…38 против 34…56 на панелях), и он
   и делал заголовки плёнки заметно мельче остальных: над кадром они
   казались подписями, а не названиями блоков. */
.film-viewport .section-title {
  margin: 0;
  letter-spacing: -.025em;
  line-height: 1.02;
  text-wrap: balance;
}
.cap-lead {
  margin: 0 0 15px;
  font-size: clamp(15px, 1.2vw, 18px);
  line-height: 1.55;
  text-wrap: pretty;
}
.cap-lead:last-child { margin-bottom: 0; }
.cap-bottom .btn { margin-top: 6px; }

/* ---------- абзац, который держит фразу одной строкой ----------

   «Неважно, что за момент — рейв, поездка, музей с близким человеком»
   и следом вывод «Важно, что он был». Вывод отбит жёстким переносом, и
   в колонке шириной 46% первая фраза на средних окнах (900…1250 px) не
   помещалась: она рвалась после «музей с», и «близким человеком.»
   оставалось сиротой посреди трёх строк — при том, что читается всё
   как две части одной мысли.

   Абзац берёт ровно ту ширину, которая нужна фразе (35em при её
   собственном кегле), и только если колонка уже.

   Потолок считается от ПРЕДМЕТА, а не подобран: предмет плёнки стоит
   на оси 880 из 1200 и шириной в карточку, то есть его левый край —
   61% ширины кадра. Дальше этого текст не идёт ни при каких условиях;
   40px — поле колонки. Ниже примерно 1000px фраза в такой потолок уже
   не влезает и переносится, как раньше, — это честнее, чем заехать
   текстом на предмет.

   На узком экране правила нет вовсе: там колонка и так во всю ширину,
   а фраза в неё не влезет ни при каком кегле. */
@media (min-width: 901px) {
  html.film-on .cap-lead.hold-line {
    width: max(100%, 35em);
    max-width: min(calc(58vw - 40px), 620px);
  }
}

/* Абзацы внутри накопительного блока проявляются каждый в свой момент,
   поэтому подъём положен и им, а не только контейнеру. */
.cap [data-in] { transform: translateY(var(--cap-y, 0px)); }

/* Тезис — итог блока «О продукте», и стоит он ОТДЕЛЬНЫМ КАДРОМ, один
   посреди экрана (см. свой cap-col в разметке). Раз он один, ему и кегль
   положен другой: в общем блоке он был чуть крупнее абзацев, а один в
   кадре обязан держать экран сам. */
.cap-thesis {
  margin: 0;
  font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: clamp(26px, 2.6vw, 40px);
  line-height: 1.08;
  letter-spacing: -.03em;
  color: var(--stage-fg, var(--ink));
  text-wrap: balance;
}
/* Одиночная подпись пошире обычной колонки: в 46% строка тезиса рвётся
   на четыре куска и перестаёт читаться одной мыслью. */
html.film-on .cap-mid { width: min(58%, 760px); }

/* Оговорка под списком: тише самого списка, но не мельче читаемого. */
.cap-aside {
  margin: 18px 0 0;
  font-size: 13px;
  line-height: 1.55;
  text-wrap: pretty;
}
/* ОГОВОРКА ВСТАЁТ В ТУ ЖЕ КОЛОНКУ, ЧТО И ТЕКСТ СПИСКА.

   У «Первых пяти карточек» строки списка разложены сеткой: слева номер
   (2.4em), справа текст. Оговорка под списком — отдельный абзац, и
   начиналась она от края колонки, то есть на полтора сантиметра левее
   всего, что над ней. Читалось как чужой блок, приехавший из другого
   места.

   Сдвиг считается из тех же чисел, что и сетка списка: ширина колонки
   номера плюс её промежуток. Правится сетка — правится и это.

   em здесь берётся от КЕГЛЯ СПИСКА, а не от своего: у оговорки он 13,
   у списка — clamp(14…16), и на 2.4em разница выходит в пять-семь
   пикселей. Ровно на них текст и не сходился. */
.stack-list ~ .cap-aside { margin-left: calc(2.4 * clamp(14px, 1.05vw, 16px) + 12px); }


/* ---------- пять шагов ----------
   В колонке пять штук в ряд не помещаются — идут списком. Активный шаг
   подсвечивается по ходу сцены: это не декор, по нему видно, какой шаг
   показывает кадр. */
.film-steps { margin: 0; padding: 0; list-style: none; display: grid; gap: 0; }
/* Кегль и отбивки подняты: в колонке оставалось пустое место до нижней
   кромки карточки в кадре, а список стоял мелким и жался кверху. Расти
   ему есть куда — сама сцена по высоте занята предметом, а не текстом. */
.film-step {
  display: grid; grid-template-columns: 2.4em 1fr; gap: 12px; align-items: baseline;
  border-top: 1px solid var(--stage-rule, var(--grid)); padding: 13px 0 12px;
}
.film-step:last-child { border-bottom: 1px solid var(--stage-rule, var(--grid)); }
.film-step-num {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  color: var(--stage-fg-dim, var(--ink-55));
  transition: color var(--t-panel) var(--ease-out);
}
/* Номер, название и подпись — три прямых потомка строки, поэтому колонку
   для двух последних задаём явно: иначе подпись уезжает в узкую колонку
   номера и рвётся по одному слову. */
.film-step-title, .film-step-sub { grid-column: 2; }
.film-step-title {
  margin: 0; font-size: clamp(16px, 1.35vw, 21px); font-weight: 700;
  line-height: 1.25; letter-spacing: -.01em;
  /* Неактивные названия приглушены цветом, а не прозрачностью: цвет
     держит 5,5:1 к бумаге и читается, а прозрачность на этом кегле
     ушла бы ниже нормы. Активный шаг берёт полные чернила — по
     контрасту и видно, где сейчас плёнка. */
  color: var(--stage-fg-dim, var(--ink-70));
  transition: color var(--t-panel) var(--ease-out);
}
.film-step-sub {
  margin: 4px 0 0; font-size: clamp(14px, 1.05vw, 16px); line-height: 1.45;
  color: var(--stage-fg-dim, var(--ink-70));
  /* Подробность видна только у активного шага, но место под неё занято
     всегда. Схлопывать высоту нельзя: это гоняло бы раскладку на каждом
     кадре скраба, а список ещё и подпрыгивал бы при смене шага. Гасим
     прозрачностью — строка стоит на месте, меняется только видимость.

     Плотность ведёт --on: её ставит film.js на каждом кадре, и она
     привязана к положению плёнки, а не к моменту переключения класса.
     Поэтому здесь НЕТ transition: анимировать нечего — значение и так
     меняется непрерывно по прокрутке, а переход поверх него давал бы
     смазывание на быстром движении. */
  min-height: 2.9em;
  opacity: var(--on, 0);
}
.film-step.is-on .film-step-num { color: var(--route); }
.film-step.is-on .film-step-title { color: var(--stage-fg, var(--ink)); }

/* ---------- первые пять карточек ----------
   Пункты выходят по одному, вместе со своей карточкой в стопке. Место
   под все пять занято сразу — иначе список подпрыгивал бы на каждом
   появлении, а он стоит рядом с движущимся кадром. */
.stack-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 0; counter-reset: stack; }
/* Кегль поднят по той же причине, что и у пяти шагов: колонка стоит
   рядом с движущимся кадром и места до нижней кромки у неё в запасе. */
.stack-list li {
  counter-increment: stack;
  display: grid; grid-template-columns: 2.4em 1fr; gap: 5px 12px;
  border-top: 1px solid var(--stage-rule, var(--grid));
  padding: 12px 0;
  font-size: clamp(14px, 1.05vw, 16px); line-height: 1.45;
  color: var(--stage-fg-dim, var(--ink-70));
}
.stack-list li:last-child { border-bottom: 1px solid var(--stage-rule, var(--grid)); }
.stack-list li::before {
  content: counter(stack, decimal-leading-zero);
  grid-row: 1 / span 2;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
  color: var(--route);
  padding-top: .2em;
}
.stack-list b { font-size: clamp(16px, 1.3vw, 20px); color: var(--stage-fg, var(--ink)); }
.stack-list span { grid-column: 2; }

/* ---------- пункты под ведущим абзацем ----------
   Три свойства карты, каждое одной строкой. Линейками, как и остальные
   перечни плёнки: это тот же язык спецификации, а не отдельный список. */
.cap-points { margin: 20px 0 0; padding: 0; list-style: none; }
.cap-points li {
  border-top: 1px solid var(--stage-rule, var(--grid));
  padding: 9px 0 8px;
  font-size: 14px; line-height: 1.45;
  color: var(--stage-fg-dim, var(--ink-70));
  text-wrap: pretty;
}
.cap-points li:last-child { border-bottom: 1px solid var(--stage-rule, var(--grid)); }

/* ---------- состав комплекта ---------- */
/* Кегль и отбивки подняты по той же причине, что у пяти шагов и у пяти
   карточек: колонка стоит рядом с предметом в кадре, и места до нижней
   кромки у неё в запасе. На 13 пикселях состав тубуса читался сноской, а
   это перечисление того, за что человек платит. */
.kit-row { margin: 0; padding: 0; list-style: none; display: grid; gap: 0; }
.kit-row li {
  border-top: 1px solid var(--stage-rule, var(--grid));
  padding: 13px 0 12px;
  font-size: clamp(14px, 1.05vw, 16px); line-height: 1.45;
  color: var(--stage-fg-dim, var(--ink-70));
  display: grid; grid-template-columns: 1fr auto; gap: 4px 16px; align-items: baseline;
}
.kit-row li:last-child { border-bottom: 1px solid var(--stage-rule, var(--grid)); }
.kit-row b { font-size: clamp(16px, 1.3vw, 20px); color: var(--stage-fg, var(--ink)); }

/* Жёсткий перенос только для узких экранов: там строка короче, и
   абзац приходится ломать по смыслу. На широких абзац переносится сам
   (см. комментарий у самого абзаца в index.html). */
.br-narrow { display: none; }

/* ---------- узкие экраны ----------
   Колонка на телефоне не работает: под текст осталась бы половина
   узкого экрана. Кадр уходит вниз отдельной полосой, текст — над ним. */
@media (max-width: 900px) {
  .br-narrow { display: inline; }
  /* Вторая строка тезиса — «музей с близким человеком. Важно, что он
     был.» — при 15 px просит 354 px, а колонка на 375-м экране даёт
     339. Пятнадцати пикселей не хватало, и вывод срывался третьей
     строкой в два слова. Кегль идёт от ширины экрана, плюс еле
     заметное поджатие межбуквенного: вместе они снимают недостачу с
     запасом, а от соседних абзацев строка отличается меньше чем на
     пиксель. Потолок прежний — на широких телефонах кегль общий. */
  html.film-on .cap-lead.hold-line {
    font-size: clamp(13px, 3.75vw, 15px);
    letter-spacing: -.01em;
  }
  /* Кегль подписи хиро идёт от ширины экрана, а не стоит на 15 px.
     С жёстким переносом строка «своими воспоминаниями. Каждая
     фотокарточка» обязана поместиться целиком: на 375 px при 15 px ей
     не хватало шести пикселей, и абзац разъезжался на четыре строки с
     одиноким «было.» в конце. Потолок прежний. */
  .cap-hero .hero-sub { font-size: clamp(13px, 3.85vw, 15px); }
  /* ХОЛСТ ВО ВЕСЬ ЭКРАН, А НЕ ПОЛОСОЙ СНИЗУ.
     Полоса в 42% высоты резала кадр по живому: ночная карта хиро
     доходила только до её верха, разросшийся лист «Как это выглядит»
     упирался в ту же кромку, а оранжевая крышка в финале заполняла
     нижний прямоугольник, тогда как верх страницы красился отдельно
     фоном вёрстки — и шов между ними был виден.

     Теперь холст занимает окно целиком. Предмет по-прежнему живёт в
     нижней полосе (её высоту держит film.js), а фон и всё, что
     разрастается, доходит до самого верха. Чтобы текст поверх остался
     читаемым, film.js гасит верх кадра градиентом — там же, где на
     широком экране гасит его левую половину. */
  /* height именно 100%, а не auto: холст — заменяемый элемент, и auto
     берёт ему не растяжку по inset, а СВОЙ размер (по умолчанию
     300 × 150). Кадр от этого выходил полосой в половину ширины экрана. */
  html.film-on .film-canvas { inset: 0; height: 100%; }
  /* Столбец занимает всё, что осталось между шапкой и кадром, и
     центрируется внутри этой полосы. `safe` здесь не украшение: на
     коротком экране самый длинный блок в полосу не влезает, и без него
     центрирование выпихнуло бы его верх под шапку — с ним блок просто
     прижимается к верху полосы и остаётся читаемым целиком. */
  html.film-on .cap-col {
    top: 44px;
    bottom: 44%;
    padding: 0 24px 14px;
    transform: none;
    display: flex;
    flex-direction: column;
    justify-content: safe center;
  }
  html.film-on .cap { width: 100%; }
  /* Отбивка между заголовком сцены и её содержанием. На мониторе она
     стоит между двумя короткими блоками посреди пустого поля, на
     телефоне — между заголовком и списком, который упирается в предмет.
     Забирать там 3,6vh у списка не за что. */
  html.film-on .cap-top + .cap-bottom { margin-top: clamp(12px, 2vh, 26px); }
  /* Хиро — исключение: в его кадре пока только рождающаяся точка, и
     отдавать ей те же четыре десятых экрана, что и карте со стеной
     карточек, значит открыть страницу пустотой. Столбец забирает место
     обратно; точка появляется ниже текста и ни с чем не спорит. */
  html.film-on .cap-col:has(.cap-hero) { bottom: 30%; }
  .cap-hero h1 { font-size: clamp(28px, 7.5vw, 42px); }
  /* ПОЯСНЕНИЯ К ПУНКТАМ ОСТАЮТСЯ. Сжимаем, а не прячем.

     Их тут прятали — и у первых пяти карточек, и (недолго) у набора в
     тубусе, — потому что списки не влезали в свою полосу и ложились на
     предмет сцены. Но пояснение к пункту и есть половина пункта:
     «Дом» без «место, из которого всё начинается» — это просто слово.
     Прятать содержание, чтобы починить вёрстку, нельзя.

     Влезают они за счёт кегля и отбивок: строки списка становятся
     плотнее, пояснение — мельче основного, а сам блок получает чуть
     больше высоты (см. bottom у .cap-col ниже). Пересчитано под 375 px
     — самый узкий экран, на который мы смотрим. */
  /* Здесь стояло `.film-step-sub { display: none }` — прямо под
     объяснением, почему прятать содержание нельзя. Правило пережило
     собственное обоснование: сжатие ниже написали для соседних списков
     (.stack-list, .kit-row), а «Как это работает» так и осталось на
     телефоне без единого пояснения — пять голых строк вместо пяти
     шагов. Сжимаем его теми же средствами: кегль, межстрочный, отбивки. */
  /* Отбивки те же, что у соседних сжатых списков (.stack-list li ниже):
     на 375 px список из пяти шагов с пояснениями встаёт в свою полосу
     с запасом, а не впритык — впритык означает, что первая же правка
     текста выдавит его на предмет сцены. */
  /* Кегли и отбивки НА ТЕЛЕФОНЕ СЧИТАЮТСЯ ОТ ВЫСОТЫ ОКНА, а не заданы
     числом. Причина простая: полоса под текст — это то, что осталось от
     окна за вычетом предмета, а предмет занимает ДОЛЮ высоты (42%,
     NARROW_BAND в film.js). Текст, набранный в пикселях, на коротком
     экране не ужимается вместе с полосой и ложится на предмет — ровно
     это и происходило на 667 px. Верхний упор клампов равен прежним
     числам, поэтому на 812 px раскладка та же, что была. */
  .film-step {
    padding: clamp(5px, 0.98vh, 8px) 0 clamp(4px, 0.86vh, 7px);
    gap: 4px 10px; grid-template-columns: 2em 1fr;
  }
  .film-step-title { font-size: clamp(12.5px, 1.79vh, 14.5px); line-height: 1.25; }
  /* min-height у пояснения — 2,9em (см. базовое правило): это две строки
     ПРИ межстрочном 1,45. Здесь межстрочный 1,3, и те же две строки
     занимают 2,6em — остальное было воздухом, по десятку пикселей на
     шаг. Уравнивание строк списка держится и на 2,6em: на телефоне
     каждое пояснение переносится ровно в две строки. */
  .film-step-sub {
    margin-top: 2px; font-size: clamp(10px, 1.42vh, 11.5px);
    line-height: 1.3; min-height: 2.6em;
  }
  /* <br> внутри пояснения НЕ ТРОГАЕМ.

     Соблазн погасить его через display:none велик — на 375 px обе
     половины фразы и так переносятся сами, а жёсткий разрыв стоит
     лишней строки. Но <br> — замещаемый элемент: скрыв его, мы убираем
     перенос и НЕ получаем на его месте пробела, и «формат и цвета.Что
     видишь на макете» слипается в одно слово. Перенос дешевле. */
  .stack-list li,
  .kit-row li {
    padding: clamp(5px, 0.98vh, 8px) 0 clamp(4px, 0.86vh, 7px);
    font-size: clamp(12px, 1.6vh, 13px); line-height: 1.35;
  }
  .stack-list li { gap: 2px 10px; grid-template-columns: 2em 1fr; }
  .kit-row li { grid-template-columns: 1fr; gap: 2px; }
  .stack-list b,
  .kit-row b { font-size: clamp(12.5px, 1.79vh, 14.5px); line-height: 1.25; }
  .stack-list span,
  .kit-row li span { font-size: clamp(10px, 1.42vh, 11.5px); line-height: 1.3; }
  /* ХВОСТОВЫЕ АБЗАЦЫ НА ТЕЛЕФОНЕ УХОДЯТ — ОБА.

     Это единственное, что здесь правда лишнее: абзац не про пункт, а
     про список целиком, и стоит он последним, то есть ровно там, где
     полоса текста упирается в предмет. Пять шагов с пояснениями и
     четыре строки сверх них в оставшиеся 58% высоты не встают ничем —
     ни кеглем, ни отбивками: считано на 812 и на 667 px. Выбор был
     между «абзац лежит поверх карточки» и «абзаца на телефоне нет»;
     первое не работает вовсе, второе честно. На широком экране оба
     абзаца остаются. */
  .stack-list ~ .cap-aside,
  .film-steps ~ .cap-aside { display: none; }
  /* Тубусу лишние восемь процентов высоты нужны и не мешают: предмет в
     той сцене низкий, до верха своей полосы не достаёт.

     СТОПКЕ ЖЕ ОНИ ВРЕДИЛИ. Карточки в ней ложатся одна на другую и
     растут ВВЕРХ: к концу сцены верхняя доходит до 466 px при полосе,
     начинающейся с 471, — то есть выходит за свой предел, а текст в это
     время спускался до 453. Отсюда и был наезд последней строки на
     стопку. Список из пяти пунктов встаёт и в 55% высоты, с запасом:
     на 812 px он кончается на 410, на 667 — на 351. */
  html.film-on .cap-col:has(.kit-row) { bottom: 36%; }
  html.film-on .cap-col:has(.stack-list) { bottom: 45%; }
}
@media (max-width: 560px) {
  html.film-on .cap-col { padding: 0 18px 14px; }
}

/* ---------- reduced motion ----------
   Плёнка не идёт: холст встаёт в поток одной картинкой финального кадра,
   а подписи разворачиваются в обычный текст друг за другом. Сюжет
   остаётся читаемым, просто он не двигается. */
html.film-static .film { height: auto; }
html.film-static .film-viewport {
  position: static;
  height: auto;
  overflow: visible;
  padding: 0 0 64px;
}
html.film-static .film-canvas {
  position: relative;
  inset: auto;
  aspect-ratio: 4 / 3;
  height: auto;
  margin-bottom: 40px;
}
html.film-static .cap-col {
  position: static;
  max-width: 1400px;
  margin: 0 auto;
  padding: 0 40px;
  transform: none;
  pointer-events: auto;
}
html.film-static .cap {
  width: auto;
  opacity: 1;
  transform: none;
  margin-bottom: 34px;
}
html.film-static .cap-top + .cap-bottom { margin-top: 0; }
/* Плёнка не идёт — листать нечего, подсказка не нужна. В статичном
   режиме film.js разворачивает все подписи, и без этого правила она
   осталась бы висеть посреди текста. */
html.film-static .scroll-hint { display: none; }
html.film-static .film-step-sub { opacity: 1; }
