/* ==========================================================================
   Talant — лендинг.

   Страница выглядит как само приложение, а не как рассказ о нём. Палитра снята
   пипеткой с его экранов, а не подобрана на глаз: подложка #1A1A1A, карточки
   #211E21…#252024, фиолетовые контейнеры #221925…#2C2236, кнопка #7354B0.
   Отсюда и ground страницы — тем же фиолетовым уведённый в тень, чтобы снимки
   приложения лежали НА странице, а не растворялись в ней.

   Фирменный фиолетовый #8D67E3 — это seed темы приложения. Он здесь
   единственный цвет и работает тремя разными значениями, потому что одно
   значение не проходит по контрасту во всех трёх ролях: заливка кнопки,
   акцент в тексте и крупная цифра — это три разные задачи.

   Шрифтовых файлов не подключаем: внешних запросов у страницы нет вообще, и
   поддомен не зависит ни от чьего аптайма.
   ========================================================================== */

/* ── Токены ────────────────────────────────────────────────────────────── */
:root {
  --bg:        #0D0C10;  /* ground */
  --surface:   #16151B;  /* карточка */
  --surface-2: #1E1C25;  /* приподнятая карточка */
  --violet:    #211A2E;  /* фиолетовый контейнер — как в приложении */

  --line:   rgba(242, 240, 247, 0.10);
  --line-2: rgba(242, 240, 247, 0.20);

  --text:   #F2F0F7;  /* 17.3:1 на ground */
  --text-2: #A8A4B8;  /*  8.1:1 */
  --text-3: #8B8799;  /*  5.6:1 (4.8:1 на приподнятой) */

  --ok:   #55D391;  /* статус замера — отдельно от фирменного цвета: */
  --warn: #F0B23E;  /* «хорошо / контроль / травма» это состояние, */
  --bad:  #EF6B6B;  /* а не акцент бренда */

  /* Свет на странице ОДИН: пятна подсветки первого экрана стоят в правом
     верхнем углу, оттуда же светит на все поверхности — карточки, значки,
     кнопку, картинки шагов. Пока у карточки свет падал слева, а у комнаты
     справа, лист читался коллажем из чужих макетов. */
  --lit: 200deg;

  --brand:      #8D67E3;  /* seed приложения: крупный текст, заливки, границы */
  --brand-deep: #5B3AA8;  /* тёмный конец градиента полосы прогресса */
  --brand-ink: #A98CEC;  /* акцент в тексте обычного кегля — 7.1:1 */
  --btn:       #7F58D8;  /* кнопка: белый по ней 4.9:1 */
  --btn-deep:  #6E4BC9;  /* дальний конец заливки: белый по нему 5.8:1 */
  /* Прежний #8D67E3 по наведению давал белому 3.9:1 — ниже нормы: контраст
     текста считается во ВСЕХ состояниях, а не только в покое. */
  --btn-hover:      #8058D9;  /* 4.87:1 */
  --btn-hover-deep: #7C55D5;

  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Мера строки — одна на всю страницу: ≈66 знаков. В em, поэтому число
     знаков одинаково и при 17 px, и при 19 px. */
  --measure: 34em;

  --page: 1200px;
  --pad: clamp(20px, 5vw, 48px);
  --gap: clamp(16px, 2.2vw, 28px);
  --section-y: clamp(66px, 8.4vw, 118px);

  --r-card: 16px;
  --r-btn: 12px;
  --r-chip: 8px;
}

/* ── База ──────────────────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  background: var(--bg);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

@media (min-width: 1100px) { body { font-size: 19px; } }

img { display: block; max-width: 100%; height: auto; }
a { color: inherit; }
h1, h2, h3, p, ul, ol, figure, table, blockquote { margin: 0; }
ul { padding: 0; list-style: none; }

:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }

.skip {
  position: absolute;
  left: 0; top: 0;
  z-index: 50;
  padding: 12px 20px;
  background: var(--btn);
  color: #fff;
  font-size: 14px;
  transform: translateY(-120%);
}
.skip:focus { transform: translateY(0); }

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

/* ── Раскладка ─────────────────────────────────────────────────────────── */
.wrap {
  max-width: var(--page);
  margin-inline: auto;
  padding-inline: var(--pad);
}

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

/* Приподнятая полоса — единственный способ задать странице ритм, когда весь
   лист тёмный: чередуется не яркость текста, а высота поверхности. */
.section--raised {
  background: var(--surface);
  border-block: 1px solid var(--line);
}

.section--violet { background: linear-gradient(180deg, var(--violet), var(--bg) 70%); }

/* ── Типографика ───────────────────────────────────────────────────────── */
/* Трекинг и интерлиньяж — ФУНКЦИИ кегля, а не два числа на весь диапазон:
   1em в этих свойствах и есть собственный кегль элемента, поэтому одна строка
   обслуживает весь clamp(). Крупный кегль стоит плотнее мелкого — на 40 px
   выходит −0.023em и 1.06, на 73.6 px −0.035em и 1.02. Строка с фиксированным
   значением стоит первой: браузер без calc() возьмёт её и получит сегодняшнее
   поведение. */
h1, h2 {
  font-weight: 700;
  line-height: 1.03;
  line-height: calc(0.98em + 3px);
  letter-spacing: -0.03em;
  letter-spacing: calc(1.1px - 0.05em);
  text-wrap: balance;
}

h1 { font-size: clamp(2.4rem, 5vw, 4.2rem); }
h2 { font-size: clamp(1.9rem, 3.8vw, 3.2rem); }

h3 {
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.005em;
}

/* Мера строки — не длиннее 66 знаков: дальше глаз теряет начало следующей. */
.lede {
  max-width: 32em;
  font-size: 1.125em;
  line-height: 1.55;
  color: var(--text-2);
}

.prose { max-width: var(--measure); color: var(--text-2); }

/* Перенос: прозе — pretty (не оставляет висячее слово в последней строке),
   коротким подписям — balance (делит на равные строки). Значение молодое, и
   браузер без поддержки просто отбрасывает объявление. */
.lede, .prose, .aside, .cta p, .foot p, .card p, .row-hint { text-wrap: pretty; }
.stats p, .features span, .shot figcaption { text-wrap: balance; }

.eyebrow {
  display: block;
  margin-bottom: 18px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--brand-ink);
}

/* Засечка перед надзаголовком — тот же знак, что засечка замера на линейке
   нормы: одна мера разметки на всю страницу. */
.eyebrow::before {
  content: '';
  display: inline-block;
  width: 20px;
  height: 2px;
  margin-right: 10px;
  vertical-align: 3px;
  background: currentColor;
}

/* Цифры выстраиваются в столбцы — и в статистике, и в таблице, и в шкалах. */
.num { font-variant-numeric: tabular-nums lining-nums; }

.head { margin-bottom: clamp(36px, 5vw, 64px); }

/* Поле под заголовком — доля его кегля, а не фиксированное число: заголовок
   течёт от 30 до 51 px, и постоянные 20 px под ним на широком экране
   оказывались ближе, чем строки внутри самого заголовка. */
.head h2 {
  margin-bottom: 20px;
  margin-bottom: max(18px, 0.46em);
  max-width: 17em;
}

/* Оптическая левая ось: у крупного кегля левый полуапрош круглой буквы
   физически отодвигает заголовок от поля, и он встаёт правее засечки
   надзаголовка и прозы под ним. На 40 px это 0.7 px, на 74 px — 1.3 px. */
.hero h1,
.head h2,
.cta h2 { margin-left: -0.018em; }

/* ── Шапка ─────────────────────────────────────────────────────────────── */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg);   /* запасное значение: без color-mix шапка остаётся
                              непрозрачной, а не исчезает */
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid transparent;
}
.topbar.is-stuck { border-bottom-color: var(--line); }

/* Без поддержки color-mix/backdrop-filter шапка обязана остаться непрозрачной:
   полупрозрачной она пропустила бы под себя текст. */
@supports not (backdrop-filter: blur(1px)) {
  .topbar { background: var(--bg); }
}

.topbar-in {
  display: flex;
  align-items: center;
  gap: 20px;
  min-height: 72px;
}

.brand { display: flex; align-items: center; gap: 14px; text-decoration: none; }
.brand img { width: 118px; }

.brand span {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  display: none;
}
@media (min-width: 760px) { .brand span { display: block; } }

.nav { margin-left: auto; }
.nav ul { display: flex; gap: 8px; }

.nav a {
  display: block;
  padding: 9px 14px;
  border-radius: var(--r-chip);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--text-2);
  text-decoration: none;
}
.nav a:hover { color: var(--text); background: var(--surface-2); }
.nav a[aria-current] { color: var(--text); background: var(--violet); }

.nav-toggle {
  display: none;
  margin-left: auto;
  padding: 10px 16px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-btn);
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  cursor: pointer;
}

@media (max-width: 880px) {
  .nav-toggle { display: block; }
  .nav {
    position: absolute;
    left: 0; right: 0; top: 100%;
    display: none;
    padding: 8px var(--pad) 20px;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
  }
  .nav.is-open { display: block; }
  .nav ul { flex-direction: column; gap: 2px; }
  .nav a { padding: 13px 14px; }
}

/* ── Кнопки ────────────────────────────────────────────────────────────── */
.actions { display: flex; flex-wrap: wrap; gap: 12px; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 15px 24px;
  border: 1px solid transparent;
  border-radius: var(--r-btn);
  font-family: var(--sans);
  font-size: 0.9375rem;
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.16s, border-color 0.16s, color 0.16s;
}

.btn svg { flex: none; }

