/* Вся вёрстка одностраничника — типографика, кнопки, header и блоки
   (hero, about, mission, principles) — в одном файле. Лендинг один,
   блоков немного: раздельные файлы на каждый блок только добавляли
   ссылки в layout.pug, не помогая ориентироваться.

   768px — единая точка перелома mobile/desktop для всего проекта,
   секции ниже используют её каждая для своих layout-правил. */

/* Цвет выделения текста мышью — фирменный зелёный (--color-accent, тот же,
   что в акцентах по всему сайту), 10% непрозрачности. ::-moz-selection
   отдельно: старый Firefox не понимал общий ::selection без префикса
   (объявлять оба в одном правиле через запятую нельзя — невалидный
   селектор целиком игнорируется всеми браузерами). */
::selection {
  background-color: rgba(var(--color-positive), .15);
  color: rgb(var(--color-positive));
}

::-moz-selection {
  background-color: rgba(var(--color-positive), 0.3);
}

/* ============================================================
   Типографика
   ============================================================
   Переиспользуемые текстовые стили из шкалы Figma (tokens/typography.css).
   Компонентные классы отвечают только за layout/цвет/отступы — начертание,
   размер и интерлиньяж задаются одним из этих классов и не повторяются
   по месту использования. Мобильный размер каждого стиля (`-s` токен)
   тоже задаётся один раз здесь, в media query ниже — не дублировать его
   по компонентам, которые применяют класс.

   line-height каждого стиля — множитель * --density (tokens/scales.css):
   при density=1 это ровно значение из Figma; меняя один --density,
   можно синхронно поджать/распустить интерлиньяж всей типографики,
   сохраняя относительную разницу между стилями (h1 плотнее footnote
   и остаётся плотнее при любом density). */

.t-ui {
  font-family: var(--font-ui);
  font-size: var(--fs-body);
  line-height: calc(var(--density) * 0.95);
}

.t-h4 {
  font-family: var(--font-ui);
  font-size: var(--fs-h4);
  line-height: calc(var(--density) * 1.05);
  letter-spacing: -0.01em;
}

.t-h1 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-h1);
  line-height: calc(var(--density) * 0.85);
}

.t-h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-h2);
  line-height: calc(var(--density) * 0.85);
}

.t-h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: calc(var(--density) * 0.85);
}

.t-lead {
  font-family: var(--font-ui);
  font-size: var(--fs-lead);
  line-height: var(--density);
  letter-spacing: -0.01em;
}

.t-capitel {
  font-family: var(--font-ui);
  font-weight: 800;
  font-size: var(--fs-capitel);
  line-height: var(--density);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.t-body {
  font-family: var(--font-text);
  font-size: var(--fs-body);
  line-height: calc(var(--density) * 1.1);
  letter-spacing: -0.01em;
}

/* body.S — самостоятельный Figma-стиль (16px), не "мобильный размер
   .t-body": в Figma нет отдельного mobile/body.S, потому что он
   совпал бы с mobile/body (тоже 16px) — поэтому здесь один флоат-
   размер без media-переключения, верно для обоих брейкпоинтов. */
.t-body-s {
  font-family: var(--font-ui);
  font-size: var(--fs-body-s); /* 16px */
  line-height: calc(var(--density) * 1.15);
  letter-spacing: -0.01em;
}

.t-factoid {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-factoid);
  line-height: calc(var(--density) * 0.85);
}

/* factoid.S — отдельный именованный стиль в Figma, не «мобильный
   .t-factoid»: свои значения на обоих брейкпоинтах (64 десктоп /
   48 мобилка), не выводятся друг из друга. */
.t-factoid-s {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-factoid-s); /* 64px */
  line-height: calc(var(--density) * 0.85);
}

.t-footnote {
  font-family: var(--font-ui);
  font-size: var(--fs-footnote);
  line-height: calc(var(--density) * 1.05);
  letter-spacing: -0.01em;
}

/* font-size здесь не переопределяется — токены --fs-* сами меняют
   значение на этом брейкпоинте (typography.css), .t-h1 и т.д. читают
   тот же var(--fs-h1) что и на десктопе. Ниже — только то, что не
   завязано на токен шкалы (line-height процентовки отличаются от
   десктопных сами по себе, это отдельные величины, не часть шкалы). */
@media (max-width: 768px) {
  .t-h1 {
    line-height: calc(var(--density) * 0.95);
  }

  .t-h2 {
    line-height: calc(var(--density) * 0.95);
  }

  .t-h3 {
    line-height: calc(var(--density) * 0.95);
  }

  .t-lead {
    line-height: calc(var(--density) * 1.15);
  }

  .t-body {
    line-height: calc(var(--density) * 1.15);
  }
}

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

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 0.5em; /* масштабируется вместе с шрифтом кнопки (.t-ui) */
  padding: 0.667em 1.167em; /* 16px / 28px при 24px font-size */
  text-decoration: none;
  border: none;
  cursor: pointer;
}

.button--light {
  background: rgb(var(--color-cta-bg));
  color: rgb(var(--color-cta-text));
  transition: background 0.5s ease, color 0.5s ease, opacity 0.5s ease;
}

.button--light:hover {
  background: rgb(var(--color-positive));
  color: rgb(var(--color-white));
  transition-duration: 0s;
}

.button--ghost {
  background: rgba(var(--color-white), 0.12);
  color: rgb(var(--color-text));
  padding: 0.333em 0.667em; /* 8px / 16px при 24px font-size */
}

/* Универсальный hover для стеклянных пилюль-ссылок ("Все отчеты",
   "Детальная статистика продукта", "Отчеты по продукту" и т.п.) —
   один список селекторов через запятую, а не отдельное правило под
   каждый класс. У .product-card__badge на светлой карточке (--light)
   заливка другого цвета — там свой оверрайд ниже, у самого badge. */
.button--ghost,
.cruise__cta,
.product-card__badge,
.reports__cta,
.reports-row__download,
.reports__tab,
.reports__year,
.contact__product {
  transition: background 0.5s ease;
}

/* Только устройства с настоящей мышью — на тач :hover навешивается по
   тапу и без mouseleave "залипает" до следующего тапа где-то ещё
   (поймано вживую на .contact__product, но причина общая для всей
   группы выше). */
@media (hover: hover) and (pointer: fine) {
  .button--ghost:hover,
  .cruise__cta:hover,
  .product-card__badge:hover,
  .reports__cta:hover,
  .reports-row__download:hover,
  .reports__tab:not(.is-active):hover,
  .reports__year:hover,
  .contact__product:not(.is-active):hover {
    background: rgba(var(--color-white), 0.2);
    transition-duration: 0s;
  }
}

/* Переключение активной кнопки в табах/сегментах (main.js) должно быть
   мгновенным по клику в обе стороны — не тем же плавным hover-переходом.
   Класс навешивается на миг кликом и снимается на следующем кадре. */
.no-transition {
  transition-duration: 0s !important;
}

/* Фиксированный бокс под иконку: сама иконка позиционируется абсолютно
   внутри и не может растянуть контейнер по высоте/ширине. */
.icon-frame {
  position: relative;
  display: block;
  width: 2rem; /* 32px */
  height: 2rem;
  flex-shrink: 0;
}

.icon-frame svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* ============================================================
   Контейнер контента
   ============================================================
   Общий на все блоки контейнер: ограничивает ширину контента 1512px
   (десктопный канвас Figma) и центрирует. Сам блок (section.foo)
   отвечает за фон и боковые отступы, .container — только за предельную
   ширину; per-block layout (flex/grid) — не через новый класс на
   .container, а через селектор `section.foo > .container { ... }`. */

.container {
  width: 100%;
  max-width: 94.5rem; /* 1512px */
  margin: 0 auto;
}

/* ============================================================
   Header
   ============================================================ */

/* padding-top — своё значение плюс env(safe-area-inset-top): с
   viewport-fit=cover (layout.pug) сайт красит область под чёлкой/
   островком сам (хедер fixed на top:0 — теперь это истинный край экрана,
   не безопасная зона), поэтому лого/навигацию/кнопку телеграма нужно
   явно отодвинуть вниз из-под них самих — иначе легли бы прямо под вырез.
   safe-area-inset-top на обычных экранах (без чёлки/острова) равен 0 —
   доп. слагаемое не задание, просто нет. */
.site-header {
  max-width: 94.5rem; /* 1512px */
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: calc(var(--density) * 0.75rem) calc(var(--density) * 1.5rem); /* 12 24 */
  padding-top: calc(var(--density) * 0.75rem + env(safe-area-inset-top, 0px));
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
}

/* Скрим под хедером: контент при скролле проходит под ним и гаснет,
   а не упирается в жёсткую кромку — высота завязана на реальную
   высоту хедера (--header-h), не на магическое число. */
.site-header::before {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: calc(var(--header-h, 0px) * 1.75);
  background-image: linear-gradient(
    180deg,
    rgb(var(--color-bg)) 0%,
    rgba(var(--color-bg), 0.734694) 14.29%,
    rgba(var(--color-bg), 0.510204) 28.57%,
    rgba(var(--color-bg), 0.326531) 42.86%,
    rgba(var(--color-bg), 0.183673) 57.14%,
    rgba(var(--color-bg), 0.0816327) 71.43%,
    rgba(var(--color-bg), 0.0204082) 85.71%,
    rgba(var(--color-bg), 0) 100%
  );
  z-index: -1;
  pointer-events: none;
}

/* Прогрессивный блюр поверх тёмного скрима (::before выше) — портирован
   с алгоритма компонента ProgressiveBlur из inspira-ui (Vue-либа, но сам
   приём чистый CSS): 8 слоёв, у каждого — не хаотичные проценты, а
   "скользящее окно" маски шириной в 2 сегмента (segment = 1/9 высоты
   зоны), сдвинутое от слоя к слою ровно на 1 сегмент, и blur слоя N
   растёт ЛИНЕЙНО — (N-1) * --header-blur-intensity, а не степенями
   двойки, как в более грубом "классическом" рецепте. Слои друг над
   другом с backdrop-filter реально суммируют размытие (каждый следующий
   слой размывает уже размытое предыдущими) — в сумме получается гладкий
   градиент силы. direction "top" в оригинале — у нас 0deg (снизу вверх):
   слой 8 (макс. blur) держит непрозрачность до самого верха зоны (у
   хедера сильнее всего), слой 1 (blur 0) — у нижнего края (эффект
   гаснет в ноль до того, как дойдёт до контента). Сила — 0.8px за слой
   (--header-blur-intensity), подобрано и подтверждено пользователем. */
.site-header__blur {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: calc(var(--header-h, 0px) * 1.5); /* та же зона, что у ::before-скрима */
  z-index: -1;
  pointer-events: none;
}

.site-header__blur-layer {
  position: absolute;
  inset: 0;
}

.site-header__blur-layer:nth-child(1) {
  backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 0));
  -webkit-backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 0));
  mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 11.11%, rgba(0, 0, 0, 1) 22.22%, rgba(0, 0, 0, 0) 33.33%);
}

.site-header__blur-layer:nth-child(2) {
  backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 1));
  -webkit-backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 1));
  mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 11.11%, rgba(0, 0, 0, 1) 22.22%, rgba(0, 0, 0, 1) 33.33%, rgba(0, 0, 0, 0) 44.44%);
}

.site-header__blur-layer:nth-child(3) {
  backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 2));
  -webkit-backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 2));
  mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 22.22%, rgba(0, 0, 0, 1) 33.33%, rgba(0, 0, 0, 1) 44.44%, rgba(0, 0, 0, 0) 55.56%);
}

.site-header__blur-layer:nth-child(4) {
  backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 3));
  -webkit-backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 3));
  mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 33.33%, rgba(0, 0, 0, 1) 44.44%, rgba(0, 0, 0, 1) 55.56%, rgba(0, 0, 0, 0) 66.67%);
}

.site-header__blur-layer:nth-child(5) {
  backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 4));
  -webkit-backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 4));
  mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 44.44%, rgba(0, 0, 0, 1) 55.56%, rgba(0, 0, 0, 1) 66.67%, rgba(0, 0, 0, 0) 77.78%);
}

.site-header__blur-layer:nth-child(6) {
  backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 5));
  -webkit-backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 5));
  mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 55.56%, rgba(0, 0, 0, 1) 66.67%, rgba(0, 0, 0, 1) 77.78%, rgba(0, 0, 0, 0) 88.89%);
}

.site-header__blur-layer:nth-child(7) {
  backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 6));
  -webkit-backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 6));
  mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 66.67%, rgba(0, 0, 0, 1) 77.78%, rgba(0, 0, 0, 1) 88.89%, rgba(0, 0, 0, 0) 100%);
}

.site-header__blur-layer:nth-child(8) {
  backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 7));
  -webkit-backdrop-filter: blur(calc(var(--header-blur-intensity, 0.8px) * 7));
  mask-image: linear-gradient(0deg, rgba(0, 0, 0, 0) 77.78%, rgba(0, 0, 0, 1) 88.89%, rgba(0, 0, 0, 1) 100%, rgba(0, 0, 0, 0) 111.11%);
}

.site-header__nav {
  display: flex;
  align-items: center;
  gap: calc(var(--density) * 1rem); /* 48px */
}

/* Регистрируем как анимируемые числа (не просто custom property) —
   иначе transition на --nav-fade-left/right (.nav-links ниже) не
   сработает, браузер не умеет плавно интерполировать произвольную
   строку, только типизированное значение. */
