/* ============================================================================
   ЛУЧ. Один авторский приём на весь сайт. 26.08.2026
   Файл новый и подключён ПОСЛЕДНИМ. styles.css (8496 строк) не тронут,
   polish-*, deshablon-*, taste-rank и motion-emil не тронуты.

   ЧТО ГОВОРИТ bolder.md И ЧТО ИЗ ЭТОГО СЛЕДУЕТ ЗДЕСЬ
   ---------------------------------------------------
   1. Первый рефлекс на «сделай смелее» - добавить эффектов - и есть
      противоположность смелости. Его надо отвергнуть первым. Поэтому тут
      НЕ россыпь анимаций, а ОДИН источник света, которому подчинены слои.
   2. Раздел выглядит плоским обычно потому, что он тихо отказался от самых
      сильных приёмов, которые страница уже умеет. Лечится не новым словарём,
      а тем, что собственный приём системы включают на полную. Здесь система
      уже владеет светом: свечение под калькулятором, акцент #4FC4E9, серебро
      заголовков, перелив на кнопках. Свет и взят как единственная идея.
   3. Полумеры читаются как шум. Один решительный ход делается ЦЕЛИКОМ, а всё
      вокруг него приглушается, чтобы ход был читаем. Если громче стало всё,
      значит стало площе.
   4. Ничего нового в систему не заводится: ни цвета, ни гарнитуры, ни радиуса.
      Луч собран из --accent и белого, которые уже в палитре.
   5. Проверка скелетом: если снять весь текст, первый экран всё равно
      рассказывает композицией - луч проходит, свет садится за панель,
      слои стоят на трёх разных глубинах.

   ЗАМЕР ДО ПРАВКИ (headless Chrome, 1440x900, живая страница)
   -----------------------------------------------------------
   - слоёв, реально едущих при прокрутке: 0 (снимки transform в трёх
     положениях скролла совпадают у всех 12 кандидатов);
   - вход первого экрана: aeHeroIn, амплитуда 14px, три блока целыми кусками;
   - заголовок первого экрана 52.48px при потолке шкалы 58px;
   - .lead первого экрана 14.5px;
   - линейка .rule статична, входа нет; шапок с линейкой девять, но живых
     шесть - у #services, #reviews и #faq она display:none;
   - .reveal: в правиле styles.css стоит 20px, но на живой странице замер
     через отключение этого файла дал 12px у 32 элементов из 34 (позднее
     правило перебивает), у одного 0px и у одного 28px;
   - caret-color в проекте не задан ни разу.

   ЧЕГО ЗДЕСЬ НЕТ И ПОЧЕМУ
   -----------------------
   - скругление 6px, серебро заголовков, перелив кнопок, круглый помощник,
     верхняя кромка и рамки девяти разделов не тронуты: канон агентства 17.08;
   - анимируются ТОЛЬКО transform и opacity, ни одного кадра по filter,
     box-shadow, width или top: они считаются на каждом кадре и роняют
     прокрутку;
   - blur на движущемся слое не применён нигде: мягкость луча набрана
     градиентом, он бесплатен;
   - всё движение гаснет по prefers-reduced-motion, но СМЫСЛ остаётся:
     снимается движение, а не содержимое.
   ========================================================================== */

:root{
  --sb-sweep: 1500ms;                          /* проход луча, один раз */
  --sb-out:   cubic-bezier(.16, 1, .3, 1);     /* усиленный ease-out */
  --sb-stage: 640ms;                           /* сборка первого экрана */
  --sb-step:  58ms;                            /* шаг ступеньки */
}


/* ===========================================================================
   1. ЛУЧ ПРОХОДИТ ПО ПЕРВОМУ ЭКРАНУ

   Единственное событие, которое человек видит до всякой прокрутки. Широкая
   мягкая полоса света идёт слева направо за композицией и уходит. Она лежит
   на z-index:0, то есть ПОЗАДИ текста (.hero__copy держит z-index:1): свет
   за предметом даёт объём, свет по тексту дал бы блик по буквам и мешал бы
   читать.

   mix-blend-mode:screen статичен, он не анимируется - анимируются только
   transform и opacity. У .hero стоит isolation:isolate, поэтому режим
   наложения заперт внутри первого экрана и до остальной страницы не доходит.
   =========================================================================== */
