/* ============================================================
   Anna Gorodetskaya — Portfolio
   base.css — общая дизайн-спина для трёх компов
   10 августа 2026

   Всё, что здесь, одинаково для Comp A / B / C.
   Компы различаются только визуальной логикой поверх этой базы,
   чтобы сравнение шло по дизайну, а не по разной вёрстке.

   Палитра НЕ зафиксирована — прямое требование Visual Direction
   Research v1: цвет приходит из медиа проектов, база нейтральна.
   Каждый комп может переопределить ground-слоты.
   ============================================================ */

/* ------------------------------------------------------------
   1. ТОКЕНЫ
   ------------------------------------------------------------ */

:root {
  /* --- ground: нейтральная база, переопределяется в компе --- */
  --ground:        #F4F3F0;  /* бумага, чуть тёплая */
  --ground-alt:    #EAE8E4;  /* вторая плоскость */
  --ink:           #101012;  /* основной текст */
  --ink-80:        #3A3A3E;
  --ink-55:        #6E6E74;  /* мета, подписи */
  --ink-30:        #A8A8AE;  /* линии, дизейбл */
  --ink-12:        #DCDBD7;  /* хайрлайны сетки */
  --inverse:       #F4F3F0;  /* текст на тёмной плоскости */

  /* --- accent: слот, а не бренд-цвет.
         Может меняться по проекту или по практике.
         Дефолт намеренно сдержанный. --- */
  --accent:        #1F4BD8;
  --accent-quiet:  color-mix(in srgb, var(--accent) 12%, transparent);

  /* --- практики: четыре метки, различимые, но не «цветовой код» --- */
  /* Четыре направления практики, 7 сент 2026. Culture перестала
     быть отдельным направлением и ушла внутрь Creative & Culture,
     на её место встала AI & Automation. Значения цветов взяты
     прежние — менялись имена и границы, а не палитра. */
  --practice-tc:   #1F4BD8;  /* Technology */
  --practice-ai:   #5B3BC4;  /* AI & Automation */
  --practice-cc:   #C08A1E;  /* Creative & Culture */
  --practice-cm:   #B5451E;  /* Communication */

  /* Старые имена оставлены живыми псевдонимами: design/comps/*
     заморожены как точка сравнения и красятся этими переменными.
     Сайт ими больше не пользуется — переименование не должно
     стоить сломанных компов. */
  --practice-mt:   var(--practice-tc);
  --practice-ca:   var(--practice-cm);
  --practice-fc:   #1E6E4E;
  --practice-cr:   var(--practice-cc);

  /* --- типографика: слоты. Пара выбирается подключением
         одного из design/system/type-*.css --- */
  --font-display:  system-ui, sans-serif;
  --font-text:     system-ui, sans-serif;
  --font-meta:     ui-monospace, monospace;

  /* оси переменных шрифтов, где они есть */
  --display-wdth:  100;
  --display-wght:  700;

  /* --- шкала кегля: флюидная, от дисплея до технической меты.
         Верх шкалы намеренно очень крупный: ресёрч требует
         «типографика несёт идентичность, а не декор». --- */
  --fs-d1:    clamp(3.25rem, 10.5vw, 10.5rem);   /* имя, титул кейса */
  --fs-d2:    clamp(2.25rem, 6.4vw, 5.75rem);    /* заголовки секций, проекты */
  --fs-d3:    clamp(1.75rem, 3.9vw, 3.25rem);    /* подзаголовки, My Role */
  --fs-head:  clamp(1.375rem, 2.2vw, 1.875rem);  /* заголовок карточки */
  --fs-deck:  clamp(1.0625rem, 1.5vw, 1.375rem); /* дек, positioning statement */
  --fs-body:  clamp(1rem, 1.05vw, 1.125rem);
  /* Мета и микро подняты на 22 августа 2026: Archivo Narrow в 12/11 px
     капсом и в разрядке на тёмном грунте читается как штрих, а не как
     текст. Это единственная типографическая ось, которую пришлось
     двигать после сборки v2 — по прямому замечанию Анны. */
  --fs-meta:  0.8125rem;  /* роль / год / клиент / практика */
  --fs-micro: 0.75rem;    /* кредиты, сноски, номера */

  --lh-tight:  0.92;   /* дисплей */
  --lh-snug:   1.08;
  --lh-deck:   1.32;
  --lh-body:   1.55;
  --lh-meta:   1.25;

  --ls-display: -0.035em;
  --ls-head:    -0.018em;
  --ls-body:     0em;
  --ls-meta:     0.05em;   /* мета набирается в разрядку, капсом */

  /* --- ритм: базовый юнит 8px --- */
  --u:     8px;
  --s-05:  4px;
  --s-1:   8px;
  --s-2:   16px;
  --s-3:   24px;
  --s-4:   32px;
  --s-5:   40px;
  --s-6:   48px;
  --s-8:   64px;
  --s-10:  80px;
  --s-12:  96px;
  --s-16:  128px;
  --s-24:  192px;

  /* вертикальный ритм секций — флюидный, плотный */
  --section-gap: clamp(64px, 8.5vw, 160px);

  /* --- сетка: 12 колонок, узкие поля.
         «Использовать вьюпорт агрессивно» — Groth как бенчмарк
         плотности. --- */
  --cols:    12;
  --gutter:  clamp(12px, 1.15vw, 20px);
  /* 48px на десктопе = поле Groth, эталона плотности из ресёрча.
     Было 34px — на треть теснее самого плотного референса; это уже
     не плотность, а зажатость. Замеры: research/references/notes.md */
  --margin:  clamp(16px, 3.4vw, 48px);

  /* читаемая колонка для длинного текста кейсов: ~66 знаков */
  --measure: 34em;

  /* --- моушн: ОДНА грамматика на весь сайт.
         Примитивы: crop reveal, slide/translate, mask,
         scale/focus, управляемый marquee. --- */
  /* Ни один из пяти референсов не переходит дольше 575 ms, Groth
     держит ровно 450 ms на всём сайте. Раскрытие в 980 ms доезжало
     уже после того, как читатель дошёл до блока. */
  --dur-fast:   180ms;
  --dur-base:   420ms;
  --dur-slow:   620ms;
  --dur-reveal: 760ms;
  --dur-marquee: 42s;

  /* портрет на первом экране: схождение точечного поля и хвост
     кроссфейда в фотографию. Референс требует короткого эффекта —
     весь жест укладывается в 1,15 s, из них последние 0,55 s
     идёт разрешение. Читает portrait-resolve.js. */
  --dur-portrait:         1650ms;
  --dur-portrait-resolve:  550ms;

  --ease-out:    cubic-bezier(0.16, 1, 0.30, 1);    /* выходы, раскрытия */
  --ease-crop:   cubic-bezier(0.76, 0, 0.24, 1);    /* кропы, шторки */
  --ease-move:   cubic-bezier(0.65, 0, 0.35, 1);    /* перемещения */
  --ease-focus:  cubic-bezier(0.22, 1, 0.36, 1);    /* масштаб/фокус */

  /* каскад: соседние элементы вступают со сдвигом */
  --stagger: 90ms;

  --z-header: 100;
  --z-overlay: 200;
}