/* Единственная залитая цветом плоскость страницы — и единственная, у которой
   есть грань: заливка идёт по оси общего света, кромка светлеет сверху и
   темнеет снизу. Оба конца — тот же фирменный фиолетовый, третьего значения не
   заводится. Верхняя точка ровно #7F58D8: белый по ней 4.9:1, это потолок. */
.btn--primary {
  background:
    linear-gradient(var(--lit), var(--btn), var(--btn-deep)) padding-box,
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.30),
      rgba(255, 255, 255, 0.08) 38%,
      rgba(0, 0, 0, 0.22) 72%,
      rgba(0, 0, 0, 0.34)) border-box;
  color: #fff;
}

.btn--primary:hover {
  background:
    linear-gradient(var(--lit), var(--btn-hover), var(--btn-hover-deep)) padding-box,
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.38),
      rgba(255, 255, 255, 0.10) 38%,
      rgba(0, 0, 0, 0.20) 72%,
      rgba(0, 0, 0, 0.30)) border-box;
}

.btn--ghost { border-color: var(--line-2); color: var(--text); }
.btn--ghost:hover { border-color: var(--text-3); background: var(--surface-2); }

/* ── Карточка ──────────────────────────────────────────────────────────── */
/* Тени нет намеренно: приложение разделяет поверхности границей и высотой
   подложки, тень под карточкой на тёмном читается грязным пятном. */
.card {
  --card-bg: var(--surface);
  padding: clamp(22px, 2.6vw, 30px);
  border: 1px solid transparent;
  border-radius: var(--r-card);
  /* Кант светится там, где курсор: заливка в padding-box, градиент в
     border-box. Без скрипта --mx/--my не заданы, подставляется центр — кант
     просто ровный, и ничего не ломается. */
  background:
    /* Подложка не плоская: на неё падает тот же свет, что и на первый экран, а
       к дальнему углу поверхность гаснет. 5.5% белого — предел, за которым
       намыв читается пятном, а не материалом. */
    linear-gradient(var(--lit),
      rgba(255, 255, 255, 0.055), rgba(255, 255, 255, 0) 46%) padding-box,
    linear-gradient(var(--card-bg), var(--card-bg)) padding-box,
    /* Пятно под курсором уходит в ПРОЗРАЧНОСТЬ, а не в ровный серый: под ним
       остаётся видна направленная кромка. */
    radial-gradient(280px circle at var(--mx, 50%) var(--my, 0%),
      rgba(141, 103, 227, 0.55), rgba(141, 103, 227, 0) 44%) border-box,
    /* Кромка светлая сверху и уходит в тень снизу — у любого физического
       предмета верх ловит свет, а низ его теряет. Тени ПОД карточкой по-прежнему
       нет: тень живёт в самой кромке, толщиной в один пиксель. */
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.16),
      rgba(242, 240, 247, 0.085) 34%,
      rgba(242, 240, 247, 0.05) 70%,
      rgba(0, 0, 0, 0.32)) border-box;
  transform: perspective(1000px) rotateX(var(--tx, 0deg)) rotateY(var(--ty, 0deg));
  transition: transform 0.4s cubic-bezier(0.22, 0.75, 0.3, 1);
}

.section--raised .card { --card-bg: var(--surface-2); }
.card:hover { transition-duration: 0.12s; }

.card h3 { margin-bottom: 8px; }
.card p { color: var(--text-2); font-size: 0.9375rem; line-height: 1.5; }

.features li { transition: background-color 0.18s; }
/* Обе записи одной специфичности: на приподнятой полосе заливка плитки задана
   селектором `.section--raised .features li` ниже по файлу, и одиночный
   `.features li:hover` её не перебивал — отклика там не было вовсе. */
.features li:hover,
.section--raised .features li:hover { background: var(--surface-2); }

/* Стрелка уезжает вперёд — единственная микроанимация на кнопке. */
.btn svg { transition: transform 0.18s; }
.btn:hover svg { transform: translateX(3px); }

.grid { display: grid; gap: var(--gap); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

@media (max-width: 900px) { .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) {
  .grid--3, .grid--2 { grid-template-columns: minmax(0, 1fr); }
}

/* ── Снимок экрана ─────────────────────────────────────────────────────── */
.shot {
  margin: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;       /* скругление обязано обрезать и саму картинку */
  background: var(--surface);
}

.shot img { width: 100%; }

.shot figcaption {
  padding: 14px 18px;
  border-top: 1px solid var(--line);
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--text-3);
}

/* ── Первый экран ──────────────────────────────────────────────────────── */
/* Свечение уходит за кадр на 38% его ширины — за узким экраном это уже вылет за
   правое поле и горизонтальный скролл у всей страницы. Режем по границе полосы:
   на таком удалении подсветка уже прозрачна, и обрыва не видно. Sticky-шапка
   лежит ВНЕ первого экрана, поэтому overflow ей ничего не ломает. */
.hero {
  position: relative;
  isolation: isolate;   /* иначе подсветка с z-index:-2 уехала бы за фон body */
  /* Поле сверху считается и по ширине, и по ВЫСОТЕ окна: на низком экране
     96 px воздуха над заголовком отнимаются у кадра, который и так уменьшается
     ради того, чтобы поместиться целиком. */
  padding-top: clamp(36px, min(6vw, 7.5vh), 84px);
  padding-bottom: var(--section-y);
  overflow: hidden;
}

/* Заголовок занимает всю полосу, под ним две колонки: слева довод и кнопки,
   справа телефон с фигурой. В колонке заголовок разваливался на пять строк по
   два слова — на полосе он ложится в две. */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "head head"
    "text figure";
  column-gap: clamp(32px, 5vw, 72px);
  row-gap: clamp(22px, 2.6vw, 34px);
  align-items: start;
}

.hero-head { grid-area: head; }
.hero-text { grid-area: text; }
.hero-figure { grid-area: figure; }

/* Поле под заголовком держит row-gap сетки: он отделяет баннер от обеих
   колонок сразу, и второе поле у самого h1 сложилось бы с ним вдвое. */
.hero h1 { margin-bottom: 0; }

.hero .lede { margin-bottom: 26px; }

/* Три области — строка-подпись под заявлением, а не три кнопки: это ОБЛАСТИ
   измерения, и вести из них некуда. Точка-разделитель ставится CSS'ом, чтобы
   в разметке не завелось пустых элементов ради значка. */
.domains {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin-top: clamp(16px, 1.8vw, 22px);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-2);
}

.domains span { display: flex; align-items: center; gap: 14px; }

.domains span + span::before {
  content: '';
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--brand);
}

/* ── Первый экран: телефон с отчётом и тот, кого он измерил ────────────── */
/* Здесь по очереди стояли снимок памятки о съёмке, кадр в рамке и одна
   вырезанная фигура. Каждый раз не хватало ГЛАВНОГО: тренер не видел, что
   именно придёт ему в приложение. Теперь на первом экране два объекта —
   телефон с готовым отчётом (что получишь) и человек со скелетом (из чего он
   посчитан). Телефон впереди и крупнее: продают не фотографию спортсменки.

   Экран телефона нарисован ВЕКТОРОМ: снимка этого экрана у страницы нет, а
   мелкий текст снимка на 300 px превращается в кашу — это видно на соседнем
   `app-tests.webp`. Числа берутся из тех же SCORE/ROWS, что раздел «Отчёт».

   Размеры телефона ФИКСИРОВАННЫЕ, а не текучие: внутри экрана настоящая
   вёрстка в пикселях, и тянуть её вместе с колонкой значит гонять кегли по
   долям пикселя. Меняется размер ступенями, на двух брейкпоинтах. */
.hero-figure {
  position: relative;
  isolation: isolate;
  width: max-content;
  margin-left: auto;
}

/* Свечение — свет на объекте, а не заливка фона: центр смещён к телефону, и к
   краям он гаснет, не оставляя вокруг композиции ореола-наклейки. */
.hero-figure::before {
  content: '';
  position: absolute;
  inset: -8% -16% -4%;
  z-index: -1;
  background: radial-gradient(closest-side at 62% 50%,
    rgba(141, 103, 227, 0.34),
    rgba(141, 103, 227, 0.25) 30%,
    rgba(141, 103, 227, 0.15) 50%,
    rgba(141, 103, 227, 0.07) 66%,
    rgba(141, 103, 227, 0.03) 80%,
    transparent 92%);
  pointer-events: none;
}

/* Оба объекта стоят на одной линии низа — как на полке. Телефон наезжает на
   панель, а не стоит рядом: два отдельных предмета читались бы коллажем, а
   перекрытие связывает их в одну сцену. */
.stage { display: flex; align-items: flex-end; }

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

   Скелет нарисован языком продукта: белые кости, фиолетовые точки с ореолом,
   тонкие дуги углов с числом — те же цвета и пропорции, что в рендере
   обработанного видео (`video_processor._draw_skeleton_styled`: кость 2 px,
   точка r5, ореол r11 при α0.3) и в live-скелете приложения
   (`SkeletonPainter`: кость 2.5, точка 4.5). Связи тоже продуктовые: нос к
   обоим плечам, линии поясов, руки и ноги ДО голеностопа — стопы во
   фронтальной съёмке продукт не рисует, они там зашумлены. */
