html { -webkit-text-size-adjust: 100%; }
:root {
  /* цвет: чёрный и белый, остальное — служебные серые для линий и заглушек */
  --c-ink: #111111;
  --c-paper: #FFFFFF;
  --c-line: #DCDCDC;                  /* линии рядов */
  --c-frame: #E4E4E4;                 /* рамка пустого медиа-слота */
  --c-portrait: #E5E5E5;              /* подложка портрета */
  --c-placeholder: #F2F2F2;           /* подложка пустого медиа */
  --c-placeholder-ink: #8A8A8A;       /* подпись-заглушка внутри подложки */
  --c-scrim: rgba(17, 17, 17, .55);   /* подложка стрелки на слоте без картинки */
  --c-overlay: rgba(17, 17, 17, 0.9); /* фон просмотра крупнее */

  /* сетка: холст 1920, семь колонок, поля 60, промежуток 20.
     Вертикальный ритм идёт теми же двумя шагами: 20 внутри блока, 60 между блоками. */
  --canvas: 1920px;
  --cols: 7;
  --margin: 60px;
  --gutter: 20px;
  --step: 60px;                       /* шаг между блоками по вертикали; на телефоне не равен полю */
  --sink-h: 190px;                    /* высота тонущего полотна в подвале */
  --nav-left: 58px;                   /* левый край меню от края холста */

  /* типографика: одно начертание, шкала 12 / 16 / 24 / 36 */
  --font: 'IBM Plex Mono', ui-monospace, 'Courier New', monospace;
  --font-ui: 'IBM Plex Mono', ui-monospace, monospace;  /* курсор и вуаль перехода */
  --fs-small: 12px;
  --fs-body: 16px;
  --fs-lead: 24px;
  --fs-case-title: 36px;
  --lh-body: 1.4;
  --track-h1: 0.36em;

  /* кривые движения */
  --ease: cubic-bezier(.22,.61,.36,1);
  --ease-slot: cubic-bezier(.16,.84,.24,1);
  --ease-zoom: cubic-bezier(.33,.02,.2,1);

  --cursor-blend: difference;
}
.transition-veil { position: fixed; inset: 0; z-index: 10000; background: var(--c-ink); color: var(--c-paper); display: flex; align-items: center; justify-content: center; pointer-events: none; opacity: 0; visibility: hidden; transition: opacity 400ms var(--ease), visibility 0s linear 400ms; }
.transition-veil.is-on { opacity: 1; visibility: visible; pointer-events: auto; transition: none; }
.transition-veil.is-calm { transition: opacity .15s linear, visibility 0s linear .15s; }
/* ширины у строки нет: 15ch считались по запасному шрифту до загрузки
   основного, и на телефоне строка уезжала вправо из центрированного бокса */
.veil-line { display: block; white-space: pre; font: 400 64px/1 var(--font-ui); letter-spacing: 0; margin-top: -4px; }
.veil-line span { display: inline-block; opacity: 0; filter: blur(10px); transition: opacity 180ms linear, filter 180ms linear; }
.veil-line span.is-lit { opacity: 1; filter: blur(0px); }
/* сдвиг вешаем на сам поток страницы: любой transform выше меню ломает position: fixed */
.page-in { opacity: 0; }
.page-in.is-in { opacity: 1; transition: opacity 500ms var(--ease); }
.page-shift { transform: translate3d(0, 12px, 0); }
.page-in.is-in .page-shift { transform: translate3d(0, 0, 0); transition: transform 500ms var(--ease); }
.page-in.is-calm .page-shift { transform: none; transition: none; }
/* после входа transform снимаем: он делает main containing block и ломает position: fixed */
.page-in.is-settled .page-shift { transform: none; transition: none; }
/* Занавес прихода до разбора тела: перекрываем страницу с первой отрисовки,
   пока bindVeil не поставит настоящую завесу. */
html.is-veiled::before { content: ""; position: fixed; inset: 0; z-index: 10001; background: var(--c-ink); }
/* Страница, открытая переходом: занавес уже отыграл, показываем её готовой.
   Непрозрачность обязательна — иначе сквозь тающий чёрный проступает
   белая бумага и переход мигает. */
.page-in.is-ready { opacity: 1; transition: none; }
.page-in.is-ready .page-shift { transform: none; transition: none; }
.page-in.is-calm.is-in { transition: opacity 150ms linear; }
@media (max-width: 900px) { .veil-line { font-size: 9vw; } }
html.cursor-custom, html.cursor-custom * { cursor: none !important; }
.cursor { position: fixed; left: 0; top: 0; z-index: 9999; pointer-events: none; opacity: 0; color: var(--c-paper); mix-blend-mode: var(--cursor-blend); font: 400 18px/1 var(--font-ui); will-change: transform; transition: opacity .18s linear, font-size .3s var(--ease); }
.cursor.is-arrow { font-size: 38px; }
/* скобки и подпись лежат в одной ячейке — смена состояния идёт кросс-фейдом */
.cursor-in { display: grid; grid-template-areas: 'c'; place-items: center; transform: translate(-50%, -50%); }
.cursor-in > * { grid-area: c; }
.cursor-in svg { height: 1em; width: 1.212em; display: block; fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: butt; stroke-linejoin: miter; transition: opacity .26s var(--ease), transform .34s var(--ease); }
.cursor.is-label .cursor-in svg { opacity: 0; transform: scale(0.7); }
/* над полотном «Солянки» скобки уступают ладони, зажатие — кулаку */
.cursor-in .cursor-hand, .cursor-in .cursor-fist { width: 1em; height: 1em; stroke-width: 1.3; stroke-linecap: square; opacity: 0; transform: scale(0.7); }
.cursor.is-hand { font-size: 34px; }
.cursor.is-hand .cursor-in > svg:first-child { opacity: 0; transform: scale(0.7); }
.cursor.is-hand:not(.is-grab) .cursor-hand { opacity: 1; transform: none; }
.cursor.is-hand.is-grab .cursor-fist { opacity: 1; transform: none; }
.cursor-arrow { opacity: 0; transition: opacity .22s var(--ease); }
.cursor.is-arrow .cursor-arrow { opacity: 1; }
body { margin: 0; background: var(--c-paper); color: var(--c-ink); font-family: var(--font); font-weight: 400; font-size: var(--fs-body); line-height: var(--lh-body); text-transform: uppercase; }
a { color: inherit; text-decoration: none; }
a:hover { color: inherit; }
:focus-visible { outline: 2px solid var(--c-ink); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }

/* --- PROJECTS (макет projects.html): 7 колонок, поля 60, промежуток 20 --- */
/* box-sizing обязателен: без него max-width считает только содержимое,
   и на экранах шире 2040 холст разъезжался до 2040, а колонка — до 257 */
.page { box-sizing: border-box; max-width: var(--canvas); margin-inline: auto;
  padding: var(--margin) var(--margin) 0; container-type: inline-size; }
.page *, .nav * { box-sizing: border-box; }
/* холст главной: как .page, но без border-box у потомков — там карточки
   считались от content-box, и общий сброс сдвинул бы их на 2 px */
.page-home { box-sizing: border-box; max-width: var(--canvas); margin-inline: auto;
  padding: var(--margin) var(--margin) 0; container-type: inline-size; }
:where(.page) :where(h1, h2, p, ul) { margin: 0; padding: 0; font-weight: 400; }
:where(.page, .nav) :where(ul) { list-style: none; margin: 0; padding: 0; }
.grid { display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); gap: var(--gutter); }
.c2 { grid-column: 2 / 3; }
.c3 { grid-column: 3 / 4; }
.c2-3 { grid-column: 2 / 4; }
.c2-4 { grid-column: 2 / 5; }
.c2-5 { grid-column: 2 / 6; }
.c4-5 { grid-column: 4 / 6; }
.c4-6 { grid-column: 4 / 7; }
.shot { display: block; aspect-ratio: 4.74; }
.c7 { grid-column: 7 / 8; }

/* меню держится за холст, а не за окно: на широких экранах холст
   центрируется, и меню обязано уехать вместе с ним */
.nav { position: fixed; left: calc((100% - min(var(--canvas), 100%)) / 2 + var(--nav-left));
  top: 50%; transform: translateY(-50%); z-index: 10; }
.nav li { margin-bottom: 12px; }
.nav .lang { margin-top: 28px; display: flex; gap: 12px; }
.nav a, .nav .is-current { display: inline-block; padding: 2px 0; }
.is-current { cursor: default; }

.h1 { font-size: 5.15cqw; line-height: 1; letter-spacing: var(--track-h1); white-space: nowrap; margin-left: -0.18em; padding: 0; }
.top-meta { align-self: center; margin: 0; }
.intro { margin-top: 16px; margin-bottom: 60px; }

.row { align-items: start; }
.row::after { content: ''; grid-column: 2 / 8; height: 1px; background: var(--c-line); margin-top: calc(-1 * var(--gutter)); }
.row:last-of-type::after { display: none; }
.row .year { font-size: var(--fs-body); line-height: 1.1; }
.row .name { font-size: var(--fs-body); overflow-wrap: normal; }
.row .tags { font-size: var(--fs-body); opacity: .5; overflow-wrap: normal; }

