/* ============================================================
   «Вселенная подписок и нейросетей» — слой движения.

   Отдельный файл, а не хвост styles.css, и это принципиально: styles.css
   отвечает за то, чтобы страницу можно было прочитать, motion.css — только
   за то, как она оживает. Не загрузился — сайт остаётся ровно тем же
   статичным сайтом, ничего не разъезжается и не остаётся невидимым.

   Три правила, которые здесь нельзя нарушать.

   1. Скрытое состояние — только при включённом JS. Классы `.reveal` прячут
      блок, а показывает его наблюдатель из site.js. Если скрипт не приехал,
      класса `js` на <html> нет, и правила прятанья просто не срабатывают:
      человек видит всё сразу, а не пустую страницу.
   2. Скрытое состояние — только при `prefers-reduced-motion: no-preference`.
      В styles.css стоит глобальное `animation: none !important` для тех, кто
      просил меньше движения; без этой оговорки анимация выключилась бы, а
      начальный `opacity: 0` остался — и текст исчез бы совсем.
   3. Двигаем `transform` и `opacity`. Всё остальное (ширина, top, фильтры на
      пол-экрана) заставляет браузер пересчитывать раскладку в каждом кадре.
      Исключения — точечные и мелкие, они помечены на месте.

   Движение здесь работает на одну мысль: страница — не документ, а окно,
   за которым что-то происходит. Звёзды медленно плывут, туманности дышат,
   по кромке карточек проходит свет. Ничего из этого не несёт смысла и не
   красит текст, поэтому контрастность из styles.css не трогается.
   ============================================================ */

