/* ==========================================================================
   Максим Мельников — портфолио

   Оглавление
     1. Шрифты и токены
     2. Сброс и примитивы вёрстки
     3. Система появления по скроллу        ← общая на все блоки
     4. Шапка секции (.shead)               ← общая на все блоки
     5. Кнопки и ссылки с барабаном
     6. Обложка
     7. Любимые кейсы
     8. Места работы
     9. Ещё проекты
    10. Футер
    11. Модалка и просмотрщик
    12. Доступность и prefers-reduced-motion

   АДАПТИВ (десктоп)

   Дизайн нарисован на 1440. От 1280 до 1920 интерфейс тянется пропорционально,
   ниже 1280 замирает, выше 1920 — тоже, и по краям появляются поля: на больших
   мониторах это нормальнее, чем растянутый на 3440px текст.

   Горизонталь тянется сама — колонки, ширины и отступы заданы долями
   контентного бокса, а не пикселями. Вертикаль и кегли — через
   clamp(значение при 1280, доля vw, значение при 1920), где доля vw =
   значение при 1440 / 1440 × 100. То есть чистое пропорциональное
   масштабирование, зажатое по краям диапазона.

   Исключение — «хром» интерфейса: кнопки, плашки, аватар, иконки, волосяные
   линии. Он растёт примерно вполовину медленнее макета: кнопка, выросшая
   в 1.33 раза, выглядит не солиднее, а просто грубее.

   Мобильных и планшетных брейкпоинтов пока нет.
   ========================================================================== */

/* ======================== 1. ШРИФТЫ И ТОКЕНЫ ======================== */

/* Lebowski by Pragmatica — бесплатный дисплейный шрифт BureauBukv × Pragmatica.
   Файл положен в fonts/ без изменений: лицензия запрещает модификацию. */
@font-face {
  font-family: 'Lebowski by Pragmatica';
  src: url('fonts/LebowskiByPragmatica-Regular.otf') format('opentype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  --c-bg:            #000000;
  --c-accent:        #ff3500;
  --c-accent-deep:   #a22909;
  --c-text:          #ffffff;
  --c-text-muted:    rgba(255, 255, 255, 0.4);
  --c-glass:         rgba(255, 255, 255, 0.08);
  --c-glass-strong:  rgba(255, 255, 255, 0.2);
  --c-rule:          rgba(217, 217, 217, 0.4);
  --c-panel:         #151516;   /* подложка футера, панели модалки, кадров */
  --c-chip:          #1d1d1e;   /* теги, плашка даты, круглая кнопка */

  --font-display: 'Lebowski by Pragmatica', 'Pragmatica', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --font-mono:    'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace;
  --font-text:    'Inter Tight', 'Inter', system-ui, -apple-system, sans-serif;
  --font-digits:  'Doto', 'Geist Mono', ui-monospace, monospace;

  --blur: blur(7.5px);

  /* ---------- сетка ---------- */
  /* Выше --page-max контент не растёт: по краям поля. Ниже --page-min
     ничего не сжимается — это нижняя граница десктопа. */
  --page-max: 1920px;
  --page-min: 1280px;

  --gutter: clamp(18px, 1.389vw, 27px);          /* 20 */

  /* Доли контентного бокса (1400 в макете). Вторая колонка — 820/1400. */
  --col-2: 58.571%;
  /* В футере своя внутренняя сетка: карточка сама отступает на --gutter
     от контента, поэтому доля другая, а абсолютная позиция та же. */
  --col-2-foot: 58.82%;

  /* ---------- кегли ---------- */
  --fs-display-xl: clamp(80px, 6.25vw,  120px);  /* 90 — заголовки секций */
  --fs-display-l:  clamp(58px, 4.514vw,  87px);  /* 65 — титул, ссылка в футере */
  --fs-display-m:  clamp(37px, 2.917vw,  56px);  /* 42 — заголовок модалки */
  --fs-display-s:  clamp(36px, 2.778vw,  53px);  /* 40 — название кейса */
  --fs-card:       clamp(25px, 1.944vw,  37px);  /* 28 — заголовок карточки */
  --fs-company:    clamp(23px, 1.806vw,  35px);  /* 26 — компания */

  /* Мелкий текст вниз не идёт: на 1280 он и так на пределе читаемости,
     а вверх растёт сдержаннее макета — иначе подписи лезут в заголовки. */
  --fs-lead:       clamp(18px, 1.250vw,  21px);  /* 18 — текст модалки */
  --fs-body:       clamp(16px, 1.111vw,  18px);  /* 16 */
  --fs-ui:         clamp(14px, 0.972vw,  16px);  /* 14 */
  --fs-note:       clamp(12px, 0.833vw,  14px);  /* 12 */

  /* ---------- вертикальный ритм ---------- */
  --sp-5:   clamp(4px,  0.347vw,  7px);
  --sp-8:   clamp(7px,  0.556vw, 11px);
  --sp-10:  clamp(9px,  0.694vw, 13px);
  --sp-15:  clamp(13px, 1.042vw, 20px);
  --sp-20:  clamp(18px, 1.389vw, 27px);
  --sp-21:  clamp(19px, 1.458vw, 28px);
  --sp-24:  clamp(21px, 1.667vw, 32px);
  --sp-25:  clamp(22px, 1.736vw, 33px);
  --sp-40:  clamp(36px, 2.778vw, 53px);
  --sp-42:  clamp(37px, 2.917vw, 56px);
  --sp-95:  clamp(84px, 6.597vw, 127px);
  --sp-110: clamp(98px, 7.639vw, 147px);

  /* воздух между блоками */
  --gap-section: clamp(231px, 18.056vw, 347px);   /* 260 */
  --gap-foot:    clamp(196px, 15.278vw, 293px);   /* 220 */

  /* ---------- хром: растёт вполовину медленнее ---------- */
  --h-btn:    clamp(46px, 3.472vw, 58px);   /* 50 */
  --h-chip:   clamp(28px, 2.083vw, 34px);   /* 30 */
  --sz-photo: clamp(54px, 4.028vw, 66px);   /* 58 — аватар и глобус */
  --r-8: 8px;
  --r-6: 6px;
  --r-4: 4px;

  /* Кривые. --ease-out-soft снят с референса (andreigorskikh.digital):
     переворот лейбла идёт ровно по нему за 400 мс, без отскока.
     --ease-out-strong — для появления блоков, у него резче старт. */
  --ease-out-soft: cubic-bezier(0, 0, 0.58, 1);
  --ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1);
  /* длинный хвост для проявления титула — снят с verteal.com */
  --ease-reveal: cubic-bezier(0.16, 1, 0.3, 1);
  /* easeOutCubic — снят с dragonfly.xyz, блок All Content */
  --ease-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);

  --flip-ms: 400ms;

  /* Тайминги появления держим тут: их подбирали на слух. */
  --reveal-ms: 520ms;   /* всплытие на 10px */
  --word-ms:   900ms;   /* слово титула из размытия */
  --wipe-ms:   760ms;   /* выезд линии слева направо */
}

/* ==================== 2. СБРОС И ПРИМИТИВЫ ВЁРСТКИ ==================== */

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

/* Полоса прокрутки зарезервирована всегда: иначе при открытии модалки
   (body получает overflow: hidden) страница дёргалась бы вбок. Заодно
   100vw и реальная ширина документа перестают расходиться. */
html {
  background: var(--c-bg);
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  min-width: var(--page-min);   /* ниже — территория мобильного брейкпоинта */
  background: var(--c-bg);
  color: var(--c-text);
  font-family: var(--font-mono);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img { display: block; }

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

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

/* Полоса во всю ширину макета — секции. */
.shell {
  position: relative;
  width: 100%;
  max-width: var(--page-max);
  margin-inline: auto;
}

/* Колонка контента внутри неё — ряды, списки, линии. Ширина, а не padding:
   от контентной кромки отсчитываются абсолютно позиционированные потомки
   (строки мест работы), и padding сдвинул бы им нуль. min() нужен, чтобы
   примитив работал и внутри .shell, и прямо в body (шапка). */
.wrap {
  position: relative;
  width: min(100% - var(--gutter) * 2, var(--page-max) - var(--gutter) * 2);
  margin-inline: auto;
}

/* Двухколонник страницы: слева основное, справа вторая колонка. */
.cols2 {
  display: grid;
  grid-template-columns: var(--col-2) 1fr;
  align-items: start;
}

/* Подпись-моношрифт: базовый мелкий текст интерфейса */
.mono,
.cover__place,
.cover__zone,
.cover__lead,
.ulink__face,
.btn,
.clock__dot {
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-ui);
  text-transform: uppercase;
}

/* ================ 3. СИСТЕМА ПОЯВЛЕНИЯ ПО СКРОЛЛУ ================
     [data-observe]  — группа наблюдения. script.js вешает на неё .is-in,
                       когда она въезжает в экран.
     [data-reveal]   — элемент, который всплывает на 10px.
     .w__in          — слово заголовка: всплывает из размытия.
     [data-rule]     — линия: выезжает слева направо.

   Группы вложены друг в друга (.cases → .case, .more → .pcard), поэтому
   одного правила «показать внутри .is-in» мало: .cases.is-in дотянулся бы
   до содержимого всех трёх кейсов и проявил их разом, мимо собственных
   наблюдателей. Третье правило это чинит — внутренняя группа, которая ещё
   не вошла, снова прячет своё содержимое. Специфичность у него выше (0,3,0
   против 0,2,0), поэтому порядок в файле роли не играет.

   Обложка намеренно вне системы: у неё появление по загрузке, keyframes. */

[data-observe] [data-reveal] {
  opacity: 0;
  transform: translateY(10px);
  transition: opacity   var(--reveal-ms) var(--ease-out-strong) var(--reveal-delay, 0ms),
              transform var(--reveal-ms) var(--ease-out-strong) var(--reveal-delay, 0ms);
}

[data-observe] .w__in {
  opacity: 0;
  transform: translateY(0.5em);
  filter: blur(14px);
  transition: opacity   var(--word-ms) var(--ease-reveal) var(--reveal-delay, 0ms),
              transform var(--word-ms) var(--ease-reveal) var(--reveal-delay, 0ms),
              filter    var(--word-ms) var(--ease-reveal) var(--reveal-delay, 0ms);
}

/* Через clip-path, а не scaleX: масштаб растянул бы сам пунктир,
   а так штрихи просто открываются. */
[data-observe] [data-rule] {
  clip-path: inset(0 100% 0 0);
  transition: clip-path var(--wipe-ms) var(--ease-out-strong) var(--rule-delay, 0ms);
}

.is-in [data-reveal],
.is-in .w__in {
  opacity: 1;
  transform: none;
  filter: none;
}

.is-in [data-rule] { clip-path: inset(0); }

/* Вложенная группа, ещё не вошедшая, перекрывает внешнюю. Второй селектор —
   для случая, когда группа и есть проявляемый элемент (карточка проекта). */
[data-observe]:not(.is-in) [data-reveal],
[data-observe][data-reveal]:not(.is-in) {
  opacity: 0;
  transform: translateY(10px);
}

[data-observe]:not(.is-in) .w__in {
  opacity: 0;
  transform: translateY(0.5em);
  filter: blur(14px);
}

[data-observe]:not(.is-in) [data-rule] { clip-path: inset(0 100% 0 0); }