.slot { --rx: 0deg; --ry: 0deg; --mx: 0px; --my: 0px; --ax: 0px; --ms: 1; position: relative; display: block; justify-self: start; width: calc(66.6667% - 6.667px); height: 100%; background: var(--c-placeholder); border: 1px solid var(--c-frame); overflow: hidden; transform: perspective(1200px) rotateX(var(--rx)) rotateY(var(--ry)); transition: width .85s var(--ease-slot), transform .85s var(--ease-slot); will-change: width, transform; }
.row:hover .slot, .row:focus-within .slot { width: 100%; }
.row:hover .slot, .row:focus-within .slot { --ms: 1.06; }
.slot.is-tilt { transition: transform .08s linear; z-index: 2; }
.media { position: absolute; inset: -6%; background: var(--c-placeholder); overflow: hidden; transform: translate3d(var(--mx), var(--my), 0) scale(var(--ms)); transition: transform .95s var(--ease-slot); will-change: transform; }
.slot.is-tilt .media { transition: transform .08s linear; }
.media::after { content: attr(data-label); position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: var(--fs-small); letter-spacing: .08em; color: var(--c-placeholder-ink); }
.media img { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
/* Мини-превью строки: стоп-кадр лежит снизу и виден всегда, ролик проявляется
   поверх него. Показываем ролик только по первому кадру — между play() и
   картинкой проходит время, и раньше на его месте мелькал бы чёрный. */
.media > .preview-video { position: absolute; inset: 0; z-index: 1; display: block;
  width: 100%; height: 100%; object-fit: cover; object-position: center;
  opacity: 0; transition: opacity 200ms var(--ease); pointer-events: none; }
.media > .preview-video.is-playing { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .media > .preview-video { display: none; } }
.slot:has(img), .slot:has(video) { border-color: transparent; }
/* Safari до 15.4 не знает :has — там те же правила по классам has-*,
   которые ставит markHas() в main.js. Без них поверх картинок всплыли бы
   подписи-заглушки и рамки пустых слотов. */
@supports not selector(:has(img)) { .slot.has-media { border-color: transparent; } }
.go { position: absolute; right: 14px; bottom: 12px; z-index: 1; display: block; transform: translate3d(var(--ax), 0, 0); transition: transform .85s var(--ease-slot); }
.go svg { height: 30px; width: 37px; display: block; overflow: visible; fill: none; stroke: var(--c-paper); stroke-width: 2.6; stroke-linecap: butt; stroke-linejoin: miter; }
.slot:not(:has(img, video)) .go { background: var(--c-scrim); padding: 5px 6px; }
@supports not selector(:has(img)) { .slot:not(.has-media) .go { background: var(--c-scrim); padding: 5px 6px; } }
.go [data-lb], .go [data-rb] { transform: translateX(0); transition: transform .55s var(--ease-slot); }
.go [data-rot] { transform: rotate(0deg); transform-box: fill-box; transform-origin: center; transition: transform .55s var(--ease-slot); }
.row:hover .go [data-rot], .row:focus-within .go [data-rot] { transform: rotate(45deg); }
.row:hover .go [data-lb], .row:focus-within .go [data-lb] { transform: translateX(-3.5px); }
.row:hover .go [data-rb], .row:focus-within .go [data-rb] { transform: translateX(3.5px); }
.slot.is-tilt .go { transition: transform .08s linear; }
.is-link { cursor: pointer; }   /* строка списка, карточка главной */

.foot { margin-top: 74px; padding-bottom: 80px; }
.talk { display: inline-block; margin-top: 76px; font-size: var(--fs-lead); }
.socials li { margin-bottom: 76px; }
.socials li:last-child { margin-bottom: 0; }
.socials a { font-size: var(--fs-lead); border-bottom: 1px solid var(--c-ink); padding-bottom: 3px; white-space: nowrap; }
.socials a:hover, .socials a:focus-visible { opacity: .55; }
.credits { margin-top: 76px; font-size: var(--fs-lead); grid-template-columns: auto auto 1fr; }
.credits > * { justify-self: start; }
.credits a { border-bottom: 1px solid var(--c-ink); padding-bottom: 3px; white-space: nowrap; }
.credits a:hover, .credits a:focus-visible { opacity: .55; }

/* --- Подвал: тонущее полотно ---
   Строки медленно идут вниз, всплывая из размытия в полосе резкости
   и снова растворяясь книзу; каждая плывёт по горизонтали со своей
   скоростью. Получается поверхность, уходящая от зрителя, а не бегущая
   строка. Движение и профиль резкости считает main.js (initSink).
   Сверху линии нет: размытый текст сам по себе прозрачным не становится,
   без длинной маски у верхней кромки остаётся серая полоса. */
.sink { position: relative; height: var(--sink-h); overflow: hidden; margin-top: var(--step);
  user-select: none;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 64px);
          mask-image: linear-gradient(to bottom, transparent 0, #000 64px); }
.sink__line { position: absolute; top: 0; left: 0; white-space: pre;
  font-size: var(--fs-small); line-height: 1.15; will-change: transform, filter, opacity; }

/* clearance for the fixed nav: column 2 must start past the nav's right edge (232px) */
@media (min-width: 901px) and (max-width: 1303px) {
  .page { padding-left: 200px; padding-right: 32px; }
  .grid { grid-template-columns: 52px minmax(96px, 0.8fr) minmax(160px, 2.2fr) minmax(155px, 0.9fr); }
  .shot { aspect-ratio: 3.6; }
  .c2 { grid-column: 1 / 2; }
  .c3 { grid-column: 2 / 3; }
  .c2-3, .c2-4, .c2-5 { grid-column: 1 / 4; }
  .c4-5, .c4-6 { grid-column: 3 / 4; }
  .slot { width: 76%; }
  .row:hover .slot, .row:focus-within .slot { width: 100%; }
  .c7 { grid-column: 4 / 5; }
  .row::after { grid-column: 1 / 5; }
  .row .name, .row .tags { overflow-wrap: normal; word-break: normal; }
}

@media (min-width: 901px) and (max-width: 1250px) {
  .socials a, .credits, .talk { font-size: var(--fs-body); }
  .socials li { margin-bottom: 48px; }
  .credits { margin-top: 48px; }
}

@media (max-width: 900px) {
  .page { padding: 24px 20px 0; }
  .nav { position: static; transform: none; display: flex; flex-wrap: wrap; gap: 0 var(--gutter); padding: 20px 20px 0; }
  .nav li { margin-bottom: 0; }
  .nav .lang { margin-top: 0; }
  /* заголовок чернилами от края до края колонки: у скобок Plex Mono
     по 0.22em пустого поля внутри знакоместа (замер по canvas), справа
     ещё хвостовой трекинг 0.05em — на это и выпущен за колонку */
  .h1 { font-size: 11cqw; letter-spacing: .05em; text-align: left; margin: 0 -0.27em 0 -0.22em;
    padding: 0 .05em 0 0; }
  .grid { grid-template-columns: minmax(0, 1fr); }
  .c2, .c3, .c2-3, .c2-4, .c2-5, .c4-5, .c4-6, .c7 { grid-column: 1 / -1; }
  .slot, .row:hover .slot, .row:focus-within .slot { width: 100%; }
  /* строка списка — один блок: имя слева, год справа на той же строке,
     ниже превью, ниже теги. Текст от превью — полшага с обеих сторон,
     линия — на шаге от тегов и на шаге до следующей строки. */
  .row { grid-template-columns: minmax(0, 1fr) auto; column-gap: var(--gutter);
    row-gap: 8px; margin-bottom: var(--gutter); }
  .row .name { grid-column: 1; grid-row: 1; font-size: 14px; align-self: last baseline; }
  .row .year { grid-column: 2; grid-row: 1; justify-self: end; align-self: last baseline; }
  .row .shot { grid-column: 1 / -1; grid-row: 2; }
  .row .tags { grid-column: 1 / -1; grid-row: 3; font-size: 14px; }
  .row::after { grid-column: 1 / -1; grid-row: 4; margin-top: 8px; }
  /* одна рамка на все строки — пропорция геро кейсов, кадр в ней cover */
  .shot { aspect-ratio: 1540 / 450; }
  .intro { margin-bottom: var(--step); }
  .foot { margin-top: 48px; }
  .talk, .credits { margin-top: 36px; }
  .socials li { margin-bottom: 24px; }
  .credits > * { grid-column: 1 / -1 !important; }
}

/* --- ГЛАВНАЯ: то, что лежало инлайном, вынесено в классы ---
   Значения перенесены один в один, поэтому раскладка не двигается.
   На остальных страницах инлайн пока перебивает эти правила сам собой:
   у него приоритет выше, и до своей очереди каждая страница живёт как жила. */
.c3-6 { grid-column: 3 / 7; }

/* меню: то, что на других страницах ещё лежит инлайном */
.nav a { transition: opacity .18s ease; }
.nav button { display: inline-block; padding: 2px 0; margin: 0; border: 0;
  background: none; color: inherit; font: inherit; text-transform: inherit; cursor: pointer; }
.nav-lang-row { margin-top: 28px; }
.nav-lang { display: flex; gap: 12px; }
/* текущий язык мигает, как курсор в поле: скобки стоят, буквы гаснут */
@keyframes lang-blink { 50% { opacity: .5; } }
.lang-cur { animation: lang-blink 1.2s steps(1, end) infinite; }
/* кнопка меню: в скобках та же стрелка, что у всех кнопок, — иначе
   [МЕНЮ] читалось подписью. Открытое меню показывает [X] без стрелки. */
[data-menu-toggle] { display: inline-flex; align-items: center; }
[data-menu-toggle] [data-arrow] { width: .92em; height: .92em; flex-shrink: 0;
  margin: 0 .2em 0 .4em; overflow: visible; stroke: currentColor;   /* воздух как у ссылок [data-ul] */
  position: relative; top: .12em; }   /* центр стрелки — на середине капители, а не строки */
[data-menu-toggle] [data-rot] { transform: rotate(0deg); transform-box: fill-box;
  transform-origin: center; transition: transform .3s var(--ease); }
[data-menu-toggle]:hover [data-rot], [data-menu-toggle]:focus-visible [data-rot] { transform: rotate(45deg); }
[data-menu-toggle].is-open [data-arrow] { display: none; }
/* разделы страницы: цифра — полутоном, точка текущего и наведение — в полную силу */
.nav a[data-nav] { opacity: .5; }
.nav a[data-nav][aria-current="page"], .nav a[data-nav]:hover, .nav a[data-nav]:focus-visible { opacity: 1; }
[data-menu-panel] { overflow: hidden; height: 0; opacity: 0;
  transition: height .3s var(--ease), opacity .3s var(--ease); }
/* на главной панель — колонка с выключкой влево, на остальных обычный список */
.menu-col { display: flex; flex-direction: column; align-items: flex-start; text-align: left; }
[data-menu-panel] > li { padding-top: 12px; }
[data-menu-panel] > li:first-child { padding-top: 28px; }
.menu-key { display: inline-block; width: 3ch; white-space: nowrap; }
[data-dot] { display: inline-block; width: 1ch; text-align: center; opacity: 0;
  transition: opacity .18s ease; }

/* заголовок главной: свой кегль и трекинг, размер досчитывает автофит */
.h1-row { height: 205px; }
.h1-home { grid-column: 2 / 8; margin: 0 -0.36em 120px 0; padding: 0; font-weight: 400;
  font-size: 6.085cqw; line-height: 1; letter-spacing: var(--track-h1);
  white-space: nowrap; display: flex; }
.h1-home > span { display: inline-block; }

/* <picture> только выбирает формат (AVIF, WebP, запасной); бокса у обёртки
   нет, картинка ведёт себя как прямой ребёнок слота */
picture { display: contents; }
/* пара слов, которую нельзя рвать даже по дефису («из-за шага») */
.nowrap { white-space: nowrap; }

/* стрелка-скобка: общая часть здесь, размер и обрезка — по месту.
   overflow держим раздельно: у кнопки скобки не выходят за рамку, у ссылки выходят. */
[data-arrow] { fill: none; stroke: var(--c-ink); stroke-width: 2.4;
  stroke-linecap: butt; stroke-linejoin: miter; }
.card-arrow { display: block; width: 62px; height: 51px; stroke-width: 2.6;
  flex-shrink: 0; overflow: visible; }
.card-arrow [data-lb] { transform: translateX(0); transition: transform .3s var(--ease); }
.card-arrow [data-rot] { transform: rotate(0deg); transform-box: fill-box;
  transform-origin: center; transition: transform .3s var(--ease); }
[data-btn] { display: inline-flex; align-items: center; gap: .4em; font-size: var(--fs-lead); }
[data-btn] [data-arrow] { display: inline-block; width: .92em; height: .92em;
  flex-shrink: 0; vertical-align: -0.1em; }
[data-btn] [data-rb] { display: inline-block; transition: transform .3s var(--ease); }
[data-ul] [data-arrow] { display: inline-block; width: .92em; height: .92em;
  flex-shrink: 0; overflow: visible; }
[data-ul] [data-rot] { transform: rotate(0deg); transform-box: fill-box;
  transform-origin: center; transition: transform .3s var(--ease); }

/* подчёркивание растёт из линии, а не появляется рамкой */
[data-ul] { display: inline-flex; align-items: center; gap: .4em; padding-bottom: 3px;
  background-image: linear-gradient(var(--c-ink), var(--c-ink));
  background-repeat: no-repeat; background-position: left bottom; background-size: 100% 1px;
  transition: background-size .3s var(--ease); }

/* карточка проекта: слот с наклоном, медиа с параллаксом, плашка снизу */
[data-slot] { position: relative; display: block; overflow: hidden;
  background: var(--c-placeholder); border: 1px solid transparent;
  transform: perspective(1200px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  transition: transform .55s var(--ease); will-change: transform; }
[data-media] { position: absolute; inset: -6%; background: var(--c-placeholder);
  transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
  transition: transform .55s var(--ease); }
[data-media] > video, [data-media] img { width: 100%; height: 100%; object-fit: cover; display: block; }
[data-bar] { position: absolute; left: 0; right: 0; bottom: 0; display: flex;
  justify-content: space-between; padding: 10px 12px; font-size: var(--fs-small);
  color: var(--c-paper); z-index: 1; transform: translate3d(var(--bx, 0px), 0, 0);
  transition: transform .55s var(--ease); }
[data-bar] > span { font-size: var(--fs-body); }
.card-meta { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--gutter); margin-top: var(--gutter); }
[data-title] { margin: 0; padding: 0; font-weight: 400; font-size: var(--fs-lead); line-height: 1.2; }
.card-client { display: block; margin-top: 6px; opacity: .5;
  font-size: var(--fs-body); }
.card-col { display: flex; flex-direction: column; }
.card-col > [data-btn] { align-self: flex-start; margin-top: auto; padding-top: 120px;
  font-size: var(--fs-case-title); }   /* единственная кнопка блока — крупнее прочих */
@media (min-width: 901px) and (max-width: 1303px) {
  .card-col > [data-btn] { font-size: var(--fs-lead); }   /* в 36 px не влезала на 1100 */
}

/* портрет: тот же слот, но без подложки и с обрезкой по нижнему краю */
.portrait-slot { background: transparent; border: 0; overflow: visible;
  width: 100%; height: 100%; aspect-ratio: 500 / 524; }
.portrait-plate { position: absolute; left: 0; right: 0; top: 8.5%; bottom: 0;
  background: var(--c-portrait); display: block; }
.portrait-media { inset: auto; top: 0; left: -3%; width: 106%; height: 100%; display: block;
  background: none; transform: translate3d(var(--mx, 0px), 0, 0); }
.portrait-media img { position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  object-fit: contain; object-position: center bottom; display: block; }
/* [3] · ABOUT · TAP — три подписи по трём точкам: крайние по 3 знака,
   поэтому space-between ставит ABOUT ровно по центру */

/* блоки главной */
.home-hero { margin-bottom: 120px; align-items: center; }
.home-work { row-gap: 120px; margin-bottom: 120px; }
/* нижнего поля нет: отступ до полотна подвала даёт само полотно (60) */
.home-contact { padding-bottom: 0; align-items: stretch; }
.home-services { margin: 0; padding: 0; list-style: none; }
.home-services > li { margin-bottom: 2px; }
.home-geo { margin: 64px 0 0; padding: 0; }
.home-lede { margin: 0; padding: 0; width: 221px; }
.home-focus { margin: 64px 0 0; padding: 0; }
.home-figure { margin: 0; padding: 0; }
.foot-talk { margin-top: 120px; }
.foot-credits { display: flex; justify-content: space-between; align-items: flex-end;
  margin-top: 120px; gap: var(--gutter); }
.foot-socials { grid-column: 7 / 8; margin: 0; padding: 0; list-style-type: none;
  display: flex; flex-direction: column; justify-content: space-between; align-items: flex-start; }
.foot-socials [data-ul] { font-size: var(--fs-body); }
.home-contact-col { grid-column: 2 / 5; width: 714px; }
.home-contact-lede { margin: 0; padding: 0; max-width: 245%; width: 775px; height: auto; }

/* --- ПРОЕКТЫ: инлайн -> классы. Подвал общий с главной --- */
.page-stack { display: grid; justify-content: flex-start; }
.grid-mid { align-items: center; }
/* панель меню: 12/20 и без нижнего поля — так на девяти страницах из десяти,
   у главной свои 28/12, поэтому расхождение держим классом, а не правкой */
.menu-tight > li { margin-bottom: 0; }
.menu-tight > li:first-child { padding-top: 20px; }
.foot-contact { padding-top: 60px; padding-bottom: 0; align-items: stretch; }
.projects-lede { margin: 0; padding: 0; max-width: 125%; width: 892px; height: auto; }

/* --- ABOUT (макет About.pdf): та же семёрка, новые вертикали 5 и 6 --- */
.c4 { grid-column: 4 / 5; }
.c5 { grid-column: 5 / 6; }
.c6 { grid-column: 6 / 7; }
.c5-6 { grid-column: 5 / 7; }

.about-hero { margin-top: 100px; }
.about-hero > * { grid-row: 1; align-self: start; }
.about-hero .portrait { grid-row: 1 / 3; }
.about-hero .lower { grid-row: 2; align-self: end; }
/* рамка портрета задаёт низ блока: тянем её на обе строки, текст не выходит за край */
@media (min-width: 1304px) {
  /* размер портрета держим на пропорции колонки: растяжка по строкам сетки
     делала его зависимым от числа строк текста — при EN рамка меняла высоту */
  /* рамка снова тянется на обе строки — нижние блоки текста заканчиваются внутри кадра,
     но само видео внутри держит свою пропорцию и не зависит от языка */
  .about-hero .portrait { align-self: stretch; aspect-ratio: auto; width: 100%; }
  /* оптический вдох: строки текста начинаются и заканчиваются внутри кадра, а не по его кромке */
  /* полулидинг первой строки: без него верхняя строка визуально выходит над кромкой кадра */
  .about-hero .bio, .about-hero .stack, .about-hero .langs { padding-top: 6px; }
  /* симметричная компенсация: нижняя строка тоже держится внутри кромки кадра */
  .about-hero .lower { padding-bottom: 6px; }
  /* --- ABOUT: инлайн -> классы. Отбивки первого экрана ---
     Селектор из двух частей и место после правила 6px не случайны:
     инлайн перебивал ту компенсацию на любой ширине, и класс обязан
     повторить это поведение, иначе первый экран поедет. */
  .about-hero .bio { padding-top: 26px; padding-bottom: 26px; }
  .about-hero .stack { padding-top: 27px; padding-bottom: 27px; height: 307px; }
  .about-hero .langs { padding-top: 20px; padding-bottom: 20px; }
  /* строки переносятся сами: жёсткий nowrap выпускал англ. текст за колонку */
  .about-hero .bio, .about-hero .lower { white-space: normal; text-wrap: pretty; overflow-wrap: normal; word-break: normal; }
}

.portrait { position: relative; display: block; aspect-ratio: 1; background: var(--c-portrait); overflow: hidden; }
.portrait img, .portrait video { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
/* видео заполняет кадр целиком: иначе его нижняя грань выше конца текстового ряда
   и последние строки оказываются за пределами аватарки */
.portrait:has(video) { background: none; }
@supports not selector(:has(img)) { .portrait.has-video { background: none; } }
.portrait::after { content: attr(data-label); position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: var(--fs-small); letter-spacing: .08em; color: var(--c-placeholder-ink); }
.portrait:has(img)::after, .portrait:has(video)::after { display: none; }
@supports not selector(:has(img)) { .portrait.has-media::after { display: none; } }

.stack-title { margin-bottom: var(--gutter); }
.stack-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--gutter); }
.stack-grid li { aspect-ratio: 1; display: flex; align-items: center; justify-content: center; padding: var(--pad, 12%); }
.stack-grid svg { width: 100%; height: 100%; display: block; }
/* модификаторы отдельных иконок: кегль задаётся жёстко, padding-ячейки не участвует */
/* +5% к кеглю: три иконки оптически мельче соседних при равной ячейке */
.stack-grid .stack-icon { padding: 0; overflow: visible; }
.stack-grid .stack-icon--openai svg,
.stack-grid .stack-icon--solidworks svg,
.stack-grid .stack-icon--anthropic svg { width: 105%; height: 105%; }

.cv { margin-top: var(--step); }
.about-hero + .cv { margin-top: 70px; }
.cv-title { margin-bottom: var(--gutter); }
.cv-row { align-items: start; }
/* записи разделены тонкой линией; у последней в разделе линии нет.
   На семи колонках линия идёт от второй колонки (первая — под меню),
   поэтому не border, а псевдоэлемент. Открытое окно описания живёт
   в контексте своей строки (у .reveal свой stacking context), строку
   с ним поднимаем над соседями — иначе их линии ложились на окно. */
.cv-row + .cv-row { margin-top: var(--gutter); }
.cv-row { position: relative; padding-bottom: var(--gutter); border-bottom: 1px solid var(--c-line); }
.cv-row:last-child { padding-bottom: 0; border-bottom: 0; }
.cv-row:has(.cv-more.is-open) { z-index: 41; }
@supports not selector(:has(img)) { .cv-row.has-open { z-index: 41; } }
@media (min-width: 1304px) {
  .cv-row { border-bottom: 0; }
  .cv-row:not(:last-child)::after { content: ""; position: absolute; bottom: 0;
    left: calc((100% - 6 * var(--gutter)) / 7 + var(--gutter));
    right: calc((100% - 6 * var(--gutter)) / 7 - 2.2em);   /* до правого края [+]: три знака и поля плюса */
    height: 1px; background: var(--c-line); }
}
.cv-row p { font-size: var(--fs-body); overflow-wrap: normal; }
.cv-more { justify-self: start; align-self: start; display: inline-flex; align-items: center; gap: 0.4em; padding-bottom: 3px; background-image: linear-gradient(var(--c-ink), var(--c-ink)); background-repeat: no-repeat; background-position: left bottom; background-size: 100% 1px; transition: background-size .3s var(--ease); }
.cv-more svg { width: .92em; height: .92em; flex-shrink: 0; display: inline-block; overflow: visible; fill: none; stroke: var(--c-ink); stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: miter; }
/* Кнопки со стрелкой: без своей точки вращения группа крутилась вокруг
   начала координат svg и стрелка улетала вбок. */
[data-btn] [data-rot] { transform: rotate(0deg); transform-box: fill-box; transform-origin: center; transition: transform .3s var(--ease); }
.cv-more [data-rot] { transform: rotate(0deg); transform-box: fill-box; transform-origin: center; transition: transform .3s var(--ease); }

@media (min-width: 901px) and (max-width: 1303px) {
  .c4, .c5, .c6 { grid-column: 3 / 4; }
  .c5-6 { grid-column: 3 / 5; }
  .about-hero > *, .about-hero .portrait, .about-hero .lower { grid-row: auto; align-self: start; }
  .about-hero .portrait { grid-column: 1 / 3; }
  .about-hero .bio { grid-column: 3 / 5; }
  .about-hero .stack { grid-column: 1 / 5; }
  .about-hero .langs { grid-column: 1 / 3; }
  .about-hero .lower.c4 { grid-column: 1 / 3; }
  .about-hero .lower.c5 { grid-column: 3 / 5; }
  .about-hero .lower.c7 { grid-column: 1 / 5; }
  .cv-row .cv-name { grid-column: 1 / 3; }
  .cv-row .cv-org { grid-column: 3 / 4; }
  .cv-row .cv-when, .cv-row .cv-more { grid-column: 4 / 5; }
  .cv-row p { overflow-wrap: normal; word-break: normal; }
  /* внутригрупповой отступ должен быть меньше межстрочного, иначе ссылка липнет к следующей строке */
  .cv-row + .cv-row { margin-top: 34px; }
  .cv-row .cv-more { margin-top: 4px; }
  .about-hero { margin-top: var(--step); }
}

@media (max-width: 900px) {
  .c4, .c5, .c6, .c5-6 { grid-column: 1 / -1; }
  /* ритм About на телефоне тем же двумя шагами: --gutter внутри блока
     (портрет, био, стек, языки — один первый экран), --step между */
  .about-hero { margin-top: var(--gutter); gap: var(--gutter); }   /* заголовок, подпись, портрет — один блок */
  .about-hero > *, .about-hero .portrait, .about-hero .lower { grid-row: auto; align-self: start; }
  .cv { margin-top: var(--step); }
  .about-hero + .cv { margin-top: var(--step); }
  .cv-row { gap: 6px; }
  .cv-row + .cv-row { margin-top: var(--gutter); }
  .cv-more { margin-top: 6px; }
}

/* --- строка «Солянка»: не проект, а вход в раздел ---
   Шесть строк выше стоят по колонкам 2 / 3 / 4-5 / 7. Эта идёт единой
   ссылкой на все шесть колонок: ритм списка ломается, сетка нет.
   Цвет как у строк выше — бумага и чернила, инверсии нет. Своего color
   строка не держит принципиально: `a:hover { color: inherit }` весомее
   любого класса, и текст при наведении подменился бы на унаследованный. */
/* строку открывает та же линия, что делит строки списка: на projects её
   давала нижняя линия предыдущей строки, но в кейсе предыдущей нет */
.row-mix { position: relative; }
.row:has(+ .row-mix)::after { display: none; }
@supports not selector(:has(img)) { .row.before-mix::after { display: none; } }
/* линия живёт на самой полосе: строка занимает все семь колонок и увела бы
   её под меню, а линии списка идут по колонкам 2-7 */
.mix-band { grid-column: 2 / 8; display: block;
  border-top: 1px solid var(--c-line); padding-top: var(--gutter); }
.mix-band:focus-visible { outline: 2px solid var(--c-ink); outline-offset: 3px; }
/* шесть треков возвращают содержимое на колонки 2 / 3 / 4-5 / 7 */
.mix-inner { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--gutter);
  align-items: start; }