/* 🔴 ОБОЙМА ДЛЯ СВЕТА. Первая же приёмка поймала блокер: луч уезжает в
   translate3d(150vw), и страница получила горизонтальную прокрутку на всех
   трёх ширинах - 712px на 375, 952px на 768, 1628px на 1440. Область
   первого экрана не режет по краям, а вешать overflow:hidden на сам .hero
   нельзя: под ним живут выпадающие списки валют калькулятора, и любой из
   них обрезался бы. Поэтому свет заперт в собственной обойме: она повторяет
   первый экран, режет ТОЛЬКО своих детей и не касается ни одного чужого
   элемента. */
.ae-light{
  position:absolute; inset:0; z-index:0;
  overflow:hidden; pointer-events:none;
}

.ae-beam{
  position:absolute; z-index:0; pointer-events:none;
  top:-40%; left:0; width:34%; height:180%;
  opacity:0;
  transform:translate3d(-45vw,0,0) rotate(13deg);
  background:linear-gradient(90deg,
      rgba(255,255,255,0)    0%,
      rgba(79,196,233,.15)  28%,
      rgba(255,255,255,.34) 50%,
      rgba(79,196,233,.15)  72%,
      rgba(255,255,255,0)  100%);
  mix-blend-mode:screen;
  animation:sbSweep var(--sb-sweep) cubic-bezier(.36,.02,.16,1) 140ms both;
  will-change:transform, opacity;
}
@keyframes sbSweep{
  0%  { opacity:0;   transform:translate3d(-45vw,0,0) rotate(13deg); }
  16% { opacity:.95; }
  78% { opacity:.55; }
  100%{ opacity:0;   transform:translate3d(150vw,0,0) rotate(13deg); }
}

/* Свет САДИТСЯ. Луч ушёл, но источник остался: за калькулятором зажигается
   мягкое пятно. Это и есть постоянный источник света первого экрана, тот же,
   за которым дальше идёт курсор. Переменные --sb-ox/--sb-oy пишет
   signature-beam.js прямо в кадре; они стоят ВНУТРИ ключевых кадров, потому
   что анимация с fill:both перебила бы inline-стиль. */
.ae-orb{
  position:absolute; z-index:0; pointer-events:none;
  /* 🔴 Пятно посажено по ЗАМЕРУ, а не на глаз: первый экран 1739px высотой,
     а кромка экрана на 900. Первая посадка (top:2%, height:84%) уводила сердце
     света на отметку ~780, то есть в невидимую часть. Сдвинуто вверх, чтобы
     свет стоял ровно там, где человек смотрит до всякой прокрутки. */
  right:-4%; top:-8%; width:58%; height:64%;
  opacity:0;
  transform:translate3d(0,0,0) scale(.92);
  background:
    radial-gradient(46% 44% at 60% 46%, rgba(79,196,233,.26), transparent 70%),
    radial-gradient(32% 30% at 36% 70%, rgba(255,255,255,.095), transparent 72%);
  animation:sbOrbIn 1100ms var(--sb-out) 820ms both;
  will-change:transform, opacity;
}
@keyframes sbOrbIn{
  from{ opacity:0; transform:translate3d(var(--sb-ox,0px), var(--sb-oy,0px),0) scale(.92); }
  to  { opacity:1; transform:translate3d(var(--sb-ox,0px), var(--sb-oy,0px),0) scale(1); }
}