.pose {
  flex: none;
  position: relative;
  width: 306px;
  /* Телефон заходит на панель ровно на ширину поля: край панели остаётся
     виден полосой, и слои читаются стопкой, а не одной склеенной картинкой.
     Заход мельче прежних 76 px: скелет доходит рукой почти до правого края
     кадра, и на 76 px телефон срезал кисть. */
  margin: 0 -72px 0 0;
  border-radius: 30px;
  border: 1px solid var(--line);
  background:
    radial-gradient(120% 80% at 70% 6%, rgba(141, 103, 227, 0.18), transparent 70%),
    linear-gradient(var(--lit), #161520, #0C0B10);
  overflow: hidden;
}

/* Разметка кадра: та же сетка, что лежит на первом экране, только плотнее.
   Она даёт панели масштаб — без неё модель висела бы в пустоте. */
.pose::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(242, 240, 247, 0.05) 1px, transparent 1px),
    linear-gradient(90deg, rgba(242, 240, 247, 0.05) 1px, transparent 1px);
  background-size: 30px 30px;
  mask-image: radial-gradient(120% 78% at 50% 34%, #000 30%, transparent 78%);
  pointer-events: none;
}

.pose-fig { display: block; width: 100%; height: auto; padding: 0 14px; }

/* Водяной знак, тайм-код и шкала — приметы КАДРА: без них панель читается
   картинкой, а это момент из видео, по которому и посчитан отчёт. Знак стоит
   там же, где его выжигает бэкенд (`video_processor._draw_watermark`) — в
   левом верхнем углу и вполсилы. */
.frame-mark {
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 2;
  opacity: 0.34;
}
.frame-mark img { width: 78px; display: block; }

.frame-time {
  position: absolute;
  top: 18px;
  right: 16px;
  z-index: 2;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: rgba(242, 240, 247, 0.62);
}

.frame-bar {
  position: relative;
  height: 3px;
  margin: 0 14px 14px;
  border-radius: 2px;
  background: rgba(242, 240, 247, 0.14);
}
.frame-bar i {
  position: absolute;
  inset: 0 38% 0 0;
  border-radius: 2px;
  background: var(--brand);
}
.frame-bar b {
  position: absolute;
  left: 62%;
  top: 50%;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #F2F0F7;
  translate: -50% -50%;
  box-shadow: 0 0 0 3px rgba(141, 103, 227, 0.45);
}