/* ------------------------------------------------------------
   2. СБРОС И ОСНОВА
   ------------------------------------------------------------ */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-text);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-body);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-synthesis-weight: none;
  /* тело страницы никогда не скроллится вбок */
  overflow-x: hidden;
}

h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd { margin: 0; }
ul, ol { padding: 0; list-style: none; }

img, video, canvas, svg {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

::selection {
  background: var(--ink);
  color: var(--ground);
}

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

/* ------------------------------------------------------------
   3. ТИПОГРАФИЧЕСКИЕ РОЛИ
   ------------------------------------------------------------ */

.t-d1, .t-d2, .t-d3 {
  font-family: var(--font-display);
  font-weight: var(--display-wght);
  font-variation-settings: "wdth" var(--display-wdth), "wght" var(--display-wght);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-display);
  text-wrap: balance;
}

.t-d1 { font-size: var(--fs-d1); }
.t-d2 { font-size: var(--fs-d2); }
.t-d3 { font-size: var(--fs-d3); line-height: var(--lh-snug); }

.t-head {
  font-family: var(--font-display);
  font-size: var(--fs-head);
  font-weight: 600;
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-head);
}

.t-deck {
  font-family: var(--font-text);
  font-size: var(--fs-deck);
  line-height: var(--lh-deck);
  letter-spacing: -0.006em;
  text-wrap: pretty;
}