.mix-count { grid-column: 1 / 2; display: block; font-size: var(--fs-body); line-height: 1.3; }
/* счёт — одна строка кеглем подписи: не заголовок, а такая же мета, как год выше */
.mix-num { font-weight: 400; }
.mix-name { grid-column: 2 / 3; display: block; font-size: var(--fs-body); line-height: 1.3; }
.mix-title, .mix-sub { display: block; }
.mix-sub { opacity: .5; }
.mix-go { grid-column: 6 / 7; display: inline-flex; align-items: center; gap: .4em;
  font-size: var(--fs-lead); line-height: 1.1; white-space: nowrap; }
/* кнопка ровно как [data-btn]: скобки текстом, системная стрелка, 0 -> 45 */
.mix-go svg { width: .92em; height: .92em; flex-shrink: 0; display: block; overflow: visible;
  fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: butt; stroke-linejoin: miter; }
.mix-go [data-rot] { transform: rotate(0deg); transform-box: fill-box; transform-origin: center;
  transition: transform .3s var(--ease); }
.mix-band:hover .mix-go [data-rot], .mix-band:focus-visible .mix-go [data-rot] { transform: rotate(45deg); }

/* лента кадров: колонки 4-5. Высота у всех одна, ширина считается из файла */
/* по бокам кадр не обрубается кромкой: маска уводит его в бумагу,
   а сама волна добавляет у краёв размытие (BLUR_EDGE в main.js) */