.app-bones { stroke: #FFFFFF; stroke-linecap: round; stroke-linejoin: round; }

/* Точка сустава — ядро с ореолом: одиночный кружок терялся между костями, а
   ореолом продукт помечает точки и на самом кадре. */
.app-glow circle { fill: rgba(141, 103, 227, 0.3); }
.app-dots circle { fill: #8D67E3; }

/* Дуга угла стоит там же, где её меряет продукт, и подписана тем же числом,
   что выжжено в кадр обработанного видео. */
.app-arc { stroke: #8D67E3; stroke-linecap: round; }
.app-deg {
  fill: #C8B0F5;
  font-weight: 700;
  text-anchor: middle;
  paint-order: stroke;
  stroke: rgba(9, 8, 12, 0.85);
  stroke-width: 6;
}

/* Подпись обходит телефон: он наезжает на панель справа, и строка, набранная
   во всю её ширину, обрывалась обрубком «33 точки те…» под чужим краем —
   хуже, чем её отсутствие. Правое поле — заход телефона плюс его тень. */
.pose-cap {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 12px 88px 14px 16px;
  border-top: 1px solid var(--line);
  background: rgba(13, 12, 16, 0.45);
}

.pose-cap b { font-size: 13px; font-weight: 600; }
.pose-cap span { font-size: 11.5px; color: var(--text-3); }

/* ── Телефон ───────────────────────────────────────────────────────────── */
.phone {
  flex: none;
  position: relative;
  /* 300 px — не «крупнее ради крупности»: на 264 px названия показателей
     переносились на вторую строку, и экран становился ВЫШЕ, чем на 300. */
  width: 300px;
  /* Телефон СВЕШИВАЕТСЯ ниже кадра, а не стоит с ним на одной линии низа. В
     вертикальном кадре 9:16 поднятая рука доходит до правой кромки — на общей
     линии низа телефон срезал бы кисть, а по ней как раз и видно заведение
     руки над головой (показатель «плечевой пояс ↔ рука»). Свешиваясь, он
     открывает верхний угол кадра и заодно перестаёт читаться приклеенным к
     панели. */
  margin-bottom: -58px;
  padding: 9px;
  border-radius: 38px;
  border: 1px solid transparent;
  /* Корпус вылеплен тем же светом, что и кнопка: блик по верхней кромке,
     тень по нижней. Тень падает ВЛЕВО, на панель кадра: телефон лежит на ней,
     и без тени два слоя слипались в одну плоскую картинку. На страницу тень
     почти не выходит — там она читалась бы пятном на тёмном. */
  box-shadow: -26px 20px 48px rgba(6, 5, 9, 0.55);
  background:
    linear-gradient(var(--lit), #262330, #131218) padding-box,
    linear-gradient(180deg,
      rgba(255, 255, 255, 0.30),
      rgba(255, 255, 255, 0.07) 32%,
      rgba(0, 0, 0, 0.35) 72%,
      rgba(0, 0, 0, 0.45)) border-box;
}

/* Подложка экрана — из приложения (#1A1A1A), а не из палитры страницы: это
   снимок продукта, и он обязан выглядеть как продукт. */
.phone-screen {
  display: flex;
  flex-direction: column;
  border-radius: 30px;
  overflow: hidden;
  background: #1A1A1A;
  color: #F2F0F7;
  font-size: 12px;
  line-height: 1.35;
}

.ph-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 9px 18px 5px;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* Батарея — единственная «иконка» строки состояния: остальное на таком кегле
   всё равно неразличимо, а пустая строка выдала бы вектор за снимок. */
.ph-bar i {
  width: 24px;
  height: 11px;
  border: 1px solid rgba(242, 240, 247, 0.45);
  border-radius: 3px;
  position: relative;
}

.ph-bar i::before {
  content: '';
  position: absolute;
  inset: 1.5px;
  right: 40%;
  border-radius: 1px;
  background: rgba(242, 240, 247, 0.75);
}

.ph-head { padding: 8px 16px 10px; }

.ph-eyebrow {
  display: block;
  margin-bottom: 4px;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #A98CEC;
}

.ph-head b { display: block; font-size: 14px; font-weight: 600; letter-spacing: -0.01em; }
.ph-note { font-size: 11px; color: #8B8799; }

.ph-score {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 12px 8px;
  padding: 9px 12px;
  border-radius: 14px;
  background: linear-gradient(var(--lit), #2C2236, #221925);
}

.gauge--ph { flex: none; width: 46px; height: 46px; }
.ph-score b { display: block; font-size: 13px; font-weight: 600; }
.ph-score span { font-size: 11px; color: #8B8799; }

.ph-rows { border-top: 1px solid rgba(242, 240, 247, 0.07); }

/* Строка замера: точка статуса слева, название и статус словом в первой
   строке, число и норма — во второй. Раскладка задана ЯВНО по клеткам:
   автоматическая расстановка ставила статус под точку, и строка ломалась. */
.ph-rows li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  align-items: center;
  gap: 3px 9px;
  padding: 7px 16px;
}

.ph-rows li + li { border-top: 1px solid rgba(242, 240, 247, 0.07); }

.ph-rows .dot {
  grid-area: 1 / 1 / 3 / 2;
  width: 7px;
  height: 7px;
  align-self: center;
}

.ph-rows b { grid-area: 1 / 2 / 2 / 3; font-size: 11.5px; font-weight: 600; }

.ph-rows .status {
  grid-area: 1 / 3 / 2 / 4;
  margin: 0;
  font-size: 8.5px;
  letter-spacing: 0.1em;
  justify-self: end;
}

.ph-rows i {
  grid-area: 2 / 2 / 3 / 3;
  font-style: normal;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.ph-norm {
  grid-area: 2 / 3 / 3 / 4;
  font-size: 10px;
  color: #8B8799;
  white-space: nowrap;
  justify-self: end;
}

/* Кнопка экрана — той же заливкой, что кнопка страницы: одно приложение. */
.ph-btn {
  margin: 10px 12px 12px;
  padding: 10px;
  border-radius: 12px;
  background: linear-gradient(var(--lit), var(--btn), var(--btn-deep));
  text-align: center;
  font-size: 12px;
  font-weight: 600;
}

@media (max-width: 1099px) {
  .pose { width: 266px; margin-right: -62px; }
  .phone { width: 272px; }
}

@media (max-width: 860px) {
  /* Области ОБЯЗАНЫ переобъявляться вместе с колонками: «head head» держит
     вторую колонку сама по себе, и одна `grid-template-columns` её не
     схлопывает — телефон оставался во второй колонке и выдавливал текст в
     ленту шириной в слово. */
  .hero-grid {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "head"
      "text"
      "figure";
  }

  /* В одну колонку композиция держит левую ось страницы, а не середину: по ней
     стоят и заголовок, и лид, и кнопки. Ширина при этом ОТПУСКАЕТСЯ: на двух
     колонках блок меряется содержимым (`width: max-content`), а в одной это
     же правило сжимало сцену до 258 px из 450 — панель кадра уезжала в
     `min-width`, и скелет в ней читался марочкой. */
  .hero-figure { margin-left: 0; width: auto; }
}

/* На узкой колонке два объекта рядом не помещаются, поэтому сцена
   поворачивается: панель кадра сверху слева, телефон — снизу справа, и он
   по-прежнему НАЕЗЖАЕТ на неё, а не стоит следом. Прятать панель за телефон
   (так было у вырезанной фигуры) больше нельзя: у неё своя подложка, и из-под
   телефона торчала бы тёмная полоса без смысла. */
@media (max-width: 560px) {
  .stage { display: grid; justify-items: start; }

  .pose { width: 62%; min-width: 186px; margin: 0; }

  /* Телефон ужимается, иначе он шире колонки: 300 px плюс поля страницы не
     помещаются в 360 px вьюпорта. Уже 252 px нельзя — на 264 названия
     показателей уже переносились, и экран становился ВЫШЕ, чем на 300. */
  .phone { width: 252px; justify-self: end; margin: -76px 0 0; }

  /* Подпись панели телефон здесь перекрывает наполовину — обрубок «33 точки
     те…» хуже, чем его отсутствие: что это за кадр, сказано чипом сверху. */
  .pose-cap { display: none; }
}

/* Контейнерные запросы текстовой колонки держат полосу статистики: по ширине
   окна её не предсказать — колонка зависит ещё и от того, сколько забрал
   телефон. Браузер без поддержки правило просто не применит. */
.hero-text { container-type: inline-size; }

/* Три числа — список в текстовой колонке. Полосой во всю ширину они отъедали
   у первого экрана сотню пикселей высоты и уезжали за сгиб; строкой под
   кнопками работают последним доводом перед кликом. Цифра и подпись стоят в
   одну строку: столбиком список вытягивался вдвое. */
.stats {
  display: grid;
  margin-top: clamp(22px, 2.8vw, 32px);
  max-width: 30em;
}

.stats li {
  display: grid;
  /* Колонка числа ФИКСИРОВАННАЯ: у «33» и «~2 мин» разная ширина, и по
     содержимому подписи трёх строк начинались бы в трёх разных местах. */
  grid-template-columns: 7.4rem minmax(0, 1fr);
  align-items: baseline;
  gap: 4px 16px;
  padding: 11px 0;
  border-top: 1px solid var(--line);
}

.stats b {
  font-size: clamp(1.4rem, 2vw, 1.85rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.03em;
  letter-spacing: calc(0.8px - 0.045em);
  color: var(--brand);
}

.stats span {
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--text-2);
}

@container (max-width: 380px) {
  /* В узкой колонке подпись под числом, а не рядом: иначе она рвётся на три
     строки об коротенькую колонку. */
  .stats li { grid-template-columns: minmax(0, 1fr); }
}

/* ── Шаги ──────────────────────────────────────────────────────────────── */
/* Номер здесь настоящий: снять → проанализировать → получить отчёт идут
   строго по порядку, и переставить их нельзя. */
.step-num {
  display: block;
  margin-bottom: 14px;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--brand-ink);
}

/* ── Отчёт ─────────────────────────────────────────────────────────────── */
/* Обе карточки ровно одной высоты. Раньше они стояли по своим размерам и
   расходились на полсотни пикселей; растянуть их полем между снимком и
   подписью нельзя — такая полоса читается дефектом отрисовки.
   Решение: снимок телефона ТЯНЕТСЯ до нужной высоты и подрезается снизу
   (`object-fit: cover`, привязка к верху). Подрез честный — это список
   тестирований, он и на самом деле продолжается за нижним краем экрана. */
.report-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 232px;
  gap: var(--gap);
  align-items: stretch;
  margin-bottom: clamp(32px, 4vw, 52px);
}

.report-layout .shot { display: flex; flex-direction: column; }

.report-layout .shot--phone img {
  flex: 1;
  min-height: 0;         /* иначе flex-элемент не даст себя сжать */
  object-fit: cover;
  object-position: top;
}

/* Подписи у обеих карточек прижаты к низу — и оказываются на одной линии. */
.report-layout figcaption { margin-top: auto; }

/* Строки разбора одной высоты: у пояснений разная длина, и без этого нижняя
   строка «съезжала» относительно соседней карточки. */
.report .rows { grid-auto-rows: 1fr; }

@media (max-width: 900px) {
  .report-layout { grid-template-columns: minmax(0, 1fr); }
  .report-layout .shot--phone { max-width: 260px; }
  .report-layout .shot--phone img { flex: none; object-fit: fill; }
}

.features {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  /* Шов между плитками — это заливка контейнера, видная в грид-щелях шириной
     1 px. Она сгущается к середине и тает у рамки: внутренняя линейка, доведённая
     до границы, даёт перекрестье, и глаз читает клетку вместо содержимого. */
  background: radial-gradient(120% 130% at 50% 40%,
    rgba(242, 240, 247, 0.16), rgba(242, 240, 247, 0.045) 82%);
  border: 1px solid var(--line);
  border-radius: var(--r-card);
  overflow: hidden;
}

.features li { padding: clamp(20px, 2.4vw, 26px); background: var(--bg); }
.section--raised .features li { background: var(--surface); }
/* Тот же ранг, что .card h3, — значит тот же кегль. Без явного значения он
   наследовался от body и на ширине от 1100 px становился 19 px: на одном листе
   стояли два подзаголовка одного ранга разного размера. */
.features b {
  display: block;
  margin-bottom: 4px;
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.005em;
}

.features span { font-size: 0.9375rem; line-height: 1.5; color: var(--text-2); }

@media (max-width: 900px) { .features { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .features { grid-template-columns: minmax(0, 1fr); } }

.aside {
  margin-top: clamp(24px, 3vw, 36px);
  font-size: 0.9375rem;
  color: var(--text-2);
  max-width: var(--measure);
}

/* ── Каталог тестов ────────────────────────────────────────────────────── */
/* Перечень того, что продукт умеет мерить, — это СПИСОК, а не витрина
   карточек: у строки название, отличие и один бейдж, и больше ей ничего не
   нужно. Шесть плиток со значками читались здесь как ещё один блок
   «преимуществ» — на странице их и так довольно. */
.catalog + .catalog { margin-top: clamp(26px, 3.2vw, 40px); }

.catalog ul {
  display: grid;
  border-top: 1px solid var(--line);
}

.catalog li {
  display: grid;
  grid-template-columns: minmax(0, 15em) auto minmax(0, 1fr);
  align-items: baseline;
  gap: 4px 20px;
  padding: 16px 0;
  border-bottom: 1px solid var(--line);
  transition: background-color 0.2s;
}

/* Строка под курсором подсвечивается и получает фирменную засечку слева — тот
   же приём, что у таблицы показателей ниже: два перечня на одной странице
   обязаны вести себя одинаково. */
.catalog li { position: relative; padding-left: 0; transition: padding-left 0.22s cubic-bezier(0.22, 0.75, 0.3, 1), background-color 0.2s; }
.catalog li:hover { background: rgba(141, 103, 227, 0.07); padding-left: 24px; }

.catalog li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 26px;
  width: 14px;
  height: 2px;
  background: var(--brand);
  scale: 0 1;
  transform-origin: left center;
  transition: scale 0.22s cubic-bezier(0.22, 0.75, 0.3, 1);
}

.catalog li:hover::before { scale: 1 1; }

.catalog b { font-size: 1.0625rem; font-weight: 600; letter-spacing: -0.005em; }
.catalog p { color: var(--text-2); font-size: 0.9375rem; line-height: 1.5; }

@media (max-width: 760px) {
  .catalog li { grid-template-columns: minmax(0, 1fr) auto; }
  .catalog p { grid-column: 1 / -1; }
}

/* ── Условия доступа ───────────────────────────────────────────────────── */
/* Три строки по одной фразе: карточка обещала бы абзац, которого здесь нет. */
.facts {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--gap);
  margin-bottom: clamp(32px, 4vw, 52px);
}

.facts li {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  align-items: center;
  gap: 4px 14px;
}

.facts .ico { margin-bottom: 0; grid-row: span 2; }
.facts b { align-self: end; font-size: 1.0625rem; font-weight: 600; }
.facts span:last-child { font-size: 0.875rem; line-height: 1.4; color: var(--text-2); }

@media (max-width: 760px) {
  .facts { grid-template-columns: minmax(0, 1fr); gap: 18px; }
}

.group + .group { margin-top: clamp(28px, 3.6vw, 44px); }

.group-label {
  display: block;
  margin-bottom: 16px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}

.chip {
  display: inline-block;
  vertical-align: middle;
  padding: 5px 10px;
  border-radius: var(--r-chip);
  background: var(--violet);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand-ink);
}

/* ── Таблица показателей ───────────────────────────────────────────────── */
.metrics {
  width: 100%;
  border-collapse: collapse;
  text-align: left;
}

.metrics th {
  padding: 0 0 14px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
}

.metrics td {
  padding-block: 18px;
  border-top: 1px solid var(--line);
  vertical-align: baseline;
}

.metrics td:first-child { width: 40%; padding-right: var(--gap); font-weight: 600; }
.metrics td:nth-child(2) { color: var(--text-2); padding-right: var(--gap); }
.metrics td:last-child { width: 1%; white-space: nowrap; text-align: right; }
.metrics th:last-child { text-align: right; }

/* Метка теста: та же плашка, что бейдж в каталоге, только приглушённее — она
   помечает строку, а не заявляет о себе. */
.tag {
  display: inline-block;
  padding: 4px 9px;
  border-radius: 7px;
  border: 1px solid var(--line);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-3);
  transition: color 0.2s, border-color 0.2s, background-color 0.2s;
}

.metrics tr:hover .tag { color: var(--brand-ink); border-color: rgba(141, 103, 227, 0.4); }
.tag--both { background: rgba(141, 103, 227, 0.12); color: var(--brand-ink); border-color: rgba(141, 103, 227, 0.26); }

/* На телефоне таблица становится списком: горизонтального скролла у страницы
   нет нигде, а в два столбца эти формулировки не встают. */
@media (max-width: 760px) {
  .metrics, .metrics tbody, .metrics tr, .metrics td { display: block; }
  .metrics thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .metrics tr { padding-block: 16px; border-top: 1px solid var(--line); }
  .metrics td { padding-block: 0; border-top: 0; }
  .metrics td:first-child { width: auto; padding-right: 0; }
  .metrics td:nth-child(2) { margin-top: 4px; font-size: 0.9375rem; padding-right: 0; }
  .metrics td:last-child { width: auto; margin-top: 10px; text-align: left; }
}

/* ── Цитата ────────────────────────────────────────────────────────────── */
.quote {
  margin-top: clamp(36px, 4.5vw, 60px);
  padding: clamp(28px, 3.4vw, 44px);
  border: 1px solid var(--line);
  border-left: 3px solid var(--brand);
  border-radius: var(--r-card);
  background: var(--surface);
}

.section--raised .quote { background: var(--surface-2); }

.quote p {
  font-size: clamp(1.2rem, 2.1vw, 1.6rem);
  font-weight: 600;
  line-height: 1.32;
  letter-spacing: -0.02em;
  letter-spacing: calc(0.5px - 0.043em);
  max-width: 26em;
  text-wrap: balance;
}

/* ── Доступ ────────────────────────────────────────────────────────────── */
.cta {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: clamp(28px, 4vw, 56px);
  align-items: center;
  margin-top: var(--gap);
  padding: clamp(28px, 4vw, 52px);
  border: 1px solid var(--line-2);
  border-radius: var(--r-card);
  background: linear-gradient(var(--lit), var(--violet), var(--surface) 78%);
}

.cta h2 {
  margin-bottom: 14px;
  margin-bottom: max(14px, 0.4em);
  font-size: clamp(1.7rem, 3vw, 2.6rem);
}
.cta p { margin-bottom: 26px; color: var(--text-2); max-width: 28em; }

/* Код чёрным по белому и остаётся: инверсия и фиолетовая заливка ломают часть
   сканеров, а неработающий QR молча уводит заявки в никуда. */
.qr-card {
  padding: 14px;
  border-radius: var(--r-card);
  background: #fff;
}

.qr-card img { width: 148px; }

.qr-note {
  margin-top: 12px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  text-align: center;
}

@media (max-width: 760px) {
  .cta { grid-template-columns: minmax(0, 1fr); }
  .qr-card { justify-self: start; }
  .qr-note { text-align: left; }
}

/* ── Подвал ────────────────────────────────────────────────────────────── */
.foot {
  padding-block: clamp(44px, 5vw, 72px);
  border-top: 1px solid var(--line);
  background: var(--surface);
}

.foot-top {
  display: grid;
  grid-template-columns: 160px minmax(0, 1fr);
  gap: clamp(24px, 4vw, 56px);
}

.foot img { width: 118px; }
.foot p { color: var(--text-2); font-size: 0.9375rem; max-width: var(--measure); }
.foot p + p { margin-top: 12px; }

@media (max-width: 760px) { .foot-top { grid-template-columns: minmax(0, 1fr); } }

.foot-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--line);
  font-size: 0.875rem;
}

