/* ==========================================================================
   Базовый слой: сетка, типографика и повторяющиеся элементы.
   Всё написано на переменных из tokens.css — при смене заказчика правится
   только tokens.css, этот файл не трогается.
   Стили конкретных секций живут в css/sections/*.css.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

/* Базовый тон висит на html, а не на body: фон корневого элемента красится
   на холст и лежит ниже всего остального — поверх него можно положить
   общестраничную подсветку с отрицательным z-index. */
/* 100%, а не none и не auto.

   auto — это автоувеличение текста Android (font boosting): браузер сам
   решает, что строка мелкая, и раздувает её, ломая подобранную раскладку.
   none формально то же самое, что 100%, но у него дурная слава: часть
   движков трактует его как «пользователю запрещено», и это провал
   доступности. 100% — нейтральное значение: своей раскладке верим,
   человеку ничего не запрещаем.

   Проверено, а не выведено (tools/_sx-text.js, 375×667): при 130% кегль
   основного текста растёт 16 → 20.8px, страница вырастает с 24 334
   до 30 985px, горизонтальной прокрутки не появляется, обрезанным
   не оказывается ни одного видимого элемента. Масштабирование пальцами
   тоже не запрещено — ни user-scalable, ни maximum-scale в meta нет.

   Свойство продублировано без префикса: -webkit-версию понимает Blink
   и WebKit, стандартную — остальные. */
html {
  -webkit-text-size-adjust: 100%;
          text-size-adjust: 100%;
  background: var(--bg-tint);
  /* Парное объявление к <meta name="color-scheme" content="only light">.
     Мета уже стоит в head, но одной её мало: во встроенном браузере
     мессенджера страницу открывает чужая оболочка, и она вправе подставить
     свой <head>. CSS-объявление едет вместе со страницей и отменить его
     обёртке нечем. Без него вычисленное color-scheme было normal, то есть
     Chrome и WebView на Android при системной тёмной теме имели право
     перекрасить светлый сайт сами (Auto Dark Theme) — подобранные контрасты
     уезжают, светлый логотип теряется на светлом.
     Появится тёмная тема — здесь будет `light dark`. */
  color-scheme: only light;
}

body {
  margin: 0;
  color: var(--ink);
  font-family: var(--font-base);
  font-size: var(--fz-body);
  line-height: var(--lh-body);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

/* ---------- Общестраничная подсветка ----------

   Раньше каждая секция несла свою радиальную подсветку в собственном фоне.
   Подсветка обрывалась на кромке секции, и на стыке двух экранов появлялась
   заметная горизонтальная граница — заказчик назвал это «плохим переходом
   фона». Здесь подсветка снята с секций и вынесена на один слой во весь
   экран: пятна не привязаны к границам блоков и переходят через них.

   Слой закреплён (fixed), поэтому при прокрутке контент едет по неподвижной
   заливке — стыков не возникает в принципе. Медленный дрейф добавляет
   ощущение живого фона; при prefers-reduced-motion он выключается. */
body::before {
  content: '';
  position: fixed;
  inset: -20%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(38% 34% at 14% 12%, rgba(124, 59, 246, .10) 0%, transparent 70%),
    radial-gradient(34% 30% at 86% 30%, rgba(124, 59, 246, .085) 0%, transparent 70%),
    radial-gradient(42% 34% at 30% 76%, rgba(124, 59, 246, .07) 0%, transparent 72%),
    radial-gradient(32% 28% at 78% 92%, rgba(124, 59, 246, .06) 0%, transparent 70%);
  animation: aura-drift 48s ease-in-out infinite alternate;
  will-change: transform;
}

@keyframes aura-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(2.5%, -2%, 0) scale(1.06); }
}

@media (prefers-reduced-motion: reduce) {
  body::before { animation: none; }
}

img, svg { max-width: 100%; display: block; }
img { height: auto; }

a { color: var(--brand); text-decoration: none; transition: color var(--t-fast); }

/* ---------- Наведение только там, где есть указатель ----------

   Все :hover на сайте (и здесь, и в css/sections/*.css) завёрнуты
   в @media (hover: hover). Без этого на телефоне состояние наведения
   залипает после тапа: браузер выдаёт элементу :hover по касанию и снимает
   его только при тапе в другое место — карточка так и стоит приподнятой
   с брендовой тенью, ссылка остаётся перекрашенной. Воспроизводится
   на любом телефоне за секунду.

   Условие ровно одно, `pointer: fine` намеренно не добавлено: свод сам
   оговаривает, что связка ловит только первичный способ ввода, и ноутбук
   с сенсорным экраном может отрапортовать `pointer: coarse` — тогда мышь
   есть, а наведения нет. Одного `hover: hover` достаточно, чтобы отсечь
   чистый тач.

   Что НЕ уезжает внутрь медиазапроса: :active, :focus, :focus-visible,
   :focus-within и состояния по атрибутам. Они нужны и на тач-устройствах,
   и на клавиатуре. Поэтому парные селекторы вида «hover или focus-within»
   разведены на два правила. */
@media (hover: hover) {
  a:hover { color: var(--brand-dark); }
}

button { font: inherit; color: inherit; }

/* ==========================================================================
   Движение: две ступени и больше никаких
   --------------------------------------------------------------------------
   Свод, раздел «Движение», даёт два рабочих коридора: ~100 мс на простой
   отклик («ощущается мгновенным», нижней границы нет — короче лучше)
   и 200–300 мс на существенную смену состояния. В tokens.css им ровно
   соответствуют --t-fast (150ms ease-out) и --t-base (220ms ease-out).
   Третьей длительности на сайте нет и заводить её не нужно.

   ПРАВИЛО, по которому выбирается ступень:

     --t-fast   всё, что происходит с САМИМ элементом управления и отвечает
                на руку человека: наведение, нажатие, фокус, «эта вкладка
                выбрана». Кнопка, карточка-переключатель, поле, ссылка.
     --t-base   всё, что происходит с СОДЕРЖИМЫМ, которое этот элемент
                показывает: другая переписка в окне, другой ответ в панели,
                добавленные карточки историй.

   Почему так, а не «карточкам 220, кнопкам 150»: разнобой в переходах
   и появился оттого, что длительность выбиралась по типу элемента. Тип —
   не признак: и кнопка, и карточка ниши поднимаются под курсором, значит
   поднимаются одинаково быстро. Признак — чья это реакция: моя руки или
   содержимого.

   Кривые: ease-out на всём (появление и отклик), обе ступени её и несут.
   linear остаётся только у бесконечных проходов блика — там ускорение
   на стыке витка читалось бы рывком.

   Анимируются transform и opacity. Исключения — три, и все не про
   траекторию: color, background и box-shadow. Они не вызывают пересчёт
   раскладки (свод запрещает margin/padding/width/height/top/left именно
   поэтому), а перекрасить кромку иначе нечем. */

/* Видимый фокус — иначе страницей нельзя пользоваться с клавиатуры */
:focus-visible {
  outline: 3px solid var(--brand);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}

/* Зона нажатия не меньше 44px — иначе на телефоне в элемент не попасть пальцем.
   Расширяем невидимой областью, чтобы не менять внешний вид: сам значок
   остаётся прежнего размера, а кликается 44×44.

   Куда вешать: на <a> и <button>, у которых внутри только значок или короткая
   строка и видимый размер меньше 44px, — стрелки карусели, иконки соцсетей
   и ссылки-колонки в подвале, крестики и прочие мелкие управляющие элементы.
   На кнопки с классом .btn не нужен: у них min-height 56 (у .btn--sm 46).
   Важно: элемент должен быть сам себе позиционирующим контекстом (класс
   ставит position: relative), а у родителя не должно быть overflow: hidden,
   иначе расширенная зона обрежется. */
.tap-safe {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.tap-safe::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: max(100%, 44px);
  height: max(100%, 44px);
  transform: translate(-50%, -50%);
}

/* ---------- Сетка ---------- */

/* Поле контейнера — не просто --container-pad, а максимум из него
   и безопасной зоны устройства.

   Зачем max(), а не сложение: на телефоне без выреза инсета равна нулю,
   и сложение дало бы то же самое поле, а на iPhone в ЛАНДШАФТЕ инсета
   слева и справа доходит до 59px — сложение раздуло бы поле до 83px
   и съело бы четверть строки. Максимум берёт большее из двух: в портрете
   работает наше поле, в ландшафте — вырез.

   Замер до правки: в css/ было ноль вхождений env(), и на 844×390
   контент начинался левее выреза — 161 текстовый и медийный бокс уходил
   под скруглённый угол и под «бровь». Условие работы — viewport-fit=cover
   в meta viewport (ставит tools/build-html.js): без него инсеты нулевые
   и это правило превращается в старое поведение, ничего не ломая. */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline:
    max(var(--container-pad), env(safe-area-inset-left))
    max(var(--container-pad), env(safe-area-inset-right));
}

.section {
  position: relative;
  padding-block: var(--section-y);
}

/* Межсекционный воздух задаётся ОДНОЙ ручкой (--section-y), и внешние поля
   крайних элементов не имеют права к нему прибавляться: иначе стык двух
   экранов зависит от того, чем секция случайно кончилась, и одинаковые
   с виду переходы расходятся на десятки пикселей.

   На сегодняшней разметке правило ничего не меняет (замер: у первых
   и последних потомков всех тринадцати секций внешние поля уже нулевые) —
   это страховка от возврата беды, а не починка. Настоящий разброс стыков
   даёт не margin, а внутреннее поле последнего блока секции: вывод отдаёт
   32px, карточки 16-24, а список вопросов — ноль. Это правится в секциях,
   в сквозной слой не убирается. */
.section > :first-child { margin-block-start: 0; }
.section > :last-child  { margin-block-end: 0; }

.section__head { margin-bottom: var(--s-7); }

/* Отбивка «надзаголовок → заголовок секции» на узком экране.

   Было пять разных значений на тринадцати секциях (375px): 10 у первого
   экрана, 12 у знакомства, 24 у проблемы, дней и продажи, 40 у семи секций
   и 48 у кейсов. Роль одна — «метка, под ней имя экрана», — и звучать она
   обязана одинаково, иначе при прокрутке секции читаются собранными
   из разных наборов.

   24 (--s-5), а не 40: на телефоне заголовок набран 28px в одну колонку,
   и 40px под меткой отрывают её от собственного заголовка сильнее, чем
   .section__head (48) отделяет всю шапку от содержимого. Закон близости
   при этом переворачивается — расстояние внутри группы становится
   соизмеримо с расстоянием вокруг неё.

   Два селектора, потому что в разметке две конструкции: бейдж стоит либо
   прямо перед заголовком, либо в обёртке вместе с датой (первый экран).
   Специфичность обоих выше, чем у секционных .*__title (0,1,0), поэтому
   порядок подключения файлов роли не играет.

   От 1000px не трогаем: там колонки, и секции разводят метку с заголовком
   по своей композиции — это принятый десктоп. */
