/* Лендинг GenRouter — перенос макета «Лендинг / Desktop / Light» (1440×4320).
 * Контейнер 1200px при полях 120px, сетка карточек 386px с зазором 20px,
 * кнопки-таблетки 48px — все значения сняты с фрейма Figma. */

.wrap {
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 24px;
}

@media (min-width: 1440px) {
  .wrap {
    padding: 0;
  }
}

/* --- Кнопки: таблетка 999px, высота 48px --- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 48px;
  padding: 0 22px;
  border-radius: 999px;
  font-family: inherit;
  font-weight: 700;
  font-size: 16px;
  line-height: 1.35;
  border: 1px solid transparent;
  cursor: pointer;
  /* Анимируем только transform, box-shadow и цвет: это свойства, которые
   * браузер считает на композиторе, поэтому наведение не дёргает раскладку.
   * Кривая с быстрым началом и мягким концом — движение читается как отклик
   * на действие, а не как самостоятельная анимация. */
  transition:
    transform 180ms cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 180ms cubic-bezier(0.2, 0.7, 0.3, 1),
    background-color 160ms ease,
    border-color 160ms ease,
    color 160ms ease;
}

/* Подъём на 1px вместо увеличения: масштаб кнопки-таблетки заметно искажает
 * скругление, а сдвиг выглядит как отклик поверхности. */
.btn:hover {
  transform: translateY(-1px);
}

.btn:active {
  /* Нажатие возвращает кнопку ниже исходного уровня — «вдавливание».
   * Короче, чем наведение: отклик на клик должен быть мгновенным. */
  transform: translateY(1px) scale(0.99);
  transition-duration: 90ms;
}

.btn-primary {
  background: var(--accent-strong);
  color: #ffffff;
  box-shadow: 0 1px 2px rgba(8, 125, 109, 0.24);
}

.btn-primary:hover {
  background: #6f7ae0;
  box-shadow: 0 6px 18px rgba(8, 125, 109, 0.26);
}

.btn-primary:active {
  background: var(--accent-strong);
  box-shadow: 0 1px 2px rgba(8, 125, 109, 0.3);
}

/* Обводка фокуса на зелёной кнопке совпадала с её собственным фоном
 * (#5e6ad2 на #5e6ad2 = контраст 1.00:1) и читалась только по просвету
 * в 2px. Белое кольцо внутри даёт разделение: белый на зелёном 5.04:1. */
.btn-primary:focus-visible {
  outline-color: #ffffff;
  outline-offset: -4px;
  box-shadow: 0 0 0 2px var(--accent-strong), 0 6px 18px rgba(8, 125, 109, 0.26);
}

.btn-ghost {
  background: var(--surface);
  border-color: var(--line);
  color: var(--text);
}

.btn-ghost:hover {
  border-color: var(--accent-strong);
  color: var(--accent-strong);
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08);
}

.btn-ghost:active {
  box-shadow: none;
}

/* Наведение — только там, где есть настоящий курсор: на тач-экране
 * :hover залипает после тапа и кнопка остаётся «поднятой». */
@media (hover: none) {
  .btn:hover {
    transform: none;
    box-shadow: none;
  }

  .btn-primary:hover {
    background: var(--accent-strong);
  }

  .btn-ghost:hover {
    border-color: var(--line);
    color: var(--text);
  }
}

/* --- 01 / Navigation, 84px --- */
.nav {
  height: 84px;
  display: flex;
  align-items: center;
  background: var(--surface);
  /* Линия отделяет шапку от героя: без неё белая полоса сливалась с фоном
   * секции и шапка выглядела не блоком, а набором висящих элементов. */
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 20;
  /* Плавный переход к «поднятому» состоянию при прокрутке (см. .nav.is-scrolled). */
  transition: box-shadow 200ms ease, border-color 200ms ease;
}

/* Класс ставит nav.js, когда страница прокручена: тень отделяет шапку от
 * уехавшего под неё контента. */
.nav.is-scrolled {
  border-bottom-color: transparent;
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06), 0 8px 24px rgba(0, 0, 0, 0.07);
}

.nav-inner {
  /* Меню обязано стоять по центру СТРАНИЦЫ, а не по центру промежутка между
   * брендом и кнопками. Сетка с auto-колонками этого не даёт: боковые блоки
   * разной ширины (бренд 128px, действия 227px), и центральная колонка
   * съезжала на 58px влево.
   *
   * Решение: боковые колонки равной ширины 1fr, меню в центральной auto.
   * Тогда центр средней колонки совпадает с центром контейнера при любой
   * ширине бренда и кнопок. minmax(0, 1fr) — чтобы длинные названия
   * не растягивали колонку и не ломали центровку. */
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: 24px;
}

/* Бренд держится левого края своей колонки, действия — правого. */
.nav-brand {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* Высота как у кнопки: элементы шапки садятся на одну линию, а не каждый
   * на свою. Плюс нормальный тач-таргет. */
  min-height: 44px;
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -0.01em;
  transition: opacity 160ms ease;
}

.nav-brand:hover {
  opacity: 0.72;
}

.nav-logo {
  display: block;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  object-fit: contain;
}

.nav-links {
  display: flex;
  /* margin-left: auto убран: позицию задаёт сетка .nav-inner, иначе меню
   * прижималось вправо и зазор до бренда получался 427px. */
  gap: 4px;
  justify-content: center;
}