/* ===========================================================================
   2. ПЕРВЫЙ ЭКРАН СОБИРАЕТСЯ В СЛЕД ЛУЧА

   Было: три больших куска (.hero__copy, .calc, .trust) въезжали целиком на
   14px. Кусок из семи разных по смыслу строк, который двигается как одна
   плита, движением не читается - это просто задержка появления.

   Стало: в след за лучом встают по очереди плашка курса, заголовок, ротор,
   абзац, кнопки, подпись и полоса курса, а панель калькулятора приходит
   отдельным планом - чуть позже, с большего сдвига и с масштаба .984, то
   есть ИЗ ГЛУБИНЫ, а не из ниоткуда. Скилл прямо запрещает появление из
   нулевого размера: в реальном мире ничто не возникает из ничего.

   Родительская анимация .hero__copy снята намеренно: две вложенные
   непрозрачности перемножались и давали вялый двойной проявитель.
   Страховка сохранена - у каждой ступени fill:both, поэтому конечное
   состояние «видно» выставляет сама анимация, без единой строки JS.
   =========================================================================== */
.hero .hero__copy{ animation:none; opacity:1; transform:none; }

.hero .hero__copy > *{
  animation:sbStageIn var(--sb-stage) var(--sb-out) both;
  animation-delay:280ms;
}
@keyframes sbStageIn{
  from{ opacity:0; transform:translate3d(0,22px,0); }
  to  { opacity:1; transform:none; }
}
.hero .hero__copy > *:nth-child(1){ animation-delay:280ms; }
.hero .hero__copy > *:nth-child(2){ animation-delay:calc(280ms + var(--sb-step) * 1); }
.hero .hero__copy > *:nth-child(3){ animation-delay:calc(280ms + var(--sb-step) * 2); }
.hero .hero__copy > *:nth-child(4){ animation-delay:calc(280ms + var(--sb-step) * 3); }
.hero .hero__copy > *:nth-child(5){ animation-delay:calc(280ms + var(--sb-step) * 4); }
.hero .hero__copy > *:nth-child(6){ animation-delay:calc(280ms + var(--sb-step) * 5); }
.hero .hero__copy > *:nth-child(7){ animation-delay:calc(280ms + var(--sb-step) * 6); }
.hero .hero__copy > *:nth-child(n+8){ animation-delay:calc(280ms + var(--sb-step) * 7); }

/* Панель калькулятора: отдельный план, приходит ИЗ ГЛУБИНЫ.

   🔴 Замер поймал ловушку: на 1440 сетка первого экрана однополосная
   (grid-template-columns отдаёт одну колонку 764px), панель стоит НИЖЕ текста,
   её верх на отметке 954px при экране 900. То есть анимация входа по загрузке
   отыгрывала бы за нижней кромкой экрана и её не увидел бы никто. Поэтому
   вход панели привязан не к загрузке, а к её ПОЯВЛЕНИЮ в поле зрения.

   Класс sb-arm ставит signature-beam.js и только он: без скриптов панель
   просто стоит на месте, ничего не пряча. Содержимое не имеет права зависеть
   от анимации. */
.calc.sb-arm{
  opacity:0; transform:translate3d(0,34px,0) scale(.982);
  transition:opacity 780ms var(--sb-out), transform 780ms var(--sb-out);
}
.calc.sb-arm.sb-in{ opacity:1; transform:none; }

/* Пока висит шторка интро, весь первый экран стоит на паузе - иначе сборка
   отыгрывает ЗА непрозрачным оверлеем и первого захода её не видит никто.
   Класс sb-force ставит сторож в signature-beam.js: если интро по какой-то
   причине не снялось за 7 секунд, движение запускается принудительно, и
   содержимое не остаётся невидимым из-за чужого сбоя. */
html.intro-lock:not(.sb-force) .ae-beam,
html.intro-lock:not(.sb-force) .ae-orb,
html.intro-lock:not(.sb-force) .hero .hero__copy > *{ animation-play-state:paused; }


/* ===========================================================================
   3. ГЛУБИНА: ТРИ ФОНОВЫХ ПЛАНА ЕДУТ С РАЗНОЙ СКОРОСТЬЮ

   Значения --sb-sy (прокрутка) и --sb-mx/--sb-my (курсор) пишет JS ОДИН раз
   за кадр и ТОЛЬКО на .hero: одна запись на кадр вместо четырёх, дальше
   раздаёт каскад. Множители подобраны так, чтобы у каждого слоя была своя
   скорость, но ни один не выезжал за первый экран заметным пятном.

   Содержимое (текст и калькулятор) НЕ трогается прокруточным параллаксом
   намеренно: transform на .hero__grid сделал бы его контейнером для любого
   position:fixed внутри калькулятора и мог бы сломать выпадающие списки.
   Глубина читается по отставанию ФОНА, а этого достаточно.
   =========================================================================== */
