/* Hallmark · genre: modern-minimal · macrostructure: Photographic (кадр во весь экран, ч/б + цвет только на технике)
 * design-system: DESIGN-SITE.md · nav: N1b три секции (знак · разделы · звонок), бургер до 1024
 * enrichment: Tier C generated still + чертёжный слой поверх · signature: выноска с числом
 * pre-emit critique: P5 H5 E5 S5 R4 V4
 * Первый экран главной mtg-msk.ru. Система — DESIGN-SITE.md, токены — tokens.css.
 */

/* ─────────────────────────── Шрифты, локально ────────────────────────────
   Ни одной внешней загрузки: файлы лежат в site/fonts. Кириллица и латиница
   разнесены по unicode-range, чтобы кириллический сабсет качался первым. */
@font-face{font-family:"Geologica";src:url("../fonts/geologica-cyrillic-shrp-normal.woff2") format("woff2-variations");
  unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;font-weight:100 900;font-display:swap}
@font-face{font-family:"Geologica";src:url("../fonts/geologica-latin-shrp-normal.woff2") format("woff2-variations");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+2000-206F,U+2122;font-weight:100 900;font-display:swap}

@font-face{font-family:"Golos Text";src:url("../fonts/golos-text-cyrillic-wght-normal.woff2") format("woff2-variations");
  unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;font-weight:400 900;font-display:swap}
@font-face{font-family:"Golos Text";src:url("../fonts/golos-text-latin-wght-normal.woff2") format("woff2-variations");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+2000-206F,U+2122;font-weight:400 900;font-display:swap}

@font-face{font-family:"Martian Mono";src:url("../fonts/martian-mono-cyrillic-400-normal.woff2") format("woff2");
  unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;font-weight:400;font-display:swap}