@property --nav-fade-left {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

@property --nav-fade-right {
  syntax: "<number>";
  inherits: false;
  initial-value: 1;
}

.logo {
  display: flex;
  align-items: center;
  /* gap не тут — на .logo__wordmark, margin-left: flex-gap не схлопывается
     сам, когда сосед схлопывается в 0 (см. .site-header .button--light
     выше, тот же приём и та же причина). */
}

/* Текстовая часть логотипа (тот же файл, что и в футере, .site-footer__wordmark)
   — видна только у самого верха страницы, прячется, как только начали
   скроллить (initLogoWordmarkVisibility, main.js), и появляется обратно
   при возврате наверх. opacity и max-width/margin анимируются вместе,
   одной transition — соседний пункт меню должен подъезжать плавно и
   синхронно с исчезновением, а не прыгать скачком, когда схлопывание
   места отделено от fade. object-fit: contain обязателен: у img высота
   фиксирована, а width:auto, и без object-fit браузер при уменьшении
   max-width растягивает/сжимает сам файл по горизонтали (видно как
   схлопывание превращается в "сплющивание" текста) — contain вместо
   этого просто обрезает лишнее по ширине, сохраняя пропорции, а
   поскольку это происходит одновременно с fade в 0, обрезка не успевает
   бросаться в глаза. */
.logo__wordmark {
  height: 1.625rem; /* 26px — из Figma лого-лок-апа нет, свой подбор под кегль хедера */
  width: auto;
  max-width: 14rem;
  object-fit: contain;
  object-position: left center;
  margin-left: calc(var(--density) * 0.625rem); /* 10px */
  /* Буквы в самом файле сидят не по центру своего вьюбокса (0 0 380 75) —
     сверху пусто больше, чем снизу (нижняя точка "g" почти касается низа),
     поэтому чисто геометрическое центрирование бокса с иконкой визуально
     сдвигает текст выше строки. Небольшой сдвиг вниз компенсирует это на
     глаз — не точный расчёт, подбор по скриншоту. */
  margin-top: 0.1875rem; /* 3px */
  opacity: 1;
  overflow: hidden;
  transition: opacity 0.3s ease, max-width 0.3s ease, margin-left 0.3s ease;
}

.logo__wordmark.is-hidden {
  opacity: 0;
  max-width: 0;
  margin-left: 0;
}

/* Синхронный инлайн-скрипт в head (layout.pug) ставит is-scrolled на html
   раньше первой отрисовки, если страницу открыли/перезагрузили уже
   проскроленной — без этого элемент долю секунды виден в состоянии по
   умолчанию (opacity:1), пока main.js в конце body не навесит is-hidden,
   и получается видимый скачок. Значения те же, что у .is-hidden, поэтому
   когда main.js позже добавит его на сам элемент — переход не запустится
   (состояние уже совпадает), это чисто для самого первого кадра. */
html.is-scrolled .logo__wordmark {
  opacity: 0;
  max-width: 0;
  margin-left: 0;
}

/* Общая полупрозрачная "таблетка" под все 4 пункта сразу (не по одной
   на каждый) — так в Figma: фон и radius на группе, пункты внутри
   встык, без gap. */
.nav-links {
  display: flex;
  align-items: center;
  /* background: rgba(var(--color-bg), 0.48); */
  /* backdrop-filter: blur(24px); */
  border-radius: var(--radius);
}

.nav-links a {
  display: block;
  padding: calc(var(--density) * 0.75rem) calc(var(--density) * 1.25rem); /* 12 20 */
  border-radius: var(--radius);
  text-decoration: none;
  white-space: nowrap;
  color: rgb(var(--color-text), .5);
  background: rgba(var(--color-text), 0);
  transition: color 0.5s ease, background 0.5s ease;
}

.nav-links a:hover,
.nav-links a.is-active {
  color: rgb(var(--color-white));
  background: rgba(var(--color-text), 0.1);
  transition-duration: 0s;
}

.site-header__actions {
  display: flex;
  align-items: center;
  /* gap между .nav-telegram и .button--light — не тут, а margin-left самой
     кнопки (см. ниже): она схлопывается при скрытии, а flex-gap так не
     умеет — соседний 0-ширины элемент всё равно резервирует gap. */
}

.nav-telegram {
  display: flex;
  align-items: center;
  gap: calc(var(--density) * 0.5rem); /* 8px */
  padding: calc(var(--density) * 0.75rem) calc(var(--density) * 1.25rem); /* 12 20 */
  border-radius: var(--radius-pill);
  text-decoration: none;
  transition: color 0.5s ease;
}

.nav-telegram:hover {
  color: rgb(var(--color-white));
  transition-duration: 0s;
}

/* Фрейм иконки по умолчанию 32px фиксированно (icon-frame) — здесь он
   рядом с текстом .t-ui, поэтому подгоняем под кегль строки через em,
   чтобы пилюля была той же высоты, что .nav-links a. */
.nav-telegram .icon-frame {
  width: 1em;
  height: 1em;
}

/* Hover — не просто подсветка, а смена ассета на цветной бренд-значок
   Telegram: два <use> друг на друге, кроссфейд по opacity. */
.nav-telegram .icon-frame svg {
  transition: opacity 0.5s ease;
}

.nav-telegram .icon-frame svg:last-child {
  opacity: 0;
}

.nav-telegram:hover .icon-frame svg {
  transition-duration: 0s;
}

.nav-telegram:hover .icon-frame svg:first-child {
  opacity: 0;
}

.nav-telegram:hover .icon-frame svg:last-child {
  opacity: 1;
}

/* Кнопка "Разбор портфеля" в хедере ведёт на форму заявки (#contact) —
   когда сама форма уже на экране (и ниже, до футера), кнопка-дубль в
   хедере не нужна, прячем (initHeaderCtaVisibility, main.js). Схлопывается
   по ширине, а не просто гаснет прозрачностью — тот же приём и по той же
   причине, что и у .reports-row__download (см. main.css): паддинг и
   margin-left (замена gap у .site-header__actions) тоже едут к 0, иначе
   скрытая кнопка всё равно резервирует место. --header-cta-w — реальная
   ширина кнопки, измеренная JS (initHeaderCtaVisibility, main.js) — текст
   "Разбор портфеля" не переносится (white-space:nowrap), а число вместо
   auto нужно, чтобы max-width вообще было чем анимировать. На мобилке
   не актуально — там .site-header__actions скрыт целиком (см. ниже). */
.site-header .button--light {
  max-width: var(--header-cta-w, 12rem);
  margin-left: calc(var(--density) * 0.625rem); /* 10px, был gap фута */
  overflow: hidden;
  white-space: nowrap;
  transition: background 0.75s ease, color 0.75s ease, max-width 0.5s ease, padding 0.5s ease, margin-left 0.5s ease, opacity 0.5s ease;
}

/* Та же специфичность, что у .button--light:hover (main.css выше) —
   .site-header .button--light как более поздний по файлу выигрывал
   transition-duration целиком, и инстант-ин по наведению пропадал
   (background/color тоже ехали 0.5s внутрь, не мгновенно — поймано
   вживую). Возвращаем инстант явно, тем же приёмом, что и у
   .hero__reports > .button--ghost выше. */
.site-header .button--light:hover {
  transition-duration: 0s;
}

.site-header .button--light.is-hidden {
  max-width: 0;
  min-width: 0; /* иначе flex-item авто-минимум перебивает max-width:0 */
  padding-left: 0;
  padding-right: 0;
  margin-left: 0;
  opacity: 0;
  pointer-events: none;
}

@media (max-width: 768px) {
  .site-header {
    padding: calc(var(--density) * 0.75rem) calc(var(--density) * 0.375rem); /* 12 6 */
    /* padding-top отдельно, после шорткода — тот сбрасывает и его тоже
       (все брейкпоинты с чёлкой/островком мобильные, это тот самый
       случай). См. комментарий у .site-header выше по файлу. */
    padding-top: calc(var(--density) * 0.75rem + env(safe-area-inset-top, 0px));
  }

  .logo {
    padding: 0 calc(var(--density) * 0.375rem); /* 0 6 — вертикально центрируется флексом строки */
  }

  /* Чуть компактнее, чем на десктопе — на мобилке и так тесно (иконка +
     прокручиваемая лента ссылок), но сам вордмарк остаётся. */
  .logo__wordmark {
    height: 1.375rem; /* 22px */
    max-width: 9rem;
  }

  /* Без бургера: nav-links остаётся видимым и на мобилке — сам является
     скролл-контейнером (тот же приём, что .news__cards: overflow-x:auto
     прямо на flex-ряду, отдельная обёртка не нужна), пункты пролистывает
     сам пользователь свайпом. Активный пункт (initNavLinksScrollspy,
     main.js — IntersectionObserver по секциям about/products/reports/news)
     подсвечивается и подтягивает ленту к себе автоскроллом, если только
     пользователь не держит её руками прямо сейчас — тогда не мешаем, и
     возвращаем ленту к активному пункту, когда он отпустит (не тапнув
     по ссылке). site-header__actions: telegram остаётся, CTA "Разбор
     портфеля" прячем — до формы/футера всё равно можно доскроллить
     страницей, дублировать кнопку в хедере на мобилке не нужно. */
  .site-header .button--light {
    display: none;
  }

  .site-header__nav {
    flex: 1;
    min-width: 0; /* иначе flex не даёт сжаться уже .nav-links */
  }

  .nav-links {
    flex: 1;
    min-width: 0;
    padding: 0px 1rem;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    white-space: nowrap;
    scroll-behavior: smooth;
    /* Затухание по краям — тот же приём переменных, что у .news__cards:
       1 = край гаснет, 0 = полностью непрозрачно. Слева гасить нечего,
       если список ещё не проскроллен вообще (scrollLeft <= 0 — там
       собственное начало первого пункта, не обрыв слова); справа — как
       только дошли до последнего пункта ("новости") — визуально это уже
       "конец", даже если последний пункт не доехал до центра ленты.
       Переключает initNavLinksScrollspy, main.js. */
    --nav-fade-w: 1.5rem;
    --nav-fade-left: 0;
    --nav-fade-right: 1;
    transition: --nav-fade-left 0.3s ease, --nav-fade-right 0.3s ease;
    mask-image: linear-gradient(
      to right,
      rgba(var(--color-bg), calc(1 - var(--nav-fade-left))) 0,
      rgba(var(--color-bg), calc(1 - var(--nav-fade-left) * 0.6)) calc(var(--nav-fade-w) * 0.4),
      rgb(var(--color-bg)) var(--nav-fade-w),
      rgb(var(--color-bg)) calc(100% - var(--nav-fade-w)),
      rgba(var(--color-bg), calc(1 - var(--nav-fade-right) * 0.6)) calc(100% - (var(--nav-fade-w) * 0.4)),
      rgba(var(--color-bg), calc(1 - var(--nav-fade-right))) 100%
    );
  }

  .nav-links::-webkit-scrollbar {
    display: none;
  }

  .nav-links a {
    padding: calc(var(--density) * 0.5rem) calc(var(--density) * 0.625rem);
    font-size: 1.25rem; /* 20px — .t-ui крупнее для этой ленты, по просьбе, не общий токен */
  }

  /* На мобилке от кнопки остаётся только иконка — текст "Telegram" не
     помещается рядом с лентой меню. Бокс иконки заметно больше "сырой"
     высоты .logo (31px) — не 1 в 1: telegram-icon (icons.svg) это КРУГ
     с fill-opacity:0.24, а не сплошная фигура на всю коробку, как у
     лого — при одинаковой высоте бокса круг оптически читается мельче
     и бледнее (те же 31px давали на глаз разные по размеру элементы,
     см. чат), поэтому размер иконки скорректирован на глаз, а не взят
     из Figma напрямую. */
  .nav-telegram span:last-child {
    display: none;
  }

  .nav-telegram {
    padding: calc(var(--density) * 0.75rem);
  }

  .nav-telegram .icon-frame {
    width: 2.375rem; /* 38px */
    height: 2.375rem;
  }
}

/* ============================================================
   Hero
   ============================================================ */

/* Общий контейнер для фона + хедера + hero: фон — position:absolute
   внутри него, поэтому лежит и под хедером тоже, а не только под hero. */
.hero-wrap {
  position: relative;
  overflow: hidden;
}

.hero {
  position: relative;
  /* top — не токен density/gap, а реальная высота фикс-хедера, см. main.js */
  padding: var(--header-h, 0px) calc(var(--density) * 1.5rem) calc(var(--density) * 3rem); /* .. 24 48 */
}

/* Один и тот же файл на десктопе и мобилке — разница только в размере/
   позиции контейнера (в точности как в Figma), кроп решает object-fit.
   Геометрия фона — точное значение из Figma, density тут не участвует. */
.hero__bg {
  position: absolute;
  inset: 0;
  height: 43.625rem; /* 698px */
  z-index: 0;
  overflow: hidden;
}

/* В отличие от mission/reports, у hero заливка в Figma не повёрнута
   на 90°, а просто отражена по вертикали (scaleY), плюс сверху лежит
   отдельный радиальный градиент-виньетка (свечение по центру сверху,
   затемнение к краям и низу) — воспроизводим его чистым CSS-градиентом,
   без второй картинки. Размеры градиента в % от .hero__bg, поэтому
   форма сохраняется и на мобильной высоте блока. */
.hero__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scaleY(-1);
}

.hero__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(50% 100% at 50% 0%, rgba(var(--color-bg), 0.4) 0%, rgb(var(--color-bg)) 100%);
}

.hero > .container {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 4.5rem); /* 72px */
  padding-top: calc(var(--density) * 4.5rem); /* 72px */
}

.hero__intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.hero__title {
  text-align: center;
  max-width: 49rem; /* 784px */
}

.hero__subtitle {
  color: rgb(var(--color-text-secondary));
  text-align: center;
  max-width: 49rem; /* 784px */
  padding-top: calc(var(--density) * 0.9375rem); /* 15px — как в Фигме, поверх gap контейнера */
}

/* В Figma карточки отчётов и кнопка "Все отчеты" — один контейнер
   (items-center, без stretch: кнопка не тянется, ширину даёт себе сама
   только .hero__stats через свой w-full). Общее и для десктопа, и для
   мобилки — отличается только gap. */
.hero__reports {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 1.5rem); /* 24px */
  width: 100%;
}

.hero__stats {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap-xs);
  width: 100%;
  border-radius: var(--radius);
  overflow: hidden;
}

/* Радиус — на .hero__stats (overflow:hidden), не на самих карточках:
   так обе всегда выглядят единой пилюлей, а не парой отдельных углов. */
.stat-card {
  background: rgb(var(--color-surface));
  padding: calc(var(--density) * 1.5rem); /* 24px */
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-height: 24.3125rem; /* 389px */
}

.stat-card__head {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.25rem); /* 4px */
}

.stat-card__title {
  display: flex;
  align-items: center;
  gap: calc(var(--density) * 0.5rem); /* 8px */
}

.stat-card__badge {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--density) * 0.375rem); /* 6px */
}

a {
  text-decoration: underline;
  text-decoration-skip-ink: none;
  text-decoration-color: rgba(var(--color-text-secondary), 0.2);
  text-decoration-thickness: .2rem;
  transition: color 0.5s ease, text-decoration-color 0.5s ease;
  text-underline-offset: .05em;
}

a:hover {
  color: rgb(var(--color-white));
  text-decoration-color: rgb(var(--color-white));
  transition-duration: 0s;
}

.stat-card__period {
  color: rgb(var(--color-text));
}

.stat-card__chart {
  margin-top: calc(var(--density) * 1.5rem); /* 24px */
}

.stat-card__chart img,
.stat-card__chart svg {
  width: 100%;
  display: block;
}

/* SVG по умолчанию обрезает содержимое по своему viewBox (браузерный
   overflow:hidden на корневом <svg>, не контейнер) — точки первого/
   последнего месяца стоят ровно на краю viewBox (cx=0 и cx=width,
   sparklines.js), у них половина круга уходила за границу и срезалась. */
.stat-card__sparkline {
  overflow: visible;
}

.stat-card__sparkline-cruise {
  stroke: rgb(var(--color-positive));
}

.stat-card__sparkline-imoex {
  stroke: rgba(var(--color-text), 0.25);
}

/* Тонкая всегда видимая полоска — сам ориентир, где на линии месяц, во
   всю высоту графика (y1/y2 = 0/sparklines.height, index.pug — правка
   пользователя, была короткой чёрточкой вокруг точки). <line>, не
   <rect>: прямоугольник в координатах viewBox растягивался вместе с
   preserveAspectRatio="none" у самого svg — график намного шире, чем
   выше, и "узкая вертикальная" фигура превращалась в "широкую
   горизонтальную", сливаясь с линией в один пунктир, а не стоя поперёк
   неё. vector-effect="non-scaling-stroke" (index.pug) держит толщину
   обводки постоянной в экранных px независимо от этого неравномерного
   масштаба — остаётся тонкой и вертикальной.
   Цвет — один нейтральный (--color-text-secondary) на всех, не под
   свою линию (было по-разному у cruise/imoex — само по себе не
   мешало, но на глаз "решётка" из разноцветных линий плюс ещё и сама
   линия графика читалась пёстро). Про запас низкая stroke-opacity —
   на всю высоту сплошным цветом (и тем более на 2 цвета) полоски
   перегружали бы график, выглядели агрессивной сеткой. pointer-events:
   none — наводиться должны на точку-мишень поверх неё, не на маркер.
   При активации — не рост (уже во всю высоту, расти некуда), а просто
   становится непрозрачной. */
.stat-card__sparkline-dot {
  stroke: rgb(var(--color-text-secondary));
  stroke-width: 1;
  stroke-opacity: 0.2;
  pointer-events: none;
  transition: stroke-opacity 0.75s ease;
}

.stat-card__sparkline-dot.is-active {
  stroke-opacity: 1;
  transition-duration: 0.25s; /* появление быстрее затухания (0.75s, базовое правило выше) — как в .reports-row */
}

.stat-card__sparkline-point {
  fill: transparent;
  cursor: pointer;
}

/* Кастомный тултип для точек спарклайна (main.js, initSparklineTooltips) —
   пул из 1-2 элементов на всю страницу (не по одному на карточку —
   подписи никогда не показываются больше чем для одной колонки сразу),
   позиционируются в JS через left/top (position: fixed, координаты
   КУРСОРА в экранных px, правка пользователя — раньше был у самой
   точки). Во втором графике колонка несёт два значения (cruise + imoex)
   — main.js рисует их двумя отдельными пузырями друг над другом
   (TOOLTIP_STACK_STEP), не одним растянутым на два ряда текста. */
.sparkline-tooltip {
  position: fixed;
  z-index: 20;
  left: 0;
  top: 0;
  transform: translate(-50%, calc(-100% - 0.625rem));
  background: rgb(var(--color-bg));
  color: rgb(var(--color-text));
  padding: 0.25rem 0.625rem;
  border-radius: var(--radius-pill);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.15s ease;
}

.sparkline-tooltip.is-visible {
  opacity: 1;
}

.stat-card__value {
  text-align: right;
  margin: calc(var(--density) * 0.75rem) 0 0;
}

.stat-card__footnote {
  color: rgb(var(--color-text-secondary));
  margin: calc(var(--density) * 0.75rem) 0 0;
}

/* Тот же приём, что у .cruise-card — .hero__stats тоже "pill"
   (radius+overflow:hidden на контейнере), поэтому двигаем не саму
   карточку, а всё, кроме первого ребёнка (.stat-card__head остаётся
   на месте, график+значение+сноска едут вниз одним блоком). */
.stat-card > *:not(:first-child) {
  transition: transform 0.75s ease;
}

.stat-card:hover > *:not(:first-child) {
  transform: translate(0px, -.5rem);
  transition-duration: 0.25s; /* появление быстрее затухания (0.75s, базовое правило выше) — как в .reports-row */
}

/* ============================================================
   Hero — появление блоков при скролле
   ============================================================
   Тот же механизм, что и у about/mission ниже (initScrollReveal,
   animations.js) — каждый элемент сам следит за собой через
   IntersectionObserver и получает .is-revealed, когда реально показался
   в зоне видимости, а не по таймеру после загрузки страницы (было так
   раньше — выглядело так, будто hero вообще не связан со скроллом, см.
   обсуждение в чате). На типичном экране hero выше вьюпорта, так что
   заголовок виден сразу, а карточки/кнопка внизу — только когда до них
   доскроллили. transition-delay ниже — только у пары карточек в
   hero__stats (они рядом, показываются практически одновременно). */