.foot-links a { color: var(--text-2); text-decoration-color: var(--line-2); text-underline-offset: 3px; }
.foot-links a:hover { color: var(--text); text-decoration-color: currentColor; }
.foot-links .copy { margin-left: auto; color: var(--text-3); }

@media (max-width: 760px) { .foot-links .copy { margin-left: 0; flex-basis: 100%; } }

/* ── Движение ──────────────────────────────────────────────────────────── */
/* Всё от скрипта и только при включённом JS: без него страница уже собрана. */
.js [data-reveal] { opacity: 0; transform: translateY(10px); }
.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.5s ease-out, transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
  transition-delay: var(--delay, 0s);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js [data-reveal] { opacity: 1; transform: none; transition: none; }
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}


/* ── Разбор отчёта ─────────────────────────────────────────────────────── */
.shot--report { background: var(--surface-2); }
.section--raised .shot--report { background: var(--bg); }

.report { padding: clamp(20px, 2.4vw, 28px); }

.report-head {
  display: flex;
  align-items: center;
  gap: 18px;
  padding-bottom: 20px;
  border-bottom: 1px solid var(--line);
}

.report-head b { display: block; font-size: 1.0625rem; font-weight: 600; }
.report-head span { font-size: 0.8125rem; color: var(--text-3); }

.gauge { flex: none; width: 84px; height: 84px; }
.gauge-track { fill: none; stroke: var(--line-2); stroke-width: 7; }

.gauge-fill {
  fill: none;
  stroke: var(--brand);
  stroke-width: 7;
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: center;
}

.gauge-num {
  fill: var(--text);
  font-size: 30px;
  font-weight: 700;
  font-variant-numeric: tabular-nums lining-nums;
  text-anchor: middle;
}

.rows { display: grid; }
.row { padding-block: 18px; }
.row + .row { border-top: 1px solid var(--line); }

.row-head { display: flex; align-items: center; gap: 10px; }
.row-head b { font-size: 0.9375rem; font-weight: 600; }

/* Статус назван СЛОВОМ, цвет только подсказывает: до дальтоника вердикт
   доходит так же, как до всех остальных. */
.dot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--st); }

.status {
  margin-left: auto;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--st);
}

.row--ok   { --st: var(--ok); }
.row--warn { --st: var(--warn); }
.row--bad  { --st: var(--bad); }

.row-data {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 14px;
  margin-top: 10px;
}

.row-data .val { font-size: 1.375rem; font-weight: 700; letter-spacing: -0.02em; }
.row-data .norm { font-size: 0.75rem; color: var(--text-3); white-space: nowrap; }

/* Линейка нормы — тот же рисунок, что в PDF-отчёте: волосяная дорожка домена,
   коридор вдвое плотнее с засечками по краям и одна короткая засечка замера.
   Цвет получает ТОЛЬКО засечка замера: коридор — это факт, а не оценка. */
.scale { width: 100%; height: 14px; overflow: visible; }
.scale-track { stroke: var(--line-2); stroke-width: 1; }
.scale-zone { stroke: var(--text-3); stroke-width: 2; }
.scale-edge { stroke: var(--text-3); stroke-width: 1.5; }
.scale-mark { stroke: var(--st); stroke-width: 2.5; stroke-linecap: round; }

.row-hint { margin-top: 8px; font-size: 0.8125rem; color: var(--text-3); }

@media (max-width: 460px) {
  .row-data { grid-template-columns: auto minmax(0, 1fr); }
  .row-data .norm { grid-column: 1 / -1; }
}


/* ── Эффекты ───────────────────────────────────────────────────────────── */

/* Зерно. Тёмные градиенты на 8-битном экране идут полосами, и зерно их
   разбивает — это лечение бандинга, а не просто фактура. Слой не ловит
   события и не попадает в поток. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 100;
  pointer-events: none;
  opacity: 0.4;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
}

/* Крупинка зерна обязана быть размером в ПИКСЕЛЬ ЭКРАНА: тайл в 140 CSS px на
   Retina рисуется вдвое крупнее пикселя, и шум перестаёт дизерить ступени —
   он просто становится виден. Мелкое зерно при той же амплитуде заметно слабее,
   поэтому непрозрачность подрастает. */
@media (min-resolution: 2dppx) {
  body::after { background-size: 70px 70px; opacity: 0.5; }
}

/* Подсветка первого экрана медленно дышит: два пятна с разным периодом не
   попадают в такт, и повтор цикла не читается. Двигаются только transform и
   opacity — композитор справляется без перерисовки слоя. */
.aurora {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
  pointer-events: none;
}

.aurora span {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  will-change: transform, opacity;
}

.aurora span:nth-child(1) {
  width: 46vw; height: 46vw;
  top: -14vw; right: -6vw;
  background: rgba(141, 103, 227, 0.30);
  animation: drift-a 22s ease-in-out infinite;
}

.aurora span:nth-child(2) {
  width: 34vw; height: 34vw;
  top: 26vw; right: 20vw;
  background: rgba(88, 60, 190, 0.26);
  animation: drift-b 29s ease-in-out infinite;
}

@keyframes drift-a {
  50% { transform: translate(-6vw, 5vw) scale(1.15); opacity: 0.75; }
}
@keyframes drift-b {
  50% { transform: translate(7vw, -4vw) scale(0.88); opacity: 0.6; }
}

/* Пятно под курсором. Координаты приходят из скрипта в --mx/--my, поэтому без
   него слой просто не рисуется: значения не заданы, и градиент не строится. */
.card, .cta { position: relative; overflow: hidden; }

.card::before, .cta::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.25s;
  background: radial-gradient(240px circle at var(--mx) var(--my),
    rgba(141, 103, 227, 0.16), transparent 62%);
}

.card:hover::before, .cta:hover::before { opacity: 1; }

/* Содержимое должно лежать НАД пятном: у ::before нет своего z-index, и без
   этого подсветка легла бы поверх текста. */
.card > *, .cta > * { position: relative; }

/* Свечение кнопки — тем же цветом, что и заливка: подсветка объекта, а не
   ореол вокруг него. */
.btn--primary { box-shadow: 0 0 0 0 rgba(141, 103, 227, 0); }
.btn--primary:hover { box-shadow: 0 8px 28px -8px rgba(141, 103, 227, 0.75); }

/* Кольцо балла вычерчивается, когда блок доехал до экрана. Длина дуги задана
   в долях (pathLength="1"), поэтому в анимации нет ни одного числа, которое
   могло бы разойтись с баллом. */
.js [data-reveal].is-in .gauge-fill {
  animation: gauge-draw 1.2s cubic-bezier(0.22, 0.75, 0.3, 1) backwards;
  animation-delay: 0.15s;
}

@keyframes gauge-draw { from { stroke-dashoffset: 1; } }