/* ==================== 4. ШАПКА СЕКЦИИ (.shead) ====================
   Одинаковая у «Любимых кейсов», «Мест работы» и «Ещё проектов»: слева
   заголовок со счётчиком, справа период и примечание. Лежит в потоке
   и высоту набирает сама — заголовок тянется вместе с экраном. */

.shead {
  padding-bottom: var(--sp-110);
}

.shead__group {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-15);
}

.shead__aside {
  display: flex;
  flex-direction: column;
  gap: var(--sp-42);
  align-items: flex-start;
}

.shead__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-display-xl);
  line-height: 1;
  color: var(--c-accent);
  white-space: nowrap;
}

.shead__count,
.shead__span {
  margin: 0;
  font-family: var(--font-text);
  font-size: var(--fs-body);
  line-height: 1;
  color: rgba(255, 53, 0, 0.4);
  white-space: nowrap;
}

.shead__note {
  width: 63.79%;   /* 370 из 580 — ширины правой колонки, а не левой */
  margin: 0;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-note);
  line-height: 1.3;
  text-transform: uppercase;
  color: var(--c-accent);
}

/* лесенка появления — одна на все три блока */
.shead__title .w:nth-child(1) .w__in { --reveal-delay:   0ms; }
.shead__title .w:nth-child(2) .w__in { --reveal-delay:  80ms; }
.shead__count { --reveal-delay: 160ms; }
.shead__span  { --reveal-delay: 200ms; }
.shead__note  { --reveal-delay: 260ms; }

/* =============== 5. КНОПКИ И ССЫЛКИ С БАРАБАНОМ =============== */

/* Ссылка с подчёркиванием в 1px. Ховер снят с andreigorskikh.digital:
   две копии лейбла крутятся как барабан — уходящая уезжает вверх
   и заваливается на 150°, приходящая приезжает снизу из того же угла. */
.ulink {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

/* Копии лежат в одной grid-ячейке — так они совпадают до пикселя без
   абсолютного позиционирования. Padding сверху даёт запас под бреве «Й»
   (текстовый бокс обрезан по cap-height, а бреве выше неё), отрицательный
   margin возвращает колонку к исходной высоте. */
.ulink__box,
.btn__box {
  display: grid;
  overflow: hidden;
  padding-block: var(--pad-top) var(--pad-bottom);
  margin-block: calc(var(--pad-top) * -1) calc(var(--pad-bottom) * -1);
}

.ulink__box { --pad-top: 0.25em; --pad-bottom: 0em;   }
.btn__box   { --pad-top: 0.25em; --pad-bottom: 0.1em; }

.ulink__face,
.btn__face {
  grid-area: 1 / 1;
  line-height: 1;
  white-space: nowrap;
  transition: transform var(--flip-ms) var(--ease-out-soft),
              opacity   var(--flip-ms) var(--ease-out-soft);
}

.ulink__face {
  justify-self: start;
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
}

.btn__face { justify-self: center; }

.ulink__face--alt { color: var(--c-accent); }

/* Прячущаяся копия уезжает за padding-бокс целиком, иначе на середине
   перехода из-под кромки выглядывает полоска текста. */
.ulink__face--alt,
.btn__face--alt {
  opacity: 0;
  transform: translateY(calc(100% + var(--pad-bottom, 0px))) rotateX(150deg);
}

/* Линия под текстом. Лейблы разной длины («Написать мне» → «Telegram»),
   поэтому дорожка равна широкому из двух, а обе полосы масштабируются по X:
   --rule-a и --rule-b считает script.js после загрузки шрифта. Это transform,
   а не width — не дёргает лейаут. */
.ulink__rule {
  position: relative;
  display: block;
  width: 100%;
  height: 1px;
}

.ulink__rule-base,
.ulink__rule-fill {
  position: absolute;
  inset: 0;
  transform-origin: left center;
  transition: transform var(--flip-ms) var(--ease-out-soft);
}

.ulink__rule-base {
  background: var(--c-rule);
  transform: scaleX(var(--rule-a, 1));
}

/* До замера доля равна 1, то есть дорожка шире короткого лейбла: линия
   выезжала во всю ширину и на глазах поджималась (заметнее всего на
   «назад» / «на главную»). Поэтому до замера её не видно совсем —
   script.js ставит data-measured в тот же кадр, что и доли. */
.ulink:not([data-measured]) :is(.ulink__rule-base, .ulink__rule-fill) {
  opacity: 0;
  transition: none;
}

.ulink__rule-fill {
  background: var(--c-accent);
  transform: scaleX(0);
}

/* Активное состояние барабана — одинаковое для ховера и клавиатуры. */
@media (hover: hover) and (pointer: fine) {
  :is(.hbtn, .btn):hover :is(.ulink__face, .btn__face) {
    opacity: 0;
    transform: translateY(calc(-100% - var(--pad-top, 0px))) rotateX(150deg);
  }

  :is(.hbtn, .btn):hover :is(.ulink__face--alt, .btn__face--alt) {
    opacity: 1;
    transform: translateY(0) rotateX(0deg);
  }

  .hbtn:hover :is(.ulink__rule-base, .ulink__rule-fill) { transform: scaleX(var(--rule-b, 1)); }
}

:is(.hbtn, .btn):focus-visible :is(.ulink__face, .btn__face) {
  opacity: 0;
  transform: translateY(calc(-100% - var(--pad-top, 0px))) rotateX(150deg);
}

:is(.hbtn, .btn):focus-visible :is(.ulink__face--alt, .btn__face--alt) {
  opacity: 1;
  transform: translateY(0) rotateX(0deg);
}

.hbtn:focus-visible :is(.ulink__rule-base, .ulink__rule-fill) { transform: scaleX(var(--rule-b, 1)); }

/* ---------- прямоугольные кнопки ---------- */

.btn {
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--h-btn);
  padding-inline: var(--sp-20);
  border-radius: var(--r-4);
  overflow: hidden;
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  transition: background-color 200ms ease, transform 160ms var(--ease-out-strong);
}

/* нажатие должно чувствоваться сразу */
.btn:active { transform: scale(0.97); }

.btn--accent {
  background: var(--c-accent);
  color: #000;
}

.btn--glass {
  background: var(--c-glass-strong);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  color: var(--c-text);
}

@media (hover: hover) and (pointer: fine) {
  .btn--accent:hover { background: #ff5426; }
  .btn--glass:hover  { background: rgba(255, 255, 255, 0.28); }
}

/* ---------- кнопки шапки ---------- */

/* Подложка появляется только на ховере — в покое от макета не отличается.
   Отступы заданы так, что содержимое остаётся на сетке: кнопка выступает
   на 4px вокруг фото/иконки и на 20px со стороны текста, поэтому вся
   шапка сдвинута на эти же 4px наружу. */
.hbtn {
  display: flex;
  align-items: center;
  /* Кнопка «написать мне» — ячейка грида шапки: без этого подложка
     на ховере растягивается на всю колонку (733px вместо 191px). */
  justify-self: start;
  border-radius: var(--r-8);
  background: transparent;
  cursor: pointer;
  transition: background-color var(--flip-ms) var(--ease-out-soft),
              backdrop-filter var(--flip-ms) var(--ease-out-soft),
              transform 160ms var(--ease-out-strong);
}

.hbtn:active { transform: scale(0.985); }

@media (hover: hover) and (pointer: fine) {
  .hbtn:hover {
    background: rgba(255, 53, 0, 0.12);
    backdrop-filter: var(--blur);
    -webkit-backdrop-filter: var(--blur);
  }
}

.hbtn:focus-visible {
  background: rgba(255, 53, 0, 0.12);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
}

/* ---------- аватар: ч/б → цвет ---------- */

.avatar {
  position: relative;
  display: block;
  width: var(--sz-photo);
  height: var(--sz-photo);
  border-radius: var(--r-6);
  overflow: hidden;
  flex-shrink: 0;
}

.avatar__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* цветная лежит снизу, ч/б сверху и растворяется */
.avatar__img--mono { transition: opacity var(--flip-ms) var(--ease-out-soft); }

@media (hover: hover) and (pointer: fine) {
  .hbtn:hover .avatar__img--mono { opacity: 0; }
}

.hbtn:focus-visible .avatar__img--mono { opacity: 0; }

/* ---------- иконка глобуса внутри кнопки языка ---------- */

.globe,
.hbtn__ico {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--sz-photo);
  height: var(--sz-photo);
  border-radius: var(--r-6);
  overflow: hidden;
  flex-shrink: 0;
  background: var(--c-glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  transition: background-color var(--flip-ms) var(--ease-out-soft);
}

@media (hover: hover) and (pointer: fine) {
  .hbtn:hover :is(.globe, .hbtn__ico) { background: rgba(255, 53, 0, 0.18); }
}

.hbtn:focus-visible :is(.globe, .hbtn__ico) { background: rgba(255, 53, 0, 0.18); }

.globe img,
.hbtn__ico svg {
  width: 41%;   /* 24 из 58 */
  height: auto;
}

/* ---------- часы и локация ---------- */
/* Один и тот же узел стоит в шапке и в футере. Раньше внутренние координаты
   были заданы абсолютом (117px до пояса); теперь это поток, поэтому при
   росте кегля цифры и подписи не наезжают друг на друга. */

.timeblock {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-8);
  width: max-content;
}

.timeblock__row {
  display: flex;
  align-items: center;
  gap: var(--sp-10);
}

.clock {
  display: flex;
  align-items: flex-end;
  gap: 2px;
}

.clock__cell {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--h-chip);
  height: var(--h-chip);
  border-radius: var(--r-6);
  background: var(--c-glass);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
}

.clock__digits {
  font-family: var(--font-digits);
  font-weight: 900;
  font-variation-settings: 'ROND' 0;
  font-size: var(--fs-body);
  line-height: 1;
  color: var(--c-accent);
  white-space: nowrap;
  text-box-trim: trim-both;
  text-box-edge: cap alphabetic;
  font-variant-numeric: tabular-nums;
}

.clock__dot {
  color: var(--c-accent);
  line-height: 1.3;
}

.cover__place,
.cover__zone {
  margin: 0;
  line-height: 1.3;
  white-space: nowrap;
}

.cover__zone { color: var(--c-text-muted); }

/* ========================= 6. ОБЛОЖКА / 207:6845 =========================
   Высоту ограничивают обе оси: по ширине она тянется с макетом, но на
   невысоком ноутбуке 52vw съели бы весь первый экран, поэтому сверху
   её подрезает vh. */

.cover {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  height: clamp(600px, min(52.083vw, 88vh), 1000px);
  overflow: hidden;
  background: var(--c-bg);
}

/* фон — растр прижат к низу, шире кадра по высоте (1326 при 750) */
.cover__bg {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 176.8%;
  pointer-events: none;
}

.cover__bg-raster {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.5;
  mix-blend-mode: luminosity;
}

/* ASCII-поле занимает только видимую часть обложки */
.cover__bg-ascii {
  position: absolute;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 56.6%;   /* 750 из 1326 */
  opacity: 0;
  transition: opacity 0.45s ease;
}

.cover[data-ascii="on"] .cover__bg-raster { display: none; }
.cover[data-ascii="on"] .cover__bg-ascii  { opacity: 1; }