.hero__title,
.hero__subtitle,
.hero__stats .stat-card {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

/* Отдельно от группы выше, не в общем списке: этому селектору
   (.hero__reports > .button--ghost) как более специфичному, чем общий
   .button--ghost (main.css выше — его hover-фон), нужен полный список
   transition тут же, одним местом, иначе он молча перетирал бы
   background короче общим transition только под ревил — и hover-фон
   переставал плавно гаснуть (ровно так и было, поймано вживую). Та же
   специфичность у .button--ghost:hover (main.css выше) означала бы, что
   именно этот, более поздний по файлу блок выигрывал transition-duration
   и у hover тоже — инстант-ин пропадал бы вместе с фейдом. Поэтому
   инстант возвращаем явно, тем же приёмом (.hero__reports > .button--ghost:hover
   ниже), чтобы не зависеть от порядка объявления. */
.hero__reports > .button--ghost {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease, background 0.75s ease;
}

.hero__reports > .button--ghost:hover {
  transition-duration: 0s;
}

.hero__stats .stat-card:nth-child(2) {
  transition-delay: 0.1s;
}

.hero__title.is-revealed,
.hero__subtitle.is-revealed,
.hero__stats .stat-card.is-revealed,
.hero__reports > .button--ghost.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

/* "Рисуем" спарклайн — реальную длину пути JS считает через
   getTotalLength() (форма зависит от данных, в CSS её не знать) и сам
   же анимирует stroke-dashoffset инлайн-стилем от длины пути до 0
   (animations.js) — transition тут просто делает эту смену плавной. */
.stat-card__sparkline path {
  transition: stroke-dashoffset 1.2s ease;
}

@media (max-width: 768px) {
  .hero {
    padding: var(--header-h, 0px) calc(var(--density) * 0.375rem) calc(var(--density) * 3rem); /* .. 6 48 — в Фигме на мобиле уже сам контент прижат к 6px */
  }

  /* Точные значения из Figma: top: calc(50% - 398.5px) + translateY(-50%)
     на элементе 664px внутри блока — в сумме top: -247px, height: 664px.
     Геометрия фона, density не участвует. */
  .hero__bg {
    top: -15.4375rem; /* -247px */
    height: 41.5rem; /* 664px */
  }

  .hero > .container {
    align-items: flex-start; /* как items-start в Figma, без stretch */
    gap: calc(var(--density) * 2.25rem); /* 36px */
    padding-top: calc(var(--density) * 1.5rem);
  }

  .hero__intro {
    align-items: flex-start;
    text-align: left;
  }

  .hero__title {
    text-align: left;
    max-width: none;
  }

  .hero__subtitle {
    text-align: left;
    max-width: none;
    padding-top: 0;
  }

  .hero__reports {
    gap: calc(var(--density) * 0.75rem); /* 12px */
  }

  .hero__stats {
    grid-template-columns: 1fr;
    border-radius: 0.5rem; /* 8px */
  }

  .stat-card {
    padding: calc(var(--density) * 0.75rem); /* 12px */
    min-height: 0;
  }

  .stat-card__badge svg {
    width: 1.6438rem; /* 26.3px */
    height: 1.125rem; /* 18px */
  }

  .hero__reports .button--ghost {
    font-size: var(--fs-body-s); /* 16px */
    padding: 0.375em 0.75em; /* 6px / 12px при 16px font-size — em, компонентная ось, не density */
  }
}

/* ============================================================
   About
   ============================================================
   Единственное светлое место на сайте (сайт в целом тёмный), инверсия
   цвета точечная, см. tokens/colors.css. Мобильной версии в Figma нет
   (макет не обновлён под мобилку) — раскладка на ≤768px предложена
   нами: одна колонка, галочка обычным блоком в потоке (без
   position:absolute и без фона), цифры в один столбец. */

.about {
  position: relative;
  background: rgb(var(--color-bg-light));
  color: rgb(var(--color-text-on-light));
  padding-top: calc(var(--density) * 1rem);
  /* Прожектор за курсором (initSpotlight, animations.js, только
     десктоп с мышью) — двигает --spot-x/--spot-y каждый кадр (lerp к
     позиции мыши, отставание — JS-параметр, не CSS). Стартовые значения =
     статичная позиция (угол), пока JS не подключился/на touch-устройствах
     (там initSpotlight не запускается вовсе). ВРЕМЕННО:
     radius/smooth крутит debug-панель (initSpotlightDebugPanel) поверх
     этих чисел — снять переменные-умолчания вместе с панелью, когда
     параметры утвердим, зашить финальные значения напрямую. */
  --spot-x: 0%;
  --spot-y: 0%;
  --spot-radius: 36rem;
  --spot-smooth: 100; /* 0..100, без единиц — умножается на 1% в calc() ниже */
}

/* Узор — SVG <pattern> (#pattern-about, определён в layout.pug, форма —
   pattern-hg.svg, см. комментарий там). Figma: hexagon-тайл уже "запечён"
   в путях, decor не на карточках, а в правом верхнем углу самой секции
   (node 373:37515) — фон .about виден узкой полосой над белой панелью
   "о нас". Цвет — конкретный CACBC8 (#pattern-about выше), без opacity —
   сплошной, как прислали. */
.about__pattern {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  /* Прожектор за курсором (--spot-x/--spot-y/--spot-radius/--spot-smooth —
     animations.js, initSpotlight/initSpotlightDebugPanel; только
     десктоп с мышью, см. комментарий там). Вне hover-зоны/на тач — точка
     стоит в углу (--spot-x/--spot-y на .about выше — верхний левый,
     под направление фирменного градиента из Figma, 301.92deg). */
  mask-image: radial-gradient(
    circle var(--spot-radius) at var(--spot-x) var(--spot-y),
    rgb(0 0 0) 0%,
    rgb(0 0 0) calc(100% - var(--spot-smooth) * 1%),
    transparent 100%
  );
}

/* Цвет узора — на самом #pattern-about (определение в layout.pug), а не
   на .about__pattern (потребитель фила) и не на #pattern-shape (общая
   форма): currentColor путей внутри <use> резолвится от места <use> в
   DOM, то есть от <pattern id="pattern-about">, где он и лежит — отсюда
   разный цвет на About/Cruise/Approach при одной общей геометрии.
   Конкретное значение из Figma (CACBC8), не токен — цвет узора, а не
   текста/фона, переиспользуется только здесь и в .about__stat-card. */
#pattern-about {
  color: #cacbc8;
}

.about > .container {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-end;
}

.about__intro {
  width: 41.5625rem; /* 665px */
  align-self: stretch; /* h-full в Figma — тянется по высоте вместе с правой панелью, не фиксированная */
  overflow: hidden; /* overflow-clip в Figma */
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between; /* было ошибочно как gap:128px — на деле контент растянут по высоте контейнера */
  padding: calc(var(--density) * 3rem) calc(var(--density) * 3rem) calc(var(--density) * 3rem) calc(var(--density) * 1.5rem); /* 48 48 48 24 */
  border-top-right-radius: var(--radius);
}

/* Карточка + диагональный чек поверх неё, хвостом вылезающий за верхний
   край — как в оригинале. Координаты и поворот (32deg) взяты из Figma
   и переведены в % от размера самой карточки, чтобы работало на любом
   размере (десктоп/мобилка меняют только --checkmark-size). */
.about__checkmark {
  position: relative;
  width: 100%;
  max-width: 21rem; /* 336px */
  aspect-ratio: 1;
  background: rgb(var(--color-surface-light));
  border-radius: var(--radius);
}

.about__checkmark svg {
  display: block;
  position: absolute;
  left: 36.2%;
  top: -38.6%;
  width: 44.94%;
  height: 114.29%;
  transform: rotate(32deg) scaleY(-1);
  transform-origin: center;
  overflow: visible;
}

/* "Рисуем" галочку тем же приёмом, что и спарклайн в hero
   (.stat-card__sparkline path выше) — JS (initCheckmarkDraw,
   animations.js) считает реальную длину пути через getTotalLength() и
   гонит stroke-dashoffset от неё до 0 при попадании карточки в зону
   видимости; transition тут только делает эту смену плавной. */
.about__checkmark svg path {
  transition: stroke-dashoffset 1s ease;
}

.about__panel-wrap {
  flex: 1;
  min-width: 0;
  padding-top: calc(var(--density) * 4.5rem); /* 72px */
}

.about__panel {
  background: rgb(var(--color-surface-light));
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 2.25rem); /* 36px */
  padding: calc(var(--density) * 1.5rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 24 24 56 */
  border-radius: var(--radius) var(--radius) 0 0;
}

.about__panel-head {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.about__panel-heading {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 1rem); /* 16px */
}

.about__stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: stretch;
  gap: calc(var(--density) * 0.75rem) calc(var(--density) * 0.5rem); /* 12 8 — row-gap column-gap */
}

/* Колонка отвечает только за ступенчатый сдвиг сверху — сама заливка
   и текст живут в .about__stat-card, отдельным блоком, как в Figma. */
.about__stat {
  display: flex;
  flex-direction: column;
}

.about__stat:nth-child(1) {
  padding-top: calc(var(--density) * 8rem); /* 128px */
}
.about__stat:nth-child(2) {
  padding-top: calc(var(--density) * 4.5rem); /* 72px */
}

/* Три слоя друг над другом (узор → белая заливка снизу вверх → текст) —
   не через background-image на самой карточке: CSS background не умеет
   красить SVG <pattern> (fill: url(#id) — только у SVG-элементов, не у
   HTML background), а порядок отрисовки в CSS такой, что фон элемента
   всегда за его позиционированными детьми — значит и узор, и градиент
   должны быть отдельными детьми, иначе градиент не окажется поверх
   узора. position: relative + overflow: hidden — сама карточка просто
   рамка со скруглением, красят её слои внутри. */
.about__stat-card {
  position: relative;
  overflow: hidden;
  flex: 1;
  min-height: 10.125rem; /* 162px */
  border-radius: var(--radius);
  transition: transform 0.75s ease;
}

