/* Awaria zamka — базовые стили. Только переменные из tokens.css.
   Направление v3 «Ночная диспетчерская» (утверждено клиентом 2026-07-21).
   Эталон внешнего вида — прототипы/v3.html.

   МИГРАЦИЯ: тёмные стили привязаны к обёрткам .map-surface / .trust-card /
   .cta-card. Страница получает новый вид, когда в её разметку добавлены эти
   обёртки; до тех пор работает по «светлым» правилам и не ломается. */

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
img, svg { display: block; max-width: 100%; }

/* ---------- База ---------- */
body {
  min-width: 320px;
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  color: var(--color-ink);
  background: var(--color-bg);
  padding-bottom: var(--sticky-h); /* место под нижнюю плашку на мобильном */
  /* Длинные слова (в русском и польском их хватает: «конфиденциальности»,
     «odpowiedzialności») на узких экранах вылезали за край. Переносим только
     когда слово реально не помещается — обычный текст не трогается. */
  overflow-wrap: break-word;
}

h1, h2, h3 {
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--color-primary);
}
/* text-wrap: balance выравнивает строки заголовка по длине — без него
   на телефоне хвост из одного-двух слов был почти у каждого второго
   («24/7», «zadzwonić», «именно нам», «до 20 км»). pretty делает то же
   мягче и для прозы: не даёт последней строке остаться одним словом.
   Свойства не поддерживаются старыми браузерами — там просто перенос
   как раньше, ничего не ломается. */
h1, h2, h3 { text-wrap: balance; }
p, li, dd, figcaption { text-wrap: pretty; }
h1 { font-size: var(--text-h1); }
h2 { font-size: var(--text-h2); }
h3 { font-size: var(--text-lg); letter-spacing: normal; }

a { color: inherit; }
button, input, textarea { font: inherit; }

/* Фокус: тёмная обводка — контраст ≥3:1 на белом/светлом/янтарном (SC 1.4.11).
   На тёмных поверхностях переопределяем на белую. */
:focus-visible {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}
.site-header :focus-visible,
.site-footer :focus-visible,
.map-surface :focus-visible,
.services__block :focus-visible,
.sticky-bar :focus-visible {
  outline-color: var(--color-bg);
}

.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-4);
}

/* ---------- Тёмная «диспетчерская» поверхность ----------
   Только hero, «Действуем официально» и финальный CTA (дизайн-система, п.3). */
.map-surface {
  position: relative;
  overflow: hidden;
  background: var(--surface-dark);
  color: var(--color-on-primary);
}
.map-surface::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: var(--surface-grid);
  background-size: var(--surface-grid-size);
  -webkit-mask-image: radial-gradient(circle at 50% 46%, #000 0 34%, transparent 78%);
  mask-image: radial-gradient(circle at 50% 46%, #000 0 34%, transparent 78%);
}
.map-surface > * { position: relative; z-index: 1; }

/* ---------- Изображения ----------
   Фон-подложка внутри тёмной карточки: картинка уходит ПОД текст и приглушается,
   поэтому контраст надписей не страдает. Отдельным <img>, а не background-image —
   только так работает ленивая загрузка. */
.map-surface > .media-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Светлое фото (небо, дневной город) выбивает контраст белого текста.
     Сначала гасим саму картинку фильтром, и только потом смешиваем — так
     она остаётся фактурой, а не спорит с надписями.
     Расчёт композита: самый светлый участок ≈rgb(250,250,255) → после фильтра
     ≈rgb(85,85,87) → при opacity .55 поверх тёмной подложки ≈rgb(57,57,59),
     контраст с белым ≈11:1 при пороге AA 4.5:1. Перепроверить замером пикселей
     при финальном ревью (шаг 16). */
  filter: brightness(.34) saturate(.5);
  opacity: .55;
  pointer-events: none;
}
/* под формой — ещё слабее: поля должны читаться без усилий */
.map-surface > .media-bg--soft { opacity: .30; }

.map-surface h1,
.map-surface h2,
.map-surface h3 { color: var(--color-on-primary); }

/* ---------- Шапка ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--color-primary);
  color: var(--color-on-primary);
}
.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding-block: var(--space-3);
}
.site-header a { color: var(--color-on-primary); }
.site-header__logo {
  margin-right: auto;
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  font-size: var(--text-lg);
  letter-spacing: var(--tracking-tight);
  text-decoration: none;
  white-space: nowrap;
}
.site-header__phone {
  display: none; /* на мобильном звонок живёт в нижней плашке */
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  text-decoration: none;
  white-space: nowrap;
}
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-sm);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pill);
  white-space: nowrap;
}
.badge--header {
  display: none; /* прячем на мобильном — место нужно мессенджерам */
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
}
.lang-switch { font-size: var(--text-sm); white-space: nowrap; }
.lang-switch__current { font-weight: var(--weight-semibold); }
.lang-switch__link {
  display: inline-block;
  padding: var(--space-2) var(--space-1);
  margin: calc(-1 * var(--space-2)) 0;
}

/* Тап-зоны мелких ссылок.
   Шапка плотная, а клиент утвердила её внешний вид — поэтому растим не сами
   элементы, а невидимую область нажатия поверх них. Логотип, значки
   мессенджеров и переключатель языка на телефоне были 28–38px в высоту:
   пальцем в них надо целиться. Прозрачный прямоугольник 48×48 по центру
   решает это, ничего не сдвигая. */
.site-header__logo,
.lang-switch__link,
.icon-msg,
.process__step h3 a { position: relative; }
.site-header__logo::after,
.lang-switch__link::after,
.icon-msg::after,
.process__step h3 a::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  translate: -50% -50%;
  min-width: var(--tap-min);
  min-height: var(--tap-min);
  width: 100%;
  height: 100%;
}
/* Переключатель языка стоит вплотную к значкам мессенджеров, и его зона,
   расширенная вбок, накрывала соседний значок: нажатие на Телеграм меняло
   язык. Отодвигаем переключатель настолько, чтобы зоны разошлись:
   ширина ссылки 24px, зона 48px, значит наружу торчит 12px — столько и
   добавляем к штатному зазору шапки. */
.lang-switch { margin-left: var(--space-3); }

/* Мессенджеры в шапке — только значки.
   Зазор 12px, а не 4px: значок 36px плюс зазор дают шаг ровно 48px, поэтому
   области нажатия двух соседей смыкаются встык и не наезжают друг на друга.
   При зазоре 4px они перекрывались, и половина зоны WhatsApp отвечала
   Телеграмом — палец попадал не туда, куда целился. */
.header-msgs { display: inline-flex; gap: var(--space-3); }
.icon-msg {
  width: 36px;
  height: 36px;
  display: inline-grid;
  place-items: center;
  border-radius: var(--radius-sm);
  background: var(--color-veil);
  color: var(--color-on-primary);
  text-decoration: none;
}
.icon-msg svg { width: 20px; height: 20px; }
.icon-msg--wa svg { color: var(--color-wa); }
.icon-msg--tg svg { color: var(--color-tg); }

/* ---------- Кнопка звонка (главный CTA) ---------- */
.btn-call {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-6);
  background: var(--color-accent);
  color: var(--color-on-accent);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  font-size: var(--text-lg);
  line-height: 1;
  text-decoration: none;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-accent);
}
.btn-call:active { transform: translateY(1px); }
.btn-call svg { width: 22px; height: 22px; flex: 0 0 auto; }