/* ---------- нижний блок обложки ---------- */
/* Титул, кнопки и подписи прижаты к низу кадра, а не расставлены по
   абсолютным координатам: при любой высоте обложки они остаются на месте
   относительно нижней кромки. */

.cover__foot {
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--sp-25);
  padding-bottom: var(--sp-20);
}

.cover__main { align-items: end; }

.cover__title {
  display: flex;
  flex-direction: column;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-display-l);
  line-height: 1;
  letter-spacing: 0;
  white-space: nowrap;
}

.cover__name { color: var(--c-accent); }
.cover__role { color: var(--c-accent-deep); }

/* Заголовки разбиты на слова: каждое проявляется отдельно */
.w {
  display: inline-block;
  vertical-align: top;
}

.w__in { display: inline-block; }

.cover__actions {
  display: flex;
  gap: 0.862%;   /* 5 из 580 */
}

.cover__cta--tg { width: 23.966%; }   /* 139 из 580 */
.cover__cta--cv { width: 37.241%; }   /* 216 из 580 */

.cover__lead {
  margin: 0;
  line-height: 1.3;
  color: var(--c-text);
}

/* доли своих колонок: 400 из 820 и 410 из 580 */
.cover__lead--left  { width: 48.8%; }
.cover__lead--right { width: 70.7%; }

/* Английская подпись справа на пару слов длиннее русской и на 410px ложится
   в три строки вместо двух. Ширину добавляем только ей: правая колонка всё
   равно шире подписи, свободного места там ещё 170px. */
:root[lang="en"] .cover__lead--right { width: 84%; }

/* ---------- появление ---------- */

/* Титул собирается по словам: каждое всплывает из размытия и наводится
   на резкость — приём с первого экрана verteal.com. Остальное просто
   всплывает на 10px. fill-mode: backwards, а не both: иначе финальный
   transform анимации перебил бы :active у кнопок.

   Обложка появляется по загрузке, а не по скроллу, поэтому keyframes
   и никакого [data-observe] — общая система раздела 3 её не касается. */

@keyframes cover-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes cover-word {
  from { opacity: 0; transform: translateY(0.5em); filter: blur(14px); }
  to   { opacity: 1; transform: translateY(0);     filter: blur(0);    }
}

.cover__contact,
.cover__time,
.cover__lang,
.cover__lead,
.cover__cta,
.hback,
.topbar__burger,
.topbar__sep {
  animation: cover-rise var(--reveal-ms) var(--ease-out-strong) backwards;
}

.cover .w__in {
  animation: cover-word var(--word-ms) var(--ease-reveal) backwards;
}

/* Порядок: титул → подписи и кнопки → шапка. Шапка заходит последней,
   на хвосте основной анимации, и сама идёт цепочкой слева направо:
   «написать мне» → часы → язык. */
.cover__name .w:nth-child(1) .w__in { animation-delay: 100ms; }
.cover__name .w:nth-child(2) .w__in { animation-delay: 180ms; }
.cover__role .w:nth-child(1) .w__in { animation-delay: 260ms; }
.cover__role .w:nth-child(2) .w__in { animation-delay: 340ms; }

.cover__lead--left  { animation-delay: 400ms; }
.cover__cta--tg     { animation-delay: 440ms; }
.cover__cta--cv     { animation-delay: 490ms; }
.cover__lead--right { animation-delay: 540ms; }

.cover__contact     { animation-delay: 660ms; }
.cover__time        { animation-delay: 730ms; }
.cover__lang        { animation-delay: 800ms; }
.topbar__burger     { animation-delay: 800ms; }

/* На кейсе титула нет, ждать нечего — шапка заходит сразу и в том же
   порядке: «назад» → «написать мне» → часы → язык. */
.hback              { animation-delay:  40ms; }
.topbar__sep        { animation-delay:  90ms; }
.page-case .cover__contact { animation-delay: 140ms; }
.page-case .cover__time    { animation-delay: 210ms; }
.page-case .cover__lang    { animation-delay: 280ms; }

/* ---------- закреплённая шапка ---------- */

/* Полоса во всю ширину окна, содержимое внутри — на общей сетке.
   Подложка должна закрывать всю ширину, а не только колонку контента:
   на широком мониторе под шапку заезжают светлые скриншоты кейсов. */
.topbar {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 20;
  width: 100%;
  pointer-events: none;
}

/* Подложка кнопки выступает на 4px вокруг содержимого, поэтому полоса шире
   контентной колонки ровно на эти 4px с каждой стороны: аватар, часы и глобус
   встают по --gutter, а подложка честно висит снаружи — без отрицательных
   полей, которые раньше давали 4px мнимого переполнения. */
.topbar__in {
  --edge: calc(var(--gutter) - 4px);
  position: relative;
  width: min(100% - var(--edge) * 2, var(--page-max) - var(--edge) * 2);
  margin-inline: auto;
  padding-block: clamp(14px, 1.111vw, 21px);   /* 16 */
  pointer-events: none;
}

.topbar__in > * { pointer-events: auto; }

.topbar__right {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
}

/* Часы на 4px ниже кнопок — так они выравниваются по содержимому,
   а не по подложке кнопки. */
.cover__time { margin-top: 4px; }

.topbar::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(180deg,
              rgba(0, 0, 0, 0.92) 0%,
              rgba(0, 0, 0, 0.72) 55%,
              rgba(0, 0, 0, 0) 100%);
  opacity: 0;
  transition: opacity 300ms var(--ease-out-soft);
  pointer-events: none;
}

[data-scrolled] .topbar::before { opacity: 1; }

.cover__contact {
  gap: var(--sp-20);
  padding: 4px var(--sp-20) 4px 4px;
}

.cover__lang {
  gap: clamp(16px, 1.25vw, 24px);
  padding: 4px 4px 4px var(--sp-20);
}

/* ===================== 7. ЛЮБИМЫЕ КЕЙСЫ / 207:6883 =====================
   Кейсы лежат в потоке. Область ховера — весь бокс кейса, от своей
   пунктирной линии до следующей, поэтому отступы входят в него, а не
   разделяют соседей margin-ом. */

.cases { margin-top: var(--gap-foot); }

.case {
  display: flex;
  gap: 1.4286%;   /* 20 из 1400 */
  padding: var(--sp-21) 0 var(--sp-20);
}

/* у последнего снизу линии нет — и запаса под неё тоже */
.case:last-child { padding-bottom: 0; }

.case__rule {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 0;
  margin: 0;
  border: 0;
  border-top: 1px dashed rgba(255, 255, 255, 0.15);
}

/* 803×485 из макета: ширина долей контента, высота — пропорцией */
.case__media {
  position: relative;
  flex: 0 0 57.357%;
  aspect-ratio: 803 / 485;
  border-radius: var(--r-8);
  overflow: hidden;
  background: var(--c-panel);
}

/* Рамка задана на контейнере, а не на картинке: overflow сам считает
   внутренний радиус, вручную его подбирать не нужно. Размеры и положение
   экранов — долями кадра, поэтому вся композиция тянется целиком. */
.case__shot {
  position: absolute;
  display: block;
  border: 5px solid rgba(255, 255, 255, 0.2);
  border-radius: clamp(20px, 1.806vw, 34px);
  overflow: hidden;
}

/* Кейс под курсором становится цветным, остальные остаются чёрно-белыми.
   Класс, а не :hover: браузер не пересчитывает :hover, пока курсор стоит
   на месте, — при скролле колесом подсветка залипала бы на старом кейсе. */
.case__shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  filter: grayscale(1);
  transition: filter 500ms var(--ease-out-soft);
}

.case.is-hot .case__shot img { filter: grayscale(0); }

/* без JS остаётся обычный ховер — неточный при скролле, но лучше, чем ничего */
@media (hover: hover) and (pointer: fine) {
  html:not(.has-js) .case:hover .case__shot img { filter: grayscale(0); }
}

/* доли кадра 803×485; средний телефон приподнят — так в макете */
.case__shot--a { left: 10.212%; top: 8.041%; width: 23.661%; height: 84.742%; }
.case__shot--b { left: 38.231%; top: 7.423%; width: 23.661%; height: 84.742%; }
.case__shot--c { left: 66.252%; top: 8.041%; width: 23.661%; height: 84.742%; }

.case__shot--wide {
  left: 8.344%;
  top: 7.216%;
  width: 83.437%;
  height: 85.567%;
  border-radius: clamp(12px, 1.111vw, 21px);
}

/* ---------- текстовая колонка ---------- */
/* Кнопки прижаты к низу колонки, а колонка тянется за высотой превью —
   поэтому они всегда стоят вровень с нижней кромкой кадра. */

.case__col {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  min-width: 0;
}

.case__body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-25);
}

.case__head {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-15);
}

.case__meta {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
  margin: 0;
  font-family: var(--font-text);
  font-size: var(--fs-ui);
  line-height: 1;
  text-transform: uppercase;
  color: var(--c-text);
  white-space: nowrap;
}

/* точка-разделитель: в макете это кружок 3px цвета #D9D9D9 */
.case__dot {
  flex-shrink: 0;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: #d9d9d9;
}

.case__name {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-display-s);
  line-height: 1;
  color: var(--c-text);
  white-space: nowrap;
}

.case__name span { color: rgba(255, 255, 255, 0.4); }

.case__desc {
  width: 63.78%;   /* 368 из 577 */
  margin: 0;
  font-family: var(--font-text);
  font-size: var(--fs-body);
  line-height: 1.2;
  letter-spacing: 1px;
  color: rgba(255, 255, 255, 0.6);
}

.case__tags {
  display: flex;
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.case__tags li {
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--h-chip);
  padding-inline: var(--sp-10);
  border-radius: var(--r-4);
  background: var(--c-chip);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-ui);
  line-height: 1;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  white-space: nowrap;
}

.case__actions {
  display: flex;
  gap: 0.867%;   /* 5 из 577 */
}

.case__cta   { width: 33.796%; }   /* 195 из 577 */
.case__figma { width: 17.678%; }   /* 102 из 577 */

/* ---------- появление ----------
   Общие правила — в разделе 3. Тут только своё: левая часть заходит
   в два слоя, сначала оседает тёмная панель, затем в неё поднимаются
   экраны. Кривая с длинным хвостом, как у титула: для широкого кадра
   короткий сдвиг на 10px читается рывком. */

.case__media {
  opacity: 0;
  transform: scale(1.04);
  transition: opacity   var(--word-ms) var(--ease-reveal) 120ms,
              transform var(--word-ms) var(--ease-reveal) 120ms;
}

.case__shot {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity   var(--wipe-ms) var(--ease-reveal) var(--shot-delay, 0ms),
              transform var(--wipe-ms) var(--ease-reveal) var(--shot-delay, 0ms);
}

.case.is-in .case__media,
.case.is-in .case__shot {
  opacity: 1;
  transform: none;
}

.case__shot--a,
.case__shot--wide { --shot-delay: 220ms; }
.case__shot--b    { --shot-delay: 300ms; }
.case__shot--c    { --shot-delay: 380ms; }

/* лесенка правой колонки: мета → название → описание → теги → кнопки */
.case__meta    { --reveal-delay: 200ms; }
.case__name    { --reveal-delay: 260ms; }
.case__desc    { --reveal-delay: 320ms; }
.case__tags    { --reveal-delay: 380ms; }
.case__actions { --reveal-delay: 440ms; }