.about__stat-card__pattern {
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* Белая заливка проступает снизу вверх поверх узора; между ними ещё один,
   полупрозрачный слой в тон подложки (--color-bg-light/0.8) — гасит
   контраст узора, прямого "opacity" на один слой фона в CSS нет, это
   его замена. */
.about__stat-card__fade {
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image:
    linear-gradient(180deg, transparent 0%, rgb(var(--color-surface-light)) 100%),
    linear-gradient(rgba(var(--color-bg-light), 0.8), rgba(var(--color-bg-light), 0.8));
}

.about__stat-card__content {
  position: relative;
  z-index: 2;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: calc(var(--density) * 1.5rem); /* 24px */
  padding: calc(var(--density) * 1.5rem); /* 24px */
}

/* В отличие от .cruise-card/.product-card/.stat-card, тут radius свой
   на карточке, не на общем "pill"-контейнере — вырезать нечему, можно
   двигать карточку целиком. */
.about__stat-card:hover {
  transform: translate(0px, -.5rem);
  transition-duration: 0.25s; /* появление быстрее затухания (0.75s, базовое правило выше) — как в .reports-row */
}

.about__stat-label {
  color: rgb(var(--color-text-secondary-on-light));
}

/* В Figma капитель-подпись везде (о нас/миссия/принципы/интрадей) —
   приглушённый серый rgb(var(--color-text-secondary)), даже на светлой панели
   about. Не color-text-secondary-on-light: тут используется тот же серый,
   что и на тёмных секциях, не более тёмный "on-light" вариант. */
.about__label {
  color: rgb(var(--color-text-secondary));
}

.about__closing {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

/* Появление при скролле (initScrollReveal, animations.js) — каждый
   блок следит за собой сам (не за секцией целиком) и получает свой
   .is-revealed, когда реально показался в зоне видимости — задержки
   тут не по порядку чтения, а только у элементов, оказывающихся в
   зоне видимости одновременно (три стата в один ряд), чтобы не
   выскакивали разом. */
.about__headline,
.about__checkmark,
.about__panel-head,
.about__stats > .about__stat,
.about__closing {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.about__stats > .about__stat:nth-child(2) {
  transition-delay: 0.1s;
}

.about__stats > .about__stat:nth-child(3) {
  transition-delay: 0.2s;
}

.about__headline.is-revealed,
.about__checkmark.is-revealed,
.about__panel-head.is-revealed,
.about__stats > .about__stat.is-revealed,
.about__closing.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

/* Мобильный макет — обновлён по Figma (node 383:42346/42347): заголовок и
   карточка с галочкой по центру, карточка меньше. Панель ниже физически
   перекрывает низ карточки (margin-top отрицательный на .about__panel-wrap,
   не тень) — по просьбе пользователя, проще и надёжнее подбора blur/spread
   у теневого варианта. */
@media (max-width: 768px) {
  .about > .container {
    flex-direction: column;
    align-items: stretch;
  }

  .about__intro {
    width: auto;
    align-items: center;
    justify-content: flex-start; /* обычная стопка, не space-between как на десктопе */
    gap: calc(var(--density) * 3rem);
    padding: calc(var(--density) * 3rem) calc(var(--density) * 0.375rem) calc(var(--density) * 0.75rem); /* 48 6 12 */
    border-top-right-radius: 0;
    /* Узор (.about__pattern, общий на секцию) должен погаснуть в солид
       к низу интро — иначе спорит с панелью ниже. */
    background-image: linear-gradient(180deg, transparent 0%, rgb(var(--color-bg-light)) 45%);
  }

  .about__headline {
    text-align: center;
  }

  .about__checkmark {
    width: 10rem; /* 160px, из Figma — сильно меньше десктопных 336px, фиксированная (не тянется, как на десктопе) */
    /* Figma: inset-тень 4.829/5.157px белым — смягчает кромку карточки
       изнутри. Без внешнего свечения — снаружи карточка у них чёткая. */
    box-shadow: inset 0 0 4.829px 5.157px rgb(var(--color-surface-light));
  }

  .about__panel-wrap {
    padding-top: 0;
    /* Панель наезжает на низ карточки с галочкой (перекрывает её собой,
       не тенью) — .about__panel-wrap идёт следующим сиблингом за
       .about__intro, отрицательный margin-top поднимает её поверх. */
    margin-top: -4rem;
    position: relative;
    z-index: 1;
  }

  .about__panel {
    gap: calc(var(--density) * 1rem); /* 16px */
    padding: calc(var(--density) * 1.5rem) calc(var(--density) * 0.375rem) calc(var(--density) * 3rem); /* 24 6 48 */
    border-top-left-radius: 0;
    box-shadow: 0px -16px 32px 0 rgb(var(--color-bg-light));
  }

  .about__panel-head {
    gap: calc(var(--density) * 0.375rem); /* 6px */
  }

  .about__panel-heading {
    gap: calc(var(--density) * 0.5rem); /* 8px */
  }

  .about__stats {
    grid-template-columns: 1fr;
    gap: calc(var(--density) * 1.5rem);
  }

  .about__stat:nth-child(1),
  .about__stat:nth-child(2) {
    padding-top: 0;
  }

  .about__stat-card {
    flex: none;
    min-height: 0;
    gap: calc(var(--density) * 0.75rem); /* 12px, не 24 как на десктопе */
  }
}

/* ============================================================
   Mission
   ============================================================
   Тёмная секция, тот же фоновый ассет, что у hero (bg.jpg), но на
   низкой прозрачности поверх сплошного тёмного фона. */

.mission {
  position: relative;
  background: rgb(var(--color-bg));
  overflow: hidden;
  padding: calc(var(--density) * 8rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 128 24 56 */
}

/* inset:0 — на всю секцию, какой бы высоты она ни была. Раньше тут была
   фиксированная высота (как у .hero__bg/.reports__bg) ради параллакса —
   актуально, только пока фон двигался кропом внутри себя (там реально
   участвует высота самого .mission__bg). После перехода mission/reports
   на translateY (initParallax, animations.js) диапазон считается от
   position .mission — своей же секции, а не от .mission__bg — фикс-высота
   ничего для параллакса больше не даёт, зато безобидно ломается, если
   контент секции когда-нибудь станет выше неё самой (пойманное вживую:
   текст обрывался в пустой фон ниже 698px). .reports__bg своя фикс-высота
   всё ещё нужна по другой причине (переключение таба/года меняет высоту
   секции) — см. комментарий там. */
.mission__bg {
  position: absolute;
  inset: 0;
  opacity: 0.2;
  overflow: hidden;
  will-change: transform; /* transform меняется JS-ом на каждом кадре скролла (initParallax, animations.js) — заранее выносим слой на GPU, а не даём браузеру решать это на лету при первом скролле (заметнее всего помогает в Safari) */
}

/* bg-circle_rotate.jpg — уже повёрнутый на 90° исходник (как и в
   reports__bg-rotate), поэтому просто background-image без transform
   и квадратной обёртки с запасом — тот же приём, что и в reports. */
.mission__bg-rotate {
  position: absolute;
  inset: 0;
  background-image: url("/assets/images/bg-circle_rotate.jpg");
  background-size: cover;
  background-position: center;
}

.mission__intro {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  max-width: 54.375rem; /* 870px */
  margin-bottom: calc(var(--density) * 8rem); /* 128px */
}

.mission__label {
  color: rgb(var(--color-text-secondary));
}

.mission__title {
  color: rgb(var(--color-text));
}

.mission__stats {
  position: relative;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: calc(var(--density) * 4rem); /* 64px */
  row-gap: calc(var(--density) * 3rem); /* 48px */
}

.mission-stat {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.mission-stat__row {
  display: flex;
  align-items: baseline;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.mission-stat__value {
  color: rgb(var(--color-text));
}

.mission-stat__value--positive {
  color: rgb(var(--color-positive));
}

.mission-stat__label {
  color: rgb(var(--color-text-secondary));
}

/* Общая анимация для переходов по сноскам (сюда ↑ из футера, и вниз к
   .site-footer__note по клику на *) — без неё непонятно, к какому именно
   блоку/сноске перешли среди остальных. Держит цвет светлым 3с (0–75%),
   затем гаснет обратно за 1с. */
@keyframes footnote-highlight {
  0%,
  75% {
    color: rgb(var(--color-text));
  }
  100% {
    color: rgb(var(--color-text-secondary));
  }
}

/* .is-highlighted навешивает main.js (initFootnoteLinks) при переходе по
   ↑ из сноски в футере — не :target: возврат делает свой scrollIntoView
   (по центру экрана, не к верхнему краю), а для этого URL двигается через
   history.pushState, который :target не запускает. */
.mission-stat__label.is-highlighted {
  animation: footnote-highlight 4s ease forwards;
}

/* Сноска через [^id] (richtext(), index.pug) — общая для любого текстового
   поля сайта, включая mission (раньше была своя отдельная .mission-stat__note,
   объединено в один механизм). Переход/подсветка — initFootnoteLinks (main.js). */
.footnote-ref {
  display: inline-block;
  color: inherit;
  opacity: 0.6;
  font-size: 0.7em;
  line-height: 1;
  vertical-align: super;
  text-decoration: none;
}

/* Появление при скролле (initScrollReveal, animations.js) — тот же
   приём, что и в about: каждый блок следит за собой сам. */
.mission__intro,
.mission-stat {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.mission__intro.is-revealed,
.mission-stat.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

/* "Пересчёт" процентных значений (initCountUp, animations.js) — цифра
   считается от 0 до настоящего значения (data-count-to, index.pug), а
   не просто проявляется текстом: создаёт ощущение живого пересчёта,
   а не готовой цифры. Только числа-проценты — у "≈1 час" (ниже) другой,
   немисловой акцент. */

/* Через секунду после появления — вспышка, "как звезда мигнула":
   резкий белый флэш (не сразу зелёный — сперва пересвет, потом остывает
   в accent), плюс пара мелких искр-звёздочек (8-конечная форма через
   clip-path, тот же приём, что в современных "sparkle"-эффектах —
   отдельные мерцающие точки рядом с текстом, а не только glow на самом
   тексте, см. чат) с небольшим несовпадением по времени, чтобы не
   мигали строго синхронно. Однократно, привязано к тому же
   .is-revealed, что уже ставит initScrollReveal (animations.js) —
   отдельный JS-триггер не нужен, animation-delay сам "ждёт" секунду. */
@keyframes mission-stat-glow {
  0% {
    color: rgb(var(--color-text));
    text-shadow: 0 0 0 rgba(var(--color-white), 0), 0 0 0 rgba(var(--color-white), 0);
  }
  8% {
    color: rgb(var(--color-text));
    text-shadow: 0 0 0.5rem rgba(var(--color-white), 1), 0 0 2.5rem rgba(var(--color-white), 0.7);
  }
  45% {
    color: rgb(var(--color-text));
    text-shadow: 0 0 0 rgba(var(--color-white), 0), 0 0 0 rgba(var(--color-white), 0);
  }
  100% {
    color: rgb(var(--color-positive));
    text-shadow: 0 0 0 rgba(var(--color-white), 0), 0 0 0 rgba(var(--color-white), 0);
  }
}

@keyframes mission-stat-sparkle {
  0% {
    opacity: 0;
    transform: scale(0) rotate(0deg);
  }
  8% {
    opacity: 1;
    transform: scale(1.3) rotate(20deg);
  }
  25% {
    opacity: 1;
    transform: scale(0.9) rotate(20deg);
  }
  55%,
  100% {
    opacity: 0;
    transform: scale(0.3) rotate(20deg);
  }
}

.mission-stat[data-glow-track] .mission-stat__value {
  position: relative;
  display: inline-block;
  /* .mission-stat — flex-column без своего align-items, поэтому по
     умолчанию stretch растягивал бы этот флекс-элемент на всю ширину
     контейнера (ту же, что у лейбла под ним) — inline-block тут не
     помогает, стрейч выигрывает у него. fit-content нужен, чтобы right у
     искры ::before (ниже) цеплялся за правый край именно текста, а не
     растянутого блока. */
  width: fit-content;
}

/* Сами искры — псевдоэлементы на значении, чтобы не заводить лишнюю
   разметку под чисто декоративный, разовый эффект. */
.mission-stat[data-glow-track] .mission-stat__value::before,
.mission-stat[data-glow-track] .mission-stat__value::after {
  content: "";
  position: absolute;
  background: rgb(var(--color-white));
  clip-path: polygon(50% 0%, 61% 39%, 100% 50%, 61% 61%, 50% 100%, 39% 61%, 0% 50%, 39% 39%);
  opacity: 0;
  pointer-events: none;
}

/* right, не left: значение — CMS-текст переменной длины (mission.json,
   stat3.value, сейчас "≈1 час"), left:10rem был подогнан под конкретную
   ширину именно этого текста и уезжал вслед за словом при любом другом
   значении. right всегда цепляется за верхний правый угол блока
   (inline-block по содержимому), независимо от того, сколько текста
   внутри. */
.mission-stat[data-glow-track] .mission-stat__value::before {
  width: 1.125rem; /* 18px */
  height: 1.125rem;
  top: 0.25rem;
  right: -0.75rem;
}

.mission-stat[data-glow-track] .mission-stat__value::after {
  width: 0.625rem; /* 10px */
  height: 0.625rem;
  bottom: 0.125rem;
  left: -0.625rem;
}

.mission-stat[data-glow-track].is-revealed .mission-stat__value {
  animation: mission-stat-glow 2s ease-out 1s 1 both;
}

.mission-stat[data-glow-track].is-revealed .mission-stat__value::before {
  animation: mission-stat-sparkle 2s ease-out 1s 1 both;
}

.mission-stat[data-glow-track].is-revealed .mission-stat__value::after {
  animation: mission-stat-sparkle 2s ease-out 1.15s 1 both; /* чуть позже первой — настоящее мерцание не синхронно */
}

@media (max-width: 768px) {
  .mission {
    /* +0.75rem сверху — фикс-хедер с блюром слегка перекрывает верх секции
       при скролле, чуть отступаем. var(--header-h) пробовали — вышло
       слишком много, откатили на фиксированную небольшую добавку. Только
       мобилка — на десктопе хедер уже и прозрачнее, не мешает. */
    padding: calc(calc(var(--density) * 3.5rem) + 0.75rem) calc(var(--density) * 0.375rem) calc(var(--density) * 3.5rem); /* 56 6 56 */
  }

  .mission__bg {
    opacity: 0.4;
  }

  .mission__intro {
    max-width: none;
    gap: calc(var(--density) * 0.375rem); /* 6px */
    margin-bottom: calc(var(--density) * 4.5rem); /* 72px */
  }

  .mission__stats {
    grid-template-columns: 1fr;
    row-gap: calc(var(--density) * 3rem); /* 48px */
  }

  .mission-stat {
    gap: calc(var(--density) * 0.375rem); /* 6px */
  }
}

/* ============================================================
   Principles
   ============================================================ */

.principles {
  background: rgb(var(--color-bg));
  padding: calc(var(--density) * 8rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 128 24 56 */
}

.principles > .container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 4.5rem); /* 72px */
}

.principles__intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  max-width: 54.375rem; /* 870px */
  text-align: center;
}

.principles__label {
  color: rgb(var(--color-text-secondary));
}

.principles__title {
  color: rgb(var(--color-text));
}

.principles__lead {
  color: rgb(var(--color-text-secondary));
}

.principles__panel {
  position: relative; /* якорь для уходящей .principles__content--exit (position:absolute, main.js — FLIP на её текущих координатах) */
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  width: 100%;
  border-radius: var(--radius);
  overflow: hidden;
}

/* min-height — не число из Figma, а посчитанный максимум среди всех
   трёх табов (--principles-content-h, syncPrinciplesContentHeight,
   main.js: измеряет все панели по очереди и берёт наибольшую, пересчёт
   при resize/смене брейкпоинта) — так карточка держит одну и ту же
   высоту при переключении таба независимо от длины текста, и уходящую
   панель можно спокойно вынимать из потока (position:absolute) под
   новую ровно того же размера, без скачка. */
.principles__content {
  order: 1;
  background: rgb(var(--color-surface));
  width: 45rem; /* 720px */
  flex-shrink: 0;
  min-height: var(--principles-content-h, 22.5rem);
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: calc(var(--density) * 1rem); /* 16px */
  padding: calc(var(--density) * 1.5rem) calc(var(--density) * 1.5rem) calc(var(--density) * 2.25rem); /* 24 24 36 */
}

.principles__content[hidden] {
  display: none;
}

/* Кроссфейд при переключении таба (initPrincipleTabs, main.js) —
   анимируется не общим блоком, а каждый смысловой кусок — текст и
   каждый стат — по отдельности, с небольшим сдвигом по времени
   (transition-delay в порядке чтения), чтобы было видно, что
   уходит/приходит не один слитный блок, а несколько.
   .principles__content-label ("как мы пришли к этому") в анимацию не
   входит — текст этой подписи одинаковый во всех трёх табах, ей нечего
   анимировать, пусть стоит постоянно.
   Уход и появление едут в одну сторону — вверх — но разными классами, не
   одним общим: entrance начинается СНИЗУ (--fade, translateY(1.5rem),
   как везде в reveal-анимациях сайта — hero/about/mission) и поднимается
   к 0, а exit УХОДИТ вверх (--exit, translateY(-0.375rem), см. ниже —
   общий класс с одним и тем же смещением не дал бы задать обоим
   направлениям разный знак). Новая панель показывается сразу же, не
   дожидаясь, пока старая исчезнет (правка пользователя) — старая на это
   время вынимается из потока (--exit: position:absolute, main.js), обе
   какой-то момент видны одновременно, и уходящая гаснет поверх уже
   появившейся новой. */
.principles__content-text,
.principles__stat {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.principles__stat:nth-child(1) {
  transition-delay: 0.1s;
}

.principles__stat:nth-child(2) {
  transition-delay: 0.2s;
}

.principles__content--fade .principles__content-text,
.principles__content--fade .principles__stat {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px, как в остальных reveal-анимациях сайта — стартовая поза появления */
  transition: none; /* эта поза выставляется под .no-transition в main.js (JS увидит его на кадр раньше, чем начнётся настоящий transition) */
}

/* Уход старой панели — вынута из потока (position:absolute, координаты
   выставлены в main.js по её собственному rect на момент клика — FLIP,
   тот же приём, что flipTabsGroup у reports) ровно на место, где она и
   была, поэтому новая панель (того же размера — см. --principles-content-h
   выше) занимает освободившееся место сразу, без ожидания. pointer-events:none
   — она всё ещё видна, пока не погаснет, и не должна ловить клики.
   Гасим ЦЕЛИКОМ (opacity на самой .principles__content, а не по отдельным
   текстовым блокам, как раньше) — иначе непрозрачный фон панели остаётся
   висеть сверху и закрывает новую панель, даже когда текст внутри уже
   невидим. Это самодостаточная CSS-анимация: main.js больше не ждёт её
   конца, чтобы показать следующую — если кликнуть ещё раз до того, как она
   успела погаснуть, новый клик просто не ждёт и переключает сразу же (не
   блокируется предыдущим уходом, даже если табов кликнули несколько подряд
   быстрее, чем эта анимация успевает доиграть). */
.principles__content--exit {
  position: absolute;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}

/* .no-transition вешается на саму .principles__content (main.js) перед
   стартовым проставлением opacity:0 у новой панели, чтобы не уехать в
   transition на первом же кадре — но transition висит на отдельных
   текстовых блоках, не на самой .principles__content, поэтому глобальный
   .no-transition (он self-target, transition-duration:0 на себе) сюда не
   достаёт: нужен отдельный descendant-вариант именно для этой карточки. */
.principles__content.no-transition .principles__content-text,
.principles__content.no-transition .principles__stat {
  transition-duration: 0s !important;
}

.principles__content-head {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.5625rem); /* 9px */
}

.principles__content-label {
  color: color-mix(in srgb, rgb(var(--color-text)) 30%, transparent);
}

.principles__content-text {
  color: rgb(var(--color-text));
}

.principles__stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: calc(var(--density) * 2rem) calc(var(--density) * 0.75rem); /* 32 12 */
}

.principles__stat {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.principles__stat-value {
  color: rgb(var(--color-text));
}

.principles__stat-label {
  color: rgb(var(--color-text));
}

.principles__tabs {
  position: relative;
  order: 2;
  flex: 1;
  align-self: stretch;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  flex-direction: column;
  padding: calc(var(--density) * 1.5rem); /* 24px */
  background: linear-gradient(
    to right,
    rgb(var(--color-surface)) 60%,
    color-mix(in srgb, rgb(var(--color-surface)) 50%, transparent) 100%
  );
}

/* Узор — тот же приём/тайл, что about/cruise/approach (#pattern-principles,
   layout.pug), поверх фона .principles__tabs (тот сам по себе — просто
   градиент-скрим из Figma, узор — отдельный слой над ним, не то же
   самое). Гасим к правому краю через mask-image (Figma: node 382:42127) —
   отдельная маска у узора, не совпадает с 60%-стопом скрима (два разных
   слоя). Цвет — приблизительный (CACBC8-как-по-другим-секциям текст не
   подходит, .about/.cruise/.approach красились в конкретные hex из
   Figma, а здесь codegen Figma не отдал заливку узора — Tile fill не
   экспортируется в CSS, только на скриншоте). Поправить, если не
   совпадёт. */
.principles__tabs__pattern {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  mask-image: linear-gradient(to right, rgb(0 0 0 / 50%) 0%, transparent 40%);
}

#pattern-principles {
  color: rgba(var(--color-text-secondary), 0.35);
}

.principles__tab {
  position: relative;
  z-index: 1;
  background: none;
  border: none;
  cursor: pointer;
  color: rgb(var(--color-text-secondary));
  padding: calc(var(--density) * 0.75rem) calc(var(--density) * 1rem); /* 12 16 */
  border-radius: var(--radius);
  transition: color 0.5s ease;
}

.principles__tab:not(.is-active):hover {
  color: rgb(var(--color-white));
  transition-duration: 0s;
}

.principles__tab.is-active {
  background: rgb(var(--color-cta-bg));
  color: rgb(var(--color-cta-text));
}

/* Появление при скролле (initScrollReveal, animations.js) — тот же приём,
   что и в hero/about/mission. .principles__panel — цельная карточка
   (контент + табы за одной скруглённой рамкой), поэтому один блок для
   реюла, а не отдельно контент и табы — они не «пара рядом», а один
   визуальный объект.

   intro — компактный блок, а gap до панели небольшой (72px), поэтому оба
   пересекают порог видимости (threshold 0.2) почти одновременно — на
   скролле выглядит как одновременный "хлопок", а не "один за другим"
   (замерено вживую, см. чат). Задержка у панели — не подмена реального
   скролл-триггера (см. animations.js), а честная компенсация того, что
   триггеры этих двух блоков физически близко друг к другу. */
.principles__intro,
.principles__panel {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.principles__panel {
  transition-delay: 0.15s;
}

.principles__intro.is-revealed,
.principles__panel.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 768px) {
  .principles {
    /* +0.75rem сверху — см. комментарий у .mission выше. */
    padding: calc(calc(var(--density) * 3.5rem) + 0.75rem) calc(var(--density) * 0.375rem) calc(var(--density) * 3.5rem);
  }

  .principles > .container {
    align-items: flex-start;
    gap: calc(var(--density) * 2.25rem); /* 36px */
  }

  .principles__intro {
    align-items: flex-start;
    text-align: left;
    max-width: none;
    gap: calc(var(--density) * 0.375rem); /* 6px */
  }

  /* transition тут — не добавка к базовому (строка выше), а полная
     замена по каскаду (тот же селектор, media query ниже по файлу
     побеждает) — opacity/transform для скролл-ревила нужно повторить
     явно, иначе появление на мобилке дёргается без анимации (поймано
     вживую). Высоту (в отличие от прежней версии) больше не анимируем
     JS-ом отдельным таймером — min-height у .principles__content теперь
     общий для всех брейкпоинтов (--principles-content-h выше), карточка
     и на мобилке держит одну и ту же высоту при смене таба. */
  .principles__panel {
    flex-direction: column;
    align-items: stretch;
    border-radius: 0.5rem; /* 8px */
    transition: opacity 0.6s ease, transform 0.6s ease;
  }

  .principles__content {
    order: 2;
    width: auto;
    gap: calc(var(--density) * 1.5rem); /* 24px */
    padding: calc(var(--density) * 0.75rem) calc(var(--density) * 0.75rem) 1.5rem; /* 12 12 24 */
  }

  .principles__stats {
    gap: calc(var(--density) * 0.75rem); /* 12px */
  }

  /* Отклонение от стандартной мобильной t-lead: здесь цифра уменьшается
     не до размера mobile/lead (24px), а до десктопного fs-body (24px
     тоже, но по факту — другое число, если сравнивать с mobile/body,
     16px) — так задано в Figma именно для этого блока. Токен --fs-body
     на мобилке уже равен 16, поэтому здесь литерал, а не var(). */
  .principles__stat-value {
    font-size: 1.5rem; /* 24px */
    line-height: calc(var(--density) * 1.05);
  }

  .principles__tabs {
    order: 1;
    flex-direction: column;
    padding: calc(var(--density) * 1.5rem) calc(var(--density) * 0.375rem) calc(var(--density) * 2.25rem);
    background: rgb(var(--color-surface));
  }

  /* На мобилке узор гасится снизу вверх, не слева направо (Figma:
     node 382:42147 — акцент у нижнего края, не у левого). */
  .principles__tabs__pattern {
    mask-image: linear-gradient(to top, rgb(0 0 0) 0%, transparent 50%);
  }

  .principles__tab {
    padding: calc(var(--density) * 0.375rem) calc(var(--density) * 0.75rem); /* 6 12 */
    border-radius: 0.5rem; /* 8px */
  }
}

/* ============================================================
   Products (интрадей: VM Basic+ / VM Premier)
   ============================================================ */

.products {
  background: rgb(var(--color-bg));
  padding: calc(var(--density) * 8rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 128 24 56 */
}

.products > .container {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 4.5rem); /* 72px */
}

.products__intro {
  display: flex;
  flex-direction: column;
  align-self: stretch; /* .products > .container центрирует детей, интро — исключение на всю ширину, как news__intro */
  gap: calc(var(--density) * 0.75rem); /* 12px */
  max-width: 54.375rem; /* 870px */
}

.products__label {
  color: rgb(var(--color-text-secondary));
}

.products__title {
  color: rgb(var(--color-text));
}

.products__cards {
  display: flex;
  gap: var(--gap-xs); /* 2px */
  width: 100%;
  border-radius: var(--radius);
  overflow: hidden;
}

.product-card {
  flex: 1;
  min-height: 27.1875rem; /* 435px */
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: calc(var(--density) * 3rem); /* 48px — минимум, если места меньше 435px (мобилка) */
  padding: calc(var(--density) * 1.5rem); /* 24px */
}

.product-card--dark {
  background: rgb(var(--color-surface));
  color: rgb(var(--color-text));
}

.product-card--light {
  background: rgb(var(--color-surface-light));
  color: rgb(var(--color-text-on-light));
}

.product-card__top,
.product-card__bottom {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

/* Тот же приём, что у .cruise-card — .products__cards тоже "pill"
   (radius+overflow:hidden на контейнере), поэтому двигаем не саму
   карточку, а всё, кроме первого ребёнка (.product-card__top остаётся
   на месте). */
.product-card > *:not(:first-child) {
  transition: transform 0.75s ease;
}

.product-card:hover > *:not(:first-child) {
  transform: translate(0px, -.5rem);
  transition-duration: 0.25s; /* появление быстрее затухания (0.75s, базовое правило выше) — как в .reports-row */
}

.product-card__logo {
  height: 4rem; /* 64px */
  display: block;
}

.product-card__note {
  color: rgb(var(--color-text-secondary));
}

.product-card__badge {
  align-self: flex-start;
  background: rgba(var(--color-white), 0.12);
  border-radius: var(--radius);
  padding: calc(var(--density) * 0.5rem) calc(var(--density) * 1rem); /* 8 16 */
  text-decoration: none;
}

.product-card--light .product-card__badge {
  background: color-mix(in srgb, rgb(var(--color-text-on-light)) 12%, transparent);
}

.product-card--light .product-card__badge:hover {
  background: color-mix(in srgb, rgb(var(--color-text-on-light)) 20%, transparent);
  color: inherit;
  transition-duration: 0s;
}

.product-card--dark .product-card__badge:hover {
  background: rgba(var(--color-white), 0.06);
  transition-duration: 0s;
}

/* Появление при скролле (initScrollReveal, animations.js) — тот же приём,
   что и в hero/about/mission/principles: каждый блок следит за собой
   сам. Карточки продуктов рядом друг с другом (как hero__stats), у
   второй — небольшая задержка, чтобы не выскакивали строго разом. */
.products__intro,
.product-card {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.product-card:nth-child(2) {
  transition-delay: 0.1s;
}

.products__intro.is-revealed,
.product-card.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 768px) {
  .products {
    /* +0.75rem сверху — см. комментарий у .mission выше. */
    padding: calc(calc(var(--density) * 3.5rem) + 0.75rem) calc(var(--density) * 0.375rem) 0; /* 56 6 0 — верх как у principles, без нижнего отступа */
  }

  .products > .container {
    align-items: flex-start;
    gap: calc(var(--density) * 2.25rem); /* 36px */
  }

  .products__intro {
    align-items: flex-start;
    text-align: left;
    max-width: none;
    gap: calc(var(--density) * 0.375rem); /* 6px */
  }

  .products__cards {
    flex-direction: column;
    border-radius: 0.5rem; /* 8px */
  }

  .product-card {
    min-height: 0;
    padding: calc(var(--density) * 1.5rem) calc(var(--density) * 0.75rem); /* 24 12 */
  }

  .product-card--dark {
    order: 2;
  }

  .product-card--light {
    order: 1;
  }

  .product-card__logo {
    height: 3rem; /* 48px */
  }

  .product-card__note {
    opacity: 0.4; /* в Figma на мобилке приглушение через opacity, не через цвет */
    color: inherit;
  }

  /* Отклонение от стандартной мобильной t-body: на мобиле этот текст —
     mobile/lead (24px/1.15) в Figma, а не mobile/body (16px), хотя на
     десктопе он того же размера (24px/body), что и note выше. */
  .product-card__label {
    font-size: var(--fs-lead); /* 24px, mobile/lead — не 16 как обычный .t-body */
    line-height: calc(var(--density) * 1.15);
  }

  .product-card__badge {
    padding: calc(var(--density) * 0.375rem) calc(var(--density) * 0.75rem); /* 6 12 */
    border-radius: 0.5rem; /* 8px */
  }
}

/* ============================================================
   Cruise (портфель: суммы под управлением, сравнение с рынком)
   ============================================================ */

.cruise {
  position: relative;
  background: rgb(var(--color-bg));
  padding: calc(var(--density) * 8rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 128 24 56 */
  /* Прожектор за курсором — тот же приём, что .about (initSpotlight,
     animations.js, только десктоп с мышью). Стартовая/дефолтная позиция —
     у правого края (100%/50%), где раньше был статичный fade. */
  --spot-x: 100%;
  --spot-y: 50%;
  --spot-radius: 36rem;
  --spot-smooth: 100;
}

.cruise::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(-90deg, rgba(var(--color-bg), 0.5) 0%, rgb(var(--color-bg)) 40%);
}

/* Узор — тот же SVG <pattern> (#pattern-cruise, layout.pug/pattern-hg.svg),
   что и .about__pattern выше, красится в тёмный (1E2122, конкретное
   значение из Figma — .cruise на тёмном фоне сама секция, узор чуть темнее).
   Прожектор за курсором — тот же mask-image, что у .about, переменные
   свои (.cruise выше). Цвет — на #pattern-cruise/approach ниже (общее
   правило на обе секции), не здесь: см. комментарий у #pattern-about
   (main.css, .about) про currentColor и <use>. */
#pattern-cruise,
#pattern-approach,
#pattern-privacy {
  color: #1e2122;
}

.cruise__pattern {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  mask-image: radial-gradient(
    circle var(--spot-radius) at var(--spot-x) var(--spot-y),
    rgb(0 0 0) 0%,
    rgb(0 0 0) calc(100% - var(--spot-smooth) * 1%),
    transparent 100%
  );
}

.cruise > .container {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 4.5rem); /* 72px */
}

.cruise__intro {
  display: flex;
  flex-direction: column;
  align-self: stretch; /* .cruise > .container центрирует детей, интро — исключение на всю ширину, как news__intro */
  gap: calc(var(--density) * 0.75rem); /* 12px */
  max-width: 54.375rem; /* 870px */
}

.cruise__label {
  color: rgb(var(--color-text-secondary));
}

.cruise__title {
  color: rgb(var(--color-text));
}

/* Карточки + CTA — своя обёртка с тесным gap (как .hero__reports),
   иначе кнопка получает тот же большой gap, что и между intro и
   карточками, от родительского .cruise > .container. */
.cruise__cards-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 1.5rem); /* 24px */
  width: 100%;
}

.cruise__cards {
  display: flex;
  gap: var(--gap-xs); /* 2px */
  width: 100%;
  min-height: 24.3125rem; /* 389px */
  border-radius: var(--radius);
  overflow: hidden;
}

.cruise-card {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: calc(var(--density) * 1.5rem); /* 24px */
  background: rgb(var(--color-surface));
  color: rgb(var(--color-text));
}

/* Двигаем не саму карточку (radius — на .cruise__cards, не на
   карточках: вылезала бы за overflow:hidden контейнера острым углом),
   а всё, кроме первого ребёнка (верхний — value/caption — остаётся на
   месте, остальное едет вниз одним блоком). Тот же приём — в
   .products__cards и .hero__stats ниже (тоже "pill"-контейнеры). */
.cruise-card > *:not(:first-child) {
  transition: transform 0.75s ease;
}

.cruise-card:hover > *:not(:first-child) {
  transform: translate(0px, -.5rem);
  transition-duration: 0.25s; /* появление быстрее затухания (0.75s, базовое правило выше) — как в .reports-row */
}

.cruise-card--a {
  flex: none;
  width: 24.6875rem; /* 395px */
}

.cruise-card--b {
  flex: none;
  width: 22.4375rem; /* 359px */
}

.cruise-card__group {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 1.25rem); /* 20px */
}