@font-face{font-family:"Martian Mono";src:url("../fonts/martian-mono-latin-400-normal.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+2000-206F,U+2122;font-weight:400;font-display:swap}
@font-face{font-family:"Martian Mono";src:url("../fonts/martian-mono-cyrillic-600-normal.woff2") format("woff2");
  unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;font-weight:600;font-display:swap}
@font-face{font-family:"Martian Mono";src:url("../fonts/martian-mono-latin-600-normal.woff2") format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+2000-206F,U+2122;font-weight:600;font-display:swap}

/* ─────────────────────────────── База ──────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html, body { overflow-x: clip; }
/* Якорь не упирается в кромку окна: строка прайса, на которую пришли
   по прямой ссылке, встаёт с отступом, а не встык к краю (замер на 1440
   до правки: top = 0). На мобильном отступ больше — там шапка липкая. */
html { -webkit-text-size-adjust: 100%; scroll-padding-top: var(--space-md); }

body {
  margin: 0;
  min-height: 100svh;
  display: flex; flex-direction: column;
  background: var(--graphite);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--text-md);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Прячется сдвигом на собственную высоту, а не отступом в -100px: при
   увеличенном базовом шрифте ссылка вырастает и фиксированный отступ
   перестаёт её скрывать — край выглядывает над шапкой. */
.skip {
  position: absolute; left: var(--space-sm); top: var(--space-sm); z-index: 99;
  translate: 0 calc(-100% - var(--space-sm) * 2);
  background: var(--amber); color: var(--graphite);
  font-family: var(--font-mono); font-size: var(--text-md);
  padding: var(--space-sm) var(--space-md); border-radius: var(--radius-btn);
}
.skip:focus { translate: 0 0; }

/* Кольцо двойное: тёмный контур вплотную к элементу, янтарный снаружи.
   Одиночное янтарное кольцо на жёлтой кнопке звонка давало контраст 1:1
   к её же заливке — на границе оно попросту исчезало. */
:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 3px;
  box-shadow: 0 0 0 3px var(--graphite);
}

/* Спрайт фирменного знака: лежит первым в body и ничего не занимает.
   position:absolute обязателен — без него нулевой svg всё равно создаёт
   строку в потоке и сдвигает первый экран на высоту строки. */
.brand-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Знак в подвале — печать организации в основной надписи чертежа.
   Почему знак, а не полный локап (brand/logo-lockup.svg): слово в нём залито
   фирменным зелёным #234623, и на графите #0E1210 это 1.79:1 — нечитаемо;
   перекрашивать логотип заказчика мы не вправе. Плюс второе «МОСТЕХНОГРАД»
   шириной 300px прямо под тем же словом в шапке ничего не добавляет.
   --chrome-dim, а не --chrome: подвал не должен спорить с шапкой. */
.foot__mark { display: block; width: 40px; height: 40px; color: var(--chrome-dim); margin-bottom: var(--space-md); }

/* ── Кадра нет ────────────────────────────────────────────────────────────
   Не битая картинка и не серый прямоугольник: поле той же пропорции
   с чертёжной штриховкой — так на чертеже показывают разрез. Место под кадр
   обозначено, плитка в сетке не съезжает, высота держится.

   Блок переехал сюда из tpl.css 11.08.2026. Причина: tpl.css подключают
   только страницы генератора, а главная собрана вручную и грузит
   tokens + hero + page. Из-за этого в ведомости услуг у строк без кадра
   не рисовалось вообще ничего — графа шириной 236px оставалась пустой,
   и владелец прочитал это как недоделку. hero.css — единственный файл
   с компонентами, который подключают ВСЕ страницы, поэтому общая заглушка
   живёт здесь; частные пропорции и позиционирование остались в tpl.css. */
.noshot {
  position: relative; aspect-ratio: 1.6 / 1;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: var(--space-2xs);
  padding: var(--space-md);
  background:
    repeating-linear-gradient(135deg,
      transparent 0 9px,
      color-mix(in oklch, var(--chrome) 12%, transparent) 9px 10px),
    var(--surface);
  border: var(--hair) solid var(--edge);
}
.noshot__mark {
  position: absolute; left: var(--space-md); top: var(--space-md);
  width: 22px; height: 22px;
  border-left: 2px solid var(--chrome-dim); border-top: 2px solid var(--chrome-dim);
}
.noshot__key {
  font-family: var(--font-mono); font-size: var(--text-mono-label);
  letter-spacing: .05em; text-transform: uppercase; color: var(--chrome);
}
.noshot__note {
  font-family: var(--font-mono); font-size: var(--text-mono-label);
  line-height: 1.5; color: var(--text-2); max-width: 34ch;
}

/* ─────────────────────────── Шапка · N9 ────────────────────────────────── */
.nav {
  position: relative; z-index: var(--z-nav); isolation: isolate;
  /* wrap — страховка от наложения: если человек поднял базовый шрифт
     в настройках браузера, блоки шапки переносятся, а не наезжают друг
     на друга. При обычном шрифте перенос не наступает ни на одной ширине. */
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; row-gap: var(--space-xs);
  gap: var(--space-md);
  padding: var(--space-md) var(--gutter);
  border-bottom: var(--hair) solid var(--edge);
  background: var(--graphite);
}
.nav__brand {
  display: inline-flex; align-items: center; gap: var(--space-sm);
  color: var(--text); text-decoration: none;
  min-height: 44px;
}
/* 24px, а не прежние 26. Замер на живом рендере 1440×900:
   cap-height слова «МОСТЕХНОГРАД» (Geologica Bold 16px) = 11.44px;
   у прежней рисованной «М» тушь занимала 23.4px из коробки 26px (90%),
   у фирменного знака после обрезки viewBox тушь равна всей коробке.
   Ставим 24px — тушь та же, что была (23.4 ≈ 24), а коробка на 2px меньше:
   круг при равной высоте читается крупнее буквы, и без этой поправки знак
   начинал доминировать над словом. 24 / 11.44 = 2.10 cap-height.
   Ниже 22px монограмма МТГ внутри круга сливается в кашу — проверено
   снимками 20/22/23/24/26 на графите.
   Вертикаль не правим: замер align-items:center дал центр знака 40.00
   против центра cap-height 40.28 — расхождение 0.28px, меньше пикселя. */
.nav__mark { width: 24px; height: 24px; color: var(--chrome); flex: none; }
.nav__name {
  font-family: var(--font-display);
  font-variation-settings: "SHRP" var(--shrp);
  font-weight: 700; font-size: var(--text-md); letter-spacing: -0.015em;
  text-transform: uppercase;
}
.nav__right { display: flex; align-items: center; gap: var(--space-xs); }

.nav__menu {
  display: inline-flex; align-items: center; gap: var(--space-xs);
  min-height: 44px; padding: 0 var(--space-sm);
  background: none; border: 0; cursor: pointer;
  color: var(--text-2);
  font-family: var(--font-mono); font-size: var(--text-mono-label);
  letter-spacing: .06em; text-transform: uppercase;
  transition: color var(--dur-short) var(--ease-out);
}
.nav__menu-lines { position: relative; width: 16px; height: 9px; display: inline-block; }
.nav__menu-lines::before, .nav__menu-lines::after {
  content: ""; position: absolute; left: 0; right: 0; height: var(--hair);
  background: currentColor;
}
.nav__menu-lines::before { top: 0; }
.nav__menu-lines::after  { bottom: 0; }

/* Панель разделов. Плотная приборная упаковка внутри — воздух снаружи. */
.menu {
  position: absolute; left: 0; right: 0; top: 100%; z-index: var(--z-nav);
  background: var(--surface);
  border-bottom: var(--hair) solid var(--edge);
}
.menu[hidden] { display: none; }
.menu__list {
  list-style: none; margin: 0 auto; padding: 0 var(--gutter);
  max-width: 1720px;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
}
.menu__list li { min-width: 0; border-top: var(--hair) solid var(--edge); border-left: var(--hair) solid var(--edge); }
.menu__list li:nth-child(-n+4) { border-top: 0; }
.menu__list li:nth-child(4n+1) { border-left: 0; }
.menu__list a {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-sm);
  min-height: 56px; padding: var(--space-xs) var(--space-md);
  color: var(--text); text-decoration: none; font-weight: 500;
  transition: color var(--dur-short) var(--ease-out), background-color var(--dur-short) var(--ease-out);
}
.menu__note {
  font-family: var(--font-mono); font-size: var(--text-mono-label);
  color: var(--label); text-transform: uppercase; letter-spacing: .04em;
  white-space: nowrap; flex: none;
}