/* ====================== 8. МЕСТА РАБОТЫ / 207:7913 ======================
   Строки лежат в потоке: рост высоты на ховере должен сдвигать соседей вниз,
   как в макете. Внутренние координаты — долями строки, вертикальные —
   через --row-pad, его же читает script.js, когда считает высоту раскрытия. */

/* Registered custom properties: без @property это просто поток токенов,
   и getComputedStyle отдал бы скрипту строку «clamp(...)», а не пиксели —
   script.js читает их, когда считает высоту раскрытой строки. */
@property --row-pad { syntax: '<length>'; inherits: true; initial-value: 15px; }
@property --row-h   { syntax: '<length>'; inherits: true; initial-value: 85px; }

/* Верх описания внутри строки. На десктопе он совпадает с полем, на мобильном
   описание уезжает под должность и дату — script.js складывает высоту
   раскрытия именно от него. */
@property --row-desc-top { syntax: '<length>'; inherits: true; initial-value: 15px; }

.work {
  --row-pad: var(--sp-15);
  --row-h: clamp(76px, 5.903vw, 113px);   /* 85 */
  --row-desc-top: var(--row-pad);
  margin-top: var(--gap-section);
}

.work__list {
  margin: 0 auto;
  padding: 0;
  list-style: none;
}

.work__row {
  position: relative;
  height: var(--row-h);
}

.work__rule {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 1px;
  background: rgba(255, 255, 255, 0.1);
}

/* Заливка отдельным слоем, а не background строки: её нужно раскрывать
   сверху вниз, а цвет фона так не анимируется. */
.work__fill {
  position: absolute;
  inset: 0;
  background: var(--c-accent);
  transform: scaleY(0);
  transform-origin: top center;
  transition: transform 600ms var(--ease-cubic);
}

.work__company,
.work__role,
.work__dates {
  position: absolute;
  margin: 0;
  transition: color 450ms var(--ease-cubic),
              transform 450ms var(--ease-cubic);
}

.work__company {
  top: var(--row-pad);
  left: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-company);
  line-height: 1;
  color: var(--c-text);
  white-space: nowrap;
}

.work__role,
.work__dates {
  left: 20.571%;   /* 288 из 1400 */
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-ui);
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
}

.work__role  { top: var(--row-pad); color: var(--c-text); }

.work__dates {
  top: calc(var(--row-pad) + var(--sp-24));
  color: rgba(255, 255, 255, 0.4);
}

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

   Обрезку нельзя вешать на саму строку: она срезала бы фотографию,
   которая вылезает за строку вверх и вниз. */
@property --fade-h {
  syntax: '<length>';
  inherits: false;
  initial-value: 34px;
}

.work__descbox {
  position: absolute;
  top: var(--row-desc-top);
  bottom: var(--row-pad);
  left: var(--col-2);
  width: 28.571%;   /* 400 из 1400 */
  overflow: hidden;
  -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - var(--fade-h)), transparent 100%);
          mask-image: linear-gradient(180deg, #000 calc(100% - var(--fade-h)), transparent 100%);
  transition: --fade-h 200ms var(--ease-cubic);
}

.work__desc {
  margin: 0;
  font-family: var(--font-text);
  font-size: var(--fs-ui);
  line-height: 1.2;
  letter-spacing: 1px;
  color: rgba(255, 255, 255, 0.4);
  transition: color 600ms var(--ease-cubic);
}

/* Фотография: правым краем упирается в текст, по вертикали всегда по центру
   строки — поэтому при росте строки не дёргается. Раскрывается маской сверху. */
.work__photo {
  position: absolute;
  top: 50%;
  left: 43.571%;    /* 610 из 1400 */
  z-index: 2;
  width: 13.571%;   /* 190 из 1400 */
  aspect-ratio: 190 / 268;
  transform: translateY(-50%);
  background: #fff;
  opacity: 0;
  clip-path: inset(0 0 100% 0);
  transition: clip-path 600ms var(--ease-cubic),
              opacity 600ms var(--ease-cubic);
  pointer-events: none;
}

.work__photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 57% center;
  mix-blend-mode: luminosity;
}

/* ---------- ховер строки ---------- */
/* .is-hot вешает script.js, а не :hover: браузер не пересчитывает :hover,
   пока курсор стоит на месте, и при скролле колесом раскрытой оставалась бы
   строка, которая уже уехала.

   Одна декларация transition на строку: и рост высоты на ховере,
   и всплытие при появлении — иначе поздняя перебила бы раннюю. */
.work__row {
  z-index: 0;
  opacity: 0;
  transform: translateY(10px);
  transition: height 600ms var(--ease-cubic),
              opacity   var(--reveal-ms) var(--ease-out-strong) var(--reveal-delay, 0ms),
              transform var(--reveal-ms) var(--ease-out-strong) var(--reveal-delay, 0ms);
}

.work.is-in .work__row {
  opacity: 1;
  transform: none;
}

/* Высота раскрытия у каждой строки своя — --row-pad, текст, --row-pad.
   Считает script.js: в CSS высоту описания не узнать, а height: auto
   не анимируется; при смене ширины окна пересчитывает заново.

   Вход быстрый, выход неспешный — как на dragonfly.xyz: строка отзывается
   сразу, а отпускает лениво. */
.work__row.is-hot {
  z-index: 1;
  height: var(--open-h, 132px);
  transition: height 380ms var(--ease-cubic),
              opacity   var(--reveal-ms) var(--ease-out-strong),
              transform var(--reveal-ms) var(--ease-out-strong);
}

/* Переход курсора на соседнюю строку. Уходящая строка обязана схлопываться
   ровно в темп раскрывающейся: при 600 мс против 380 мс сумма высот не
   монотонна — нижний край раскрытой строки (а с ним и край заливки) уходит
   на 13px ниже своего места и потом возвращается. Ленивый выход в 600 мс
   остаётся для ухода с блока целиком, где схлопывание ни с чем не спорит. */
.work__list:has(.work__row.is-hot) .work__row:not(.is-hot) {
  transition-duration: 380ms, var(--reveal-ms), var(--reveal-ms);
}

.work__row.is-hot .work__fill {
  transform: scaleY(1);
  transition-duration: 220ms;
}

.work__row.is-hot .work__company {
  transform: translateX(var(--row-pad));
  color: #000;
}

.work__row.is-hot .work__role   { color: #000; }
.work__row.is-hot .work__dates  { color: rgba(0, 0, 0, 0.5); }

.work__row.is-hot .work__desc {
  color: #000;
  transition-duration: 450ms;
}

/* Кромка растворения убирается на входе неспешно, а возвращается быстро:
   при схлопывании край должен смягчиться раньше, чем строка догонит. */
.work__row.is-hot .work__descbox {
  --fade-h: 0px;
  transition-duration: 450ms;
}

/* с небольшой задержкой, чтобы фотография шла следом за заливкой */
.work__row.is-hot .work__photo {
  opacity: 1;
  clip-path: inset(0);
  transition: clip-path 460ms var(--ease-cubic) 80ms,
              opacity 460ms var(--ease-cubic) 80ms;
}

/* ---------- появление ---------- */

.work__row:nth-child(1) { --rule-delay: 280ms; --reveal-delay: 330ms; }
.work__row:nth-child(2) { --rule-delay: 340ms; --reveal-delay: 390ms; }
.work__row:nth-child(3) { --rule-delay: 400ms; --reveal-delay: 450ms; }
.work__row:nth-child(4) { --rule-delay: 460ms; --reveal-delay: 510ms; }

/* ======================= 9. ЕЩЁ ПРОЕКТЫ / 229:8226 =======================
   Сетка не регулярная: колонки ровные, а по вертикали карточки разнесены
   по трём уровням и в каждой колонке стоят по-своему. Раньше это был
   абсолют по координатам макета и заданная руками высота секции.

   Теперь это грид 4×6: чётные ряды — карточки, нечётные — проставки
   (21 / 42 / 20 из макета), карточка занимает свою клетку. Высота секции
   складывается сама, а колонки и карточки тянутся вместе с контентом. */

.more { margin-top: var(--gap-section); }

/* margin-inline: auto — .wrap центрирует им, краткая форма его бы обнулила.
   -1px снизу: линия нарисована бордюром, и без этого она заняла бы в потоке
   свой пиксель и сдвинула всю сетку вниз. */
.more__rule {
  height: 0;
  margin: 0 auto -1px;
  border: 0;
  border-top: 1px solid rgba(255, 255, 255, 0.15);
  --rule-delay: 260ms;
}

.more__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows:
    clamp(19px, 1.458vw, 28px)  auto      /* 21 */
    clamp(37px, 2.917vw, 56px)  auto      /* 42 */
    clamp(18px, 1.389vw, 27px)  auto;     /* 20 */
  column-gap: 1.4286%;   /* 20 из 1400 */
  margin-block: 0;   /* не margin: 0 — .wrap центрирует через margin-inline */
  padding: 0;
  list-style: none;
}

/* li — только контейнер списка; в сетку встают сами карточки */
.more__grid > li { display: contents; }

/* ---------- карточка ---------- */

.pcard {
  position: relative;
  display: block;
  aspect-ratio: 335 / 420;
  border-radius: var(--r-8);
  overflow: hidden;
  background: #1c1c1d;
}

/* Подложка своя у каждой карточки: у роликов и Despath медиа не закрывает
   кадр целиком, и по краям видно именно её. */
.pcard__media {
  position: absolute;
  inset: 0;
  border-radius: var(--r-8);
  overflow: hidden;
  background: var(--bg);
}

/* Кадрирование задано долями карточки теми же пропорциями, что в макете:
   где-то медиа упирается в кадр по высоте и режется по бокам, где-то
   вписано по ширине и открывает подложку сверху и снизу. */
.pcard__media img,
.pcard__media video {
  position: absolute;
  top: var(--my);
  left: var(--mx);
  display: block;
  width: var(--mw);
  height: var(--mh);
  object-fit: cover;
  filter: grayscale(1);
  transition: filter 500ms var(--ease-out-soft);
}

.pcard.is-hot .pcard__media img,
.pcard.is-hot .pcard__media video { filter: grayscale(0); }

/* Затемнение сверху и снизу: держит заголовок и плашки читаемыми
   на любой картинке, включая белые ролики. */
.pcard__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg,
              rgba(0, 0, 0, 0.8) 19.712%,
              rgba(0, 0, 0, 0) 51.442%,
              rgba(0, 0, 0, 0.8) 100%);
  pointer-events: none;
}

.pcard__title {
  position: absolute;
  top: var(--sp-15);
  left: var(--sp-15);
  right: var(--sp-15);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-family: var(--font-display);
  font-size: var(--fs-card);
  line-height: 1.2;
  color: var(--c-text);
  transition: color 500ms var(--ease-out-soft);
}

.pcard.is-hot .pcard__title { color: var(--c-accent); }

.pcard__date,
.pcard__badge {
  position: absolute;
  bottom: var(--sp-15);
  display: flex;
  align-items: center;
  justify-content: center;
  height: var(--h-chip);
  border-radius: var(--r-4);
  background: var(--c-glass-strong);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
}

.pcard__date {
  left: var(--sp-15);
  padding-inline: var(--sp-10);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-ui);
  line-height: 1;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  white-space: nowrap;
}

.pcard__badge {
  right: var(--sp-15);
  width: var(--h-chip);
}