/* Отклонение от .t-h4: на десктопе этот текст — 31px/1.1, не заведён
   в Figma как именованный стиль (в отличие от мобильного варианта,
   который в точности совпадает с mobile/h4 — поэтому на мобилке просто
   наследуется стандартное поведение .t-h4). Явно scoped под min-width:
   без него правило той же специфичности, что и мобильный .t-h4, но
   позже по файлу — и перебивало бы мобильный размер (тот же баг, что
   нашли в reports-row__month, 2026-08-18). */
@media (min-width: 769px) {
  .cruise-card__caption {
    font-size: 1.9375rem; /* 31px */
    line-height: calc(var(--density) * 1.1);
  }
}

.cruise-card__footnote {
  color: rgb(var(--color-text-secondary));
}

.cruise-card__link {
  text-decoration: none;
}

/* Третья карточка — заголовок сверху, ниже список сравнения с рынком. */
.cruise-compare {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.cruise-compare .cruise-card__footnote {
  margin-top: calc(var(--density) * 0.25rem); /* 4px, чуть плотнее чем gap строк */
}

.cruise-compare__row {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.25rem); /* 4px */
}

.cruise-compare__head {
  display: flex;
  align-items: center;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.cruise-compare__brand {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--density) * 0.375rem); /* 6px */
}

.cruise-compare__icon {
  display: block;
}

.cruise-compare__name {
  font-family: var(--font-ui);
  font-weight: 700;
  font-size: 1.75rem; /* 28px */
  line-height: calc(var(--density) * 1.05);
  letter-spacing: -0.01em;
  color: rgb(var(--color-text));
}

.cruise-compare__pct {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 1.75rem; /* 28px */
  line-height: calc(var(--density) * 1.05);
  letter-spacing: -0.01em;
  color: rgb(var(--color-text));
}

.cruise-compare__pct--positive {
  color: rgb(var(--color-positive));
}

.cruise-compare__bar {
  height: 0.375rem; /* 6px */
  border-radius: var(--radius-pill);
  background: rgba(var(--color-text-secondary), 0.3);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 1s ease;
}

.cruise-compare__bar--positive {
  background: rgb(var(--color-positive));
}

/* Заполнение слева направо — тот же приём по духу, что "рисование"
   спарклайна в hero (stat-card__sparkline, animations.js), только без
   JS: итоговая ширина уже известна (inline style, index.pug), поэтому
   можно чисто CSS-transform'ом от scaleX(0) до scaleX(1), без измерения
   длины пути. Триггер — .is-revealed на самой карточке (initScrollReveal
   уже ставит его на .cruise-card целиком), отдельный наблюдатель на
   каждый бар не нужен — тот же приём, что у mission-stat-glow.

   Полоски идут друг за другом, а не разом — задержка растёт по номеру
   строки (.cruise-compare__row порядковый, список фондов из
   src/_data/compare.js — длина зависит от таблицы, поэтому явные
   nth-child на разумный запас позиций, а не динамический расчёт). */
.cruise-compare__row:nth-child(1) .cruise-compare__bar {
  transition-delay: 0.3s;
}

.cruise-compare__row:nth-child(2) .cruise-compare__bar {
  transition-delay: 0.45s;
}

.cruise-compare__row:nth-child(3) .cruise-compare__bar {
  transition-delay: 0.6s;
}

.cruise-compare__row:nth-child(4) .cruise-compare__bar {
  transition-delay: 0.75s;
}

.cruise-compare__row:nth-child(5) .cruise-compare__bar {
  transition-delay: 0.9s;
}

.cruise-compare__row:nth-child(6) .cruise-compare__bar {
  transition-delay: 1.05s;
}
.cruise-card.is-revealed .cruise-compare__bar {
  transform: scaleX(1);
}

.cruise__cta,
.news-card__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(var(--color-white), 0.12);
  backdrop-filter: blur(6px);
  border-radius: var(--radius);
  padding: calc(var(--density) * 0.5rem) calc(var(--density) * 1rem); /* 8 16 */
  color: rgb(var(--color-text));
  text-decoration: none;
}

.cruise__cta {
  align-self: center;
}

/* Появление при скролле (initScrollReveal, animations.js) — тот же приём,
   что и в hero/about/mission/principles/products. Три карточки в ряд
   (как about__stats) — у второй и третьей небольшая задержка, чтобы не
   выскакивали строго разом. */
.cruise__intro,
.cruise-card,
.cruise__cta {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.cruise-card:nth-child(2) {
  transition-delay: 0.1s;
}

.cruise-card:nth-child(3) {
  transition-delay: 0.2s;
}

.cruise__intro.is-revealed,
.cruise-card.is-revealed,
.cruise__cta.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 768px) {
  .cruise {
    /* +0.75rem сверху — см. комментарий у .mission выше. */
    padding: calc(calc(var(--density) * 3.5rem) + 0.75rem) calc(var(--density) * 0.375rem) calc(var(--density) * 3rem); /* 56 6 48 */
  }

  .cruise > .container {
    align-items: flex-start;
    gap: calc(var(--density) * 3rem); /* 48px */
  }

  .cruise__cards-wrap {
    gap: calc(var(--density) * 0.75rem); /* 12px */
  }

  .cruise::before {
    background: linear-gradient(69deg, rgb(var(--color-bg)) 20%, rgba(var(--color-bg), 0) 80%);
  }

  .cruise__intro {
    align-items: flex-start;
    text-align: left;
    max-width: none;
    gap: calc(var(--density) * 0.375rem); /* 6px */
  }

  .cruise__cards {
    flex-direction: column;
    min-height: 0;
    border-radius: 0.5rem; /* 8px */
  }

  .cruise-card--a,
  .cruise-card--b {
    width: auto;
    height: 10.3125rem; /* 165px */
  }

  .cruise-card--c {
    gap: calc(var(--density) * 3rem); /* 48px, вместо justify-between — высота уже не фиксирована */
  }

  .cruise-card {
    padding: calc(var(--density) * 0.75rem); /* 12px */
  }

  .cruise-card__group {
    gap: calc(var(--density) * 0.375rem); /* 6px */
  }

  .cruise-compare {
    gap: calc(var(--density) * 0.75rem); /* 12px */
  }

  .cruise-compare__row {
    gap: calc(var(--density) * 0.5rem); /* 8px */
  }

  .cruise-compare__head {
    gap: calc(var(--density) * 0.25rem); /* 4px */
  }

  .cruise-compare__brand {
    gap: calc(var(--density) * 0.25rem); /* 4px */
  }

  .cruise-compare__icon {
    width: 1.4611rem; /* 23.377px */
    height: 1rem; /* 16px */
  }

  .cruise-compare__name {
    font-weight: 600; /* на мобилке уже не bold, обычный вес .t-body-s */
    font-size: 1rem; /* 16px */
    line-height: calc(var(--density) * 1.15);
  }

  .cruise-compare__pct {
    font-weight: 600;
    font-size: var(--fs-footnote); /* 12px */
    line-height: calc(var(--density) * 1.05);
  }

  .cruise__cta {
    padding: calc(var(--density) * 0.375rem) calc(var(--density) * 0.75rem); /* 6 12 */
    border-radius: 0.5rem; /* 8px */
    color: rgb(var(--color-white));
  }
}

/* ============================================================
   Approach (цикл исследований / ограничения риска / коммуникация)
   ============================================================
   В Figma слева от контента — отдельная "рельса" с 3 иконками и
   соединяющей линией, привязанная к фиксированным пиксельным
   координатам (152/533/854px). Вместо этого рельса и группы лежат
   в одной CSS-グрид-колонке (auto-flow: иконка → группа → иконка...),
   поэтому иконка всегда стоит вровень с началом своей группы и линия
   между первой и последней иконкой считается от их центров, а не от
   зашитых цифр — тянется вместе с контентом на любой высоте. */