@media (max-width: 1023px) {
  .menu__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .menu__list li:nth-child(-n+4) { border-top: var(--hair) solid var(--edge); }
  .menu__list li:nth-child(-n+2) { border-top: 0; }
  .menu__list li:nth-child(4n+1) { border-left: var(--hair) solid var(--edge); }
  .menu__list li:nth-child(odd)  { border-left: 0; }
}
@media (max-width: 559px) {
  .menu__list { grid-template-columns: minmax(0, 1fr); }
  .menu__list li { border-left: 0; border-top: var(--hair) solid var(--edge); }
  .menu__list li:first-child { border-top: 0; }
  .menu__list a { padding-inline: 0; }
}

/* ── От 1024px меню разворачивается в ряд ────────────────────────────────
   Замер в SITEMAP.md §1: семь пунктов 559px, шапка целиком 962px, на 1024
   запас 62px, на 768 не хватает 194px. Поэтому порог ровно 1024, ниже —
   бургер. Прятать «Технику» и «Цены» на десктопе нельзя: снабженец приходит
   именно за ними, и он не должен ничего открывать, чтобы их увидеть. */
@media (min-width: 1024px) {
  .nav { gap: clamp(12px, 1.6vw, 40px); }
  .nav__menu { display: none; }

  .menu {
    position: static; z-index: auto;
    background: none; border-bottom: 0;
    min-width: 0; flex: 0 1 auto;
  }
  .menu__list {
    max-width: none; margin: 0; padding: 0;
    display: flex; align-items: center;
    gap: clamp(10px, 1.15vw, 22px);
  }
  .menu__list li { border: 0; }
  .menu__list a {
    position: relative;
    /* 44, а не 40: WCAG 2.5.8 меряет самостоятельную цель, и на 40px пункты
       десктопного меню были единственным местом на сайте ниже нормы. */
    min-height: 44px; padding: 0; gap: 0;
    font-size: var(--text-md); font-weight: 500; white-space: nowrap;
    color: var(--text-2);
  }
  /* Активная кромка вместо подчёркивания браузером: хайрлайн выезжает
     из-под пункта, как отчерченная на чертеже позиция. */
  .menu__list a::after {
    content: ""; position: absolute; left: 0; right: 0; bottom: 6px;
    height: var(--hair); background: var(--chrome);
    transform: scaleX(0); transform-origin: left center;
    transition: transform var(--dur-short) var(--ease-out);
  }
  .menu__note { display: none; }
}
/* 1024–1199: ряд из семи пунктов + телефон помещаются только при более узких
   полях шапки. Проверено на рендере: при прежних 43px поля шапка переносилась
   во вторую строку — метрике переполнения здесь верить нельзя, wrap срабатывает
   молча и «ошибки» не показывает. */
@media (min-width: 1024px) and (max-width: 1199px) {
  /* Замер на 1024: бренд 174 + ряд 553 + действие 233 = 960 при доступных 976,
     и с зазорами шапки контент не влезал — шапка молча уезжала во вторую строку.
     Поля и зазоры ужаты ровно настолько, чтобы остался запас 22px. */
  .nav { padding-inline: var(--space-lg); gap: var(--space-sm); }
  .menu__list { gap: var(--space-xs); }
}
@media (min-width: 1024px) and (hover: hover) and (pointer: fine) {
  .menu__list a:hover { color: var(--text); background: none; }
  .menu__list a:hover::after { transform: scaleX(1); }
}
@media (min-width: 1024px) and (prefers-reduced-motion: reduce) {
  .menu__list a::after { transition: none; }
}

/* ─────────────────────────────── Кнопки ────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--space-xs);
  min-height: 48px; padding: 0 var(--space-lg);
  border: var(--hair) solid transparent; border-radius: var(--radius-btn);
  font-family: var(--font-body); font-size: var(--text-md); font-weight: 600;
  line-height: 1.2; text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: background-color var(--dur-short) var(--ease-out),
              border-color var(--dur-short) var(--ease-out),
              color var(--dur-short) var(--ease-out),
              transform var(--dur-instant) var(--ease-out);
}
/* Отклик на нажатии, а не на отпускании. Кнопка чуть проседает — как настоящая. */
.btn:active { transform: scale(0.98) translateY(1px); }

.btn--call {
  background: var(--amber); color: var(--graphite);
  font-variant-numeric: tabular-nums;
}
.btn__ico { width: 17px; height: 17px; flex: none; }
.btn__label-short { display: none; }

.btn--primary { background: var(--green-brand); color: var(--text); border-color: var(--green-brand); }
.btn__arrow { width: 22px; height: 11px; flex: none; transition: transform var(--dur-short) var(--ease-out); }

