/* ============================================================
   J-Park — внутренние страницы
   project.html · calc.html · works.html · news.html · contacts.html

   Только специфика этих страниц. Палитра, типографика, сетки,
   .btn / .card / .section / .wrap / .sec-head / .deflist / .form,
   шапка, футер и [data-reveal] приезжают из site.css, который
   подключается ПЕРЕД этим файлом.
   ============================================================ */

/* .visually-hidden, .crumbs, .section--head, шапка и её мобильная лента
   (включая горизонтальное меню ниже 900 px) — всё общее уехало в site.css. */

/* Тех-метка «гравировка на карабине» (design-direction.md, язык
   технической маркировки) — заведена в index.html <style> для секций
   #types/#produce/#works и там же остаётся (трогать этот файл не входит
   в task 10). Здесь — минимальный дубль для внутренних страниц: task 10
   впервые использует .spec-tag на tropa.html (подпись 3D-панели), а без
   этого правила на ней нет ни одного своего .spec-tag, и класс просто
   не находит стиль. */
.spec-tag{
  font-family:var(--f-mono);font-size:11px;font-weight:500;
  letter-spacing:.12em;text-transform:uppercase;
  font-variant-numeric:tabular-nums;
  color:var(--blue-text);margin:0;
}
/* Светлый вариант метки — для текста поверх тёмной подложки (тень
   градиента на обложке плитки works.html). В index.html он читает
   переменную --shadow-text из своего локального :root, которой на
   внутренних страницах нет — тень задана тем же значением буквально,
   как уже сделано для .phero[data-frameless] чуть ниже по файлу. */
.spec-tag--light{color:rgba(255,255,255,.88);text-shadow:0 1px 2px rgba(10,16,12,.5),0 8px 40px rgba(10,16,12,.45)}

/* ============================================================
   Кадр-фон: герой и полосы-разделители (project.html)
   Кадры — prototype/img-src/bg/cp-*-{960,1920,2560,3840}.webp, собраны
   прямо из мастера hero-full-v7-4k.mp4 (tools/make_page_bg_4k.py).
   Кадр стоит в разметке как <img class="shot__bg"> с srcset, а не
   background-image: фон растянут на всю ширину окна, и ступень должна
   выбираться по РЕАЛЬНОЙ ширине × DPR. background-image этого не умеет —
   image-set() различает только плотность, и 4К-монитор с DPR 1 получал бы
   версию для обычного десктопа. Поверх — градиент ::after, иначе текст
   не читается.
   ============================================================ */
.shot{
  position:relative;isolation:isolate;overflow:hidden;
  background:var(--forest);color:var(--light);
  border-top:0;
}
.shot__bg{
  position:absolute;inset:0;z-index:-2;
  width:100%;height:100%;object-fit:cover;object-position:center;
  /* лёгкий запас: кадр чуть крупнее блока, чтобы дрожание сабпиксельных
     границ на ресайзе не вскрывало кромку */
  transform:scale(1.04);
}
.shot::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:
    linear-gradient(180deg,rgba(12,18,14,.62) 0%,rgba(12,18,14,.28) 42%,rgba(12,18,14,.78) 100%),
    linear-gradient(96deg,rgba(12,18,14,.66) 0%,rgba(12,18,14,.12) 62%);
}
.shot .eyebrow{color:var(--amber)}
.shot .muted{color:rgba(247,243,235,.72)}
.shot .h1,.shot .h2,.shot .h3{color:var(--light)}
.shot .crumbs{color:rgba(247,243,235,.6)}
.shot .crumbs a:hover{color:var(--amber)}
.shot .btn-ghost{color:rgba(247,243,235,.85);border-color:rgba(247,243,235,.3)}
.shot .btn-ghost:hover{color:var(--amber);border-color:var(--amber)}

/* Мягкая дымка поверх кадра — тот же приём, что в hero.
   Слой не может закрыть секцию целиком (иначе дымка перестаёт читаться
   как дымка), поэтому у его коробки есть кромка — и раньше она была
   ПРЯМОЙ: облако упиралось в границу блока и обрывалось линейкой.
   Лечим маской: по вертикали слой растворяется к обоим своим краям, и
   вместо кромки остаётся сход на нет. Маска живёт в локальных координатах
   элемента, поэтому у перевёрнутого .haze--top (scaleY(-1)) она сама
   встаёт правильной стороной — отдельного правила не нужно.
   Коробку заодно выпускаем за боковые границы секции: PNG у cloud-2 по
   бокам и так прозрачный, но при cover на узком экране кадрируется
   вертикаль, и запас по ширине держит края картинки за пределами блока.
   Сама секция обрезает лишнее своим overflow:hidden. */