/* Пункт меню — своя область нажатия с подсветкой, а не голый текст:
 * до этого попасть можно было только точно по буквам.
 *
 * Подчёркивания нет намеренно: скруглённая подложка сама показывает, где
 * курсор, а линия под текстом рядом с ней читалась как второй, лишний
 * указатель на то же состояние. */
.nav-links a {
  position: relative;
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding: 0 14px;
  border-radius: 999px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-2);
  transition: color 160ms ease, background-color 160ms ease;
}

.nav-links a:hover {
  color: var(--text);
  background: var(--surface-2);
}

/* Действия в шапке: «Войти» — тихая ссылка, кабинет — единственная кнопка. */
.nav-actions {
  display: flex;
  align-items: center;
  /* 12px вместо 8px: ссылка и кнопка перестают читаться как один слипшийся
   * блок, но остаются одной группой. justify-self держит группу у правого
   * края колонки сетки. */
  gap: 12px;
  justify-self: end;
}

.nav-link-in {
  display: inline-flex;
  align-items: center;
  /* 48px — как у кнопки рядом: разница 44 против 48 и давала ощущение,
   * что элементы «плавают» по вертикали. */
  min-height: 48px;
  padding: 0 14px;
  border-radius: 999px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-2);
  transition: color 160ms ease, background-color 160ms ease;
}

.nav-link-in:hover {
  color: var(--text);
  background: var(--surface-2);
}

/* Переключатель языка — сегментированная пара EN/RU.
 *
 * Почему две кнопки, а не <select> и не одна кнопка-тумблер: выбор всего из
 * двух вариантов, и оба видны сразу — человек понимает, что доступен русский,
 * не нажимая ничего. Одна кнопка с подписью «RU» читается двояко (это текущий
 * язык или предлагаемый?), а select на телефоне открывает системное колесо.
 *
 * Ширина кнопок фиксированная: подписи «EN» и «RU» разной ширины, и без
 * min-width рамка переключателя меняла бы размер при смене языка, сдвигая
 * соседние ссылки шапки.
 *
 * Высота 36px, а не 44: это вспомогательный элемент, и рядом с ним стоят
 * ссылка 48px и кнопка 48px — равная высота сделала бы переключатель
 * главным элементом шапки. Ширина 42px при высоте 36px даёт площадь нажатия
 * 1512px², что выше порога комфортного попадания пальцем; на телефоне ниже
 * заданы 44px по высоте. */
.lang-switch {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
}

.lang-btn {
  min-width: 42px;
  min-height: 30px;
  padding: 0 8px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--text-2);
  font-family: inherit;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: color 160ms ease, background-color 160ms ease;
}

.lang-btn:hover {
  color: var(--text);
  background: var(--surface-2);
}

/* Активный язык залит акцентом. Белый текст на --accent-strong (#5e6ad2)
   даёт контраст 5.04:1 — та же пара, что на основной кнопке страницы. */
.lang-btn.is-active {
  background: var(--accent-strong);
  color: #fff;
}

.lang-btn.is-active:hover {
  background: var(--accent-strong);
  color: #fff;
}

/* --- 02 / Hero, 515px --- */
.hero {
  position: relative;
  background: var(--canvas);
  /* Низ был 88px, и вместе с 48px следующей секции под кнопками копилось
     136px против 76px сверху — экран выглядел «съехавшим» вверх. 56px дают
     суммарно 104px: снизу по-прежнему больше, чем сверху (так и должно быть,
     низ делят две секции), но разрыв уже не бросается в глаза. */
  padding: 76px 0 56px;
  overflow: hidden;
}

/* Декоративные пятна — акцент-soft, лежат под контентом.
 *
 * Размывка обязательна: круг с резким краем секция обрезает ровной линией,
 * и на месте среза видна ступенька цвета. С blur край растворяется, поэтому
 * обрезка не читается как артефакт вёрстки. */
.blob {
  position: absolute;
  z-index: 0;
  border-radius: 50%;
  background: var(--accent-soft);
  filter: blur(56px);
  opacity: 0.85;
  pointer-events: none;
}

.blob-a {
  width: 620px;
  height: 620px;
  top: -160px;
  right: -60px;
}

.blob-b {
  width: 360px;
  height: 360px;
  top: 300px;
  right: -240px;
}

.hero-grid {
  position: relative;
  z-index: 1;
  display: grid;
  /* 620/508 вместо 570/558: фразе «Все сильные модели.» нужно 592px, а в 570px
     она не влезала и слово «модели.» срывалось в отдельную короткую строку.
     50px забраны у карточки — ей столько не нужно, её текст переносится. */
  grid-template-columns: minmax(0, 620fr) minmax(0, 508fr);
  gap: 72px;
  /* stretch, а не center: карточка теперь равна по высоте левой колонке и
     выравнивается по верху заголовка и низу кнопок сама. При center она
     висела посередине, и низ не совпадал с подзаголовком на 7px. */
  align-items: stretch;
}

/* Ниже 1440px у .wrap появляется padding 24px, контейнер теряет 48px, и
   левая колонка садится до 594px — фразе «Все сильные модели.» нужно 602px,
   поэтому на ноутбуках заголовок снова рвался на три строки. Возвращаем
   недостающее из зазора: 48px между колонками на такой ширине достаточно. */
@media (min-width: 901px) and (max-width: 1439px) {
  .hero-grid {
    gap: 48px;
  }
}

.hero-copy h1 {
  margin: 0;
  font-size: 60px;
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -0.03em;
}

.hero-body {
  margin: 24px 0 0;
  max-width: 520px;
  font-size: 19px;
  line-height: 1.5;
  font-weight: 400;
  color: var(--text-2);
}