.btn--ghost { background: none; color: var(--text); border-color: var(--edge-strong); }

/* Hover — только там, где есть настоящий курсор: на тач-экранах тап
   оставляет за собой «залипший» hover, и кнопка выглядит нажатой. */
@media (hover: hover) and (pointer: fine) {
  .btn--call:hover { background: var(--amber-hi); }
  .btn--primary:hover { background: var(--green-hi); border-color: var(--green-hi); }
  .btn--primary:hover .btn__arrow { transform: translateX(3px); }
  .btn--ghost:hover { border-color: var(--chrome); background: var(--surface); }
  .nav__menu:hover { color: var(--text); }
  .menu__list a:hover { color: var(--green-signal); background: var(--surface-2); }
}

/* ─────────────────────────── Первый экран ──────────────────────────────── */
main { flex: 1; display: flex; flex-direction: column; }

.hero {
  position: relative; isolation: isolate; flex: 1;
  display: grid; grid-template-rows: 1fr auto;
  padding-top: clamp(var(--space-lg), 4vh, var(--space-2xl));
  overflow: clip;
}

/* ── Кадр ────────────────────────────────────────────────────────────────
   На десктопе снимок лежит подложкой всего экрана. В отличие от прежнего
   кадра, у этого левая часть СВЕТЛАЯ (пыль и небо, яркость до 0.99), поэтому
   поле под текст обязательно — см. hero__veil. Второй градиент, у самого низа,
   закрывает шов между фотографией и штампом. */
.hero__frame {
  position: absolute; inset: 0; z-index: var(--z-frame);
  margin: 0; overflow: clip;
  /* по горизонтали кадр прижат к левому краю: тень снимка обязана совпадать
     с колонкой текста, иначе при обрезке она уезжает вместе с кадром */
  display: grid; place-items: center start;
}
/* Ручной cover: пропорция снимка задана aspect-ratio, а min-width/min-height
   растягивают коробку до заполнения. В отличие от object-fit коробка
   остаётся коробкой снимка — поэтому процент внутри неё это точка на машине,
   и выноска указывает туда, куда должна, при любом размере окна. */
.hero__stage {
  position: relative;
  aspect-ratio: 1408 / 768;
  width: auto; height: auto;
  min-width: 100%; min-height: 100%;
}
.hero__shot { display: block; width: 100%; height: 100%; }
/* Видео ложится ровно на снимок и проявляется, когда реально пошло: до этого
   момента виден кадр, который и так первый кадр ролика. */
.hero__video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
  opacity: 0; transition: opacity var(--dur-med) var(--ease-out);
}
.hero__video.is-playing { opacity: 1; }

/* Клин лежит внутри коробки кадра — поэтому и он, и выноски двигаются
   вместе со снимком, а текст выносок остаётся поверх него незатронутым. */
/* Слева на кадре пыль и небо — замеренная яркость до 0.99, это самое светлое
   место снимка, и текстовая колонка стоит ровно там. Поэтому подложка здесь
   не «лёгкий клин», а плотное поле: до 26% ширины сплошной графит, к 38%
   ещё держит 88%, дальше уходит в ноль, и правые две трети кадра остаются
   нетронутыми. Плотность подобрана замером по фактическим пикселям. */
.hero__veil {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    linear-gradient(99deg,
      var(--graphite) 0%,
      var(--graphite) 22%,
      color-mix(in oklch, var(--graphite) 93%, transparent) 37%,
      color-mix(in oklch, var(--graphite) 36%, transparent) 50%,
      transparent 62%),
    linear-gradient(to top, var(--graphite) 0%, transparent 12%);
}
@media (max-width: 1023px) {
  /* врезкой кадр стоит под текстом, закрывать нечего */
  .hero__veil { display: none; }
}

.hero__frame::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, var(--graphite) 0%,
              color-mix(in oklch, var(--graphite) 55%, transparent) 7%, transparent 18%);
}

/* Сетка первого экрана: текст в левой трети — ровно там, где на снимке тень.
   Правые две трети отданы машине, блок туда не заходит: это и есть асимметрия,
   которую требует система, и она же диктуется самим кадром. */
.hero__inner {
  position: relative; z-index: var(--z-content);
  display: grid; align-items: center;
  grid-template-columns: minmax(0, 30rem) minmax(0, 1fr);
  gap: clamp(var(--space-lg), 4vw, var(--space-3xl));
  padding: 0 var(--gutter) var(--space-xl);
  width: 100%; max-width: 1720px; margin-inline: auto;
}
.hero__text { grid-column: 1; }

/* Поисковая строка над заголовком. Видимая, а не спрятанная: скрытый текст
   под ключевые фразы — клоакинг. Набрана моно-лейблом, как остальные
   служебные подписи, поэтому не спорит с H1 и читается как подзаголовок. */
.hero__kicker {
  margin: 0 0 var(--space-sm);
  max-width: 34ch;
  font-family: var(--font-mono); font-size: var(--text-mono-label);
  letter-spacing: .04em; line-height: 1.5;
  color: var(--label);
}