@media (max-width: 999px) {
  .badge + :is(h1, h2, h3, .display),
  :has(> .badge) + :is(h1, h2, h3, .display) { margin-block-start: var(--s-5); }
}

/* ---------- Надзаголовок секции на телефоне: не плашка, а строка ----------

   Замер прошлого круга: на 375 связка «бейдж + заголовок + лид» съедает
   от 23% (вопросы) до 74% (платформа) первого экрана секции, а в ландшафте
   в трёх секциях — весь экран целиком, содержания видно 0px. Из этой связки
   бейдж — самая дорогая часть на единицу смысла: плашка 46px высотой плюс
   собственная отбивка 24 ради одного слова.

   Плюс отдельный дефект: на 320 и 375 плашка «Калькулятор второго чека»
   не помещается в строку, переносится, и точка-маячок остаётся висеть
   у первой строки. То же грозит «Платформе NextBot» и «Что даёт интенсив».
   Как только бейдж перестаёт быть плашкой, перенос перестаёт быть дефектом:
   это просто строка в две строки.

   Что снимается: заливка, рамка, тень, поля и min-height. Что остаётся:
   кегль, вес, разгон капса, цвет и точка-маячок с её пульсом — то есть
   всё, чем метка узнаётся. Отбивка до заголовка 24 → 12: группа стала
   теснее, и расстояние внутри неё обязано уменьшиться вместе с ней.

   .section .badge, а не просто .badge: первый экран не .section, и его
   композиция (бейдж в одной строке с датой, высота ряда 46px) принята
   заказчиком и держится именно на плашке. Речь только о шапках секций.

   --badge-dot-x пересчитывается обязательно: от неё считается посадка
   расходящегося кольца, и без правки кольцо осталось бы висеть в 30px
   от левой кромки, то есть в 26px правее самой точки. */
@media (max-width: 767px) {
  .section .badge {
    --badge-dot-x: 4px;      /* поле 0 + половина точки 8 */
    min-height: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    /* Выключка по верху, а не по центру: у двухстрочной метки точка
       обязана стоять при первой строке. Смещение на полстроки вниз
       ставит её на оптическую середину знака. */
    align-items: flex-start;
    line-height: var(--lh-heading);
  }

  .section .badge::before { margin-top: .34em; }
  .section .badge::after  { top: calc(.34em + 4px); }

  .section .badge + :is(h1, h2, h3, .display),
  .section :has(> .badge) + :is(h1, h2, h3, .display) { margin-block-start: var(--s-3); }
}

/* Широкое содержимое (таблицы, мокапы) не должно ломать страницу.

   overscroll-behavior-x: contain — часть самой утилиты, а не секции.
   Раньше она стояла только у ленты комьюнити, а лента платформы жила
   с auto: две одинаковые с виду ленты вели себя по-разному, и у той,
   что с auto, свайп на упоре уходил системе — во встроенном браузере
   мессенджера это закрытие карточки или переход «назад» вместо прокрутки
   ленты. Поведение ленты не может зависеть от того, в какой секции она
   стоит. */
.scroll-x {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

/* ---------- Типографика ---------- */

h1, h2, h3 { margin: 0; text-wrap: balance; }
p { margin: 0; text-wrap: pretty; }

/* ---------- Переносы ----------

   Свод, раздел «Кириллица → Переносы». Условие для `auto` уже выполнено:
   на <html> стоит lang="ru", без него браузеру нечем выбрать словарь
   и `auto` тихо деградирует до `manual`. Словарь ru в Chrome с версии 112,
   в Firefox и Safari давно.

   Включаем не глобально, а сверху вниз: `auto` наследуется от body на весь
   текст, и тут же гасится там, где перенос вредит. Свод про это говорит
   прямо: автоперенос готов разорвать короткое слово, и «в основном тексте
   это незаметно, в крупном заголовке видно на весь экран, в кнопке читается
   как сломанная вёрстка».

   hyphenate-limit-chars: 8 4 3 — не рвать слова короче 8 букв, минимум
   4 знака до переноса и 3 после. Поддержано в Chrome 109 и Firefox 137,
   в Safari нет: там придётся жить с дефолтом, и это осознанно — для
   критичных длинных слов в разметке стоит &shy; (см. «металло&shy;конструкциям»
   в sections/11-stories.html).

   Chrome и Firefox рвут одно и то же слово в разных местах — оба варианта
   корректны, одинаковости требовать бессмысленно. */
body {
  -webkit-hyphens: auto;
  hyphens: auto;
  hyphenate-limit-chars: 8 4 3;
}

/* Что не переносится никогда: заголовки, кнопки, надзаголовки, выводы
   секций и крупные числа. Роли, набранные капслоком, гасят перенос у себя
   сами — правило стоит рядом с их text-transform в css/sections/*.css,
   чтобы удаление секции не оставляло висячий селектор в базе. */
h1, h2, h3, h4,
.display, .h1, .h2, .h3,
.badge,
.btn, button,
.verdict, .callout,
.marker, .underline-stroke,
/* Реплики в мокапах переписки и короткие подписи. Причина отдельная
   от заголовков и от кнопок: это не наша типографика, а СКРИНШОТ ЧУЖОГО
   ИНТЕРФЕЙСА. Мессенджеры переносов по слогам не делают вовсе, поэтому
   дефис в середине слова внутри пузыря мгновенно выдаёт, что перед нами
   вёрстка, а не снимок экрана — весь смысл мокапа на этом и держится.
   Та же логика у коротких меток: имя показателя, состояние кейса, шаг
   истории, сноска у пункта тарифа. Все они набраны в 2-4 слова, ни одно
   не выигрывает от разрыва, а рваное короткое слово читается браком.

   Замер до правки (tools/eye-hyph.js): 22 переносимых элемента на 320,
   8 на 375, 11 на 430. Учтено, что Safari не поддерживает
   hyphenate-limit-chars: там порог 8/4/3 не работает и рвёт заметно
   чаще, чем видно в Chrome, — значит полагаться на порог нельзя,
   у этих ролей перенос гасится жёстко. */
.niches__msg,
.client-chat__msg,
.cases__state-text,
.stories__step-text,
.selling__metric-name,
.price__feature-note {
  -webkit-hyphens: none;
  hyphens: none;
}

/* .lead и .subhead в этот список не попали намеренно: это абзацы, а не
   заголовки. На 375 подзаголовок набран 18px в колонке 343px, и слова
   вроде «квалифицировать» переносом только выигрывают. */

/* ---------- Курсива на сайте нет ----------

   Ни Golos Text, ни Oswald кириллического курсива не имеют: в css/fonts.css
   у каждого семейства одно начертание, font-style: normal. Любой italic
   браузер отрисовал бы синтетическим наклоном прямого, а свод (раздел
   «Курсив») показывает замером, почему в кириллице это видно сразу:
   у настоящего курсива меняется конструкция знаков и ширина «т» вырастает
   на 50–90%, у наклона не меняется ничего.

   Оба места, где курсив стоял (цитата партнёра и приписка у кнопки цены),
   переведены на прямое начертание. Эта строка держит решение: если em, i,
   cite, address или blockquote появятся снова, они наберутся прямым,
   а не поддельным наклоном. Гасим только синтез наклона — синтетический
   жир не трогаем, он отвечает за фолбэк на Arial Bold, пока грузится
   вебшрифт. */
body { font-synthesis-style: none; }

/* Крупные заголовки макета — узкий жирный гротеск, всегда в верхнем регистре */
/* Кегль, интерлиньяж и трекинг заголовков живут только здесь и в tokens.css.
   Локальные font-size / line-height / letter-spacing на h2 из секций удалены:
   раньше их было восемь, три и три соответственно, и подряд идущие экраны
   звучали с разной громкостью без смысловой причины. */
.display {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--lh-tight);
  /* Капс, значит разгон, а не поджим. Было .01em — ниже нижней границы
     свода (0.02em на любом text-transform: uppercase). */
  letter-spacing: var(--ls-caps-lg);
  text-transform: uppercase;
}

.h1 { font-size: var(--fz-h1); }
.h2 { font-size: var(--fz-h2); }
.h3 { font-size: var(--fz-h3); font-weight: 700; line-height: var(--lh-heading); }

/* Подзаголовок секции. Один кегль, один интерлиньяж, одна мера на все
   секции — секции их не переопределяют. Было пять кеглей (16.6…23.2 на 1440),
   семь интерлиньяжей (1.25…1.65) и семь разных max-width, из них четыре
   в пикселях и три снятых вовсе: на #intro строка уходила на 91 знак. */
.lead {
  font-size: var(--fz-lead);
  line-height: var(--lh-lead);
  color: var(--muted);
  max-width: var(--measure);
  /* Порог переноса у лида выше общего (8 4 3): подзаголовок набран 21px,
     и на таком кегле разрыв обычного девятибуквенного слова читается как
     брак — на 1440 выходило «нормальная обра-ботка» (#selling) и «Оста-лось
     нажать» (#cta). При 12 6 4 слова короче двенадцати букв не рвутся вовсе,
     а ради чего перенос у лида и оставлен — «квалифицировать» на 375
     в колонке 343px — рвётся по-прежнему. */
  hyphenate-limit-chars: 12 6 4;
}

/* Подзаголовок-описание под крупным заголовком секции: в макете он заметно
   мельче лида, но крупнее основного текста */
.subhead {
  font-size: var(--fz-body);
  line-height: var(--lh-body);
  color: var(--muted);
  max-width: var(--measure);
}