.approach {
  position: relative;
  background: rgb(var(--color-bg));
  padding: calc(var(--density) * 8rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 128 24 56 */
  /* Прожектор за курсором — тот же приём, что .about/.cruise выше.
     Дефолтная позиция — левый верхний угол (0%/0%), где раньше был
     статичный акцент (Figma node 368:23758). */
  --spot-x: 0%;
  --spot-y: 0%;
  --spot-radius: 36rem;
  --spot-smooth: 100;
}

/* Узор — тот же приём, что .cruise__pattern выше (цвет — общее правило
   #pattern-cruise/#pattern-approach там же). Прожектор за курсором — тот
   же mask-image, что у .about/.cruise, переменные свои (.approach выше). */
.approach__pattern {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  mask-image: radial-gradient(
    circle var(--spot-radius) at var(--spot-x) var(--spot-y),
    rgb(0 0 0) 0%,
    rgb(0 0 0) calc(100% - var(--spot-smooth) * 1%),
    transparent 100%
  );
}

.approach > .container {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 3rem; /* 48px */
}

/* Два самостоятельных блока рядом: рельса с иконками — гибкая
   колонка (как в Figma: flex:1, иконки центрируются в ней по
   горизонтали), контент — колонка фиксированной ширины 894px.
   Позиция каждой иконки по вертикали (top) и размер самой линии —
   считает и подставляет инлайн-стилями JS (initApproachRail,
   main.js): иконка встаёт по центру своего .approach__title, точную
   Y чистым CSS не получить, т.к. высота групп разная (сетка тегов /
   список / подпункты). */
.approach__rail {
  position: relative;
  flex: 1;
}

.approach__rail-line {
  position: absolute;
  left: 50%; /* рельса гибкая, иконки центрируются в ней — линия по той же оси */
  transform: translateX(-50%);
  width: 0.125rem;
  background: #313131; /* непройденная часть */
  z-index: 0;
  overflow: hidden;
}

/* Заполненная часть линии — она же прогресс скролла. Высоту (0..100%
   линии) считает тот же JS, что расставляет иконки: одно число решает
   и докраску линии, и когда иконка переключится в .is-active — см.
   комментарий в main.js. */
.approach__rail-fill {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 0;
  background: rgb(var(--color-text));
}

.approach__content {
  flex-shrink: 0;
  width: 52.9375rem; /* 847px, из Figma — совпадает с about__panel (1512 - 665) */
  display: flex;
  flex-direction: column;
  gap: 3.5rem; /* 56px */
  padding: calc(var(--density) * 1.5rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 24 24 56, как about__panel */
}

/* Иконка задублирована в разметке: --rail стоит в левой колонке
   (десктоп), --inline — рядом с заголовком своей группы (мобилка).
   Только одна из них видна на брейкпоинт, см. media query ниже. */
.approach__icon {
  border-radius: var(--radius-pill);
  backdrop-filter: blur(6px);
  display: flex;
  align-items: center;
  justify-content: center;
}

.approach__icon svg {
  display: block;
}

.approach__icon--search {
  background: rgb(var(--color-text));
  color: rgb(var(--color-bg)); /* currentColor-иконка тёмная на светлом кружке */
}

.approach__icon--controls,
.approach__icon--chat {
  background: rgba(var(--color-text), 0.12);
  color: rgb(var(--color-text)); /* currentColor-иконка светлая на тёмном кружке */
}

/* Внешний вид иконки решает не её имя, а то, дошёл ли до неё скролл
   (.is-active, ставит JS — initApproachRail на десктопе через рельсу,
   initApproachInlineIcons на мобилке напрямую по позиции заголовка) —
   переопределяет правила по имени выше через специфичность (2 класса
   вместо 1), имя иконки тут ни при чём. Общий класс на обоих вариантах
   (--rail и --inline), т.к. состояния и переход одни и те же. */
.approach__icon--rail:not(.is-active),
.approach__icon--inline:not(.is-active) {
  background: rgb(var(--color-surface));
  color: rgb(var(--color-text));
}

.approach__icon--rail.is-active,
.approach__icon--inline.is-active {
  background: rgb(var(--color-text));
  color: rgb(var(--color-bg));
}

.approach__icon--inline {
  transition: background 0.3s ease, color 0.3s ease;
}

.approach__icon--rail {
  position: absolute;
  z-index: 1;
  left: 50%;
  width: 4.5rem;
  height: 4.5rem;
  transform: translate(-50%, -50%); /* top (px, центр .approach__title) подставляет JS */
  backdrop-filter: none; /* фон непрозрачный в обоих состояниях — блюру нечего показывать */
  transition: background 0.3s ease, color 0.3s ease;
}

.approach__icon--rail svg {
  width: 3rem;
  height: 3rem;
}

.approach__icon--inline {
  display: none;
}

.approach__group {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 1.5rem); /* 24px */
}

.approach__group-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.approach__title {
  flex: 1;
  min-width: 0;
  color: rgb(var(--color-text));
}

.approach__tags {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.approach__tag {
  position: relative;
  flex: 0 0 11.7946rem; /* 188.714px — фиксированная ширина карточки-тега из Figma */
  height: 6.625rem; /* 106px */
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  padding: calc(var(--density) * 0.375rem) calc(var(--density) * 1rem) calc(var(--density) * 1rem) calc(var(--density) * 0.375rem); /* 6 16 16 6 */
  transition: background 0.5s ease, backdrop-filter 0.5s ease;
}

.approach__tag-box {
  position: relative;
  width: 1.5rem; /* 24px */
  height: 1.5rem;
  flex-shrink: 0;
  background: rgba(var(--color-text), 0.12);
}

/* Галочка внутри — тот же путь, приём "рисования" (getTotalLength()/
   stroke-dashoffset, main.css/animations.js, initTagChecksDraw) и тот же
   прямоугольник позиционирования (left/top/width/height %, transform),
   что и в .about__checkmark svg — просто масштаб другой (там квадрат
   336px, тут 24px): проценты одни и те же, поэтому хвост галочки так же
   уходит за верхний край коробки, как там за карточку. currentColor
   вместо зашитого цвета — красится через color ниже, как иконки в
   icons.svg. */
.approach__tag-check {
  position: absolute;
  left: 36.2%;
  top: -38.6%;
  width: 44.94%;
  height: 114.29%;
  transform: rotate(32deg) scaleY(-1);
  transform-origin: center;
  color: rgba(var(--color-text), 0.6);
  transition: color 0.5s ease;
}

/* Инстант-ин/смуз-аут, как везде на сайте (hover — сразу, уход —
   плавно): при ховере над тегом галочка мгновенно становится
   непрозрачной, обратно — плавно гаснет до тех же rgba(.6). */
.approach__tag:hover .approach__tag-check {
  color: rgb(var(--color-text));
  transition-duration: 0s;
}

/* Очерёдность ("друг за другом") делает JS через setTimeout
   (initTagChecksDraw, animations.js) — не transition-delay по nth-child:
   теги переносятся в 2 ряда, и второй ряд физически пересекает порог
   видимости позже первого при обычном плавном скролле (scroll-behavior:
   smooth, settings.css) — фиксированный CSS-delay от разметки ломался бы
   об это (не совпадал с реальным порядком появления). */
.approach__tag-check path {
  transition: stroke-dashoffset 0.6s ease;
}

.approach__tag-text {
  color: #d9d9d9;
}

.approach__tag:hover {
  background: rgba(var(--color-text), 0.08);
  backdrop-filter: blur(6px);
  transition-duration: 0s;
}

.approach__list {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.approach__list-item {
  display: flex;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

/* Заливка по ходу скролла — не одноразовый is-revealed-переключатель, а
   непрерывный прогресс 0..1 (initReadingFill, animations.js, тот же
   принцип, что и approach__rail: где строка сейчас относительно окна
   чтения экрана), кладётся как --reveal на сам .approach__list-item —
   у буллита им же управляется alpha фона. */
.approach__list-item {
  --reveal: 0;
}

.approach__bullet {
  width: 0.5rem; /* 8px */
  height: 0.5rem;
  flex-shrink: 0;
  margin-top: calc(var(--density) * 0.375rem); /* 6px — выравнивание по первой строке текста */
  background: rgba(var(--color-text), calc(0.5 + var(--reveal) * 0.5));
}

.approach__subitems {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 1.5rem); /* 24px */
}

.approach__subitem {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.5rem); /* 8px */
}

.approach__sublabel {
  color: rgb(var(--color-text-secondary));
}

/* Буква за буквой, по мере скролла (initReadingFill, animations.js) —
   честная разбивка текста на .char-спаны, общая техника для
   .approach__list-item (одна строка) и .approach__subitem (2-3 строки):
   раньше список красился отдельно, градиентом по x-координате всей
   строки — не по буквам, а сплошной полосой, визуально расходилось с
   subitem'ами, которые уже были на честных спанах (унифицировано по
   просьбе пользователя). Абзац оборачивается в слова и буквы один раз
   при инициализации, дальше JS кладёт каждой букве свой --reveal (0..1,
   тем же каскадным принципом, что и у строк/пунктов) — само собой
   читается и как "слово за словом", и как "строка за строкой", раз буквы
   идут по чтению, а не по горизонтали общей рамки. */
.approach__list-item p .word,
.approach__subitem p:last-child .word {
  white-space: nowrap; /* слово переносится на новую строку целиком, не разрезается между буквами-спанами */
}

.approach__list-item p .char,
.approach__subitem p:last-child .char {
  color: rgba(var(--color-text), calc(0.5 + var(--reveal, 0) * 0.5));
}

/* Появление при скролле (initScrollReveal, animations.js) — но только
   opacity, без translateY (в отличие от остальных секций): позиции
   иконок рельсы (.approach__icon--rail) считает initApproachRail
   (main.js) один раз при загрузке — по getBoundingClientRect()
   .approach__title, и больше не пересчитывает (только по resize). Если
   бы .approach__group ещё двигался по transform в этот момент (обычный
   translateY(1.5rem) в начальном состоянии), иконка навсегда осталась
   бы смещена на те же 24px от центра заголовка — измерено вживую,
   opacity на getBoundingClientRect не влияет, transform влияет. Рельса
   сюда не входит вообще — у неё уже своя, отдельная, функциональная
   анимация прогресса (тот же initApproachRail). */
.approach__group {
  opacity: 0;
  transition: opacity 0.6s ease;
}

.approach__group.is-revealed {
  opacity: 1;
}

@media (max-width: 768px) {
  .approach {
    /* +0.75rem сверху — см. комментарий у .mission выше. */
    padding: calc(calc(var(--density) * 1.5rem) + 0.75rem) calc(var(--density) * 0.375rem) calc(var(--density) * 3rem); /* 24 6 48 */
  }

  .approach > .container {
    flex-direction: column;
  }

  .approach__rail {
    display: none; /* на мобилке иконка стоит рядом с заголовком своей группы, рельсы нет */
  }

  .approach__content {
    width: 100%;
    gap: calc(var(--density) * 3rem); /* 48px */
    padding-left: 0;
    padding-right: 0;
  }

  .approach__icon--inline {
    display: flex;
    width: 3rem; /* 48px */
    height: 3rem;
    flex-shrink: 0;
    backdrop-filter: blur(3.6px);
  }

  .approach__icon--inline svg {
    width: 1.8rem; /* 28.8px */
    height: 1.8rem;
  }

  .approach__group {
    gap: calc(var(--density) * 0.75rem); /* 12px */
  }

  /* В Figma мобильная версия этой сетки физически обрезана рамкой
     391px без скролла — из 7 тегов видно только 4 (десктопный блок
     скопирован и обрезан по ширине без адаптации, недоделанная
     мобилка). Показываем весь контент, а не обрезанные 4 тега —
     статичная сетка 2 колонки, без горизонтального скролла. */
  .approach__tag {
    flex: 1 1 calc(50% - 0.375rem); /* 2 колонки, gap 12px */
  }

  /* Тот же вид, что у :hover на десктопе (выше по файлу), но без
     transition-duration:0s — на мобилке нет курсора, чтобы этот фон
     навести и увидеть, поэтому initTagChecksDraw (animations.js)
     навешивает класс сам, сразу после того, как у тега дорисовалась
     галочка, и тут это должно быть плавно (обычный transition
     .approach__tag/-check, main.css выше), а не мгновенно, как у hover. */
  .approach__tag.is-filled {
    background: rgba(var(--color-text), 0.08);
    backdrop-filter: blur(6px);
  }

  .approach__tag.is-filled .approach__tag-check {
    color: rgb(var(--color-text));
  }

  .approach__list {
    gap: calc(var(--density) * 0.5rem); /* 8px */
  }

  .approach__subitems {
    gap: calc(var(--density) * 1rem); /* 16px */
  }

  .approach__subitem {
    gap: calc(var(--density) * 0.25rem); /* 4px */
  }
}

/* ============================================================
   Reports (список ежемесячных отчётов с затуханием + CTA)
   ============================================================
   12 строк в Figma — почти одинаковый повторяющийся компонент, но
   с разъехавшимися по ходу дублирования мелкими значениями (то 48,
   то 64px у цифры; то 12/6, то 16/8 паддинг у кнопки «Скачать» и
   т.п.) — явно "накопленный дрейф" копипаста, а не осознанный design
   decision по каждой строке. Ниже — один согласованный вариант по
   большинству строк, а не 12 чуть разных наборов правил. */

.reports {
  position: relative;
  overflow: hidden;
  background: rgb(var(--color-bg));
  padding: calc(var(--density) * 8rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 128 24 56 */
}

.reports > .container {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 4.5rem); /* 72px */
}

/* .reports__bg — просто клиппинг-обёртка на всю секцию (inset:0, как
   .mission__bg) + цель параллакса (initParallax двигает её через
   transform, читает position самой секции .reports, к размеру обёртки
   это не имеет отношения). Сама картинка — .reports__bg-rotate ниже —
   фиксированного размера, НЕ inset:0: секция .reports живая
   (переключение таба/года меняет её высоту), а .reports__bg-rotate —
   background-size:cover, который бы каждый раз пересчитывал масштаб под
   новую высоту — та самая "картинка меняет размер" (пойманное вживую:
   переключение таба на более длинный визуально "зумило" круг). Ниже
   фиксированной картинки эта секция просто ничего не рисует — виден
   обычный фон .reports (rgb(var(--color-bg))), в который картинка и так
   уже гаснет через градиент у .reports__bg-rotate::before ниже, поэтому
   шва на стыке нет ни при какой высоте секции. */
.reports__bg {
  position: absolute;
  inset: 0;
  opacity: 0.2;
  overflow: hidden;
  will-change: transform; /* см. .mission__bg — тот же JS-параллакс через transform на каждом кадре скролла */
}

.reports__bg-rotate {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 43.625rem; /* 698px, как у .hero__bg — константа, никогда не подстраивается под секцию */
  background-image: url("/assets/images/bg-circle_rotate.jpg");
  background-size: cover;
  background-position: center;
}

.reports__bg-rotate::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgb(var(--color-bg)), transparent);
}

.reports__intro {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  max-width: 54.375rem; /* 870px */
}

.reports__label {
  color: rgb(var(--color-text-secondary));
}

.reports__title {
  color: rgb(var(--color-text));
}

.reports__panel {
  position: relative;
}

@property --reports-fade {
  syntax: "<number>";
  inherits: false;
  initial-value: 1;
}

.reports__card {
  position: relative;
  z-index: 1;
  background: color-mix(in srgb, rgb(var(--color-surface)) 76%, transparent);
  backdrop-filter: blur(6px);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 2rem); /* 32px — зазор между тулбаром и списком */
  padding-top: calc(var(--density) * 1.5rem); /* 24px */
  /* Настоящая маска (mask-image), а не отдельный ::after-блок с
     закрашенным фоном — так карточка (с её blur/полупрозрачностью)
     реально гаснет в то, что под ней, а не в подставной сплошной цвет.
     1 = гаснет книзу (список обрезан), 0 = не гаснет (всё раскрыто) —
     .is-expanded плавно переключает через transition ниже. */
  --reports-fade-w: 12rem; /* 192px */
  --reports-fade: 1;
  /* height — чтобы переключение продукта/года (main.js, showPanel/
     activateProduct) ехало плавно, а не дёргалось: сама карточка (у
     CRUISE рядов заметно меньше, чем у Basic+/Premier) иначе меняла бы
     высоту мгновенно. .reports__bg теперь фиксированного размера
     (main.css выше) и от этой высоты больше не зависит — но плавная
     карточка сама по себе всё ещё лучше мгновенного скачка. */
  transition: --reports-fade 0.3s ease, height 0.4s ease;
  mask-image: linear-gradient(
    to bottom,
    rgb(var(--color-bg)) calc(100% - var(--reports-fade-w)),
    rgba(var(--color-bg), calc(1 - var(--reports-fade) * 0.05)) calc(100% - (var(--reports-fade-w) * 0.857)),
    rgba(var(--color-bg), calc(1 - var(--reports-fade) * 0.19)) calc(100% - (var(--reports-fade-w) * 0.714)),
    rgba(var(--color-bg), calc(1 - var(--reports-fade) * 0.39)) calc(100% - (var(--reports-fade-w) * 0.571)),
    rgba(var(--color-bg), calc(1 - var(--reports-fade) * 0.61)) calc(100% - (var(--reports-fade-w) * 0.429)),
    rgba(var(--color-bg), calc(1 - var(--reports-fade) * 0.81)) calc(100% - (var(--reports-fade-w) * 0.286)),
    rgba(var(--color-bg), calc(1 - var(--reports-fade) * 0.95)) calc(100% - (var(--reports-fade-w) * 0.143)),
    rgba(var(--color-bg), calc(1 - var(--reports-fade))) 100%
  );
}

/* is-short — навешивает main.js, когда в текущей видимой панели меньше 9
   строк: обрезать нечего, значит не нужны ни затухание, ни кнопка "показать
   все" (см. .reports__cta-wrap ниже). */
.reports__panel.is-expanded .reports__card,
.reports__panel.is-short .reports__card {
  --reports-fade: 0;
}

.reports__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 calc(var(--density) * 1.5rem); /* верт. отступ теперь на .reports__card */
}

.reports__tabs {
  display: flex;
  border-radius: var(--radius);
  overflow: hidden;
}

.reports__tab {
  background: rgba(var(--color-white), 0.12);
  color: rgb(var(--color-white));
  border: none;
  cursor: pointer;
  padding: calc(var(--density) * 0.5rem) calc(var(--density) * 1rem); /* 8 16 */
}

.reports__tab.is-active {
  background: rgb(var(--color-cta-bg));
  color: rgb(var(--color-cta-text));
}

.reports__year {
  display: flex;
  align-items: center;
  gap: calc(var(--density) * 0.25rem); /* 4px */
  background: rgba(var(--color-white), 0.12);
  color: rgb(var(--color-white));
  border: none;
  cursor: pointer;
  border-radius: var(--radius);
  padding: calc(var(--density) * 0.5rem) calc(var(--density) * 1rem); /* 8 16 */
}

.reports__year-icon {
  display: block;
}

/* Обёртка нужна как якорь для абсолютно позиционированного меню (main.js
   строит список лет из data-reports-year уже отрендеренных панелей) —
   сама кнопка .reports__year не меняется. */
/* Дропдаун года "выезжает" при переключении продукта, а не появляется
   рывком через hidden — main.js (initReportsBrowser, setYearWrapVisible)
   анимирует через класс .is-hidden тем же приёмом no-transition + двойной
   rAF, что и .principles__content--fade, и снимает/ставит hidden синхронно
   с началом/концом transition. Общее правило (не только мобилка) — на
   десктопе плавно то же самое, только табы рядом не двигаются (там нет
   .reports__toolbar:has(), см. мобильный блок ниже). */
.reports__year-wrap {
  position: relative;
  opacity: 1;
  transform: translateX(0);
  transition: opacity 0.3s ease, transform 0.3s ease;
}

.reports__year-wrap.is-hidden {
  opacity: 0;
  transform: translateX(0.75rem); /* 12px */
}

.reports__year-menu {
  position: absolute;
  top: calc(100% + var(--gap-s));
  right: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  min-width: 100%;
  background: rgb(var(--color-surface));
  border: var(--border-width-s) solid rgba(var(--color-white), 0.12);
  border-radius: var(--radius);
  overflow: hidden;
}

.reports__year-menu[hidden] {
  display: none;
}

.reports__year-option {
  background: none;
  color: rgb(var(--color-white));
  border: none;
  cursor: pointer;
  text-align: left;
  padding: calc(var(--density) * 0.5rem) calc(var(--density) * 1rem); /* 8 16 */
}

.reports__year-option:hover,
.reports__year-option.is-active {
  background: rgba(var(--color-white), 0.12);
}

.reports__list {
  position: relative;
  z-index: 1;
}

.reports-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: calc(var(--density) * 1.5rem); /* 24px */
  padding: calc(var(--density) * 0.625rem) calc(var(--density) * 1.5rem) calc(var(--density) * 1.5rem); /* 10 24 */
  border-bottom: var(--border-width-s) solid rgba(var(--color-white), 0.08);
}

/* Свёрнуто по умолчанию — видно первые 8 строк, остальные открывает
   .reports__cta по клику (main.js навешивает .is-expanded). */
.reports__panel:not(.is-expanded) .reports-row:nth-child(n + 9) {
  display: none;
}

.reports__panel:not(.is-expanded) .reports-row:nth-child(8) {
  border-bottom: none;
}

.reports-row:last-child {
  border-bottom: none;
}

.reports-row__head {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  color: rgb(var(--color-text));
}

.reports-row__desc {
  color: rgb(var(--color-text-secondary));
  max-width: 34.125rem; /* 546px */
}

.reports-row__foot {
  display: flex;
  align-items: center;
  gap: calc(var(--density) * 1.5rem); /* 24px */
}

/* Отклонение от .t-h4 только на десктопе (28px по умолчанию) — здесь
   24px. Явно scoped под min-width: без него правило той же
   специфичности, что и мобильный .t-h4, но позже по файлу — и
   перебивало бы мобильный размер (было багом, нашли 2026-08-18). */
@media (min-width: 769px) {
  .reports-row__month {
    font-size: 1.5rem; /* 24px */
  }
}


.reports-row__value {
  color: rgb(var(--color-text));
}

/* Годовая строка CRUISE (Figma: node 268:7859) — два значения рядом
   (доходность CRUISE и для сравнения индекс IMOEX), у каждого своя
   подпись с иконкой. Подпись скрыта и появляется по ховеру всей строки —
   так в Figma-прототипе, не задаётся статикой кадра. */
.reports-row__metrics {
  display: flex;
  align-items: center;
  gap: calc(var(--density) * 1.5rem); /* 24px, как gap между метрикой и кнопкой скачать */
}

.reports-row__metric {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: calc(var(--density) * 0.25rem); /* 4px */
}

.reports-row__metric--benchmark .reports-row__value {
  color: rgba(var(--color-text), 0.5);
}

.reports-row__metric-caption {
  display: flex;
  align-items: center;
  gap: calc(var(--density) * 0.25rem); /* 4px */
  opacity: 0;
  transition: opacity 0.75s ease;
}

.reports-row:hover .reports-row__metric-caption {
  opacity: 1;
  transition-duration: 0.25s; /* появление быстрее затухания (0.75s, базовое правило выше) */
}

.reports-row__metric-caption span {
  font-weight: 600;
}

.reports-row__metric-icon {
  height: 1rem; /* 16px */
  display: block;
}

.reports-row__metric-icon--logo {
  width: 1.4625rem; /* 23.4px — пропорции лого-метки (44:31), не квадрат */
}

.reports-row__metric-icon--moex {
  width: 1rem; /* 16px, квадратная */
  color: rgb(var(--color-text));
}

.reports-row__download {
  display: inline-flex;
  align-items: center;
  gap: calc(var(--density) * 0.5rem); /* 8px */
  background: rgba(var(--color-white), 0.12);
  color: rgb(var(--color-text));
  border-radius: var(--radius);
  padding: calc(var(--density) * 0.5rem) calc(var(--density) * 1rem); /* 8 16 */
  text-decoration: none;
}

.reports-row__download svg {
  width: 1.5rem; /* 16px */
  height: 1.5rem;
  display: block;
  opacity: .4;
}