.haze{
  position:absolute;inset:auto -12% -14% -12%;height:74%;z-index:-1;
  background:url("img-src/bg/haze.webp") center bottom / cover no-repeat;
  opacity:.42;mix-blend-mode:screen;pointer-events:none;
  -webkit-mask-image:linear-gradient(to top,transparent 0%,#000 16%,#000 52%,transparent 100%);
  mask-image:linear-gradient(to top,transparent 0%,#000 16%,#000 52%,transparent 100%);
  animation:hazeDrift 46s var(--ease) infinite alternate;
}
@media (min-resolution:1.5dppx),(min-width:1900px){
  .haze{background-image:url("img-src/bg/haze-2560.webp")}
}
.haze--top{inset:-14% -12% auto -12%;height:64%;opacity:.3;transform:scaleY(-1)}
@keyframes hazeDrift{
  from{transform:translate3d(-2%,0,0) scale(1.02)}
  to{transform:translate3d(2%,-1.5%,0) scale(1.08)}
}
.haze--top{animation-name:hazeDriftTop}
@keyframes hazeDriftTop{
  from{transform:scaleY(-1) translate3d(2%,0,0) scale(1.02)}
  to{transform:scaleY(-1) translate3d(-2%,-1%,0) scale(1.07)}
}
/* Условие сознательно сужено 09.09.2026 (правка владельца).
   Системный флаг «уменьшить движение» включается не только осознанно:
   его ставят эмуляция в DevTools, энергосбережение, политики и
   расширения — и сайт превращался в набор неподвижных картинок.
   Решение о движении принимает motion.js (одна точка на все скрипты),
   но CSS он переопределить не может, поэтому здесь добавлено
   `and (scripting: none)`: правила остаются в силе там, где скрипты
   выключены и переопределить нечем, и не срабатывают там, где
   решение уже принято в motion.js. Вернуть прежнее поведение —
   убрать `and (scripting: none)` и поставить RESPECT_SYSTEM = true. */
@media (prefers-reduced-motion:reduce) and (scripting:none){.haze{animation:none}}
/* Большая полупрозрачная дымка остаётся частью композиции, но на узком
   или тач-экране не двигается постоянно за пределами вьюпорта. Это
   экономит композитный слой и батарею во время длинной прокрутки. */
@media (max-width:900px),(pointer:coarse){.haze{animation:none}}

/* ---------- герой страницы ---------- */
.phero{
  min-height:min(84vh,700px);
  display:flex;align-items:flex-end;
  padding-top:clamp(72px,14vh,150px);
  padding-bottom:clamp(40px,6vh,72px);
}
/* Подложка под заголовком героя. Переработана 01.09.2026: владелец
   сказал, что эффект «слишком сильный». Так и было — плотная заливка
   .42 плюс blur(12px) превращали четверть кадра в матовое стекло, и
   фотография за карточкой переставала читаться как фотография.
   Читаемость держится теперь тремя слабыми средствами вместо одного
   сильного: подложка убывает по диагонали (плотнее у текста в левом
   верхнем углу, почти прозрачная у дальнего края), блюр вдвое мягче, а
   контраст букв добирается тенью на самом тексте — она работает точно
   по контуру глифов и не трогает кадр вокруг. */
.phero__card{
  max-width:660px;padding:clamp(24px,3vw,40px);
  background:linear-gradient(145deg,
    rgba(12,18,14,.54) 0%, rgba(12,18,14,.36) 52%, rgba(12,18,14,.14) 100%);
  border:1px solid rgba(247,243,235,.12);
  backdrop-filter:blur(5px) saturate(1.08);border-radius:4px;
}
.phero__card .h1,.phero__card .eyebrow{text-shadow:0 2px 18px rgba(8,12,9,.55)}
.phero__card p{text-shadow:0 1px 12px rgba(8,12,9,.5)}
/* «Индивидуальный» — самое длинное слово страницы: на 390 px при нижней
   границе .h1 (2.1rem) оно вылезало за карточку и обрезалось. */
/* overflow-wrap:break-word убран правкой 01.09.2026. Он рвал слова прямо
   посередине — «Индивидуаль/ный» в заголовке этой страницы, — потому что
   на средних ширинах слово не влезало в строку целиком. Вместо разрыва
   слова уменьшаем кегль: нижняя граница clamp опущена, а верхняя привязана
   к ширине мягче, так что длинное слово садится в строку само.
   break-word оставлен только как аварийный режим для очень узких экранов
   (ниже 360 px), где иначе поехала бы вся раскладка. */
.phero__card .h1{
  margin:0 0 16px;
  font-size:clamp(1.35rem,5.4vw,4rem);
  overflow-wrap:normal;word-break:normal;hyphens:none;
}
@media (max-width:359px){
  .phero__card .h1{overflow-wrap:break-word}
}
.phero__card p{margin:0;color:rgba(247,243,235,.84);font-size:clamp(15.5px,1.2vw,18px);line-height:1.65}
.phero__facts{
  display:flex;flex-wrap:wrap;gap:10px 28px;margin-top:22px;padding-top:18px;
  border-top:1px solid rgba(247,243,235,.18);
  font-family:var(--f-mono);font-size:11.5px;letter-spacing:.13em;
  text-transform:uppercase;color:rgba(247,243,235,.62);
}
.phero__facts b{display:block;font-family:var(--f-display);font-size:18px;
  letter-spacing:-.02em;text-transform:none;color:var(--amber);font-weight:600;margin-bottom:3px}
.phero .cta-row{margin-top:26px}

/* ---------- герой тропы: два режима света ----------
   Базовый .shot::after рассчитан на сумеречный кадр: два градиента,
   вертикальный и горизонтальный, перемножаются и дают в левом нижнем
   углу ≈0.92 черноты. Ровно там стоит .phero__card с заголовком, и на
   дневном кадре это читается как «картинку зачем-то залили».

   Дневные значения ослаблены примерно вдвое. Читаемость заголовка при
   этом не страдает: её держит не скрим, а собственный фон карточки с
   backdrop-filter — ему ниже добавлено плотности взамен.

   Специфичность: .phero[data-time="day"]::after — (0,2,1) против
   (0,1,1) у .shot::after, так что переопределение срабатывает без
   !important и без правки базового правила, которым живут остальные
   страницы. */
.phero[data-time="day"]::after{
  background:
    linear-gradient(180deg,rgba(12,18,14,.28) 0%,rgba(12,18,14,.06) 45%,rgba(12,18,14,.52) 100%),
    linear-gradient(96deg,rgba(12,18,14,.42) 0%,rgba(12,18,14,.04) 60%);
}
.phero[data-time="day"] .phero__card{background:rgba(12,18,14,.52)}

/* ---------- герой тропы: плашка снята (замечание заказчика, 2026-08-13) ----------
   «Мне вот этот сам квадрат не нравится, он сильно уж перекрывает
   картинку» — плашка убрана целиком (фон/рамка/blur/скругление), текст
   лежит прямо на кадре. Флаг [data-frameless] стоит только на герое
   tropa.html: .phero__card используется ещё в project.html и domiki.html,
   которых эта жалоба не касалась (по domiki.html вдобавок параллельно
   работает другой агент) — общее правило .phero__card трогать нельзя,
   поэтому переопределение адресное. Атрибут, а не класс-модификатор:
   check_tropa.py сверяет открывающий тег героя регэкспом на буквальное
   значение class="section section--flat shot shot--1 phero" — лишний
   класс в этой строке этот тест ломает, а лишний атрибут в хвосте тега
   ему не мешает.

   Отступы и max-width у .phero__card не трогаем: они держат меру строки
   и внутренний ритм карточки, к форме плашки отношения не имеют.

   Читаемость без плашки держат два приёма вместо нее:
   1) text-shadow на заголовке и абзаце — тот же язык, что уже в проекте
      у текста поверх фотографии без плашки (.tropa-promo__in .h2 и
      .tropa-promo__thesis p в index.html, там же --shadow-text);
   2) свой локальный скрим в [data-frameless][data-time="day"]::after
      ниже — усиление именно в нижнем левом углу, где встаёт текст, а не
      по всему кадру: иначе тёмное пятно на месте снятой плашки читалось
      бы как та же жалоба, только без рамки. */
.phero[data-frameless] .phero__card{
  background:none;border:0;backdrop-filter:none;border-radius:0;
}
.phero[data-frameless][data-time="day"] .phero__card{background:none}
.phero[data-frameless] .phero__card .h1,
.phero[data-frameless] .phero__card p{
  text-shadow:0 1px 2px rgba(10,16,12,.55),0 10px 34px rgba(10,16,12,.5);
}
/* Дневной кадр «Золотой час» местами очень светлый — а без плашки текст
   лежит поверх него без своего фона. Мерено скриншотами (playwright +
   сэмплинг самых светлых пикселей под текстом, см. отчёт задачи): на
   1440/834/390 заголовок и абзац занимают зону примерно x 6–89%,
   y 34–76% от блока .shot — то есть почти всю нижнюю половину кадра по
   ширине, а не только угол. Радиальное пятно в углу (первая версия)
   покрывало только часть этой зоны и не вытягивало WCAG на широких
   экранах, где по низу кадра идёт светлый дощатый настил. Замена —
   два линейных слоя на всю зону: вертикальный держит нижнюю половину
   кадра, горизонтальный добавляет плотности слева, где текст начинается,
   и держит её до 90% ширины, а не гаснет к середине, как в исходном
   .shot::after. Оба мягкие (без резких ступеней), поэтому читаются как
   виньетка кадра, а не как та же плашка без рамки. */
.phero[data-frameless][data-time="day"]::after{
  background:
    linear-gradient(180deg,rgba(12,18,14,.08) 0%,rgba(12,18,14,.24) 30%,rgba(12,18,14,.52) 58%,rgba(12,18,14,.82) 100%),
    linear-gradient(100deg,rgba(12,18,14,.62) 0%,rgba(12,18,14,.4) 45%,rgba(12,18,14,.2) 75%,rgba(12,18,14,.05) 96%);
}
/* Ночной кадр темнее и базовый .shot::after (сумеречные значения) почти
   везде хватает — но на 390px (портретный кадр tropa-01v.webp) в зоне
   абзаца попался светлый блик, и без плашки контраст не дотягивал до
   4.5:1 совсем немного. Тот же приём, что и днём, но слабее: полтора
   раза плотнее базовых значений, а не с нуля. */
.phero[data-frameless][data-time="night"]::after{
  background:
    linear-gradient(180deg,rgba(12,18,14,.68) 0%,rgba(12,18,14,.4) 40%,rgba(12,18,14,.86) 100%),
    linear-gradient(96deg,rgba(12,18,14,.74) 0%,rgba(12,18,14,.2) 62%);
}

/* Переключатель времени суток (.dnt), ночной слой (.shot__bg--night) и
   длинный ход автосмены — с 07.09.2026 общий компонент в site.css: тем же
   daynight.js живёт промо-полоса тропы на главной (index.html,
   #tropa-promo), а index.html pages.css не подключает. Здесь остаётся
   только то, что принадлежит герою: его скрим. */

/* Скрим под текстом у дня и ночи разный, и он обязан ехать вместе с
   кадром — иначе плотность подложки менялась бы скачком посреди
   плавного перехода, и текст на секунду проваливался бы в контрасте. */
.phero.is-cycling::after{transition:background 3.8s cubic-bezier(.45,0,.35,1)}

/* Тропа в hero «как на главной» (14.09.2026). Тень .montage__shade у главной
   рассчитана на тёмное видео, а дневной кадр тропы «Золотой час» местами
   очень светлый: значения ниже — те же, что были подобраны замером контраста
   для прежнего hero тропы (.phero[data-frameless] выше), только теперь они
   лежат на .montage__shade. Ночью кадр темнее — тень слабее. */
.flight[data-daynight][data-time="day"] .montage__shade{
  background:
    linear-gradient(180deg,rgba(12,18,14,.30) 0%,rgba(12,18,14,.24) 30%,rgba(12,18,14,.52) 58%,rgba(12,18,14,.82) 100%),
    linear-gradient(100deg,rgba(12,18,14,.62) 0%,rgba(12,18,14,.4) 45%,rgba(12,18,14,.2) 75%,rgba(12,18,14,.05) 96%);
}
.flight[data-daynight][data-time="night"] .montage__shade{
  background:
    linear-gradient(180deg,rgba(12,18,14,.68) 0%,rgba(12,18,14,.4) 40%,rgba(12,18,14,.86) 100%),
    linear-gradient(96deg,rgba(12,18,14,.74) 0%,rgba(12,18,14,.2) 62%);
}
/* На телефоне переключатель уезжает наверх (site.css) — под фиксированную
   шапку. Отступ от её реальной высоты: --head-h меряет pages.js. */
@media (max-width:700px){
  .flight .dnt{top:calc(var(--head-h, 96px) + 14px)}
}

/* tropa.html, герой — талреп в правом верхнем углу. Тот же угол, что
   занимал домик: он свободен от .phero__card (та max-width:660px и
   прижата к низу слева) и от переключателя времени суток (тот внизу
   справа, см. .dnt). */
.cutout--turnbuckle{
  top:clamp(56px,11vh,120px);right:clamp(16px,6vw,80px);
  width:clamp(72px,7vw,116px);
  transform:rotate(6deg);
}

/* Каттауты внутренних страниц (2026-08-08): приём доведён до сквозного,
   по одному предмету на страницу, привязка по смыслу секции.

   Ширины сильно разные и это намеренно — они заданы пропорцией файла, а не
   вкусом: секция моста 1000x288 почти лента, обвязка 494x1000 вертикальна.
   При равной ширине лента выглядела бы вдвое мельче обвязки по массе. */

/* tropa.html #build («Из чего собрана тропа») — секция навесного моста.
   Слева: справа в этой секции .sec-head--split держит правую колонку. */
.cutout--bridge{
  top:clamp(-40px,-4.5vh,-24px);left:clamp(16px,5vw,80px);
  width:clamp(150px,14vw,240px);
  transform:rotate(-3deg);
}
/* works.html, финальный блок «Хотите такой же» — страховочная обвязка.
   Контент секции центрирован, поэтому у обоих полей чисто; правая сторона
   выбрана, чтобы не спорить с домиком на тропе, который тоже справа. */
.cutout--harness{
  top:clamp(-56px,-6vh,-32px);right:clamp(18px,6vw,92px);
  width:clamp(70px,6vw,104px);
  transform:rotate(5deg);
}
/* Узел на catalog.html живёт НЕ здесь, а в catalog.css: эта страница
   подключает site.css + catalog.css и pages.css не грузит вовсе. Правило,
   положенное сюда, не применялось, и контейнер каттаута растягивался на
   натуральные 900px, накрывая собой три текстовых блока секции запроса. */

/* ---------- 3D-модель домика (domiki.html #mount, задача 2) ----------
   Эксперимент: сгенерированный меш в <model-viewer> (локальный скрипт,
   без CDN — prototype/vendor/model-viewer.min.js). Панель — тот же язык
   «карточка со страховочной линией сверху», что у остального сайта:
   тех-метка вместо подписи, тонкая рамка вместо тени. */
.build3d{
  margin-top:clamp(32px,4.4vh,56px);
  border-top:1px solid var(--line);padding-top:clamp(20px,2.6vw,32px);
}
.build3d__tag{display:block;margin-bottom:14px}
.build3d model-viewer{
  width:100%;height:clamp(320px,40vw,460px);
  background:var(--cream-deep);border-radius:3px;
  --poster-color:transparent;
}

/* ---------- узкая полоса-цитата на кадре ---------- */
.band{padding-block:clamp(72px,11vh,130px)}
.band__in{max-width:760px}
.band .h2{color:var(--light)}
.band p{color:rgba(247,243,235,.8);font-size:clamp(15.5px,1.2vw,18px);line-height:1.7;margin:0}

/* ---------- декор из слоёв конструктива ---------- */
/* Слой-декор вылезает за правое поле — секцию режем, иначе документ
   становится шире вьюпорта и появляется горизонтальная прокрутка. */
.parts{position:relative;overflow:hidden}
/* Декоративный мост в секции этапов. Две строчки здесь неочевидны и обе
   были багом, а не стилем.
   height:auto — у картинки есть HTML-атрибуты width/height, и внешний CSS
   переопределял только ширину. Высота при этом бралась прямо из атрибута,
   в пикселях, без масштабирования: ширина сжималась до 520, а высота
   оставалась 819, и мост растягивался по вертикали далеко за секцию.
   z-index:-1 — абсолютный элемент без z-index красится ПОВЕРХ обычного
   потока, то есть поверх заголовка, лида и плиток шагов. Плитки залиты
   непрозрачным --cream, поэтому под ними мост просто скрывается, а виден
   остаётся только там, где в макете и правда свободное поле. */
.parts__img{
  position:absolute;right:calc(var(--pad-x) * -.4);bottom:-6%;
  width:min(46%,520px);height:auto;z-index:-1;opacity:.9;pointer-events:none;
  filter:drop-shadow(0 26px 40px rgba(12,18,14,.45));
}
@media (max-width:1080px){.parts__img{display:none}}

/* ============================================================
   project.html — табы «5 типов конструкции»
   Кадр слева, текст справа (14.09.2026: раньше в табах был один текст
   без фотографий). Кадры вертикальные 4:5 — те же, что в панелях главной.
   ============================================================ */
.choice__grid{
  display:grid;grid-template-columns:minmax(0,5fr) minmax(0,6fr);
  gap:clamp(24px,4vw,64px);align-items:center;margin-top:clamp(22px,3vw,36px);
}
/* Высота кадра ограничена: на широком экране колонка 4:5 иначе вытянула бы
   панель выше вьюпорта, и кнопка ушла бы за нижний край. */
.choice__shot{
  margin:0;aspect-ratio:4/5;max-height:min(72vh,640px);
  overflow:hidden;border-radius:4px;background:var(--forest);
}
.choice__shot img{display:block;width:100%;height:100%;object-fit:cover}
.choice__text .h3{margin:0 0 18px}
.choice__spec{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 22px;padding:0}
.choice__spec i{
  font-style:normal;font-family:var(--f-mono);font-size:11px;letter-spacing:.12em;
  text-transform:uppercase;color:var(--ink-soft);
  padding:7px 11px;border:1px solid var(--line);border-radius:2px;
}
.choice__note{
  margin:0;padding:20px 0 0;border-top:1px solid var(--line);max-width:56ch;
  font-size:15.5px;line-height:1.65;color:var(--ink-soft);
}
.choice__text .cta-row{margin-top:26px}
@media (max-width:760px){
  .choice__grid{grid-template-columns:1fr}
  .choice__shot{aspect-ratio:4/3;max-height:none}
}
.tabpanel{animation:panelIn .4s var(--ease)}
@keyframes panelIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce) and (scripting:none){.tabpanel{animation:none}}

/* ---------- сравнение проектов ---------- */
.versus{
  display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);
  gap:clamp(18px,2.4vw,40px);align-items:stretch;
}
.versus__col{
  background:var(--light);border:1px solid var(--line-soft);border-radius:3px;
  padding:clamp(24px,2.6vw,38px);display:flex;flex-direction:column;gap:18px;
}
.versus__col--lead{border-color:rgba(46,92,138,.3);box-shadow:var(--shadow-card)}
.versus__col ul{list-style:none;margin:0;padding:0;display:grid;gap:1px;background:var(--line-soft)}
.versus__col li{background:var(--light);padding:15px 0 16px;font-size:15.5px;line-height:1.55;display:flex;gap:12px}
.versus__col li::before{
  content:"—";flex:none;color:var(--amber);font-family:var(--f-mono);font-size:13px;line-height:1.7;
}
.versus__col--plain li::before{color:var(--ink-soft);opacity:.5}
.versus__vs{
  align-self:center;font-family:var(--f-mono);font-size:12px;letter-spacing:.22em;
  text-transform:uppercase;color:var(--ink-soft);
  display:flex;flex-direction:column;align-items:center;gap:14px;
}
.versus__vs::before,.versus__vs::after{content:"";width:1px;flex:1 1 40px;background:var(--line);min-height:40px}
@media (max-width:900px){
  .versus{grid-template-columns:1fr}
  .versus__vs{flex-direction:row;gap:16px}
  .versus__vs::before,.versus__vs::after{width:auto;height:1px;flex:1 1 auto;min-height:0}
}