.hero-actions {
  display: flex;
  align-items: center;
  /* 24px вместо 12px: между рамками кнопки и ссылки было 12px при высоте
     кнопки 48px — они читались как один слипшийся элемент. 24px = половина
     высоты кнопки, привычная пропорция. */
  gap: 24px;
  margin-top: 24px;
}

/* Вторичное действие — ссылка, а не вторая кнопка: две равновесные кнопки
 * рядом размывали главный призыв. 44px по высоте — тач-таргет. */
.hero-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 6px;
  font-size: 15px;
  font-weight: 600;
  color: var(--accent-strong);
}

.hero-link:hover {
  color: var(--text);
}

/* Опора под кнопками: три коротких факта. Галочка рисуется в CSS, потому что
 * это декор — скринридеру она прочитала бы «галочка» перед каждым пунктом. */
.hero-trust {
  margin: 28px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 22px;
}

.hero-trust li {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text-2);
}

.hero-trust li::before {
  content: "";
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  /* Галочка — маленький SVG в маске: цвет задаёт background, без лишнего файла.
     Скринридеру декор не читается, потому что это ::before, а не текст. */
  background: var(--accent-strong);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 8.4l2.4 2.4L12 5.2' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 8.4l2.4 2.4L12 5.2' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
}

/* --- Терминал --- */
.terminal {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 18px;
  overflow: hidden;
}

/* Тёмная живая карточка первого экрана. Отдельный класс: в секции demo
 * терминал остаётся светлым, там внутри код для копирования, и тёмная
 * заливка выбивалась бы из светлой секции.
 *
 * Высота фиксирована заранее и колонка растянута на всю высоту: текст
 * печатается внутри готовой рамки, поэтому страница не дёргается вниз на
 * каждой новой строке. Цвета проверены расчётом против фона #0d0d10:
 * вопрос 16.87:1, ответ 11.67:1, строка статуса 10.26:1 — все выше AA. */
.terminal-live {
  --term-bg: #0d0d10;
  --term-bar: #141418;
  --term-line: #1b1b21;
  /* Мятный акцент карточки: на тёмном фоне 10.80:1, на нём тёмный текст
     бейджа 10.07:1 — проверено расчётом, оба выше AA. */
  --term-mint: #8b95e8;
  background: var(--term-bg);
  border-color: var(--term-line);
  display: flex;
  flex-direction: column;
  min-height: 100%;
  box-shadow: 0 24px 60px rgba(13, 23, 20, 0.18);
}

.terminal-bar {
  height: 51px;
  display: flex;
  align-items: center;
  padding: 0 20px;
  background: var(--surface-2);
}

.terminal-live .terminal-bar {
  background: var(--term-bar);
  border-bottom: 1px solid var(--term-line);
  gap: 12px;
}

.terminal-url {
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 13px;
  color: var(--text);
}

.terminal-live .terminal-url {
  color: rgba(255,255,255,.14);
}

/* Хвост «/ v1» тише самого адреса: это подробность, а не заголовок. */
.terminal-live .terminal-url i {
  font-style: normal;
  color: #8a8f98;
}

/* Три точки как в окне терминала — сразу говорят, что это окно программы,
 * а не блок текста на странице. Декор, поэтому создаётся в CSS. */
