@charset "UTF-8";
/* =========================================================================
   Retrieva — хореография

   Семь актов. Скролл двигает саму раскладку, а не подкрашивает элементы:
   слои наезжают, ленты разъезжаются, слова собираются и пролетают сквозь
   экран. Значения --p, --w, --shift, --mx, --my выставляет js/motion.js —
   здесь описано только то, как элемент на них реагирует.

   Всё, что прячется до появления в кадре, прячется ТОЛЬКО при html.js.
   Без скрипта страница остаётся полностью читаемой.
   ========================================================================= */

/* --------------------- ПОЯВЛЕНИЕ С ЗАДЕРЖКОЙ (reveal) -------------------
   Элементы входят по одному с шагом 70 мс, а не всей плитой. Шаг задаёт
   --i на самом элементе: в разметке это data-reveal у контейнера. */
html.js [data-reveal] {
  opacity: 0;
  transform: translateY(2.4rem);
  transition: opacity .7s var(--ease) calc(var(--i, 0) * 70ms),
              transform .9s var(--ease) calc(var(--i, 0) * 70ms);
}
html.js [data-reveal].is-in { opacity: 1; transform: none; }

/* ----------------------------- АКТ 01 · РАЗНОС --------------------------
   Два ряда сообщений едут навстречу друг другу. Позицию задаёт скрипт
   через transform, здесь — только подготовка слоя к сдвигу. */
.pain__row { transform: translate3d(0, 0, 0); }

/* ----------------------------- АКТ 02 · ШТОРА ---------------------------
   Слой «Ночная смена» наезжает на предыдущий и закрывает его собой. */
/* Штора растворяется на обоих краях, а не обрывается линией: сверху её
   съедает наезд, снизу — прозрачный хвост градиента. Жёсткий стык внизу
   читался как ошибка вёрстки, потому что сверху перехода не видно. */
.night__curtain {
  position: absolute; inset: 0; z-index: -1;
  background: linear-gradient(180deg,
              rgba(28, 33, 29, 0) 0%,
              #1c211d 14%,
              #171b18 62%,
              rgba(11, 13, 12, 0) 100%);
  transform: translateY(var(--shift, 0%));
  will-change: transform;
}

/* ---------------------------- АКТ 03 · ПРОТЯЖКА -------------------------
   Заголовок проявляется слева направо на глубину прокрутки. Сам градиент
   объявлен вместе с компонентом, сюда вынесен только безопасный откат,
   если скрипт не отработал: строка видна целиком. */
html:not(.js) [data-wipe] { --w: 100%; }

/* ---------------------------- АКТ 05 · СБОРКА ---------------------------
   Заголовок прилетает отдельными пузырями и складывается во фразу.
   Разлёт задаётся из скрипта (--dx, --dy, --o), возврат — этим переходом. */
.w {
  display: inline-block;
  transform: translate(var(--dx, 0), var(--dy, 0));
  opacity: var(--o, 1);
  transition: transform .85s var(--ease), opacity .6s var(--ease);
}

/* ---------------------------- АКТ 06 · МАГНИТ ---------------------------
   Пятно света идёт за курсором. Сами градиенты объявлены у компонентов
   (.skills__card, .plan, .integr__grid li) — скрипт правит только --mx/--my. */

/* -------------------------- АКТ 07 · ПРОШИВКА И СЧЁТ --------------------
   Оба приёма меняют только текстовый узел, отдельного оформления не
   требуют. Курсор на «прошивке» остаётся стрелкой: это не кнопка. */
.scramble { cursor: default; }

/* ------------------------------ ФОН И ИСКРА ----------------------------- */
/* Водяной знак — крупные контуры пузырей. Единственный фон, который
   говорит именно про Retrieva: страница сделана из того, что мы продаём.
   Объекты большие и опознаваемые, поэтому сором не читаются даже стоя
   на месте — в отличие от мелких точек, которые тут и были раньше.
   Знак неподвижен намеренно: водяные знаки не ездят. */
/* Контейнер фона. Без него абсолютные слои внутри цепляются к начальному
   содержащему блоку: знак перестаёт быть закреплённым и уезжает вместе со
   страницей после первого экрана, свечение ничем не обрезается и раздувает
   документ по горизонтали, а сам слой начинает ловить клики по кнопкам. */
.veil {
  position: fixed; inset: 0; z-index: 0;
  pointer-events: none; overflow: hidden;
}