/* 🔴 Масштаб холста ТОЛЬКО по вертикали, и это не придирка. Первая версия
   стояла scale(1.06), то есть 6% в обе стороны, и на 768 давала ровно 23px
   горизонтальной прокрутки, на 375 - 11px (замер во времени: появлялась на
   третьей секунде, когда снималась шторка интро). Холст лежит вне обоймы
   света, обрезать его нечем. Запас нужен был только по высоте - закрыть
   полосу, которая оголяется при параллаксе, - поэтому scaleY и решает задачу
   целиком, а горизонтали не касается вовсе. 8% при ходе до 62px закрывают
   обе кромки с запасом. */
.hero .cnst{
  transform:translate3d(calc(var(--sb-mx,0px) * .35), calc(var(--sb-sy,0px) * .12), 0) scaleY(1.08);
  will-change:transform;
}
.hero .hero__glow{
  transform:translate3d(var(--sb-mx,0px), calc(var(--sb-sy,0px) * .16), 0);
  will-change:transform;
}
/* Пятно света идёт ПРОТИВ курсора: встречное движение слоёв и есть объём. */
.ae-orb{ --sb-ox:calc(var(--sb-mx,0px) * -.75); --sb-oy:calc(var(--sb-sy,0px) * .24 + var(--sb-my,0px) * -.6); }


/* ===========================================================================
   4. ТОТ ЖЕ ЛУЧ В РАЗДЕЛАХ: ЛИНЕЙКА ПРОЧЕРЧИВАЕТСЯ

   Линейку .rule под надзаголовком носят девять шапок разделов, но ЖИВЫХ из
   них шесть: у #services, #reviews и #faq линейка стоит display:none
   (styles.css, строка 5481 и соседние), и приём их не касается. Замер на
   живой странице: 6 линеек шириной 140/140/220/140/220/140. Раньше все шесть
   стояли мёртво. Теперь при появлении раздела линейка прочерчивается слева направо -
   тот же свет, что прошёл по первому экрану, только короче. Это ОДНА
   грамматика на весь сайт, а не второй приём.

   scaleX - чистый transform, ширина не анимируется. Origin слева, потому что
   градиент линейки гаснет вправо: свет обязан идти из яркого конца в тусклый.
   =========================================================================== */
/* 🔴 ГАШЕНИЕ ЛИНЕЙКИ РАЗРЕШЕНО ТОЛЬКО ЖИВОМУ СКРИПТУ.
   Приёмка со скептиком поймала потерю содержимого: при выключенном JS класс
   is-visible не ставит никто, страховка `:not(.reveal)` не срабатывает (все
   девять шапок носят .reveal), и линейки оставались в scaleX(0) - замер без
   JS дал 6 из 6 живых линеек невидимыми. До правки они просто стояли.
   Метка .ae12-live ставится effects.js при разборе файла и на этой странице
   уже служит признаком «скрипты доехали» (по ней же работает аварийный показ
   блоков). Гасить линейку имеет право только тот, кто потом сможет её
   прочертить, поэтому вход заперт этой же меткой.
   Правило `.section__head:not(.reveal)` оставлено на случай шапки без reveal:
   такая никогда не получит is-visible, и её линейка обязана стоять всегда. */
.ae12-live .section__head .rule{
  transform:scaleX(0); transform-origin:0 50%;
  transition:transform 760ms var(--sb-out) 120ms;
}
.ae12-live .section__head.is-visible .rule,
.section__head:not(.reveal) .rule{ transform:scaleX(1); }


/* ===========================================================================
   5. ПОЯВЛЕНИЕ БЛОКОВ: АМПЛИТУДА С 20px ДО 30px

   Механика входа на странице была, но на 20px её не видно: блок «уже стоял».
   Заголовки разделов из этого исключены - у них ход в другом, в линейке выше,
   и двигать шапку целиком значило бы дважды сказать одно и то же.
   =========================================================================== */