.pcard__badge img { width: 60%; height: auto; }

/* ---------- раскладка карточек ----------
   клетка грида        — где карточка стоит
   --bg                — подложка под медиа
   --mx/--my/--mw/--mh — кадрирование медиа, долями карточки 335×420
   --reveal-delay      — по колонке, чтобы соседи в ряду заходили волной
                         слева направо; между рядами задержки не нужны —
                         ряд появляется тогда, когда до него доскроллили. */

.pcard--telegram     { grid-column: 1; grid-row: 2; --reveal-delay:   0ms; --bg: var(--c-panel); --mx: -34.03%; --my:     0; --mw: 167.76%; --mh: 100.24%; }
.pcard--cryptowallet { grid-column: 1; grid-row: 4; --reveal-delay:   0ms; --bg: var(--c-panel); --mx: -33.43%; --my:     0; --mw: 166.57%; --mh:  99.76%; }

.pcard--rocketar     { grid-column: 2; grid-row: 4; --reveal-delay:  60ms; --bg: #ffffff;        --mx:   0.30%; --my: 20.24%; --mw:  99.70%; --mh:  59.52%; }
.pcard--rosatom      { grid-column: 2; grid-row: 6; --reveal-delay:  60ms; --bg: var(--c-panel); --mx: -12.84%; --my:     0; --mw: 125.37%; --mh: 100.00%; }

.pcard--natryapki    { grid-column: 3; grid-row: 2; --reveal-delay: 120ms; --bg: #ffffff;        --mx:   0.30%; --my: 20.48%; --mw:  99.70%; --mh:  58.81%; }
.pcard--lectures     { grid-column: 3; grid-row: 6; --reveal-delay: 120ms; --bg: var(--c-panel); --mx:-111.64%; --my:     0; --mw: 223.28%; --mh: 118.57%; }

.pcard--bitarmor     { grid-column: 4; grid-row: 4; --reveal-delay: 180ms; --bg: #181818;        --mx:   0.30%; --my: 20.24%; --mw:  99.70%; --mh:  59.52%; }
.pcard--despath      { grid-column: 4; grid-row: 6; --reveal-delay: 180ms; --bg: #1c1c1c;        --mx:      0;  --my: 21.90%; --mw: 100.30%; --mh:  75.24%; }

/* ========================== 10. ФУТЕР / 207:7011 ==========================
   Верхняя половина — двухколонник: слева подпись, ссылка и копирайт,
   справа колонки ссылок и часы. Копирайт и часы прижаты к низу колонок,
   поэтому при росте кегля обе стороны остаются выровненными.
   Снизу — пиксельный рисунок во всю ширину карточки. */

.foot {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-20);
  margin-top: var(--gap-foot);
  padding: var(--sp-40) var(--sp-20) clamp(20px, 1.597vw, 31px);
  border-radius: var(--r-8);
  background: var(--c-panel);
}

.foot__top {
  display: grid;
  grid-template-columns: var(--col-2-foot) 1fr;
  min-height: clamp(251px, 19.583vw, 376px);   /* 282 */
}

.foot__side {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--sp-20);
}

.foot__label,
.foot__copy {
  margin: 0;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-ui);
  line-height: 1.3;
  text-transform: uppercase;
  color: var(--c-text);
}

.foot__copy { color: rgba(255, 255, 255, 0.4); }

.foot__tg {
  position: relative;
  display: inline-block;
  margin-top: var(--sp-5);
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-display-l);
  line-height: 1;
  color: var(--c-accent);
  white-space: nowrap;
  transition: color 300ms var(--ease-cubic);
}

/* подчёркивание выезжает слева направо, цвет не меняется */
.foot__tg::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.13em;   /* ниже выносной у «p», иначе линия режет букву */
  height: 3px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 420ms var(--ease-out-soft);
}

@media (hover: hover) and (pointer: fine) {
  .foot__tg:hover::after { transform: scaleX(1); }
}

.foot__tg:focus-visible::after { transform: scaleX(1); }

/* ---------- колонки ссылок ---------- */

.foot__cols {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-95);
}

.foot__col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-20);
}

.foot__coltitle {
  margin: 0;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-ui);
  line-height: 1.3;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.4);
  white-space: nowrap;
}

.foot__list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-10);
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--font-text);
  font-size: var(--fs-ui);
  line-height: 1.2;
  letter-spacing: 1px;
  color: var(--c-text);
}

.foot__list a {
  display: inline-block;
  white-space: nowrap;
  transition: color 300ms var(--ease-cubic),
              transform 300ms var(--ease-cubic);
}

@media (hover: hover) and (pointer: fine) {
  .foot__list a:hover {
    color: var(--c-accent);
    transform: translateX(6px);
  }
}

/* ---------- пиксельный рисунок ---------- */
/* Размер держит CSS, шаг сетки dots.js считает от него: сетка 53×16
   остаётся та же, а точки разъезжаются вместе с шириной карточки. */
.foot__dots {
  display: block;
  width: 100%;
  aspect-ratio: 1360 / 404;
}

/* слева направо и сверху вниз: подпись → ссылка → колонки → время → рисунок */
.foot__label            { --reveal-delay:   0ms; }
.foot__tg               { --reveal-delay:  70ms; }
.foot__col:nth-child(1) { --reveal-delay: 140ms; }
.foot__col:nth-child(2) { --reveal-delay: 200ms; }
.foot__col:nth-child(3) { --reveal-delay: 260ms; }
.foot__time             { --reveal-delay: 320ms; }
.foot__copy             { --reveal-delay: 380ms; }
.foot__dots             { --reveal-delay: 440ms; }

/* ==================== 11. МОДАЛКА / 229:8303 ==================== */

.modal {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal[hidden] { display: none; }

.modal__overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  opacity: 0;
  transition: opacity 320ms var(--ease-cubic);
}

/* По 30px сверху и снизу от края экрана — высота панели тянется за окном,
   ширина за макетом (1200 при 1440), но не шире 1600: на очень широком
   мониторе кадр всё равно ограничен высотой окна. */
.modal__panel {
  position: relative;
  width: clamp(960px, 83.333vw, 1600px);
  height: calc(100vh - 60px);
  max-height: 1100px;
  border-radius: var(--r-8);
  overflow: hidden;
  background: var(--c-panel);
  opacity: 0;
  transform: translateY(16px) scale(0.985);
  transition: opacity 380ms var(--ease-cubic),
              transform 380ms var(--ease-cubic);
}

.modal.is-open .modal__overlay { opacity: 1; }

.modal.is-open .modal__panel {
  opacity: 1;
  transform: none;
}

/* Прокрутка внутри панели. Полоса 3px с закруглением — как в макете;
   нативная, чтобы её можно было таскать, а не только смотреть. */
.modal__scroll {
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.2) transparent;
}

.modal__scroll::-webkit-scrollbar { width: 3px; }
.modal__scroll::-webkit-scrollbar-track { background: transparent; }

.modal__scroll::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.2);
  border-radius: 100px;
}

/* ---------- шапка ---------- */

/* Липкая: в макете при прокрутке медиа уезжает под неё, а шапка остаётся.
   Наверху она прозрачная (видно фон панели), а как только контент поехал —
   получает затемнение с блюром, чтобы заголовок не смешивался с картинкой.

   Высота не фиксирована: заголовок в модалке показывается целиком и может
   встать в две строки — шапка растёт под него. */
.modal__header {
  position: sticky;
  top: 0;
  z-index: 2;
  padding: 30px 100px 30px 30px;
  background: transparent;
  transition: background-color 320ms var(--ease-cubic),
              backdrop-filter 320ms var(--ease-cubic);
}

.modal__scroll.is-scrolled .modal__header {
  background: rgba(0, 0, 0, 0.25);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

.modal__date {
  display: inline-flex;
  align-items: center;
  margin: 0 0 15px;
  padding: 8px 10px;
  border-radius: var(--r-4);
  background: var(--c-chip);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: var(--fs-body);
  line-height: 1;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.6);
  white-space: nowrap;
}

/* Целиком, без многоточия — обрезка нужна только в карточках. */
.modal__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--fs-display-m);
  line-height: 1.2;
  color: var(--c-text);
  text-wrap: pretty;
}

.modal__close {
  position: absolute;
  top: 15px;
  right: 15px;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 100px;
  background: var(--c-chip);
  color: var(--c-text);
  transition: transform 420ms var(--ease-cubic),
              background-color 420ms var(--ease-cubic);
}

/* крестик доворачивается против часовой */
@media (hover: hover) and (pointer: fine) {
  .modal__close:hover {
    transform: rotate(-90deg);
    background: var(--c-accent);
  }
}

.modal__close:focus-visible {
  transform: rotate(-90deg);
  background: var(--c-accent);
}

/* ---------- область медиа ---------- */

/* Тянется за высотой панели: отступы + шапка. Иначе на высоком экране
   под медиа висела бы пустота. */
/* isolation: панель управления внутри лежит с z-index 3 и без своего
   контекста наложения оказывалась выше липкой шапки (z-index 2) —
   при прокрутке кнопки проезжали поверх заголовка. */
.modal__media {
  position: relative;
  isolation: isolate;
  height: clamp(320px, calc(100vh - 247px), 900px);
  margin: 0 30px;
  border-radius: var(--r-8);
  overflow: hidden;
  background: var(--c-chip);
}

.modal__stage {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal__stage img,
.modal__stage video {
  display: block;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* Картинку можно приближать: тянем за неё же, поэтому курсор-рука. */
.modal__stage--zoom { cursor: grab; }
.modal__stage--zoom.is-panning { cursor: grabbing; }

.modal__stage img {
  transform: scale(var(--zoom, 1)) translate(var(--px, 0px), var(--py, 0px));
  transform-origin: center;
  transition: transform 220ms var(--ease-cubic);
  will-change: transform;
}

.modal__stage.is-panning img { transition: none; }

/* ---------- панель управления просмотрщика ---------- */

/* Тёмная, а не светлая как остальное стекло сайта: панель лежит поверх
   произвольного кадра, и на белых роликах светлая была не видна вовсе. */
.modal__tools {
  position: absolute;
  bottom: 20px;
  left: 50%;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 4px;
  height: 40px;
  padding: 0 6px;
  border-radius: 100px;
  background: rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  transform: translateX(-50%);
}

.modal__tools[hidden] { display: none; }

.tool {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 100px;
  color: var(--c-text);
  transition: background-color 200ms var(--ease-cubic),
              color 200ms var(--ease-cubic);
}

@media (hover: hover) and (pointer: fine) {
  .tool:hover { background: rgba(255, 255, 255, 0.2); }
}

.tool:disabled {
  opacity: 0.35;
  cursor: default;
}

.tool--wide {
  width: auto;
  padding: 0 10px;
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 13px;
  line-height: 1;
  color: rgba(255, 255, 255, 0.85);
  font-variant-numeric: tabular-nums;
}

/* дорожка перемотки видео */
.scrub {
  position: relative;
  width: 320px;
  height: 28px;
  display: flex;
  align-items: center;
  cursor: pointer;
  touch-action: none;
}

.scrub__track {
  position: relative;
  width: 100%;
  height: 3px;
  border-radius: 100px;
  background: rgba(255, 255, 255, 0.3);
}

.scrub__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--p, 0%);
  border-radius: 100px;
  background: var(--c-accent);
}