/* ---------- Разбивка подзаголовка на телефоне ----------

   Заказчик показал на экране 6 слово, уехавшее на отдельную строку.
   Половина этой поломки — не раскладка, а порог переноса.

   На 393 и 430 лид калькулятора рвал слово: «дополнит-ельно». Порог
   hyphenate-limit-chars у лида стоял 12 6 4, а «дополнительно» — 13 букв,
   то есть ровно попадало под перенос. На телефоне поднимаем порог до 14:
   слова короче четырнадцати букв не рвутся вовсе, а то, ради чего перенос
   у лида и оставлен, — «квалифицировать» (15) на 375 в колонке 327px —
   рвётся по-прежнему (проверено, лид ниш).

   Тринадцатибуквенных слов в русском много, и на телефонной колонке
   в 288-382px разрыв такого слова не выигрывает почти ничего: экономия
   в полстроки против дефиса посреди подзаголовка. На широкой колонке
   размен другой, поэтому от 700px порог остаётся прежним и десктоп
   не трогается.

   Граница 699 — та же, на которой в tokens.css живёт мобильная ступень
   кегля. Новых границ проект не заводит.

   Вторая половина поломки экрана 6 — хвост «уже ведёшь.» на отдельной
   строке — лечится text-wrap: balance, но НЕ здесь. Замер по всем
   одиннадцати лидам на 320/375/393/430: balance выигрывает у калькулятора
   и историй и проигрывает у ниш, знакомства, финального призыва
   и — что важнее всего — у экрана 9, где на 430 он ломает ровно ту фразу,
   на которую жаловался заказчик («быстрый ответ, нормальная / обработка»).
   Поэтому balance стоит адресно, в css/sections/06-calculator.css. */
@media (max-width: 699px) {
  .lead { hyphenate-limit-chars: 14 6 4; }
}

/* ---------- Мера строки на планшете ----------

   Свод: 45-75 знаков, ориентир 66, задавать в ch. На телефоне мера
   выдерживается сама — колонка узкая, длиннейшая строка 33 знака на 320,
   40 на 375, 47 на 430. На планшете колонка вырастает вдвое, а собственную
   меру имеют только .lead и .subhead: на 768 подвальные реквизиты набирались
   в 77 знаков, цитата истории партнёра в 77, ответ FAQ в 69 — то есть выше
   верхней границы.

   Селекторы по тегу, а не по классу, намеренно: 0,0,1 проигрывает любому
   секционному .*__lead (0,1,0), поэтому секции, у которых мера подобрана
   под свою композицию, ничего не теряют. Правило подхватывает ровно то,
   у чего меры не было вовсе.

   Мера в ch, а не в px: ch — ширина нуля, у Golos Text это 0.62em,
   значит 60ch держит ~68 кириллических знаков НЕЗАВИСИМО от кегля.
   Тот же токен в пикселях пришлось бы пересчитывать на каждый кегль.

   Только 700-999: ниже мера и так в норме, а от 1000 начинается принятый
   десктоп. */
@media (min-width: 700px) and (max-width: 999px) {
  p, blockquote, dd { max-width: var(--measure); }

  /* Единственное исключение — текст, набранный по центру. Ограничение
     ширины у центрированного абзаца сдвигает строку влево относительно
     заголовка над ней: коробка сужается от левого края, а текст
     центрируется уже внутри неё. Финальный призыв — единственное такое
     место на сайте (проверено на 768: остальные центрированные абзацы
     короче меры и под ограничение не попадают). */
  .cta__date, .cta__note { max-width: none; }
}

.text-muted { color: var(--muted); }
.text-small { font-size: var(--fz-small); }
.text-caption { font-size: var(--fz-caption); color: var(--muted-2); }

/* Акцент — только на смысловом ударении, не на каждом втором слове */
.accent { color: var(--brand); }

/* Подчёркивание-росчерк под фразой (как в макете под «новый продукт») */
.underline-stroke {
  background-image: linear-gradient(transparent 62%, var(--brand-soft) 62%);
  padding-inline: .08em;
}

/* ---------- Бейдж-надзаголовок ---------- */

/* Один компонент на всю страницу: одна высота (46px по макету), один кегль
   (15px), одни поля. Высоту держит min-height при нулевых вертикальных
   полях, поэтому иконка внутри габарит не меняет — раньше бейдж со значком
   был 46px, а без значка 43.1, и на странице набиралось шесть разных высот
   и четыре кегля для одной и той же роли.
   Кегль фиксированный, не clamp: на узких экранах clamp давал 12px —
   ниже порога читаемости.
   Контурный вариант той же высоты: border-box съедает рамку. */
.badge {
  /* Расстояние от левой кромки бейджа до ЦЕНТРА точки-маячка: поле 26
     плюс половина точки. Вынесено в переменную, потому что от него зависит
     посадка расходящегося кольца (.badge::after) — если секция когда-нибудь
     поменяет поле, кольцо обязано уехать вместе с точкой. */
  --badge-dot-x: 30px;

  position: relative;   /* система координат для кольца ::after */
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  min-height: 46px;
  padding: 0 26px;
  /* В макете это скруглённый прямоугольник, а не таблетка: при радиусе пилюли
     надзаголовок секции начинает читаться как кнопка. */
  border-radius: var(--r-sm);
  /* Отделка бейджа общая на весь сайт. Раньше плоская заливка --brand-soft;
     перелив и мягкое свечение появились сначала локально на паре экранов,
     и при прокрутке бейджи чередовались — плоский, объёмный, плоский.
     По docs/visual-language.md плоский цвет самый дешёвый приём набора,
     поэтому материал поднят сюда, а локальные копии в секциях удалены. */
  background: var(--grad-soft);
  border: 1px solid rgba(124, 59, 246, .16);
  box-shadow: 0 1px 2px rgba(27, 22, 51, .04),
              0 10px 28px rgba(124, 59, 246, .12);
  color: var(--brand);
  font-size: var(--fz-small);
  font-weight: 600;
  letter-spacing: var(--ls-caps-sm);
  text-transform: uppercase;
  line-height: 1;
}

.badge--outline { background: transparent; border: 1.5px solid var(--brand); box-shadow: none; }
.badge .icon { width: 18px; height: 18px; flex: none; }

/* Точка-маячок слева. Не украшение: она заранее задаёт круглую форму,
   которой ниже набраны значки секций, и оживляет надзаголовок — заказчик
   попросил такую на экране 4, а разный вид одинаковых надзаголовков
   как раз и есть та «нет единой системы», с которой всё началось.
   Пульс медленный и малой амплитуды: это индикатор, а не мигалка. */
.badge::before {
  content: '';
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--grad-brand);
  box-shadow: 0 0 0 4px rgba(124, 59, 246, .14);
}

/* Значков внутри надзаголовков нет намеренно. Стояли они на шести секциях
   из тринадцати и в каждом случае повторяли собственное слово: калькулятор
   у «Калькулятора», монитор у «Платформы», телеграм у «TG-комьюнити».
   Половина надзаголовков со значком, половина без — это и есть разнобой,
   который заказчик называет «нет единой системы». Осталась одна метка
   на все секции — точка. Если значок когда-нибудь понадобится, точку
   надо гасить: два кружка подряд перед словом читаются как брак вёрстки. */
.badge:has(.icon)::before,
.badge:has(.icon)::after { display: none; }

/* Расходящееся кольцо — ОТДЕЛЬНЫЙ слой, а не тень на точке.

   Было: та же волна рисовалась вторым box-shadow у самой точки, и кадры
   меняли его радиус разлёта. box-shadow — свойство с перерисовкой: каждый
   кадр браузер заново считает и заливает область вокруг точки, и делает
   это на всех тринадцати надзаголовках страницы одновременно, бесконечно.
   Свод про это говорит прямо: анимировать можно transform и opacity,
   потому что только они обрабатываются на стадии композиции.

   Стало: кольцо — свой элемент фиксированного размера (26px = точка 8
   плюс два разлёта по 9), нарисованный ВНУТРЕННЕЙ тенью, поэтому середина
   у него прозрачная и точка сквозь него видна. Кадры меняют только
   scale и opacity — раскладка и заливка не пересчитываются вовсе.

   Рисунок движения сохранён один в один: кольцо стартует прижатым к точке,
   быстро набирает плотность, разлетается до 26px и гаснет; больше половины
   цикла его нет вовсе, поэтому читается редкий пульс, а не мигание.
   Постоянное кольцо 4px в анимации по-прежнему не участвует — это первая,
   статическая тень на самой точке. */
.badge::after {
  content: '';
  position: absolute;
  left: var(--badge-dot-x);
  top: 50%;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: var(--r-pill);
  box-shadow: inset 0 0 0 9px rgba(124, 59, 246, .30);
  opacity: 0;
  transform: scale(.31);
  pointer-events: none;
  animation: badge-ring 2.8s ease-out infinite;
}

/* Дышит расходящееся кольцо, сама точка не меняет ни размера, ни цвета.
   Постоянное кольцо 4px в анимации не участвует — это первая тень.
   Больше половины цикла второго кольца нет вовсе, поэтому движение
   читается как редкий пульс, а не как мигание; стартует оно с нулевым
   разлётом, то есть без площади, и скачка на стыке витков нет. */
@keyframes badge-ring {
  0%   { transform: scale(.31); opacity: 0; }
  12%  { opacity: 1; }
  55%  { transform: scale(1);   opacity: 0; }
  100% { transform: scale(1);   opacity: 0; }
}

/* Конечный кадр — кольцо разлетелось и погасло, то есть при выключенном
   движении на экране остаётся только сама точка. */
@media (prefers-reduced-motion: reduce) {
  .badge::after { animation: none; opacity: 0; }
}

/* ---------- Кнопки ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-3);
  min-height: 56px;
  padding: 0 32px;
  border: 0;
  border-radius: var(--r-md);
  font-size: var(--fz-body);
  font-weight: 700;
  letter-spacing: var(--ls-caps-md);
  text-transform: uppercase;
  cursor: pointer;
  /* Одна ступень на все три свойства: наведение, нажатие и фокус кнопки —
     это отклик на руку. Было 220/220/150 — тень уезжала заметно позже,
     чем кнопка проседала под нажатием, и отклик читался вязким. */
  transition: background var(--t-fast), box-shadow var(--t-fast), transform var(--t-fast);
}

.btn .icon { width: 20px; height: 20px; flex: none; }