.veil__mark {
  position: absolute; inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='460' height='460' viewBox='0 0 460 460'%3E%3Cg fill='none' stroke='rgba(185,194,187,0.13)' stroke-width='1.5'%3E%3Cpath d='M58,44 H222 Q240,44 240,62 V90 Q240,108 222,108 H40 V62 Q40,44 58,44 Z'/%3E%3Cpath d='M300,250 H404 Q420,250 420,266 V292 Q420,308 404,308 H282 V266 Q282,250 300,250 Z'/%3E%3Cpath d='M120,330 H250 Q266,330 266,346 V372 Q266,388 250,388 H102 V346 Q102,330 120,330 Z'/%3E%3C/g%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='560' height='560' viewBox='0 0 560 560'%3E%3Cg fill='none' stroke='rgba(185,194,187,0.10)' stroke-width='1.5'%3E%3Cpath d='M330,80 H470 Q486,80 486,96 V122 Q486,138 470,138 H348 V96 Q348,80 330,80 Z'/%3E%3Cpath d='M90,400 H210 Q226,400 226,416 V442 Q226,458 210,458 H72 V416 Q72,400 90,400 Z'/%3E%3C/g%3E%3C/svg%3E");
  background-size: 46rem 46rem, 71rem 71rem;
  background-position: 0 0, 17rem 9rem;
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 14%, #000 86%, transparent);
          mask-image: linear-gradient(180deg, transparent, #000 14%, #000 86%, transparent);
}

/* Дыхание вместо частиц: одно очень большое мягкое пятно, которое ползёт
   почти незаметно. Дискретных объектов нет — значит и сору взяться неоткуда.
   Двигаем только transform, полминуты на проход. */
.veil__drift {
  position: absolute; left: 50%; top: 45%;
  width: 130rem; height: 100rem; margin: -50rem 0 0 -65rem;
  background: radial-gradient(38% 40% at 50% 50%, rgba(71, 86, 58, .42), transparent 70%);
  animation: drift 54s ease-in-out infinite alternate;
  will-change: transform;
}
@keyframes drift {
  from { transform: translate3d(-9%, -4%, 0); }
  to   { transform: translate3d(9%, 5%, 0); }
}
@media (max-width: 760px) {
  .veil__mark { background-size: 34rem 34rem, 52rem 52rem; opacity: .62; }
}
.page { position: relative; z-index: 1; }

.depth__spark { animation: pulse 3.2s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .55; } }

@keyframes reach { 0%, 100% { transform: scaleX(.3); opacity: .4; } 50% { transform: scaleX(1); opacity: 1; } }

/* Пузыри на первом экране слегка качаются — экран не выглядит замершим. */
.msg { animation: float 4.6s ease-in-out infinite; }
.msg:nth-child(2) { animation-delay: .6s; }
.msg:nth-child(3) { animation-delay: 1.2s; }
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-.3rem); } }

/* ------------------------- ДЕМО: СОБСТВЕННЫЕ КАДРЫ ----------------------
   Нужны движку demo.js и не зависят от остальной хореографии. */
@keyframes demo-pop { from { opacity: 0; transform: translateY(.7rem); } to { opacity: 1; transform: none; } }
@keyframes demo-blink { 0%, 60%, 100% { opacity: .3; } 30% { opacity: 1; } }

/* ============================ БЕЗ ДВИЖЕНИЯ =============================
   Посетитель попросил систему не анимировать интерфейс — выключаем все
   семь актов разом. Страница остаётся полностью читаемой: скрытое
   показываем, сдвинутое возвращаем на место. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }

  html.js [data-reveal] { opacity: 1; transform: none; }
  .veil__drift { animation: none; transform: none; }
  [data-wipe] { --w: 100%; color: var(--paper); -webkit-text-fill-color: var(--paper); background: none; }
  .night__curtain { transform: none; }
  .w { transform: none; opacity: 1; }
  .pain__row { transform: none !important; }
  .skills__strip { transform: none !important; }
}

/* ============================== ЗАСТАВКА ================================
   Класс intro-on ставит инлайновый скрипт в <head> — до первой отрисовки,
   иначе страница успевает моргнуть содержимым. Без скрипта и при
   prefers-reduced-motion класса нет, и всей заставки нет вместе с ним.
   ======================================================================== */
.intro { display: none; }

html.intro-on { overflow: hidden; }
html.intro-on .intro {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  position: fixed; inset: 0; z-index: 90;
  background: var(--ink); cursor: pointer;
}

/* Дождь сообщений — фон заставки. Пузыри пустые: читать их не нужно,
   они показывают, что переписка идёт, пока имя собирается из шума. */
.intro__rain { position: absolute; inset: 0; width: 100%; height: 100%; }
.intro__word { position: relative; z-index: 1; }

.intro__word {
  display: flex; align-items: baseline;
  font: 700 clamp(3.6rem, 9vw, 9rem)/1 var(--f-display);
  letter-spacing: -.045em; color: var(--paper);
}
/* Имя набрано моноширинно на время сборки: иначе строка дёргается
   на каждом такте, пока символы подставляются разной ширины. */
.intro__name { font-variant-numeric: tabular-nums; }
.intro__dot {
  width: .16em; height: .16em; border-radius: 50%;
  background: var(--sage); margin-left: .07em; flex: none;
}


/* Логотип в шапке ждёт прилёта точки. Порядок правил важен: intro-land
   идёт после intro-on и тем же весом перебивает его. */
html.intro-on .logo__name { opacity: 0; transform: translateX(-.5rem); }
html.intro-on .logo__dot  { opacity: 0; }

html.intro-land .intro { opacity: 0; transition: opacity .25s var(--ease) .06s; }
html.intro-land .logo__dot { opacity: 1; transition: opacity .15s var(--ease); }
html.intro-land .logo__name {
  opacity: 1; transform: none;
  transition: opacity .25s var(--ease) .1s, transform .25s var(--ease) .1s;
}