/* Видимый ползунок: без него непонятно, что дорожку можно тащить. */
.scrub__knob {
  position: absolute;
  top: 50%;
  left: var(--p, 0%);
  width: 11px;
  height: 11px;
  border-radius: 100px;
  background: var(--c-accent);
  transform: translate(-50%, -50%);
  transition: transform 160ms var(--ease-cubic);
  pointer-events: none;
}

.scrub:hover .scrub__knob,
.scrub.is-dragging .scrub__knob { transform: translate(-50%, -50%) scale(1.25); }

/* ---------- текст под медиа ---------- */

.modal__text {
  width: min(600px, 50%);
  margin: 20px auto 0;
  padding-bottom: 30px;
  font-family: var(--font-text);
  font-size: var(--fs-lead);
  line-height: 1.45;
  letter-spacing: 1px;
  color: var(--c-text);
  white-space: pre-wrap;
}

.modal__text[hidden] { display: none; }

/* Остальные ролики проекта — простой стопкой под основным кадром, во всю
   ширину панели. Свой просмотрщик им не нужен: перемотка и пауза есть
   у главного, а эти просто крутятся, пока их видно. */
.modal__extra {
  display: grid;
  gap: 20px;
  margin: 20px 30px 0;
}

.modal__extra[hidden] { display: none; }

.modal__extra video {
  width: 100%;
  border-radius: var(--r-8);
  background: var(--c-chip);
  cursor: pointer;
}

/* Без текста медиа не должно упираться в низ панели. Через :has(), а не
   :last-child: блок текста всегда лежит в разметке, просто скрыт, — поэтому
   медиа последним элементом не бывает никогда. */
.modal__scroll:has(.modal__text[hidden]):has(.modal__extra[hidden]) .modal__media { margin-bottom: 30px; }
.modal__scroll:has(.modal__text[hidden]) .modal__extra { margin-bottom: 30px; }

/* ==================== 12. СТРАНИЦА КЕЙСА / 259:2571 ====================

   Колонка кейса — 1200 из 1440 в макете. Выше 1440 она не растёт: у статьи
   должна остаться читаемая мера, поэтому и кегли внутри фиксированные,
   а не в vw, как на главной. Тянется только хром — шапка и футер. */

/* Колонка статьи тянется по макетной пропорции: 1200 из 1440 — это 83.33vw.
   Ниже 1440 она сжимается вместе с полями (на 1280 остаётся 1067 и поля
   по 107), выше — замирает на 1200. Текстовые блоки внутри держат свой
   размер и просто центрируются: сжимаются только кадры во всю колонку. */
.wrap--case { width: min(83.333vw, 1200px); }

/* Отбивка до футера снята с макета: она короче общей --gap-foot, а задаётся
   на самом футере — иначе поля соседей схлопываются и побеждает большее. */
.page-case .foot { margin-top: 181px; }

/* ---------- «назад» в шапке ---------- */

.topbar__left {
  display: flex;
  align-items: flex-start;
}

.hback {
  gap: var(--sp-20);
  padding: 4px var(--sp-20) 4px 4px;
}

/* Разделитель стоит на --gutter слева от аватара; слева от него —
   компенсация 4px подложки соседней кнопки. */
.topbar__sep {
  flex-shrink: 0;
  width: 1px;
  height: var(--sz-photo);
  margin-top: 4px;
  margin-inline: calc(var(--gutter) - 4px);
  background: var(--c-rule);
}

/* ---------- шапка кейса ---------- */

.chero { padding-top: 160px; }

.chero__icon {
  width: 100px;
  height: 100px;
  margin-inline: auto;
}

/* Кегль фиксированный: колонка статьи не растёт, и от плавающего кегля
   весь блок ниже ехал бы по вертикали. Шрифт текстовый, а не моно —
   в шапке кейса это Inter Tight капсом, в отличие от хрома страницы. */
.chero__meta {
  margin: 38px 0 0;
  font-family: var(--font-text);
  font-size: 14px;
  text-transform: uppercase;
  text-align: center;
}

/* Точка-разделитель стоит на увеличенных пробелах — как в макете. */
.chero__sep { margin-inline: 6px; }

.chero__title {
  margin: 9px 0 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 52px;
  line-height: 1;
  text-align: center;
}

.chero__grey { color: var(--c-text-muted); }

/* ---------- блоки статьи ---------- */

/* Кегли внутри статьи фиксированные — колонка не тянется (см. .wrap--case).
   Заголовок набран дисплейным шрифтом в 45 с межстрочным в единицу:
   в макете строки стоят вплотную, ровно на кегль. */
.cblock { margin-top: 140px; }
.cblock--role    { margin-top: 150px; }
.cblock--process { margin-top: 140px; }

.ctitle {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 45px;
  line-height: 1;
  text-align: center;
}

/* Подзаголовок разрежен: в макете у него заметный трекинг, без него
   строка становится короче макетной на полсотни пикселей. */
.csub {
  margin: 16px 0 0;
  font-family: var(--font-text);
  font-size: 18px;
  line-height: 22px;
  letter-spacing: 0.056em;
  color: rgba(255, 255, 255, 0.5);
  text-align: center;
}

/* Плашка раздела */
.ctag {
  display: flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  height: 30px;
  margin: 0 auto 15px;
  padding-inline: 11px;
  border-radius: var(--r-4);
  background: var(--c-accent);
  font-family: var(--font-mono);
  font-weight: 500;
  font-size: 14px;
  text-transform: uppercase;
  line-height: 1;
}

/* ---------- панель «контекст / проблема» ---------- */

/* Разделитель — отдельный элемент, а не border колонки: в макете он на 15px
   короче карточки с обеих сторон, то есть заходит в её падинги. */
.panel {
  position: relative;
  display: grid;
  grid-template-columns: 349px 349px;
  column-gap: 40px;
  width: 800px;
  /* Высота по контенту: в макете плашка была заметно выше текста, и снизу
     оставалась дыра в сотню пикселей. Отступы сверху и снизу теперь равны. */
  margin: 50px auto 0;
  padding: 24px 30px 24px;
  border: 1px solid rgba(255, 255, 255, 0.05);
  border-radius: var(--r-6);
  background: var(--c-chip);
}

.panel__rule {
  position: absolute;
  top: 15px;
  bottom: 15px;
  left: 50%;
  width: 1px;
  background: rgba(255, 255, 255, 0.05);
}

.panel__head {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 32px;
  line-height: 1;
}

.panel__text {
  margin: 0;
  font-family: var(--font-text);
  font-size: 16px;
  line-height: 24px;
}

.panel__head + .panel__text { margin-top: 15px; }

/* ---------- роли ---------- */

/* Колонка 240 и шаг 272 сняты с макета: иконки трёх колонок стоят
   ровно по центрам, средняя — по центру страницы. */
.roles {
  display: grid;
  grid-template-columns: repeat(3, 240px);
  justify-content: center;
  column-gap: 32px;
  row-gap: 40px;
  margin: 49px 0 0;
  padding: 0;
  list-style: none;
}

.role { text-align: center; }

/* Иконки взяты растром из макета: набор в проекте не заведён, а рисовать
   их заново — это девять почти-совпадений вместо точного попадания.
   Подложка сплошная: в макете это 5% белого поверх #080808. */
.role__ico {
  box-sizing: content-box;
  display: block;
  width: 24px;
  height: 24px;
  margin-inline: auto;
  padding: 9px;
  border-radius: var(--r-4);
  background: var(--c-panel);
}

.role p {
  margin: 10px 0 0;
  font-family: var(--font-text);
  font-size: 14.25px;
  line-height: 17px;
}

/* ---------- пара кадров «до / после» ---------- */

/* Просвет в 6px — это подложка контейнера, а не фон страницы: в макете
   между кадрами видна та же плашка, что под карточками. */
.cframes {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 6px;
  margin-top: 49px;
  border-radius: var(--r-6);
  background: var(--c-chip);
  overflow: hidden;
}

.cframes img {
  width: 100%;
  height: auto;
}

/* Подпись под большим кадром — тот же стиль, что у подзаголовков блоков. */
.cnote { margin-top: 51px; }

/* ---------- карточки дизайн-этапов ---------- */

.stages {
  display: grid;
  grid-template-columns: repeat(2, 410px);
  justify-content: center;
  gap: 40px 21px;
  margin: 49px 0 0;
  padding: 0;
  list-style: none;
}

/* Высота фиксированная: в макете все шесть карточек одинаковые, а кадр
   внутри подрезается их краем — при высоте по контенту резать было бы нечем. */
.stage {
  position: relative;
  height: 600px;
  padding: 25px 30px 0;
  border-radius: var(--r-6);
  background: var(--c-chip);
  overflow: hidden;
}

.stage__head {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 32px;
  line-height: 38px;
}

.stage__text {
  margin: 9px 0 0;
  font-family: var(--font-text);
  font-size: 16px;
  line-height: 24px;
}

.stage__text a {
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
  transition: color 200ms ease;
}

@media (hover: hover) and (pointer: fine) {
  .stage__text a:hover { color: var(--c-accent); }
}

/* Последняя карточка — заглушка: текст приглушён, вместо кадра подпись. */
.stage--soon :is(.stage__head, .stage__text) { color: var(--c-text-muted); }

/* Одиночный кадр — в карточках с графиком и заглушкой. */
.stage__shot {
  position: absolute;
  top: 305px;
  left: 20px;
  width: 390px;
}

/* Стопка кадров. Рамка — узел из макета: подложка 10% белого со скруглением
   14, внутри мокап с отступом 10. Собрана в CSS, а не запечена в картинку:
   в исходной секции Figma та же рамка лежит на сером холсте, и её полупрозрачная
   подложка при экспорте вбирает этот серый — по краям кадра лезли светлые
   артефакты. Так в картинке остаётся только сам скриншот. */
.stage__shots {
  position: absolute;
  top: 305px;
  left: 20px;
  width: 410px;
  height: 298px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}

.stage--analysis .stage__shots { top: 319px; }

/* Кадры сменяют друг друга встык, без перехода: в макете это щелчок,
   а не растворение. */
.stage__frame {
  position: absolute;
  top: 10px;
  left: 10px;
  width: 400px;
  opacity: 0;
}

.stage__frame.is-on { opacity: 1; }

.stage__shot--chart {
  top: 343px;
  left: 30px;
  width: 350px;
}

.stage__shot--soon {
  top: 304px;
  left: 122px;
  width: 178px;
}

/* ---------- кадры блока ---------- */

.cshots {
  display: grid;
  gap: 20px;
  margin-top: 49px;
}

.cshots img {
  width: 100%;
  height: auto;
  border-radius: var(--r-6);
}

/* ---------- слайдер ---------- */

/* Кадр 16:9 задан пропорцией, а не высотой: колонка на 1280 чуть уже 1200,
   и высота должна ехать за шириной, иначе картинка вылезет из рамки. */
.slider {
  position: relative;
  margin-top: 40px;
}

.slider__view {
  border-radius: var(--r-6);
  overflow: hidden;
  background: var(--c-chip);
}

.slider__track {
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
  transition: transform 620ms var(--ease-out-strong);
}

.slider__slide {
  flex: 0 0 100%;
  min-width: 0;
}