/* Главная кнопка сайта. Два приёма, и оба про «перелив вместо заливки»
   из docs/visual-language.md.

   1. Градиент разведён по краям палитры: --brand-dark → --brand-light.
   Прежняя пара --brand → --brand-light отличалась на 15-18 единиц по каналам —
   на кнопке 440×115 такой перелив не читался вовсе, и кнопка выглядела
   плоской заливкой. Теперь между концами 1.56 по относительной яркости:
   перелив видно, но кнопка не пёстрая — цвета соседние по палитре,
   новых не заведено. Белый текст держит контраст на обоих концах:
   7.1:1 на тёмном, 4.6:1 на светлом при норме 4.5.

   2. Блик: наклонная светлая полоса раз в 7 секунд неспешно проходит
   по кнопке. Это второй слой фона, а не псевдоэлемент, и так сделано
   намеренно. Псевдоэлементу понадобились бы position: relative и
   overflow: hidden на самой кнопке, а на .btn--primary уже висит чужой
   ::after: в секции «Кейсы» он растянут через inset: 0 на всю карточку
   и делает её кликабельной целиком. Дай кнопке свой контекст
   позиционирования — и зона нажатия схлопнется до кнопки, да ещё
   и обрежется по overflow. Фоновый слой не трогает ни то, ни другое,
   скругление обрезает его само, а тень и outline фокуса рисуются
   поверх и остаются как были.

   Мигать и дёргаться блику нечем: на обоих концах цикла полоса целиком
   за пределами кнопки, то есть на стыке витков показывать просто нечего.
   Расчёт: слой шире кнопки в 2.6 раза, поэтому сдвиг background-position
   на P% смещает его на −1.6×ширина×P. Сама полоса занимает 40…60% слоя,
   значит она видна только при P от 2.5% до 97.5%, а ход идёт от 220%
   до −80% — это 32% цикла, около 2 секунд прохода и 5 секунд покоя.
   Всё в долях, поэтому цикл одинаков и на кнопке 200px, и на кнопке
   во всю ширину экрана. Края полосы полностью прозрачны — вход и выход
   мягкие, без кромки.

   Порядок слоёв: блик первым, то есть поверх фирменного градиента. */
.btn--primary {
  background-image:
    linear-gradient(105deg,
      rgba(255, 255, 255, 0)   40%,
      rgba(255, 255, 255, .16) 50%,
      rgba(255, 255, 255, 0)   60%),
    linear-gradient(135deg, var(--brand-dark) 0%, var(--brand-light) 100%);
  background-repeat: no-repeat;
  background-size: 260% 100%, auto;
  background-position: 220% 0, 0 0;
  color: #fff;
  box-shadow: var(--sh-brand);
}

/* Блик крутится только на десктопе.

   Он анимирует background-position, то есть свойство с ПЕРЕРИСОВКОЙ:
   каждый кадр браузер заново заливает всю площадь кнопки. На десктопе
   кнопка 440×115 и запас производительности большой, приём принят
   заказчиком («чтобы чуть-чуть переливалась — это тоже удорожает сайт»),
   поэтому там всё остаётся как было.

   На телефоне цена та же, а показывать нечего: кнопка растянута во всю
   ширину экрана (.btn { width: 100% } ниже 640), полоса блика идёт
   по её длинной стороне и на 375px читается как медленное осветление
   фона без формы. Замер в покое на первом экране при CPU 4x: 40.9 fps
   против 67.0 fps с prefers-reduced-motion — то есть половину кадрового
   бюджета съедали вечные анимации, а INP по .niches__card доходил
   до 544 мс при пороге 200. Виноват не обработчик события, а занятый
   главный поток: с reduced-motion те же элементы дают 112 мс.

   Граница 1000px — та же, что у остальных мобильных ступеней. */
@media (min-width: 1000px) {
  .btn--primary { animation: btn-sheen 7s linear infinite; }
}

@keyframes btn-sheen {
  from { background-position: 220% 0, 0 0; }
  to   { background-position: -80% 0, 0 0; }
}

@media (hover: hover) {
  .btn--primary:hover { color: #fff; box-shadow: 0 14px 34px rgba(124, 59, 246, .36); }
}

/* Без анимации полоса остаётся на стартовой позиции, то есть за пределами
   кнопки: в покое блика не видно вовсе. У выключенной кнопки живого блика
   тоже быть не должно. */
@media (prefers-reduced-motion: reduce) {
  .btn--primary { animation: none; }
}

.btn--primary[disabled] { animation: none; }

.btn--ghost {
  background: var(--surface);
  color: var(--ink);
  border: 1.5px solid var(--line);
  box-shadow: var(--sh-card);
}
@media (hover: hover) {
  .btn--ghost:hover { color: var(--ink); border-color: var(--brand); }
}

.btn--sm { min-height: 46px; padding: 0 22px; font-size: var(--fz-small); }

/* ---------- Подъём и просадка: одинаково у всех кнопок ----------

   Было так: подъём на 2px по наведению стоял ровно на одной кнопке сайта —
   .price__btn на экране 12, — а просадка на 1px по нажатию ровно
   на .btn--primary. Из двенадцати кнопок «Создать ИИ БРО» одна вела себя
   иначе, чем одиннадцать соседних, а «Показать ещё» (.btn--ghost) под
   пальцем не отвечала ничем: на телефоне, где наведения нет вовсе,
   она выглядела мёртвой до самого момента, когда под ней появлялись
   карточки.

   Ход тот же, что у карточек-переключателей, только вдвое короче: карточка
   ниши поднимается на 4px, кнопка на 2 — она и так подсвечена тенью,
   и высокий подъём у элемента 91px читался бы прыжком. Нажатие у обоих
   одно, +1px.

   Просадка снаружи @media (hover: hover) намеренно: нажатие есть
   и на тач-устройстве, и это там единственный доступный отклик.
   :not([disabled]) в обоих селекторах, а не отдельным сбросом ниже:
   у выключенной кнопки не должно быть ни подъёма, ни просадки, а
   специфичность у правил тогда одинаковая и порядок объявления решает
   всё сам — нажатие объявлено после наведения, поэтому при нажатии
   на наведённую кнопку побеждает просадка. */
@media (hover: hover) {
  .btn:not([disabled]):hover { transform: translateY(-2px); }
}
.btn:not([disabled]):active { transform: translateY(1px); }

.btn[disabled] { opacity: .5; cursor: not-allowed; box-shadow: none; }

/* ---------- Отклик на палец ----------

   На тач-устройстве вся ховерная половина отклика не применяется вовсе:
   подъём на 2px и уход тени в бренд живут в @media (hover: hover), а
   от нажатия остаётся единственный ход — просадка на 1px. Один пиксель
   происходит ПОД САМИМ ПАЛЬЦЕМ, то есть ровно там, где человек ничего
   не видит: подушечка накрывает пятно примерно 40×40, и весь отклик
   оказывается под ней. Практически кнопка на телефоне выглядит мёртвой.

   Правило: на тач-устройстве отклик обязан быть виден ЗА пределами пятна
   касания. Двигать элемент для этого нельзя — заметное смещение под
   пальцем читается как сбой вёрстки; значит меняется то, что занимает
   всю площадь и весь контур, — заливка и тень.

     главная кнопка   перелив уходит в один тон --brand-dark (правый,
                      светлый конец темнеет заметно), брендовое свечение
                      схлопывается в контактную тень: кнопка «прижимается»;
     контурная        подложка из белой становится брендово-бледной,
                      кромка — брендовой: меняется весь периметр;
     круглая стрелка  та же смена фона, что была только под курсором;
     карточка-кнопка  тень уходит в брендовую — по всем четырём кромкам.

   Просадка на 1px остаётся: вместе со сменой заливки она даёт ощущение
   физического нажатия, а не подсветки.

   Длительность общая --t-fast (150 мс): по своду простой отклик
   ощущается мгновенным до ~100 мс и нижней границы у него нет —
   переходы уже объявлены на .btn и .card--interactive, здесь меняются
   только значения. */
@media (hover: none) {
  .btn--primary:not([disabled]):active {
    background-image:
      linear-gradient(105deg,
        rgba(255, 255, 255, 0)   40%,
        rgba(255, 255, 255, .16) 50%,
        rgba(255, 255, 255, 0)   60%),
      linear-gradient(135deg, var(--brand-dark) 0%, var(--brand-dark) 100%);
    box-shadow: 0 1px 3px rgba(124, 59, 246, .34);
  }

  .btn--ghost:not([disabled]):active {
    background: var(--brand-soft);
    border-color: var(--brand);
    box-shadow: 0 1px 2px rgba(27, 22, 51, .06);
  }

  .btn-round:active { background: var(--brand-dark); }

  button.card--interactive:active,
  a.card--interactive:active { box-shadow: var(--sh-hover); }
}

/* Круглая кнопка-стрелка */
.btn-round {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border: 0;
  border-radius: var(--r-pill);
  background: var(--brand);
  color: #fff;
  cursor: pointer;
  transition: background var(--t-fast), transform var(--t-fast);
}
@media (hover: hover) {
  .btn-round:hover { background: var(--brand-dark); }
}
.btn-round:active { transform: translateY(1px); }
.btn-round .icon { width: 22px; height: 22px; }

/* ---------- Карточки ---------- */

.card {
  background: var(--surface);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-card);
  padding: var(--s-6);
}

.card--flat { box-shadow: none; border: 1px solid var(--line); }
.card--lift { box-shadow: var(--sh-lift); }

/* Карточка отзывается на наведение: чуть поднимается, тень уходит в бренд.
   Приём общий для всего сайта — интерфейс перестаёт быть картинкой
   и начинает вести себя как интерфейс. */
.card--interactive {
  /* Та же ступень, что у кнопок: подъём карточки и просадка кнопки —
     одна и та же реакция на курсор, и разной скорости у них быть не может.
     Было 220 против 150 у кнопки — на экране 13, где карточка-вопрос стоит
     вплотную к кнопке, разницу видно. */
  transition: transform var(--t-fast), box-shadow var(--t-fast);
}

@media (hover: hover) {
  .card--interactive:hover {
    transform: translateY(-4px);
    box-shadow: var(--sh-hover);
  }
}

/* Нажатие — только там, где карточка сама является элементом управления.
   Селектор по тегу, а не по классу, и это не придирка: .card--interactive
   висит и на <li> программы дней, и на <article> истории партнёра — там
   нажимать нечего, и просадка под пальцем обещала бы действие, которого
   не будет. Кнопками и ссылками из всего набора являются ровно карточки
   ниш (экран 5) и строки вопросов (экран 13).
   Смещение то же, что у кнопок: под курсором карточка стоит на −4px,
   под нажатием проваливается на +1 — ход 5px, его видно. */
button.card--interactive:active,
a.card--interactive:active { transform: translateY(1px); }