.hero__title {
  margin: 0 0 var(--space-lg);
  font-family: var(--font-display);
  font-variation-settings: "SHRP" var(--shrp);
  font-weight: 800; font-size: var(--text-hero);
  line-height: .9; letter-spacing: -0.035em; text-transform: uppercase;
  overflow-wrap: break-word; min-width: 0;
}
.hero__lead {
  margin: 0 0 var(--space-xl);
  max-width: 34ch;
  color: var(--text-2); font-size: var(--text-lead); line-height: 1.5;
}
.hero__lead-geo { color: var(--chrome); white-space: nowrap; }

.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-sm); }

/* Блок, а не flex: внутри строки теперь есть ссылка на телефон, и во flex
   она становилась отдельной колонкой — текст сжимался в узкий столбец слева. */
.hero__hint {
  margin: var(--space-sm) 0 0;
  display: block;
  font-family: var(--font-mono); font-size: var(--text-mono-label);
  color: var(--label); max-width: 42ch; line-height: 1.6;
}
.hero__hint[hidden] { display: none; }
/* Ссылка внутри строки статуса: отрицательные поля компенсируют padding,
   поэтому строка не раздувается, а область нажатия вырастает с 18 до 30px. */
.hero__hint-tel {
  display: inline-block; padding: 6px 4px; margin: -6px -4px;
  color: var(--amber); text-decoration: underline;
  text-underline-offset: 3px; white-space: nowrap;
}
/* строка статуса не должна возникать скачком: это смена состояния, которую
   человек в этот момент как раз читает.
   fail-open: кадр `from` этой анимации прячет, поэтому правило живёт только
   внутри класса `has-anim`. Класс ставит /js/reveal.js и снимает его сам,
   если за 400 мс браузер не отдал ни одного `animationstart`. Нет скрипта,
   нет рабочих анимаций — строка просто видна, без появления. */
.has-anim .hero__hint:not([hidden]) { animation: ink var(--dur-short) var(--ease-out) both; }
.hero__hint::before { content: "→ "; color: var(--green-signal); }

/* ───────── SIGNATURE · чертёжная разметка поверх живого кадра ────────────
   Один приём на весь сайт: точка на реальном узле машины → выносная линия →
   засечка-полка → моноширинная подпись с числом. Размер, вынесенный за контур
   детали, как на чертеже. Подпись никогда не ложится на саму машину: линия
   уводит её в тёмное поле кадра.
   Точки привязки (--x/--y) — проценты внутри коробки снимка, поэтому они
   держатся при любом размере окна; на мобильном кадрирование другое,
   значит и проценты другие — см. блок адаптива. */
.marks { position: absolute; inset: 0; z-index: var(--z-marks); margin: 0; pointer-events: none; }

.mark {
  position: absolute; left: var(--x); top: var(--y);
  display: flex; flex-direction: column; align-items: flex-start;
  max-width: 16rem;
}
.mark__dot {
  flex: none; width: 7px; height: 7px; margin: -3px 0 0 -3px;
  border: var(--hair) solid var(--chrome); border-radius: 50%;
}
/* Длина выносной линии — в абсолютных единицах, а не в процентах: процент
   считался бы от высоты самой выноски, которая определяется содержимым,
   и линия схлопывалась в ноль. */
.mark__line {
  flex: none; display: block; width: var(--hair); height: var(--len, 60px);
  background: var(--chrome); transform-origin: top center;
}
/* Полка. На чертеже выноска заканчивается полкой, на которой пишут размер —
   поэтому она идёт во всю ширину подписи, а не куском в 20 пикселей. */
.mark__tick { flex: none; align-self: stretch; display: block; height: 2px; background: var(--chrome); }
/* Поле подписи. Это не карточка: ни тени, ни радиуса, ни иконки — гравированная
   табличка на приборной панели. И это единственный способ держать контраст
   на фотографии: подсвеченная пыль в кадре доходит до яркости 1.0, на ней
   не читается никакой текст, а положение светлых пятен меняется вместе
   с кадрированием под ширину окна. Плотность 96% подобрана по замеру. */
.mark__body {
  display: block; padding: var(--space-xs) var(--space-sm) var(--space-sm);
  background: color-mix(in oklch, var(--graphite) 96%, transparent);
}
.mark__key {
  display: block; font-family: var(--font-mono); font-size: var(--text-mono-label);
  letter-spacing: .04em; text-transform: uppercase; color: var(--text-2);
}
.mark__val {
  display: block; margin-top: 3px; white-space: nowrap;
  font-family: var(--font-mono); font-weight: 600; font-size: var(--text-md);
  color: var(--chrome); font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}

/* Ставка — от гидромолота, которым машина в кадре и работает.
   Проценты взяты не по первому кадру, а по середине наезда видео (масштаб
   1.03): за 8 секунд ролик подъезжает на 6%, и точка, выставленная по старту,
   к концу уходила бы с детали на 24px. По середине максимальное отклонение
   вдвое меньше и укладывается в размер самого узла. */