@font-face {
    /* Моноширинный — для служебных подписей: рубрики, бейджи, HUD на фото.
       Тот же JetBrains Mono, что в генераторе карточек Маркета, — цифры и
       коды по всему бренду выглядят одинаково. */
    font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap;
    src: url('../fonts/jbmono-cyrillic-500.woff2') format('woff2');
    unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
    font-family: 'JetBrains Mono'; font-style: normal; font-weight: 500; font-display: swap;
    src: url('../fonts/jbmono-latin-500.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
    --neon:    #35e0ff;      /* холодный свет звезды: линии, сетка, свечение */
    --neon-2:  #b98cff;      /* фиолет туманности, вторая точка градиента */
    --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Consolas, monospace;
    --ease: cubic-bezier(.22,.68,.28,1);
}

/* Типизированные свойства: без @property браузер считает значение обычной
   строкой и анимировать его не умеет — угол в conic-gradient прыгал бы
   от 0 к 360 без промежуточных кадров. */
@property --angle { syntax: "<angle>";      initial-value: 0deg; inherits: false; }
@property --sheen { syntax: "<percentage>"; initial-value: -55%; inherits: false; }

/* ============================================================
   1. Фоновые слои: сетка, горизонт, свечение, зерно
   ============================================================ */

/* Порядок слоёв снизу вверх:
     background у body   — фотография глубокого поля Хаббла
     body::before  (-3)  — вуаль с виньеткой, поднимает контраст текста
     .fx           (-2)  — свои звёзды, туманности, сетка и плоскость эклиптики
     контент       (авто)
     .grain        (60)  — зерно поверх всего, чтобы кадр не выглядел «цифровым»
   Отрицательный z-index у фиксированных слоёв кладёт их выше фона body, но
   ниже содержимого, и ни один из них не перехватывает клики.

   Зачем свои звёзды поверх фотографии звёзд: фотография прибита к экрану
   (background-attachment: fixed) и потому мертва. Эти два слоя медленно
   плывут с разной скоростью — небо получает глубину, которой у плоского
   кадра нет в принципе. */
.fx, .grain { position: fixed; inset: 0; pointer-events: none; }
.fx { z-index: -2; overflow: hidden; }

/* Сетка. Тянется чуть больше экрана и ползёт по диагонали трансформом —
   не background-position: сдвиг фона на весь экран перерисовывается
   процессором каждый кадр, трансформ уезжает на видеокарту. */
.fx::before {
    content: ""; position: absolute; inset: -90px;
    background-image:
        linear-gradient(rgba(120,170,255,.55) 1px, transparent 1px),
        linear-gradient(90deg, rgba(120,170,255,.55) 1px, transparent 1px);
    background-size: 72px 72px;
    -webkit-mask-image: radial-gradient(115% 78% at 50% 4%, #000 0%, transparent 74%);
    mask-image: radial-gradient(115% 78% at 50% 4%, #000 0%, transparent 74%);
    opacity: .09;
    animation: fx-grid 52s linear infinite;
}
@keyframes fx-grid { to { transform: translate3d(72px, 72px, 0); } }

/* Звёздные слои. Точки нарисованы радиальными градиентами в одном
   background — это дешевле любого канваса и не требует ни байта картинок.
   Слоя два и они разной крупности: мелкий уходит вправо медленно, крупный
   вдвое быстрее и заодно мерцает. Разная скорость и есть параллакс —
   ощущение, что между нами и дальним небом есть расстояние.

   Плитка 400×400 и сдвиг ровно на её ширину: петля получается бесшовной,
   стыка не видно. */
.fx-stars {
    position: absolute; inset: 0;
    background-repeat: repeat;
}
.fx-stars--far {
    background-image:
        radial-gradient(1.2px 1.2px at 34px 62px, rgba(255,255,255,.75), transparent),
        radial-gradient(1px 1px at 158px 24px, rgba(190,215,255,.7), transparent),
        radial-gradient(1.4px 1.4px at 262px 148px, rgba(255,255,255,.6), transparent),
        radial-gradient(1px 1px at 88px 226px, rgba(255,226,190,.65), transparent),
        radial-gradient(1.2px 1.2px at 330px 300px, rgba(255,255,255,.55), transparent),
        radial-gradient(1px 1px at 210px 352px, rgba(190,215,255,.6), transparent);
    background-size: 400px 400px;
    opacity: .55;
    animation: fx-stars 210s linear infinite;
}
.fx-stars--near {
    background-image:
        radial-gradient(2px 2px at 120px 96px, rgba(255,255,255,.9), transparent),
        radial-gradient(1.8px 1.8px at 296px 210px, rgba(200,230,255,.85), transparent),
        radial-gradient(2.2px 2.2px at 60px 330px, rgba(255,238,214,.8), transparent);
    /* Шаг плитки обязан совпадать со сдвигом в fx-stars, иначе на петле
       появляется шов: точки прыгают в тот кадр, где анимация начинается заново. */
    background-size: 400px 400px;
    opacity: .7;
    animation: fx-stars 96s linear infinite, fx-twinkle 7s ease-in-out infinite alternate;
}
@keyframes fx-stars { to { transform: translate3d(-400px, 0, 0); } }
@keyframes fx-twinkle { from { opacity: .42; } to { opacity: .8; } }

/* Пятна света — «дыхание» сцены. Радиальный градиент уже мягкий сам по себе,
   поэтому blur не нужен: фильтр на пол-экрана стоит дороже всей остальной
   анимации вместе взятой. */
.fx-glow { position: absolute; border-radius: 50%; }
.fx-glow--a {
    width: 58vw; height: 58vw; left: -16vw; top: -18vw;
    background: radial-gradient(circle, rgba(53,224,255,.20) 0%, transparent 62%);
    animation: fx-float-a 34s ease-in-out infinite alternate;
}
.fx-glow--b {
    width: 46vw; height: 46vw; right: -14vw; top: 28vh;
    background: radial-gradient(circle, rgba(255,138,77,.15) 0%, transparent 64%);
    animation: fx-float-b 41s ease-in-out infinite alternate;
}
@keyframes fx-float-a { to { transform: translate3d(9vw, 7vh, 0) scale(1.14); } }
@keyframes fx-float-b { to { transform: translate3d(-7vw, -9vh, 0) scale(1.1); } }

/* Плоскость эклиптики: сетка, уложенная в перспективу у нижнего края, —
   пол под ногами у того, кто смотрит в небо. Перспектива на обёртке,
   движение на вложенном слое: так плоскость наклоняется один раз, а бежит
   по ней уже готовая текстура. */
.fx-floor {
    position: absolute; left: -10%; right: -10%; bottom: 0; height: 26vh;
    perspective: 320px; perspective-origin: 50% 0%;
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 62%, #000 100%);
    mask-image: linear-gradient(180deg, transparent 0%, #000 62%, #000 100%);
    /* Прозрачность подобрана по тексту, а не по красоте слоя: на 0.5 линии
       начинали пересекать заголовки разделов и читались как дефект печати. */
    opacity: .3;
}
.fx-floor i {
    position: absolute; inset: 0 0 -60%;
    transform-origin: 50% 0%;
    background-image:
        linear-gradient(rgba(88,206,255,.60) 1px, transparent 1px),
        linear-gradient(90deg, rgba(150,120,255,.45) 1px, transparent 1px);
    background-size: 84px 84px;
    animation: fx-floor 5.5s linear infinite;
}
@keyframes fx-floor {
    from { transform: rotateX(74deg) translate3d(0, 0, 0); }
    to   { transform: rotateX(74deg) translate3d(0, 84px, 0); }
}
/* Линия самого горизонта — там, где плоскость уходит в туман. */
.fx-floor::after {
    content: ""; position: absolute; left: 0; right: 0; top: 0; height: 1px;
    background: linear-gradient(90deg, transparent, rgba(53,224,255,.5) 22%, rgba(185,140,255,.6) 50%, rgba(53,224,255,.5) 78%, transparent);
}

/* Зерно. Плёнка в несколько процентов: в лоб её не видно, но без неё крупные
   градиенты идут полосами — на плитках витрины это заметно первым делом.
   Режим смешивания overlay, а не multiply: на тёмном небе умножение просто
   гасит и без того чёрное, зерна не остаётся вовсе. */
.grain {
    z-index: 60; opacity: .06; mix-blend-mode: overlay;
    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='.86' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ============================================================
   2. Шапка: полоса прочтения и сжатие при прокрутке
   ============================================================ */

/* Полоса прогресса. Где браузер умеет привязывать анимацию к прокрутке
   (`animation-timeline`) — обходимся без единой строчки JS и без обработчика
   scroll вообще. Где не умеет (Safari, Firefox) — site.js подставляет --p,
   и правило ниже читает его. Оба пути дают одну и ту же полосу. */
.progress {
    position: fixed; left: 0; right: 0; top: 0; height: 2px; z-index: 50;
    transform-origin: 0 50%; transform: scaleX(var(--p, 0));
    background: linear-gradient(90deg, var(--star) 0%, var(--neon) 62%, var(--neon-2) 100%);
    box-shadow: 0 0 12px rgba(53,224,255,.55);
    pointer-events: none;
}
@supports (animation-timeline: scroll()) {
    .progress {
        animation: progress linear both;
        animation-timeline: scroll(root block);
    }
    @keyframes progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }
}

.top { transition: background-color .3s var(--ease), box-shadow .3s var(--ease); }
.top-in { transition: height .3s var(--ease); }
.top.is-stuck { background: rgba(6,9,22,.90); box-shadow: 0 18px 40px -24px rgba(0,0,0,.9); }
.top.is-stuck .top-in { height: 58px; }
.top .brand img { transition: transform .3s var(--ease); }
.top.is-stuck .brand img { transform: scale(.88); }

/* Подчёркивание в меню растёт из левого края — направление чтения. */
.menu a { position: relative; }
.menu a::after {
    content: ""; position: absolute; left: 14px; right: 14px; bottom: 5px; height: 2px;
    border-radius: 2px; background: linear-gradient(90deg, var(--star), var(--neon));
    transform: scaleX(0); transform-origin: 0 50%; transition: transform .28s var(--ease);
}
.menu a:hover::after, .menu a.is-here::after { transform: scaleX(1); }

/* ============================================================
   3. Появление блоков при прокрутке
   ============================================================ */

@media (prefers-reduced-motion: no-preference) {
    html.js .reveal {
        opacity: 0; transform: translate3d(0, 26px, 0);
        /* Задержка считается из порядкового номера, который проставлен в
           разметке: соседние карточки выходят лесенкой, а не хором. Шаг
           маленький — на шести плитках 70 мс уже читается как волна, а
           больше начинает выглядеть как медленная страница. */
        --d: calc(var(--i, 0) * 70ms);
    }
    html.js .reveal.is-in {
        opacity: 1; transform: none;
        transition: opacity .72s var(--ease) var(--d), transform .72s var(--ease) var(--d);
    }
    /* Заголовки разделов приезжают чуть иначе — сдвигом вбок: так глазу
       понятно, что это новая глава, а не ещё одна карточка. */
    html.js .sec-head.reveal { transform: translate3d(-18px, 8px, 0); }
}

/* ============================================================
   4. Первый экран
   ============================================================ */

@media (prefers-reduced-motion: no-preference) {
    /* Заголовок собирается по словам: каждое слово живёт в окне с
       overflow: hidden и выезжает снизу. Разбивку делает site.js — держать
       её в разметке значит запретить редактору править текст. */
    html.js .hero h1 .w { display: inline-block; overflow: hidden; vertical-align: bottom; }
    html.js .hero h1 .w > i {
        display: inline-block; font-style: normal;
        transform: translate3d(0, 108%, 0);
        animation: word-up .85s var(--ease) forwards;
        animation-delay: calc(var(--i, 0) * 52ms + 100ms);
    }
    @keyframes word-up { to { transform: none; } }

    /* Остальные части первого экрана — простым подъёмом. Они над сгибом,
       наблюдатель здесь не нужен: запускаем сразу, лесенкой. */
    html.js .hero-kicker,
    html.js .hero-text > p,
    html.js .hero-actions,
    html.js .hero-photo,
    html.js .fact {
        opacity: 0; animation: rise .8s var(--ease) forwards;
    }
    html.js .hero-kicker    { animation-delay: .05s; }
    html.js .hero-text > p  { animation-delay: .34s; }
    html.js .hero-actions   { animation-delay: .44s; }
    html.js .hero-photo     { animation-delay: .18s; }
    html.js .fact           { animation-delay: calc(.5s + var(--i, 0) * 80ms); }
    @keyframes rise { from { opacity: 0; transform: translate3d(0, 18px, 0); } to { opacity: 1; transform: none; } }
}

/* Живая точка у плашки: «магазин работает прямо сейчас» — самое дешёвое
   доказательство из возможных. */
.hero-kicker { position: relative; }
.hero-kicker::after {
    content: ""; width: 7px; height: 7px; border-radius: 50%;
    background: var(--green); box-shadow: 0 0 0 0 rgba(63,167,107,.55);
    animation: live 2.6s ease-out infinite;
}
@keyframes live {
    0%   { box-shadow: 0 0 0 0 rgba(63,167,107,.55); }
    70%  { box-shadow: 0 0 0 9px rgba(63,167,107,0); }
    100% { box-shadow: 0 0 0 0 rgba(63,167,107,0); }
}
/* На узком экране название в плашку одной строкой не влезает, и точка —
   последний элемент строки — повисает справа от переноса, в пустоте. Убираем
   её там совсем: ровно такая же живая точка горит в метке «магазин на связи»
   на фотографии прямо над плашкой, смысл не теряется. */
@media (max-width: 380px) {
    .hero-kicker::after { display: none; }
}

/* Рамка карточки первого экрана: по кромке медленно проходит световой
   блик. Приём стандартный — conic-gradient обрезается маской по толщине
   рамки (mask-composite: exclude вырезает середину). */
.hero-card::before {
    content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
    background: conic-gradient(from var(--angle),
        transparent 0turn, rgba(53,224,255,.8) .07turn, rgba(185,140,255,.6) .13turn, transparent .26turn, transparent 1turn);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    animation: turn 14s linear infinite;
    pointer-events: none; z-index: 3;
}
@keyframes turn { to { --angle: 1turn; } }

/* Фотография героя: HUD поверх кадра. Скобки по углам и бегущая строка
   развёртки — кадр начинает читаться как изображение с телескопа, а не как
   обои. Всё внутри aria-hidden, экранной читалке этого не видно. */
.hud { position: absolute; inset: 0; z-index: 2; pointer-events: none; overflow: hidden; }
.hud::before, .hud::after {
    content: ""; position: absolute; width: 34px; height: 34px;
    border: 2px solid rgba(53,224,255,.75);
}
.hud::before { top: 16px; left: 16px; border-right: 0; border-bottom: 0; border-radius: 8px 0 0 0; }
.hud::after  { bottom: 16px; right: 16px; border-left: 0; border-top: 0; border-radius: 0 0 8px 0; }
.hud-scan {
    position: absolute; left: 0; right: 0; height: 38%;
    background: linear-gradient(180deg, transparent 0%, rgba(53,224,255,.16) 55%, rgba(185,140,255,.26) 82%, transparent 100%);
    animation: scan 6.5s cubic-bezier(.6,0,.4,1) infinite;
}
@keyframes scan {
    0%, 12%  { transform: translate3d(0, -120%, 0); opacity: 0; }
    28%      { opacity: 1; }
    88%      { opacity: .35; }
    100%     { transform: translate3d(0, 300%, 0); opacity: 0; }
}
.hud-tag {
    position: absolute; right: 16px; top: 16px; z-index: 3;
    display: inline-flex; align-items: center; gap: 7px;
    font-family: var(--mono); font-size: 11px; font-weight: 500; letter-spacing: .02em;
    color: #cdefff; background: rgba(6,14,30,.72);
    border: 1px solid rgba(53,224,255,.5); border-radius: 999px; padding: 5px 11px 5px 9px;
    backdrop-filter: blur(6px);
}
.hud-tag::before {
    content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--green);
    animation: blink 2.2s ease-in-out infinite;
}
/* Тот же подъём до читаемого размера, что у остальных служебных подписей в
   styles.css. Правило живёт здесь, а не там: motion.css грузится последним и
   свой же font-size перебил бы любое правило соседнего файла. */
@media (max-width: 720px) {
    .hud-tag { font-size: 12px; }
}
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

/* Параллакс кадра — там, где браузер умеет привязать анимацию к прокрутке.
   Через JS это был бы обработчик scroll с пересчётом в каждом кадре; здесь
   всё считает компоновщик, и на слабом ноутбуке разница видна сразу. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) and (min-width: 861px) {
        .hero-photo img {
            animation: pan linear both;
            animation-timeline: view();
            animation-range: entry 0% exit 100%;
        }
        @keyframes pan {
            from { transform: translate3d(0, -3.5%, 0) scale(1.1); }
            to   { transform: translate3d(0,  3.5%, 0) scale(1.1); }
        }
    }
}

/* ============================================================
   5. Плитки витрины
   ============================================================ */

/* Наклон и подъём собраны из переменных, а не записаны одним transform:
   иначе правило из styles.css (`.tile:hover { transform: translateY(-3px) }`)
   затирало бы наклон ровно в тот момент, когда он нужен. */
html.js .tile, html.js .cat {
    --ty: 0px;
    transform: perspective(760px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translate3d(0, var(--ty), 0);
    transition: transform .28s var(--ease), box-shadow .28s var(--ease), border-color .28s var(--ease);
}
html.js .tile:hover, html.js .tile:focus-visible,
html.js .cat:hover, html.js .cat:focus-within {
    --ty: -6px;
    transform: perspective(760px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) translate3d(0, var(--ty), 0);
}
/* Пока палец ведёт плитку, наклон должен успевать за ним — длинный переход
   здесь превращается в «резину». Класс вешает site.js на время наведения. */
html.js .tile.is-live, html.js .cat.is-live { transition: box-shadow .28s var(--ease), border-color .28s var(--ease); }

/* Прожектор под курсором. Координаты приходят из site.js в процентах. */
.tile::after, .cat::after {
    content: ""; position: absolute; inset: 0; z-index: 4; pointer-events: none;
    border-radius: inherit;
    background: radial-gradient(260px circle at var(--mx, 50%) var(--my, 50%), rgba(53,224,255,.16), transparent 60%);
    opacity: 0; transition: opacity .3s var(--ease);
}
.tile:hover::after, .cat:hover::after { opacity: 1; }

/* Кольцо по кромке — то же, что у карточки героя, но зажигается по наведению. */
.tile::before {
    content: ""; position: absolute; inset: 0; z-index: 5; padding: 1px; border-radius: inherit;
    background: conic-gradient(from var(--angle),
        transparent 0turn, var(--glow, var(--neon)) .08turn, transparent .24turn, transparent 1turn);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    opacity: 0; transition: opacity .3s var(--ease); pointer-events: none;
}
.tile:hover::before, .tile:focus-visible::before { opacity: .95; animation: turn 3.4s linear infinite; }

/* Блик по обложке. Фон здесь переопределён целиком (тот же градиент бренда,
   что в styles.css, плюс слой блика первым): у background нет способа
   добавить слой, не переписав список. Позиция блика — типизированная
   переменная --sheen, поэтому её можно анимировать. */
.tile-art {
    background:
        linear-gradient(100deg, transparent 34%, rgba(255,255,255,.30) 47%, transparent 60%) var(--sheen) 0 / 240% 100% no-repeat,
        linear-gradient(158deg, var(--c1) 0%, var(--c2) 100%);
}
.tile:hover .tile-art { animation: sheen .9s var(--ease); }
@keyframes sheen { from { --sheen: -55%; } to { --sheen: 155%; } }

/* Свечение под логотипом медленно дышит — обложка перестаёт быть картинкой
   и становится источником света. */
.tile-art::before { animation: breathe 7s ease-in-out infinite alternate; }
@keyframes breathe { from { opacity: .34; transform: scale(1); } to { opacity: .52; transform: scale(1.16); } }
.tile-art .lg { transition: transform .32s var(--ease), filter .32s var(--ease); }
.tile:hover .tile-art .lg {
    transform: translateY(-3px) scale(1.07);
    filter: drop-shadow(0 8px 20px rgba(0,0,0,.42));
}

/* Бейджи — моноширинным: подпись служебная, и шрифт об этом сообщает. */
.tile-tag, .tag, .eyebrow { font-family: var(--mono); }
.tile-tag, .tag { font-weight: 500; }
.eyebrow { font-weight: 500; letter-spacing: .18em; }

/* ============================================================
   6. Категории, шаги, стена брендов
   ============================================================ */

.cat .ic { transition: transform .3s var(--ease), background-color .3s var(--ease); }
.cat:hover .ic { transform: translateY(-2px) rotate(-4deg) scale(1.06); }
.cat-go a { transition: background-color .2s, transform .2s var(--ease); }
.cat-go a:hover { transform: translateY(-1px); }

/* Номер шага пульсирует кольцом — по одному, по очереди: взгляд проходит
   цепочку в том же порядке, в каком её проходит заказ. */
.step::before { position: relative; }
.step { position: relative; }
@media (prefers-reduced-motion: no-preference) {
    html.js .step.is-in::before { animation: ring 2.8s ease-out infinite; animation-delay: calc(var(--i, 0) * .9s); }
    @keyframes ring {
        0%       { box-shadow: 0 0 0 0 rgba(87,200,255,.5); }
        55%, 100% { box-shadow: 0 0 0 14px rgba(87,200,255,0); }
    }
}
/* Пунктир между шагами — на широком экране, где шаги стоят в ряд. Штрихи
   бегут слева направо: связь между карточками читается как движение. */
@media (min-width: 1001px) {
    .step:not(:last-child)::after {
        content: ""; position: absolute; right: -19px; top: 42px; width: 20px; height: 2px;
        background: repeating-linear-gradient(90deg, rgba(87,200,255,.55) 0 4px, transparent 4px 8px);
        animation: dashes 1.1s linear infinite;
    }
    @keyframes dashes { to { background-position: 8px 0; } }
}

/* Стена брендов — бегущая строка. Две ленты навстречу друг другу: ряд,
   ползущий в одну сторону, читается как слайдер, который куда-то ведёт;
   встречное движение читается как фон. Дубль ленты делает site.js — без
   него ряд остаётся обычной строкой со всеми значками. */
.marquee { overflow: hidden; }
.marquee + .marquee { margin-top: 14px; }
.marquee-track { display: flex; gap: 26px; width: max-content; }
.marquee.is-ready {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
.marquee.is-ready .marquee-track { animation: marq 46s linear infinite; }
.marquee.is-ready.marquee--rev .marquee-track { animation-direction: reverse; }
.marquee-group { display: flex; gap: 26px; align-items: center; }
/* Без скрипта дубля нет, а лента шире экрана — половина значков уехала бы
   под обрез. Тогда ряд просто переносится по ширине, как обычная строка. */
html:not(.js) .marquee-track, html:not(.js) .marquee-group { flex-wrap: wrap; width: auto; }
/* Наведение и клавиатурный фокус останавливают ленту: значок нельзя
   разглядеть, пока он уезжает. */
.marquee:hover .marquee-track, .marquee:focus-within .marquee-track { animation-play-state: paused; }
@keyframes marq { to { transform: translate3d(-50%, 0, 0); } }
.brandwall-row .lg { transition: transform .25s var(--ease), background-color .25s; }
.brandwall-row .lg:hover { transform: scale(1.22); --tint: var(--star-hi); }

/* ============================================================
   6b. Орбиты брендов
   ============================================================ */

/* Вращение колец. Раскладка знаков по окружности лежит в styles.css —
   здесь только движение, поэтому без этого файла кольцо просто стоит.

   Крутится сцена целиком, одним трансформом на слой: анимировать двадцать
   знаков по отдельности значило бы двадцать анимаций вместо трёх.

   Скорость у каждого кольца своя (--dur в разметке) и соседние идут в
   разные стороны: одинаково крутящиеся кольца читаются как одна деталь,
   встречные — как объём. */
@media (prefers-reduced-motion: no-preference) {
    .ring-stage { animation: ring-turn var(--dur, 44s) linear infinite; }
    .ring--rev .ring-stage { animation-direction: reverse; }
    @keyframes ring-turn { to { transform: rotateY(-360deg); } }

    /* Наведение и клавиатурный фокус останавливают кольцо: по уезжающему
       знаку не попасть, а прочитать подпись тем более. Тот же приём, что
       у бегущей строки брендов ниже. */
    .ring:hover .ring-stage,
    .ring:focus-within .ring-stage { animation-play-state: paused; }
}
.ring-i { transition: border-color .2s var(--ease), background-color .2s var(--ease); }

/* ============================================================
   6c. Карусель брендов
   ============================================================ */

/* Полоса заполнения под каруселью: ширину и сдвиг считает site.js из
   положения прокрутки. Переход короткий — полоса должна успевать за
   пальцем, а не догонять его. */
.car-rail i { transition: transform .18s linear, width .18s linear; }
.car-arm { transition: background-color .18s var(--ease), color .18s var(--ease), transform .18s var(--ease); }
.car-arm:hover:not([disabled]) { transform: scale(1.06); }

/* ============================================================
   6d. Вопросы и ответы
   ============================================================ */

/* Раскрытие ответа. Высоту у <details> анимировать нечем, поэтому едет
   содержимое: подъём с проявлением. Само раскрытие при этом остаётся
   браузерным и работает даже с выключенным скриптом. */
@media (prefers-reduced-motion: no-preference) {
    .qa[open] .qa-a { animation: qa-open .32s var(--ease); }
    @keyframes qa-open {
        from { opacity: 0; transform: translate3d(0, -6px, 0); }
        to   { opacity: 1; transform: none; }
    }
}
.qa { transition: border-color .2s var(--ease), background-color .2s var(--ease); }
.qa:hover { border-color: var(--border); }

/* ============================================================
   7. Кнопки, разделители, контакты
   ============================================================ */

/* Главный призыв: градиент медленно переливается, по наведению проходит
   блик. Overflow скрыт — блик обязан обрезаться по скруглению. */
.btn-cta { position: relative; overflow: hidden; background-size: 190% 190%; animation: cta 9s ease-in-out infinite alternate; }
@keyframes cta { to { background-position: 100% 100%; } }
.btn-cta::after {
    content: ""; position: absolute; top: 0; bottom: 0; left: -50%; width: 40%;
    background: linear-gradient(100deg, transparent, rgba(255,255,255,.45), transparent);
    transform: skewX(-18deg); pointer-events: none;
}
.btn-cta:hover::after { animation: shine .8s ease-out; }
@keyframes shine { to { left: 125%; } }
.btn-ghost { transition: transform .18s var(--ease), box-shadow .18s var(--ease), background-color .16s; }
.btn-ghost:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }

/* Разделитель: по трассе идёт спутник. Раньше здесь качалась волна — на
   звёздном небе она читалась как шов от прошлой темы.

   Точка движется от края до края и гаснет на самых краях, поэтому «прыжка»
   в момент перезапуска не видно: она исчезает раньше, чем возвращается.
   Разделителей на странице несколько, и все идут вразнобой — задержка
   считается из порядкового номера в разметке. */
@media (prefers-reduced-motion: no-preference) {
    .orbit i {
        animation: orbit-run 11s cubic-bezier(.45,0,.55,1) infinite;
        animation-delay: calc(var(--i, 0) * -2.6s);
    }
    @keyframes orbit-run {
        0%   { transform: translate3d(0, 0, 0); opacity: 0; }
        12%  { opacity: 1; }
        86%  { opacity: 1; }
        100% { transform: translate3d(calc(100% - 6px), 0, 0); opacity: 0; }
    }
}

/* Блок контактов: под градиентом плывёт свечение туманности. Содержимое
   поднято над ним z-индексом, иначе слой перекрыл бы текст. */
.contact { position: relative; overflow: hidden; }
.contact > * { position: relative; z-index: 1; }
.contact::before {
    content: ""; position: absolute; inset: -45% -20%;
    background:
        radial-gradient(closest-side, rgba(53,224,255,.42), transparent) 18% 30% / 62% 88% no-repeat,
        radial-gradient(closest-side, rgba(185,140,255,.34), transparent) 82% 74% / 58% 92% no-repeat;
    animation: aurora 22s ease-in-out infinite alternate;
}
@keyframes aurora {
    from { transform: translate3d(-3%, -2%, 0) scale(1); }
    to   { transform: translate3d(4%, 3%, 0) scale(1.12); }
}
/* Строки контактов: при наведении отъезжают вправо — маленький намёк, что
   строка ведёт наружу. Строка про часы ответа ссылкой не является и стоит
   на месте (a.crow, а не .crow). */
a.crow { transition: transform .22s var(--ease), background-color .22s, border-color .22s; }
a.crow:hover { transform: translateX(4px); }

/* Значки в полосе доверия чуть покачиваются — каждый в своём такте. */
@media (prefers-reduced-motion: no-preference) {
    html.js .fact .lg, html.js .fact .lg-img {
        animation: bob 5.4s ease-in-out infinite;
        animation-delay: calc(var(--i, 0) * .55s);
    }
    @keyframes bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3px); } }
}
.fact { transition: transform .24s var(--ease), box-shadow .24s var(--ease); }
.fact:hover { transform: translateY(-3px); box-shadow: var(--shadow-lg); }

/* Фотография в «О магазине» медленно приближается, пока раздел на экране. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .about-photo img {
            animation: zoom linear both;
            animation-timeline: view(); animation-range: entry 0% exit 100%;
        }
        @keyframes zoom { from { transform: scale(1.16); } to { transform: scale(1); } }
    }
}

/* ============================================================
   8. Телефон
   ============================================================ */
@media (max-width: 720px) {
    /* На телефоне выключаем всё, что стоит дорого при коротком экране и
       пальцевой прокрутке: плоскость эклиптики, зерно поверх страницы,
       наклон плиток. Звёзды остаются — два композитных слоя стоят дёшево,
       а без них небо на телефоне становится обоями. */
    .fx-floor, .grain { display: none; }
    .fx::before { opacity: .06; }
    html.js .tile, html.js .cat { transform: translate3d(0, var(--ty), 0); }
    html.js .tile:hover, html.js .cat:hover { transform: translate3d(0, var(--ty), 0); }
    .hud::before, .hud::after { width: 24px; height: 24px; top: 12px; left: 12px; }
    .hud::after { top: auto; left: auto; bottom: 12px; right: 12px; }
    .marquee.is-ready .marquee-track { animation-duration: 34s; }
}