.slider__slide img {
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* Стрелки и индикаторы лежат на кадре, отступ от края — 15px по макету. */
.slider__nav {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.15);
  backdrop-filter: var(--blur);
  -webkit-backdrop-filter: var(--blur);
  color: var(--c-text);
  transition: background-color 200ms ease, scale 160ms var(--ease-out-strong);
}

.slider__nav--prev { left: 15px; }
.slider__nav--next { right: 15px; rotate: 180deg; }

.slider__nav svg {
  width: 24px;
  height: 24px;
}

.slider__nav:active { scale: 0.94; }

@media (hover: hover) and (pointer: fine) {
  .slider__nav:hover { background: rgba(0, 0, 0, 0.4); }
}

.slider__dots {
  position: absolute;
  right: 15px;
  bottom: 15px;
  left: 15px;
  display: flex;
  gap: 10px;
}

/* Полоса-индикатор: их ровно столько, сколько слайдов, — собирает script.js. */
.slider__dot {
  flex: 1 1 0;
  height: 5px;
  border-radius: var(--r-4);
  background: rgba(255, 255, 255, 0.1);
  transition: background-color 300ms var(--ease-out-soft);
}

.slider__dot.is-on { background: var(--c-text); }

@media (hover: hover) and (pointer: fine) {
  .slider__dot:not(.is-on):hover { background: rgba(255, 255, 255, 0.32); }
}

/* ==================== 13. ДОСТУПНОСТЬ И ДВИЖЕНИЕ ==================== */

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

/* Движение убираем, проявление по прозрачности оставляем: оно помогает
   понять, что страница загрузилась, и не вызывает укачивания. */
@media (prefers-reduced-motion: reduce) {
  .cover__contact,
  .cover__time,
  .cover__lang,
  .cover__lead,
  .cover__cta,
  .hback,
  .topbar__sep,
  .cover .w__in {
    animation: cover-fade 240ms linear backwards;
  }

  /* Слайдер переключается мгновенно: кадр меняется, ничего не едет. */
  .slider__track { transition: none; }

  /* появление по скроллу выключаем целиком — всё сразу на месте.
     Селекторы те же, что в разделе 3, плюс элементы со своей анимацией. */
  [data-observe] [data-reveal],
  [data-observe] .w__in,
  [data-observe] [data-rule],
  [data-observe][data-reveal],
  [data-observe]:not(.is-in) [data-reveal],
  [data-observe]:not(.is-in) .w__in,
  [data-observe]:not(.is-in) [data-rule],
  .case__media,
  .case__shot,
  .work__row {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    clip-path: none !important;
  }

  @keyframes cover-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
  }

  *, *::before, *::after { transition-duration: 0.01ms !important; }
}


/* ==================== 14. МОБИЛЬНЫЙ БРЕЙКПОИНТ / 273:3663 ====================
   База макета — 360px, дальше «автолейаут»: колонка контента тянется до
   краёв экрана (--gutter 16px), а вертикальные отступы и кегли фиксированы
   или растут по vw до планшетного потолка. Верхняя граница — 1023px:
   ровно до неё мета viewport отдаёт реальную ширину устройства (см. head
   в index.html), выше остаётся масштабированный десктоп на 1280.

   Пока адаптированы первые два экрана: обложка с меню и «Любимые кейсы».
   Разделы ниже наследуют только токены и ждут своей очереди. */

/* Бургер и линия панели меню живут исключительно на мобильном. */
.topbar__burger,
.topbar__rule { display: none; }

/* .mtop — шапка с бургером (главная). Класс стоит в разметке рядом с самой
   кнопкой, чтобы мобильные правила не задели шапку страницы кейса: там она
   своя, с «назад», и своего мобильного макета ещё ждёт. */