.terminal-dots {
  flex: 0 0 auto;
  width: 44px;
  height: 10px;
  border-radius: 999px;
  background:
    radial-gradient(circle 5px at 5px 5px, #f85149 98%, transparent) 0 0 / 17px 10px no-repeat,
    radial-gradient(circle 5px at 5px 5px, #d29922 98%, transparent) 17px 0 / 17px 10px no-repeat,
    radial-gradient(circle 5px at 5px 5px, #3fb950 98%, transparent) 34px 0 / 10px 10px no-repeat;
}

/* «онлайн» у правого края: признак живого шлюза, а не украшение. */
.terminal-live-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #8a8f98;
}

.terminal-live-badge i {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--term-mint);
  box-shadow: 0 0 0 0 rgba(42, 223, 192, 0.55);
  animation: term-pulse 2.4s ease-out infinite;
}

@keyframes term-pulse {
  0% { box-shadow: 0 0 0 0 rgba(42, 223, 192, 0.5); }
  70%, 100% { box-shadow: 0 0 0 7px rgba(42, 223, 192, 0); }
}

.terminal-flow {
  padding: 26px;
  /* Строки терминала не переносятся (см. ниже), поэтому на узком экране
   * им нужен собственный скролл: без него текст молча обрезался
   * многоточием и читался как оборванная фраза. */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* В живой карточке текст, наоборот, переносится всегда: это диалог, а не
 * однострочная команда. Горизонтального скролла нет — значит нет и области,
 * до которой нельзя добраться с клавиатуры. */
.terminal-live .terminal-flow {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  padding: 24px 26px 26px;
  overflow-x: visible;
}

.terminal-flow p {
  margin: 0;
  font-family: var(--font-mono);
  /* В макете каждая строка — одна строка терминала, без переноса. */
  white-space: nowrap;
}

.terminal-live .terminal-flow p {
  white-space: normal;
  overflow-wrap: anywhere;
}

/* Команды — то, что человек набирает сам. Знак $ ставится в CSS, чтобы
 * скрипт печатал только текст команды, а приглашение было на месте сразу. */
.t-cmd {
  font-size: 13.5px;
  line-height: 1.5;
  font-weight: 700;
  /* Основной текст, а не поверхность.
     Ребрендинг подставил сюда #1b1b21 — это --surface-2 из палитры GenSpace,
     цвет ПОДЛОЖКИ. На фоне терминала (#0d0d10) контраст выходил 1.13:1 при
     норме WCAG AA 4.5:1, то есть команд не было видно вовсе. */
  color: var(--text);
  /* Команда занимает ровно одну строку: пока она набирается, число строк не
     должно меняться, иначе карточка растёт и страница дёргается. Длинные
     команды не переносим — у моноширинного текста это читается как вывод
     консоли, а не как обрезка. */
  min-height: 1.5em;
  white-space: nowrap !important;
  overflow: hidden;
  text-overflow: clip;
}

.t-cmd + .t-cmd {
  margin-top: 7px !important;
}

.t-cmd::before {
  content: "$ ";
  color: var(--term-mint);
}

/* Служебные строки вывода: тише команд, со своим знаком-указателем.
 * Знаки разные, чтобы строки не читались как однородный список. */
.t-route,
.t-tools {
  font-size: 12.5px;
  line-height: 1.5;
  color: #8a8f98;
}

.t-route {
  margin-top: 16px !important;
  color: var(--term-mint);
}

.t-route::before {
  content: "↻ ";
}

.t-tools {
  margin-top: 14px !important;
}

.t-tools::before {
  content: "↳ ";
  color: var(--term-mint);
}

.t-answer {
  margin-top: 14px !important;
  font-size: 14px;
  line-height: 1.62;
  color: #a4aebb;
  /* Высота под ТРИ строки задана заранее. Иначе по мере набора ответ занимал
     то одну строку, то две, то три, карточка росла (386→432px замером) и вся
     страница дёргалась вниз. Теперь место под ответ зарезервировано, а текст
     заполняет его на месте. */
  min-height: calc(3 * 1.62em);
}

/* Пустое приглашение с курсором прижато к низу: так карточка выглядит как
 * работающая консоль, ждущая следующей команды, а не как законченный текст. */
.t-prompt {
  margin-top: auto !important;
  padding-top: 16px;
  font-size: 13.5px;
  line-height: 1.5;
  font-weight: 700;
  color: var(--text);
}

.t-prompt::before {
  content: "$ ";
  color: var(--term-mint);
}

/* Курсор в пустом приглашении горит всегда — консоль ждёт ввода. */
.t-prompt::after {
  content: "";
  display: inline-block;
  width: 8px;
  height: 1em;
  vertical-align: -0.15em;
  background: var(--term-mint);
  animation: term-caret 1.1s steps(1, end) infinite;
}

/* Курсор виден только пока идёт набор: класс ставит и снимает скрипт. */
.terminal-live [data-line].is-typing::after {
  content: "";
  display: inline-block;
  width: 8px;
  height: 1em;
  margin-left: 2px;
  vertical-align: -0.15em;
  background: var(--term-mint);
  animation: term-caret 1s steps(1, end) infinite;
}

@keyframes term-caret {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

/* Подвал карточки — как строка состояния в кабинете. Цифры примерные, поэтому
 * без цены запроса: её пришлось бы держать актуальной, иначе она обманет. */
.terminal-stats {
  display: flex;
  align-items: center;
  gap: 26px;
  padding: 14px 26px;
  background: var(--term-bar);
  border-top: 1px solid var(--term-line);
}

.t-stat {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 700;
  /* Значение метрики — то, ради чего на неё смотрят, поэтому полный контраст.
     Подпись ниже (.t-stat b) намеренно приглушена: она лишь поясняет число. */
  color: var(--text);
  white-space: nowrap;
}

.t-stat b {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #8a8f98;
}

/* Подсвечен только отклик: это то, что человек замечает в работе. */
.t-stat em {
  font-style: normal;
  color: var(--term-mint);
}

/* До запуска сценарий прячет строки, чтобы они не мелькнули целиком.
 * Класс ставится скриптом — без JS текст остаётся виден полностью. */
.terminal-live.is-scripted [data-line] {
  visibility: hidden;
}

.terminal-live.is-scripted [data-line].is-shown {
  visibility: visible;
}

/* Код клиента в секции demo. Собственный горизонтальный скролл вместо
 * обрезки: строку кода нельзя показать «до середины» — её копируют. */
.terminal-code {
  margin: 0;
  padding: 22px 24px;
  overflow-x: auto;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1.65;
  color: var(--text);
  -webkit-overflow-scrolling: touch;
}

.terminal-code code {
  font: inherit;
}

/* --- Заголовки секций --- */
.section-title {
  margin: 0;
  font-size: 42px;
  line-height: 1.16;
  font-weight: 700;
  letter-spacing: -0.025em;
  max-width: 860px;
}

.section-lede {
  margin: 16px 0 0;
  font-size: 18px;
  line-height: 1.5;
  font-weight: 400;
  color: var(--text-2);
  max-width: 860px;
}

/* ================= Появление при прокрутке =================
 *
 * Блоки всплывают, когда доходят до экрана. Класс .reveal ставится в разметке,
 * .is-visible — скриптом reveal.js через IntersectionObserver.
 *
 * Почему начальное состояние привязано к атрибуту, а не задано сразу:
 * если файл reveal.js не загрузился, класс .is-visible никто не поставит и
 * страница осталась бы пустой. Атрибут data-reveal-ready ставит сам скрипт
 * первым же действием — значит правило прячет блоки только там, где их
 * гарантированно покажут.
 *
 * Анимируем opacity и transform: это свойства композитора, они не заставляют
 * браузер пересчитывать раскладку и не дёргают страницу. */
[data-reveal-ready] .reveal {
  opacity: 0;
  /* Небольшой сдвиг вбок добавляет направление: карточки не просто всплывают,
     а приходят слева направо вслед за задержкой. */
  transform: translate3d(-14px, 16px, 0);
}

/* Анимация появления объявлена через КАСТОМНЫЕ СВОЙСТВА, а не одним
 * `transition`.
 *
 * Причина измерена на живой странице: `.card` ниже по файлу объявляет свой
 * `transition` для наведения, специфичность у них одинаковая — и короткое
 * правило побеждало длинное. У карточек оставались только hover-переходы:
 * `opacity` не анимировалась вовсе (отсюда «возникают резко»), а
 * `transition-delay` обнулялся, из-за чего вся волна `.reveal-1..6`
 * схлопывалась и шесть блоков появлялись разом.
 *
 * Теперь каждый блок складывает СВОИ переходы из переменных, поэтому
 * появление и отклик на курсор больше не вытесняют друг друга. */
.reveal {
  --reveal-duration: 680ms;
  --reveal-ease: cubic-bezier(0.22, 0.68, 0.24, 1);
  --reveal-delay: 0ms;
  transition:
    opacity var(--reveal-duration) var(--reveal-ease) var(--reveal-delay),
    transform var(--reveal-duration) var(--reveal-ease) var(--reveal-delay);
}

.reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* Задержки соседей: волна слева направо и сверху вниз.
 *
 * 90 мс — шаг, на котором последовательность читается как движение, а не как
 * набор независимых вспышек. Шесть карточек разворачиваются за ~0.54 с: ещё
 * не ожидание, но уже заметный порядок. */
.reveal-1 { --reveal-delay: 90ms; }
.reveal-2 { --reveal-delay: 180ms; }
.reveal-3 { --reveal-delay: 270ms; }
.reveal-4 { --reveal-delay: 360ms; }
.reveal-5 { --reveal-delay: 450ms; }
.reveal-6 { --reveal-delay: 540ms; }

@media (prefers-reduced-motion: reduce) {
  /* Просьбу о покое уважаем: блоки просто на месте, без всплытия.
     Начальное состояние тоже снимаем — иначе содержимое осталось бы
     прозрачным, если скрипт по какой-то причине не дойдёт до него. */
  [data-reveal-ready] .reveal,
  .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Мягкая смена языка.
 *
 * Раньше переключатель перезагружал страницу: белая вспышка, потерянная
 * позиция прокрутки и заново проигранные анимации появления. Теперь текст
 * подменяется на месте, а переход прикрыт коротким затуханием — буквы не
 * «прыгают» на глазах.
 *
 * Гасим только прозрачность: раскладка не трогается, поэтому страница не
 * дёргается, а длинная английская строка не успевает мигнуть переносом. */
html[data-lang-switching] body {
  opacity: 0.55;
}

body {
  transition: opacity 180ms ease;
}

@media (prefers-reduced-motion: reduce) {
  html[data-lang-switching] body {
    opacity: 1;
  }

  body {
    transition: none;
  }
}

/* --- 03 / Лента совместимости --- */
.marquee-section {
  background: var(--canvas);
  padding: 48px 0 64px;
}

.marquee {
  /* 56 − 14 = 42px: внутренний отступ ниже добавляет 14px сверху, поэтому
     видимое расстояние до заголовка остаётся тем же 56px. */
  margin-top: 42px;
  display: flex;
  gap: 16px;
  /* Запас 14px по вертикали ВНУТРИ ленты. Так выросший при наведении чип
     (46 → 49.7px) и его тень попадают в область отсечения и не срезаются:
     раньше overflow: hidden рубил ему верх и низ, и вместо таблетки под
     курсором оставался прямоугольник.

     Почему не overflow: visible и не overflow-clip-margin: и то и другое
     выпускает вторую копию ленты за край экрана, и вся страница получает
     горизонтальную прокрутку (замер показывал scrollWidth 6181px при
     ширине 390px). hidden обрезает по всем сторонам разом — значит запас
     должен быть внутри, а не снаружи. */
  padding: 14px 0;
  overflow: hidden;
  /* Края растворяются — лента выглядит бесконечной, а не обрезанной.
   * Фиксированные 96px, а не проценты: на телефоне 6% это ~23px, чего
   * не хватало даже на половину чипа, и первый чип выглядел срезанным. */
  mask-image: linear-gradient(
    90deg,
    transparent,
    #000 96px,
    #000 calc(100% - 96px),
    transparent
  );
  -webkit-mask-image: linear-gradient(
    90deg,
    transparent,
    #000 96px,
    #000 calc(100% - 96px),
    transparent
  );
}

.marquee-track {
  display: flex;
  gap: 16px;
  flex: 0 0 auto;
  animation: marquee 22s linear infinite;
}

/* Наведение останавливает ленту: чтобы прочитать название или прицелиться в
 * чип, движение нужно остановить. Пауза на обе копии сразу — иначе вторая
 * уезжает и шов становится виден. Курсор ушёл — бег продолжается с того же
 * места, потому что animation-play-state не сбрасывает прогресс. */
.marquee:hover .marquee-track {
  animation-play-state: paused;
}

/* На тач-экране «наведения» нет, а :hover залипает после тапа и лента
 * замирала бы насовсем. */
@media (hover: none) {
  .marquee:hover .marquee-track {
    animation-play-state: running;
  }
}

@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(calc(-100% - 16px)); }
}

@media (prefers-reduced-motion: reduce) {
  /* Движение выключаем, но отклик оставляем цветом: кнопка обязана давать
   * понять, что нажатие сработало, даже без анимации. */
  .btn:hover,
  .btn:active,
  .nav-brand:hover {
    transform: none;
  }

  .marquee-track {
    animation: none;
  }

  /* Мигающий курсор в карточке терминала и пульс «онлайн» — тоже движение.
     Скрипт в этом режиме вообще не печатает, но правила нужны: курсор в
     пустом приглашении и точка живут на чистом CSS, без участия скрипта. */
  .terminal-live [data-line].is-typing::after,
  .t-prompt::after {
    animation: none;
    opacity: 1;
  }

  .terminal-live-badge i {
    animation: none;
  }

  /* Без анимации вторая копия — просто дубль за краем экрана: она не нужна,
     а перенос строк показывает весь список сразу, ничего не обрезая. */
  .marquee {
    mask-image: none;
    -webkit-mask-image: none;
    flex-wrap: wrap;
  }

  .marquee-track {
    flex-wrap: wrap;
  }

  .marquee-track[aria-hidden="true"] {
    display: none;
  }
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  /* 36→46px и кегль 13→15px: лента была мелкой и читалась как подпись, а не
     как список инструментов, который просят рассмотреть. */
  height: 46px;
  padding: 0 20px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line);
  font-size: 15px;
  font-weight: 600;
  white-space: nowrap;
  /* Анимируем только transform, тень и цвет рамки — свойства композитора,
     поэтому увеличение не пересчитывает раскладку ленты. */
  transition:
    transform 180ms cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 180ms cubic-bezier(0.2, 0.7, 0.3, 1),
    border-color 160ms ease;
}

/* Наведение увеличивает чип (лента при этом замирает, см. .marquee:hover).
   Вторая копия ленты помечена aria-hidden и живёт только ради бесшовного
   цикла — но реагировать на курсор должна так же, иначе под курсором чип
   вёл бы себя по-разному в зависимости от того, какая копия сейчас видна. */
.chip:hover {
  transform: scale(1.08);
  border-color: var(--accent);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.12);
  z-index: 1;
}