.mix-strip { grid-column: 3 / 5; position: relative; display: block; height: 216px;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 64px, #000 calc(100% - 64px), transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 64px, #000 calc(100% - 64px), transparent 100%); }
.mix-frame { position: absolute; left: 0; top: 50%; height: 132px; width: auto;
  margin-top: -66px; display: block; object-fit: cover; object-position: center;
  transform-origin: 50% 50%; transform: translate3d(-9999px, 0, 0);
  will-change: transform, filter; backface-visibility: hidden; }

@media (prefers-reduced-motion: reduce) {
  .mix-frame { filter: none !important; }
  .mix-go [data-rot] { transition: none; }
}

@media (min-width: 901px) and (max-width: 1303px) {
  .mix-band { grid-column: 1 / 5; }
  .mix-inner { grid-template-columns: minmax(96px, 0.8fr) minmax(160px, 2.2fr) minmax(155px, 0.9fr); }
  .mix-count { grid-column: 1 / 2; }
  .mix-name { grid-column: 2 / 3; }
  .mix-go { grid-column: 3 / 4; justify-self: end; font-size: var(--fs-body); }
  .mix-strip { grid-column: 1 / 4; grid-row: 2; margin-top: 20px; }
}

@media (max-width: 900px) {
  .mix-band { grid-column: 1 / -1; }
  .mix-inner { grid-template-columns: minmax(0, 1fr) auto; gap: var(--gutter); }
  .mix-count { grid-column: 1; grid-row: 1; }
  .mix-go { grid-column: 2; grid-row: 1; justify-self: end; align-self: baseline;
    font-size: var(--fs-lead); }
  .mix-name, .mix-strip { grid-column: 1 / -1; }
  .mix-count, .mix-name { font-size: 14px; }
  .mix-title, .mix-sub { display: inline; }   /* имя и подпись одной строкой */
  .mix-strip { height: 132px; }
  .mix-frame { height: 96px; margin-top: -48px; }
}

/* --- маркер [+] и окно описания --- */
.cv-hint { font-size: var(--fs-body); justify-self: start; }
.cv-more { position: relative; display: inline-flex; align-items: baseline; gap: 0; font-size: var(--fs-body); background-image: none; padding-bottom: 0; }
.cv-more .plus-br { display: inline-block; }
/* поворот одного глифа: + непрерывно переходит в ×, подмена символа дала бы скачок */
.cv-more .plus { display: inline-block; padding: 0 .18em; transform: rotate(0deg); transition: transform .25s var(--ease); }
.cv-more.is-open .plus { transform: rotate(45deg); }

.more-pop { position: fixed; left: 0; top: 0; z-index: 40; width: 440px; padding: 20px; display: block; background: var(--c-ink); color: var(--c-paper); opacity: 0; visibility: hidden; filter: blur(10px); transform: translate3d(var(--px, 0px), calc(var(--py, 0px) + 6px), 0); transition: opacity .12s linear, filter .12s linear, transform .12s var(--ease), visibility 0s linear .12s; pointer-events: none; }
.cv-more.is-open .more-pop { opacity: 1; visibility: visible; filter: blur(0px); transform: translate3d(var(--px, 0px), var(--py, 0px), 0); transition: opacity .2s linear, filter .2s linear, transform .45s var(--ease), visibility 0s; }
.more-text { display: block; font-size: var(--fs-body); line-height: 1.3; color: var(--c-paper); text-transform: uppercase; text-align: left; white-space: pre-wrap; text-wrap: pretty; word-break: normal; overflow-wrap: normal; hyphens: none; }
/* буквы остаются inline: inline-block схлопнул бы пробелы между словами */
.more-text [data-ch] { display: inline; opacity: 0; filter: blur(4px); transition: opacity .16s linear, filter .16s linear; }
.more-text [data-ch].is-lit { opacity: 1; filter: blur(0px); }

@media (max-width: 900px) { .more-pop { width: calc(100vw - 40px); } }

/* --- CASE 1 (макет Case 1.pdf): та же семёрка, медиа слева / комментарий в 7-й --- */
.c2-6 { grid-column: 2 / 7; }
.c2-7 { grid-column: 2 / 8; }
.case-hero { margin-top: var(--step); }
.case-sec { margin-top: 120px; }
.case-row { align-items: start; }
.case-row + .case-row { margin-top: var(--gutter); }
.case-label { font-size: var(--fs-body); }
.case-body { font-size: var(--fs-body); text-wrap: pretty; }
.case-task { font-size: var(--fs-lead); text-wrap: pretty; }
.case-cap { font-size: var(--fs-body); opacity: .5; text-wrap: pretty; }
.case-media { position: relative; display: block; width: 100%; background: var(--c-placeholder); border: 1px solid var(--c-frame); overflow: hidden; }
.case-media img { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
.case-media > video, .case-media > canvas { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
/* ролик с прозрачностью показывает canvas (см. bindAlphaVideos); само видео
   остаётся в слоте невидимым и того же размера — по нему работает observer */
video.is-mirrored { position: absolute; inset: 0; opacity: 0; pointer-events: none; }
.case-media::after { content: attr(data-label); position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; padding: 0 20px; font-size: var(--fs-small); letter-spacing: .08em; color: var(--c-placeholder-ink); text-align: center; }
.case-media:has(img)::after, .case-media:has(video)::after, .case-media:has(iframe)::after { display: none; }
.case-media:has(img), .case-media:has(video), .case-media:has(iframe) { border-color: transparent; }
@supports not selector(:has(img)) { .case-media.has-media::after { display: none; } .case-media.has-media { border-color: transparent; } }
.case-next { margin-top: 120px; }
.case-next .case-label { margin-bottom: var(--gutter); }

@media (min-width: 901px) and (max-width: 1303px) {
  .c2-6, .c2-7 { grid-column: 1 / 4; }
  .case-sec, .case-next { margin-top: 80px; }
  .case-hero { margin-top: 40px; }
}

@media (max-width: 900px) {
  .c2-6, .c2-7 { grid-column: 1 / -1; }
  .case-sec, .case-next { margin-top: var(--step); }
  .case-hero { margin-top: 28px; }
  .case-task { font-size: var(--fs-body); }
  .case-row + .case-row { margin-top: 12px; }
}


/* --- CASE 1: медиа слева (2–5 или 2–7), комментарий всегда 6–7 --- */
.c6-7 { grid-column: 6 / 8; }
.c3-5 { grid-column: 3 / 6; }
.c5-7 { grid-column: 5 / 8; }
.case-head { margin-top: 0; }
.case-title { font-size: var(--fs-case-title); line-height: 48px; letter-spacing: 0; white-space: normal; text-wrap: pretty; }
.case-hero { margin-top: 24px; }
.case-note .case-label { margin-bottom: 12px; }
@media (min-width: 901px) and (max-width: 1303px) {
  .c6-7 { grid-column: 4 / 5; }
  .c3-5 { grid-column: 2 / 4; }
  .c5-7 { grid-column: 3 / 5; }
  /* адрес почты в 22 знака шире четвёртого трека (174 px на 1100) — контакты
     идут строкой под именем, а не тянут страницу вбок */
  .resume-head .r-contacts { grid-column: 1 / -1; text-align: left; }
  .case-title { font-size: 28px; line-height: 38px; }
}

@media (max-width: 900px) {
  .c6-7, .c3-5, .c5-7 { grid-column: 1 / -1; }
  .case-title { font-size: 22px; line-height: 30px; }
  .case-hero { margin-top: 16px; }
}

/* --- CASE 1: ритм 20/60, без плейсхолдерных рамок и подложек --- */
.case-sec { margin-top: var(--step); }
.case-hero { margin-top: var(--gutter); }
.case-note .case-label { margin-bottom: var(--gutter); }
.case-next { margin-top: var(--step); }
.case-media { background: none; border: 0; }
.case-media:has(img), .case-media:has(video) { border: 0; }
@supports not selector(:has(img)) { .case-media.has-media { border: 0; } }
.nav a:hover, .nav button:hover, .nav a:focus-visible { text-decoration: none; }

/* 901–1303: комментарий уходит в широкий трек, медиа — в левые два */
@media (min-width: 901px) and (max-width: 1303px) {
  /* четыре трека этой сетки не держат схему «медиа слева / комментарий справа»:
     раскладываем в две строки на полную ширину контента */
  .case-row .c6-7, .case-sec .c6-7 { grid-column: 1 / 5; }
  .case-row .c2-5, .case-row .c2-7, .case-row .c3-5 { grid-column: 1 / 5; }
  .case-row .c6-7 { margin-top: var(--gutter); }
  /* пользовательские inline-размеры отменяем: иначе горизонтальный скролл */
  .case-media, .case-body { width: auto !important; left: auto !important; height: auto !important; }
  .case-media { position: relative; }
}
@media (max-width: 900px) {
  .case-media, .case-body { width: auto !important; left: auto !important; height: auto !important; }
}

/* серая подложка только там, где контента ещё нет */
.case-media { background: none; border: 0; }
.case-media:not(:has(img, video, iframe)) { background: var(--c-placeholder); border: 1px solid var(--c-frame); }
@supports not selector(:has(img)) { .case-media:not(.has-media) { background: var(--c-placeholder); border: 1px solid var(--c-frame); } }
.c2-4 .case-media, .case-media.c2-4 { max-width: 100%; }

/* --- CASE 1: контейнер справа от меню = колонки 2–7, вертикальный поток шаг 60 --- */
.case-col { grid-column: 2 / 8; display: grid; gap: var(--step); }
.case-block { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--gutter); align-items: start; }
.case-block > .b-media { grid-column: 1 / 5; display: grid; gap: var(--gutter); align-content: start; }
.case-block > .b-text { grid-column: 5 / 7; display: grid; gap: var(--step); align-content: start; }
.case-block > .b-full { grid-column: 1 / 7; }
.b-row2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gutter); }
/* Подпись, стоящая на нижней границе медиа. Фиксированный отступ, снятый
   с макета 1920, на других ширинах уводил её ниже кадра. */
.case-block > .b-text:has(.b-pin) { align-self: stretch; align-content: stretch; }
.b-note:has(.b-pin) { display: flex; flex-direction: column; height: 100%; }
@supports not selector(:has(img)) { .case-block > .b-text.has-pin { align-self: stretch; align-content: stretch; } .b-note.has-pin { display: flex; flex-direction: column; height: 100%; } }
.b-note > .b-pin { margin-top: auto; }
.b-note { display: grid; gap: var(--gutter); }
/* заголовок и теги стоят на одной нижней линии: у одностроч. заголовка
   иначе оставался провал до обложки, а у двустрочного расхождения не было */
.case-head .case-title { grid-column: 1 / 5; align-self: end; }
.case-head .case-cap { grid-column: 5 / 7; text-align: right; align-self: end; opacity: 1; }
.case-col .case-media { width: 100%; }
.case-col .case-body, .case-col .case-media { width: auto; }

@media (max-width: 1303px) {
  /* первый трек этой сетки — зазор под фиксированное меню, контент начинается со второго */
  .case-col { grid-column: 2 / -1; gap: 40px; }
  .case-block { grid-template-columns: minmax(0, 1fr); }
  .case-block > .b-media, .case-block > .b-text, .case-block > .b-full,
  .case-head .case-title, .case-head .case-cap { grid-column: 1 / -1; }
  .case-head .case-cap { text-align: left; }
  .case-block > .b-text { gap: 40px; }
}

@media (max-width: 900px) { .case-col { grid-column: 1 / -1; } }

/* секция = вертикальный стек рядов «медиа 4 кол. + свой комментарий 2 кол.» */
.case-stack { display: block; }
.case-stack > .case-block + .case-block { margin-top: 0; }

/* меню: никаких подчёркиваний при наведении/фокусе, ни у ссылок, ни у кнопок */
.nav a, .nav button, .nav a:hover, .nav button:hover, .nav a:focus, .nav button:focus,
.nav a:focus-visible, .nav button:focus-visible, .nav a:active, .nav button:active {
  text-decoration: none; border-bottom: 0; background-image: none;
}

/* активный раздел отмечен точкой [•], без подчёркивания */
nav a, nav button, nav a:hover, nav button:hover, nav a:focus, nav button:focus,
nav a:focus-visible, nav button:focus-visible, nav a:active, nav button:active,
nav a[aria-current="page"] {
  text-decoration: none; border-bottom: 0; background-image: none;
}