/* Засечка замера встаёт на своё место последней — после того, как прочитана
   строка. */
.js [data-reveal].is-in .scale-mark {
  animation: mark-in 0.45s cubic-bezier(0.34, 1.4, 0.64, 1) backwards;
  animation-delay: calc(0.4s + var(--r, 0) * 0.12s);
  transform-box: fill-box;
  transform-origin: center;
}

@keyframes mark-in { from { opacity: 0; scale: 1 0.2; } }

@media (prefers-reduced-motion: reduce) {
  .aurora span { animation: none; }
  .card::before, .cta::before { display: none; }
}


/* ══════════════════════════════════════════════════════════════════════════
   СЛОЙ ЭФФЕКТОВ

   Всё ниже — украшение, и всё ниже обязано выключаться. Правило одно: если
   эффект не сработал (нет скрипта, нет поддержки свойства, включено
   «уменьшить движение»), страница остаётся ПОЛНОСТЬЮ читаемой. Поэтому
   конечное состояние везде — обычное состояние элемента, а анимация идёт от
   скрытого; переменные, которые задаёт скрипт, всегда имеют запасное
   значение в самом CSS.

   Указатель обслуживает ОДИН цикл кадра на всю страницу (см. main.js):
   курсорное свечение, наклон карточки и магнит кнопок читают одно и то же
   событие. Отдельные слушатели на pointermove — это лишние пересчёты геометрии
   на каждое движение мыши.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Полоса прогресса ──────────────────────────────────────────────────── */
.progress {
  position: fixed;
  inset: 0 0 auto;
  height: 2px;
  z-index: 40;
  pointer-events: none;
}

.progress i {
  display: block;
  height: 100%;
  transform-origin: left center;
  scale: var(--p, 0) 1;
  background: linear-gradient(90deg, var(--brand-deep, #5B3AA8), var(--brand), #D9C6FF);
}

/* Где браузер умеет вести анимацию по прокрутке — ведём ею: это идёт вне
   основного потока и не дёргает верстку ни на один кадр. Скрипт тогда полосу
   не трогает вовсе. */
@supports (animation-timeline: scroll()) {
  .progress i {
    scale: 0 1;
    animation: progress-grow linear both;
    animation-timeline: scroll(root block);
  }
  @keyframes progress-grow { to { scale: 1 1; } }
}

/* ── Курсорное свечение и виньетка ─────────────────────────────────────── */
/* Оба слоя живут ВНУТРИ первого экрана, а не на всей странице. Фиксированным
   слоем под содержимым они проявлялись бы только в секциях без собственного
   фона — приподнятые полосы, тикер и подвал их перекрывают, и атмосфера шла
   бы через одну. Атмосфера — дело первого экрана; дальше идёт содержание. */
.cursor-glow,
.vignette {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}

.cursor-glow {
  opacity: 0;
  transition: opacity 0.5s;
  background: radial-gradient(460px circle at var(--cx, 50%) var(--cy, 50%),
    rgba(141, 103, 227, 0.16), transparent 66%);
}

.cursor-glow.is-on { opacity: 1; }

/* Виньетка собирает взгляд к середине полосы набора. Края уводятся в тень
   плавно — резкая граница читалась бы рамкой. */
.vignette {
  background: radial-gradient(120% 86% at 50% 38%,
    transparent 42%,
    rgba(0, 0, 0, 0.06) 58%,
    rgba(0, 0, 0, 0.16) 70%,
    rgba(0, 0, 0, 0.30) 80%,
    rgba(0, 0, 0, 0.45) 89%,
    rgba(0, 0, 0, 0.60) 100%);
}

/* ── Заголовок по словам ───────────────────────────────────────────────── */
.h-split .w {
  display: inline-block;
  overflow: hidden;
  /* Шторка обрезала бы выносные элементы (у, р, д) — опускаем её на 0.14em и
     тем же на столько поднимаем обратно, чтобы строка не разъехалась. */
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
  vertical-align: top;
}

.h-split .w > span { display: inline-block; }

.js .h-split .w > span {
  animation: word-up 0.95s cubic-bezier(0.16, 1, 0.3, 1) backwards;
  animation-delay: calc(0.1s + var(--w) * 0.07s);
}

@keyframes word-up { from { translate: 0 108%; } }

/* Лид и кнопки приходят следом за последним словом. */
.js .hero .lede { animation: rise 0.8s ease-out backwards; animation-delay: 0.62s; }
.js .hero .actions { animation: rise 0.8s ease-out backwards; animation-delay: 0.72s; }
.js .hero .eyebrow { animation: rise 0.7s ease-out backwards; }
.js .hero-figure { animation: rise 1s cubic-bezier(0.16, 1, 0.3, 1) backwards; animation-delay: 0.3s; }

/* Статистика — часть первого экрана, и появляется вместе с ним, а не по
   наблюдателю. По наблюдателю она стоит НИЖЕ сгиба (836 px при вьюпорте 913),
   и на загрузке читатель видел пустую рамку с границей, но без чисел. */
.js .stats { animation: rise 0.9s cubic-bezier(0.16, 1, 0.3, 1) backwards; animation-delay: 0.85s; }

@keyframes rise { from { opacity: 0; translate: 0 14px; } }

/* ── Третье пятно подсветки ────────────────────────────────────────────── */
.aurora span:nth-child(3) {
  width: 26vw; height: 26vw;
  top: 4vw; right: 34vw;
  background: rgba(196, 122, 255, 0.18);
  animation: drift-c 37s ease-in-out infinite;
}

@keyframes drift-c {
  50% { transform: translate(-5vw, 8vw) scale(1.25); opacity: 0.5; }
}

/* Разворот в перспективе, наклон за курсором и бегущий по кругу кант были у
   РАМКИ кадра. Рамки больше нет: у вырезанной фигуры нет ни граней, ни канта,
   которым можно было бы светить, а разворот силуэта в перспективе читался бы
   перекосом самой съёмки. Осталось одно движение — параллакс по прокрутке. */

/* Кант обходит по кругу блок призыва к действию — там он единственный.
   Без поддержки @property угол не анимируется, и остаётся ровный градиент. */
@property --ring {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

@keyframes ring-spin { to { --ring: 360deg; } }

/* ── Бегущая строка ────────────────────────────────────────────────────── */
.ticker {
  overflow: hidden;
  padding-block: 18px;
  border-block: 1px solid var(--line);
  background: var(--surface);
  /* Края растворяются, иначе строка «упирается» в границы экрана. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 9%, #000 91%, transparent);
}

.ticker-row { display: flex; width: max-content; animation: ticker 46s linear infinite; }
.ticker:hover .ticker-row { animation-play-state: paused; }

.ticker ul { display: flex; }

.ticker li {
  display: flex;
  align-items: center;
  gap: 26px;
  padding-inline: 13px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}

/* Точка-разделитель фирменная: она же маркер статуса в строке отчёта. */
.ticker li::after {
  content: '';
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--brand);
  opacity: 0.7;
}

/* Сдвиг ровно на половину: второй список — копия первого, и на 50% шов
   приходится встык. */
@keyframes ticker { to { translate: -50% 0; } }

/* ── Статистика ────────────────────────────────────────────────────────── */
.stats > * { position: relative; overflow: hidden; }

/* Волосяная линия строки «заряжается» фирменным цветом — вместе с тем, как
   число досчитывает до своего значения. Базовое состояние — линия НА МЕСТЕ:
   без скрипта строки всё равно разделены, а анимация идёт от нуля. */
.stats > *::after {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--brand), transparent);
  transform-origin: left center;
  scale: 1 1;
}

.js .stats > *::after {
  animation: sweep 1.1s cubic-bezier(0.22, 0.75, 0.3, 1) backwards;
  animation-delay: calc(0.95s + var(--delay, 0s));
}

@keyframes sweep { from { scale: 0 1; } }

/* ── Призыв к действию ─────────────────────────────────────────────────── */
.cta {
  border: 1px solid transparent;
  background:
    linear-gradient(var(--lit), var(--violet), var(--surface) 78%) padding-box,
    conic-gradient(from var(--ring),
      rgba(141, 103, 227, 0.2),
      rgba(217, 198, 255, 0.9) 10%,
      rgba(141, 103, 227, 0.2) 26%,
      rgba(141, 103, 227, 0.2) 100%) border-box;
  animation: ring-spin 11s linear infinite;
}

/* Магнит: кнопка тянется к курсору на несколько пикселей. Смещение приходит
   из скрипта, поэтому без него кнопка просто стоит на месте. */
[data-magnet] {
  translate: var(--dx, 0) var(--dy, 0);
  transition: translate 0.35s cubic-bezier(0.22, 0.75, 0.3, 1);
}

[data-magnet]:hover { transition-duration: 0.12s; }

/* ── Проступание с размытием ───────────────────────────────────────────── */
/* Размытие на входе — то, что отличает «появилось» от «выехало»: глаз
   воспринимает его как наводку резкости, а не как движение блока. */
.js [data-reveal] { opacity: 0; transform: translateY(14px); filter: blur(6px); }

.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
  filter: none;
  transition:
    opacity 0.6s ease-out,
    transform 0.7s cubic-bezier(0.16, 1, 0.3, 1),
    filter 0.6s ease-out;
  transition-delay: var(--delay, 0s);
}

/* Строки разбора приходят по очереди, сверху вниз. */
.js .shot--report.is-in .row {
  animation: rise 0.6s cubic-bezier(0.16, 1, 0.3, 1) backwards;
  animation-delay: calc(0.25s + var(--r) * 0.11s);
}