/* Кнопка "Скачать" на каждой строке — только по ховеру самой строки:
   иначе дублируется на весь список и перегружает. Скрытая кнопка не
   должна резервировать место — гасим до 0 не только max-width, но и
   padding: паддинг не сжимается сам собой ниже своего значения (обычная
   логика box-model), поэтому пока он не 0, блок никогда не схлопнется
   до конца, сколько ни дави max-width — проверено живым замером
   (оставался «пол» ровно в сумму паддингов). Сам паддинг кнопки (16/16,
   как в базовом правиле выше) остаётся СИММЕТРИЧНЫМ и в скрытом, и в
   открытом состоянии — засовывать в него бывший foot-gap было ошибкой
   (перекашивало пилюлю: слева 40px, справа 16px). Тот 24px-gap живёт
   отдельно, как margin-left — margin, в отличие от padding, у пустого
   блока спокойно уходит в 0 без "пола".
   Раскрытие и схлопывание — оба плавные (правка пользователя: раньше
   раскрытие было мгновенным, как везде в проекте, тут это решили не
   оставлять), но с разной длительностью — раскрытие быстрее (0.25s),
   схлопывание медленнее (0.75s, базовое правило ниже) — max-width/
   padding/margin/opacity едут к цели синхронно в обе стороны, контент
   сужается/расширяется пропорционально.
   --download-w — ширина кнопки целиком (контент + паддинг 16/16, БЕЗ
   margin), измеренная JS (initReportsDownloadWidth, main.js) при
   padding:0 (как в скрытом состоянии) плюс 32px (16+16) уже в формуле —
   крутить их тут в CSS ещё раз не нужно.
   Только десктоп — на мобилке ховера нет, кнопка видна всегда. */
@media (min-width: 769px) {
  .reports-row__foot {
    gap: 0;
  }

  .reports-row__download {
    max-width: 0;
    min-width: 0; /* иначе flex-item авто-минимум (по контенту) перебивает max-width:0 */
    padding-left: 0;
    padding-right: 0;
    margin-left: 0;
    opacity: 0;
    overflow: hidden;
    transition: max-width 0.75s ease, padding 0.75s ease, margin-left 0.75s ease, opacity 0.75s ease;
  }

  .reports-row:hover .reports-row__download {
    max-width: var(--download-w, 8rem);
    padding-left: calc(var(--density) * 1rem); /* 16px, как в базовом правиле */
    padding-right: calc(var(--density) * 1rem); /* 16px */
    margin-left: calc(var(--density) * 1.5rem); /* 24px — бывший gap фута */
    opacity: 1;
    transition-duration: 0.25s;
  }
}

.reports__cta-wrap {
  position: relative;
  z-index: 2;
  display: flex;
  justify-content: center;
  margin-top: -12rem; /* 192px, поднимает CTA в зону затухания карточки */
  padding-top: calc(var(--density) * 8rem); /* 128px */
  /* padding-top поднимает саму кнопку, но остаётся кликабельной областью —
     без pointer-events перекрывает хову/клики (Скачать, ряд целиком) на
     строках под собой. Возвращаем клики только самой кнопке. */
  pointer-events: none;
}

.reports__cta {
  background: rgba(var(--color-white), 0.12);
  color: rgb(var(--color-text));
  border: none;
  cursor: pointer;
  border-radius: var(--radius);
  padding: calc(var(--density) * 0.875rem) calc(var(--density) * 1.25rem); /* 14 20 */
  pointer-events: auto;
}

.reports__panel.is-expanded .reports__cta-wrap,
.reports__panel.is-short .reports__cta-wrap {
  display: none;
}

/* Появление при скролле (initScrollReveal, animations.js) — тот же приём,
   что и в остальных секциях: intro и вся карточка отчётов как один блок
   (как .principles__panel — внутри табы/дропдаун/список, но это один
   визуальный объект). offsetHeight/getBoundingClientRect().width,
   которые меряет reports-специфичный JS (main.js — --reports-fade-w,
   --download-w), transform по Y не задевают, в отличие от approach —
   там ломалось именно измерение .top. */
.reports__intro,
.reports__panel {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.reports__intro.is-revealed,
.reports__panel.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 768px) {
  .reports {
    /* +0.75rem сверху — см. комментарий у .mission выше. */
    padding: calc(calc(var(--density) * 3.5rem) + 0.75rem) calc(var(--density) * 0.375rem) calc(var(--density) * 3rem); /* 56 6 48 */
  }

  .reports > .container {
    gap: calc(var(--density) * 2.25rem); /* 36px */
  }

  .reports__bg {
    opacity: 0.4;
  }

  .reports__intro {
    gap: calc(var(--density) * 0.375rem); /* 6px */
    max-width: none;
  }

  .reports__card {
    gap: calc(var(--density) * 1.5rem); /* 24px */
    padding-top: calc(var(--density) * 0.75rem); /* 12px */
    --reports-fade-w: 11.5rem; /* 184px */
  }

  .reports__toolbar {
    padding: 0 calc(var(--density) * 0.75rem); /* 12px */
  }

  /* CRUISE не даёт выбор года — .reports__year-wrap скрыт (hidden), и
     табы остаются единственным ребёнком тулбара: центрируем их (Figma:
     node 114:1972, кадр Cruise). У Basic+/Premier дропдаун года виден —
     тулбар возвращается к space-between (табы слева, год справа), это
     базовое правило .reports__toolbar выше. */
  .reports__toolbar:has(.reports__year-wrap[hidden]) {
    justify-content: center;
  }

  .reports__tabs,
  .reports__year {
    border-radius: 0.5rem; /* 8px */
  }

  /* Само центрирование (justify-content, выше) не анимируется — сдвиг
     .reports__tabs при появлении/исчезновении дропдауна года проигрывается
     через transform (FLIP, main.js), тем же таймингом 0.3s, что и
     .reports__year-wrap ниже, чтобы оба двигались синхронно, а не рывком
     врозь. */
  .reports__tabs {
    transition: transform 0.3s ease;
  }

  .reports__tab,
  .reports__year {
    padding: calc(var(--density) * 0.375rem) calc(var(--density) * 0.75rem); /* 6 12 */
  }

  /* Мобильный кадр для строки отчёта (Figma: node 114:1972) — в отличие
     от десктопа (голова слева, стат/кнопка справа), тут всё в один
     столбец и всё по центру: заголовок, описание, статы и "Скачать". */
  .reports-row {
    flex-direction: column;
    align-items: stretch;
    padding: calc(var(--density) * 1rem) calc(var(--density) * 0.75rem) calc(var(--density) * 2rem);
  }

  .reports-row__head {
    gap: calc(var(--density) * 0.3125rem); /* 5px */
  }

  .reports-row__month,
  .reports-row__desc {
    text-align: center;
  }

  .reports-row__foot {
    flex-direction: column;
    align-items: center;
  }

  /* .reports-row__metrics (гэп/направление) на мобилке теперь не
     отличается от десктопа — Figma даёт те же 24px и один ряд без
     переноса, раньше тут был defensive-фолбэк без дизайна (см. git). */
  .reports-row__metric {
    align-items: center;
  }

  /* На тач-устройствах ховера нет — подпись под значением (иконка +
     "Cruise"/"Индекс IMOEX") должна быть видна всегда, а не только по
     ховеру строки, как на десктопе (main.css, .reports-row__metric-caption). */
  .reports-row__metric-caption {
    opacity: 1;
  }

  .reports-row__download {
    gap: calc(var(--density) * 0.25rem); /* 4px, не 8 как на десктопе */
    padding: calc(var(--density) * 0.375rem) calc(var(--density) * 0.75rem); /* 6 12, не 8 16 */
  }

  .reports__cta-wrap {
    margin-top: -11.5rem; /* 184px */
  }

  .reports__cta {
    padding: calc(var(--density) * 0.75rem) calc(var(--density) * 1.25rem); /* 12 20 */
    border-radius: 0.5rem; /* 8px */
  }
}

/* ============================================================
   News (карусель карточек с новостями/лонгридами)
   ============================================================
   Карточки фиксированной ширины шире вьюпорта на любом брейкпоинте —
   горизонтальный скролл с точками-пагинацией задуман в Figma и на
   десктопе, и на мобилке (не только на мобилке, как в Approach).
   Точки статичные (без JS слежения за позицией скролла) — как и
   затухающие теги в Approach. */

.news {
  background: rgb(var(--color-bg));
  padding: calc(var(--density) * 8rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 128 24 56 */
}

.news > .container {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 4.5rem); /* 72px */
}

.news__intro {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  max-width: 54.375rem; /* 870px */
}

.news__label {
  color: rgb(var(--color-text-secondary));
}

.news__title {
  color: rgb(var(--color-text));
}

.news__scroll {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 1.5rem); /* 24px */
}

.news__cards {
  display: flex;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  width: 100%;
  overflow-x: auto;
  overflow-y: hidden; /* без этого overflow-x:auto молча включает и overflow-y:auto (CSS-квирк) —
    ещё не показанные карточки (.news-card, реюл, animations.js) стоят на transform:translateY(),
    который раздувает scrollable-область по вертикали (проверено: 423px против 400px реальной
    высоты), и контейнер начинал скроллиться вертикально */
  scrollbar-width: none;
}

/* Курсор-подсказка для перетаскивания мышью (initNewsDragScroll, main.js —
   на Windows блок иначе нечем горизонтально проскроллить). Только там,
   где есть настоящая мышь — на тач-устройствах перетаскивание не
   работает (там нативный свайп, initNewsActiveCard) и курсора всё равно
   не видно. */
@media (hover: hover) and (pointer: fine) {
  .news__cards {
    cursor: grab;
  }

  .news__cards.is-dragging {
    cursor: grabbing;
    user-select: none;
  }
}

/* Затухание по краям — только на десктопе (на мобилке карточки и так
   на всю ширину, обрезать нечего). Слева/справа включаются по факту
   скролла (main.js ставит data-at-start/data-at-end), а не всегда
   оба сразу: в начале только правый край, в конце — только левый. */
@property --news-fade-left {
  syntax: "<number>";
  inherits: false;
  initial-value: 1;
}

@property --news-fade-right {
  syntax: "<number>";
  inherits: false;
  initial-value: 1;
}

@media (min-width: 769px) {
  .news__cards {
    --news-fade-w: 10rem;
    /* 1 = край гаснет (обычное состояние), 0 = полностью непрозрачно
       (у самого начала/конца скролла — main.js держит data-at-start/
       data-at-end). Меняем не сам mask-image (он не анимируется), а
       эти два числа — тогда есть на чём плавно перейти. */
    --news-fade-left: 1;
    --news-fade-right: 1;
    transition: --news-fade-left 0.3s ease, --news-fade-right 0.3s ease;

    /* Альфа на каждом стопе — sine ease-in-out (t=1/7..6/7) между
       "непрозрачно" (1) и полным затуханием, смешанные по --news-fade-*. */
    mask-image: linear-gradient(
      to right,
      rgba(var(--color-bg), calc(1 - var(--news-fade-left))) 0,
      rgba(var(--color-bg), calc(1 - var(--news-fade-left) * 0.95)) calc(var(--news-fade-w) * 0.143),
      rgba(var(--color-bg), calc(1 - var(--news-fade-left) * 0.81)) calc(var(--news-fade-w) * 0.286),
      rgba(var(--color-bg), calc(1 - var(--news-fade-left) * 0.61)) calc(var(--news-fade-w) * 0.429),
      rgba(var(--color-bg), calc(1 - var(--news-fade-left) * 0.39)) calc(var(--news-fade-w) * 0.571),
      rgba(var(--color-bg), calc(1 - var(--news-fade-left) * 0.19)) calc(var(--news-fade-w) * 0.714),
      rgba(var(--color-bg), calc(1 - var(--news-fade-left) * 0.05)) calc(var(--news-fade-w) * 0.857),
      rgb(var(--color-bg)) var(--news-fade-w),
      rgb(var(--color-bg)) calc(100% - var(--news-fade-w)),
      rgba(var(--color-bg), calc(1 - var(--news-fade-right) * 0.05)) calc(100% - (var(--news-fade-w) * 0.857)),
      rgba(var(--color-bg), calc(1 - var(--news-fade-right) * 0.19)) calc(100% - (var(--news-fade-w) * 0.714)),
      rgba(var(--color-bg), calc(1 - var(--news-fade-right) * 0.39)) calc(100% - (var(--news-fade-w) * 0.571)),
      rgba(var(--color-bg), calc(1 - var(--news-fade-right) * 0.61)) calc(100% - (var(--news-fade-w) * 0.429)),
      rgba(var(--color-bg), calc(1 - var(--news-fade-right) * 0.81)) calc(100% - (var(--news-fade-w) * 0.286)),
      rgba(var(--color-bg), calc(1 - var(--news-fade-right) * 0.95)) calc(100% - (var(--news-fade-w) * 0.143)),
      rgba(var(--color-bg), calc(1 - var(--news-fade-right))) 100%
    );
  }

  .news__cards[data-at-start] {
    --news-fade-left: 0;
  }

  .news__cards[data-at-end] {
    --news-fade-right: 0;
  }
}

.news__cards::-webkit-scrollbar {
  display: none;
}

/* Проценты внутри linear-gradient() сами по себе не анимируются через
   transition (background-image не интерполируется) — регистрируем их
   как типизированные custom properties, тогда браузер умеет их твинить. */
@property --news-card-fade-start {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 72%;
}

@property --news-card-fade-end {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 84%;
}

.news-card {
  scroll-snap-align: start;
  flex: 0 0 38.75rem; /* 620px */
  height: 26.25rem; /* 420px */
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding: calc(var(--density) * 1.5rem); /* 24px */
  border-radius: var(--radius);
  --news-card-fade-start: 72%;
  --news-card-fade-end: 84%;
  background: linear-gradient(180deg, transparent var(--news-card-fade-start), rgb(var(--color-surface)) var(--news-card-fade-end)), rgb(var(--color-white));
  /* transition тут НЕ задаём — ниже по файлу .news-card ещё раз попадает
     в общее правило скролл-ревила (.news__intro, .news-card { transition:
     opacity/transform }), и CSS не объединяет transition между двумя
     блоками одного селектора — побеждает целиком последний по каскаду.
     Полный список (включая background и обе @property выше) — там же,
     одним местом, чтобы не расходились по файлу и не перетирали друг
     друга молча (поймано вживую: градиент карточки всегда переключался
     мгновенно, а не за 0.5s, именно из-за этого). */
}

/* Ховер даёт тот же вид, что у постоянного --positive (card 1) —
   вместо JS-класса на mouseenter/leave просто общий селектор.
   .is-current — тот же вид, но на тач-устройствах без hover (нет мыши,
   некуда навести): initNewsActiveCard (main.js) сам решает, какая
   карточка сейчас "текущая" по прокрутке ленты, и вешает класс на неё
   вместо реального hover.
   :hover везде ниже — только внутри @media (hover: hover) and
   (pointer: fine) (как .button--ghost и другие выше по файлу): на iOS
   Safari тап по карточке всё равно ставит :hover, а снимается он не
   скроллом, а только тапом по другому элементу — без гейта подсветка
   "залипала" при свайпе вправо и не пропадала при свайпе назад (баг,
   пойманный на iPhone 13 Pro). .is-current не завязан на :hover и всегда
   однозначно решает, что подсвечено на тач-устройствах.
   Появление/затухание — 0.25s/0.75s (правка пользователя, перенесено с
   .reports-row): раньше на десктопном :hover было мгновенное появление
   (transition-duration:0s), теперь оба направления плавные, но с разной
   скоростью — быстрее внутрь, медленнее наружу; .is-current и так уже
   был плавным (0.75s/0.75s, отдельная более ранняя правка). */
.news-card--positive,
.news-card.is-current {
  background: linear-gradient(180deg, transparent var(--news-card-fade-start), rgb(var(--color-positive)) var(--news-card-fade-end)), rgb(var(--color-white));
}

.news-card.is-current {
  --news-card-fade-start: 66%;
  --news-card-fade-end: 78%;
}

@media (hover: hover) and (pointer: fine) {
  .news-card:hover {
    background: linear-gradient(180deg, transparent var(--news-card-fade-start), rgb(var(--color-positive)) var(--news-card-fade-end)), rgb(var(--color-white));
    --news-card-fade-start: 66%;
    --news-card-fade-end: 78%;
    transition-duration: 0.25s; /* появление быстрее затухания (0.75s, .news-card transition ниже) — как в .reports-row */
  }
}

.news-card__head {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  padding-right: calc(var(--density) * 2rem); /* 32px, чтобы текст не растягивался во всю ширину карточки */
  color: rgb(var(--color-text-on-light));
}

.news-card__title {
  transition: color 0.5s ease;
}

.news-card--positive .news-card__title,
.news-card.is-current .news-card__title {
  color: rgb(var(--color-positive));
}

.news-card__desc {
  color: rgb(var(--color-text-secondary));
  transition: color 0.5s ease;
}

.news-card--positive .news-card__desc,
.news-card.is-current .news-card__desc {
  color: rgb(var(--color-bg), .5);
}

@media (hover: hover) and (pointer: fine) {
  .news-card:hover .news-card__title {
    color: rgb(var(--color-positive));
    transition-duration: 0s;
  }

  .news-card:hover .news-card__desc {
    color: rgb(var(--color-bg), .5);
    transition-duration: 0s;
  }
}

/* span, не div — просто строка из даты и (скрытой по умолчанию)
   кнопки, центрированная по высоте. */
.news-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  color: rgb(var(--color-white));
  transition: color 0.5s ease;
}

.news-card.is-current .news-card__foot {
  color: rgba(var(--color-text));
}

@media (hover: hover) and (pointer: fine) {
  .news-card:hover .news-card__foot {
    color: rgba(var(--color-text));
    transition-duration: 0s;
  }
}

.news-card__link {
  /* Свой color, не унаследованный от .news-card__foot — там на hover/
     is-current текст красится в --color-text (для даты рядом), а на
     зелёном фоне это не белый, а сероватый оттенок — низкий контраст
     именно у этой кнопки (текст должен быть белым всегда, вне
     зависимости от состояния). Ссылка и так невидима (opacity:0), пока
     не наведена/не активна — держать её всегда белой безопасно. */
  color: rgb(var(--color-white));
  gap: calc(var(--density) * 0.5rem); /* 8px */
  opacity: 0;
  transition: opacity 0.5s ease;
}

.news-card.is-current .news-card__link {
  opacity: 1;
}

@media (hover: hover) and (pointer: fine) {
  .news-card:hover .news-card__link {
    opacity: 1;
    transition-duration: 0s;
  }
}

.news-card__link svg {
  height: 1rem; /* 24px */
  width: auto;
}

