  /* ===== Slide viewport (клиппинг снэп-скролла) =====
     Раньше эту роль играл overflow:hidden прямо на #stage — теперь у #stage
     его нет (см. комментарий там, баг с обрезкой .color-back), поэтому клип
     переехал сюда. Без z-index (auto) — НЕ создаёт свой stacking context,
     поэтому z-index:2 у #slide-track внутри по-прежнему сравнивается напрямую
     с #phone-frame-layer/#searchbar-layer снаружи (см. их комментарии
     в разметке) — переносом сюда порядок наложения не затронут.
     top:calc(-1*var(--header-h)) (НЕ inset:0) — расширяет клип-окно вверх
     ровно на высоту хедера: текст, улетающий вверх при параллаксе (напр.
     "ИЗМЕНИЛСЯ" на переходе слайд2→слайд3, js/parallax-texts.js toSlide3),
     иначе обрезался бы жёстким швом ровно под хедером — теперь граница
     обрезки истинный край экрана (0), под хедером и его прогрессивным
     блюром (см. .menubar-blur-layer) есть куда уходить. #slide-track
     компенсирует свой top (0→var(--header-h), см. ниже) — визуально ни один
     слайд в состоянии покоя не сдвинулся ни на пиксель, расширение чисто
     "в запас" сверху.
     ВАЖНО — calc(var(--header-h) / var(--stage-scale)), НЕ голый
     var(--header-h): в отличие от .stage-wrap (снаружи #stage, реальные
     пиксели), #slide-viewport/#slide-track живут ВНУТРИ #stage, который сам
     имеет transform:scale(s) (см. js/stage.js, потолок масштаба на desktop —
     сжатая по ширине узкая витрина). "68px" внутри этой scaled-системы
     координат после рендера превращается в 68×s РЕАЛЬНЫХ пикселей — на узком
     окне (s<1) компенсация выходит МЕНЬШЕ настоящей высоты хедера, и верх
     слайда (напр. заголовок "МИР") обрезается раньше, чем нужно. Деление на
     --stage-scale (обновляется в js/stage.js синхронно с самим
     transform:scale, см. документацию у :root) заранее "укрупняет"
     design-space-значение так, что после умножения на s на рендере снова
     получается ровно 68 реальных px — при любом масштабе.

     ВТОРОЙ вычитаемый член — var(--stage-center-offset). На высоких/узких
     окнах (когда ширина — связывающее ограничение масштаба и slideH
     получается больше 760, см. js/stage.js fitStage) #stage-content сам
     получает translateY((slideH-760)/2) для вертикального центрирования
     760-высокой композиции — этот сдвиг применяется ко ВСЕМ потомкам
     #stage-content, включая #slide-viewport, и "съедает" часть расширения
     выше, сдвигая границу viewport ниже истинного верха экрана ровно на
     translateY-сдвиг, отрендеренный через scale ((slideH-760)/2 × s) — это
     и есть источник бага "контент обрезается до хедера" на узких по ширине
     окнах. Поэтому дополнительно вычитаем --stage-center-offset
     (design-space, ставится в js/stage.js синхронно с translateY на
     #stage-content) — этот член алгебраически сокращается с тем же
     центрирующим сдвигом на #stage-content при рендере, так что итоговая
     экранная позиция границы всегда 0, независимо от slideH. */
  #slide-viewport{ position:absolute; top:calc(-1 * var(--header-h) / var(--stage-scale) - var(--stage-center-offset)); right:0; bottom:0; left:0; overflow:hidden; }

  /* ===== Slide track (snap-scrolls vertically) ===== */
  /* top:calc(var(--header-h) / var(--stage-scale) + var(--stage-center-offset))
     (НЕ var(--header-h) само по себе — см. подробное объяснение у
     #slide-viewport выше, та же причина: компенсация внутри scaled-координат
     #stage должна учитывать текущий масштаб, иначе рассинхрон с .stage-wrap
     на узких окнах) — компенсирует ОБА сдвига #slide-viewport выше (и
     header-h/scale, и center-offset), чтобы экранная позиция слайдов в покое
     не изменилась ни на пиксель ни при каком slideH. */
  #slide-track{ position:absolute; left:0; top:calc(var(--header-h) / var(--stage-scale) + var(--stage-center-offset)); width:1440px; height:6840px; z-index:2; }
  /* height — ДЕВЯТЬ полос по 760, а не одиннадцать по числу позиций: слайды
     8, 9 и 10 (индексы 7/8/9) с редизайна 2026-08-07 делят одну полосу, см.
     trackSlot() в js/slides.js. Реальное значение всё равно проставляет
     js/stage.js (slideH * 9) — здесь дефолт до первого fitStage. */
  /* phone-frame живёт здесь (не в #phone-shared) — см. комментарий у разметки
     в конце #stage-content. z-index — ДИНАМИЧЕСКИЙ, переключается из
     js/phone-shared.js (setPhone/setPhoneSlide3/exitPhoneSlide3), т.к. на
     слайдах 1/2 рамка должна быть ПОЗАДИ контента (стикеры/карта слайда 2
     специально вылезают за края рамки), а на слайде 3 — ПОВЕРХ воды/
     объектов. 1 здесь — просто дефолт на случай, если JS ещё не отработал
     (z-index:1, ниже #slide-track(2) — тот же порядок, что был у старого
     #phone-shared до переноса рамки); реальные значения — 1 (база, слайды
     1/2) и 10 (слайд 3, выше #slide-track(2), ниже .menubar/.apps-sidebar
     (100)) — см. Z_BASE/Z_SLIDE3 в js/phone-shared.js. Раньше здесь был
     жёстко зашитый z-index:10 — казалось, что на слайдах 1/2 это "почти не
     влияет", но стикеры/карта слайда 2 вылезают за рамку намного больше,
     чем предполагалось, и рамка перекрывала их сверху — баг, пойманный
     пользователем на скриншоте. border/left/top/width/height/border-radius/
     border-color анимируются напрямую через GSAP при входе/выходе слайда 3
     (js/phone-shared.js, setPhoneSlide3/exitPhoneSlide3) — разовый переход,
     reflow не проблема (в отличие от continuous-анимаций в проекте, где
     принципиально используется только transform). */
  #phone-frame-layer{ position:absolute; inset:0; z-index:1; pointer-events:none; }
  /* Задний слой параллакса слайда App (см. HTML-комментарий у #phone-frame-
     layer и js/slide4-app.js) — без z-index (не создаёт новый stacking
     context, .phone-frame сравнивается как раньше). */
  #phone-frame-parallax{ position:absolute; inset:0; pointer-events:none; }
  /* Поисковая строка/кружок — статичный z-index:3, ВСЕГДА выше #slide-track
     (z:2), в отличие от #phone-frame-layer (переключается 1⇄10 по слайду) —
     см. HTML-комментарий у #searchbar-layer. */
  #searchbar-layer{ position:absolute; inset:0; z-index:3; pointer-events:none; }