/* ---------- Мессенджеры — вторичные кнопки ----------
   Никогда не янтарные: звонок остаётся главным действием. */
.msgs { display: flex; flex-wrap: wrap; gap: var(--space-2); }
/* На телефоне кнопка звонка идёт во всю ширину, а мессенджеры под ней
   занимали столько, сколько занимал текст, — пара обрывалась справа и
   выглядела съехавшей. Делим ту же ширину пополам (просьба владельца
   2026-07-26). */
@media (max-width: 767px) {
  /* Кнопка звонка идёт во всю ширину, поэтому боковые поля в 24px ничего не
     держат — зато из-за них длинная русская подпись «Позвонить: 513 513 575»
     не влезала в строку на экранах 320–360px и кнопка становилась двухэтажной,
     на 12px выше мессенджеров рядом. */
  .btn-call { padding-inline: var(--space-3); }
  /* minmax(0, 1fr), а не 1fr: у 1fr минимум равен ширине содержимого, поэтому
     на узком экране колонка WhatsApp выходила шире колонки Telegram — 141 и
     133px вместо равных. */
  .msgs { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .btn-msg {
    justify-content: center;
    padding-inline: var(--space-2);
    gap: var(--space-2);
    /* Высота ровно как у кнопки звонка: у той line-height 1 и min-height 48,
       у мессенджеров была унаследованная строка 24.8px и они выходили на 51px —
       рядом читались как более грузные. */
    line-height: 1;
    /* min(), а НЕ clamp с нижней границей в rem. У владельца в телефоне
       увеличен системный размер шрифта: rem вырос, нижняя граница clamp
       победила ограничение по ширине экрана, подпись перестала помещаться —
       и ломалась посреди слова: «WhatsAp / p». min() держит кегль привязанным
       к ширине экрана, системный шрифт его раздуть не может. */
    font-size: min(var(--text-lg), 4.2vw);
    /* У body включён перенос длинных слов (он нужен «конфиденциальности»
       и «odpowiedzialności» в тексте). Внутри кнопки он и разрывал слово. */
    white-space: nowrap;
  }
  .btn-msg svg { width: 20px; height: 20px; flex: 0 0 auto; }
}
.btn-msg {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--color-edge);
  border-radius: var(--radius-md);
  background: var(--color-veil);
  color: var(--color-on-primary);
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
  text-decoration: none;
}
.btn-msg svg { width: 22px; height: 22px; flex: 0 0 auto; }
.btn-msg--wa svg { color: var(--color-wa); }
.btn-msg--tg svg { color: var(--color-tg); }

/* ---------- 1 · Hero ---------- */
.hero { padding-block: var(--space-4) 0; }
.hero-card {
  /* Поле карточки переменной: от него же считается вынос фото за край
     на телефоне, и на невысоком экране оно меняется — числом в двух местах
     это разъехалось бы. */
  --hero-pad: var(--space-6);
  /* svh, а не dvh. dvh меняется на телефоне при каждой прокрутке — браузер
     прячет адресную строку, экран «растёт», высота карточки пересчитывается,
     фото под текстом перекадрируется и едет относительно него. svh — высота
     при РАЗВЁРНУТЫХ панелях, она постоянна. vh перед ней — запасной вариант
     для браузеров без поддержки svh. */
  min-height: min(720px, calc(100vh - 96px));
  min-height: min(720px, calc(100svh - 96px));
  border-radius: var(--radius-lg);
  padding: var(--hero-pad);
  display: flex;
  align-items: stretch;
}
.hero-card__content {
  width: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-4);
}
.hero h1 { max-width: 15ch; }
.hero__sub {
  max-width: 42em;
  font-size: var(--text-lg);
  color: var(--color-ink-soft);
}
.map-surface .hero__sub { color: var(--color-on-dark-soft); }
.hero__cta { width: 100%; }
.hero__badges {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.badge--soft {
  background: var(--color-bg-soft);
  color: var(--color-primary);
  font-weight: var(--weight-semibold);
  padding: var(--space-2) var(--space-3);
}
.badge--status {
  min-height: 32px;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-edge);
  background: var(--color-veil);
  color: var(--color-on-primary);
  font-weight: var(--weight-semibold);
}

/* ---------- Бегущая строка ---------- */
.marquee {
  width: 100%;
  overflow: hidden;
  margin-top: var(--space-4);
  background: var(--color-primary);
  color: var(--color-on-primary);
}
/* Шесть копий строки, сдвиг ровно на одну (100%/6) — лента идёт бесконечно
   и не обрывается даже на широком мониторе. С двумя копиями трек был короче
   экрана, и после текста ехала пустота. */
.marquee__track {
  display: flex;
  width: max-content;
  animation: marquee 38s linear infinite;
}
.marquee__line {
  display: inline-flex;
  align-items: center;
  padding-block: var(--space-4);
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
  line-height: 1;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
}
.marquee__dot { color: var(--color-accent); padding-inline: var(--space-3); }
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-16.6667%); } /* одна копия из шести */
}

/* ---------- Секции (общее) ---------- */
.section { padding-block: var(--space-12); }
.section--soft { background: var(--color-bg-soft); }
.section h2 { margin-bottom: var(--space-6); }

/* ---------- 2 · Ситуации (4 карточки с рендерами механизма) ----------
   Секция на «дыме»: у рендеров вырезан белый фон, объект лежит прямо на
   подложке — ни рамок, ни плашек. Плоские иконки убраны (решение владельца
   2026-07-23: пиктограммы замков как главный визуал больше не используем). */
.situations {
  background: var(--color-sand);
  color: var(--color-ink);
}
.situations__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6); /* на телефоне 32px между карточками читались как разрыв */
}
.card {
  display: grid;
  align-content: start;
  gap: var(--space-2);
  text-decoration: none;
}
.card__media { margin-bottom: var(--space-2); }
.card__media img {
  display: block;
  width: 100%;
  height: auto;
  transition: transform var(--motion-in) var(--ease-out);
}
/* На телефоне кадр 4:3 занимал 417px, и четыре карточки давали больше двух
   экранов прокрутки. Подрезаем сверху и снизу — объект в центре, не страдает. */
@media (max-width: 767px) {
  .card__media img {
    aspect-ratio: 16 / 10;
    object-fit: cover;
    object-position: 50% 48%;
  }
  .situations .container,
  .why .container { padding-inline: var(--space-4); }
  /* 24, а не 32: между двумя соседними секциями складываются оба поля, и
     разрыв в 64px на телефоне читался как «страница кончилась». Теперь 48. */
  .section { padding-block: var(--space-6); }
  .section h2 { margin-bottom: var(--space-4); }
  .why__grid { gap: var(--space-6); }
  .services__cols .services__block { padding-block: var(--space-6); }
}
.card:hover .card__media img,
.card:focus-visible .card__media img { transform: scale(1.03); }
.card h3 { font-size: var(--text-lg); color: var(--color-ink); }
.card p { font-size: var(--text-base); color: var(--color-ink-on-sand); }
/* Кнопка звонка в карточке (просьба клиента 2026-07-25: текстовая ссылка со
   стрелкой не читалась как действие). Контурная, не янтарная: главный CTA на
   странице один, четыре янтарные кнопки подряд размыли бы акцент.
   На телефоне — во всю ширину карточки, чтобы попадать пальцем не целясь. */