.reveal:not(.is-visible):not(.section__head){ transform:translate3d(0,30px,0); }
.ae4-stagger > .reveal:not(.is-visible){ transform:translate3d(0,30px,0) scale(.985); }


/* ===========================================================================
   6. СТАТИКА: ТО, ЧТО ВИДНО БЕЗ ЕДИНОГО ДВИЖЕНИЯ

   6.1 Заголовок первого экрана. Шкала системы допускает 58px, а на деле
   выдавала 52.48px: заголовок работал не в полную силу. Поднят до 64px на
   широком экране, трекинг -.042em - это ровно тот пункт bolder.md, где
   собственный масштаб системы включают на полную вместо изобретения нового.
   Телефонная шкала (max-width:620px) не тронута: там правило системы стоит
   в медиазапросе, а мой файл лежит позже, поэтому мой кегль обязан быть
   заперт в min-width:901px, иначе он перебил бы мобильный.
   =========================================================================== */
@media (min-width:901px){
  .hero h1{
    /* Потолок 56px выбран прибором, а не на глаз: перебор кеглей на живой
       странице дал 52/54/56 - три строки, 58 и выше - четыре. Заголовок
       собран ручными переносами под три строки, и четвёртая строка развалила
       бы композицию ради лишних двух пунктов кегля. */
    font-size:clamp(38px, 7.9cqi, 56px);
    letter-spacing:-.042em;
  }
  /* 6.2 Подводка первого экрана была 14.5px, то есть мельче обычного текста
     страницы. Первый абзац сайта не может быть самым мелким на экране. */
  .hero .lead{ font-size:16.5px; line-height:1.6; max-width:56ch; }
}

/* 6.3 Цифры. Курс и суммы в калькуляторе меняются на глазах, и при
   пропорциональных цифрах строка при каждом пересчёте дёргается по ширине.
   Табличные цифры держат разряд на месте. */
.rate-strip__v, #heroPillRate,
.calc input, .calc__out, .field__box input, .stats__v{
  font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum" 1;
}

/* 6.4 Поверхности, которые рисует браузер, а не мы. Каретка в полях ввода
   была системной (белая): единственное место на сайте, где цвет приходил
   не из палитры. Замер до правки: caret-color не задан в проекте ни разу. */
input, textarea, [contenteditable]{ caret-color:var(--accent); }


/* ===========================================================================
   7. МЕНЬШЕ ДВИЖЕНИЯ - ЗНАЧИТ БЕЗ ДВИЖЕНИЯ

   Гасится ход, а не содержимое: луч и пятно остаются как статичный свет,
   первый экран стоит собранным, линейки прочерчены, блоки на местах.
   JS в этом режиме не запускает цикл кадров вовсе.
   =========================================================================== */
@media (prefers-reduced-motion: reduce){
  .ae-beam{ animation:none; opacity:.28; transform:translate3d(18vw,0,0) rotate(13deg); }
  .ae-orb { animation:none; opacity:1;   transform:none; }
  .hero .hero__copy > *{ animation:none; opacity:1; transform:none; }
  /* Если «меньше движения» включили уже на открытой странице, класс sb-arm
     мог быть поставлен раньше. Панель обязана быть видна в любом случае. */
  .calc.sb-arm{ opacity:1; transform:none; transition:none; }
  .hero .cnst, .hero .hero__glow{ transform:none; }
  /* Вес селектора поднят до веса включающего правила выше (.ae12-live ...):
     медиазапрос веса не добавляет, и правило в два класса проиграло бы
     правилу в три. Линейка при «меньше движения» обязана быть прочерчена
     сразу, без опоры на класс is-visible от скрипта. */
  html .section__head .rule,
  html.ae12-live .section__head .rule{ transition:none; transform:scaleX(1); }
  .reveal:not(.is-visible):not(.section__head),
  .ae4-stagger > .reveal:not(.is-visible){ transform:none; }
}