.t-body {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  max-width: var(--measure);
  text-wrap: pretty;
}

/* мета — единственный «технический» голос на сайте:
   капс, разрядка, малый кегль. Не терминальная эстетика:
   это производственная разметка, а не консоль. */
.t-meta {
  font-family: var(--font-meta);
  font-size: var(--fs-meta);
  line-height: var(--lh-meta);
  letter-spacing: var(--ls-meta);
  text-transform: uppercase;
  color: var(--ink-55);
}

.t-micro {
  font-family: var(--font-meta);
  font-size: var(--fs-micro);
  line-height: 1.4;
  letter-spacing: 0.05em;
  color: var(--ink-55);
}

/* нумерация индексов (What I Do, project index) */
.t-num {
  font-family: var(--font-meta);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  /* --ink-30 в этой шкале описан как «линии, дизейбл»: на тёмном фоне
     он даёт 2.6:1, и мелкие мета-метки (Role, Period, Client…) не
     дотягивали до AA на каждой странице кейса. Текст берёт --ink-55. */
  color: var(--ink-55);
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------
   4. СЕТКА И ОБОЛОЧКИ
   ------------------------------------------------------------ */

.shell {
  padding-inline: var(--margin);
}

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

/* колоночные хелперы: .c-1 … .c-12 = ширина в колонках,
   .o-1 … .o-11 = сдвиг от левого края.

   ВАЖНО: ширина задаётся через grid-column-END, а не через
   шорткат grid-column. Шорткат `grid-column: span 8` раскрывается
   в `start: span 8 / end: auto`, и следующий за ним
   `grid-column-start: 5` из .o-* затирает start — элемент
   схлопывается в ОДНУ колонку. Через end этого не происходит:
   start остаётся авто (или приходит из .o-*), а span живёт в end. */
.c-1  { grid-column-end: span 1;  }
.c-2  { grid-column-end: span 2;  }
.c-3  { grid-column-end: span 3;  }
.c-4  { grid-column-end: span 4;  }
.c-5  { grid-column-end: span 5;  }
.c-6  { grid-column-end: span 6;  }
.c-7  { grid-column-end: span 7;  }
.c-8  { grid-column-end: span 8;  }
.c-9  { grid-column-end: span 9;  }
.c-10 { grid-column-end: span 10; }
.c-11 { grid-column-end: span 11; }
.c-12 { grid-column-end: span 12; }

.o-1  { grid-column-start: 2;  }
.o-2  { grid-column-start: 3;  }
.o-3  { grid-column-start: 4;  }
.o-4  { grid-column-start: 5;  }
.o-5  { grid-column-start: 6;  }
.o-6  { grid-column-start: 7;  }
.o-7  { grid-column-start: 8;  }
.o-8  { grid-column-start: 9;  }

/* полноширинный блок, выходящий за поля */
.bleed {
  margin-inline: calc(var(--margin) * -1);
  width: calc(100% + var(--margin) * 2);
}

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

/* хайрлайн-разделитель: край сетки виден, а не подразумевается */
.rule {
  border: 0;
  border-top: 1px solid var(--ink-12);
  margin: 0;
}

/* ------------------------------------------------------------
   5. МЕДИА-БЛОКИ
   ------------------------------------------------------------ */

/* Медиа-блок всегда несёт под собой подписанный плейсхолдер.
   Загрузился кадр — плейсхолдер закрыт. Не загрузился (кадра
   ещё нет в пуле) — виден честный лейбл, а не битая картинка. */
.media {
  position: relative;
  overflow: hidden;
  display: grid;
  place-items: center;
  background:
    repeating-linear-gradient(
      135deg,
      var(--ground-alt) 0 10px,
      color-mix(in srgb, var(--ground-alt) 90%, var(--ink)) 10px 20px
    );
}

.media__label {
  font-family: var(--font-meta);
  font-size: var(--fs-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-55);
  text-align: center;
  padding: var(--s-2);
  max-width: 26ch;
  grid-area: 1 / 1;
}

.media > img,
.media > video,
.media > iframe,
.media > canvas {
  grid-area: 1 / 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  position: relative;
  z-index: 1;
  border: 0;
}

.ar-365  { aspect-ratio: 1546 / 423; }  /* MJFF-баннер — родная пропорция */
.ar-243  { aspect-ratio: 2188 / 900; }  /* JMW-карусель — родная пропорция */
.ar-263  { aspect-ratio: 1708 / 650; }  /* MJFF-баннеры кампаний — родная пропорция */
.ar-205  { aspect-ratio: 3016 / 1474; } /* JMW web-selects — родная пропорция */
.ar-19   { aspect-ratio: 1200 / 630; }  /* The Pipeline covers — родная пропорция */
.ar-119  { aspect-ratio: 940 / 788; }   /* MJFF 2023 key visual — родная пропорция */
.ar-43   { aspect-ratio: 2600 / 1950; }  /* Solaris credits page — родная пропорция */
.ar-21   { aspect-ratio: 21 / 9; }
.ar-169  { aspect-ratio: 16 / 9; }
.ar-1610 { aspect-ratio: 16 / 10; }     /* пропорция рамки карточки стены */
.ar-32   { aspect-ratio: 3 / 2; }
.ar-11   { aspect-ratio: 1 / 1; }
.ar-45   { aspect-ratio: 4 / 5; }
.ar-23   { aspect-ratio: 2 / 3; }
.ar-916  { aspect-ratio: 9 / 16; }

/* .is-gap — не «слот ждёт картинку», а зафиксированная дыра
   в ассетах. Выглядит иначе, чтобы на ревью не спутать одно
   с другим. */
.media.is-gap {
  background: none;
  border: 1px dashed var(--ink-30);
}
.media.is-gap .media__label {
  color: var(--ink-30);
  line-height: 1.7;
}

/* медиа на тёмной плоскости (комп C) */
.media--dark {
  background:
    repeating-linear-gradient(
      135deg,
      color-mix(in srgb, var(--ground) 82%, #fff) 0 10px,
      color-mix(in srgb, var(--ground) 74%, #fff) 10px 20px
    );
}

/* ------------------------------------------------------------
   6. МОУШН-ПРИМИТИВЫ
   Одна грамматика. Элемент получает data-m="<примитив>",
   motion.js вешает .is-in при входе во вьюпорт.
   ------------------------------------------------------------ */

[data-m] {
  --m-delay: 0ms;
  will-change: transform, opacity, clip-path;
}

/* --- crop reveal: раскрытие кропом.
       Реализовано ШТОРКОЙ, а не clip-path. Причина: элемент со
       стартовым clip-path: inset(100%) имеет нулевую видимую
       площадь, и Chromium никогда не считает его пересекающим
       вьюпорт — IntersectionObserver по нему не срабатывает ни
       разу, и блок остаётся невидимым навсегда.
       Шторка даёт тот же визуальный жест и не ломает наблюдение. --- */
[data-m="crop"],
[data-m="crop-x"] {
  position: relative;
  overflow: hidden;
}

[data-m="crop"]::after,
[data-m="crop-x"]::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  background: var(--shutter, var(--ground));
  transition: transform var(--dur-reveal) var(--ease-crop);
  transition-delay: var(--m-delay);
}

/* снизу вверх */
[data-m="crop"]::after       { transform: translateY(0); }
[data-m="crop"].is-in::after { transform: translateY(-101%); }

/* слева направо — при смене направления движения */
[data-m="crop-x"]::after       { transform: translateX(0); }
[data-m="crop-x"].is-in::after { transform: translateX(101%); }

/* --- slide: сдвиг + проявление --- */
[data-m="slide"] {
  opacity: 0;
  transform: translateY(28px);
  transition:
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-slow) var(--ease-out);
  transition-delay: var(--m-delay);
}
[data-m="slide"].is-in { opacity: 1; transform: none; }

[data-m="slide-x"] {
  opacity: 0;
  transform: translateX(40px);
  transition:
    opacity var(--dur-base) var(--ease-out),
    transform var(--dur-slow) var(--ease-move);
  transition-delay: var(--m-delay);
}
[data-m="slide-x"].is-in { opacity: 1; transform: none; }

/* --- mask: построчное раскрытие текста.
       Строка оборачивается в .line > span --- */
.line {
  display: block;
  overflow: hidden;
}
.line > span {
  display: block;
  transform: translateY(105%);
  transition: transform var(--dur-slow) var(--ease-out);
  transition-delay: var(--m-delay);
}
[data-m="mask"].is-in .line > span,
.is-in > .line > span { transform: none; }

/* --- scale / focus: медиа въезжает из лёгкого приближения --- */
[data-m="focus"] > img,
[data-m="focus"] > video,
[data-m="focus"] > canvas {
  transform: scale(1.07);
  transition: transform var(--dur-reveal) var(--ease-focus);
  transition-delay: var(--m-delay);
}
[data-m="focus"].is-in > img,
[data-m="focus"].is-in > video,
[data-m="focus"].is-in > canvas { transform: none; }

/* --- marquee: структурный разделитель, не постоянный декор.
       Ресёрч: использовать только как разделитель секций. --- */
.marquee {
  display: flex;
  overflow: hidden;
  white-space: nowrap;
  user-select: none;
}
.marquee__track {
  display: flex;
  flex: 0 0 auto;
  min-width: 100%;
  animation: marquee var(--dur-marquee) linear infinite;
}
.marquee--reverse .marquee__track { animation-direction: reverse; }
.marquee:hover .marquee__track { animation-play-state: paused; }

@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-100%); }
}