.steps--5{grid-template-columns:repeat(5,minmax(0,1fr))}
@media (max-width:1080px){.steps--5{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:620px){.steps--5{grid-template-columns:1fr}}

.steps--3{grid-template-columns:repeat(3,minmax(0,1fr))}
.steps--3 .step p{font-size:14.5px;line-height:1.6}
@media (max-width:620px){.steps--3{grid-template-columns:1fr}}

/* ---------- шаги «Конструктива» с предметом ----------
   Внутри шага: картинка слева, текст справа (.steps--figs .step ниже).
   Снаружи «Конструктив» стоит в .steps--3 — три колонки, и на 1440 это
   даёт три предмета в ряд, выглядит хорошо. Но .steps--3 сбрасывается в
   одну колонку только ниже 620px, а между 621 и 1080 три колонки ещё
   держатся — текстовая колонка ужимается до ~75px, слова переносятся по
   одному, и «Сертифицированная» не влезает. Ниже своя пара media-запросов
   схлопывает именно эту комбинацию классов в одну колонку раньше, чем
   общее правило .steps--3 — по строке на шаг вместо трёх.

   Колонка под предмет узкая и фиксированная по верхней границе: объекты
   разной пропорции (сетка почти квадрат, фонарь вытянут), и при равной
   ширине они хотя бы выстраиваются по левому краю, а не пляшут.

   .step__fig — <span> с display:block: он лежит внутри .step, который
   получает tabindex, и лишний интерактивный узел здесь не нужен. */
.steps--figs .step{
  display:grid;grid-template-columns:minmax(0,120px) 1fr;
  gap:clamp(14px,2vw,24px);align-items:start;
}
.steps--figs .step__fig{display:block}
.steps--figs .step__fig img{
  display:block;width:100%;height:auto;
  filter:drop-shadow(0 12px 22px rgba(27,33,24,.22));
}
/* display:flex, а не block: .step__txt — грид-элемент .step (родительский
   gap ушёл на промежуток между картинкой и текстом), и без своего flex
   с gap внутри него номер/заголовок/абзац слипаются вплотную — вертикальный
   ритм 12px, общий для всех .step на сайте, здесь пропадал. */
.steps--figs .step__txt{display:flex;flex-direction:column;gap:12px;min-width:0}
.steps--figs .step:focus-visible{outline:2px solid var(--blue);outline-offset:6px}
@media (max-width:1080px){
  /* .steps--3 держит три колонки до 1080px — здесь именно тот диапазон,
     где текстовая колонка сжата сильнее всего. Комбинация классов
     специфичнее одиночного .steps--3, поэтому побеждает без !important. */
  .steps--3.steps--figs{grid-template-columns:1fr}
}
@media (max-width:700px){
  .steps--figs .step{grid-template-columns:84px 1fr;gap:14px}
}

/* Активная карточка «Конструктива»: подсветка идёт в модели, но связь
   между наведением и тем, что зажглось, надо показать и на самой
   карточке — иначе на широком экране, где модель ниже сгиба, эффект
   происходит вне поля зрения. */
.steps--figs .step{transition:opacity .25s var(--ease)}
.steps--figs:hover .step:not(.is-lit),
.steps--figs:focus-within .step:not(.is-lit){opacity:.55}
.steps--figs .step.is-lit .step__num{color:var(--blue-text)}
@media (prefers-reduced-motion:reduce) and (scripting:none){
  .steps--figs .step{transition:none}
}

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

   На узком экране персонаж не уменьшается вместе с колонкой, а
   убирается: при ширине меньше ~120px фигура в полный рост перестаёт
   читаться, и остаётся цветное пятно, крадущее экран у текста. */
.fig{display:grid;gap:clamp(18px,3vw,36px);align-items:start}
.fig--left{grid-template-columns:minmax(0,180px) 1fr}
.fig__img{display:block;width:100%;height:auto}
.fig__body{min-width:0}

/* Одиночная фигура без пары абзацев — встаёт в готовый правый слот
   .sec-head--split (там же, где в других секциях лид-абзац), а не в
   отдельную строку сетки: пустой правый слот и отдельная строка под
   картинку вместе читались дырой в вёрстке.

   .fig__solo — обёртка с [data-reveal], .fig__solo__img — сама
   картинка. Раздельно специально: [data-reveal].is-in{transform:none}
   (site.css) владеет transform элемента с этим атрибутом, и повешенный
   на тот же узел --mirror снимался бы в момент появления. Обёртка
   получает анимацию, картинка — трансформ, и они друг другу не мешают. */
.fig__solo{display:block;width:clamp(120px,13vw,190px);margin-bottom:clamp(18px,2.4vw,30px)}
.fig__solo--right{margin-left:auto}
.fig__solo__img{display:block;width:100%;height:auto}
/* Зеркалит позу по горизонтали — см. комментарий у #who в tropa.html:
   меняет направление жеста, не трогая сам кадр. Обвязка и каска на
   персонаже симметричны, отражение не выворачивает их заметно. */
.fig__solo--mirror{transform:scaleX(-1)}

/* ---------- мини-сцена в карточке «Где ставят» ----------
   Сцена сидит над текстом карточки и не растягивается на всю её ширину:
   это вырезка на прозрачном фоне, а не фотография в медиа-поле, и
   растянутая до края она читалась бы как неудачно обрезанный снимок.
   Высота фиксирована, ширина следует пропорции файла — четыре сцены
   разной ширины, и выравнивать их надо по низу, а не по бокам. */
.card__fig{
  display:flex;align-items:flex-end;justify-content:center;
  height:clamp(96px,9vw,132px);
  padding:clamp(14px,1.8vw,20px) clamp(14px,1.8vw,20px) 0;
}
.card__fig img{display:block;width:auto;height:100%;object-fit:contain}

@media (max-width:820px){
  .fig--left{grid-template-columns:1fr}
  .fig__img{display:none}
  .fig__solo{display:none}
}

/* ---------- форма-вопрос ---------- */
.ask{display:grid;grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr);gap:clamp(28px,4vw,72px);align-items:start}
.ask__form{
  background:var(--cream);border:1px solid var(--line-soft);border-radius:4px;
  padding:clamp(24px,2.8vw,38px);box-shadow:var(--shadow-card);
}
.section--deep .ask__form{background:var(--light)}
.form__note{
  margin:12px 0 0;font-family:var(--f-mono);font-size:11px;letter-spacing:.08em;
  color:var(--ink-soft);min-height:1.2em;
}
.form__note[data-state="ok"]{color:var(--blue-text)}
.form__note[data-state="err"]{color:#B4553A}
@media (max-width:900px){.ask{grid-template-columns:1fr}}

/* ============================================================
   calc.html — конфигуратор
   ============================================================ */
.cfg{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(0,.85fr);gap:clamp(26px,3.4vw,58px);align-items:start}
.cfg__steps{display:grid;gap:clamp(22px,3vh,38px)}

.qstep{border-top:1px solid var(--line);padding-top:clamp(20px,2.6vh,30px)}
.qstep:first-child{border-top:0;padding-top:0}
.qstep__head{display:flex;align-items:baseline;gap:14px;margin-bottom:18px}
.qstep__num{font-family:var(--f-mono);font-size:11px;letter-spacing:.2em;color:var(--amber);flex:none}
.qstep__hint{margin:-8px 0 18px;font-size:14.5px;color:var(--ink-soft);line-height:1.6}

/* счётчик маршрутов */
.rows{display:grid;gap:1px;background:var(--line-soft);border:1px solid var(--line-soft)}
.row{
  background:var(--cream);display:flex;align-items:center;gap:16px;
  padding:14px 16px;flex-wrap:wrap;
}
.row__label{font-size:15.5px;flex:1 1 160px}
.row__label small{display:block;font-family:var(--f-mono);font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-soft);margin-top:3px}
.stepper{display:flex;align-items:center;gap:2px;flex:none}
.stepper button{
  width:38px;height:38px;flex:none;cursor:pointer;
  background:var(--light);border:1px solid var(--line);border-radius:2px;
  font-family:var(--f-mono);font-size:16px;line-height:1;color:var(--ink);
  transition:background .25s ease,border-color .25s ease,color .25s ease;
}
.stepper button:hover:not(:disabled){background:var(--blue);border-color:var(--blue);color:var(--light)}
.stepper button:disabled{opacity:.35;cursor:not-allowed}
.stepper output{
  min-width:56px;text-align:center;font-family:var(--f-display);font-weight:600;
  font-size:19px;font-variant-numeric:tabular-nums;color:var(--blue);
}