.card__link {
  min-height: var(--tap-min);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  margin-top: var(--space-2);
  padding: var(--space-3) var(--space-6);
  border: 1.5px solid var(--color-ink);
  border-radius: var(--radius-md);
  color: var(--color-ink);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  font-size: var(--text-base);
  text-decoration: none;
  transition: background var(--motion-hover) var(--ease-out),
              color var(--motion-hover) var(--ease-out);
}
.card__link:hover,
.card__link:focus-visible {
  background: var(--color-ink);
  color: var(--color-sand);
}
.card__link-ico { width: 18px; height: 18px; flex: 0 0 auto; }
@media (prefers-reduced-motion: reduce) {
  .card__media img { transition: none; }
  .card:hover .card__media img { transform: none; }
}
.card__link:hover, .card__link:focus-visible { text-decoration: underline; }

/* ---------- 3 · Услуги ----------
   Услуги идут строками через всю ширину, а не двумя коробками рядом:
   крупное название — что делаем — звонок. Секция тёмная целиком, чтобы
   после светлых ситуаций страница снова меняла дыхание. */
.services {
  background: var(--color-primary);
  color: var(--color-on-primary);
}
.services h2 { color: var(--color-on-primary); }
.services__cols { display: grid; gap: 0; }
.services__block {
  display: grid;
  gap: var(--space-4);
  align-items: start;
  padding-block: var(--space-8);
  border-top: 1px solid var(--color-on-dark-line);
}
.services__block:last-child { border-bottom: 1px solid var(--color-on-dark-line); }
.services__block h3 {
  color: var(--color-on-primary);
  font-size: var(--text-2xl);
  line-height: 1.15;
  letter-spacing: var(--tracking-tight);
  margin: 0;
}
.services__call {
  min-height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  width: fit-content;
  color: var(--color-accent);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  font-size: var(--text-base);
  text-decoration: none;
  white-space: nowrap;
}
.services__call:hover { text-decoration: underline; }
.services__block ul {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-3);
}
.services__block li {
  position: relative;
  padding-left: var(--space-6);
  color: var(--color-on-dark-soft);
}
.services__block li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .68em;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--color-accent);
}
.services__note {
  margin-top: var(--space-4);
  padding-top: var(--space-3);
  border-top: 1px solid var(--color-on-dark-line);
  font-size: var(--text-sm);
  color: var(--color-on-dark-soft);
}

/* ---------- 4 · Почему мы ----------
   Порядковые номера 01–04 заменены на сам факт: человеку в стрессе цифра
   «15–20» говорит больше, чем «02». Секция на песочном — держит связку
   с ситуациями и разводит две тёмные секции вокруг. */
.why { background: var(--color-sand); }
.why__grid { display: grid; gap: var(--space-8); }
.why__item {
  border-top: 2px solid var(--color-ink);
  padding-top: var(--space-6);
}
.why__num {
  color: var(--color-ink);
  font-family: var(--font-heading);
  font-size: clamp(2.2rem, 5vw, 3.4rem);
  font-weight: var(--weight-bold);
  line-height: 1;
  letter-spacing: var(--tracking-tight);
  margin-bottom: var(--space-4);
  white-space: nowrap; /* «15–20» не должно переноситься посреди числа */
}
/* Единица измерения стоит РЯДОМ с числом, а не под ним (просьба владельца
   2026-07-26): отдельной строкой она растягивала каждый пункт на лишние
   пол-экрана, и четыре пункта не помещались в поле зрения. */
.why__num span {
  margin-left: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  letter-spacing: 0;
  color: var(--color-accent-ink);
  white-space: normal;
}
.why__item h3 { font-size: var(--text-lg); letter-spacing: var(--tracking-tight); }
.why__item p {
  margin-top: var(--space-2);
  font-size: var(--text-base);
  color: var(--color-ink-on-sand);
}

/* ---------- 5 · Процесс ----------
   Мелкие кружки на ниточке заменены полосой-прогрессом: точка на линии,
   подпись шага, крупный заголовок. Первая точка янтарная — с неё
   начинается всё, и она же телефон. Мобильная раскладка — вертикальная. */
.process__list {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-6);
}
.process__step {
  position: relative;
  padding-left: var(--space-8);
}
.process__step::after {
  content: "";
  position: absolute;
  top: 6px;
  left: 0;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-pill);
  background: var(--color-ink);
}
.process__step:first-child::after { background: var(--color-accent); }
.process__num {
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  color: var(--color-accent-ink);
  letter-spacing: .08em;
  margin-bottom: var(--space-2);
}
.process__step h3 { font-size: var(--text-xl); letter-spacing: var(--tracking-tight); }
.process__step h3 a {
  text-decoration: none;
  display: inline-block;
  padding-block: var(--space-1); /* тап-зона ≥24px, шкала отступов */
}
.process__step p {
  margin-top: var(--space-2);
  font-size: var(--text-base);
  color: var(--color-ink-soft);
}

/* ---------- 6 · Доверие ----------
   Панорама Варшавы убрана: стоковый вид города к услуге отношения не имел.
   Вместо неё — схема зоны выезда (реальная информация бизнеса, промт 8.9
   ставит её выше генерации). Реквизиты: подпись сверху, значение крупно. */
.trust { background: var(--color-primary); color: var(--color-on-primary); }
.trust h2 { color: var(--color-on-primary); }
.trust__grid { display: grid; gap: var(--space-12); align-items: center; }
.trust__list {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-6);
  margin: 0;
}
.trust__list li {
  border-top: 1px solid var(--color-on-dark-line);
  padding-top: var(--space-4);
}
.trust__label {
  display: block;
  font-size: var(--text-sm);
  color: var(--color-on-dark-soft);
  margin-bottom: var(--space-2);
}
.trust__value {
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight);
}

/* Карта зоны: настоящие границы Варшавы (данные OSM, упрощены до 74 точек)
   и контур зоны +20 км вокруг них. Векторы в коде — ни одного внешнего
   запроса, значит ни веса тайлов, ни передачи IP посторонним до согласия. */
.zone { width: 100%; max-width: 380px; height: auto; display: block; margin-inline: auto; }
/* Город: внешний контур заметный, районы — тонкой сеткой внутри. Кривой
   контур зоны убран: он читался как случайная клякса. Зону показывает
   свечение, уходящее за край кадра, — «и дальше во все стороны». */