/* --- ховер медиа: одна реакция на весь сайт --- */
.hov-media img,
.hov-media canvas {
  transition: transform var(--dur-slow) var(--ease-focus);
}
@media (hover: hover) {
  .hov-media:hover img,
  .hov-media:hover canvas { transform: scale(1.035); }
}

/* --- подчёркивание ссылки: растёт от левого края --- */
.link-u {
  position: relative;
  display: inline-block;
}
.link-u::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -0.12em;
  width: 100%;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-base) var(--ease-out);
}
@media (hover: hover) {
  .link-u:hover::after { transform: scaleX(1); }
}
.link-u[aria-current="page"]::after { transform: scaleX(1); }

/* ------------------------------------------------------------
   7. ПОРТРЕТ КАК ЦИФРОВОЙ МАТЕРИАЛ, КОТОРЫЙ УХОДИТ
   Лицо собирается из пикселей, рассыпается и пересобирается
   в кадр проекта. В покое лица нет.

   <img> в разметке — это КАДР ПРОЕКТА. Лицо живёт только в
   канвасе и приходит из data-portrait-face. Поэтому без JS и
   при выключенной анимации показывается проект, а не лицо.
   Логика в system/portrait.js.
   ------------------------------------------------------------ */

.portrait {
  position: relative;
  overflow: hidden;
  background: var(--ground-alt);
}
.portrait canvas,
.portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.portrait canvas { position: absolute; inset: 0; z-index: 1; }