@media (max-width: 1023px) {

  :root {
    --gutter: 16px;

    /* 36 / 42 / 32 из макета на 360, дальше растут вместе с шириной */
    --fs-display-l:  clamp(36px, 10vw,     56px);
    --fs-display-xl: clamp(42px, 11.667vw, 64px);
    --fs-display-s:  clamp(32px, 8.889vw,  48px);

    --fs-body: 14px;
    --fs-ui:   14px;
    --fs-note: 12px;

    --sp-8:  8px;
    --sp-10: 12px;
    --sp-15: 15px;
    --sp-20: 20px;
    --sp-21: 21px;
    --sp-25: 25px;

    --h-btn:    50px;
    --h-chip:   30px;
    --sz-photo: 58px;
  }

  /* минимальная ширина десктопа больше не держит документ */
  body { min-width: 0; }

  /* ---------- шапка: полоса + раскрывающаяся панель / 273:2632 ---------- */
  /* Панель — та же стеклянная подложка, что у кнопок (0.08 + blur 7.5).
     Закрытая шапка от макета не отличается: часы слева, бургер справа.

     Все правила висят на .mtop — шапке с бургером. */

  .mtop .topbar__in {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    padding: var(--gutter);
    background: transparent;
    transition: background-color 380ms var(--ease-out-soft),
                backdrop-filter  380ms var(--ease-out-soft);
  }

  .mtop.is-open .topbar__in {
    background: var(--c-glass);
    backdrop-filter: var(--blur);
    -webkit-backdrop-filter: var(--blur);
  }

  /* контейнер часов и языка разъезжается: часы остаются в первой строке,
     язык уходит в раскрывающуюся часть */
  .mtop .topbar__right { display: contents; }

  .mtop .cover__time    { order: 1; margin-top: 0; }
  .mtop .topbar__rule   { order: 2; }
  .mtop .cover__contact { order: 3; }
  .mtop .cover__lang    { order: 4; }

  .topbar__burger {
    position: absolute;
    top: var(--gutter);
    right: var(--gutter);
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--sz-photo);
    height: var(--sz-photo);
    border-radius: var(--r-6);
    background: var(--c-glass);
    backdrop-filter: var(--blur);
    -webkit-backdrop-filter: var(--blur);
    transition: background-color var(--flip-ms) var(--ease-out-soft),
                transform 160ms var(--ease-out-strong);
  }

  .topbar__burger:active { transform: scale(0.94); }

  /* иконки лежат в одной точке и переключаются кроссфейдом */
  .topbar__burger-ico {
    position: absolute;
    width: 24px;
    height: 24px;
    transition: opacity 220ms var(--ease-out-soft),
                transform 300ms var(--ease-out-strong);
  }

  .topbar__burger-ico--close { opacity: 0; transform: rotate(-45deg); }

  .is-open .topbar__burger-ico        { opacity: 0; transform: rotate(45deg); }
  .is-open .topbar__burger-ico--close { opacity: 1; transform: none; }

  /* Раскрытие: высоты строк известны заранее (линия 1px, ряды по 58px),
     поэтому анимируем height и margin-top напрямую — без замеров из JS
     и без grid 0fr → 1fr, который в Safari дёргает содержимое. */
  .mtop .topbar__rule,
  .mtop .cover__contact,
  .mtop .cover__lang {
    height: 0;
    margin-top: 0;
    opacity: 0;
    overflow: hidden;
    pointer-events: none;
    transition: height     380ms var(--ease-out-strong),
                margin-top 380ms var(--ease-out-strong),
                opacity    260ms var(--ease-out-soft);
  }

  /* margin-bottom: у <hr> он свой, 0.5em, и сдвигал бы строку контакта */
  .mtop .topbar__rule {
    display: block;
    width: 100%;
    margin-bottom: 0;
    border: 0;
    background: var(--c-rule);
  }

  .mtop.is-open .topbar__rule { height: 1px; margin-top: var(--sp-20); opacity: 1; }

  .mtop.is-open :is(.cover__contact, .cover__lang) {
    height: var(--sz-photo);
    margin-top: var(--sp-20);
    opacity: 1;
    pointer-events: auto;
  }

  /* В макете кнопки шапки прижаты к сетке без выступа подложки:
     фото и глобус встают ровно по --gutter. */
  /* Слева иконка стоит по --gutter, справа подложка ховера должна выходить
     за подпись — как на десктопе, иначе она обрезается ровно по букве. */
  .mtop .cover__contact,
  .mtop .cover__lang {
    gap: var(--sp-20);
    padding: 0 var(--sp-20) 0 0;
  }

  /* Строки меню — ссылки и кнопки: во всю ширину панели они бы ловили тап
     по пустому месту справа от подписи. */
  .mtop :is(.hback, .cover__contact, .cover__lang) { align-self: flex-start; }

  /* язык повторяет строку «написать мне»: иконка слева, подпись справа */
  .mtop .cover__lang .globe { order: -1; }

  /* Стекло на стекле в макете сведено в плоский #303030: панель уже размывает
     фон, второй раз размывать нечего — да и в Figma это именно заливка. */
  .mtop.is-open :is(.topbar__burger, .clock__cell, .globe) {
    background: #303030;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* ...но на ховере и фокусе иконка должна краснеть, как везде: плоская
     заливка выше по файлу и перебивала общее правило. */
  .mtop.is-open .hbtn:is(:hover, :focus-visible) :is(.globe, .hbtn__ico) {
    background: rgba(255, 53, 0, 0.18);
  }

  /* ---------- шапка статьи / 273:2716 и 273:2652 ---------- */
  /* Отличие от главной одно: в первой строке стоит «назад», а часы уходят
     в раскрывающуюся часть вместе с контактом. Порядок в панели по макету:
     назад → часы → линия → написать мне → язык. */

  .mtop--case .topbar__left { display: contents; }
  .mtop--case .topbar__sep  { display: none; }

  .mtop--case .hback {
    order: 1;
    gap: var(--sp-20);
    padding: 0 var(--sp-20) 0 0;
  }

  .mtop--case .topbar__rule   { order: 3; }
  .mtop--case .cover__contact { order: 4; }
  .mtop--case .cover__lang    { order: 5; }

  .mtop--case .cover__time {
    order: 2;
    height: 0;
    margin-top: 0;
    opacity: 0;
    overflow: hidden;
    pointer-events: none;
    transition: height     380ms var(--ease-out-strong),
                margin-top 380ms var(--ease-out-strong),
                opacity    260ms var(--ease-out-soft);
  }

  .mtop--case.is-open .cover__time {
    height: 56px;
    margin-top: var(--gutter);
    opacity: 1;
    pointer-events: auto;
  }

  /* ---------- обложка ---------- */
  /* Первый экран занимает весь кадр устройства; фон и ASCII уже считаются
     долями обложки, поэтому трогать их не нужно. */

  .cover {
    height: 100svh;
    min-height: 600px;
  }

  .cover__foot {
    gap: 0;
    padding-bottom: var(--gutter);
  }

  /* титул, подпись, кнопки — одной колонкой; .cover__main растворяется,
     чтобы кнопки могли уехать под подпись */
  .cover__main { display: contents; }

  .cover__title    { order: 1; }
  .cover__actions  { order: 3; margin-top: var(--sp-25); gap: 5px; }

  /* Кегль и интерлиньяж дублируются на контейнере: подписи внутри стали
     inline, и высоту строки задаёт уже блок, а не они сами. */
  .cover__leads {
    order: 2;
    display: block;
    margin-top: var(--sp-15);
    font-family: var(--font-mono);
    font-size: var(--fs-note);
    line-height: 1.3;
  }

  /* На мобильном обе подписи читаются одним абзацем: левая склеивается
     с правой в общий поток, перенос после «мобильных» из десктопной
     версии в макете сохранён — на 328px он и даёт четыре строки. */
  .cover__lead {
    display: inline;
    width: auto;
    font-size: var(--fs-note);
  }

  /* На десктопе половины стоят в разных колонках и точка не нужна,
     в общем потоке без неё два предложения слипаются. */
  .cover__lead--left::after { content: '.'; }

  /* inline-элемент не сдвинуть transform-ом, поэтому обе половины просто
     проявляются вместе, а не лесенкой */
  .cover__lead--left,
  .cover__lead--right { animation-delay: 400ms; }

  :root[lang="en"] .cover__lead--right { width: auto; }

  .cover__cta--tg,
  .cover__cta--cv { flex: 1 0 0; width: auto; min-width: 0; }

  /* ---------- любимые кейсы ---------- */

  .cases { margin-top: 120px; }

  .shead { display: block; padding-bottom: var(--sp-20); }

  .shead__group { gap: 5px; }

  .shead__aside {
    margin-top: 45px;
    gap: 10px;
  }

  .shead__note { width: 100%; }

  /* Порядок карточки переворачивается: сначала текст, под ним превью,
     кнопки в самом низу. .case__col растворяется, чтобы кадр смог
     встать между описанием и кнопками. */
  .case {
    flex-direction: column;
    gap: 0;
  }

  .case__col { display: contents; }

  .case__body    { order: 1; gap: var(--sp-20); width: 100%; }
  .case__media   { order: 2; flex: none; width: 100%; margin-top: var(--sp-20); border-radius: var(--r-4); }
  .case__actions { order: 3; margin-top: 10px; gap: 5px; }

  .case__head { gap: 10px; }

  .case__desc { width: 100%; }

  .case__tags li {
    height: 28px;
    font-size: var(--fs-note);
  }

  /* рамки экранов внутри кадра пропорций не меняют — только толщина
     обводки и радиус пересчитаны с 803px на 328px */
  .case__shot {
    border-width: 2px;
    border-radius: 10px;
  }

  .case__shot--wide { border-radius: 6px; }

  .case__cta   { flex: 1 0 0; width: auto; min-width: 0; }
  .case__figma { flex: 0 0 120px; width: auto; }

  /* ---------- места работы / 262:2650 ---------- */
  /* Строка разворачивается в колонку: компания, должность, даты, описание.
     Описание в покое обрезано нижней кромкой строки и растворяется, по тапу
     строка заливается акцентом и вырастает до полного текста.

     Раскрытие — по тапу, а не по наведению: это делает модуль в script.js,
     класс у состояния тот же .is-hot, поэтому вся анимация раздела 8
     работает без изменений. Фотографии на мобильном в макете нет. */

  .work {
    --row-pad: 15px;
    --row-h: 190px;
    --row-desc-top: 123px;
    --fs-company: clamp(26px, 5vw, 34px);
    margin-top: 120px;
  }

  .work__row {
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }

  /* в макете линия остаётся видна и поверх акцентной заливки */
  .work__rule { z-index: 1; }

  .work__company { top: 14px; }

  .work__role,
  .work__dates { left: 0; }

  .work__role  { top: 70px; }
  .work__dates { top: 89px; }

  /* Ширина описания на 20px меньше строки и в покое, и в раскрытии: сдвигается
     оно целиком, а перекладывать текст при раскрытии нельзя — script.js
     считает высоту заранее, по закрытому состоянию. */
  .work__descbox {
    left: 0;
    bottom: 0;
    width: calc(100% - 20px);
  }

  /* в раскрытии содержимое отступает от кромки заливки на 10px */
  .work__row.is-hot :is(.work__company, .work__role, .work__dates, .work__descbox) {
    transform: translateX(10px);
  }

  .work__row.is-hot .work__descbox { bottom: var(--row-pad); }

  .work__photo { display: none; }

  /* ---------- футер ---------- */
  /* Один вертикальный шаг на весь блок: между ссылкой и копирайтом, между
     колонками ссылок, вокруг часов и до пиксельного рисунка — одно и то же
     расстояние. Иначе на узком экране колонки разъезжаются вразнобой. */

  .foot {
    --foot-gap: var(--sp-40);
    gap: var(--foot-gap);
  }

  .foot__top { display: block; min-height: 0; }

  .foot__side { gap: var(--foot-gap); }
  .foot__side + .foot__side { margin-top: var(--foot-gap); }

  .foot__cols { flex-wrap: wrap; gap: var(--foot-gap); }

  /* «Контакты» уходят к «Социальным сетям», «Проекты» — в следующий ряд:
     так в первом ряду стоят два самых нужных блока. */
  .foot__col:nth-child(2) { order: 3; }
  .foot__col:nth-child(3) { order: 2; }

  /* ---------- временная заглушка для «Ещё проектов» ----------
     Свой мобильный макет раздел ещё ждёт. Пока разбираем десктопную сетку
     4×6, чтобы страница не уезжала вбок: карточки встают в одну колонку. */

  .more { margin-top: 120px; }

  .more__grid {
    display: flex;
    flex-direction: column;
    gap: var(--sp-20);
    grid-template-columns: none;
    grid-template-rows: none;
  }

  .more__grid > li { display: block; }

  /* ---------- модалка ---------- */
  /* На телефоне панель занимает весь экран: поля и скругления тут только
     отъедали бы ширину. Текст прижат к левому краю колонки и тянется
     вправо до противоположного поля — колонка в 600px на 360 схлопывалась
     в лапшу шириной с полстроки. */

  .modal__panel {
    width: 100%;
    height: 100svh;
    max-height: none;
    border-radius: 0;
  }

  .modal__header { padding: var(--gutter) 66px var(--gutter) var(--gutter); }

  .modal__title { font-size: clamp(28px, 7.778vw, 42px); }
  .modal__date  { margin-bottom: 10px; font-size: var(--fs-ui); }

  .modal__close { top: var(--gutter); right: var(--gutter); }

  /* Высота кадра от экрана, а не от окна за вычетом десктопной шапки:
     формула с 247px на телефоне отдавала почти весь экран одному медиа. */
  .modal__media {
    height: min(50svh, 100vw);
    min-height: 240px;
    margin-inline: var(--gutter);
  }

  /* Дорожка перемотки была фиксированной в 320px и вылезала за экран
     вместе со всей панелью — теперь панель во всю ширину кадра,
     а дорожка забирает остаток. */
  .modal__tools {
    left: 10px;
    right: 10px;
    width: auto;
    transform: none;
  }

  .scrub { width: auto; flex: 1 1 0; min-width: 0; }

  .modal__text {
    width: auto;
    margin-inline: var(--gutter);
    padding-bottom: var(--sp-25);
  }

  .modal__extra { margin-inline: var(--gutter); }

  /* ---------- страница кейса ---------- */
  /* Макета мобильной статьи нет — раскладка по тем же правилам, что и на
     главной: одна колонка до краёв экрана, дисплейные кегли ужимаются по vw,
     текстовые остаются фиксированными. Принудительные переносы из десктопной
     вёрстки снимаются: на 328px они рвут строки в неожиданных местах. */

  .wrap--case { width: min(100% - var(--gutter) * 2, 1200px); }

  .chero { padding-top: 120px; }

  .chero__icon { width: 80px; height: 80px; }

  .chero__meta  { margin-top: var(--sp-25); }
  .chero__title { font-size: clamp(32px, 8.889vw, 48px); }

  .cblock,
  .cblock--role,
  .cblock--process { margin-top: 80px; }

  .ctitle { font-size: clamp(26px, 7.222vw, 40px); }
  .csub   { font-size: 16px; line-height: 1.35; }

  .ctitle br,
  .csub br,
  .role p br { display: none; }

  .slider { margin-top: var(--sp-25); }

  /* панель «контекст / проблема»: колонки встают друг под друга,
     вертикальный разделитель между ними больше не нужен */
  .panel {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--sp-25);
    width: auto;
    margin-top: 30px;
    padding: var(--sp-20);
  }

  /* Разделитель не убираем, а кладём поперёк — между колонками, которые
     теперь стоят друг под другом. Порядком двигаем его на место. */
  .panel__rule {
    position: static;
    order: 1;
    width: auto;
    height: 1px;
  }

  .panel__col:first-of-type { order: 0; }
  .panel__col:last-of-type  { order: 2; }

  .panel__head { font-size: 26px; }

  /* Колонок столько, сколько влезает: на 360 это две, на планшете три.
     Одна колонка растягивала короткие подписи на весь экран. */
  .roles {
    grid-template-columns: repeat(auto-fit, minmax(max(150px, (100% - var(--sp-20) * 2) / 3), 1fr));
    column-gap: var(--sp-20);
    row-gap: 32px;
    margin-top: 32px;
  }

  /* Карточки этапов: высота по контенту, но кадр внутри ведёт себя как на
     десктопе — рамка шире карточки и уходит за её правый и нижний край. */
  .stages {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-20);
    margin-top: 32px;
  }

  .stage {
    height: auto;
    padding: var(--sp-20) var(--sp-20) 0;
  }

  /* карточки с одиночным кадром (график, «скоро») низом не упираются */
  .stage:has(.stage__shot) { padding-bottom: var(--sp-20); }

  .stage__head { font-size: 24px; line-height: 1.15; }
  .stage__text { font-size: 15px; line-height: 1.4; }

  .stage__shot,
  .stage__shots {
    position: relative;
    top: auto;
    left: auto;
    margin-top: var(--sp-20);
  }

  .stage__shot { width: 100%; }

  .stage--analysis .stage__shots { top: auto; }

  /* Рамка ровно с карточку шириной (410 из 410 в макете), а левым краем
     стоит по её падингу — поэтому справа она выходит за карточку на те же
     20px и подрезается её overflow. Пропорция и вложение кадра — из макета. */
  /* Пропорция — не вся рамка (410×298), а её видимая часть: на десктопе
     карточка срезает снизу 3px, а у аналитики — 17px, и ровно в этот срез
     попадает серый край, запечённый в самих кадрах. Без этого он вылезает. */
  .stage__shots {
    width: calc(100% + var(--sp-20) * 2);
    height: auto;
    aspect-ratio: 410 / 295;
  }

  .stage--analysis .stage__shots { aspect-ratio: 410 / 281; }

  /* вложение кадра — те же 10px из макета, долями от видимой части */
  .stage__frame {
    top: 3.39%;
    left: 2.44%;
    width: 97.56%;
  }

  .stage--analysis .stage__frame { top: 3.56%; }

  .stage__shot--chart { width: 90%; margin-inline: auto; }

  /* «Скоро!» — подпись, а не кадр: во всю карточку она кричит. */
  .stage__shot--soon {
    width: 40%;
    max-width: 150px;
    margin-inline: auto;
  }

  /* В макете у этой карточки размер как у остальных, хотя контента меньше.
     Держим минимальную высоту, а подпись центрируем в остатке. */
  .stage--soon {
    display: flex;
    flex-direction: column;
    min-height: 380px;
  }

  .stage--soon .stage__shot--soon { margin-block: auto; }

  .cshots  { margin-top: 32px; }
  .cnote   { margin-top: 32px; }

  /* Пара «до / после» остаётся в ряд: это сравнение, друг под другом оно
     перестаёт читаться. */
  .cframes { margin-top: 32px; }

  .page-case .foot { margin-top: 100px; }
}