@media (hover: none) {
  .chip:hover {
    transform: none;
    border-color: var(--line);
    box-shadow: none;
  }
}

.chip::before {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
}

/* --- 04 / Возможности --- */
.features {
  background: var(--surface-2);
  padding: 80px 0;
}

.cards-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin-top: 28px;
}

.cards-2 {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
  margin-top: 28px;
}

.card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 24px;
  /* Отклик на курсор: карточка поднимается и получает тень. Анимируем только
     свойства композитора, поэтому наведение не пересчитывает сетку. */
  transition:
    transform 200ms cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 200ms cubic-bezier(0.2, 0.7, 0.3, 1),
    border-color 180ms ease;
}

/* Карточка, которая ещё и появляется при прокрутке.
 *
 * Пока блок не показан, `transform` принадлежит анимации появления — на этом
 * и ломалось: короткое hover-правило `.card` перебивало длинное `.reveal`,
 * `opacity` переставала анимироваться, а задержки волны обнулялись. Здесь обе
 * роли сведены в один `transition` явно: длинное появление плюс короткий
 * отклик на тень и рамку.
 *
 * `transform` указан ОДИН раз, с длительностью появления: пока карточка
 * выезжает, подъём по наведению всё равно не нужен — и перечислить свойство
 * дважды в одном `transition` нельзя, браузер оставит последнее. */