/* варианты-плитки: радио и чекбоксы */
.opts{display:grid;gap:10px}
.opts--2{grid-template-columns:repeat(2,minmax(0,1fr))}
.opts--chips{display:flex;flex-wrap:wrap;gap:8px}
.opt{
  position:relative;display:flex;align-items:flex-start;gap:12px;cursor:pointer;
  background:var(--light);border:1px solid var(--line);border-radius:3px;
  padding:15px 16px;font-size:15.5px;line-height:1.45;
  transition:border-color .25s ease,background .25s ease,box-shadow .25s ease;
}
.opt input{position:absolute;opacity:0;width:0;height:0}
.opt__mark{
  flex:none;width:18px;height:18px;margin-top:1px;border:1px solid var(--line);
  border-radius:2px;background:var(--cream);position:relative;
  transition:border-color .25s ease,background .25s ease;
}
.opt--radio .opt__mark{border-radius:50%}
.opt__mark::after{
  content:"";position:absolute;inset:0;margin:auto;opacity:0;
  transition:opacity .2s ease,transform .2s var(--ease);
}
.opt--radio .opt__mark::after{width:8px;height:8px;border-radius:50%;background:var(--light)}
.opt--check .opt__mark::after{
  width:5px;height:9px;margin-top:2px;border:2px solid var(--light);
  border-top:0;border-left:0;transform:rotate(42deg) scale(.6);
}
.opt:hover{border-color:rgba(22,33,43,.35)}
.opt:has(input:checked){border-color:var(--blue);background:rgba(46,92,138,.06);box-shadow:inset 0 0 0 1px rgba(46,92,138,.25)}
.opt:has(input:checked) .opt__mark{background:var(--blue);border-color:var(--blue)}
.opt:has(input:checked) .opt__mark::after{opacity:1}
.opt--check:has(input:checked) .opt__mark::after{transform:rotate(42deg) scale(1)}
.opt:has(input:focus-visible){outline:2px solid var(--amber);outline-offset:3px}
.opt b{font-weight:600}
.opt small{display:block;color:var(--ink-soft);font-size:13.5px;margin-top:4px}