/* ── Выключатель ───────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  /* Пятна подсветки перечислены ПОИМЁННО: анимация задана каждому через
     `:nth-child()`, и общий `.aurora span` (специфичность 0,1,1) её не
     перебивает — три пятна продолжали дышать при выключенном движении. */
  .aurora span,
  .aurora span:nth-child(1),
  .aurora span:nth-child(2),
  .aurora span:nth-child(3),
  .cta,
  .ticker-row,
  .h-split .w > span,
  .hero .lede,
  .hero .actions,
  .hero .eyebrow,
  .stats > *::after,
  .shot--report .row { animation: none; }

  .stats > *::after { scale: 1 1; }
  .stats { animation: none; }
  .card::before, .cta::before, .cursor-glow, .progress { display: none; }
  .card, [data-magnet] { transform: none; translate: 0; transition: none; }
  .progress i { scale: 0 1; }
}


/* ══════════════════════════════════════════════════════════════════════════
   ВТОРОЙ СЛОЙ ОФОРМЛЕНИЯ
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Иконки ────────────────────────────────────────────────────────────── */
/* Рисованы здесь, а не взяты из презентации: там розовые растровые значки
   256×256 — чужие по цвету и по весу (7–9 КБ каждый). Свои идут одним стилем,
   красятся currentColor, не стоят ни одного запроса и остаются резкими на
   любом экране. */
.ico {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-bottom: 16px;
  border-radius: 12px;
  border: 1px solid rgba(141, 103, 227, 0.26);
  background: linear-gradient(var(--lit), var(--violet), rgba(30, 28, 37, 0.6));
  color: var(--brand-ink);
  transition: color 0.2s, border-color 0.2s, background-color 0.2s, transform 0.3s;
}

.ico svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.card:hover .ico,
.features li:hover .ico {
  color: #DCCBFF;
  border-color: rgba(141, 103, 227, 0.6);
  transform: translateY(-2px);
}

/* В перечне отчёта иконка стоит слева от текста: строка там короткая, и
   столбиком она растянула бы плитку вдвое. */
.features li {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  gap: 0 14px;
  align-content: start;
}

.features .ico { margin-bottom: 0; grid-row: span 2; }
.features b { align-self: end; }

/* Трекинг прописных добавляет пустоту ПОСЛЕ последней буквы: внутри плашки она
   превращается в неравные поля, у прижатого к краю статуса — в отступ от края,
   у центрованной подписи — в сдвиг влево на половину трекинга. Вычитаем её ровно
   там, где она видна; у подписей по левому краю (.eyebrow, .group-label,
   .step-num) вычитать нечего. */
.chip { padding-right: calc(10px - 0.08em); }
.tag { padding-right: calc(9px - 0.1em); }
.status { margin-right: -0.08em; }
.qr-note { text-indent: 0.14em; }
.ticker li::after { margin-left: -0.14em; }

@media (max-width: 760px) {
  /* Подпись под кодом встаёт по левому краю — компенсировать нечего. */
  .qr-note { text-indent: 0; }
}

/* ── Ключевые слова заголовков ─────────────────────────────────────────── */
/* Градиент кладётся на ВЫДЕЛЕННОЕ слово, а не на весь заголовок: залитый
   целиком заголовок теряет контраст на светлом конце градиента, а одно слово
   работает акцентом и остаётся читаемым. */
.gr {
  background: linear-gradient(100deg, #FFFFFF 10%, var(--brand-ink) 55%, #D9C6FF 90%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Подстраховка: если браузер не умеет клип по тексту, слово останется белым,
   а не исчезнет. */
@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .gr { color: var(--text); }
}

/* ── Граница секции ────────────────────────────────────────────────────── */
/* Волосяная линия между полосами — не ровная серая, а с фирменным сгущением
   к середине: край листа так читается как шов, а не как обрез. */
.section--raised {
  border-image: linear-gradient(90deg,
    transparent, rgba(141, 103, 227, 0.45) 22%, rgba(141, 103, 227, 0.45) 78%, transparent) 1;
  border-block-style: solid;
  border-block-width: 1px;
}

/* ── Орб за призывом к действию ────────────────────────────────────────── */
#access { position: relative; isolation: isolate; overflow: hidden; }

#access::before {
  content: '';
  position: absolute;
  z-index: -1;
  right: -14vw;
  bottom: -20vw;
  width: 52vw;
  height: 52vw;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgba(141, 103, 227, 0.26), transparent 74%);
  animation: drift-b 31s ease-in-out infinite;
  pointer-events: none;
}

/* ── Бегущая строка: отклик ────────────────────────────────────────────── */
.ticker li { transition: color 0.2s; }
.ticker li:hover { color: var(--text); }

/* ── Параллакс по прокрутке ────────────────────────────────────────────── */
/* Ведёт браузер: анимация привязана к положению элемента в окне, идёт вне
   основного потока и не стоит ни одного обработчика. Где не поддерживается —
   просто нет параллакса, и ничего не ломается. */
@supports (animation-timeline: view()) {
  .pose {
    animation: lift linear both;
    animation-timeline: view();
    animation-range: entry 20% exit 90%;
  }

  @keyframes lift {
    from { translate: 0 42px; }
    to { translate: 0 -42px; }
  }

  .aurora span {
    animation-timeline: view(), auto;
    animation-range: cover, auto;
  }

  .ico {
    animation: ico-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 62%;
  }

  @keyframes ico-in {
    from { opacity: 0; scale: 0.72; rotate: -12deg; }
    to { opacity: 1; scale: 1; rotate: 0deg; }
  }
}

@media (prefers-reduced-motion: reduce) {
  #access::before { animation: none; }
  .ico { opacity: 1; scale: 1; rotate: 0deg; animation: none; }
  .pose { animation: none; translate: 0; }
}


/* Каталог: значок и бейдж — одной строкой над названием теста. Столбиком они
   дают три яруса над одной строкой описания, и карточка распухает вдвое. */
.card .ico:has(+ .chip) {
  display: inline-grid;
  vertical-align: middle;
  margin-bottom: 14px;
}

.card .ico + .chip { margin-left: 12px; }

/* Без :has() значок остаётся блоком, бейдж встаёт под ним — не нарядно, но и
   не сломано. */
@supports not selector(:has(*)) {
  .card .ico + .chip { display: block; margin-left: 0; margin-top: -4px; }
}


/* ── Таблица показателей ───────────────────────────────────────────────── */
/* Самый плоский блок страницы: восемь строк подряд без единой зацепки для
   глаза. Строка под курсором подсвечивается и получает фирменную засечку
   слева — та же засечка, что у надзаголовков и у замера на линейке нормы. */
.metrics tbody tr {
  position: relative;
  transition: background-color 0.2s;
}

.metrics tbody tr:hover { background: rgba(141, 103, 227, 0.07); }

.metrics tbody td:first-child {
  position: relative;
  padding-left: 0;
  transition: padding-left 0.22s cubic-bezier(0.22, 0.75, 0.3, 1);
}

.metrics tbody td:first-child::before {
  content: '';
  position: absolute;
  left: 0;
  top: 50%;
  width: 14px;
  height: 2px;
  margin-top: -1px;
  background: var(--brand);
  scale: 0 1;
  transform-origin: left center;
  transition: scale 0.22s cubic-bezier(0.22, 0.75, 0.3, 1);
}

.metrics tbody tr:hover td:first-child { padding-left: 24px; }
.metrics tbody tr:hover td:first-child::before { scale: 1 1; }

/* Строки приходят по очереди — таблица «набирается», а не появляется плитой. */
@supports (animation-timeline: view()) {
  .metrics tbody tr {
    animation: row-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 55%;
  }

  @keyframes row-in { from { opacity: 0; translate: -10px 0; } }
}

/* ── Подвал ────────────────────────────────────────────────────────────── */
/* Тонкая фирменная линия по верхнему краю: подвал перестаёт быть «остатком
   страницы» и получает свою границу — ту же, что у приподнятых полос. */
.foot {
  position: relative;
  border-top: 0;
  background:
    radial-gradient(90% 100% at 66% 0%, rgba(141, 103, 227, 0.10), transparent 62%),
    var(--surface);
}

.foot::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: linear-gradient(90deg,
    transparent, rgba(141, 103, 227, 0.5) 25%, rgba(141, 103, 227, 0.5) 75%, transparent);
}

/* Логотип в подвале проявляется под курсором — мелкая любезность, не более. */
.foot img { opacity: 0.75; transition: opacity 0.25s; }
.foot-top:hover img { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .metrics tbody tr { animation: none; opacity: 1; translate: 0; }
}


/* ══════════════════════════════════════════════════════════════════════════
   ТРЕТИЙ СЛОЙ
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Заголовки секций по словам ────────────────────────────────────────── */
/* У h1 слова выходят по загрузке (он и так виден), у h2 — когда секция дошла
   до экрана. Сама рамка заголовка при этом только проявляется: сдвиг и
   размытие ей отключены, иначе блок едет вместе со словами внутри него и
   движение читается смазанным. */
.js h2.h-split[data-reveal] { transform: none; filter: none; }

.js h2.h-split[data-reveal].is-in {
  transform: none;
  filter: none;
  transition: opacity 0.3s ease-out;
  transition-delay: var(--delay, 0s);
}

.js h2.h-split.is-in .w > span {
  animation: word-up 0.85s cubic-bezier(0.16, 1, 0.3, 1) backwards;
  animation-delay: calc(var(--w) * 0.045s);
}

/* ── Лучи и разграфлённое поле ─────────────────────────────────────────── */
/* Поле — та же калибровочная сетка, по которой продукт меряет углы. Контраст
   на пределе видимости: это фактура, а не элемент, который что-то сообщает. */