.card.reveal {
  transition:
    opacity var(--reveal-duration) var(--reveal-ease) var(--reveal-delay),
    transform var(--reveal-duration) var(--reveal-ease) var(--reveal-delay),
    box-shadow 200ms cubic-bezier(0.2, 0.7, 0.3, 1),
    border-color 180ms ease;
}

/* Появление ЗАВЕРШИЛОСЬ — возвращаем карточке быстрый отклик на курсор.
 *
 * Класс ставит reveal.js по событию transitionend, а НЕ вместе с is-visible:
 * is-visible появляется в момент СТАРТА анимации, и правило с коротким
 * transition оборвало бы её на 200 мс, заодно потеряв задержку волны. */
.card.reveal.reveal-done {
  transition:
    opacity 200ms ease,
    transform 200ms cubic-bezier(0.2, 0.7, 0.3, 1),
    box-shadow 200ms cubic-bezier(0.2, 0.7, 0.3, 1),
    border-color 180ms ease;
}

.card:hover {
  transform: translateY(-3px);
  border-color: var(--accent);
  box-shadow: 0 14px 34px rgba(0, 0, 0, 0.10);
}

/* Точка в подсвеченной карточке растёт вместе с ней — иначе при наведении
   карточка оживает, а её метка остаётся неподвижной и это заметно. */
.card:hover .dot {
  transform: scale(1.3);
}

@media (hover: none) {
  /* На тач-экране :hover залипает после тапа: карточка осталась бы поднятой. */
  .card:hover {
    transform: none;
    border-color: var(--line);
    box-shadow: none;
  }

  .card:hover .dot {
    transform: none;
  }
}

/* Акцентная карточка — заливка accent-soft, как в макете. */
.card-soft {
  background: var(--accent-soft);
}

.dot {
  display: block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent);
  /* Свой transition: без него точка при наведении на карточку прыгала бы
     в новый размер мгновенно, пока сама карточка плавно поднимается. */
  transition: transform 200ms cubic-bezier(0.2, 0.7, 0.3, 1);
}