.chipopt{
  position:relative;cursor:pointer;
  font-family:var(--f-mono);font-size:12px;letter-spacing:.1em;
  border:1px solid var(--line);border-radius:999px;padding:9px 16px;
  background:var(--light);color:var(--ink-soft);
  transition:color .25s ease,border-color .25s ease,background .25s ease;
}
.chipopt input{position:absolute;opacity:0;width:0;height:0}
.chipopt:hover{color:var(--ink);border-color:rgba(22,33,43,.35)}
.chipopt:has(input:checked){background:var(--blue);border-color:var(--blue);color:var(--light)}
.chipopt:has(input:focus-visible){outline:2px solid var(--amber);outline-offset:3px}

/* сводка */
.sum{position:sticky;top:calc(var(--head-h,64px) + 18px)}
.sum__box{
  background:var(--forest);color:var(--light);border-radius:4px;
  padding:clamp(22px,2.4vw,32px);box-shadow:var(--shadow-card);
}
.sum__box .eyebrow{color:var(--amber)}
.sum__title{font-family:var(--f-display);font-weight:600;font-size:1.25rem;margin:0 0 18px;letter-spacing:-.02em}
.sum__list{margin:0;display:grid;gap:1px;background:rgba(247,243,235,.14);border:1px solid rgba(247,243,235,.14);border-radius:2px}
.sum__list > div{background:var(--forest);padding:14px 16px}
.sum__list dt{
  font-family:var(--f-mono);font-size:10px;letter-spacing:.18em;text-transform:uppercase;
  color:rgba(247,243,235,.5);margin:0 0 7px;
}
.sum__list dd{margin:0;font-size:15.5px;line-height:1.5;color:var(--light)}
.sum__list dd em{font-style:normal;color:var(--amber);font-family:var(--f-mono);font-size:13px}
.sum__list dd ul{list-style:none;margin:0;padding:0;display:grid;gap:5px}
.sum__list dd li::before{content:"— ";color:var(--amber)}
.sum .cta-row{margin-top:22px}
.sum__hint{
  margin:14px 0 0;font-family:var(--f-mono);font-size:10.5px;letter-spacing:.08em;
  line-height:1.6;color:rgba(247,243,235,.5);
}
.sum-lead{margin-top:22px;padding-top:20px;border-top:1px solid rgba(247,243,235,.22)}
.sum-lead__form{display:grid;gap:12px}
.sum-lead__form .hp{display:none}
.sum-lead__form .field label{color:var(--light);font-weight:500}
.sum-lead__form .field input{background:rgba(247,243,235,.98);color:var(--ink)}
.sum-lead__form .check{color:rgba(247,243,235,.76)}
.sum-lead__form .check a{color:var(--light)}
.sum-lead__form .form__note{color:rgba(247,243,235,.72)}
.sum-lead__form .form__note[data-state="ok"]{color:#b7d8ff}
.sum-lead__form .form__note[data-state="err"]{color:#ffb59f}

/* статический блок «входит / не входит» */
.kit{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--gap)}
.kit__col{background:var(--light);border:1px solid var(--line-soft);border-radius:3px;padding:22px 22px 24px}
.kit__col h3{
  font-family:var(--f-mono);font-weight:500;font-size:11px;letter-spacing:.2em;
  text-transform:uppercase;color:var(--ink-soft);margin:0 0 14px;
}
.kit__col--in h3{color:var(--blue-text)}
.kit__col--out h3{color:var(--wood)}
.kit__col ul{list-style:none;margin:0;padding:0;display:grid;gap:10px;font-size:15.5px;line-height:1.5}
.kit__col li{display:flex;gap:10px}
.kit__col li::before{content:"—";flex:none;color:var(--amber);font-family:var(--f-mono);font-size:12px;line-height:1.6}
@media (max-width:900px){
  .cfg{grid-template-columns:1fr}
  .sum{position:static}
}
@media (max-width:620px){.kit,.opts--2{grid-template-columns:1fr}}

/* ============================================================
   works.html — сетка проектов + галерея
   ============================================================ */
/* Каркас панели фильтров (.bar / .bar__in / .chips / .chip) — в site.css;
   здесь только то, чем works.html отличается от каталога: счётчик стоит
   в той же строке справа, а не под панелью. */
.bar__count{margin-left:auto}
@media (max-width:900px){
  .bar__count{margin-left:0}
}

/* Бенто-сетка работ. Четыре колонки на десктопе; крупная плитка занимает
   две колонки и две строки, широкая — две колонки и одну. Размер задаётся
   классом от генератора (tools/works_layout.py), а не автоматикой
   grid-auto-flow: раскладка должна быть предсказуемой, иначе гейт по
   разрешению теряет смысл.

   Узор PATTERN = [big, wide, small, small] закрывает ровно два ряда без
   остатка: 4 (big, 2×2) + 2 (wide, 2×1) + 1 + 1 (small) = 8 ячеек = 2
   ряда по 4 колонки. Дыр в раскладке арифметически не остаётся, поэтому
   grid-auto-flow держим авто (по умолчанию row) — dense здесь не нужен и
   даже вреден: dense ищет для каждого элемента первую подходящую ячейку
   заново, из-за чего узкие плитки обгоняют более раннюю широкую и
   порядок ОТРИСОВКИ перестаёт совпадать с порядком works.json. */
.bento{
  display:grid;gap:2px;
  grid-template-columns:repeat(4,1fr);
  grid-auto-rows:minmax(0,1fr);
  /* Зазор в 2px раньше показывал фон страницы, и разделители читались
     белыми (правка заказчика 25.08.2026: «чтобы не белый был, а такой же
     коричневый, как на блоках с фотками»). Красим саму сетку — тогда
     через зазор просвечивает она, а не .section. Тот же --wood, что у
     разделителей аккордеона типов: приём на сайте один. */
  background:var(--wood);
}
/* Пока кадр не пришёл, плитка не пустая дыра, а ждущий прямоугольник:
   по тёмно-зелёной подложке медленно идёт светлый блик. Он живёт на
   ::before под самим кадром, поэтому как только картинка проявилась —
   она его закрывает, а класс .is-ready убирает псевдоэлемент совсем,
   чтобы анимация не крутилась под 44 загруженными плитками.
   Правка заказчика 25.08.2026: «чтобы не было такого, что они
   прогружаются на ходу, некрасиво, либо добавить красивые лоадеры». */
.bento__tile::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background-color:var(--forest);
  background-image:linear-gradient(100deg,
    rgba(255,255,255,0) 34%, rgba(255,255,255,.075) 50%, rgba(255,255,255,0) 66%);
  background-size:260% 100%;
  animation:tile-wait 1.6s linear infinite;
}
.bento__tile.is-ready::before{content:none}
@keyframes tile-wait{from{background-position:130% 0}to{background-position:-130% 0}}
/* Кадр не появляется рывком, а проявляется за 0.4s. Переключение кадров
   в листалке идёт через hidden (display:none), с opacity не спорит. */