.mark--rate { --x: 58.9%; --y: 44.8%; --len: clamp(38px, 7vh, 84px); }

/* Экипаж — от зеркала кабины вниз, туда, где сидит машинист. Блок прижат
   правым краем к точке, поэтому подпись растёт влево и не вылезает за поле.
   Вторая выноска включается только от 1280px: ниже кадр показывается почти
   целиком, кабина сидит у самого края, а две подписи начинают наезжать
   друг на друга — проверено на рендере 768 и 1024. */
.mark--crew {
  display: none;
  --x: 77.5%; --y: 65.6%; --len: clamp(46px, 8vh, 96px);
  left: auto; right: calc(100% - var(--x));
  align-items: flex-end; text-align: right;
}
@media (min-width: 1280px) { .mark--crew { display: flex; } }
.mark--crew .mark__dot { margin: -3px -3px 0 0; }

/* ─────────── Штамп — основная надпись чертежа, плотная упаковка ───────── */
.stamp {
  position: relative; z-index: var(--z-stamp);
  margin: 0;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  container-type: inline-size;
  border-top: var(--hair) solid var(--edge);
  /* непрозрачно: сквозь прежние 82% просвечивал светлый бетон из кадра
     и ронял лейблы штампа до 3.88:1 при норме 4.5 */
  background: var(--surface);
}
.stamp__cell {
  padding: var(--space-md) var(--space-lg);
  border-left: var(--hair) solid var(--edge);
  min-width: 0;
}
.stamp__cell:first-child { border-left: 0; padding-left: var(--gutter); }
.stamp__cell:last-child  { padding-right: var(--gutter); }
.stamp__key {
  font-family: var(--font-mono); font-size: var(--text-mono-label);
  letter-spacing: .05em; text-transform: uppercase; color: var(--label);
}
.stamp__val {
  margin: var(--space-2xs) 0 0;
  font-family: var(--font-display);
  font-variation-settings: "SHRP" var(--shrp);
  font-weight: 600; font-size: clamp(1rem, 1.1vw + .72rem, 1.375rem);
  color: var(--text); font-variant-numeric: tabular-nums; letter-spacing: -.02em;
  overflow-wrap: anywhere;
}
.stamp__unit, .stamp__note {
  font-family: var(--font-body); font-weight: 400; font-size: var(--text-mono-label);
  color: var(--text-2); letter-spacing: 0;
}
.stamp__note { display: block; color: var(--label); font-size: var(--text-mono-label); }

/* ───────────────────────────── Движение ────────────────────────────────
   Приёмов ровно два. Первый: разметка наносится на кадр по очереди — точка
   ставится, линия вытягивается от неё вниз, следом проявляется подпись.
   Именно в таком порядке чертит человек, поэтому появление читается как
   работа, а не как «всё вылетело fade-in одновременно». Второй: кадр слегка
   отстаёт при прокрутке. Оба на нативном CSS, оба с фолбэком. */