/* пока идёт морф — канвас поверх, кадр проекта скрыт */
.portrait.is-morphing img { opacity: 0; }
.portrait.is-done img { opacity: 1; transition: opacity var(--dur-base) linear; }
.portrait.is-done canvas { display: none; }

/* ------------------------------------------------------------
   8. МЕТА-РЕЙКА
   Роль / год / клиент / практика. Повторяющаяся система,
   которая держит ориентацию при разном ритме медиа-блоков.
   ------------------------------------------------------------ */

.meta-rail {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-1) var(--s-3);
  align-items: baseline;
}

.meta-rail dt {
  font-family: var(--font-meta);
  font-size: var(--fs-micro);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  /* --ink-30 держит линии и дизейбл; для текста на тёмном фоне он
     даёт 2.6:1. Метки Role / Period / Client стоят на каждой странице
     кейса — им нужен --ink-55. */
  color: var(--ink-55);
}

.meta-rail dd {
  font-family: var(--font-meta);
  font-size: var(--fs-meta);
  letter-spacing: 0.03em;
  color: var(--ink-80);
  margin: 0;
}

/* метка практики: точка + текст, а не цветная плашка */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--font-meta);
  font-size: var(--fs-micro);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-55);
  white-space: nowrap;
}
.tag::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--tag-color, var(--ink-30));
  flex: 0 0 auto;
}
.tag--tc { --tag-color: var(--practice-tc); }
.tag--ai { --tag-color: var(--practice-ai); }
.tag--cc { --tag-color: var(--practice-cc); }
.tag--cm { --tag-color: var(--practice-cm); }