.bento__tile img{opacity:0;transition:opacity .4s var(--e2)}
.bento__tile img.is-on{opacity:1}
@media (prefers-reduced-motion:reduce) and (scripting:none){
  .bento__tile::before{animation:none}
  .bento__tile img{transition:none}
}
.bento__tile{
  position:relative;overflow:hidden;
  aspect-ratio:4/3;
  text-decoration:none;color:#fff;background:var(--forest);
  cursor:pointer;
  -webkit-user-drag:none;
  /* Горизонтальный жест — наш, вертикальный остаётся прокруткой страницы.
     Без этого объявления вычисленное значение — auto, и браузер на телефоне
     забирает жест себе уже на 20 px: трасса обрывается на pointercancel,
     pointerup не приходит, листание не срабатывает вовсе. Соседние страницы
     объявляют то же самое (catalog.css:111,184,254), а стрелок на coarse-
     указателе нет (ниже), так что без pan-y свайпа на телефоне не было бы. */
  touch-action:pan-y;
}
/* Широкая плитка занимает 2 колонки, но ОДНУ строку — без своего
   аспекта она унаследовала бы 4/3 от базового класса и на двойной
   ширине вышла бы вдвое выше нужной строки (4/3 при удвоенной ширине
   ≈ высота ДВУХ строк). 8/3 держит её высоту равной высоте соседней
   маленькой плитки, иначе трек строки раздувается и рядом с крупной
   плиткой в сетке остаётся пустая дыра. */
.bento__tile--wide{grid-column:span 2;aspect-ratio:8/3}
.bento__tile--big{grid-column:span 2;grid-row:span 2;aspect-ratio:auto}
/* Под фильтром по типу конструкции все плитки становятся обычными.
   Правило «блок отдаётся целиком» (tools/works_layout.py) держится только
   на ПОЛНОМ списке: блок [big, wide, small, small] закрывает ровно два
   ряда, потому что в нём есть все четыре плитки. Фильтр же выдёргивает
   плитки из середины блоков, крупные остаются крупными — и сетка рвётся.
   Замер покрытия #wGrid до этого правила: все — 99.0%, «Модульные» — 95.1%,
   «Прочие» — 96.5%, «На опорах» — 66.2%, «На деревьях» — 74.8%.
   Пересчитывать раскладку на лету незачем: под фильтром список короткий,
   ритм крупных плиток в нём всё равно не читается, а ровная сетка читается. */
.bento.is-filtered .bento__tile--wide,
.bento.is-filtered .bento__tile--big{
  grid-column:span 1;grid-row:span 1;aspect-ratio:4/3;
}
.bento__tile img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;display:block;
  /* -webkit-user-drag на .bento__tile (выше) не наследуется потомками —
     без дублирования здесь браузер сам стартует HTML5 drag над <img> и
     обрывает поток pointer-событий листания (см. bento.js, тот же
     эффект уже ловили и лечили на ленте главной, .film img). */
  -webkit-user-drag:none;user-select:none;
  /* --e2 из index.html здесь не в области видимости — на внутренних
     страницах общий токен транзишена называется --ease (site.css). */
  transition:transform .6s var(--ease);
}
/* Скрытый кадр обязан пропасть ИМЕННО ЗДЕСЬ. Правило [hidden]{display:none}
   живёт в таблице стилей браузера, а любое авторское объявление сильнее
   пользовательского агента — поэтому display:block выше отменял hidden у
   всех догруженных кадров, и они лежали стопкой inset:0 друг на друге:
   листание переключало точки, но картинка не менялась. */
.bento__tile img[hidden]{display:none}
.bento__tile:hover img{transform:scale(1.03)}
/* Текст лежит НА обложке: снизу тёмный градиент, поверх — город и
   название. Тот же приём, что у кадров ленты на главной. */