/* Верхняя кромка карточки, ловящая «свет»: тонкая светлая линия сверху.
   Незаметная деталь, которая отделяет плоскую заливку от объёмной. */
.card--edge {
  position: relative;
  overflow: hidden;
}

.card--edge::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: linear-gradient(90deg,
              transparent 0%,
              rgba(255, 255, 255, .9) 30%,
              rgba(255, 255, 255, .9) 70%,
              transparent 100%);
}

/* Кружок под иконку.

   Шкала размеров — четыре ступени, отношение иконки к кружку везде ~0.46,
   поэтому оптический вес значка одинаков на всех размерах:

     .icon-badge--sm   48 / 22   строки списков, плашки-подписи, мелкие ярлыки
     .icon-badge       56 / 26   базовый: иконка в карточке
     .icon-badge--lg   72 / 33   акцентные карточки, где значок ведёт композицию
     .icon-badge--xl   84 / 38   блоки во всю ширину и акцентные заголовки
                                 (в выводах секций значков нет — см. .verdict)

   Промежуточных размеров не заводим: на странице их набралось десять
   (44.6…100) с отношением от 0.42 до 0.59 — одинаковые по смыслу значки
   выглядели разновесно. Нужен другой размер — берём ближайшую ступень.
   Форма: по умолчанию скруглённый квадрат, .icon-badge--round делает круг.
   .icon-badge--accent заливает кружок фирменным градиентом и добавляет
   свечение — для мест, где значок должен читаться как элемент, а не как
   иллюстрация. */
.icon-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: var(--r-md);
  /* Перелив и волосяная кромка вместо плоского --brand-soft: кружков под
     значки на странице больше полусотни, и плоская заливка делала дешёвым
     сразу весь сайт. Приём тот же, что у надзаголовков, — материал общий,
     чтобы блоки не выглядели из разных наборов. */
  background: var(--grad-soft);
  border: 1px solid rgba(124, 59, 246, .12);
  color: var(--brand);
  flex: none;
}
.icon-badge .icon { width: 26px; height: 26px; }

.icon-badge--sm { width: 48px; height: 48px; }
.icon-badge--sm .icon { width: 22px; height: 22px; }

.icon-badge--lg { width: 72px; height: 72px; }
.icon-badge--lg .icon { width: 33px; height: 33px; }

.icon-badge--xl { width: 84px; height: 84px; }
.icon-badge--xl .icon { width: 38px; height: 38px; }

.icon-badge--round { border-radius: var(--r-pill); }

.icon-badge--accent {
  background: var(--grad-brand);
  border-color: transparent;
  color: #fff;
  box-shadow: var(--glow-brand);
}

/* ==========================================================================
   Вывод секции — «полка света»   (приходит на смену .callout)
   --------------------------------------------------------------------------
   Шесть секций заканчивались одинаковой плашкой во всю ширину: перелив,
   обводка, тень, фраза по центру. Подряд они читались как один
   повторяющийся элемент — ровно то, что заказчик назвал «нет единой системы»:
   «слишком много уже вот этих плашечек: плашка, фон, тень небольшая —
   таких повторяющихся элементов уже много».

   Здесь вывод — фраза прямо на фоне страницы. Держит её объект, у которого
   есть только ВЕРХНИЙ срез: волосяная кромка + световой омут под ней.
   Ни бортов, ни низа, ни скругления, ни тени. Вниз и в стороны полка
   растворяется, поэтому прямоугольник не собирается и на кромке секции
   ничего не обрывается — приём безопасен для секций с overflow: hidden.

   Правило посадки одно: ПОЛКА САДИТСЯ ПОД ОБЪЕКТОМ, ИЗ КОТОРОГО СЛЕДУЕТ
   ВЫВОД. Композиция симметрична (робот по центру, мокап во всю ширину) —
   полка по центру. Вывод следует из объекта в правой колонке — полка
   прижата вправо (.verdict--right), и её световой шлейф уходит влево:
   получается движение слева направо, о котором говорил заказчик.

   Второе правило: ПЛОТНЫМ ОБЪЕКТОМ ВЫВОД СТАНОВИТСЯ ТОЛЬКО ТАМ, ГДЕ НА НЕГО
   ЧТО-ТО ОПИРАЕТСЯ. Это один экран из шести — третий (.verdict--stage):
   там полка непрозрачна сверху и перекрывает жёсткий срез вырезки робота.

   У секции ровно одна ручка — --verdict-measure. Она меняет число строк,
   но не громкость: кегль общий на все шесть выводов и в секции не трогается.
   ========================================================================== */

.verdict,
.callout {
  /* Мера в em, а не в ch и не в px: при смене кегля число строк не плывёт.
     24em — это 45-47 знаков в строке. */
  --verdict-measure: 24em;
  /* Насколько полка выступает за фразу влево и вправо. Проценты считаются
     от ширины блока, потолок в px обязателен: без него на однострочном
     выводе экрана 3 полка вырастала до ширины окна и читалась разделителем
     секций. */
  --verdict-lead:  min(26%, 220px);
  --verdict-trail: min(26%, 220px);

  position: relative;
  /* z-index создаёт контекст наложения: полка уходит на -1/-2 внутри него
     и оказывается под текстом вывода, но над роботом и декором секции. */
  z-index: 1;
  /* fit-content, а не 100%: ширина блока равна длине фразы, и полка привязана
     к ней. Растянутая на весь контейнер полка читалась бы разделителем между
     экранами — ровно то, от чего уходили общим фоном. Отсюда же вариативность:
     шесть выводов разной длины дают шесть разных ширин при одном приёме. */
  width: fit-content;
  max-width: min(100%, var(--verdict-measure));
  margin-inline: auto;
  padding: clamp(var(--s-5), 2.2vw, var(--s-6))
           clamp(var(--s-4), 1.6vw, var(--s-6))
           clamp(var(--s-6), 3vw, var(--s-7));
  /* Один кегль на все шесть выводов. Прежде они разъезжались (46 на экране 3,
     32 на 9 и 10, 68 на 6) — одинаковые по роли блоки выглядели из разных
     наборов. Основной гротеск, не --font-display: заголовочный шрифт капсом
     закреплён за заголовками секций, и вывод не должен с ними спорить. */
  font-size: var(--fz-verdict);
  font-weight: 700;
  line-height: 1.2;
  color: var(--ink);
  /* Выключка по центру сохранена (согласованная манера «только текст, крупно,
     по центру»). Она не спорит с прижатой вправо полкой: блок обжимает фразу
     по ширине, поэтому центрируется фраза внутри самой себя, а не внутри
     пустого прямоугольника. */
  text-align: center;
  text-wrap: balance;
}

/* Полка. Верхний срез — единственная её граница.
   Кромка не ровная по яркости: она вспыхивает посередине и гаснет к концам
   длинным скатом. Это принципиально — кромка ровной плотности на длине
   в тысячу пикселей читается как <hr> под композицией, а кромка с пиком
   под объектом читается как свет, поймавший ребро. Проверено рендером:
   с плоскими стопами приём выглядел разделителем секций. */
.verdict::before,
.callout::before {
  content: '';
  position: absolute;
  z-index: -1;
  top: 0;
  bottom: 0;
  left:  calc(-1 * var(--verdict-lead));
  right: calc(-1 * var(--verdict-trail));
  pointer-events: none;
  background:
    linear-gradient(90deg,
      rgba(124, 59, 246, 0)   0%,
      rgba(124, 59, 246, .03) 28%,
      rgba(124, 59, 246, .36) 50%,
      rgba(124, 59, 246, .03) 72%,
      rgba(124, 59, 246, 0)   100%) top left / 100% 1px no-repeat,
    /* Световой омут под кромкой. Радиусы подобраны так, чтобы заливка дошла
       до нуля раньше, чем до боковых и нижней кромок блока: обе оси гаснут
       на стопе 74%, то есть на кромках альфа уже нулевая. Поэтому
       overflow: hidden секции резать нечего. */
    radial-gradient(52% 120% at 50% 0%,
      rgba(124, 59, 246, .19) 0%,
      rgba(124, 59, 246, .07) 40%,
      rgba(124, 59, 246, 0)   74%);
}

/* Полка под объектом правой колонки. Прижата вправо, шлейф уходит влево —
   это и есть движение слева направо. Пустоты слева не возникает: её занимает
   сам шлейф, он часть приёма, а не побочный эффект. */
.verdict--right {
  --verdict-lead:  min(46%, 420px);
  --verdict-trail: min(6%, 56px);
  margin-inline: auto 0;
}

/* ---------- Полка-постамент ----------

   Единственное место, где полка работает не только глазом: экран 3.
   Робот вырезан как есть, по низу кадра жёсткий горизонтальный срез, и срез
   уходит на 64px за верхнюю кромку вывода (03-intro.css, .intro__visual
   { margin-bottom }). Раньше его перекрывала непрозрачная плашка — теперь
   перекрывает непрозрачная верхушка полки. Это по-прежнему ФИЗИЧЕСКОЕ
   перекрытие: ни масок по картинке, ни растушёвки среза, они отвергались.

   Плотность набирается токеном --brand-soft, а не белым и не --bg-tint:
   поверх общестраничной подсветки (body::before) нейтральный прямоугольник
   был бы виден как более светлое пятно, а брендовый тон читается
   продолжением этой же подсветки.

   Глубина перекрытия задана в пикселях, а не в процентах: она не должна
   зависеть от того, во сколько строк встала фраза. 84px непрозрачного
   против 64px среза — запас 20px на всех ширинах.

   z-index: -2, а не -1: кромка полки обязана проходить ПОВЕРХ постамента,
   иначе на 768 и уже постамент её съедает и полка теряет своё единственное
   ребро. Оба слоя всё равно выше робота — .verdict создаёт свой контекст
   наложения на z-index: 1.

   Второй селектор — переходный: пока экран 3 стоит на старой разметке
   (.callout .intro__callout), постамент обязан работать и там, иначе наружу
   вылезает срез вырезки. Уйдёт вместе со всем блоком совместимости ниже. */