.card h3 {
  margin: 12px 0 0;
  font-size: 20px;
  line-height: 1.35;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.card p {
  margin: 12px 0 0;
  font-size: 15px;
  line-height: 1.5;
  font-weight: 400;
  color: var(--text-2);
}

/* --- 05 / Как это работает --- */
.how {
  background: var(--canvas);
  padding: 80px 0;
}

.step-num {
  display: block;
  margin-bottom: 12px;
  font-family: var(--font-mono);
  font-weight: 700;
  font-size: 14px;
  color: var(--accent-strong);
}

.url-sample {
  margin-top: 28px;
  padding: 20px 24px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
}

.url-sample p {
  margin: 0;
  font-family: var(--font-mono);
}

.url-main {
  font-size: 15px;
  color: var(--text);
}

.url-auth {
  margin-top: 8px !important;
  font-size: 14px;
  color: var(--text-2);
}

/* --- 06 / Живая демонстрация --- */
.demo {
  background: var(--surface);
  padding: 80px 0;
}

.demo-split {
  display: grid;
  grid-template-columns: 420px 1fr;
  gap: 64px;
  align-items: center;
}

.demo-body {
  margin: 20px 0 24px;
  font-size: 18px;
  line-height: 1.52;
  font-weight: 400;
  color: var(--text-2);
}

/* --- 07 / Тарифы --- */
.pricing {
  background: var(--canvas);
  padding: 80px 0;
}

/* Плашка с ценой. Стоит до карточек, потому что это первое, что ищут на
 * странице тарифов: цифру, а не описание. Две части — базовая ставка и
 * множитель кэша: ровно то, из чего складывается счёт (см. app/pricing.py). */
.price-band {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  margin-top: 28px;
}

.price-main,
.price-cache {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 260px;
  padding: 24px 28px;
  border-radius: 16px;
  border: 1px solid var(--line);
  background: var(--surface);
}

/* Кэш подсвечен: это выгода клиента, а не просто вторая цифра. */
.price-cache {
  background: var(--accent-soft);
  border-color: transparent;
}

.price-value {
  font-family: var(--font-mono);
  font-size: 38px;
  line-height: 1.1;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text);
}

.price-cache .price-value {
  color: var(--accent-strong);
}

.price-unit {
  font-size: 16px;
  font-weight: 700;
}

.price-note {
  font-size: 14px;
  font-weight: 400;
  color: var(--text-2);
}

/* --- 09 / Final CTA --- */
.cta {
  position: relative;
  background: var(--canvas);
  padding: 76px 0 84px;
  overflow: hidden;
}

.blob-cta {
  width: 560px;
  height: 420px;
  top: -80px;
  right: 60px;
}

.cta-inner {
  position: relative;
  z-index: 1;
}

.cta-title {
  margin: 0;
  max-width: 850px;
  font-size: 42px;
  line-height: 1.18;
  font-weight: 700;
  letter-spacing: -0.025em;
}

.cta-body {
  margin: 22px 0 24px;
  font-size: 18px;
  font-weight: 400;
  color: var(--text-2);
}

/* --- 10 / Footer --- */
.footer {
  background: var(--surface);
  padding: 56px 0 28px;
  border-top: 1px solid var(--line);
}

/* Четыре колонки: о сервисе шире остальных, потому что в ней абзац текста,
   а не список ссылок. Реквизиты последние — их ищут целенаправленно. */
.footer-top {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(2, minmax(0, 1fr)) minmax(0, 1.3fr);
  gap: 40px;
}

.footer-brand {
  display: block;
  font-size: 20px;
  font-weight: 700;
}

.footer-copy {
  margin: 12px 0 0;
  max-width: 300px;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 400;
  color: var(--text-2);
}

.footer-nav,
.footer-legal {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.footer-nav b,
.footer-legal b {
  margin-bottom: 14px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-2);
}

/* Ссылки подвала — тач-таргет 32px по высоте: в столбик они стояли вплотную
   и на телефоне попасть в нужную было делом случая. */
.footer-nav a {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  transition: color 160ms ease;
}

.footer-nav a:hover {
  color: var(--accent-strong);
}

.footer-legal p {
  margin: 0 0 10px;
  font-size: 14px;
  line-height: 1.6;
  font-weight: 400;
  color: var(--text-2);
}

.footer-legal a {
  color: var(--text-2);
  transition: color 160ms ease;
}

.footer-legal a:hover {
  color: var(--accent-strong);
}

/* Примечание про реквизиты — мельче основного текста подвала: это сноска,
   а не контактные данные. ФИО и ИНН со страниц убраны по решению владельца,
   поэтому моноширинного .footer-inn здесь больше нет. */
.footer-note {
  max-width: 260px;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-2);
}

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 32px;
  margin-top: 44px;
  padding-top: 22px;
  border-top: 1px solid var(--line);
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-2);
}