@keyframes leader-v { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes ink      { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes prick    { from { opacity: 0; transform: scale(.9); }      to { opacity: 1; transform: none; } }

/* Проба, по которой /js/reveal.js решает, идут ли анимации в этом браузере.
   Кадр только `to` — сама проба ничего не прячет ни при каком исходе. */
@keyframes mtg-anim-probe { to { opacity: 1; } }

/* fail-open: у кадров prick / ink / leader-v первый кадр прячет, поэтому все
   три правила включаются только классом `has-anim`. Его ставит /js/reveal.js
   до первой отрисовки (иначе выноска мигнула бы) и снимает сам, если за 400 мс
   не пришло ни одного события `animationstart`. Значит и при отсутствии
   скрипта, и при неработающих анимациях выноска ВИДНА — просто без появления.
   Прежняя версия правил стояла голой: анимации не пошли — выноски нет. */
.has-anim .mark__dot  { animation: prick var(--dur-short) var(--ease-out) both; }
.has-anim .mark__line { animation: leader-v var(--dur-med) var(--ease-out) both; }
/* fail-open: то же, что строкой выше — прячет только класс `has-anim`,
   и снимает его тот же скрипт, который поставил. */
.has-anim .mark__tick, .has-anim .mark__body { animation: ink var(--dur-med) var(--ease-out) both; }

.mark--rate .mark__dot  { animation-delay: 200ms; }
.mark--rate .mark__line { animation-delay: 280ms; }
.mark--rate .mark__tick, .mark--rate .mark__body { animation-delay: 420ms; }
.mark--crew .mark__dot  { animation-delay: 340ms; }
.mark--crew .mark__line { animation-delay: 420ms; }
.mark--crew .mark__tick, .mark--crew .mark__body { animation-delay: 560ms; }

/* ✅ ОБРАЗЕЦ, КАК НАДО ПИСАТЬ КАДРЫ. У drift есть только `to`: кадры идут
   от натурального состояния к украшенному. Если таймлайн не поедет —
   а в WebKit 26.5 он именно так и делает, отвечая на @supports «да», —
   элемент просто останется в натуральном виде, потому что застревать
   не в чем. Пара «кадр from со спрятанным состоянием» + `fill-mode: both`
   запрещена на этом сайте: ровно она 11.08.2026 стёрла у заказчика полки
   со ставками (tpl.css) и засечки шкалы (pages.css). Прятать вправе только
   механизм, который в тот же момент умеет показать обратно — сейчас это
   единственный сторож /js/reveal.js. */
@supports (animation-timeline: scroll()) {
  @media (min-width: 1024px) {
    @keyframes drift { to { transform: translate3d(0, 6%, 0) scale(1.04); opacity: .55; } }
    .hero__stage {
      animation: drift linear both;
      /* fail-open: контент виден и без продвижения таймлайна — у drift есть
         только кадр `to`, первый кадр совпадает с натуральным состоянием кадра
         первого экрана. Таймлайн стоит — снимок просто не отъезжает. */
      animation-timeline: scroll(root);
      animation-range: 0 80vh;
    }
  }
}

/* Переход к калькулятору: кадр — общий элемент, морфится, а не мигает */
@supports (view-transition-name: none) {
  .hero__frame { view-transition-name: hero-frame; }
}

@media (prefers-reduced-motion: reduce) {
  .mark__dot, .mark__line, .mark__tick, .mark__body,
  .hero__stage, .hero__hint { animation: none !important; }
  .hero__video { transition: none; }
  .mark__line { transform: none; }
  /* убираем перемещение, но не отклик: цвет и кромка продолжают отвечать
     на нажатие — иначе интерфейс кажется мёртвым, а не спокойным */
  .btn { transition-property: background-color, border-color, color; transition-duration: 120ms; }
  .btn__arrow, .menu__list a::after { transition-duration: 1ms; }
}

/* ─────────────────────────── Адаптив ───────────────────────────────────
   Мобильный — не урезанный десктоп. Ниже 1024 снимок перестаёт быть
   подложкой и встаёт в поток врезкой под кнопками: узкая колонка режет
   широкий кадр до неузнаваемой полосы, а текст поверх пыли теряет контраст.
   Врезкой кадр остаётся кадром, текст остаётся на графите. */
@media (max-width: 1023px) {
  .hero { display: flex; flex-direction: column; }
  .hero__inner { grid-template-columns: minmax(0, 1fr); gap: var(--space-md); align-content: start; }
  .hero__lead  { max-width: 46ch; }

  .hero__frame {
    position: relative; inset: auto; display: block;
    margin-top: auto; width: 100%;
  }
  .hero__stage { min-width: 0; min-height: 0; width: 100%; height: auto; }
  .hero__frame::after {
    background: linear-gradient(to top, var(--graphite) 0%,
                color-mix(in oklch, var(--graphite) 45%, transparent) 9%, transparent 22%);
  }
  /* Полоса врезки выше самого кадра не нужна — снимок кадрируется по высоте
     контейнера. Пропорция совпадает с файлом, поэтому выноски точны. */
  .mark--rate { --len: 54px; }
}

/* 768–1023 — кадр десктопный (источник переключается на 767px),
   пропорция коробки обязана совпадать с файлом 1408×768. */
@media (min-width: 768px) and (max-width: 1023px) {
  .hero__stage { aspect-ratio: 1408 / 768; }
}

@media (max-width: 767px) {
  /* Мобильный файл — свой кроп 800×688 вокруг гидромолота, поэтому
     и пропорция коробки, и точки привязки выносок другие. */
  .hero__stage { aspect-ratio: 888 / 638; }
  .mark--rate { --x: 34.3%; --y: 33.7%; --len: 40px; }

  /* Отступы поджаты: кадр вместе с ценой обязан попасть в первый экран
     телефона — иначе главное число уезжает за сгиб. */
  .hero { padding-top: var(--space-md); }
  .hero__inner { padding-bottom: var(--space-sm); gap: var(--space-xs); align-content: start; }
  .hero__title { margin-bottom: var(--space-md); }
  .hero__lead { margin-bottom: var(--space-md); }
  .hero__title { line-height: .92; letter-spacing: -.03em; }
  .hero__lead { margin-bottom: var(--space-lg); }
  .hero__lead-geo { white-space: normal; }
  .hero__actions { flex-direction: column; align-items: stretch; }
  .hero__actions .btn { width: 100%; }

  /* штамп складывается в столбик: три строки читаются сверху вниз,
     как строки основной надписи чертежа */
  .stamp { grid-template-columns: minmax(0, 1fr); }
  .stamp__cell {
    padding: var(--space-sm) var(--gutter);
    border-left: 0; border-top: var(--hair) solid var(--edge);
    display: block;
  }
  .stamp__cell:first-child { border-top: 0; }
  .stamp__val { text-align: left; margin-top: var(--space-3xs); }
  .stamp__note { display: block; }

  .nav { padding: var(--space-sm) var(--gutter); gap: var(--space-xs); }
  /* Знак и название — брендовый блок, а не читаемый контент: его рост
     ограничен долей ширины экрана, иначе при увеличенном базовом шрифте
     он выдавливает из шапки телефон. Обычный шрифт под потолок не упирается. */
  .nav__name { font-size: min(var(--text-md), 5.2vw); }
  .btn { min-height: 48px; padding: 0 var(--space-md); }
  .btn--call .btn__label { display: none; }
  .btn--call .btn__label-short { display: inline; }
}

/* Шапка ужимается ступенями: сначала уходит слово «Меню», потом слово в кнопке
   звонка. Жёлтый квадрат с трубкой остаётся заметным и держит тач-таргет 48px. */
@media (max-width: 559px) {
  .nav__menu { padding: 0 var(--space-xs); gap: 0; min-width: 44px; justify-content: center; }
  .nav__menu-word { display: none; }
  .nav__menu-lines { width: 18px; height: 11px; }
}
@media (max-width: 449px) {
  .btn--call { padding: 0; width: 48px; }
  .btn--call .btn__label-short { display: none; }
  .btn--call .btn__ico { width: 19px; height: 19px; }
}
@media (max-width: 359px) {
  .hero__title { font-size: clamp(2rem, 10vw, 2.5rem); }
  /* Знак остаётся, надпись уходит: при увеличенном базовом шрифте
     «МОСТЕХНОГРАД» наезжает на бургер и кнопку звонка. */
  .nav__name { display: none; }
  .nav__brand { min-width: 44px; }
  /* Здесь знак остаётся один за весь логотип, и слова, к которому он был
     подогнан по оптике, рядом больше нет. 28px — та же доля от 320px,
     какую 24px занимают от 375 (7.5%), и монограмма МТГ внутри кольца
     остаётся читаемой (ниже 22px она сливается). */
  .nav__mark { width: 28px; height: 28px; }
}



/* От 1500px равные графы штампа начинают пустовать: под подпись в 180px
   отводилось 640px. Графы садятся по содержимому и расходятся по ширине —
   так и устроена основная надпись на чертеже. */
@media (min-width: 1500px) {
  .stamp { grid-template-columns: repeat(3, minmax(0, max-content)); justify-content: space-between; }
  .stamp__cell { padding-inline: clamp(24px, 3vw, 64px); }
  .stamp__cell:first-child { padding-left: var(--gutter); }
  .stamp__cell:last-child  { padding-right: var(--gutter); }
}

/* Шапка: тонкая кромка отделяет разделы от действия — приборная деталь,
   которая уводит ряд от канонической SaaS-шапки. */
@media (min-width: 1024px) {
  .nav__right { position: relative; padding-left: clamp(16px, 2vw, 36px); }
  .nav__right::before {
    content: ""; position: absolute; left: 0; top: 50%; translate: 0 -50%;
    width: var(--hair); height: 26px; background: var(--edge-strong);
  }
}

/* ───────── Липкая шапка на мобильном ────────────────────────────────────
   Страница высотой 9400px, а единственный способ связаться — телефон
   в шапке: без прилипания он уезжает на первом же экране и возвращается
   только прокруткой вверх. Прилипает только ниже 1024: на десктопе
   стеклянно-липкая шапка — стоп-паттерн, да и расстояние до верха там
   один жест колеса.
   Фон непрозрачный (графит), поэтому контраст под шапкой не падает —
   ни на тёмных секциях, ни на светлом острове контактов.
   Боковые поля через max(): в ландшафте вырез съедает край экрана. */
@media (max-width: 1023px) {
  .nav {
    position: sticky; top: 0;
    padding-inline:
      max(var(--gutter), env(safe-area-inset-left))
      max(var(--gutter), env(safe-area-inset-right));
  }
  /* Якорь не должен уезжать под прилипшую шапку: заголовок раздела встаёт
     ПОД ней, а не за ней. 76px — фактическая высота шапки на 375
     (замер getBoundingClientRect), плюс зазор. */
  html { scroll-padding-top: 84px; }
}

/* ═════════════════ Шапка без JavaScript ═══════════════════════════════════
   Замер до правки: при отключённом JS на 1440 панель разделов оставалась
   с атрибутом hidden (скрипт снимает его сам), а бургер на этой ширине
   display:none — в шапке не было НИ ОДНОГО пункта навигации, 0 из 7.
   На 375 бургер виден, но без обработчика не открывается — тот же ноль.

   Без скрипта панель просто стоит в потоке под шапкой и видна целиком,
   а бургер убирается: кнопка, которая не может ничего открыть, хуже
   отсутствия кнопки. Гейта два — медиа-фича scripting поддержана не везде,
   класс no-js снимается первой строкой скрипта в head и работает всюду. */
@media (scripting: none) {
  .nav__menu { display: none; }
  .menu[hidden] { display: block; }
}
@media (scripting: none) and (max-width: 1023px) {
  .menu { position: static; flex: 1 0 100%; border-bottom: 0; }
}
.no-js .nav__menu { display: none; }
.no-js .menu[hidden] { display: block; }
@media (max-width: 1023px) {
  .no-js .menu { position: static; flex: 1 0 100%; border-bottom: 0; }
}