.grid-field {
  position: absolute;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(242, 240, 247, 0.028) 1px, transparent 1px),
    linear-gradient(90deg, rgba(242, 240, 247, 0.028) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: radial-gradient(90% 70% at 62% 30%, #000, transparent 72%);
  mask-image: radial-gradient(90% 70% at 62% 30%, #000, transparent 72%);
}

/* Лучи — не «свет из ниоткуда»: они идут из той же точки, что и подсветка за
   экраном приложения, и медленно расходятся. Три штуки с разной скоростью,
   чтобы веер не читался вращающейся мельницей. */
.beams {
  position: absolute;
  inset: -30% -10% auto;
  height: 150%;
  z-index: -3;
  pointer-events: none;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(#000 20%, transparent 78%);
  mask-image: linear-gradient(#000 20%, transparent 78%);
}

.beams i {
  position: absolute;
  top: 0;
  left: 62%;
  width: 34vw;
  height: 100%;
  transform-origin: 50% 0;
  background: linear-gradient(180deg, rgba(141, 103, 227, 0.16), transparent 62%);
  filter: blur(26px);
}

.beams i:nth-child(1) { rotate: -16deg; animation: beam 19s ease-in-out infinite; }
.beams i:nth-child(2) { rotate: 6deg; width: 22vw; animation: beam 27s ease-in-out infinite reverse; }
.beams i:nth-child(3) { rotate: 24deg; width: 16vw; opacity: 0.7; animation: beam 34s ease-in-out infinite; }

@keyframes beam {
  50% { rotate: 8deg; opacity: 0.45; }
}

/* ── Связка шагов ──────────────────────────────────────────────────────── */
/* Пунктир проходит ЗА карточками и виден только в промежутках: три шага
   читаются одной цепочкой, а не тремя отдельными плитками. */
.steps { position: relative; }

.steps::before {
  content: '';
  position: absolute;
  z-index: 0;
  left: 14%;
  right: 14%;
  /* По середине рисунка шага: рисунок начинается под полем карточки (≈29 px)
     и высотой 98 px. Ниже линия упиралась бы в номер шага. */
  top: 78px;
  height: 1px;
  background: repeating-linear-gradient(90deg,
    rgba(141, 103, 227, 0.55) 0 7px, transparent 7px 14px);
}

.steps > .card { z-index: 1; }

@media (max-width: 900px) { .steps::before { display: none; } }

/* ── Блик по кнопке ────────────────────────────────────────────────────── */
/* Проходит ОДИН раз за наведение и только по главной кнопке: блик на каждой
   кнопке страницы превращает её в витрину. */
.btn--primary { position: relative; overflow: hidden; }

.btn--primary::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 36%, rgba(255, 255, 255, 0.38) 50%, transparent 64%);
  background-size: 240% 100%;
  background-position: 160% 0;
}

.btn--primary:hover::before { animation: btn-shine 0.75s ease-out; }

@keyframes btn-shine { to { background-position: -60% 0; } }

.btn--primary > * { position: relative; }

/* Логотип в шапке отзывается тем же движением. */
.brand img { transition: filter 0.3s, scale 0.3s; }
.brand:hover img { filter: brightness(1.25) drop-shadow(0 0 10px rgba(141, 103, 227, 0.5)); scale: 1.02; }

/* ── Числа статистики ──────────────────────────────────────────────────── */
.stats b {
  background: linear-gradient(160deg, #EADFFF 6%, var(--brand) 70%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

@supports not ((background-clip: text) or (-webkit-background-clip: text)) {
  .stats b { color: var(--brand); }
}

/* ── Проявление снимков ────────────────────────────────────────────────── */
/* Снимок не «выцветает», а открывается снизу вверх — как проявляется кадр. */
.js [data-reveal].is-in .shot img,
.js .pose-fig {
  animation: frame-in 0.95s cubic-bezier(0.76, 0, 0.24, 1) backwards;
}

.js .pose-fig { animation-delay: 0.35s; }

/* Телефон встаёт следом за моделью: сначала поза, потом отчёт по ней. */
.js .phone {
  animation: rise 0.9s cubic-bezier(0.16, 1, 0.3, 1) backwards;
  animation-delay: 0.55s;
}

.js .phone .gauge-fill {
  animation: gauge-draw 1.2s cubic-bezier(0.22, 0.75, 0.3, 1) backwards;
  animation-delay: 1.1s;
}

@keyframes frame-in { from { clip-path: inset(0 0 100% 0); } }

/* ── Кольцо курсора ────────────────────────────────────────────────────── */
/* Не подмена системного курсора, а спутник: сам курсор на месте, и ничего не
   ломается, если скрипт не запустился. */
.cursor-ring {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 60;
  width: 26px;
  height: 26px;
  margin: -13px 0 0 -13px;
  border: 1px solid rgba(141, 103, 227, 0.75);
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
  translate: var(--rx, 0) var(--ry, 0);
  transition: translate 0.13s cubic-bezier(0.2, 0.8, 0.2, 1), scale 0.2s, opacity 0.3s,
              background-color 0.2s, border-color 0.2s;
}

.cursor-ring.is-on { opacity: 1; }

.cursor-ring.is-hot {
  scale: 1.9;
  background: rgba(141, 103, 227, 0.14);
  border-color: rgba(217, 198, 255, 0.9);
}

/* ── Уход секции ───────────────────────────────────────────────────────── */
@supports (animation-timeline: view()) {
  .section .head {
    animation: head-exit linear both;
    animation-timeline: view();
    animation-range: exit 30% exit 100%;
  }

  @keyframes head-exit { to { opacity: 0.35; scale: 0.97; } }
}

/* ── Выключатель ───────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .beams i,
  .js h2.h-split.is-in .w > span,
  .js [data-reveal].is-in .shot img,
  .js .pose-fig,
  .js .phone,
  .js .phone .gauge-fill,
  .btn--primary::before,
  .section .head { animation: none; }

  .cursor-ring { display: none; }
  .section .head { opacity: 1; scale: 1; }
}


/* ── Картинка шага ─────────────────────────────────────────────────────── */
/* Три шага были тремя строчками текста на всю полосу — секция читалась
   пустой. Каждый шаг получает СВОЙ живой рисунок: видоискатель, проход
   сканирования и мини-разбор. Это не абстракции «на тему»: ровно это и
   происходит на шаге. */
.viz {
  display: block;
  position: relative;
  height: 98px;
  margin-bottom: 20px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: linear-gradient(var(--lit), rgba(141, 103, 227, 0.12), rgba(30, 28, 37, 0.55));
  overflow: hidden;
}

/* 01 — видоискатель. Две противоположные скобки читаются рамкой кадра, все
   четыре превратили бы карточку в телевизор. */
.viz--rec::before,
.viz--rec::after {
  content: '';
  position: absolute;
  width: 18px;
  height: 18px;
  border: 1.5px solid rgba(141, 103, 227, 0.65);
}

.viz--rec::before { left: 14px; top: 14px; border-right: 0; border-bottom: 0; border-radius: 5px 0 0 0; }
.viz--rec::after { right: 14px; bottom: 14px; border-left: 0; border-top: 0; border-radius: 0 0 5px 0; }

.viz--rec b {
  position: absolute;
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--text-2);
}

.viz--rec b::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--bad);
  animation: rec 1.7s ease-in-out infinite;
}

@keyframes rec { 50% { opacity: 0.25; box-shadow: 0 0 0 5px rgba(239, 107, 107, 0.13); } }

/* 02 — проход сканирования по полю точек. Полоса шире блока и ездит фоном:
   так она работает на любой ширине карточки, без пересчёта в пикселях. */
.viz--scan {
  background-image:
    radial-gradient(circle, rgba(141, 103, 227, 0.45) 1.4px, transparent 1.7px),
    linear-gradient(var(--lit), rgba(141, 103, 227, 0.12), rgba(30, 28, 37, 0.55));
  background-size: 17px 17px, auto;
  background-position: 9px 9px, 0 0;
}

.viz--scan s {
  position: absolute;
  inset: 0;
  text-decoration: none;
  background: linear-gradient(90deg, transparent 42%, rgba(217, 198, 255, 0.55) 50%, transparent 58%);
  background-size: 260% 100%;
  animation: scan 3s ease-in-out infinite;
}

@keyframes scan { from { background-position: 150% 0; } to { background-position: -50% 0; } }

/* 03 — тот же разбор, что ждёт в конце: кольцо балла и две строки замера. */
.viz--score {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr);
  align-items: center;
  gap: 10px 14px;
  padding-inline: 18px;
}

.viz--score svg { grid-row: span 2; width: 56px; height: 56px; }
.viz-track { fill: none; stroke: var(--line-2); stroke-width: 5; }

.viz-arc {
  fill: none;
  stroke: var(--brand);
  stroke-width: 5;
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: center;
}

.js [data-reveal].is-in .viz-arc {
  animation: gauge-draw 1.2s cubic-bezier(0.22, 0.75, 0.3, 1) backwards;
  animation-delay: 0.35s;
}

.viz--score i {
  position: relative;
  height: 2px;
  border-radius: 2px;
  background: var(--line-2);
}

.viz--score i::after {
  content: '';
  position: absolute;
  top: -3px;
  width: 2px;
  height: 8px;
  border-radius: 2px;
}

.viz--score i:nth-of-type(1)::after { left: 34%; background: var(--warn); }
.viz--score i:nth-of-type(2)::after { left: 70%; background: var(--ok); }

@media (prefers-reduced-motion: reduce) {
  .viz--rec b::before, .viz--scan s, .js [data-reveal].is-in .viz-arc { animation: none; }
}