/* 7-я, последняя карточка ленты — не новость из таблицы, а статичный
   переход в Телеграм. Своё оформление, не обычный .news-card__head/
   __foot: текст внизу слева, крупная иконка в правом верхнем углу
   (правка пользователя, замена прежней версии — текст+иконка по
   центру). Ховер красит карточку целиком в --color-telegram (фирменный
   синий, #3390EC — как на .nav-telegram:hover в хедере, tokens/colors.css),
   а не в --color-positive, как у обычных карточек (main.css выше, там
   только уголок градиентом) — эта карточка ведёт в Telegram, а не
   отчитывается о доходности, зелёный тут неуместен. Переопределяем
   background отдельно и после общих
   .news-card:hover правил, чтобы выиграть каскад (тот же селектор, тот
   же уровень специфичности). */
.news-card--cta {
  background: rgb(var(--color-white));
  transition: background 0.5s ease;
}

.news-card--cta.is-current {
  background: rgb(var(--color-telegram));
}

@media (hover: hover) and (pointer: fine) {
  .news-card--cta:hover {
    background: rgb(var(--color-telegram));
    transition-duration: 0s;
  }
}

.news-card__cta-link {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  width: 100%;
  height: 100%;
  color: rgb(var(--color-text-on-light));
  text-decoration: none;
  transition: color 0.5s ease;
}

.news-card--cta.is-current .news-card__cta-link {
  color: rgb(var(--color-white));
}

@media (hover: hover) and (pointer: fine) {
  .news-card--cta:hover .news-card__cta-link {
    color: rgb(var(--color-white));
    transition-duration: 0s;
  }
}

.news-card__cta-icon {
  position: absolute;
  top: 0;
  right: 0;
}

.news-card__cta-icon svg {
  width: 7rem; /* подобрано на глаз по скриншоту-референсу */
  height: auto;
}

/* Один трек вместо набора точек — заливка идёт от заполнения по мере
   горизонтального скролла .news__cards (ширину .news__progress-fill
   в % ставит main.js), а не по номеру текущей карточки. */
.news__progress {
  width: 8rem; /* 128px — своё значение, не из Figma (там были точки) */
  height: 1rem; /* 16px, как высота у старых точек */
  background: rgba(var(--color-white), 0.24);
  overflow: hidden;
}

.news__progress-fill {
  height: 100%;
  width: 0%; /* реальное стартовое значение ставит main.js (1/кол-во карточек) */
  background: rgb(var(--color-white));
}

/* Появление при скролле (initScrollReveal, animations.js) — карточки
   рядом друг с другом в горизонтальном скроллере (как hero__stats), у
   второй небольшая задержка. IntersectionObserver по умолчанию учитывает
   обрезку скроллящимся предком (.news__cards, overflow-x:auto) — карточки
   за пределами видимой области скроллера не считаются пересекающими
   вьюпорт, поэтому они также проявляются по мере горизонтального
   скролла карусели, не только вертикального скролла страницы. */
.news__intro,
.news-card {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
}

.news__intro {
  transition: opacity 0.6s ease, transform 0.6s ease;
}

/* Полный список transition для карточки — один раз, тут, а не растащен
   по файлу: помимо ревила (opacity/transform) сюда же background и обе
   @property выше (--news-card-fade-start/-end, decl. рядом с .news-card)
   — иначе этот блок как более поздний в каскаде молча перетирал бы их
   своим более коротким transition (тот же селектор .news-card, тот же
   уровень специфичности — see комментарий у самого .news-card). */
.news-card {
  transition: opacity 0.6s ease, transform 0.6s ease, background 0.75s ease, --news-card-fade-start 0.75s ease, --news-card-fade-end 0.75s ease;
}

/* Только opacity/transform (ревил) — 4 значения, не одно: без списка
   transition-delay: 0.1s размазывался бы по ВСЕМ 5 свойствам из
   transition выше, включая background/--news-card-fade-*. На hover это
   давало на второй карточке 100мс задержку перед мгновенной сменой
   фона — цвет "залипал", а потом дёргано скакал, пока остальные карточки
   красились сразу (поймано вживую, см. чат). */
.news-card:nth-child(2) {
  transition-delay: 0.1s, 0.1s, 0s, 0s, 0s;
}

.news__intro.is-revealed,
.news-card.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 768px) {
  .news {
    /* +0.75rem сверху — см. комментарий у .mission выше. */
    padding: calc(calc(var(--density) * 3.5rem) + 0.75rem) calc(var(--density) * 0.375rem); /* 56 6 */
  }

  .news > .container {
    gap: calc(var(--density) * 2.25rem); /* 36px */
  }

  .news__intro {
    gap: calc(var(--density) * 0.375rem); /* 6px */
    max-width: none;
  }

  .news__cards {
    scroll-snap-type: x mandatory;
    gap: calc(var(--density) * 0.375rem); /* 6px */
  }

  .news-card__cta-text {
    font-size: 3rem;
  }

  /* Карточка чуть уже экрана — не 100%, а с запасом под "выпирающий"
     край соседней, иначе на глаз похоже на один статичный блок, а не
     на карусель (пойманное вживую — не видно, что можно скроллить). У
     первой карточки (scroll-snap-align:start, общее правило выше по
     файлу) выпирает справа следующая. У последней — свой снап именно на
     :last-child (а не общий :start) переворачивает выравнивание на
     конец контейнера, и слева тем же образом выпирает предпоследняя.
     Запас (1.5rem) — это gap (6px) + сам выпирающий край (~18px): меньше
     не читалось бы как явный кусок соседней карточки, больше — теснило
     бы предыдущую версию (3rem) саму себя избыточно (правка пользователя). */
  .news-card {
    flex: 0 0 calc(100% - (var(--density) * 1.5rem));
    height: 15.625rem; /* 250px */
    padding: calc(var(--density) * 0.75rem); /* 12px */
    border-radius: 0.5rem; /* 8px */
  }

  .news-card:last-child {
    scroll-snap-align: end;
  }

  .news-card__head {
    padding-right: 0;
  }

  .news-card__link svg {
    height: 0.8rem;
  }

  .news__progress {
    width: 6rem; /* 96px */
    height: 0.5rem; /* 8px, как высота у старых точек на мобилке */
  }
}

/* ============================================================
   Contact (заявка на консультацию + email)
   ============================================================ */

.contact {
  position: relative;
  overflow: hidden;
  background: rgb(var(--color-bg));
  padding: calc(var(--density) * 8rem) calc(var(--density) * 1.5rem) calc(var(--density) * 3.5rem); /* 128 24 56 */
}

.contact > .container {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 4.5rem); /* 72px */
}

.contact__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
}

.contact__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.contact__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, rgba(var(--color-bg), 0.7), rgba(var(--color-bg), 0.7)),
    linear-gradient(to top, rgb(var(--color-bg)), transparent);
}

.contact__intro {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  max-width: 54.375rem; /* 870px */
  text-align: center;
}

.contact__label {
  color: rgb(var(--color-text-secondary));
}

.contact__title {
  color: rgb(var(--color-text));
}

.contact__form {
  width: 100%;
  max-width: 32.625rem; /* 522px */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 2.25rem); /* 36px */
}

.contact__fields {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 1rem); /* 16px */
  width: 100%;
}

.contact__field {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.contact__field-label {
  color: rgb(var(--color-text-secondary));
}

.contact__products {
  display: flex;
  border-radius: var(--radius);
  overflow: hidden;
}

.contact__product {
  background: rgba(var(--color-white), 0.12);
  color: rgb(var(--color-white));
  border: none;
  cursor: pointer;
  padding: calc(var(--density) * 0.5rem) calc(var(--density) * 1rem); /* 8 16 */
}

.contact__product.is-active {
  background: rgb(var(--color-cta-bg));
  color: rgb(var(--color-cta-text));
}

.contact__inputs {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.5rem); /* 8px */
  width: 100%;
}

.contact__input {
  width: 100%;
  background: rgba(var(--color-white), 0.08);
  border: none;
  border-radius: var(--radius);
  padding: calc(var(--density) * 1rem); /* 16px */
  color: rgb(var(--color-text));
}

.contact__input::placeholder {
  color: rgb(var(--color-text-secondary));
}

.contact__submit {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 0.75rem); /* 12px */
}

.contact__button {
  background: rgb(var(--color-white));
  backdrop-filter: blur(32px);
  color: rgb(var(--color-cta-text));
  border: none;
  cursor: pointer;
  border-radius: var(--radius);
  padding: calc(var(--density) * 1rem) calc(var(--density) * 1.75rem); /* 16 28 */
  transition: background 0.75s ease, color 0.75s ease;
}

/* Тот же hover, что у .button--light (шапка, "Разбор портфеля") —
   мгновенно в зелёный по наведению, плавно обратно. */
.contact__button:hover {
  background: rgb(var(--color-positive));
  color: rgb(var(--color-white));
  transition-duration: 0s;
}

/* iOS-стиль "неверный пароль" — несколько широких, затухающих по амплитуде
   колебаний с ease-in-out между ними, а не частые мелкие шаги: первая
   версия дёргала кнопку каждые 40-50ms (10%-шаги за 0.4s) — на глаз это
   читалось как вибрация телефона, а не как настоящий iOS-шейк (жалоба
   пользователя). Реже опорных точек и длиннее пауза между ними — каждый
   бросок браузер успевает плавно доиграть, а не просто перескочить. */
@keyframes contact-button-shake {
  0%, 100% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-16px);
  }
  50% {
    transform: translateX(12px);
  }
  75% {
    transform: translateX(-6px);
  }
}

.contact__button.is-shaking {
  animation: contact-button-shake 0.5s ease-in-out;
}

/* .contact__status ложится ровно поверх .contact__consent (тот же бокс,
   .contact__consent-wrap — их общий positioning context), не рядом с ним
   и не отдельной зарезервированной строкой — предыдущие заходы упирались
   в одну и ту же связь: .contact__submit не имеет собственной высоты, её
   задаёт содержимое, а .contact (секция) — содержимым .contact__submit,
   и любое изменение высоты где-то внутри там же отражается на
   .contact__bg (main.css, inset:0 + object-fit:cover — пересчитывает
   кроп при любом изменении высоты секции, тот же механизм, что и баг с
   .reports__bg при смене таба). Отдельная строка под статус — либо
   пустой зазор, когда текста нет, либо меняет высоту, когда текст
   появляется. Единственный вариант без зазора и без пересчёта фона —
   вообще не добавлять новую высоту: статус и .contact__consent делят
   одно и то же место.

   Кроссфейд между ними — тот же приём, что переключение таба в
   "принципах" (initPrincipleTabs, main.js): новый текст появляется сразу
   же, не дожидаясь, пока старый уйдёт, но уход быстрее появления (0.2s
   exit против 0.25s entrance — 0.6s пробовали первым, вышло заметно
   медленно, по правке пользователя ужали) — окно, где видны оба,
   получается коротким и однобоким (уходящий уже почти погас, новый
   только начал проявляться), поэтому не читается как каша из двух
   наложенных текстов (более ранняя версия делала оба за одно и то же
   время — 0.4s навстречу друг другу, и вот это уже выглядело как
   настоящее наложение, поймано пользователем на скриншоте). Вертикальный
   сдвиг (translateY) в ту же сторону, что и остальные reveal-анимации
   сайта, тоже помогает глазу разделить "уходит" и "приходит", а не
   просто взгляд на затухание. */
.contact__consent-wrap {
  position: relative;
  width: 100%;
}

.contact__status,
.contact__consent {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.contact__status {
  position: absolute;
  inset: 0;
  color: rgb(var(--color-white));
  text-align: center;
  pointer-events: none;
}

.contact__consent {
  color: rgb(var(--color-text-secondary));
  text-align: center;
}

/* Стартовая поза появления — задаётся синхронно с .no-transition (JS,
   тот же приём, что и .principles__content--fade), снимается кадром
   позже, тогда и начинается настоящий переход. */
.contact__status.is-entering,
.contact__consent.is-entering {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px, как в остальных reveal-анимациях сайта */
}

.contact__status.is-leaving,
.contact__consent.is-leaving {
  opacity: 0;
  transform: translateY(-0.375rem); /* 6px — то же соотношение, что у .principles__content--exit */
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.contact__reach {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(var(--density) * 0.5rem); /* 8px */
  text-align: center;
  width: 100%;
}

.contact__reach-note {
  color: rgb(var(--color-text-secondary));
}

/* Появление при скролле (initScrollReveal, animations.js) — тот же приём,
   что и в остальных секциях. */
.contact__intro,
.contact__form,
.contact__reach {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.contact__intro.is-revealed,
.contact__form.is-revealed,
.contact__reach.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 768px) {
  .contact {
    /* +0.75rem сверху — см. комментарий у .mission выше. */
    padding: calc(calc(var(--density) * 3.5rem) + 0.75rem) calc(var(--density) * 0.375rem); /* 56 6 */
  }

  .contact > .container {
    align-items: flex-start;
    gap: calc(var(--density) * 2.25rem); /* 36px */
  }

  .contact__intro {
    align-items: flex-start;
    text-align: left;
    max-width: none;
    gap: calc(var(--density) * 0.375rem); /* 6px */
  }

  .contact__field {
    align-items: flex-start;
    width: 100%;
  }

  .contact__product {
    padding: calc(var(--density) * 0.375rem) calc(var(--density) * 0.75rem); /* 6 12 */
  }

  .contact__submit {
    align-items: flex-start;
  }

  .contact__status,
  .contact__consent {
    text-align: left;
  }

  .contact__button {
    width: 100%;
  }

  .contact__reach {
    align-items: flex-start;
    text-align: left;
  }
}

/* ============================================================
   Footer
   ============================================================ */

.site-footer {
  background: rgb(var(--color-bg));
  padding: calc(var(--density) * 3.5rem) calc(var(--density) * 1.5rem); /* 56 24 */
}

.site-footer__wordmark {
  display: block;
  width: 100%;
  height: auto;
}

.site-footer__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: calc(var(--density) * 1.5rem); /* 24px */
}

.site-footer__col {
  display: flex;
  flex-direction: column;
  gap: calc(var(--density) * 0.75rem); /* 12px */
  color: rgb(var(--color-text-secondary));
}

/* Переход по * из .mission-stat приводит сюда — среди нескольких сносок
   подряд непонятно, какую именно открыли. .is-highlighted навешивает
   main.js (initFootnoteLinks) — не :target, см. .mission-stat__label
   выше про то, почему. @keyframes footnote-highlight общий на оба. */
.site-footer__note.is-highlighted {
  animation: footnote-highlight 4s ease forwards;
}

.site-footer__link {
  color: rgb(var(--color-text));
}

.site-footer__col--credit {
  align-items: flex-end;
  justify-content: flex-end;
  padding: calc(var(--density) * 0.625rem); /* 10px */
  text-align: right;
}

/* Кликабельна только сама пара иконка+текст (ссылка на magomed.ae), не вся
   колонка футера — .site-footer__col--credit остаётся просто контейнером
   разметки/отступов. stroke="currentColor" в svg (index.pug) — иконка
   красится тем же color, что и текст, без него img/svg-файл цвет менять
   не умеет. */
.site-footer__credit {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: calc(var(--density) * 0.75rem); /* 12px, как .site-footer__col */
  text-decoration: none;
  transition: color 0.5s ease;
}

.site-footer__credit:hover {
  color: rgb(var(--color-text));
  transition-duration: 0s;
}

.site-footer__credit-icon {
  width: 2.975rem; /* 47.6px */
  height: 2.875rem; /* 46px */
}

/* Появление при скролле (initScrollReveal, animations.js) — тот же приём,
   что и в остальных секциях: вордмарк и три колонки сетки, у второй и
   третьей колонки небольшая задержка (стоят в ряд, как about__stats). */
.site-footer__wordmark,
.site-footer__col {
  opacity: 0;
  transform: translateY(1.5rem); /* 24px */
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.site-footer__col:nth-child(2) {
  transition-delay: 0.1s;
}

.site-footer__col:nth-child(3) {
  transition-delay: 0.2s;
}

.site-footer__wordmark.is-revealed,
.site-footer__col.is-revealed {
  opacity: 1;
  transform: translateY(0);
}

@media (max-width: 768px) {
  .site-footer {
    padding: calc(var(--density) * 8rem) calc(var(--density) * 0.375rem) calc(var(--density) * 1.5rem); /* 128 6 24 */
  }

  .site-footer__grid {
    display: flex;
    flex-direction: column;
    gap: calc(var(--density) * 2.25rem); /* 36px */
  }

  .site-footer__col--credit {
    align-items: flex-start;
    justify-content: flex-start;
    padding: 0;
    text-align: left;
    opacity: 0.5; /* в Figma доп. приглушение поверх уже приглушённого цвета */
  }

  .site-footer__credit {
    align-items: flex-start;
  }

  .site-footer__credit-icon {
    width: 1.55rem; /* 24.8px */
    height: 1.5rem; /* 24px */
  }

  .site-footer__link,
  .site-footer__col--notes a {
    text-decoration-thickness: var(--border-width-s); /* 1px, тоньше чем на десктопе */
  }
}

/* ============================================================
   Курсор
   ============================================================
   По образцу tumanstudio.com/en — точка вместо системного курсора,
   mix-blend-mode:difference инвертирует то, что под ней, поэтому видна
   на любом фоне (тёмном/светлом) без ручного подбора цвета под каждую
   секцию. Только десктоп с настоящей мышью (initCustomCursor,
   animations.js, тот же гейт hover:hover+pointer:fine, что у
   initSpotlight) — на тач-устройствах системного курсора и так нет. */
.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  pointer-events: none;
  transform: translate(-50%, -50%);
  mix-blend-mode: difference;
  opacity: 0; /* до первого mousemove (initCustomCursor, animations.js) реальных координат нет */
}

.cursor.is-positioned {
  opacity: 1;
}

.cursor__dot {
  width: 1.25rem; /* 20px */
  height: 1.25rem;
  border-radius: 50%;
  background: rgb(var(--color-white));
  transition: transform 0.2s cubic-bezier(0.4, 0, 1, 1);
}

.cursor.is-hover .cursor__dot {
  transform: scale(2);
}

/* Системный курсор гасим только там же, где рисуем свою точку (тот же
   гейт) — иначе после инициализации остались бы оба разом. !important
   осознанно: у кнопок/ссылок в проекте уже есть свои cursor:pointer
   (main.css) — они специфичнее .cursor-body и без !important выигрывали
   бы у него. */
@media (hover: hover) and (pointer: fine) {
  .cursor-body,
  .cursor-body * {
    cursor: none !important;
  }
}