/* вертикальный ролик занимает половину медиа-зоны, а не все четыре колонки */
.b-half { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--gutter); }
.b-half > .case-media { grid-column: 1; }

/* «[СЛЕДУЩИЙ]» шире одной колонки — уводим подпись в свою строку над карточкой */
.case-next .row { row-gap: var(--gutter); }
.case-next .year { grid-column: 2 / 4; grid-row: 1; white-space: nowrap; }
.case-next .name { grid-row: 2; }
.case-next .shot { grid-row: 2; }
.case-next .tags { grid-row: 2; }
@media (max-width: 1303px) {
  .case-next .year, .case-next .name, .case-next .shot, .case-next .tags { grid-column: 1 / -1; grid-row: auto; }
}

/* пара роликов: каждый по 2 колонки, выравнивание по верху */
.b-row2 { align-items: start; }

/* ряды внутри медиа-зоны: три, четыре и пять элементов в строку */
.b-row3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--gutter); align-items: start; }
.b-row4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--gutter); align-items: start; }
.b-row5 { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--gutter); align-items: start; }
@media (max-width: 900px) {
  .b-row3, .b-row4, .b-row5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* нумерованный список сценариев: маркер идёт в строку, чтобы текст
   начинался ровно на границе колонки, а не с отступом под цифру */
.scenarios { list-style: none; margin: 0; padding: 0; counter-reset: scenario; }
.scenarios li { counter-increment: scenario; }
.scenarios li::before { content: counter(scenario) ". "; }

/* метрики результата: тире перед строкой, тем же приёмом — без отступа */
.metrics { list-style: none; margin: 0; padding: 0; }
.metrics li::before { content: "— "; }

/* вводная и список метрик читаются одним абзацем: зазор сетки .b-note между ними не нужен */
.b-tight > * { margin: 0; }

/* обложка: кадр 16:9 обрезается до полосы 1540/449, и центральная обрезка
   подходит не всякому сюжету. Точку кадрирования задаёт --crop на самом
   слоте: 0% — виден верх исходника, 100% — низ, 50% — центр (по умолчанию) */
.case-media.is-crop > video,
.case-media.is-crop img { object-position: center var(--crop, 50%); }

/* узкие медиа-зоны кейса: колонки страницы 2–4 и 3–4 */
.case-block > .b-c2-4 { grid-column: 1 / 4; display: grid; gap: var(--gutter); align-content: start; }
.case-block > .b-c3-4 { grid-column: 2 / 4; display: grid; gap: var(--gutter); align-content: start; }
@media (max-width: 1303px) { .case-block > .b-c2-4, .case-block > .b-c3-4 { grid-column: 1 / -1; } }

/* поток контейнера задаётся margin-top на блоках, а не общим gap */
.case-col { gap: 0; }
.case-col > .case-block:first-child, .case-col > .case-stack:first-child { margin-top: 0; }
.case-next { margin-top: var(--step); }

.case-col > .case-hero { margin-top: var(--gutter); }

/* блок «Следующий»: всё в одном ряду, выравнивание по верхнему краю */
.case-next .row { grid-auto-rows: auto; align-items: start; row-gap: 0; }
.case-next .year, .case-next .name, .case-next .shot, .case-next .tags { grid-row: 1; align-self: start; margin: 0; }
.case-next .year { grid-column: 2 / 3; white-space: nowrap; }
.case-next .name { grid-column: 3 / 4; }
.case-next .shot { grid-column: 4 / 7; }
.case-next .tags { grid-column: 7 / 8; }
@media (max-width: 1303px) {
  .case-next .year, .case-next .name, .case-next .shot, .case-next .tags { grid-column: 1 / -1; grid-row: auto; }
  .case-next .row { row-gap: var(--gutter); }
}

/* ряд «Следующий»: наведение в любом месте ряда поднимает превью */
.case-next .row { cursor: pointer; }
/* наезд в блоке «Следующий» одинаковый и для стоп-кадра, и для ролика,
   иначе на наведении они разъезжаются друг относительно друга */
.case-next .slot .media img, .case-next .slot .media video { transition: transform 0.55s var(--ease); }
.case-next .slot.is-hover .media img, .case-next .slot.is-hover .media video { transform: scale(1.03); }

/* курсор-подпись на увеличиваемом контенте */
.cursor-text { white-space: nowrap; font: 400 18px/1 var(--font-ui); letter-spacing: 0.04em; opacity: 0; transform: scale(0.82); transition: opacity .26s var(--ease), transform .34s var(--ease); }
.cursor.is-label .cursor-text { opacity: 1; transform: scale(1); }

/* просмотр крупнее: не на весь экран */
/* фон не только гаснет, но и уходит в расфокус — то же блюр-проявление, что и у блоков */
.zoom-ov { position: fixed; inset: 0; z-index: 9000; display: grid; place-items: center; background: var(--c-overlay); opacity: 0; -webkit-backdrop-filter: blur(0px); backdrop-filter: blur(0px); transition: opacity 0.6s var(--ease-zoom), backdrop-filter 0.6s var(--ease-zoom), -webkit-backdrop-filter 0.6s var(--ease-zoom); }
.zoom-ov.is-in { opacity: 1; -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px); }
.zoom-box { width: min(78vw, calc(78vh * var(--r, 1))); max-width: 78vw; max-height: 78vh; overflow: hidden; transform: scale(0.96); opacity: 0; filter: blur(8px); transition: transform 0.78s var(--ease-zoom), opacity 0.5s var(--ease-zoom), filter 0.7s var(--ease-zoom); will-change: transform, opacity, filter; }
.zoom-ov.is-in .zoom-box { transform: scale(1); opacity: 1; filter: blur(0px); }
.zoom-box img, .zoom-box > video, .zoom-box > canvas { display: block; width: 100%; height: 100%; object-fit: contain; }
@media (max-width: 900px) { .zoom-box { max-width: 92vw; max-height: 72vh; } }

/* проявление блоков при скролле */
/* Проявление размытием — единственный приём для всего, что не набирается:
   изображений, видео, блоков, длинных абзацев, меню и подписей. Слой и
   изоляция перерисовки только на время перехода, иначе десятки постоянных
   слоёв роняют первый скролл. */
.reveal { opacity: 0; filter: blur(10px); transform: translate3d(0, 12px, 0);
  transition: opacity 520ms var(--ease), filter 520ms var(--ease), transform 520ms var(--ease); }
.reveal.is-in { opacity: 1; filter: blur(0px); transform: translate3d(0, 0, 0); }
.reveal.is-animating { will-change: opacity, filter, transform; contain: paint; }
/* После проявления фильтр и сдвиг снимаются совсем: blur(0px) — всё ещё
   фильтр, и Safari держит текст в слое фильтра — заголовок рисуется мягче
   и грубее, чем остальной текст. Меню оставляет transform: им центрируется. */
.reveal.is-in:not(.is-animating), .reveal.is-in:not(.is-animating) > .b-text { filter: none; }
.reveal.is-in:not(.is-animating):not(.nav) { transform: none; }
/* Размытие проявления — только для текстовых единиц. У строки списка,
   карточки главной и медиа-блока кейса оно раскладывает слой в тысячу
   пикселей каждый кадр, на 2x-экране — четыре миллиона пикселей на блок,
   и первые полсекунды страницы дёргаются. Им остаются прозрачность
   и сдвиг; текст внутри медиа-блока (.b-text) проявляется размытием сам. */
.row.reveal, [data-card].reveal, .case-block.reveal, .case-stack.reveal,
.home-figure.reveal, .home-work > .reveal, .mix-band.reveal { filter: none; }
/* Меню центрируется собственным translateY(-50%), а проявление тоже пишет
   transform — и затирало центрирование: меню съезжало вниз на половину
   своей высоты. На главной этого не было видно, там центрирование инлайном. */
.nav.reveal { transform: translate3d(0, calc(-50% + 12px), 0); }
.nav.reveal.is-in { transform: translateY(-50%); }
.reveal > .b-text { transition: opacity 520ms var(--ease) .12s, filter 520ms var(--ease) .12s; opacity: 0; filter: blur(10px); }
.reveal.is-in > .b-text { opacity: 1; filter: blur(0px); }

/* Содержимое прячется до первой отрисовки. Класс ставит скрипт: без JS
   правило не сработает и человек увидит обычную страницу, а не белый лист. */
html.js-intro body { opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .reveal, .reveal > .b-text { opacity: 1; filter: none; transform: none; transition: none; }
  [data-ch] { opacity: 1 !important; filter: none !important; visibility: visible !important; }
  html.js-intro body { opacity: 1; }
  .lang-cur { animation: none; }
  /* глобальный запрет переходов выше гасит и это, поэтому важность:
     страница всё же должна появиться, а не выскочить */
  .page-in.is-calm.is-in { transition: opacity 150ms linear !important; }
}
@media print {
  .reveal, .reveal > .b-text { opacity: 1 !important; filter: none !important; transform: none !important; }
  [data-ch] { opacity: 1 !important; filter: none !important; visibility: visible !important; }
  html.js-intro body { opacity: 1 !important; }
}


/* мудборд: левая колонка уже правой, пропорция 377/623 снята с макета */
.b-mood { display: grid; grid-template-columns: 377fr 623fr; gap: var(--gutter); align-items: start; }
.b-mood > .b-col { display: grid; gap: var(--gutter); align-content: start; }
@media (max-width: 900px) { .b-mood { grid-template-columns: minmax(0, 1fr); } }

/* Плашки геолокации: единственное место, где к моноширинному набору
   добавлен второй шрифт — так задано в макете кейса «Московский меч».
   Кегль привязан к ширине слота, чтобы высота прописных держала
   макетные 65 px при слоте 1020: 0.74 em × 8.63cqw × 1020 = 65 */