.tile__foot{
  position:absolute;left:0;right:0;bottom:0;z-index:2;
  display:flex;flex-direction:column;gap:6px;
  padding:16px clamp(14px,1.4vw,20px);
  background:linear-gradient(to top,rgba(12,18,14,.82) 0%,rgba(12,18,14,.45) 55%,transparent 100%);
}
.tile__name{font-family:var(--f-display);font-size:clamp(15px,1.15vw,19px);line-height:1.25}
.bento__tile--big .tile__name{font-size:clamp(19px,1.6vw,26px)}
.tile__kind{position:absolute;top:12px;left:12px;z-index:2;color:#fff}
/* Точки и стрелки лежат поверх обложки, но ниже подписи по стеку: подпись
   несёт смысл, они — управление. */
.tile__dots{
  position:absolute;right:12px;bottom:12px;z-index:3;
  display:flex;gap:6px;
}
.tile__dots button{
  width:7px;height:7px;padding:0;border:0;border-radius:50%;
  background:rgba(255,255,255,.45);cursor:pointer;
}
.tile__dots button[aria-current="true"]{background:#fff}
.tile__arrow{
  position:absolute;top:50%;transform:translateY(-50%);z-index:3;
  width:34px;height:34px;border:0;border-radius:50%;
  background:rgba(247,243,235,.85);color:var(--ink);
  font-size:18px;line-height:1;cursor:pointer;
  opacity:0;transition:opacity .25s ease;
}
.bento__tile:hover .tile__arrow,.tile__arrow:focus-visible{opacity:1}
.tile__arrow--prev{left:10px}
.tile__arrow--next{right:10px}
/* На телефоне стрелок нет: там листают свайпом, а две кнопки поверх
   небольшой плитки перекрыли бы саму фотографию. */
@media (pointer:coarse){.tile__arrow{display:none}}
@media (max-width:900px){
  .bento{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:560px){
  /* При одной колонке разница размеров теряет смысл: все плитки равны. */
  .bento{grid-template-columns:1fr}
  .bento__tile--wide,.bento__tile--big{grid-column:span 1;grid-row:span 1;aspect-ratio:4/3}
}

/* Пустой результат фильтра (напр. «Детские» — в каталоге пока нет ни
   одного настоящего нетпарка): страховочная линия сверху вместо сетки. */
.wgrid__empty{
  margin:0;padding:48px 0 8px;border-top:1px solid var(--line);
  font-family:var(--f-mono);font-size:12px;letter-spacing:.1em;
  text-transform:uppercase;color:var(--ink-soft);text-align:center;
}
.wgrid__empty[hidden]{display:none}

/* ============================================================
   works/*.html — страница работы (44 шт., генератор — tools/build_pages.py)
   ------------------------------------------------------------
   Правка заказчика 09.09.2026: «при клике на работу надо переходить на
   страницу с работой, где сборка бенто-сетка со всеми фото, а справа
   описание». Раньше здесь была колонка разворотов во всю ширину.

   Сама сетка снимков — это .bento с works.html, целиком, вместе с узором
   [big, wide, small, small] и брейкпоинтами: второй копии этих правил нет
   и быть не должно. Здесь только то, чем страница работы отличается —
   две колонки и поведение правой.
   ============================================================ */
.wpage__city{margin-top:18px}
.wpage__title{margin-top:8px}
/* 320 px правой колонке хватает на подпись и значение в одну строку у
   всех четырёх строк описания («Тип конструкции» — самая длинная) и не
   отнимает у сетки снимков четвёртую колонку: 1320 - 320 - 40 = 960, это
   ровно четыре плитки по 239 px с зазорами (tools/build_pages.py). */
.wpage__grid{
  display:grid;grid-template-columns:minmax(0,1fr) 320px;
  gap:clamp(24px,3vw,40px);align-items:start;
  margin-top:clamp(20px,3vh,34px);
}
/* Плитка снимка — ссылка на увеличенный кадр, и курсор обязан это
   показывать: zoom-in читается как «откроется крупно», а не «уведёт на
   другую страницу», как pointer у плиток галереи. */
.bento--shots .bento__tile{cursor:zoom-in}
/* Колонка описания едет вместе с прокруткой: снимков на странице до
   сорока, и кнопка заявки не должна оставаться в самом верху страницы.
   96px — высота залипающей шапки плюс воздух. */
.wpage__side{position:sticky;top:96px}
.wpage__facts{margin-top:14px}
/* Внутри узкой колонки паддинги .deflist (22px) съедают половину ширины. */
.wpage__facts > div{padding:14px 16px 16px}
.wpage__cta{margin-top:20px;width:100%;justify-content:center}
.wpage__tel{margin-top:12px;font-size:14px;color:var(--ink-soft);text-align:center}
.wpage__nav{
  display:flex;flex-direction:column;gap:10px;align-items:flex-start;
  margin-top:24px;padding-top:20px;border-top:1px solid var(--line-soft);
}
/* Ниже 1080 px описание уходит ПОД сетку (правка заказчика), и залипание
   ему больше не нужно: колонка и так последняя на странице. Порог не 900
   и не 1200: на 1080 сетка снимков в левой колонке ужимается до 170 px на
   плитку — это уже не фотография, а марка. */
@media (max-width:1080px){
  .wpage__grid{grid-template-columns:1fr}
  .wpage__side{position:static}
  .wpage__cta{max-width:420px}
  .wpage__tel{text-align:left}
}

/* ============================================================
   news.html — список + аккордеон
   ============================================================ */
.nlist{display:grid;gap:1px;background:var(--line-soft);border-block:1px solid var(--line-soft)}
.nitem{background:var(--cream)}
.nitem[hidden]{display:none}
.nitem__head{
  width:100%;display:grid;grid-template-columns:132px minmax(0,1fr) auto;
  gap:clamp(16px,2.2vw,30px);align-items:center;
  background:none;border:0;padding:18px 0;text-align:left;font:inherit;color:inherit;cursor:pointer;
  transition:background .3s ease;
}
.nitem__head:hover{background:var(--light)}
.nitem__pic{
  width:132px;aspect-ratio:4 / 3;object-fit:cover;border-radius:2px;background:var(--cream-deep);
  transition:transform .5s var(--ease);
}
.nitem__head:hover .nitem__pic{transform:scale(1.04)}
/* Внутренности кнопки — инлайновые <span> (внутрь <button> нельзя класть
   блочную семантику вроде <h3>), поэтому раскладку задаём явно. */
.nitem__mid{min-width:0;display:block}
.nitem__date{
  font-family:var(--f-mono);font-size:10.5px;letter-spacing:.18em;text-transform:uppercase;
  color:var(--blue-text);display:block;margin-bottom:8px;
}
.nitem__title{
  display:block;
  font-family:var(--f-display);font-weight:600;font-size:clamp(15.5px,1.35vw,19px);
  letter-spacing:-.015em;line-height:1.25;margin:0 0 8px;
}
.nitem__lead{display:block;margin:0;font-size:14.5px;line-height:1.6;color:var(--ink-soft)}
.nitem__sign{
  flex:none;width:34px;height:34px;border:1px solid var(--line);border-radius:50%;
  display:grid;place-items:center;color:var(--ink-soft);
  font-family:var(--f-mono);font-size:15px;line-height:1;
  transition:transform .35s var(--ease),background .3s ease,color .3s ease,border-color .3s ease;
}
.nitem__head:hover .nitem__sign{border-color:var(--blue);color:var(--blue)}
.nitem__head[aria-expanded="true"] .nitem__sign{
  transform:rotate(45deg);background:var(--blue);border-color:var(--blue);color:var(--light);
}
.nitem__body{
  display:grid;grid-template-rows:0fr;
  transition:grid-template-rows .45s var(--ease);
}
.nitem__body[data-open="1"]{grid-template-rows:1fr}
.nitem__body > div{overflow:hidden}
.nitem__full{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.72fr);
  gap:clamp(20px,3vw,44px);padding:6px 0 30px;align-items:start;
}
.nitem__full p{margin:0 0 12px;font-size:15.5px;line-height:1.7;color:var(--ink-soft)}
.nitem__full p:last-child{margin-bottom:0}
.nitem__full img{width:100%;border-radius:3px;background:var(--cream-deep)}
@media (max-width:900px){
  .nitem__head{grid-template-columns:96px minmax(0,1fr) auto;gap:16px}
  .nitem__pic{width:96px}
  .nitem__full{grid-template-columns:1fr}
}
@media (max-width:620px){
  .nitem__head{grid-template-columns:74px minmax(0,1fr) auto;gap:12px;padding:16px 0;align-items:start}
  .nitem__pic{width:74px}
  .nitem__sign{width:30px;height:30px;margin-top:2px}
  .nitem__lead{display:none}
}

/* ============================================================
   contacts.html
   ============================================================ */
.cgrid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(26px,3.4vw,64px);align-items:start}
.cfacts{display:grid;gap:1px;background:var(--line-soft);border:1px solid var(--line-soft)}
.cfacts > div{background:var(--cream);padding:20px 22px 22px}
.cfacts dt{
  font-family:var(--f-mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;
  color:var(--ink-soft);margin:0 0 9px;
}
.cfacts dd{margin:0;font-size:17px;line-height:1.45}
.cfacts dd a{text-decoration:none;border-bottom:1px solid transparent;transition:color .25s ease,border-color .25s ease}
.cfacts dd a:hover{color:var(--blue);border-color:var(--blue)}
.cfacts dd.big{font-family:var(--f-display);font-weight:600;font-size:clamp(1.15rem,1.8vw,1.5rem);letter-spacing:-.02em;color:var(--blue)}

.socs{display:flex;flex-wrap:wrap;gap:10px;margin-top:var(--gap)}
.soc{
  display:inline-flex;align-items:center;gap:9px;text-decoration:none;
  border:1px solid var(--line);border-radius:999px;padding:10px 18px;
  font-family:var(--f-mono);font-size:11.5px;letter-spacing:.12em;text-transform:uppercase;
  color:var(--ink-soft);transition:color .25s ease,border-color .25s ease,background .25s ease;
}
.soc:hover{color:var(--light);background:var(--blue);border-color:var(--blue)}

.mapbox{
  position:relative;border:1px solid var(--line);border-radius:3px;overflow:hidden;
  background:var(--cream-deep);min-height:380px;display:grid;place-items:center;
  padding:clamp(24px,3vw,40px);text-align:center;
}
.mapbox::before{
  content:"";position:absolute;inset:0;
  background-image:
    linear-gradient(var(--line-soft) 1px,transparent 1px),
    linear-gradient(90deg,var(--line-soft) 1px,transparent 1px);
  background-size:38px 38px;opacity:.9;
}
.mapbox__in{position:relative;max-width:420px}
.mapbox__pin{
  width:52px;height:52px;margin:0 auto 18px;border-radius:50%;
  background:var(--blue);display:grid;place-items:center;color:var(--light);
  font-family:var(--f-mono);font-size:18px;
  box-shadow:0 0 0 10px rgba(46,92,138,.14);
}
.mapbox h3{margin:0 0 10px}
.mapbox p{margin:0;font-size:15px;line-height:1.6;color:var(--ink-soft)}
.mapbox .btn-line{margin-top:20px}
/* Живая карта (14.09.2026). До этого на месте карты стоял текст-заглушка
   из прототипа, и на боевом сайте посетитель читал «виджет не подключён».
   Карта — iframe конструктора, а не его script-версия: чужой скрипт не
   исполняется в контексте страницы, а loading="lazy" не тянет карту,
   пока до неё не дошли. Решётка-фон заглушки здесь не нужна. */
.mapbox--live{display:block;padding:0;min-height:0;text-align:left}
.mapbox--live::before{content:none}
.mapbox__frame{display:block;width:100%;height:clamp(360px,56vh,520px);border:0}
/* Подписи и кнопки под картой нет: адрес стоит слева в блоке контактов,
   а «Открыть в Яндекс Картах» есть в самом виджете — вторая кнопка дублировала бы её. */

.welcome{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(26px,3.6vw,64px);align-items:center}
.welcome img{width:100%;border-radius:3px;box-shadow:var(--shadow-card)}
.welcome__shot{aspect-ratio:16 / 9;height:auto;object-fit:cover;display:block}
@media (max-width:900px){
  .cgrid,.welcome{grid-template-columns:1fr}
}

/* ============================================================
   tropa.html — галерея из плоских кадров (без модалки works.html:
   на прототипе всего 4 кадра, заменятся генерацией в Task 8)
   ============================================================ */
.tgallery img{
  width:100%;height:100%;aspect-ratio:4 / 3;object-fit:cover;
  border-radius:3px;background:var(--cream-deep);
  filter:saturate(.94) contrast(1.04);
  transition:transform .5s var(--ease);
}
.tgallery img:hover{transform:scale(1.02)}
@media (prefers-reduced-motion:reduce) and (scripting:none){.tgallery img:hover{transform:none}}

/* ---------- бенто-раскладка галереи (tropa.html) ----------
   Модификатор, а не правка .tgallery: базовый класс общий с галереей
   домиков, и переписать его значило бы молча переверстать вторую
   страницу — а там шесть кадров одной пропорции, для которых разброс
   размеров пришлось бы назначать произвольно.

   Высота строк задана grid-auto-rows, а не aspect-ratio на ячейках.
   Ячейка на две строки должна быть ровно вдвое выше рядовой плюс зазор;
   с aspect-ratio строки считались бы от содержимого, и высоты соседних
   ячеек разъезжались бы на промежуточных ширинах. */
.tgallery--bento{
  display:grid;grid-template-columns:repeat(4,1fr);gap:var(--gap);
  grid-auto-rows:clamp(128px,13.5vw,196px);
}
.tgallery--bento img{width:100%;height:100%;object-fit:cover}
.tgallery--bento .b-anchor{grid-column:span 2;grid-row:span 2}
.tgallery--bento .b-tall{grid-row:span 2}
.tgallery--bento .b-wide{grid-column:span 3}

/* Планшет: две колонки. Якорь остаётся во всю ширину, но перестаёт быть
   двухэтажным — на 2 колонках он съел бы весь экран. */
@media (max-width:900px){
  .tgallery--bento{grid-template-columns:repeat(2,1fr)}
  .tgallery--bento .b-anchor{grid-column:span 2;grid-row:span 1}
  .tgallery--bento .b-wide{grid-column:span 2}
  .tgallery--bento .b-tall{grid-row:span 1}
}

/* Телефон: одна колонка, порядок кадров сохраняется. Спаны сброшены,
   иначе одиночная колонка растянула бы якорь на два экрана. */
@media (max-width:560px){
  .tgallery--bento{grid-template-columns:1fr;grid-auto-rows:clamp(180px,52vw,240px)}
  .tgallery--bento .b-anchor,
  .tgallery--bento .b-tall,
  .tgallery--bento .b-wide{grid-column:auto;grid-row:auto}
}

/* ---------- бенто с одним акцентом (domiki.html) ----------
   Три колонки, якорь 2×2, пять рядовых ячеек. Отдельный модификатор,
   а не переиспользование .tgallery--bento: там четыре колонки под шесть
   кадров разной пропорции, здесь три под шесть одинаковых, и общий
   класс пришлось бы обвешивать исключениями с обеих сторон. */
.tgallery--bento-quiet{
  display:grid;grid-template-columns:repeat(3,1fr);gap:var(--gap);
  grid-auto-rows:clamp(140px,14vw,205px);
}
.tgallery--bento-quiet img{width:100%;height:100%;object-fit:cover}
.tgallery--bento-quiet .b-anchor{grid-column:span 2;grid-row:span 2}
@media (max-width:900px){
  .tgallery--bento-quiet{grid-template-columns:repeat(2,1fr)}
  .tgallery--bento-quiet .b-anchor{grid-column:span 2;grid-row:span 1}
}
@media (max-width:560px){
  .tgallery--bento-quiet{grid-template-columns:1fr;grid-auto-rows:clamp(180px,52vw,240px)}
  .tgallery--bento-quiet .b-anchor{grid-column:auto;grid-row:auto}
}

/* ---------- «Смежный тип»: переход между tropa.html и domiki.html ----------
   Ссылкой сделана вся карточка, а не заголовок внутри: цель клика должна
   быть крупной, иначе на телефоне в неё не попасть пальцем без промаха.
   Отсюда <span> с display:block вместо <div> — <a> не может содержать
   секционные теги, а вложенный блок в инлайновой ссылке невалиден. */
.kin{
  display:grid;grid-template-columns:minmax(0,220px) 1fr auto;
  gap:clamp(18px,3vw,34px);align-items:center;
  padding:clamp(16px,2vw,22px);
  border:1px solid var(--line);border-radius:3px;
  text-decoration:none;color:inherit;
  transition:border-color .25s var(--ease),transform .25s var(--ease);
}
.kin:hover{border-color:var(--blue);transform:translateY(-2px)}
.kin:focus-visible{outline:2px solid var(--blue);outline-offset:3px}
.kin__media{display:block;overflow:hidden;border-radius:2px}
/* height:auto обязателен. У <img> проставлены атрибуты width/height (нужны
   как есть — резервируют место и гасят скачок вёрстки на загрузке), и браузер
   вносит их в каскад презентационной подсказкой height:1200px и т.п.
   aspect-ratio считает высоту по ширине только когда высота auto; здесь она
   не auto, и правило молча игнорируется — картинка рендерится в атрибутной
   высоте, а не в 4/3. width спасает то, что width:100% ниже перебивает
   подсказку width; для высоты нужна отдельная строка. */
.kin__media img{display:block;width:100%;height:auto;aspect-ratio:4/3;object-fit:cover}
.kin__body{display:block}
.kin__body .eyebrow{display:block;margin-bottom:8px}
.kin__body .h3{display:block;margin:0 0 8px}
.kin__note{display:block;font-size:15.5px;line-height:1.6;max-width:52ch}
.kin__arw{font-size:26px;color:var(--blue);line-height:1}
@media (max-width:700px){
  .kin{grid-template-columns:1fr;gap:16px}
  .kin__arw{justify-self:start}
}
@media (prefers-reduced-motion:reduce) and (scripting:none){
  .kin{transition:none}
  .kin:hover{transform:none}
}