.zone__city {
  fill: none;
  stroke: var(--color-on-primary);
  stroke-width: 1.6;
  stroke-linejoin: round;
}
.zone__dz {
  fill: rgba(255,255,255,.05);
  stroke: rgba(255,255,255,.28);
  stroke-width: .7;
  stroke-linejoin: round;
}
.zone__glow { fill: url(#zoneGlow); }
.zone__credit {
  margin: var(--space-3) 0 0;
  text-align: center;
  font-size: var(--text-xs);
  color: var(--color-on-dark-soft);
}
.zone__credit a { color: inherit; }
.zone__dot { fill: var(--color-accent); }
.zone__dash { stroke: var(--color-accent); stroke-width: 1.5; stroke-dasharray: 4 4; }
.zone__txt {
  fill: var(--color-on-primary);
  font-family: var(--font-heading);
  font-size: 13px;
  font-weight: var(--weight-semibold);
}
.zone__txt--soft { fill: var(--color-on-dark-soft); font-weight: var(--weight-medium); }

/* ---------- 7 · FAQ ---------- */
/* Интервал держит только gap контейнера. Раньше к нему добавлялся
   margin-bottom карточки — фактически выходило 16px вместо 8, и снизу
   последней карточки болтался лишний отступ. */
.faq__list { display: grid; gap: var(--space-4); }
.faq__item {
  border: 1px solid var(--color-line);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  overflow: hidden;
}
.faq__item summary {
  cursor: pointer;
  list-style: none;
  min-height: var(--tap-min);
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: var(--space-2);
  align-items: center;
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  color: var(--color-primary);
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary::before {
  content: "+";
  color: var(--color-accent-ink);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  font-size: var(--text-xl);
  line-height: 1;
  transition: transform var(--motion-hover) var(--ease-out);
}
/* «+» доворачивается в «×» — мягче, чем подмена символа */
.faq__item[open] summary::before { transform: rotate(45deg); }
.faq__item p {
  padding: 0 var(--space-4) var(--space-4) calc(24px + var(--space-6));
  font-size: var(--text-sm);
  color: var(--color-ink-soft);
}

/* ---------- 8 · Финальный CTA + форма ---------- */
.cta-card {
  border-radius: var(--radius-lg);
  padding: var(--space-6);
}
/* text-wrap: balance выравнивает строки по длине. Без него заголовок
   рассыпался лесенкой: «Zamknięte drzwi» / «albo auto?» / «Zadzwoń —» /
   «pomożemy.» — последние строки в два слова. */
.cta-card h2 { max-width: 13ch; text-wrap: balance; }
.cta__call { margin-top: var(--space-6); width: 100%; }
.cta__msgs { margin-top: var(--space-3); }
.cta__divider {
  margin-block: var(--space-6) var(--space-4);
  color: var(--color-ink-soft);
  font-weight: var(--weight-semibold);
}
.cta-card .cta__divider { color: var(--color-on-dark-soft); }

@media (max-width: 767px) {
  /* Ограничение в 13 знаков рассчитано на широкий экран, где заголовок стоит
     колонкой рядом с формой. На телефоне оно душило строку до двух слов.
     Кегль ниже общего H2: заголовок здесь длинный (45 знаков), и в общем
     размере он занимал три-четыре строки, раздувая блок. В этом — две. */
  .cta-card h2 {
    max-width: none;
    font-size: clamp(1.1rem, 5.85vw, 2rem);
  }
  /* Кнопка звонка и мессенджеры здесь дублируют нижнюю плашку, которая на
     телефоне висит на экране всегда (просьба владельца 2026-07-26). Блок
     остаётся тем, чем и должен быть: «не можешь говорить — оставь номер».
     На планшете и десктопе плашки нет — там и кнопка, и мессенджеры на месте. */
  .cta__call,
  .cta__msgs { display: none; }
  .cta__divider { margin-block: var(--space-4); }
  /* Форма ужимается: без кнопки звонка блок и так стал короче, а длинная
     форма на телефоне читается как анкета, а не как «оставь номер». */
  .lead-form { gap: var(--space-3); }
  .lead-form__field textarea { min-height: 72px; }
  .cta-card { padding: var(--space-4); }
}

/* max-width 480px держит строку читаемой на широком экране, но на телефоне
   он обрезал форму на 31px справа: кнопка звонка шла во всю ширину карточки,
   а поля не доходили до её края — выглядело как съехавшая вёрстка. */
.lead-form { display: grid; gap: var(--space-4); }
@media (min-width: 768px) {
  .lead-form { max-width: 480px; }
}
.lead-form__field { display: grid; gap: var(--space-1); }
.lead-form__field label,
.lead-form__label {
  font-weight: var(--weight-semibold);
  font-size: var(--text-sm);
}
.lead-form__field input,
.lead-form__field textarea {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3);
  color: var(--color-ink);
  background: var(--color-bg);
  border: 1px solid var(--color-line);
  border-radius: var(--radius-sm);
  /* ⚠️ Здесь НЕ ставить `outline: none`. Так было в прототипе, и при переносе
     это молча убило видимый фокус на полях формы (SC 2.4.7) — поймано
     автопроверкой на шаге 16. Обводку даёт общее правило :focus-visible,
     а внутри тёмной карточки она перекрашивается в белую. */
}
.lead-form__field textarea { min-height: 98px; resize: vertical; }
.lead-form__field input[aria-invalid="true"] { border-color: var(--color-error); }
.lead-form__error {
  font-size: var(--text-sm);
  color: var(--color-error);
}
.lead-form__submit {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-6);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  font-size: var(--text-base);
  color: var(--color-on-accent);
  background: var(--color-accent);
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
}
.lead-form__submit:disabled {
  opacity: .65;
  cursor: wait;
}
.lead-form__status {
  font-size: var(--text-sm);
  line-height: 1.5;
}
.lead-form__status--error { color: var(--color-error); }
.lead-form__status a {
  color: inherit;
  font-weight: var(--weight-bold);
  text-decoration-thickness: 2px;
}
.lead-form__trap {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.lead-form__rodo {
  font-size: var(--text-xs);
  color: var(--color-ink-soft);
  line-height: 1.5;
}

/* Вложение фото/PDF */
.lead-form__file {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: var(--space-3) var(--space-4);
  border: 1px dashed var(--color-line);
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: var(--font-heading);
  font-weight: var(--weight-semibold);
}
.lead-form__file:hover { border-color: var(--color-accent); }
.lead-form__file:focus-within {
  outline: 3px solid var(--color-primary);
  outline-offset: 2px;
}
.lead-form__file svg { width: 20px; height: 20px; color: var(--color-accent-ink); flex: 0 0 auto; }
.lead-form__filetext { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* input скрыт визуально, но остаётся в порядке табуляции */
.lead-form__file input[type="file"] {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); border: 0;
}
.lead-form__hint { font-size: var(--text-xs); color: var(--color-ink-soft); }

/* Форма внутри тёмной карточки */
.cta-card .lead-form__field label,
.cta-card .lead-form__label,
.cta-card .lead-form__file { color: var(--color-on-primary); }
.cta-card .lead-form__field input,
.cta-card .lead-form__field textarea {
  color: var(--color-on-primary);
  background: rgba(11,12,14,.38);
  border-color: var(--color-edge);
}
.cta-card .lead-form__field input::placeholder { color: var(--color-ink-soft); }
.cta-card .lead-form__field input:focus,
.cta-card .lead-form__field textarea:focus { border-color: var(--color-accent); }
.cta-card .lead-form__file { border-color: var(--color-edge); }
.cta-card .lead-form__file svg { color: var(--color-accent); }
.cta-card .lead-form__file:focus-within { outline-color: var(--color-bg); }
.cta-card .lead-form__error,
.cta-card .lead-form__status,
.cta-card .lead-form__hint,
.cta-card .lead-form__rodo { color: var(--color-on-dark-soft); }
.cta-card .lead-form__status--error,
.cta-card .lead-form__status a { color: var(--color-on-primary); }
.cta-card .lead-form__rodo a { color: var(--color-on-primary); }

/* ---------- Текстовые страницы (шаблон B) ---------- */
.text-page h1 { margin-bottom: var(--space-4); }
/* H2 здесь считался от общего --text-h2 и на телефоне выходил 28px против
   29,6px у H1 — уровни были неразличимы. */
.text-page h2 {
  font-size: var(--text-xl);
  margin-top: var(--space-8);
  margin-bottom: var(--space-3);
}
/* 42em давало 80–85 знаков в строке; для юридического текста комфортно 60–70 */
.text-page p { max-width: 65ch; }
.text-page p + p { margin-top: var(--space-3); }

/* ---------- «Спасибо» (шаблон C) ---------- */
.thanks { padding-block: var(--space-16); }
.thanks__sub { margin-top: var(--space-4); color: var(--color-ink-soft); }
.thanks__call { margin-top: var(--space-4); }
.thanks__back { margin-top: var(--space-8); }

/* ---------- Футер ---------- */
.site-footer {
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-size: var(--text-sm);
  padding-block: var(--space-8);
  margin-top: var(--space-16);
}
/* На телефоне между последней строкой формы и первой строкой подвала было
   128px пустоты: 32 внизу секции + 64 отступа подвала + 32 внутри подвала.
   На экране в 844px это треть экрана ни на что. Снизу подвала пусто ещё
   раз: собственные 32px плюс запас под плашку. Ужимаем и то и другое —
   разделение секций держит смена фона, а не воздух. */
@media (max-width: 767px) {
  .site-footer {
    margin-top: var(--space-6);
    padding-block: var(--space-6) var(--space-4);
  }
}
.site-footer p { margin-bottom: var(--space-2); }
.site-footer a { color: var(--color-on-primary); }
/* Ссылки в футере — 18px по высоте: добираем до тап-зоны 24px, не ломая строку */
.site-footer a {
  display: inline-block;
  padding-block: var(--space-1);
}
.site-footer__brand { font-family: var(--font-heading); font-weight: var(--weight-bold); }
.site-footer__phone {
  font-family: var(--font-heading);
  font-size: var(--text-lg);
  font-weight: var(--weight-bold);
}
.site-footer__msgs a { font-weight: var(--weight-semibold); text-decoration: none; }
.site-footer__msgs a:hover { text-decoration: underline; }

/* ---------- Нижняя плашка (мобильный) ---------- */
/* Новая: звонок + два мессенджера */
.sticky-bar {
  position: fixed;
  inset-inline: 0;
  bottom: 0;
  z-index: 25;
  display: flex;
  gap: var(--space-2);
  align-items: stretch;
  padding: var(--space-2) var(--space-3) calc(var(--space-2) + env(safe-area-inset-bottom));
  background: var(--color-primary);
  box-shadow: var(--shadow-sticky);
}
.sticky-call__main {
  flex: 1;
  min-height: var(--tap-min);
  border-radius: var(--radius-md);
  background: var(--color-accent);
  color: var(--color-on-accent);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  font-family: var(--font-heading);
  font-weight: var(--weight-bold);
  text-decoration: none;
}
.sticky-call__main svg { width: 20px; height: 20px; }
.sticky-msg {
  width: 54px;
  min-height: var(--tap-min);
  border-radius: var(--radius-md);
  background: rgba(255,255,255,.10);
  display: inline-grid;
  place-items: center;
  color: var(--color-on-primary);
  text-decoration: none;
}
.sticky-msg svg { width: 24px; height: 24px; }
.sticky-msg--wa svg { color: var(--color-wa); }
.sticky-msg--tg svg { color: var(--color-tg); }

/* ---------- Движение (спроектировано в motion-brief.md) ----------
   🔴 Контент видим ПО УМОЛЧАНИЮ. Скрывающие правила включаются только когда JS
   повесил `.js-motion` на <html>. Скрипт не выполнился или человек попросил
   меньше движения — всё просто на месте. Контент никогда не заперт за анимацией.
   🔴 Кнопка звонка и нижняя плашка НЕ участвуют в появлении: номер доступен
   с первого кадра (главное правило брифа). */
/* Базовое появление: короткий сдвиг + длинная мягкая кривая. Амплитуда
   намеренно маленькая (12px, не 20): чем меньше ход и чем дольше кривая,
   тем дороже выглядит движение. Резкий скачок читается как дешёвый. */
.reveal {
  transition: opacity 620ms var(--ease-out),
              transform 620ms var(--ease-out);
  transition-delay: calc(var(--i, 0) * var(--motion-stagger));
}
.js-motion .reveal { opacity: 0; transform: translateY(12px); }
.js-motion .reveal.is-in { opacity: 1; transform: none; }

/* Волна внутри секции: каждый следующий элемент стартует на 90ms позже.
   Задержку даёт --i, его проставляет main.js. */
.js-motion .situations .card,
.js-motion .services__block,
.js-motion .why__item {
  --wave: calc(var(--i, 0) * 90ms);
}

/* ---- Ситуации: карточка не едет целиком, двигаются её слои ----
   Кадр наплывает масштабом дольше текста — получается глубина, а не
   общий сдвиг блока. Текст догоняет с небольшой задержкой. */
.js-motion .situations .card .card__media img {
  opacity: 0;
  transform: scale(1.06);
  transition: opacity 760ms var(--ease-out) var(--wave),
              transform 1100ms var(--ease-out) var(--wave);
}
.js-motion .situations.is-in .card .card__media img { opacity: 1; transform: none; }
.js-motion .situations .card h3,
.js-motion .situations .card p,
.js-motion .situations .card .card__link {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 560ms var(--ease-out), transform 560ms var(--ease-out);
}
.js-motion .situations.is-in .card h3 {
  opacity: 1; transform: none;
  transition-delay: calc(var(--wave) + 140ms);
}
.js-motion .situations.is-in .card p {
  opacity: 1; transform: none;
  transition-delay: calc(var(--wave) + 200ms);
}
.js-motion .situations.is-in .card .card__link {
  opacity: 1; transform: none;
  transition-delay: calc(var(--wave) + 260ms);
}

/* ---- Услуги: строки проявляются волной сверху вниз ---- */
.js-motion .services__block {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 620ms var(--ease-out) var(--wave),
              transform 620ms var(--ease-out) var(--wave);
}
.js-motion .services.is-in .services__block { opacity: 1; transform: none; }

/* ---- Почему мы: линия сверху прочерчивается, следом содержимое ---- */
.why__item { position: relative; border-top: 0; }
.why__item::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: var(--color-ink);
}
.js-motion .why__item::before {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 700ms var(--ease-out) var(--wave);
}
.js-motion .why.is-in .why__item::before { transform: scaleX(1); }
.js-motion .why__item .why__num,
.js-motion .why__item h3,
.js-motion .why__item p {
  opacity: 0;
  transition: opacity 560ms var(--ease-out);
}
.js-motion .why.is-in .why__item .why__num { opacity: 1; transition-delay: calc(var(--wave) + 180ms); }
.js-motion .why.is-in .why__item h3        { opacity: 1; transition-delay: calc(var(--wave) + 240ms); }
.js-motion .why.is-in .why__item p         { opacity: 1; transition-delay: calc(var(--wave) + 300ms); }

/* ---- Как это работает: точки шагов загораются по очереди ---- */
.js-motion .process__step::after {
  transform: scale(0);
  transition: transform 420ms var(--ease-out);
  transition-delay: calc(var(--i, 0) * 90ms);
}
.js-motion .process.is-in .process__step::after { transform: scale(1); }

/* ---- Зона выезда: круг обводится по контуру ----
   680 — длина окружности радиуса 108 (2πr). Обводка «рисуется» от нуля,
   заливка и подписи подтягиваются следом. */
.js-motion .zone__dot,
.js-motion .zone__dash,
.js-motion .zone__txt {
  opacity: 0;
  transition: opacity 620ms var(--ease-out);
}
.js-motion .trust.is-in .zone__dot  { opacity: 1; transition-delay: 900ms; }
.js-motion .trust.is-in .zone__dash { opacity: 1; transition-delay: 1050ms; }
.js-motion .trust.is-in .zone__txt  { opacity: 1; transition-delay: 1200ms; }

/* Ответ FAQ появляется мягко (высоту не анимируем — это дёргает раскладку) */
.faq__item[open] p { animation: faq-in 240ms var(--ease-out) both; }
@keyframes faq-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- Постоянное движение (ambient) ----------
   Повтор оправдан смыслом: служба дежурит круглосуточно, поэтому «живёт»
   именно то, что говорит о дежурстве. Правила, которые держим:
   · только transform/opacity/filter — иначе телефон греется;
   · вне экрана анимация на ПАУЗЕ (animation-play-state), не крутится вхолостую;
   · кнопка звонка и номер по-прежнему неподвижны (главное правило motion-brief);
   · при prefers-reduced-motion всё это выключается блоком ниже. */

/* Радар в схеме зоны: луч обходит круг, от центра расходится волна.
   Смысл прямой — диспетчер на связи, зона под наблюдением. */
/* Пока секция не в кадре — радара не видно вовсе: неподвижный луч поперёк
   схемы выглядит как ошибка вёрстки, а не как украшение. */
.zone__sweep, .zone__ping { opacity: 0; }
/* Луч: у центра ярче, к краю растворяется — иначе поверх карты он читается
   как случайная палка. transform-origin ровно в точке города (160,160). */
.zone__sweep {
  stroke: url(#sweepFade);
  stroke-width: 2;
  stroke-linecap: round;
  transform-origin: 160px 160px;
  animation: zone-sweep 9s linear infinite;
  animation-play-state: paused;
}
.zone__ping {
  fill: none;
  stroke: var(--color-accent);
  stroke-width: 1.5;
  transform-origin: 160px 160px;
  animation: zone-ping 4.5s var(--ease-out) infinite;
  animation-play-state: paused;
}
.trust.is-in .zone__sweep {
  opacity: .5;
  animation-play-state: running;
  transition: opacity 800ms var(--ease-out) 1400ms;
}
.trust.is-in .zone__ping {
  opacity: 1;
  animation-play-state: running;
  transition: opacity 800ms var(--ease-out) 1400ms;
}
@keyframes zone-sweep {
  to { transform: rotate(360deg); }
}
@keyframes zone-ping {
  0%   { transform: scale(.35); opacity: .55; }
  70%  { opacity: 0; }
  100% { transform: scale(5.4); opacity: 0; }
}

/* Точки шагов дышат по очереди — процесс идёт, а не замер картинкой */
.process.is-in .process__step::after {
  animation: step-glow 3.6s var(--ease-out) infinite;
  animation-delay: calc(var(--i, 0) * 900ms);
}
@keyframes step-glow {
  0%, 72%, 100% { box-shadow: 0 0 0 0 rgba(245,158,11,0); }
  36%           { box-shadow: 0 0 0 7px rgba(245,158,11,.18); }
}

/* Бейдж 24/7 в шапке: медленное кольцо. Линия открыта — это единственное,
   что мигает рядом с номером, и делает это очень мягко */
.badge--header {
  animation: badge-ring 4.4s var(--ease-out) infinite;
}
@keyframes badge-ring {
  0%, 70%, 100% { box-shadow: 0 0 0 0 rgba(245,158,11,.5); }
  35%           { box-shadow: 0 0 0 6px rgba(245,158,11,0); }
}

/* Свечение внутри механизма едва дышит. Только на десктопе: перерисовка
   четырёх крупных кадров каждый кадр — лишняя работа для телефона */
@media (min-width: 1080px) {
  .situations.is-in .card__media img {
    animation: lock-breathe 6.5s ease-in-out infinite;
    animation-delay: calc(var(--i, 0) * 800ms);
  }
  @keyframes lock-breathe {
    0%, 100% { filter: brightness(1); }
    50%      { filter: brightness(1.07); }
  }
}

/* ОДИН импульс на плашке, когда hero уехал вверх. Не мигалка: повторов нет */
@keyframes sticky-pulse {
  0%, 100% { box-shadow: var(--shadow-sticky); }
  50%      { box-shadow: 0 -2px 22px rgba(245,158,11,.55); }
}
.sticky-bar.is-pulse { animation: sticky-pulse 600ms var(--ease-out) 1; }

/* ---------- Уважение к настройке «меньше движения» ---------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Брейкпоинты: tablet ≥768, desktop ≥1080 ---------- */
@media (min-width: 768px) {
  body { padding-bottom: 0; }
  .section { padding-block: var(--space-16); }
  .site-header__phone { display: inline-flex; }
  .badge--header { display: inline-flex; }
  .hero { padding-block: var(--space-6) 0; }
  .hero-card { min-height: 600px; padding: var(--space-12); }
  .hero h1 { max-width: 18ch; }
  .hero__cta { width: fit-content; }
  .situations__grid { grid-template-columns: repeat(2, 1fr); gap: var(--space-12) var(--space-8); }
  /* Строка услуги: название | что делаем | звонок */
  .card__link { width: fit-content; }
  .marquee__line { font-size: var(--text-xl); }
  .services__block { grid-template-columns: minmax(220px, 320px) 1fr auto; gap: var(--space-8); }
  .why__grid { grid-template-columns: repeat(2, 1fr); gap: var(--space-8) var(--space-6); }
  .process__list { grid-template-columns: repeat(4, 1fr); gap: var(--space-6); }
  .process__step { padding: var(--space-8) 0 0; }
  /* Полоса-прогресс: тянется через шаг и стык до следующего. */
  .process__step::before {
    content: "";
    position: absolute;
    top: 13px;
    left: 0;
    right: calc(var(--space-6) * -1);
    height: 2px;
    background: var(--color-line);
  }
  .process__step:last-child::before { right: 0; }
  .process__step h3, .process__step p { max-width: 15em; }
  /* Линия прочерчивается вбок при появлении секции. */
  .js-motion .process__step::before {
    transform: scaleX(0);
    transform-origin: left;
  }
  .js-motion .process.is-in .process__step::before { transform: scaleX(1); }
  .trust__grid { grid-template-columns: 1.1fr .9fr; gap: var(--space-16); }
  .trust-card, .cta-card { padding: var(--space-12); }
  .cta-card {
    display: grid;
    grid-template-columns: .92fr 1.08fr;
    gap: var(--space-12);
    align-items: start;
  }
  .cta__call, .lead-form__submit { width: fit-content; }
  .sticky-bar { display: none; }
}

@media (min-width: 1080px) {
  .site-header__logo { font-size: var(--text-xl); }
  .hero-card { padding: var(--space-16); }
  .why__grid { grid-template-columns: repeat(4, 1fr); gap: var(--space-8); }
}

/* ---------- 1-бис · Hero с фото механизма ----------
   Включается классом .hero-card--photo — страницы без него не задеты.
   Движение: наезд камеры к замку + дорисованные пины садятся на линию среза
   + развёртка при загрузке. Только transform/opacity, без JS.
   🔴 Кнопка звонка и номер в анимациях НЕ участвуют — видны с первого кадра. */

.hero-card--photo { position: relative; overflow: hidden; }
.hero-card--photo::before { display: none; }        /* клетка уступает место фото */
.hero-card--photo picture { display: contents; }
.hero-card--photo .hero-card__content { position: relative; z-index: 2; }

/* Фото и накладка лежат в одной коробке и масштабируются вместе — только так
   дорисованные пины остаются приклеены к настоящим при наезде.
   Сама коробка только позиционирует кадр, движения на ней нет */
.hero-media { position: absolute; inset: 0; z-index: 0; }

/* Наезд к замку — ОДНО непрерывное движение туда-обратно, без остановок
   и без синхронизации с пинами: рывки были именно от пауз в цикле.
   Точка наезда стоит на механизме, поэтому кадр идёт «в замок» */
/* Наезд камеры работает везде, включая телефон — владелец подтвердил, что
   сама анимация нравится. Дёрганье кадра относительно текста, которое было
   видно на записи экрана, шло не отсюда, а от dvh: см. .hero-card выше. */
.hero-zoom {
  position: absolute; inset: 0;
  transform-origin: 61% 58%;
  animation: heroZoom 24s ease-in-out infinite alternate;
  will-change: transform;
}
@keyframes heroZoom {
  from { transform: scale(1);    }
  to   { transform: scale(1.08); }
}

.hero-photo {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
}

/* Накладка. object-fit: cover у фото и preserveAspectRatio="slice" у SVG —
   это одна геометрия, поэтому координаты пинов совпадают пиксель в пиксель */
.hero-fx {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none;
}
.hero-fx--vert { display: none; }

/* Пины садятся по очереди. Цикл 7 с: движение ~1,5 с, остальное кадр стоит */
.fx-pin { animation: heroPinSet 7s cubic-bezier(.16,1,.3,1) infinite; }
.fx-pin:nth-of-type(2) { animation-delay: .09s; }
.fx-pin:nth-of-type(3) { animation-delay: .18s; }
.fx-pin:nth-of-type(4) { animation-delay: .27s; }
.fx-pin:nth-of-type(5) { animation-delay: .36s; }
@keyframes heroPinSet {
  0%, 4%    { transform: translate(0, 0);     opacity: .22; }
  10%       { transform: translate(0, -34px); opacity: 1;   }
  16%       { transform: translate(0, -30px); opacity: .85; }
  70%       { transform: translate(0, -30px); opacity: .5;  }
  88%, 100% { transform: translate(0, 0);     opacity: .22; }
}

/* У вертикального кадра свой масштаб: те же 43% длины пина — это 25 единиц */

/* Холодное свечение на пинах: медленный вдох-выдох, НЕ синхронный с циклом
   пинов (7s) — иначе читается как одна общая мигалка. Разные периоды дают
   ощущение живого света, а не анимации по расписанию.
   mix-blend-mode: screen — свет складывается с кадром, а не красит его. */
.fx-cool {
  mix-blend-mode: screen;
  opacity: .34;
  /* Без transform-box у SVG-фигуры точка отсчёта трансформации — начало
     системы координат холста, и scale() унёс бы пятно за кадр вместо того,
     чтобы раздуть его на месте. */
  transform-box: fill-box;
  transform-origin: center;
  animation: heroCool 5.5s ease-in-out infinite alternate;
}
@keyframes heroCool {
  from { opacity: .30; transform: scale(.94); }
  to   { opacity: .72; transform: scale(1.06); }
}

.fx-line { stroke-dasharray: 150; animation: heroLineDraw 7s ease-out infinite; }
@keyframes heroLineDraw {
  0%, 6%    { stroke-dashoffset: 150; opacity: 0;   }
  15%       { stroke-dashoffset: 0;   opacity: 1;   }
  70%       { stroke-dashoffset: 0;   opacity: .55; }
  88%, 100% { stroke-dashoffset: 0;   opacity: 0;   }
}

.fx-flash { animation: heroFlash 7s ease-out infinite; }
@keyframes heroFlash {
  0%, 12%   { opacity: 0;  }
  17%       { opacity: .9; }
  34%, 100% { opacity: 0;  }
}

/* Вуаль: гасим фото под текстом, иначе буквы не читаются.
   Справа от 54% почти прозрачная — там механизм, его закрывать нельзя */
.hero-veil {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(100deg,
    rgba(11,12,14,.96) 0%, rgba(11,12,14,.92) 36%,
    rgba(11,12,14,.55) 52%, rgba(11,12,14,.14) 68%,
    rgba(11,12,14,.06) 100%);
}

/* На десктопе колонка текста обязана заканчиваться ДО механизма:
   пины стоят на 59–64% ширины карточки, текст доходил до 75% и закрывал их */
@media (min-width: 1080px) {
  .hero-card--photo .hero-card__content { max-width: 52%; }
  .hero-card--photo .hero__sub { max-width: none; }
}

/* Свечение механизма мягко пульсирует — «замок под напряжением» */
.hero-glow {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: radial-gradient(38% 42% at 59% 54%,
    rgba(245,158,11,.30) 0%, rgba(245,158,11,.10) 45%, rgba(245,158,11,0) 72%);
  mix-blend-mode: screen;
  animation: heroBreathe 4.2s ease-in-out infinite alternate;
}
@keyframes heroBreathe { from { opacity: .45; } to { opacity: 1; } }

/* Развёртка проходит по кадру один раз при загрузке и больше не повторяется */
.hero-scan {
  position: absolute; top: 0; bottom: 0; left: 0; width: 45%;
  z-index: 1; pointer-events: none; opacity: 0;
  background: linear-gradient(100deg,
    rgba(245,158,11,0) 0%, rgba(245,158,11,.24) 46%,
    rgba(253,230,138,.38) 52%, rgba(245,158,11,0) 100%);
  mix-blend-mode: screen;
  animation: heroScan 1500ms cubic-bezier(.16,1,.3,1) 260ms 1 both;
}
@keyframes heroScan {
  0%   { transform: translateX(-40%); opacity: 0; }
  22%  { opacity: 1; }
  100% { transform: translateX(230%); opacity: 0; }
}

/* Каскад входа. Кнопки в каскад не входят — они уже здесь и кликабельны */
.anim-in { animation: heroFadeUp 320ms cubic-bezier(.16,1,.3,1) both; }
.anim-in--1 { animation-delay:  60ms; }
.anim-in--2 { animation-delay: 120ms; }
.anim-in--3 { animation-delay: 180ms; }
@keyframes heroFadeUp {
  from { opacity: 0; transform: translate3d(0, 20px, 0); }
  to   { opacity: 1; transform: none; }
}

/* Мобильный: текст лежит НА фото — вуаль вертикальная и плотнее.
   Кадр опущен и приближен, иначе механизм оказывается за кнопкой звонка */
@media (max-width: 1079px) {
  .hero-fx--wide { display: none; }
  .hero-fx--vert { display: block; }
  /* Кадр виден ПОЛНОСТЬЮ: коробка прижата к низу и имеет ровно пропорции
     снимка — и фото, и накладка ложатся в неё без обрезки и совпадают
     друг с другом. (object-fit: contain так использовать нельзя: SVG считает
     геометрию по slice, и дорисованные пины уехали бы с настоящих.) */
  /* Кадр опущен так, чтобы механизм с пинами оказался НИЖЕ текста: он
     главный акцент первого экрана, а раньше лежал прямо под заголовком.
     Пропорции коробки не трогаем — фото и SVG-накладка должны совпадать
     (см. пояснение выше), поэтому двигаем только положение. */
  /* Фото стоит ПОД текстом в потоке, а не лежит абсолютом на всей карточке.
     Прежняя схема держалась на том, что экран высокий: на телефоне владельца
     браузерные панели съедают около 280px, карточка ужалась до 468px, и текст
     лёг прямо на механизм — перекрытие 286px. В потоке это невозможно:
     карточка растёт под содержимое, а не сжимает его. Отрицательные поля
     возвращают фото к краям карточки, нижние углы скругляются по ней. */
  .hero-media {
    position: relative;
    inset: auto;
    height: auto;
    aspect-ratio: 1602 / 1336;
    transform: none;
    margin: var(--space-4) calc(-1 * var(--hero-pad)) calc(-1 * var(--hero-pad));
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    overflow: hidden;
    /* Верхний край кадра растворяется в подложке карточки. Иначе тёмный блок
       с текстом обрывался в фотографию прямой линией через весь экран.
       Верх снимка — дверь и полотно, там ничего важного, его и гасим. */
    -webkit-mask-image: linear-gradient(180deg,
      transparent 0, rgba(0,0,0,.22) 7%, rgba(0,0,0,.72) 17%, #000 27%);
    mask-image: linear-gradient(180deg,
      transparent 0, rgba(0,0,0,.22) 7%, rgba(0,0,0,.72) 17%, #000 27%);
  }
  /* Точка наезда — на пинах: после сдвига кадра вправо они лежат на 43%
     ширины и 54% высоты окна. Считается из координат мастера, не на глаз. */
  .hero-zoom { transform-origin: 43% 54%; }
  /* 60px шапка + 64px липкая панель звонка + запас: иначе низ карточки
     с замком уезжает под панель */
  /* Высоту экрана карточка больше не занимает принудительно: она ровно такая,
     какая нужна тексту и кадру. Раньше min-height подгонял её под экран и на
     невысоких телефонах именно он и создавал давку. */
  /* Колонкой, и кадр вторым: в разметке .hero-media стоит ПЕРЕД текстом
     (так было нужно абсолютному позиционированию), поэтому порядок задаём
     явно, а не переставляем блоки в шести файлах. */
  .hero-card--photo {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  .hero-card--photo .hero-media { order: 2; }
  .hero-card--photo .hero-card__content { order: 1; }
  .hero-card--photo .hero-card__content { justify-content: flex-start; }

  /* Бейджи «24/7 · Warszawa + 20 km · 15–20 min» на телефоне сняты
     (решение владельца 2026-07-25): они съедали верх экрана и опускали
     текст прямо на механизм. Ничего не потеряно — 24/7 и Warszawa стоят
     в самом заголовке, «dojazd 15–20 min» бежит в строке под первым
     экраном и повторяется цифрой в «Dlaczego warto». На десктопе места
     хватает, там бейджи остаются. */
  .hero-card--photo .hero__badges { display: none; }
  .badge__long { display: none; }

  /* Невысокий телефон (SE и подобные): текст занимал 68% экрана, и механизм
     снова заезжал под кнопки мессенджеров. Сжимаем ТЕКСТ, а не кадр —
     опускать кадр дальше некуда, он уже свисает под липкую панель. */
  @media (max-height: 700px) {
    .hero-card--photo h1 { font-size: 1.7rem; }
    .hero-card--photo .hero__sub {
      font-size: var(--text-base);
      margin-block: var(--space-3);
    }
    .hero-card--photo { --hero-pad: var(--space-4); }
  }

  /* «Pogotowie zamkowe» одной строкой: ломало не место в колонке (нужно
     273 px при 310), а общее ограничение .hero h1 { max-width: 15ch } */
  .hero-card--photo h1 { max-width: none; }
  /* Вуаль существовала, чтобы текст читался поверх фото. Текст с фото ушёл —
     гасить нечего, а кадру она только сбивала контраст. */
  .hero-card--photo .hero-veil { display: none; }
  .hero-card--photo .hero-glow {
    background: radial-gradient(56% 26% at 62% 86%,
      rgba(245,158,11,.28) 0%, rgba(245,158,11,.09) 48%, rgba(245,158,11,0) 74%);
  }
}

/* Кому движение мешает: общий блок выше уже гасит анимации через !important,
   здесь только доводим статичный кадр до осмысленного вида */
@media (prefers-reduced-motion: reduce) {
  .hero-zoom { transform: none; }
  .hero-scan { display: none; }
  .hero-glow { opacity: .7; }
  .anim-in   { opacity: 1; transform: none; }
  .fx-pin    { transform: translate(0, -30px); opacity: .5; }
  .fx-line   { stroke-dashoffset: 0; opacity: .5; }
  .fx-flash  { opacity: 0; }
  .fx-cool   { opacity: .5; transform: none; }
}

/* ============================================================
   Согласие на куки (шаг 14) — логика в consent.js
   Появляется НАД липкой плашкой: плашка со звонком не перекрывается
   никогда, номер остаётся доступен даже с открытым баннером.
   ============================================================ */
.consent {
  position: fixed;
  left: var(--space-3);
  right: var(--space-3);
  bottom: calc(var(--sticky-h) + var(--space-3));
  z-index: 30;                /* выше плашки (25) и шапки (20) */
  display: grid;
  gap: var(--space-4);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid var(--color-on-dark-line);
  background: var(--color-primary);
  color: var(--color-on-primary);
  box-shadow: var(--shadow-sticky);
  /* До решения человека баннер не участвует ни в раскладке, ни в фокусе */
  opacity: 0;
  visibility: hidden;
  transform: translateY(12px);
  transition: opacity var(--motion-in) var(--ease-out),
              transform var(--motion-in) var(--ease-out),
              visibility var(--motion-in);
}

.consent.is-open {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.consent__text {
  margin: 0;
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  color: var(--color-on-dark-soft);
  text-wrap: pretty;
}

.consent__text a {
  color: var(--color-on-primary);
  text-underline-offset: 3px;
}

.consent__actions {
  display: grid;
  grid-template-columns: 1fr 1fr;   /* отказ и согласие равны по весу —
                                       требование регулятора ЕЭЗ */
  gap: var(--space-3);
}

.consent__btn {
  min-height: var(--tap-min);
  padding-inline: var(--space-4);
  border-radius: var(--radius-md);
  border: 1px solid transparent;
  font: inherit;
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: background var(--motion-hover) var(--ease-out),
              border-color var(--motion-hover) var(--ease-out);
}

.consent__btn--accept {
  background: var(--color-accent);
  color: var(--color-on-accent);
}

.consent__btn--ghost {
  background: var(--color-veil);
  border-color: var(--color-edge);
  color: var(--color-on-primary);
}

.consent__btn--ghost:hover { background: var(--color-on-dark-line); }

/* С 768px липкой плашки нет (её скрывает правило выше) — баннер садится
   прямо к нижнему краю и уходит в угол, чтобы не перекрывать контент */
@media (min-width: 768px) {
  .consent {
    left: auto;
    right: var(--space-6);
    bottom: var(--space-6);
    max-width: 420px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .consent { transition: none; }
}