/* ================= Мобильный: «Лендинг / Mobile / Light», 393px ================= */
@media (max-width: 900px) {
  .nav-links {
    display: none;
  }

  /* Меню скрыто, поэтому средняя колонка сетки не нужна: бренд слева,
     действия справа. */
  .nav-inner {
    grid-template-columns: auto 1fr;
    gap: 12px;
  }

  .nav {
    height: 68px;
  }

  .nav-brand {
    font-size: 18px;
  }

  .nav .btn {
    min-height: 44px;
    padding: 0 16px;
    font-size: 14px;
  }

  .nav-actions {
    margin-left: auto;
    /* 6px вместо 8px: в группу добавился переключатель языка, и на 320px
       прежние зазоры выводили шапку за край экрана на 10px (замерено). */
    gap: 6px;
  }

  .nav-link-in {
    min-height: 44px;
    padding: 0 8px;
    font-size: 14px;
  }

  /* На телефоне переключатель остаётся тач-целью, но занимает минимум:
     он вспомогательный, а место в шапке делят ещё бренд, «Войти» и кнопка. */
  .lang-btn {
    min-height: 38px;
    min-width: 34px;
    padding: 0 4px;
    font-size: 12px;
    letter-spacing: 0;
  }

  .lang-switch {
    padding: 2px;
    gap: 0;
  }

  .hero {
    padding: 40px 0 48px;
  }

  .hero-grid,
  .demo-split {
    grid-template-columns: 1fr;
    gap: 32px;
  }

  /* clamp вместо одной фиксированной ступени: между 320px и 900px заголовок
     меняется плавно, поэтому на самых узких экранах не остаётся строк-сирот. */
  .hero-copy h1 {
    font-size: clamp(31px, 8.6vw, 44px);
  }

  .hero-body,
  .section-lede,
  .demo-body,
  .cta-body {
    font-size: 16px;
  }

  .hero-actions {
    flex-direction: column;
    align-items: stretch;
    /* В столбик 24px избыточны — элементы и так разделены переносом. */
    gap: 12px;
  }

  .hero-link {
    justify-content: center;
  }

  .section-title,
  .cta-title {
    font-size: clamp(25px, 6.6vw, 34px);
  }

  .cards-3,
  .cards-2 {
    grid-template-columns: 1fr;
  }

  /* Цена на телефоне в столбик: две плашки по 260px в ряд не влезают
     (нужно 540px при доступных 342px), и flex-basis рвал бы цифру. */
  .price-band {
    flex-direction: column;
    gap: 12px;
  }

  .price-main,
  .price-cache {
    padding: 20px 22px;
  }

  .price-value {
    font-size: 32px;
  }

  .features,
  .how,
  .demo,
  .pricing,
    .blob-a {
    width: 340px;
    height: 340px;
    right: -140px;
  }

  .blob-b,
  .blob-cta {
    display: none;
  }

  .url-sample p {
    font-size: 13px;
    word-break: break-all;
  }

  .terminal-flow,
  .terminal-code {
    padding: 18px;
  }

  /* На телефоне скролл внутри карточки незаметен: строка выглядит просто
     обрезанной. Поэтому здесь строки переносятся — текст виден целиком. */
  .terminal-flow {
    overflow-x: visible;
  }

  .terminal-flow p {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
  }

  /* На одной колонке растягивать карточку не по чему — высоту задаёт её
     собственный текст. min-height: 100% от auto-высоты строки сетки дал бы
     0 и карточка схлопнулась бы. */
  .terminal-live {
    min-height: 0;
  }

  .terminal-live .terminal-flow {
    padding: 18px 18px 20px;
  }

  /* Приглашение на телефоне идёт сразу за выводом: прижимать его к низу
     нечему, карточка обжимает содержимое. */
  .t-prompt {
    margin-top: 14px !important;
    padding-top: 0;
  }

  /* Три показателя в ряд на 342px не помещаются (нужно ~330px без зазоров),
     поэтому зазор меньше, а кегль чуть ниже. Переносить в столбик не стал:
     строка состояния должна читаться одной строкой. */
  .terminal-stats {
    gap: 14px;
    padding: 12px 18px;
  }

  .t-stat {
    font-size: 13px;
  }

  .terminal-code {
    font-size: 12px;
  }

  /* Кнопка в шапке не должна ломаться на две строки. */
  .nav .btn {
    white-space: nowrap;
  }

  /* Подвал на телефоне: две колонки вместо четырёх. В одну вытягивало бы
     страницу на лишние 300px, а ссылки и реквизиты короткие. */
  .footer {
    padding: 40px 0 24px;
  }

  .footer-top {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px 20px;
  }

  /* Блок «о сервисе» — на всю ширину: в нём абзац, а не список. */
  .footer-about {
    grid-column: 1 / -1;
  }

  .footer-bottom {
    margin-top: 28px;
    gap: 8px;
  }
}

/* Узкие телефоны (iPhone SE и подобные, 320–359px). Здесь шапка не влезала:
   бренд + «Войти» + кнопка требовали 350px при вьюпорте 320px, и вся страница
   получала горизонтальную прокрутку. Дефект был и до правок первого экрана —
   проверено на проде тем же измерителем.

   Лечим сжатием шапки, а не скрытием входа: «Войти» — единственный путь для
   вернувшегося человека, спрятать его хуже, чем уменьшить. */
@media (max-width: 359px) {
  .wrap {
    padding: 0 16px;
  }

  .nav-brand {
    font-size: 16px;
    gap: 8px;
  }

  .nav-logo {
    width: 26px;
    height: 26px;
  }

  .nav-actions {
    gap: 4px;
  }

  .nav-link-in {
    padding: 0 6px;
    font-size: 13px;
  }

  /* 320–359px: шапке и до локализации не хватало места (дефект был на проде).
     Переключатель сжимаем, а не скрываем: спрятанный выбор языка на телефоне
     означал бы, что русскоязычный посетитель с узкого экрана не сможет уйти
     с английского вообще. */
  .lang-btn {
    min-width: 28px;
    min-height: 36px;
    padding: 0 2px;
    font-size: 11px;
    letter-spacing: 0;
  }

  .nav .btn {
    padding: 0 10px;
    font-size: 13px;
  }
}