.verdict--stage::after,
.callout.intro__callout::after {
  content: '';
  position: absolute;
  z-index: -2;
  top: 0;
  left: 50%;
  /* Ширина в долях блока с потолком: непрозрачная часть обязана накрыть
     фигуру целиком. После боковой маски непрозрачны 64% ширины — это
     386px на 375 против робота в 335, 484 на 768 против 400, 486 на 1440
     и 1920 против 405 и 422. Фигура накрыта с запасом на любой ширине. */
  width: min(180%, 760px);
  height: 148px;
  transform: translateX(-50%);
  pointer-events: none;
  background:
    /* Контактная тень под фигурой: без неё робот выглядит наклеенным
       на полку, а не стоящим на ней. Ровно там, где он касается кромки. */
    radial-gradient(24% 40% at 50% 0%,
      rgba(27, 22, 51, .14) 0%,
      rgba(27, 22, 51, .05) 46%,
      rgba(27, 22, 51, 0)   74%),
    /* Само перекрытие: 84px непрозрачного, дальше растворение до нуля. */
    linear-gradient(180deg,
      var(--brand-soft) 0,
      var(--brand-soft) 84px,
      rgba(239, 233, 254, .55) 106px,
      rgba(239, 233, 254, .20) 126px,
      rgba(239, 233, 254, 0)   148px);
  /* Боковое растворение маской, а не градиентом: вертикальную плотность
     нужно держать в пикселях, а горизонтальную — в долях ширины, одним
     градиентом это не совмещается. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 18%, #000 82%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0%, #000 18%, #000 82%, transparent 100%);
}

/* Внутренний блок текста. Нужен там, где у секции есть свой инструмент или
   свой класс на строке (экраны 3 и 9); в остальных выводах не заводим. */
.verdict__text { display: block; }

/* Надзаголовок вывода — только там, где это не итог, а реплика (экран 9).
   Кружка с кавычкой и вертикальной черты вместо него не появляется:
   связка снята заказчиком по всему сайту. */
.verdict__label {
  display: block;
  margin-bottom: var(--s-3);
  font-family: var(--font-display);
  font-size: var(--fz-caption);
  font-weight: 600;
  /* .16em — вдвое выше верхней границы свода (0.02–0.08em). Роль «мелкая
     капсовая метка» жила на сайте в трёх трекингах и шести кеглях; теперь
     у неё один кегль (--fz-caption) и один разгон. */
  letter-spacing: var(--ls-caps-sm);
  line-height: 1;
  text-transform: uppercase;
  /* Тон на ступень темнее обычного акцента: метка набрана 13.5px, а --brand
     на светлой подложке вывода давал 4.39:1 при норме 4.5 (SC 1.4.3, AA).
     --brand-dark выводит на 5.7:1, разницы в цвете на таком кегле не видно. */
  color: var(--brand-dark);
}

/* Внутри вывода акцент делает штрих, а не цвет. Если кто-то положит .accent
   в .verdict, гасим: два акцентных приёма в одном блоке заказчик снимал. */
.verdict .accent,
.callout .accent { color: inherit; }

/* ---------- Хайлайтер ----------

   Заменяет и .accent, и .underline-stroke внутри выводов. Штрих рисуется
   фоном самого инлайнового элемента, а не псевдоэлементом: тогда при переносе
   строки каждый фрагмент получает свой штрих — как настоящая пометка
   маркером, а не один прямоугольник поверх двух строк.

   Два слоя разной высоты, а не один: верхний светлее и выше, нижний плотнее
   и ниже. Одним слоем штрих выходил ровным прямоугольником и читался как
   выделение мышью; разница плотности между верхом и низом даёт нажим пера.

   Класс называется .marker, а не .mark, чтобы не путать с элементом <mark>:
   у того своя семантика и жёлтая заливка от браузера. Ставится на <span>,
   на 2-4 слова: подсвеченное придаточное целиком снова читается плашкой.

   .underline-stroke — прежнее имя того же приёма, см. блок совместимости. */
.marker,
.underline-stroke {
  padding-inline: .18em;
  margin-inline: -.12em;
  color: inherit;
  background-image:
    linear-gradient(90deg,
      rgba(124, 59, 246, 0)   0,
      rgba(124, 59, 246, .09) .30em,
      rgba(124, 59, 246, .13) 44%,
      rgba(124, 59, 246, .10) 72%,
      rgba(124, 59, 246, .08) calc(100% - .30em),
      rgba(124, 59, 246, 0)   100%),
    linear-gradient(90deg,
      rgba(124, 59, 246, 0)   0,
      rgba(124, 59, 246, .24) .26em,
      rgba(124, 59, 246, .19) 46%,
      rgba(124, 59, 246, .27) 78%,
      rgba(124, 59, 246, .21) calc(100% - .26em),
      rgba(124, 59, 246, 0)   100%);
  background-repeat: no-repeat;
  /* Высота и посадка подобраны на снимках: верхний слой накрывает строчные
     целиком, нижний садится под базовую линию. Значения считаются от
     content-area инлайнового бокса и зависят от метрик Golos Text —
     при смене шрифта пересматриваются одним снимком. */
  background-size: 100% .60em, 100% .28em;
  background-position: 0 68%, 0 94%;
}

/* В режиме высокой контрастности фоны не рисуются — выделение обязано
   остаться хоть чем-то. */
@media (forced-colors: active) {
  .marker,
  .underline-stroke { text-decoration: underline; }
}

/* ---------- Мера вывода на телефоне ----------

   Секции задают меру в em: 20 у знакомства и калькулятора, 22 у дней,
   ниш и продажи, 24 у комьюнити. На 768 это 420/462/462/394/462/504 —
   шесть разных ширин, приём работает.

   На телефоне он отменялся сам собой. Кегль вывода на своём полу (21px),
   а контейнер ужался в 2.2 раза: самая маленькая мера, 20em, это 420px
   против контейнера в 327. Все шесть упирались в min(100%, …) и выходили
   ровно по 327 — то есть ровно шестью одинаковыми прямоугольниками,
   от которых заказчик и уходил («слишком много уже вот этих плашечек»).
   Замер до правки: 335/335/335/335/335/335 на 375, 280×6 на 320,
   390×6 на 430.

   Коэффициент .62 пересаживает секционную меру в мобильный масштаб,
   не трогая ни кегль (он принят), ни сами секции: 20em → 260px,
   22em → 286, 24em → 312 при контейнере 327. Ширины расходятся на 26%,
   ни один вывод не длиннее четырёх строк.

   Из правила выведена полка-постамент (экран 3): её непрозрачная верхушка
   считается в долях ШИРИНЫ БЛОКА и обязана перекрыть срез вырезки робота
   (335px на 375). Сузь блок — и перекрытие уедет вместе с ним, наружу
   вылезет жёсткий горизонтальный срез. Вывод этой секции остаётся во всю
   ширину контейнера, и это не исключение из приёма, а его же правило:
   там на полку что-то опирается, значит её размер задаёт объект, а не текст.

   Граница 767, а не 640: в 640-767 контейнер ещё узкий (24px поля),
   мера в em так же не связывает. */
@media (max-width: 767px) {
  .verdict:not(.verdict--stage),
  .callout:not(.verdict--stage) {
    max-width: min(100%, calc(var(--verdict-measure) * .62));
  }
}

/* ---------- Адаптив ----------
   Ниже 640 колонок в секциях нет, привязывать полку не к чему: прижим
   вправо снимается, все выводы встают по центру. Вылеты полки ужимаются —
   на узком экране блок и так во всю ширину контейнера. */
@media (max-width: 640px) {
  .verdict,
  .callout {
    --verdict-lead:  min(30%, 120px);
    --verdict-trail: min(30%, 120px);
  }

  .verdict--right {
    --verdict-lead:  min(38%, 160px);
    --verdict-trail: min(20%, 80px);
    margin-inline: auto;
  }
}

/* ---------- Совместимость: старые имена ----------

   Шесть секций пока держат в разметке .callout / .callout--center, а на
   экране 6 ещё и .underline-stroke: их переводят на .verdict по одной,
   параллельно. Имена сохранены, наполнение подменено — страница в любой
   момент показывает один приём, а не два разных подряд.

   Что чем стало:
     .callout           → .verdict          (полка света вместо плашки)
     .callout--center   → ничего не делает: выключка по центру теперь базовая
     .underline-stroke  → .marker           (штрих-хайлайтер)
     .callout .icon-badge (размер --xl)     → выводов со значками больше нет
   Правила отсюда удаляются целиком, когда последняя секция перейдёт
   на .verdict, а из разметки уйдут старые имена. */

/* Выключка по центру — теперь базовое поведение .verdict, модификатору
   делать нечего. Правило оставлено пустым намеренно: пусть тот, кто ищет
   класс по файлу, находит объяснение, а не пустоту. */
.callout--center { /* см. .verdict: text-align: center уже там */ }

/* Секции ставили на плашку тень и обрезку. На полке тень висит
   прямоугольником в воздухе (борта и низа у неё нет), а overflow: hidden
   срезает световой омут. Гасим повышенной специфичностью, без !important:
   .callout.callout сильнее секционного .intro__callout. */
.callout.callout {
  box-shadow: none;
  overflow: visible;
}

/* ---------- Иконки ---------- */

.icon {
  width: 24px;
  height: 24px;
  flex: none;
  stroke: currentColor;
  fill: none;
}

/* ---------- Фоновый декор ---------- */
/* Концентрические окружности и точечная сетка с макета — рисуются CSS,
   картинками не тащатся. Декор не кликается и не мешает тексту.

   ГДЕ ставить — не на глаз. Правило целиком в docs/visual-language.md,
   раздел «Декор: два вопроса». Коротко: узор привязан к объекту секции.
   Нет объекта — нет узора; объект-силуэт (вырезка без фона) — кольца
   ореолом по его оси; объект-окно (скриншот, мокап, переписка) — точки
   из-под его внутреннего угла. Свет (.…__glow) — не узор и живёт
   по своему правилу: он под действием, а не за объектом. */

.decor {
  position: absolute;
  pointer-events: none;
  z-index: 0;
}

/* Плотность одна на весь сайт. Раньше базовые .04-.07 стояли на семи
   экранах, а экран 7 держал собственные .08-.16 — «потому что на --bg-tint
   базовые не читаются». Замечание верное, вывод был сделан в одной секции:
   на снимках 1440 кольца экранов 4, 5, 8 не видно вообще, то есть приём
   там формально стоял, а фактически отсутствовал. Раз читается только
   верхний набор — он и становится единственным. */
.decor-rings {
  border-radius: 50%;
  background:
    radial-gradient(circle, transparent 0 38%, rgba(124, 59, 246, .16) 38% 38.6%, transparent 38.6%),
    radial-gradient(circle, transparent 0 58%, rgba(124, 59, 246, .13) 58% 58.5%, transparent 58.5%),
    radial-gradient(circle, transparent 0 78%, rgba(124, 59, 246, .10) 78% 78.4%, transparent 78.4%),
    radial-gradient(circle, transparent 0 98%, rgba(124, 59, 246, .08) 98% 98.4%, transparent 98.4%);
}

/* То же и с точками: было .5 на экранах 3, 4, 11 и .9 на экране 7.
   Остаётся .9 — на нём узор виден, на .5 сетка сливалась с фоном.

   Маска здесь же и общая. Сама по себе .decor-dots — повторяющийся фон
   в прямоугольной коробке, и без маски сетка кончается прямой линейкой
   по всем четырём сторонам; раньше каждая секция гасила её своей маской,
   и одинаковый узор по сайту заканчивался по-разному. Теперь секции
   задают только место и габариты. */
.decor-dots {
  background-image: radial-gradient(rgba(124, 59, 246, .22) 1.5px, transparent 1.5px);
  background-size: 16px 16px;
  opacity: .9;
  -webkit-mask-image: radial-gradient(70% 70% at 50% 50%, #000 0%, rgba(0, 0, 0, .45) 56%, transparent 100%);
          mask-image: radial-gradient(70% 70% at 50% 50%, #000 0%, rgba(0, 0, 0, .45) 56%, transparent 100%);
}

/* Содержимое секции всегда над декором */
.section > .container { position: relative; z-index: 1; }

/* ---------- Разделители ---------- */

.divider { height: 1px; background: var(--line); border: 0; margin: 0; }
.divider--v { width: 1px; height: auto; align-self: stretch; }

/* ---------- Служебное ---------- */

[hidden] { display: none !important; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Плавная прокрутка по якорям объявляется ДО блока с prefers-reduced-motion.
   Раньше она стояла после него: селекторы одинаковой силы (0,0,1), и по
   порядку побеждало правило без медиазапроса — при «уменьшить движение»
   страница всё равно ехала плавно. Проверено: getComputedStyle(html)
   .scrollBehavior отдавал smooth в обоих режимах. */
html { scroll-behavior: smooth; }

/* ---------- Уменьшенное движение ----------

   Свод, раздел «prefers-reduced-motion», формулирует приём точно:
   «тот же таймлайн, длительность 0, без движения по траектории.
   НЕ отключать анимацию совсем — иначе элементы с animation-fill-mode
   останутся в стартовом состоянии».

   Здесь раньше стояло `animation: none !important; transition: none !important`.
   На этой странице оно пока не ломало ничего только потому, что все
   начальные состояния (`opacity: 0`) живут исключительно в кадрах анимаций
   и нигде не объявлены в обычных правилах. Это условие держится на честном
   слове: один `.x { opacity: 0 }` в любой секции — и при выключенном движении
   реплики ниш, ответ на экране 13 или добавленные истории просто исчезают
   с экрана, причём заметит это только тот, у кого настройка включена.

   Рецепт ниже такого сценария не допускает в принципе: анимация
   проигрывается, но за 0.01 мс, и элемент гарантированно оказывается
   в КОНЕЧНОМ кадре. Движения по траектории при этом нет — глазу нечего
   отследить за сотую долю миллисекунды.

   animation-iteration-count: 1 обязателен: без него бесконечные проходы
   (блик кнопки, пульс точки в бейдже, дрейф фона) продолжали бы крутиться
   с частотой кадра. Так они отрабатывают один виток и встают.

   Секции, где начальное состояние особенно важно, дублируют у себя
   `animation: none` — это не противоречие, а страховка на случай выноса
   секции вместе с её файлом. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0s !important;
    transition-duration: .01ms !important;
    transition-delay: 0s !important;
  }
  html { scroll-behavior: auto; }
}

/* ---------- Общие правила адаптива ---------- */

@media (max-width: 640px) {
  .btn { width: 100%; padding-inline: var(--s-5); }
  .btn--sm { width: auto; }

  /* 46 → 48. Роль у вторичной кнопки одна, значений на неё должно быть
     одно, и оно обязано лежать на шкале: 46 в --s-1…--s-10 нет, 48 есть
     (--s-7). Разницу в два пикселя глазом не поймать, но на телефоне
     вторичная кнопка стоит в одном экране с главной (истории, кейсы),
     и высоты 46 / 56 / 64 подряд — это разнобой, который видно.
     64 остаются у первого экрана и финального призыва: их задают
     сами секции, и там это осознанное «крупнее остальных». */
  .btn--sm { min-height: 48px; }

  /* Вся шкала кружков на ступень мельче — отношение иконки сохраняется */
  .icon-badge { width: 48px; height: 48px; }
  .icon-badge .icon { width: 22px; height: 22px; }

  .icon-badge--sm { width: 40px; height: 40px; }
  .icon-badge--sm .icon { width: 18px; height: 18px; }

  .icon-badge--lg { width: 56px; height: 56px; }
  .icon-badge--lg .icon { width: 26px; height: 26px; }

  .icon-badge--xl { width: 64px; height: 64px; }
  .icon-badge--xl .icon { width: 30px; height: 30px; }
}

/* ==========================================================================
   ЛИПКАЯ НИЖНЯЯ ПАНЕЛЬ: ЦЕНА И КНОПКА
   --------------------------------------------------------------------------
   Зачем она есть. Замер прошлого круга: между кнопкой первого экрана
   и следующей нажимаемой кнопкой на 375 лежит 11 322 px — семнадцать
   экранов подряд без единого призыва к действию. Восемь секций (проблема,
   знакомство, три дня, ниши, калькулятор, платформа, как продавать,
   комьюнити) не содержат ни одной кнопки, и это ровно те экраны, где
   человек прогревается и принимает решение. На десктопе так не больно:
   там секции ниже, но шире, и кнопка первого экрана держится в поле зрения
   дольше. Поэтому панель живёт только на узком экране и только на телефоне.

   Разметку панели создаёт js/app.js, а не HTML, и это не лень. Панель
   без скрипта не имеет смысла: показывать её всегда — значит держать
   вторую кнопку на экране цены и в подвале рядом с первой, то есть ровно
   тот дефект, от которого она и заводится. Нет JS — нет панели, страница
   при этом ничего не теряет: собственные кнопки секций на месте.

   ЧТО ЗДЕСЬ ГЛАВНОЕ ДЛЯ ТОГО, КТО БУДЕТ ПРАВИТЬ

   1. Условие видимости — этот медиазапрос, и он ЕДИНСТВЕННЫЙ источник
      правды. Скрипт не повторяет его строкой, а читает у панели
      вычисленный display: пока он none, наблюдатели не заводятся вовсе.
      Меняешь запрос — поведение скрипта меняется само.
   2. Ландшафт телефона (852×393) шире 767, поэтому вторая ветка запроса
      ловит его отдельно — по низкому окну. (pointer: coarse) там обязателен:
      без него в ветку попадает и десктоп с низким окном, а на десктопе
      панели быть не должно вовсе.
   3. Высота панели объявлена переменной --mobile-cta-h и её читает липкий
      итог калькулятора (06-calculator.css). Две липкие полосы на одном
      экране не должны наезжать друг на друга, и записана высота ровно
      в одном месте — здесь.
   ========================================================================== */

/* Ручка для чужих липких слоёв. Ноль по умолчанию: панели нет ни на
   десктопе, ни пока скрипт не решил её показать. */
:root { --mobile-cta-h: 0px; }

.mobile-cta { display: none; }

@media (max-width: 767px),
       (max-height: 500px) and (orientation: landscape) and (pointer: coarse) {

  :root {
    /* Поле панели и высота кнопки в ней — две ручки, из которых считается
       всё остальное. 56 — главная кнопка сайта (.btn { min-height: 56 }):
       заводить панели собственный размер кнопки значит завести четвёртую
       высоту у одной и той же роли. */
    --mobile-cta-pad: var(--s-2);
    --mobile-cta-btn: 56px;
    /* Полная высота панели вместе с кромкой и нижним вырезом.
       max(), а не сложение: на телефоне без выреза инсета равна нулю
       и поле остаётся нашим, на iPhone поле становится вырезом.
       Сложение дало бы 8 + 34 = 42 и раздуло бы панель. */
    --mobile-cta-bar: calc(1px + var(--mobile-cta-pad) + var(--mobile-cta-btn)
                           + max(var(--mobile-cta-pad), env(safe-area-inset-bottom)));
  }

  /* Класс ставит скрипт, когда панель на этом вьюпорте применима.
     Отступ зарезервирован ПОСТОЯННО, а не по факту видимости: иначе высота
     документа менялась бы при каждом появлении панели, и страница дёргалась
     бы под пальцем.

     Запас висит на ПОДВАЛЕ, а не на body. Разница видна только в самом низу
     страницы, и она грубая: подвал тёмный, фон страницы светлый, а панель
     над подвалом намеренно спрятана (см. список блокировщиков в js/app.js) —
     поэтому запас на body давал полосу светлого фона высотой 73px под
     тёмным подвалом, ровно на последнем экране. Замер на 375: подвал
     кончался на 24280, документ — на 24353. На подвале те же 73px просто
     продолжают его заливку, и высота документа не меняется.

     Распорка, а не padding-bottom: своё поле у подвала уже задано
     (14-footer.css), и подменять его отсюда значило бы держать одно
     число в двух файлах. Пустой блок внутри подвала складывается
     с этим полем, а не вместо него. */
  html.has-cta body > footer::after {
    content: '';
    display: block;
    height: var(--mobile-cta-bar);
  }

  /* Второй класс — по факту видимости. Его читает только чужая липкая
     полоса: пока панель на экране, итог калькулятора садится над ней. */
  html.has-cta-bar { --mobile-cta-h: var(--mobile-cta-bar); }

  .mobile-cta {
    display: flex;
    align-items: center;
    gap: var(--s-3);

    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 30;

    padding-top: var(--mobile-cta-pad);
    padding-bottom: max(var(--mobile-cta-pad), env(safe-area-inset-bottom));
    /* Те же поля, что у .container, включая вырезы: панель обязана стоять
       по одной вертикали с содержимым страницы, а в ландшафте с чёлкой
       не заезжать под неё. */
    padding-inline:
      max(var(--container-pad), env(safe-area-inset-left))
      max(var(--container-pad), env(safe-area-inset-right));

    /* Материал взят у липкого итога калькулятора: на странице уже есть
       ровно такая нижняя полоса, и второй её вид означал бы два разных
       прибора на одном месте экрана. */
    background: var(--surface);
    border-top: 1px solid var(--line);
    box-shadow: 0 -6px 20px rgba(27, 22, 51, .06);

    /* Спрятанное состояние. Уход — --t-fast, приход — --t-base ниже:
       по своду вход чуть длиннее выхода, и обе ступени взяты из тех двух,
       что на сайте есть. Третьей длительности не заводим.
       Движение только по transform и opacity — свойства стадии композиции,
       раскладку они не пересчитывают. */
    transform: translateY(100%);
    opacity: 0;
    pointer-events: none;
    transition: transform var(--t-fast), opacity var(--t-fast);
  }

  .mobile-cta.is-shown {
    transform: none;
    opacity: 1;
    pointer-events: auto;
    transition: transform var(--t-base), opacity var(--t-base);
  }

  /* Цена — эхо плашки первого экрана, на ступень тише: там 24/19,
     здесь 19/15. Цифры табличные, иначе при смене суммы строка пляшет. */
  .mobile-cta__price {
    display: flex;
    align-items: baseline;
    gap: var(--s-2);
    /* Уступает кнопке, а не наоборот: у кнопки надпись nowrap и при нехватке
       места она вылезала бы наружу, а цена — одно короткое число, которому
       сжатие не вредит. */
    flex: 0 1 auto;
    min-width: 0;
    font-variant-numeric: tabular-nums;
    font-feature-settings: 'tnum' 1;
  }

  .mobile-cta__now {
    font-size: var(--fz-h3);
    font-weight: 700;
    line-height: 1;
  }

  /* Кнопка занимает всё, что осталось. width: auto снимает .btn { width: 100% }
     из мобильной ступени выше — во флексе оно спорит с flex-basis.
     Поля 16 вместо 32: места в полосе мало, и с полями 32 надпись
     «СОЗДАТЬ ИИ БРО» в кнопку не помещается.

     min-width: max-content вместо прежнего нуля. Надпись стоит nowrap,
     то есть сжатая кнопка не переносит текст, а выпускает его наружу —
     он ложится поверх цены. Так и случилось, когда цена стала длиннее:
     на 320 кнопке осталось 82px при надписи в полтораста. Теперь кнопка
     не сжимается ниже своей надписи ни при какой цене, а уступает цена —
     ей для этого сняли flex: none выше. */
  .mobile-cta .btn {
    flex: 1 1 auto;
    width: auto;
    min-width: max-content;
    min-height: var(--mobile-cta-btn);
    padding-inline: var(--s-4);
    white-space: nowrap;
  }
}

/* Ландшафт телефона: 56 + поля это 14% высоты экрана, слишком дорого.
   Панель сжимается до 52. Ветка отдельная и без (pointer: coarse) —
   переменные, которые она правит, читает только сама панель, а панели
   на десктопе нет вовсе (display: none выше). */
@media (max-height: 500px) and (orientation: landscape) {
  :root {
    --mobile-cta-pad: var(--s-1);
    --mobile-cta-btn: 44px;
  }
  .mobile-cta__now { font-size: var(--fz-small); }

  /* Кнопка перестаёт тянуться. На 852 растянутая во всю ширину она выходит
     800×44 — надпись в 14 знаков посреди пустой плашки читается дёшево.
     Цена слева, действие справа: обычная раскладка нижней панели, и обе
     кромки совпадают с полями страницы. */
  .mobile-cta { justify-content: space-between; }
  .mobile-cta .btn { flex: 0 1 420px; }
}

/* Без движения — без движения. Глобальный блок ниже и так сжимает
   длительность до 0.01 мс, но здесь снимается и сама траектория:
   спрятанная панель не смещена, она просто прозрачна. */
@media (prefers-reduced-motion: reduce) {
  .mobile-cta,
  .mobile-cta.is-shown { transform: none; }
}

/* ==========================================================================
   ПОДСВЕТКА ТАПА
   --------------------------------------------------------------------------
   По умолчанию мобильный WebKit/Blink рисует поверх нажатого элемента
   полупрозрачный прямоугольник (замер: rgba(51, 181, 229, .4) — голубой,
   не из палитры сайта, с прямыми углами поверх скруглений 16 и 24).
   Он появляется В МОМЕНТ КАСАНИЯ и на 300 мс перекрывает наши собственные
   состояния нажатия.

   Снимаем ТОЛЬКО там, где у элемента есть своё видимое нажатие. Это
   не придирка: если у элемента своего состояния нет, серый прямоугольник —
   единственное, чем браузер сообщает «касание засчиталось», и убрать его
   значит сделать элемент мёртвым под пальцем. Поэтому в списке ниже нет
   ни обычных ссылок, ни .tap-safe, ни .pain__more — у них своего нажатия
   пока нет, и подсветка остаётся работать.

   Свойство наследуется, поэтому хватает объявления на самом элементе
   управления — его содержимое подхватит.

     .btn                 просадка 1px + смена заливки и тени (@media hover: none)
     .btn-round           просадка 1px + смена фона
     .card--interactive   просадка 1px + брендовая тень (кнопки ниш, строки FAQ)
     .calculator__total   просадка 1px
     input[type=range]    бегунок идёт за пальцем; одиночный тап по дорожке
                          намеренно не делает ничего, и «засчитывать» нечего
     summary              состояние нажатия заводится тут же, ниже
   ========================================================================== */

.btn,
.btn-round,
button.card--interactive,
a.card--interactive,
.calculator__total,
input[type="range"],
summary {
  -webkit-tap-highlight-color: transparent;
}

/* Нажатие у раскрывающихся строк (истории, кейсы, вопросы).
   Только на тач-устройстве: на десктопе у этих строк своё поведение
   и вид, принятый заказчиком, и трогать его нельзя.
   Приглушение, а не смещение: строка раскрывашки бывает во всю ширину
   экрана, и сдвиг такого блока под пальцем читается сбоем вёрстки,
   а изменение прозрачности видно за пределами пятна касания. */
@media (hover: none) {
  summary { transition: opacity var(--t-fast); }
  summary:active { opacity: .62; }
}

/* ===========================================================================
   ЮРИДИЧЕСКИЕ СТРАНИЦЫ (согласие, оферта)
   --------------------------------------------------------------------------
   Лежат здесь, а не отдельным файлом в css/sections/: сборка архива
   (build.ps1) папку css/ не копирует, стили склеиваются в один бандл
   из подключённого к главной. Отдельный файл в бандл бы не попал,
   и в архиве эти страницы остались бы без оформления.
   ========================================================================== */
.legal {
    padding: var(--s-8) 0 var(--s-10);
  }

  .legal__inner {
    /* Ширина колонки под чтение: примерно 70 знаков в строке.
       Юридический текст читают подряд, а не сканируют, и длинная строка
       здесь дороже, чем на лендинге. */
    max-width: 46em;
  }

  .legal__back {
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
    margin-bottom: var(--s-6);
    color: var(--muted);
    font-size: var(--fz-small);
  }

  .legal__back:hover { color: var(--brand); }

  .legal__title {
    margin-bottom: var(--s-2);
  }

  .legal__sub {
    margin-bottom: var(--s-7);
    color: var(--muted);
  }

  .legal h2 {
    margin-top: var(--s-7);
    margin-bottom: var(--s-3);
    font-size: var(--fz-h4);
    line-height: var(--lh-title);
  }

  .legal p { margin-bottom: var(--s-3); }

  .legal ul {
    margin: 0 0 var(--s-3);
    padding-left: var(--s-5);
  }

  .legal li { margin-bottom: var(--s-2); }

  /* Реквизиты оператора: строки идут парами «что — значение», поэтому
     не абзацем сплошняком, а списком описаний. */
  .legal__req {
    margin: 0 0 var(--s-3);
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: var(--s-2) var(--s-4);
  }

  .legal__req dt { color: var(--muted); }
  .legal__req dd { margin: 0; }

  @media (max-width: 560px) {
    .legal__req { grid-template-columns: 1fr; gap: 0 0; }
    .legal__req dt { margin-top: var(--s-2); }
  }

/* ==========================================================================
   ФЛАГ СТРАНЫ У ЦЕНЫ
   --------------------------------------------------------------------------
   Эмодзи, а не картинка: флаг не должен тянуть за собой запрос и слой
   кэширования ради двух десятков пикселей. На системах без флаговых эмодзи
   (часть сборок Windows) вместо флага покажутся две буквы кода страны —
   это не поломка, а честный запасной вид, и рядом всё равно стоит код валюты.

   Кегль привязан к строке, а не задан числом: флаг обязан быть ростом
   с цифры, рядом с которыми стоит, в любом месте страницы.
   ========================================================================== */
/* ФЛАГ СТРАНЫ У ЦЕНЫ

   Путь сюда занял три подхода, и первые два не работают.

   Эмодзи-флаг не годится: в Windows нет шрифта с флагами вовсе, и система
   рисует вместо картинки две буквы кода страны. Рядом с кодом валюты это
   читалось как «KZ KZT» — удвоение вместо флага.

   Спрайт значков тоже не годится, хотя все остальные иконки живут именно
   в нём. Внутри флага есть обрезка и повторно используемые элементы через
   url(#…), а при подключении спрайта извне такие ссылки не разрешаются:
   флаг обрезался «в ноль» и пропадал совсем.

   Поэтому отдельными файлами и обычным <img>: картинка загружается как
   самостоятельный документ, и всё внутри неё работает.

   ОДНА КОРОБКА 2:1 И object-fit: cover. У флагов разные пропорции: Казахстан
   и Беларусь 2:1, Россия 3:2. Сначала стоял contain — он вписывал флаг целиком,
   и российский при той же высоте выходил на четверть уже остальных. Рядом
   в столбце это читалось как «российский почему-то меньше».

   cover заполняет коробку целиком, и все три становятся одинаковыми
   прямоугольниками. Подрезается при этом только российский: у двух других
   пропорции ровно 2:1, и они попадают в коробку без изменений. Обрезка идёт
   сверху и снизу, то есть по белой и красной полосам, — порядок и цвета
   триколора сохраняются, а на трёх десятках пикселей смещение полос
   не читается. Резать по горизонтали было бы нельзя: у Казахстана и Беларуси
   орнамент прижат к левому краю, его бы и срезало. */