@font-face {
  font-family: "Druk Cyr";
  src: url("../fonts/DrukCyr-Medium.ttf") format("truetype");
  font-weight: 500; font-style: normal; font-display: swap;
}
/* подложка под прозрачный мокап: цвет из самих мокапов, не интерфейса */
.case-media.mock-bg { background: #F2F2F7; }
.case-media.b-plate { container-type: inline-size; display: grid; place-items: center;
  background: var(--c-placeholder); border: 0; }
.case-media.b-plate::after { content: none; }
.b-plate > span { font-family: "Druk Cyr", var(--font); font-weight: 500;
  font-size: 8.63cqw; line-height: 1; letter-spacing: 0; white-space: nowrap; color: var(--c-ink); }

/* Коллажи кейса NADIR. Пропорции колонок сняты с макета:
   референсы — крупный кадр и два узких столбца, финал — вертикаль и столбец. */
.b-refs { display: grid; grid-template-columns: 501fr 241fr 241fr; gap: var(--gutter); align-items: start; }
.b-final { display: grid; grid-template-columns: 403fr 600fr; gap: var(--gutter); align-items: start; }
.b-refs > .b-col, .b-final > .b-col { display: grid; gap: var(--gutter); align-content: start; }
@media (max-width: 900px) {
  .b-refs, .b-final { grid-template-columns: minmax(0, 1fr); }
}

/* Кадры петли пилона: у грани и торца разные ширины экрана, высота общая.
   Ширины заданы инлайном в долях медиа-зоны, промежутки распределяет флекс. */
.b-loop { display: flex; justify-content: space-between; align-items: flex-start; }
@media (max-width: 900px) {
  .b-loop { flex-wrap: wrap; gap: var(--gutter); justify-content: flex-start; }
  /* две петли из четырёх, одной высоты: ширины из пропорций файлов
     (356/1920 + 714/1920 = 0.557), высота от ширины колонки */
  .b-loop { flex-wrap: nowrap; justify-content: space-between; }
  .b-loop > .case-media { width: auto !important; max-width: none;
    height: calc((100cqw - var(--gutter)) / 0.557); }
  .b-loop > .case-media:nth-child(n + 3) { display: none; }
}

/* Встроенный плеер занимает слот целиком: у него свой запуск и свой
   полноэкранный режим, наш просмотр крупнее его не перехватывает. */
.case-media > iframe { display: block; width: 100%; height: 100%; border: 0; }
.case-media.is-embed { background: var(--c-ink); }

/* Стрелки просмотра: переключают кадры кейса, не закрывая окно.
   Набор тот же бракетный, что у [МЕНЮ] и [ЗАКРЫТЬ], цвет — бумага. */
/* знак тот же, что на карточках проектов: скобки и диагональ внутри */
.zoom-nav svg { display: block; width: 37px; height: 30px; overflow: visible;
  fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: butt; stroke-linejoin: miter; }
.zoom-nav [data-rot] { transform: rotate(45deg); transform-box: fill-box; transform-origin: center; }
.zoom-nav [data-lb], .zoom-nav [data-rb] { transform: translateX(0); transition: transform .55s var(--ease-slot); }
.zoom-nav[data-dir="prev"] svg { transform: scaleX(-1); }
.zoom-nav:hover [data-lb], .zoom-nav:focus-visible [data-lb] { transform: translateX(-3.5px); }
.zoom-nav:hover [data-rb], .zoom-nav:focus-visible [data-rb] { transform: translateX(3.5px); }
.zoom-nav { position: absolute; top: 50%; transform: translateY(-50%); z-index: 1;
  margin: 0; padding: 20px; border: 0; background: none; color: var(--c-paper);
  opacity: .55; transition: opacity .25s var(--ease); }
.zoom-nav:hover, .zoom-nav:focus-visible { opacity: 1; }
.zoom-nav[data-dir="prev"] { left: 40px; }
.zoom-nav[data-dir="next"] { right: 40px; }
/* Ссылка на внешний проект: тот же знак у верхнего правого угла кадра.
   Обёртка нужна только как точка отсчёта — размер задаёт сам кадр. */
.zoom-wrap { position: relative; }
.zoom-link { position: absolute; left: 100%; top: 0; margin-left: 24px; padding: 8px;
  color: var(--c-paper); opacity: .55; transition: opacity .25s var(--ease); }
.zoom-link:hover, .zoom-link:focus-visible { opacity: 1; }
.zoom-link svg { display: block; width: 37px; height: 30px; overflow: visible;
  fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: butt; stroke-linejoin: miter; }
.zoom-link [data-rot] { transform: rotate(0deg); transform-box: fill-box; transform-origin: center;
  transition: transform .3s var(--ease); }
.zoom-link [data-lb], .zoom-link [data-rb] { transition: transform .3s var(--ease); }
.zoom-link:hover [data-rot], .zoom-link:focus-visible [data-rot] { transform: rotate(45deg); }
.zoom-link:hover [data-lb], .zoom-link:focus-visible [data-lb] { transform: translateX(-3.5px); }
.zoom-link:hover [data-rb], .zoom-link:focus-visible [data-rb] { transform: translateX(3.5px); }
@media (max-width: 900px) {
  .zoom-nav { padding: 12px; }
  .zoom-link { left: auto; right: 0; top: auto; bottom: 100%; margin: 0 -8px 8px 0; }
  .zoom-nav[data-dir="prev"] { left: 4px; }
  .zoom-nav[data-dir="next"] { right: 4px; }
}

/* --- «Солянка»: полотно с работами --- */
/* Сцена — окно во всю оставшуюся высоту экрана; полотно внутри втрое больше
   и ездит трансформом. Скролл документа здесь не участвует. */
.pg-stage { position: relative; width: 100%; height: calc(100vh - 260px); min-height: 420px;
  margin-top: var(--margin); overflow: hidden; touch-action: none; cursor: grab; }
.pg-stage:active { cursor: grabbing; }
.pg-stage:focus-visible { outline: 1px solid var(--c-ink); outline-offset: -1px; }
.pg-canvas { position: absolute; left: 0; top: 0; will-change: transform; }

.pg-work { position: absolute; left: 0; top: 0; margin: 0; will-change: transform;
  transform-origin: 0 0; }
.pg-work:focus-visible { outline: 1px solid var(--c-ink); outline-offset: 6px; }
.pg-media { display: block; width: 100%; overflow: hidden; }
.pg-media img, .pg-media > video { display: block; width: 100%; height: 100%; object-fit: cover; }
.pg-cap { margin: 8px 0 0; font-size: var(--fs-body); color: var(--c-ink); }
/* Работа со внешним проектом: стрелка-ссылка после подписи, в рост строки */
.pg-link { display: inline-block; vertical-align: -0.35em; margin-left: 6px; color: var(--c-ink); }
.pg-link svg { display: block; width: 29px; height: 24px; overflow: visible;
  fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: butt; stroke-linejoin: miter; }
.pg-link [data-rot] { transform: rotate(0deg); transform-box: fill-box; transform-origin: center;
  transition: transform .3s var(--ease); }
.pg-link [data-lb], .pg-link [data-rb] { transition: transform .3s var(--ease); }
.pg-link:hover [data-rot], .pg-link:focus-visible [data-rot] { transform: rotate(45deg); }
.pg-link:hover [data-lb], .pg-link:focus-visible [data-lb] { transform: translateX(-2.5px); }
.pg-link:hover [data-rb], .pg-link:focus-visible [data-rb] { transform: translateX(2.5px); }

/* Подсказка о том, как двигать полотно. После первого касания она не
   исчезает совсем, а уходит в полутон: способ управления неочевиден,
   и напоминание должно оставаться под рукой. */
.pg-hint { position: absolute; left: 0; right: 0; bottom: 48px; margin: 0;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  font-size: var(--fs-lead); color: var(--c-ink); pointer-events: none;
  transition: opacity .4s var(--ease); }
.pg-hint.is-off { opacity: .32; }
.pg-hand { flex: none; display: block; }

/* Расфокус у краёв экрана: четыре слоя backdrop-filter, маска — рамка по
   периметру, сужающаяся от слоя к слою. Один слой давал бы ступеньку. */
.pg-edge { position: absolute; inset: 0; pointer-events: none; }
.pg-edge-l { position: absolute; inset: 0; display: block;
  -webkit-mask-composite: source-over; mask-composite: add; }
.pg-edge-l[data-l="1"] { -webkit-backdrop-filter: blur(2.5px); backdrop-filter: blur(2.5px);
  -webkit-mask-image: linear-gradient(to right, #000, transparent 140px), linear-gradient(to left, #000, transparent 140px), linear-gradient(to bottom, #000, transparent 140px), linear-gradient(to top, #000, transparent 140px);
  mask-image: linear-gradient(to right, #000, transparent 140px), linear-gradient(to left, #000, transparent 140px), linear-gradient(to bottom, #000, transparent 140px), linear-gradient(to top, #000, transparent 140px); }
.pg-edge-l[data-l="2"] { -webkit-backdrop-filter: blur(5px); backdrop-filter: blur(5px);
  -webkit-mask-image: linear-gradient(to right, #000, transparent 105px), linear-gradient(to left, #000, transparent 105px), linear-gradient(to bottom, #000, transparent 105px), linear-gradient(to top, #000, transparent 105px);
  mask-image: linear-gradient(to right, #000, transparent 105px), linear-gradient(to left, #000, transparent 105px), linear-gradient(to bottom, #000, transparent 105px), linear-gradient(to top, #000, transparent 105px); }
.pg-edge-l[data-l="3"] { -webkit-backdrop-filter: blur(7.5px); backdrop-filter: blur(7.5px);
  -webkit-mask-image: linear-gradient(to right, #000, transparent 70px), linear-gradient(to left, #000, transparent 70px), linear-gradient(to bottom, #000, transparent 70px), linear-gradient(to top, #000, transparent 70px);
  mask-image: linear-gradient(to right, #000, transparent 70px), linear-gradient(to left, #000, transparent 70px), linear-gradient(to bottom, #000, transparent 70px), linear-gradient(to top, #000, transparent 70px); }
.pg-edge-l[data-l="4"] { -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  -webkit-mask-image: linear-gradient(to right, #000, transparent 35px), linear-gradient(to left, #000, transparent 35px), linear-gradient(to bottom, #000, transparent 35px), linear-gradient(to top, #000, transparent 35px);
  mask-image: linear-gradient(to right, #000, transparent 35px), linear-gradient(to left, #000, transparent 35px), linear-gradient(to bottom, #000, transparent 35px), linear-gradient(to top, #000, transparent 35px); }

/* На тач размытие снимаем: четыре backdrop-filter на весь экран стоят кадров
   и батареи, а перетаскивание пальцем важнее украшения. */
@media (hover: none), (max-width: 900px) { .pg-edge { display: none; } }
@media (prefers-reduced-motion: reduce) { .pg-edge { display: none; } .pg-hint { transition: none; } }
@media (max-width: 900px) { .pg-stage { height: calc(100vh - 200px); } }

/* --- «Солянка»: полотно во всё окно --- */
/* Раньше сцена начиналась под заголовком, и работы обрезались об эту линию.
   Теперь окно и есть сцена: обрез только по краю экрана, как и ожидается. */
.pg-page { overflow: hidden; }
/* Фон здесь обязателен: difference смешивается с тем, что уже нарисовано,
   а фон страницы в подложку смешения не входит — без него меню белым по белому. */
.pg-page .pg-stage { position: fixed; inset: 0; width: auto; height: auto;
  min-height: 0; margin: 0; z-index: 0; background: var(--c-paper); }

/* Шапка и меню лежат поверх работ и читаются на любом фоне: difference
   делает их чёрными на бумаге и белыми на тёмном кадре. Смешение стоит на
   самой .page, а не на заголовке внутри: собственный контекст наложения
   отрезал бы заголовок от подложки. */
.pg-page .page { position: relative; z-index: 2; pointer-events: none;
  padding-top: 40px; mix-blend-mode: difference; color: var(--c-paper); }
.pg-page .page a, .pg-page .page button { pointer-events: auto; }
/* Смешивание включаем только когда под меню действительно проехала работа.
   Слой с mix-blend-mode теряет субпиксельное сглаживание, и на белом
   полотне меню выглядело жирнее, чем на остальных страницах. */
.pg-page .nav { z-index: 12; }
.pg-page .nav.is-over { mix-blend-mode: difference; color: var(--c-paper); }
.pg-page .nav.is-over a, .pg-page .nav.is-over button { color: var(--c-paper); }

/* Полотно не выделяется и не перетаскивается средствами браузера:
   родное перетаскивание картинки перехватывало указатель и рвало драг. */
.pg-stage, .pg-stage * { -webkit-user-select: none; user-select: none; -webkit-user-drag: none; }
.pg-media img, .pg-media > video { pointer-events: none; }

/* Подсказка лежит поверх работ и инвертируется на них, как меню:
   z-index выше любого плана (планы до 135), белый цвет через difference
   на бумаге читается чёрным. */
.pg-page .pg-hint { z-index: 200; mix-blend-mode: difference; color: var(--c-paper); }


/* --- КЕЙСЫ: инлайн -> классы ---
   Вертикальный ритм кейса идёт двумя шагами: 20 внутри блока, 60 между
   блоками. Оба стояли инлайном у каждой секции и перебивали обнуления
   .case-stack > .case-block + .case-block и .case-col > *:first-child.
   Классы обязаны перебить их тоже, поэтому селекторы из трёх частей
   и место в самом конце файла. */
.case-col .case-block.mt-step, .case-col .case-stack.mt-step { margin-top: var(--gutter); }
.case-col .case-block.mt-block, .case-col .case-stack.mt-block { margin-top: var(--step); }
.case-col.mt-step { margin-top: var(--gutter); }
.case-col.mt-block { margin-top: var(--step); }
.page-center { display: grid; justify-content: center; }
.case-head { min-height: 64px; }
.case-hero { padding-top: 0; padding-bottom: 0; }
.row .next-label { font-size: var(--fs-lead); }
.case-next .row { align-items: stretch; justify-content: flex-start; display: grid; padding: 0; }
/* кнопка на Sostav несёт стрелку блоком и красит её текущим цветом —
   у остальных кнопок стрелка строчная, поэтому правило отдельное */
.b-pin [data-arrow] { display: block; overflow: visible; stroke: currentColor;
  vertical-align: baseline; }

/* имя страницы видно только в мобильной полосе */
.nav-name { display: none; }

/* --- МОБИЛЬНЫЕ: токены и полоса меню -------------------------------------
   Ниже 900 меняются сами токены, а не отдельные правила: поле страницы,
   шаг ритма, кегли и число колонок. Всё, что считает от них, переезжает
   само собой. --track-h1 срезан сознательно: пятнадцать знаков имени при
   трекинге 36% на ширине 350 дают кегль около 24, то есть заголовок
   сравнялся бы с основным набором. */
@media (max-width: 900px) {
  :root {
    --margin: 20px;
    --gutter: 16px;
    --fs-lead: 18px;
    --fs-body: 14px;
    --cols: 1;
    --track-h1: 0.05em;
    --bar-h: 44px;
    --step: 48px;
  }

  /* Колонки 1 нет, фиксированному меню слева жить негде: становится полосой */
  .nav {
    position: sticky; top: 0; z-index: 20;
    display: grid; grid-template-columns: 1fr auto;
    align-items: center; column-gap: var(--gutter);
    min-height: var(--bar-h); padding: 0 var(--margin);
    margin: 0; background: var(--c-paper);
    border-bottom: 1px solid var(--c-line);
    transform: none;
  }
  .nav.reveal, .nav.reveal.is-in { transform: none; }
  .nav-name { display: block; grid-row: 1; grid-column: 1;
    font-size: var(--fs-body); line-height: 1; }
  .nav .lang { grid-row: 1; grid-column: 2; margin: 0;
    display: flex; gap: var(--gutter); }
  /* якорей разделов на телефоне нет: пять пунктов в строку не влезают,
     а в панели они путались бы со списком страниц. В панели остаются
     только переходы между страницами. */
  .nav > ul:first-of-type { display: none; }
  .nav [data-menu-panel] { grid-row: 2; grid-column: 1 / -1; }
  .nav a, .nav button, .nav .is-current { min-height: var(--bar-h);
    display: inline-flex; align-items: center; }
  .nav [data-menu-panel] a { min-height: 44px; }

  /* полоса не должна накрывать якорь, к которому только что перешли */
  [id] { scroll-margin-top: calc(var(--bar-h) + 8px); }

  .page { padding: var(--gutter) var(--margin) 0; }

  /* Общие детали. Наклон и параллакс построены на курсоре, которого нет;
     заодно уходит выпуск медиа на 6% за слот — он тянул ширину документа. */
  .slot, [data-slot] { transform: none; will-change: auto; }
  .media, [data-media]:not(.portrait-media) { inset: 0; transform: none; }
  .go { transform: none; }
  /* колонка одна: всё, что стояло в 7-й, встаёт в поток; ширины в пикселях
     снимаются — на 320 они снова вылезли бы за экран */
  .foot-socials, .home-contact-col { grid-column: 1 / -1; }
  .projects-lede, .home-contact-lede, .home-lede, .home-contact-col {
    width: auto; max-width: none; }
  .desk-only { display: none; }        /* «контакты и соцсети — справа»: тут они ниже */
  [data-btn] { white-space: nowrap; }

  /* --- подвал ярусами (макет mobile-footer.html) ---
     Призыв, приглашение, соцсети, низ — каждая зона отделена линией.
     Обёртка колонки раскрывается (display: contents), чтобы соцсети
     встали между приглашением и низом: в разметке они соседний блок. */
  /* подвал: шаг между блоками до линии, нижнее поле последней строки
     снято, чтобы шаг не складывался с ним */
  .foot-contact, .home-contact { padding-top: var(--step); padding-bottom: 0; }
  .row-mix, .case-next .row { margin-bottom: 0; }
  .foot-contact > .c2-4, .home-contact-col { display: contents; }

  /* призыв — якорь подвала: одна строка на всю ширину колонки, как
     заголовок. Кегль от ширины контейнера: строка «[ LET'S TALK ↗ ]»
     с промежутками кнопки — 9.32em, значит 100cqw / 9.32 = 10.73cqw
     заполняют колонку ровно; остаток от округления глифов разбирает
     space-between, скобки стоят по краям поля. */
  .foot-talk { order: 1; box-sizing: border-box; width: 100%; margin: 0; padding: 26px 0 0;
    justify-content: space-between;
    border-top: 1px solid var(--c-line);
    font-size: 10.7cqw; line-height: 1.1; }

  /* приглашение вплотную к призыву: зазор сетки секции гасим наполовину,
     остаток — свес скобок призыва */
  .projects-lede, .home-contact-lede { order: 2; padding: 0 0 18px; opacity: .75;
    margin-top: calc(-1 * var(--gutter) / 4);
    border-bottom: 1px solid var(--c-line); }

  /* соцсети списком: стрелка после названия, как во всей системе, но
     выключена по правому краю — стрелки собираются в столбик.
     Подчёркивание text-decoration: оно ложится под слово, а не под
     всю строку с промежутком до стрелки. */
  .foot-socials { order: 3; margin: 0; padding: 6px 0; display: block; }
  .foot-socials li { display: block; }
  .foot-socials [data-ul] { display: flex; width: 100%; justify-content: space-between;
    align-items: center; padding: 11px 0; margin: 0; font-size: var(--fs-lead);
    background-image: none;
    text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 4px; }

  /* низ: копирайт, CV и почта одной строкой (на 390 — 312 px из 350),
     на 320 почта уходит на вторую. Линии над низом нет: он продолжает
     список соцсетей. По базовой линии, а не по центру: у подчёркнутых
     ссылок высоту добирает верхнее поле. */
  .foot-credits { order: 4; display: flex; flex-wrap: wrap; align-items: baseline;
    gap: 0 var(--gutter); margin: 0; padding: 12px 0 26px; }

  .sink { margin-top: 0; }
}

/* --- МОБИЛЬНЫЕ: главная --- */
@media (max-width: 900px) {
  .page-home { padding: var(--gutter) var(--margin) 0; }
  /* заголовок выпущен за колонку на бок скобки и хвостовой трекинг: там
     пусто, но на 768 этот выпуск давал горизонтальную прокрутку */
  .page, .page-home { overflow-x: clip; }
  /* колонка одна: любой диапазон колонок — во всю ширину. Старый список
     покрывал не все классы, и .c3-6 уезжал в неявные треки */
  .c2, .c3, .c4, .c5, .c6, .c7, .c2-3, .c2-4, .c2-5, .c2-6, .c2-7,
  .c3-5, .c3-6, .c4-5, .c4-6, .c5-6, .c6-7 { grid-column: 1 / -1; }
  /* портрет: выпуск на 3% за кадр на 768 вылезал за экран */
  .portrait-media { left: 0; width: 100%; }
  /* полоса: у главной свой список языка, не .lang */
  .nav .nav-lang-row { grid-row: 1; grid-column: 2; margin: 0; }
  .nav .nav-lang { display: flex; gap: var(--gutter); }
  /* заголовок: без автофита на этой странице, кегль считается от ширины
     контейнера — пятнадцать знаков при трекинге 0.05em ложатся в 350 px */
  .h1-row { height: auto; }
  .h1-home { grid-column: 1 / -1; font-size: 10.8cqw; margin: 0 -0.27em var(--gutter) -0.22em;
    padding: 0 .05em 0 0; justify-content: flex-start; }
  /* первый экран (макет 390): теги в две колонки · координаты по краям ·
     портрет · лид · фокус справа. Обёртки колонок распускаются в contents,
     чтобы их дети встали в один поток */
  .home-hero { margin-bottom: calc(var(--gutter) * 4); align-items: start; row-gap: calc(var(--gutter) / 2); }
  .home-hero > .c3, .home-hero > .c6 { display: contents; }
  /* теги: слева коммуникация · бренд · системы · продуктовый подход,
     справа 3D · AI · аналитика · motion — порядок макета, не списка.
     Девятый (арт-дирекшн) в макете не поместился. */
  .home-services { order: 1; display: grid; grid-template-columns: minmax(0, 1fr) auto;
    column-gap: var(--gutter); row-gap: 2px; }
  .home-services > li { margin: 0; }
  .home-services > li:nth-child(1) { grid-column: 1; grid-row: 1; }
  .home-services > li:nth-child(2) { grid-column: 1; grid-row: 2; }
  .home-services > li:nth-child(3) { grid-column: 1; grid-row: 3; }
  .home-services > li:nth-child(7) { grid-column: 1; grid-row: 4; }
  .home-services > li:nth-child(5) { grid-column: 2; grid-row: 1; text-align: right; }
  .home-services > li:nth-child(6) { grid-column: 2; grid-row: 2; text-align: right; }
  .home-services > li:nth-child(8) { grid-column: 2; grid-row: 3; text-align: right; }
  .home-services > li:nth-child(4) { grid-column: 2; grid-row: 4; text-align: right; }
  .home-services > li:nth-child(9) { display: none; }
  /* координаты: широта слева, долгота справа, над портретом */
  /* координаты — подпись к портрету: стоят по краям, чуть ниже макушки
     (у кадра над плашкой 8.5 % пустого поля с волосами по центру) */
  .home-geo { order: 2; margin: calc(var(--gutter) * 2) 0 0;
    display: flex; justify-content: space-between; }
  .home-geo br { display: none; }
  .home-figure { order: 3; margin-top: calc(-1 * var(--gutter) - var(--gutter) / 2); }
  .home-lede { order: 4; margin-top: calc(var(--gutter) * 1.5); }
  .home-focus { order: 5; margin: calc(var(--step) - var(--gutter) / 2) 0 0; padding-left: 20%;
    text-align: right; }
  /* работы: превью · название · превью · название, кнопка — в конце */
  .home-work { row-gap: calc(var(--gutter) * 3); margin-bottom: 0; }
  .home-work > .card-col { display: contents; }
  .card-col > [data-btn] { order: 9; padding-top: 0; margin-top: 0;
    justify-self: center; font-size: 24px; }
  .card-meta { gap: var(--gutter); margin-top: var(--gutter); }
  .card-arrow { width: 44px; height: 36px; }
  /* тап-цели не ниже 44 px; у подчёркнутых ссылок высоту добирает
     верхнее поле, а не min-height с прижимом к низу: при flex-end стрелка
     садилась ниже строки. Текст со стрелкой остаются по центру строки,
     линия — под словом. */
  [data-btn] { min-height: 44px; }
  [data-ul] { padding-top: calc(44px - 1em * var(--lh-body) - 3px); }
}

/* --- МОБИЛЬНЫЕ: About --- */
@media (max-width: 900px) {
  /* окно по [+] раскрывается под строкой, а не поповером у края экрана:
     на 390 фиксированное окно с отступом 20 закрывало бы половину экрана */
  .more-pop { position: static; width: auto; margin-top: var(--gutter);
    display: none; opacity: 1; visibility: visible; filter: none; transform: none;
    transition: none; pointer-events: auto; }
  .cv-more.is-open .more-pop { display: block; transform: none; }
  /* видео 2804 x 2956: квадратный кадр срезал макушку */
  .about-hero .portrait { aspect-ratio: 2804 / 2956; }
  /* запись опыта: роль и [+] в одной строке, [+] у правого края, ниже
     организация и даты, окно описания — на всю ширину под записью.
     Ссылка [+] распускается в contents: иначе окно жило бы в её узкой
     колонке. Скобки и плюс — три соседних трека без зазора. */
  .cv-row { grid-template-columns: minmax(0, 1fr) auto auto auto; column-gap: 0; }
  .cv-row .cv-name { grid-column: 1; grid-row: 1; padding-right: var(--gutter); }
  .cv-row .cv-org { grid-column: 1 / -1; grid-row: 2; }
  .cv-row .cv-when { grid-column: 1 / -1; grid-row: 3; }
  .cv-row .cv-more { display: contents; }
  .cv-more > .plus-br:first-child { grid-column: 2; grid-row: 1; }
  .cv-more > .plus { grid-column: 3; grid-row: 1; }
  .cv-more > .plus + .plus-br { grid-column: 4; grid-row: 1; }
  .cv-more > .more-pop { grid-column: 1 / -1; grid-row: 4; }
}

/* --- МОБИЛЬНЫЕ: «Солянка» --- */
@media (max-width: 900px) {
  /* размытие у краёв — четыре слоя backdrop-filter на весь экран; на телефоне
     это батарея, а не украшение. Левитацию выключает сам скрипт по (hover: none). */
  .pg-edge { display: none; }
  /* полоса меню на телефоне непрозрачная: difference с белым фоном
     превращал её в негатив работы под ней. Полотно начинается под полосой. */
  .pg-page .nav { z-index: 30; }
  .pg-page .nav.is-over { mix-blend-mode: normal; color: var(--c-ink); }
  .pg-page .nav.is-over a, .pg-page .nav.is-over button { color: var(--c-ink); }
  .pg-page .pg-stage { top: var(--bar-h); }
}

/* --- МОБИЛЬНЫЕ: полоса прокрутки ---
   На телефоне она наложенная и ширины не занимает, а в эмуляции съедает
   15 px и сетка честно считается под неверное число. Прячем отрисовку,
   прокрутка остаётся. На десктопе не трогаем: на кейсе в 7700 px полоса —
   единственный указатель, где ты находишься. */
@media (max-width: 900px) {
  html { scrollbar-width: none; }
  html::-webkit-scrollbar { display: none; width: 0; height: 0; }
}

/* --- МОБИЛЬНЫЕ: ритм кейса ---
   Два шага, как на десктопе: --gutter внутри блока, --step между.
   Текст блока идёт первым: ярлык [РАЗДЕЛ] открывает блок, а не подписывает
   картинку над ним. Пустая текстовая колонка не занимает ряд — иначе
   к шагу между блоками добавлялся бы её зазор. Литерал 40 в правиле
   до 1303 оставлен четырём трекам, здесь шаг из токена. */
@media (max-width: 900px) {
  .case-block > .b-text { order: -1; gap: var(--step); }
  .case-block > .b-text:empty { display: none; }
  /* блок с ярлыком — начало раздела и в одной колонке: шаг между блоками,
     даже если на десктопе он стоял вплотную (mt-step) к соседу по ряду */
  .case-col .case-block.mt-step:has(.b-note > .case-label:first-child) { margin-top: var(--step); }
  @supports not selector(:has(img)) { .case-col .case-block.mt-step.has-label-first { margin-top: var(--step); } }
}

/* --- МОБИЛЬНЫЕ: геро кейса ---
   Первый кадр у всех кейсов одной рамки — той же, что у строк списка.
   Родная пропорция файла тут не действует: широкие лента и ролик РИА
   кадрируются в неё cover, как и на десктопе (точку даёт --crop). */
@media (max-width: 900px) {
  /* :has — отдельным правилом: в списке селекторов он обнулил бы правило
     целиком там, где не поддержан, и герой потерял бы пропорцию */
  .case-hero .case-media, .case-hero .case-media.is-native { aspect-ratio: 1540 / 450 !important; }
  .case-hero .case-media:has(img) { aspect-ratio: 1540 / 450 !important; }
  @supports not selector(:has(img)) { .case-hero .case-media.has-img { aspect-ratio: 1540 / 450 !important; } }
  .case-hero .case-media img, .case-hero .case-media > video { height: 100%; }
}

/* --- МОБИЛЬНЫЕ: блоки кейса в родных пропорциях ---
   На широком холсте блок кейса — слот, в который файл кадрируется.
   В одной колонке кадр узкий, и от такого кропа он теряет сюжет:
   слот перестаёт задавать высоту, её даёт сам файл. Строки списка
   проектов, наоборот, держат одну рамку на все (см. .shot выше).
   Картинке пропорцию сообщают атрибуты width/height, ролику — метаданные:
   main.js кладёт её в --ar и ставит .is-native, а до них блок держит
   пропорцию слота, иначе он прыгал бы через заглушку 2:1. Там, где слот
   и файл сходятся ближе 3 %, разницы не видно вовсе. Плашки и встраивания
   без файла (.b-plate, .is-embed) остаются в пропорции слота. */
@media (max-width: 900px) {
  .case-media:has(img) { aspect-ratio: auto !important; }
  @supports not selector(:has(img)) { .case-media.has-img { aspect-ratio: auto !important; } }
  .case-media img { height: auto; }
  /* .is-fixed — слот намеренный и на телефоне: у файла пустые поля,
     которые кроп в квадрат срезает без потерь (бейджи РИА) */
  .case-media.is-fixed:has(img) { aspect-ratio: 1 / 1 !important; }
  @supports not selector(:has(img)) { .case-media.is-fixed.has-img { aspect-ratio: 1 / 1 !important; } }
  .case-media.is-fixed img { height: 100%; }
  .case-media.is-native { aspect-ratio: var(--ar) !important; }
}

/* --- МОБИЛЬНЫЕ: подвал ---
   Высота полосы та же, что на десктопе — 130 читались обрезком; кегль
   мельче, чтобы строк в ней было столько же. Механика та же. */
@media (max-width: 900px) {
  .sink__line { font-size: 10px; }
}

/* --- CV: страница резюме. Разметка та же семёрка: ярлык и даты в колонке 2,
   текст в 3–5 (мера ~80 знаков), организация в 6–7. Строки записей —
   .cv-row с About: та же линия и шаг. --- */
.cv-actions { display: flex; flex-wrap: wrap; gap: var(--gutter); justify-content: flex-end; align-self: center; }
.cv-actions button[data-btn] { border: 0; background: none; padding: 0; margin: 0; color: inherit;
  font-family: inherit; line-height: inherit; text-transform: inherit; cursor: pointer; }
.resume { display: grid; margin-top: 100px; }   /* как .about-hero после заголовка */
.resume .grid { align-items: start; }
/* ритм тот же, что у About: шаг между блоками, промежуток от ярлыка
   к его записям и между соседними записями */
.resume > .grid + .grid { margin-top: var(--step); }
.resume > .r-head + .grid, .resume > .cv-row + .cv-row { margin-top: var(--gutter); }
.resume p, .resume address { margin: 0; font-style: normal; }
.resume .r-name { font-size: var(--fs-case-title); line-height: 48px; }
.resume .r-alias { font-size: var(--fs-body); opacity: .5; }
.resume .r-role { margin-top: var(--gutter); }
.resume .r-contacts { text-align: right; line-height: 1.6; }
.resume .r-contacts [data-ul] { text-transform: none; }   /* адреса — как набраны */
.resume .r-title { margin-bottom: 8px; }
.resume .r-list { list-style: none; margin: 0; padding: 0; }
.resume .r-list li { position: relative; padding-left: 2ch; }
.resume .r-list li::before { content: "—"; position: absolute; left: 0; }
.resume .r-list li + li { margin-top: 4px; }
.resume .r-courses li::before, .resume .r-skills li::before { content: "_"; }
.resume .cv-row { padding-bottom: var(--gutter); }
@media (max-width: 900px) {
  .cv-actions { justify-content: center; margin-top: var(--gutter); }
  .resume { margin-top: var(--step); }
  .resume .grid { row-gap: 8px; }
  .resume > .r-head + .grid { margin-top: 8px; }
  .resume .r-contacts { text-align: left; margin-top: var(--gutter); }
  .resume .r-name { font-size: var(--fs-lead); line-height: 1.2; }
  .resume .r-org { opacity: .5; }
}

/* --- Печать резюме: лист A4, без меню, кнопок, подвала и полотна.
   Сетка сворачивается в три дорожки: даты · текст · организация.
   Ширина листа 794 px попадает под мобильные правила — то, что они
   меняют (полутон организации, кегль имени, выключка), здесь возвращается. --- */
@media print {
  @page { size: A4; margin: 11mm 12mm; }
  .cv-page .nav, .cv-page .cv-actions, .cv-page .foot-contact, .cv-page .sink,
  .cv-page .cursor, .cv-page .transition-veil, .cv-page .grid-mid { display: none !important; }
  html { zoom: 1 !important; }   /* масштаб ноутбука (fitCanvas) на бумагу не идёт */
  body.cv-page { background: #fff; font-size: 10px; line-height: 1.4; }
  .cv-page .page { max-width: none; width: auto; margin: 0; padding: 0; display: block; }
  .resume { display: grid; margin-top: 0; font-size: 10px; line-height: 1.4; }
  .resume > .grid + .grid { margin-top: 3mm; }
  .resume > .r-head + .grid, .resume > .cv-row + .cv-row { margin-top: 1.5mm; }
  .resume .grid { display: grid; grid-template-columns: 34mm 1fr 38mm; column-gap: 5mm; row-gap: 1mm; }
  .resume .cv-row p, .resume p { font-size: inherit; line-height: inherit; }
  /* ярлык раздела не остаётся один внизу листа */
  .resume .grid:has(> .r-label:only-child) { break-after: avoid; }
  .resume .grid > .c2 { grid-column: 1; }
  .resume .grid > .c3-5 { grid-column: 2; }
  .resume .grid > .c3-6 { grid-column: 2 / 4; }
  .resume .grid > .c6-7 { grid-column: 3; }
  .resume .grid > .c2-5 { grid-column: 1 / 3; }
  .resume-head { padding-bottom: 3mm; border-bottom: 1px solid #111; }
  .resume .r-name { font-size: 18px; line-height: 1.15; }
  .resume .r-alias { font-size: 10px; }
  .resume .r-role { margin-top: 2mm; }
  .resume .r-contacts { text-align: right; line-height: 1.5; margin: 0; }
  .resume .r-contacts br { display: none; }
  .resume .r-contacts > span, .resume .r-contacts > a { display: block; padding: 0; }
  .resume .r-org { opacity: 1; }
  .resume .r-title { margin-bottom: 1mm; }
  .resume .r-list li + li { margin-top: .5mm; }
  .resume .cv-row { break-inside: avoid; padding-bottom: 1.5mm; border-bottom: 1px solid #d7d7d7; }
  .resume .cv-row::after { display: none; }
  .resume [data-ul] { background: none; border: 0; }
}

/* --- МОБИЛЬНЫЕ: переносы ---
   Текст идёт во всю колонку, но последняя строка абзаца не должна быть
   одним словом; предлоги к следующему слову клеит main.js (glue). */
@media (max-width: 900px) {
  p, h2, li, .mix-sub { text-wrap: pretty; }
}

/* --- МОБИЛЬНЫЕ: ссылка со статьёй (Россия) ---
   Кнопка слева, подпись справа в две строки, по базовой линии последней
   строки. Подпись кеглем подписи 12: с 14 обе не входят в 350. */
@media (max-width: 900px) {
  .b-note:has(> .b-pin + .case-cap) { display: grid; grid-template-columns: auto minmax(0, 1fr);
    height: auto; }
  .case-block:has(.b-pin + .case-cap) { row-gap: 8px; }   /* ссылка → ролик на полшага */
  .b-note:has(> .b-pin + .case-cap) > * { grid-column: 1 / -1; }
  .b-note:has(> .b-pin + .case-cap) > .b-pin { grid-column: 1; grid-row: 3; margin-top: 0;
    align-self: last baseline; }
  .b-note:has(> .b-pin + .case-cap) > .b-pin + .case-cap { grid-column: 2; grid-row: 3;
    align-self: last baseline; justify-self: end; max-width: 23ch;   /* перенос после «проекте» */
    text-align: right; font-size: var(--fs-small); }
  @supports not selector(:has(img)) {
    .b-note.has-pin-cap { display: grid; grid-template-columns: auto minmax(0, 1fr); height: auto; }
    .case-block.has-pin-cap { row-gap: 8px; }
    .b-note.has-pin-cap > * { grid-column: 1 / -1; }
    .b-note.has-pin-cap > .b-pin { grid-column: 1; grid-row: 3; margin-top: 0; align-self: last baseline; }
    .b-note.has-pin-cap > .b-pin + .case-cap { grid-column: 2; grid-row: 3; align-self: last baseline;
      justify-self: end; max-width: 23ch; text-align: right; font-size: var(--fs-small); }
  }
}

/* --- МОБИЛЬНЫЕ: строка «следующий» и теги ---
   Кнопка слева, имя следующего проекта справа в той же строке, ниже
   превью, ниже теги. Теги в одну строку: перенос из словаря нужен
   только узкой седьмой колонке десктопа. */
@media (max-width: 900px) {
  .case-next .row { grid-template-columns: auto minmax(0, 1fr); column-gap: var(--gutter); row-gap: 8px; }
  /* по базовой линии: кнопка выше строки имени на 44 px тап-цели */
  .case-next .year { grid-column: 1; grid-row: 1; align-self: last baseline; }
  /* без тап-цели в 44: её низ уводил превью от кнопки; ниже всё превью — ссылка */
  .case-next .year [data-btn] { min-height: 0; }
  .case-next .name { grid-column: 2; grid-row: 1; align-self: last baseline;
    justify-self: end; text-align: right; }
  .case-next .shot { grid-column: 1 / -1; grid-row: 2; }
  .case-next .tags { grid-column: 1 / -1; grid-row: 3; }
  .row .tags br { display: none; }
}

/* Нажатие. Системную серую подсветку iOS снимаем (свойство наследуется —
   одной строкой на html), взамен — свой полутон на само нажимаемое:
   ссылки и кнопки, слоты «крупнее». Контейнеры (строка, карточка) не
   в списке: у них внутри своя ссылка, иначе полутон умножался бы.
   Мгновенно — длительность переходов на время нажатия нулевая; у слота
   на это время замирает и переход ширины, отпустили — идёт дальше. */
html { -webkit-tap-highlight-color: transparent; }
a:active, button:active, [data-zoom]:active { opacity: .55; transition-duration: 0s; }
/* у кого покой уже полутон — при нажатии темнее, иначе отклика не видно */
.nav a[data-nav]:active, .zoom-nav:active, .zoom-link:active { opacity: .3; }