/* награда — отдельный визуальный маркер, не ещё один тег */
.award {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  font-family: var(--font-meta);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  border: 1px solid var(--ink-30);
  padding: 0.35em 0.7em;
}

/* ------------------------------------------------------------
   9. АДАПТИВ
   Мобильная версия линеаризуется. Ни один комп не пытается
   воспроизвести десктопную композицию на 390.
   ------------------------------------------------------------ */

@media (max-width: 1024px) {
  :root { --cols: 8; }
  .c-7, .c-8, .c-9, .c-10, .c-11, .c-12 { grid-column-end: span 8; }
  .o-4, .o-5, .o-6, .o-7, .o-8 { grid-column-start: auto; }
}

@media (max-width: 720px) {
  :root {
    --cols: 4;
    --margin: 16px;
    --section-gap: 56px;
    --ls-display: -0.028em;
  }
  /* всё сваливается в одну колонку */
  [class*="c-"] { grid-column: 1 / -1; }
  [class*="o-"] { grid-column-start: 1; }
  .grid { row-gap: var(--s-3); }
  .t-body { max-width: none; }

  /* Маска по строкам на узком экране отключается.
     Причина: строка, размеченная вручную, на 390 px переносится
     на две визуальные строки, а `.line { overflow: hidden }`
     срезает вторую. Переносы остаются, анимация уходит. */
  .line { overflow: visible; }
  .line > span { transform: none; }
}

/* ------------------------------------------------------------
   10. REDUCED MOTION
   Не ветка в конце, а часть базы. Иерархия сохраняется,
   переходы становятся прямыми, портрет — статикой.
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  /* конечные состояния выставляются сразу, а не «мгновенно анимируются»:
     ни один элемент не должен остаться невидимым */
  [data-m="crop"]::after,
  [data-m="crop-x"]::after { display: none !important; }

  [data-m="slide"],
  [data-m="slide-x"] { opacity: 1 !important; transform: none !important; }

  .line > span { transform: none !important; }

  [data-m="focus"] > img,
  [data-m="focus"] > video,
  [data-m="focus"] > canvas { transform: none !important; }

  .marquee__track { animation: none !important; }

  /* лицо не показывается вообще — сразу кадр проекта */
  .portrait canvas { display: none !important; }
  .portrait img { opacity: 1 !important; }

  @media (hover: hover) {
    .hov-media:hover img,
    .hov-media:hover canvas { transform: none; }
  }
}

/* доступность: скрыто визуально, доступно скринридеру */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* пропустить к контенту — навигация никогда не блокируется */
.skip {
  position: absolute;
  left: var(--margin);
  top: -100px;
  z-index: calc(var(--z-header) + 10);
  background: var(--ink);
  color: var(--ground);
  padding: var(--s-1) var(--s-2);
  font-family: var(--font-meta);
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transition: top var(--dur-fast) var(--ease-out);
}
.skip:focus { top: var(--s-2); }

/* === alchemy: motion tokens === */
/* Длительности и кривые сцены-героя темы alchemy — взяты дословно
   из .hero-scene img { animation: scene-arrive ... } и
   .hero.replaying .hero-scene img { animation: scene-replay ... }
   в research/alchemy-reference/index.html. Не переиспользуют
   --dur-portrait (1650ms) и --ease-out/crop/move/focus — значения
   другие, "один в один" без подгонки. Сами объявления transition/
   animation в разложенном CSS на эти токены НЕ переведены — это
   отдельный шаг. */
:root {
  --dur-scene-arrive:  2.2s;
  --dur-scene-replay:  1.8s;
  --ease-scene-arrive: cubic-bezier(0.2, 0.65, 0.3, 1);
  --ease-scene-replay: cubic-bezier(0.17, 0.67, 0.32, 1);
}

@keyframes scene-arrive{from{transform:scale(1.07) translateX(2%);filter:blur(7px)}to{transform:scale(1);filter:blur(0)}}

@keyframes scene-replay{0%{transform:scale(1)}35%{transform:scale(1.09) rotate(1.4deg)}70%{transform:scale(1.015) rotate(-.4deg)}100%{transform:scale(1)}}
