/* =========================================================================
   ИВОЛВ — landing
   Дизайн-система: J Audio Cassette (заголовки) + Jost (наборный текст), Signal Orange, белый фон.
   Токены зафиксированы до вёрстки. Произвольных значений на месте — нет.
   ========================================================================= */

/* ---- Шрифт: J Audio Cassette, self-hosted (заголовки) --------------------- */
@font-face{
  font-family:'J Audio Cassette';
  src:url('assets/fonts/j-audio-cassette/JAudioCassette-Regular.woff2') format('woff2');
  font-weight:400; font-style:normal; font-display:swap;
}

/* ---- Шрифт: Jost, self-hosted (вариативный, наборный текст) --------------- */
@font-face{
  font-family:'Jost';
  src:url('assets/fonts/jost/Jost-Variable-cyrillic.woff2') format('woff2');
  font-weight:400 700; font-style:normal; font-display:swap;
  unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116;
}
@font-face{
  font-family:'Jost';
  src:url('assets/fonts/jost/Jost-Variable-latin.woff2') format('woff2');
  font-weight:400 700; font-style:normal; font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+20AC,U+2122;
}

/* ---- Токены --------------------------------------------------------------- */
:root{
  /* Цвет */
  --paper:#FFFFFF;
  --ink:#0A0A0A;
  --muted:#6B6B6B;
  --faint:#767676;   /* самый светлый серый, проходящий WCAG AA на белом (4.54:1). Было #9A9A9A — 2.81:1, ниже порога для 12px-текста и ссылки на политику */
  --line:#E6E6E6;
  --line-strong:#0A0A0A;
  /* Signal Orange, сдвинутый чуть ближе к красному: H10° вместо H12° у
     #EC3A0F. Светлота опущена 49%→46.5% ради контраста: при L49% белый текст
     на заливке давал 4.18:1, ниже порога WCAG AA (4.5:1) для кегля мельче
     24px — то есть все кнопки и строки направлений при наведении. Hue и
     насыщенность не тронуты (H10° S88%). Теперь 4.58:1 — и для белого на
     заливке, и для акцента текстом на белом (контраст симметричен).
     Выбрано из восьми кандидатов на временной странице сравнения. */
  --accent:#DF310E;         /* было: #EB340F (L49%) ← #EC3A0F — Signal Orange */
  --accent-ink:#C02A0C;     /* затемнённый акцент для текста на белом (AA) */

  /* Инверсная поверхность — семейство для тёмных секций (форма, CTA кейсов).
     Раньше эти шесть значений стояли прямо в правилах: #B9B9B9 повторялся
     трижды, #FF8A72 дважды, остальные по разу. Вынесены не ради DRY, а ради
     смысла: имя говорит роль, хекс не говорит ничего, а на тёмном нужна своя
     шкала — --muted/--faint рассчитаны на белый фон и здесь не работают. */
  --inverse-lead:#B9B9B9;         /* вторичный текст: лид формы, статус, текст успеха */
  --inverse-label:#CFCFCF;        /* подписи полей */
  --inverse-line:#3A3A3A;         /* нижняя граница поля в покое */
  --inverse-placeholder:#5E5E5E;  /* текст-подсказка в полях */
  --error-line:#FF6A4D;           /* граница поля с ошибкой */
  --error-text:#FF8A72;           /* текст сообщения об ошибке */

  /* Типографика */
  --font:'Jost', system-ui, sans-serif;
  --font-heading:'J Audio Cassette', 'Jost', system-ui, sans-serif;
  --fs-xs:0.75rem;   /* 12 */
  --fs-sm:0.875rem;  /* 14 */
  --fs-base:1rem;    /* 16 */
  --fs-md:1.125rem;  /* 18 */
  --fs-lg:1.375rem;  /* 22 */
  --fs-xl:1.875rem;  /* 30 */
  --fs-2xl:2.75rem;  /* 44 */
  --fs-3xl:4rem;     /* 64 */

  /* Резиновая шкала заголовков — продолжение наборной вверх. Наборная выше
     дискретна: ступени для текста. Здесь границы, между которыми clamp()
     ездит по ширине экрана. Раньше эти пять значений стояли числами прямо в
     правилах, из-за чего шкала выглядела единственным источником правды,
     хотя половина заголовков ей не следовала. Размеры не изменились ни на
     пиксель — просто получили имена. */
  --fs-fluid-1:2rem;     /* 32 — минимум заголовков страниц и hero на ≤720 */
  --fs-fluid-2:2.25rem;  /* 36 — минимум hero на ≤1024 */
  --fs-fluid-3:2.5rem;   /* 40 — минимум hero на десктопе, максимум на ≤720 */
  --fs-fluid-4:3.25rem;  /* 52 — максимум hero на ≤1024 */
  --fs-fluid-5:4.5rem;   /* 72 — максимум hero на ≥1600 */

  /* Отступы (8-база) */
  --space-1:4px; --space-2:8px; --space-3:12px; --space-4:16px;
  --space-5:24px; --space-6:32px; --space-7:48px; --space-8:64px;
  --space-9:96px; --space-10:128px;

  /* Линии */
  --hair:1px; --line-1:1.5px; --line-2:2px;

  /* Геометрия / движение */
  --radius:0px;
  --ease-signal:cubic-bezier(.16,1,.3,1);   /* «snap» синхронизации */
  --dur:.6s;

  /* Сетка */
  --maxw:1440px;
  --gutter:24px;
  --edge:clamp(20px, 5vw, 64px);
  --edge-left:calc(max(0px, (100vw - var(--maxw)) / 2) + var(--edge));
  --header-h:64px;
}

/* ---- Reset / базовое ------------------------------------------------------ */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{-webkit-text-size-adjust:100%}
/* Плавность скролла: на десктопе — через Lenis (initSmoothScroll в script.js),
   на тач-устройствах — нативным scroll-behavior ниже. Lenis на телефоне не
   инициализируется вовсе: пользы для прокрутки он там не даёт, а обработчики
   касаний вешает, из-за чего скролл залипает. При выключенном JS остаётся
   нативное поведение, при reduced-motion — мгновенный переход (блок в конце). */
@media (hover:none),(pointer:coarse){
  html{scroll-behavior:smooth}
}
/* Служебные правила Lenis — из его lenis.css (1.3.25), перенесены сюда,
   чтобы не тянуть отдельный файл. Классы .lenis/.lenis-smooth ставит на <html>
   сама библиотека, только на десктопе, где она включена. */
html.lenis,html.lenis body{height:auto}
.lenis:not(.lenis-autoToggle).lenis-stopped{overflow:clip}
.lenis [data-lenis-prevent]{overscroll-behavior:contain}
.lenis.lenis-smooth iframe{pointer-events:none}
[id]{scroll-margin-top:var(--header-h)}   /* шапка fixed — компенсируем переход по якорям. Без лишнего "воздуха": у about/services/cases/voices есть собственный .rule (border-top) — его надо ставить впритык к шапке, а не в 16px от неё, иначе получаются две независимые полоски рядом. Lenis сам читает это значение в scrollTo(), доп. offset в JS не нужен и даже вреден (см. initSmoothScroll) */
body{
  font-family:var(--font);
  font-weight:400;
  font-size:var(--fs-base);
  line-height:1.6;
  color:var(--ink);
  background:var(--paper);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
h1,h2,h3{font-family:var(--font-heading);font-weight:400;color:var(--ink)}
a{color:inherit;text-decoration:none}
button{font-family:inherit;cursor:pointer;border:0;background:none;color:inherit}
input,textarea,select{font-family:inherit;font-size:inherit;color:inherit}
img,svg,canvas{display:block;max-width:100%}
::selection{background:var(--accent);color:var(--paper)}

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

/* ---- Утилиты каркаса ------------------------------------------------------ */
.shell{max-width:var(--maxw);margin-inline:auto;padding-inline:var(--edge)}
.accent{color:var(--accent-ink)}

/* Разделительная линия между секциями */
.rule{border-top:var(--hair) solid var(--line)}

/* =========================================================================
   Шапка — по умолчанию живёт ВНУТРИ левой колонки hero (см. разметку),
   никакой полосы над картинкой. JS (initHeaderPin) добавляет .is-pinned,
   когда hero прокручен мимо — тогда шапка становится обычным fixed-баром
   на всю ширину, как на остальных секциях.
   ========================================================================= */
.site-header{position:relative;z-index:50}
.site-header__inner{
  display:flex;align-items:center;justify-content:flex-start;
  gap:var(--space-8);
  height:var(--header-h);
}
.site-header.is-pinned{
  position:fixed;top:0;left:0;right:0;z-index:50;
  /* Блюр фиксированной шапки пересчитывается каждый кадр по всему, что проезжает
     под ней — самый дорогой эффект на странице. Снизили 8px→4px и подняли
     непрозрачность подложки 88%→92%, чтобы текст под шапкой всё так же не
     просвечивал: визуально почти без разницы (фон белый), по стоимости кадра
     скролла — примерно вдвое дешевле, заметно в Safari и на слабых машинах. */
  background:color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter:saturate(120%) blur(4px);
  -webkit-backdrop-filter:saturate(120%) blur(4px);
  border-bottom:var(--hair) solid var(--line);
}
.site-header.is-pinned .site-header__inner{
  max-width:var(--maxw);margin-inline:auto;padding-inline:var(--edge);
}
.brand{display:inline-flex;align-items:center;color:var(--ink);flex-shrink:0}
.brand svg{height:22px;width:auto;color:currentColor}

.nav{display:flex;align-items:center}
.nav__links{display:flex;gap:var(--space-6)}
.nav__link{
  font-size:var(--fs-sm);color:var(--muted);
  position:relative;padding-block:4px;transition:color .2s ease;
}
.nav__link::after{
  content:"";position:absolute;left:0;right:100%;bottom:0;height:var(--line-1);
  background:var(--accent);transition:right .28s var(--ease-signal);
}
.nav__link:hover{color:var(--ink)}
.nav__link:hover::after{right:0}

.btn{
  display:inline-flex;align-items:center;gap:var(--space-2);
  font-size:var(--fs-sm);font-weight:500;line-height:1;
  padding:12px 18px;border:var(--line-2) solid var(--ink);
  background:var(--ink);color:var(--paper);
  transition:background .2s ease,color .2s ease,transform .2s var(--ease-signal);
}
/* Браузеры на телефоне рисуют поверх нажатой ссылки свою полупрозрачную
   серую плашку. На чёрных кнопках она теряется, а на прозрачной («Все кейсы»)
   читается как случайная серая заливка — цвета, которого в системе нет.
   Гасим её и даём собственный отклик на нажатие: та же заливка, что у hover
   на десктопе. По правилу отклика из DESIGN.md акцент и должен появляться
   в ответ на действие, а нажатие — это действие. */
a,button,input,textarea,select,[role="button"]{-webkit-tap-highlight-color:transparent}
.btn:active{transform:translateY(1px);background:var(--accent);border-color:var(--accent);color:var(--paper)}
.btn--ghost:active{background:var(--ink);border-color:var(--ink);color:var(--paper)}
.cases-filter__btn:active{background:var(--ink);border-color:var(--ink);color:var(--paper)}

/* Заливки при наведении — только для настоящего курсора. На тач-экране :hover
   срабатывает от касания и залипает после него: кнопка остаётся залитой, пока
   не тронешь что-то другое. Отклик на нажатие там даёт :active выше. */
@media (hover:hover) and (pointer:fine){
  .form__consent a:hover{color:var(--paper)}
  @media (min-width:721px) and (max-width:1024px){
    .site-header:not(.is-pinned) .nav__link:hover{color:var(--paper);opacity:1}
  }
  .btn:hover{background:var(--accent);border-color:var(--accent);color:var(--paper)}
  .btn--ghost:hover{background:var(--ink);color:var(--paper)}
  .hero__visual-cta:hover{background:var(--accent);border-color:var(--accent);color:var(--paper)}
  .form .btn:hover{background:var(--accent);border-color:var(--accent);color:var(--paper)}
  .case-cta .btn:hover{background:var(--accent);border-color:var(--accent);color:var(--paper)}
  .cases-filter__btn:hover{border-color:var(--ink)}
}

/* Нажатие пальцем короткое, и переход 0.2s не успевает доехать: фон замирает
   на полпути между прозрачным и чернильным, а полупрозрачный чёрный поверх
   белого — это средне-серый rgb(133,133,133). Именно он и читался как
   «странный серый цвет» на кнопке «Все кейсы». Отклик на нажатие обязан быть
   мгновенным — снимаем у него длительность. Возврат остаётся плавным. */
@media (hover:none),(pointer:coarse){
  .btn:active,.btn--ghost:active,.cases-filter__btn:active{transition-duration:0s}
}
.btn--ghost{background:transparent;color:var(--ink)}
.btn--lg{font-size:var(--fs-base);padding:16px 24px}

.nav__toggle{
  display:none;flex-direction:column;justify-content:center;gap:5px;
  width:32px;height:32px;flex-shrink:0;position:relative;
}
/* Зона нажатия 44×44 (минимум Apple HIG) при видимых 32×32: невидимый
   ореол по 6px вокруг. Соседняя ссылка «← Все кейсы» отстоит на 12px+,
   так что зоны не пересекаются. */
.nav__toggle::before{content:"";position:absolute;inset:-6px}
.nav__toggle span{
  display:block;width:100%;height:var(--line-1);background:var(--ink);
  transition:transform .3s var(--ease-signal),opacity .2s ease;
}
.nav__toggle[aria-expanded="true"] span:first-child{transform:translateY(3.5px) rotate(45deg)}
.nav__toggle[aria-expanded="true"] span:last-child{transform:translateY(-3.5px) rotate(-45deg)}

/* =========================================================================
   Hero — split-screen на всю высоту экрана.
   Слева: шапка (см. выше) + заголовок, в отступах сайта. Справа: изображение
   от края до края и от верха до низа — без какой-либо полосы над ним,
   только плавающая кнопка в углу (как в референсах Nordgz/Nooma).
   ========================================================================= */
.hero{position:relative}
.hero__split{
  display:grid;
  grid-template-columns:minmax(380px, 3fr) minmax(300px, 2fr);   /* ≈ 60% / 40% */
  height:100vh;
  /* svh, а не dvh. dvh меняется, когда в мобильном браузере прячется и
     появляется адресная строка — а высота hero входит в общую высоту
     документа, поэтому при каждом таком изменении весь контент ниже
     сдвигался. Рывок ловился где угодно: и у заголовка «Свежие проекты»
     посреди страницы, и в самом низу при прокрутке вверх-вниз. svh — высота
     при полностью показанной обвязке браузера, она во время прокрутки не
     меняется. Цена: когда адресная строка спрячется, снизу покажется полоска
     следующей секции. Поддержка у svh та же, что у dvh. */
  height:100svh;   /* фоллбэк до вычисления --vh в JS (см. initRealVH в script.js) */
  height:calc(var(--vh, 1svh) * 100);   /* держит высоту в реальных px — вьюпортные единицы при зуме страницы рассинхронизируются с шириной grid-колонок, искажая object-fit:cover картинки */
}
.hero__content{
  display:flex;flex-direction:column;
  padding:var(--space-3) var(--edge) 0 var(--edge-left);   /* верх — уровень шапки=кнопке на картинке; низ — 0, весь остаток вертикали отдан центровке hero__body; слева — как у .shell, чтобы текст выровнялся с остальными секциями */
}
.hero__body{
  flex:1;
  display:flex;flex-direction:column;justify-content:center;
}
.hero__title{
  font-weight:400;
  font-size:clamp(var(--fs-fluid-3), 4.4vw, var(--fs-3xl));
  line-height:1.05;
  letter-spacing:-0.01em;
  overflow-wrap:break-word;
}
.hero__lead{
  margin-top:var(--space-6);
  max-width:38ch;
  font-size:var(--fs-md);
  color:var(--ink);
  line-height:1.55;
}
.hero__cta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-5);margin-top:var(--space-7)}
.hero__cta-contact{display:none}   /* показываем только на мобильном — на десктопе своя CTA есть на картинке */

/* Правая панель — чистое видео без фильтров, во весь экран. Постер задан
   ещё и здесь, CSS-фоном контейнера (не только атрибутом poster у <video>) —
   у <video poster> ненадёжный момент первого пейнта, часть браузеров рисует
   чёрный кадр до декодирования постера; background виден мгновенно, как
   только сама картинка загружена (см. preload as="image" в head index.html). */
.hero__visual{
  position:relative;
  overflow:hidden;
  background:var(--ink) url('assets/hero-visual-poster.webp') center/cover no-repeat;
}
.hero__visual video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;
  pointer-events:none;
}
.hero__visual-cta{
  position:absolute;top:var(--space-5);right:var(--space-5);z-index:2;
  background:var(--paper);border-color:var(--paper);color:var(--ink);
}
/* Маячок для initHeaderPin. Был position:relative — то есть стоял в потоке и
   добавлял ровно 1px между нижним краем hero и border-top следующей секции.
   На стыке это читалось как случайный светлый зазор под видео. Выносим из
   потока: .hero уже position:relative, поэтому absolute садится точно на её
   нижнюю границу — наблюдатель ловит ту же линию, а высоты секции не
   прибавляет. Порог в initHeaderPin (headerH+8) к сдвигу на пиксель
   нечувствителен. */
#hero-sentinel{position:absolute;left:0;right:0;bottom:0;height:1px}

/* =========================================================================
   О нас — узкая мера слева, карточки-принципы справа
   ========================================================================= */
.about{padding-block:var(--space-9)}
.about__grid{display:grid;grid-template-columns:minmax(0,3fr) minmax(0,4fr);gap:var(--space-8)}
.section-tag{
  font-family:var(--font-heading);
  font-size:var(--fs-sm);color:var(--accent-ink);font-weight:400;
  letter-spacing:.02em;
}
.about__head{align-self:start;position:sticky;top:96px}
/* Заголовки секций — резиновые со скоростью 4vw при 4.4vw у H1 страниц.
   Раньше здесь стоял фиксированный --fs-2xl (44px от 721 до 1599px), а H1
   задавались clamp() и росли плавно. В окне 721–999px они пересекались:
   H1 страницы кейсов был 32–44px против неизменных 44px у H2, то есть
   главный заголовок оказывался мельче подзаголовка. У .case-cta__title то же
   самое было и на телефоне — он не входил в медиазапрос ≤720px, который
   опускал остальные H2 до --fs-xl.
   4vw медленнее 4.4vw, поэтому H1 теперь минимум на 10% крупнее на любой
   ширине, а нижняя граница clamp() повторяет то, что делал тот медиазапрос. */
.about__title{font-size:clamp(var(--fs-xl), 4vw, var(--fs-2xl));line-height:1.05;letter-spacing:-.03em;margin-top:var(--space-4)}
.about__lead{max-width:44ch;color:var(--ink);margin-top:var(--space-5);line-height:1.55}

.ab__rail{display:flex;flex-direction:column;gap:14px;margin-top:var(--space-8)}
.ab__rail-dot{
  width:6px;height:6px;border-radius:50%;background:var(--line-strong);opacity:.25;
  transition:opacity .35s ease,transform .35s var(--ease-signal),background .35s ease;
}
.ab__rail-dot.is-active{background:var(--accent);opacity:1;transform:scale(1.6)}

.ab__steps{display:flex;flex-direction:column}
.ab-step{
  min-height:56vh;display:flex;flex-direction:column;justify-content:center;
  max-width:48ch;margin-inline:auto;   /* колонка справа (4fr) шире меры текста — центрируем блок в ней, а не прижимаем к левому краю */
  padding-block:var(--space-6);border-bottom:var(--hair) solid var(--line);
  opacity:.3;transition:opacity .5s var(--ease-signal);
  /* Шаг фиксирован по высоте (min-height выше), но контент внутри центрирован,
     поэтому при раскрытии описания текст уезжает вверх примерно на 25px, хотя
     сам блок стоит на месте. Браузерный scroll anchoring считает такой сдвиг
     поводом подкрутить scrollTop — то есть толкает страницу против скролла
     пользователя, и делает это каждый кадр те 0.5s, пока идёт max-height
     анимация. При быстрой прокрутке вверх подряд запускается несколько таких
     анимаций, компенсации накладываются — это и есть тряска. Высота документа
     при этом не меняется (проверено: 6332px при любом активном шаге), так что
     якорь здесь не нужен вовсе — исключаем поддерево из его выбора.
     В Safari свойство не поддерживается, но там нет и самого anchoring. */
  overflow-anchor:none;
}
.ab-step:last-child{border-bottom:0}
.ab-step.is-active{opacity:1}
.ab-step__label{
  font-family:var(--font-heading);font-weight:400;color:var(--ink);
  font-size:var(--fs-lg);line-height:1.05;letter-spacing:-.01em;
  transition:color .45s ease;
}
/* Раньше активный шаг рос до --fs-2xl (44px) — крупнее собственного H2 секции
   (.about__title, 30px). Акцент на активном шаге даёт opacity (см. .ab-step.is-active
   выше) и раскрытие описания — размер лейбла везде фиксированный, ниже H2. */
.ab-step__desc{
  color:var(--ink);margin-top:var(--space-3);line-height:1.55;
  opacity:0;max-height:0;overflow:hidden;
  transition:opacity .4s ease .1s,max-height .5s var(--ease-signal);
}
/* 8em, а не 6em. Предел здесь нужен только ради анимации раскрытия (высоту
   auto анимировать нельзя), но он же режет текст, если тот не уложился.
   Самое длинное описание — «Выделенный проджект» — занимает 6.21em на всех
   ширинах выше 1024, то есть прежние 6em обрезали последнюю строку. Запас до
   8em покрывает ещё одну строку: описания здесь правят руками, и правка в
   пару слов не должна молча отрезать хвост. Ниже 1025px ограничение снимается
   совсем (max-height:none в мобильном блоке) — там шаги раскрыты всегда. */
.ab-step.is-active .ab-step__desc{opacity:1;max-height:8em}

/* =========================================================================
   Направления — ряды, разлинованные, hover раскрывает
   ========================================================================= */
.services{padding-block:var(--space-8)}
.services__head{display:flex;justify-content:space-between;align-items:end;gap:var(--space-5);margin-bottom:var(--space-6)}
.services__title{font-size:clamp(var(--fs-xl), 4vw, var(--fs-2xl));letter-spacing:-.03em;line-height:1.1;margin-top:var(--space-4)}
.services__list{border-top:var(--line-2) solid var(--ink)}
.service{
  position:relative;
  display:grid;
  grid-template-columns:minmax(0,1fr) minmax(0,1.2fr);
  align-items:center;gap:var(--space-5);
  padding:var(--space-6) var(--space-3);
  border-bottom:var(--hair) solid var(--line);
}
.service::before{
  content:"";position:absolute;inset:0;background:var(--accent);
  opacity:0;transition:opacity .15s ease;z-index:0;
}
.service__name,.service__desc{position:relative;z-index:1;transition:color .15s ease}
.service__name{font-family:var(--font-heading);font-weight:400;font-size:var(--fs-lg);letter-spacing:-.01em}
.service__desc{color:var(--ink);font-size:var(--fs-sm);line-height:1.5}
/* Быстрый кроссфейд заливки целиком — 0.15s в обе стороны, чтобы при перемещении
   курсора между строками не оставался «плывущий» остаточный след предыдущей строки. */
@media (hover:hover) and (pointer:fine){
  .service:hover::before{opacity:1}
  .service:hover .service__name,.service:hover .service__desc{color:var(--paper)}
}

/* =========================================================================
   Кейсы — сетка 2×2 карточек, обложка + бейдж-уголок, dot-паттерн при hover
   ========================================================================= */
.cases{padding-block:var(--space-9)}
.cases__head{max-width:44ch;margin-bottom:var(--space-8)}
.cases__title{font-size:clamp(var(--fs-xl), 4vw, var(--fs-2xl));letter-spacing:-.03em;line-height:1;margin-top:var(--space-3)}
.cases__list{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-6)}
.case{display:block}
.case__media{
  position:relative;aspect-ratio:16/10;
  border:var(--hair) solid var(--line);
  background:var(--paper);
  overflow:hidden;
}
.case__img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;display:block;
  transition:transform .6s var(--ease-signal);
}
/* Эффекты карточки — только там, где есть настоящий курсор. На тач-экране
   :hover срабатывает от касания пальцем во время прокрутки: обложка 2560px
   начинала масштабироваться на 0.6s внутри overflow:hidden, бейдж крутился,
   и всё это перерисовывалось прямо посреди скролла — отсюда рывки, особенно
   при обратной прокрутке, когда палец проезжает по уже показанным карточкам.
   Та же защита, что стоит у строк направлений (.service ниже). */
@media (hover:hover) and (pointer:fine){
  .case:hover .case__img{transform:scale(1.05)}
}

/* Тач-версия того же эффекта. На десктопе его даёт :hover, на телефоне курсора
   нет — поэтому карточку подсвечивает не касание, а её положение: активной
   становится та, что ближе к центру экрана (initCaseFocus в script.js, та же
   механика, что у «дыхания» пунктов в блоке «О нас»).
   Разница с прежним поведением принципиальная: раньше эффект запускался от
   каждого касания пальцем во время прокрутки, по всем карточкам подряд и
   внахлёст — отсюда были рывки. Теперь активна ровно одна карточка, переход
   происходит предсказуемо и один раз. Масштаб уменьшен с 1.05 до 1.03,
   длительность с 0.6s до 0.45s — чтобы на слабых телефонах перерисовка
   картинки укладывалась в кадр. */
@media (hover:none),(pointer:coarse){
  .case__img{transition:transform .45s var(--ease-signal)}
  .case.is-near .case__img{transform:scale(1.03)}
  .case.is-near .case__badge{background:var(--accent);color:var(--paper);border-color:var(--accent);transform:rotate(45deg)}
}
.case__badge{
  position:absolute;top:var(--space-3);right:var(--space-3);width:30px;height:30px;
  border:var(--hair) solid var(--line);background:var(--paper);
  display:flex;align-items:center;justify-content:center;color:var(--ink);
  transition:background .3s ease,color .3s ease,transform .3s var(--ease-signal);z-index:2;
}
@media (hover:hover) and (pointer:fine){
  .case:hover .case__badge{background:var(--accent);color:var(--paper);border-color:var(--accent);transform:rotate(45deg)}
}
.case__meta{padding-top:var(--space-5)}
.case__name{font-size:var(--fs-lg);letter-spacing:-.02em;margin:var(--space-2) 0 var(--space-3);line-height:1.1}
.case__text{color:var(--ink);font-size:var(--fs-sm);line-height:1.5}
.case__tags{display:flex;flex-wrap:wrap;gap:var(--space-2);margin-top:var(--space-4)}
.tagchip{font-size:var(--fs-xs);color:var(--muted);border:var(--hair) solid var(--line);padding:5px 10px}

/* =========================================================================
   Отзывы — крупная типографика, без аватар-шума
   ========================================================================= */
.voices{padding-block:var(--space-8)}
.voices__title{font-size:clamp(var(--fs-xl), 4vw, var(--fs-2xl));letter-spacing:-.03em;margin-top:var(--space-3);margin-bottom:var(--space-8);line-height:1.1}
.voices__list{display:grid;grid-template-columns:repeat(3,1fr);gap:var(--space-7);border-top:var(--line-2) solid var(--ink);padding-top:var(--space-7)}
.voice{display:flex;flex-direction:column}
.voice__quote{font-size:var(--fs-lg);line-height:1.35;letter-spacing:-.01em}
.voice__quote::before{content:"«";color:var(--accent-ink)}
.voice__quote::after{content:"»";color:var(--accent-ink)}
.voice__by{margin-top:auto;padding-top:var(--space-7);font-size:var(--fs-sm)}
.voice__name{font-weight:700}
.voice__role{color:var(--muted)}

/* =========================================================================
   Форма — высокая плотность, snap-валидация
   ========================================================================= */
.contact{padding-block:var(--space-9);background:var(--ink);color:var(--paper)}
.contact__grid{display:grid;grid-template-columns:minmax(0,4fr) minmax(0,6fr);gap:var(--space-8)}
.contact__title{font-size:clamp(var(--fs-xl), 4vw, var(--fs-2xl));letter-spacing:-.03em;line-height:1.1;color:var(--paper)}
.contact__lead{margin-top:var(--space-5);color:var(--inverse-lead);max-width:34ch}
/* Адрес почты в лиде формы + кнопка копирования. Адрес и иконка не
   разрываются переносом — иконка без адреса в начале строки ничего не значит. */
.copy-mail{white-space:nowrap}
.copy-mail__link{color:var(--paper);text-decoration:underline;text-decoration-thickness:var(--hair);text-underline-offset:3px}
.copy-mail__btn{
  position:relative;display:inline-grid;place-items:center;vertical-align:middle;
  width:24px;height:24px;margin-left:var(--space-1);margin-top:-2px;
  color:var(--inverse-lead);transition:color .2s ease;
}
/* Зона нажатия 44×44 при видимых 24×24 — тот же приём, что у бургера. */
.copy-mail__btn::before{content:"";position:absolute;inset:-10px}
.copy-mail__btn svg{grid-area:1/1;width:16px;height:16px;fill:none;stroke:currentColor;stroke-width:var(--line-1)}
.copy-mail__done{opacity:0;transform:scale(.6);transition:opacity .2s ease,transform .3s var(--ease-signal)}
.copy-mail__icon{transition:opacity .2s ease,transform .3s var(--ease-signal)}
.copy-mail__btn.is-copied{color:var(--paper)}
.copy-mail__btn.is-copied .copy-mail__icon{opacity:0;transform:scale(.6)}
.copy-mail__btn.is-copied .copy-mail__done{opacity:1;transform:none}
/* Подпись над иконкой. Она же role="status" — скринридер прочитает её текст. */
.copy-mail__tip{
  position:absolute;bottom:calc(100% + var(--space-2));left:50%;
  padding:var(--space-1) var(--space-2);
  font-size:var(--fs-xs);line-height:1.4;color:var(--ink);background:var(--paper);
  transform:translate(-50%,4px);opacity:0;pointer-events:none;
  transition:opacity .2s ease,transform .3s var(--ease-signal);
}
.copy-mail__btn.is-copied .copy-mail__tip{opacity:1;transform:translate(-50%,0)}
@media (hover:hover) and (pointer:fine){
  .copy-mail__btn:hover{color:var(--paper)}
  .copy-mail__link:hover{color:var(--inverse-label)}
}

.form{display:grid;gap:var(--space-6)}
/* У .form__fields не было собственного отступа: роль разделителя играл пустой
   слот ошибки (.field__err с min-height:1em). В покое 12px пустоты читались
   как воздух, но стоило ошибке появиться — она занимала слот целиком и
   упиралась в следующий элемент, оставляя 2px. Хуже всего это выглядело перед
   кнопкой: «Опишите задачу — хотя бы пару фраз» налипало прямо на неё.
   Даём полям собственный ритм, чтобы разделение не зависело от того, есть
   сообщение об ошибке или нет. */
.form__fields{display:grid;gap:var(--space-4)}
.form__row{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-6)}
.field{position:relative;display:flex;flex-direction:column;gap:var(--space-2)}
.field label{font-size:var(--fs-sm);color:var(--inverse-label);display:flex;justify-content:space-between;align-items:center}
.field input,.field textarea,.field select{
  background:transparent;border:0;border-bottom:var(--line-2) solid var(--inverse-line);
  color:var(--paper);padding:12px 2px;font-size:var(--fs-md);
  transition:border-color .25s ease;
}
.field textarea{resize:vertical;min-height:96px}
.field select{appearance:none;background:transparent}
.field select option{color:var(--ink)}
.field input::placeholder,.field textarea::placeholder{color:var(--inverse-placeholder)}
.field input:focus,.field textarea:focus,.field select:focus{outline:none;border-color:var(--paper)}
.field.is-valid input,.field.is-valid textarea,.field.is-valid select{border-color:var(--paper)}
.field.is-error input,.field.is-error textarea,.field.is-error select{border-color:var(--error-line)}
.field__err{font-size:var(--fs-xs);color:var(--error-text);min-height:1em;opacity:0;transform:translateY(-2px);transition:opacity .2s ease,transform .2s ease}
.field.is-error .field__err{opacity:1;transform:none}

.form__foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-5)}
.form .btn{border-color:var(--paper);background:var(--paper);color:var(--ink)}
.form .btn[disabled]{opacity:.5;pointer-events:none}
/* Согласие на обработку данных. Стоит ОТДЕЛЬНЫМ блоком после .form__foot, а не
   внутри него: тот — flex-ряд, и flex-basis:100% там не помогает, его перебивает
   max-width — элемент считается шириной 374px, помещается рядом с кнопкой и
   встаёт в одну строку с ней. Вынесенный блок такой проблемы не имеет, а
   max-width продолжает держать меру строки. Цвета инверсные — секция тёмная. */
.form__consent{
  margin-top:var(--space-4);
  font-size:var(--fs-xs);line-height:1.5;color:var(--inverse-lead);max-width:52ch;
}
.form__consent a{color:var(--inverse-label);text-decoration:underline;text-underline-offset:2px}
.form__status{font-size:var(--fs-sm);color:var(--inverse-lead)}
.form__status.is-bad{color:var(--error-text)}

/* Honeypot — за пределами экрана, а не display:none/visibility:hidden
   (те боты нередко игнорируют, а позиционирование — реже) */
.hp-field{position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden}

/* успех */
.form.is-sent .form__fields{display:none}
.form__success{display:none;padding:var(--space-7) 0}
.form.is-sent .form__success{display:block}
.form__success h3{font-size:var(--fs-xl);margin-bottom:var(--space-3)}
.form__success p{color:var(--inverse-lead);max-width:44ch}

/* =========================================================================
   Подвал
   ========================================================================= */
.site-footer{background:var(--paper);color:var(--ink);border-top:var(--hair) solid var(--line);padding-block:var(--space-7)}
.footer__grid{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-7);flex-wrap:wrap}
.footer__brand svg{height:24px;color:var(--ink)}
.footer__tagline{color:var(--muted);margin-top:var(--space-4);max-width:32ch;font-size:var(--fs-sm);line-height:1.55}
.footer__col{text-align:right}
/* Заголовок колонки подвала — h2, а не h4: подвал это отдельный лендмарк,
   и h2 не создаёт пропуска уровня ни на одной странице (на 404.html нет ни
   одного h2 в main, поэтому h3 там всё равно был бы пропуском).
   Наборный шрифт и вес заданы явно: глобальное правило h1,h2,h3 иначе
   подставит --font-heading и вес 400, а у прежнего h4 был Jost 700. */
.footer__col h2{font-family:var(--font);font-weight:700;font-size:var(--fs-xs);letter-spacing:.14em;text-transform:uppercase;color:var(--faint);margin-bottom:var(--space-4)}
.footer__col a,.footer__col span{display:block;color:var(--ink);font-size:var(--fs-sm);padding-block:4px}
.footer__col a:hover{color:var(--accent)}
.footer__bottom{display:flex;justify-content:flex-end;gap:var(--space-5);flex-wrap:wrap;
  margin-top:var(--space-6);padding-top:var(--space-4);border-top:var(--hair) solid var(--line);color:var(--faint);font-size:var(--fs-xs)}
.footer__bottom a{position:relative;color:var(--faint);transition:color .2s ease}
.footer__bottom a::after{content:"";position:absolute;inset:-11px -4px}
.footer__bottom a:hover{color:var(--accent)}

/* =========================================================================
   Страница «Политика конфиденциальности» — простая контентная страница,
   без reveal/canvas/скролл-эффектов.
   ========================================================================= */
.policy-header{position:relative;z-index:50;border-bottom:var(--hair) solid var(--line)}
.policy-header__inner{display:flex;align-items:center;justify-content:space-between;gap:var(--space-5);height:var(--header-h)}
.policy-back{position:relative;font-size:var(--fs-sm);color:var(--muted);transition:color .2s ease}
.policy-back::after{content:"";position:absolute;inset:-11px -4px}   /* зона нажатия 44px, см. .case-back */
.policy-back:hover{color:var(--ink)}

.policy{max-width:72ch;padding-block:var(--space-9)}
/* Кегль резиновый, как у всех остальных заголовков разделов. Был жёстко
   зафиксирован на --fs-2xl (44px) — единственный такой на сайте, — и на 390px
   слово «конфиденциальности» требовало 407px при доступных 350: заголовок
   уезжал за правый край и тянул за собой горизонтальную прокрутку всей
   страницы. overflow-wrap — страховка на случай других длинных слов. */
.policy__title{
  font-size:clamp(var(--fs-xl), 4vw, var(--fs-2xl));
  letter-spacing:-.03em;line-height:1.1;
  overflow-wrap:break-word;
}
.policy__lead{margin-top:var(--space-4);color:var(--muted);max-width:56ch;line-height:1.55}
.policy__section{margin-top:var(--space-8)}
.policy__section h2{
  font-family:var(--font-heading);font-weight:400;font-size:var(--fs-lg);
  letter-spacing:-.01em;margin-bottom:var(--space-4);
}
.policy__section p{line-height:1.65;color:var(--ink)}
.policy__section p + p{margin-top:var(--space-4)}
.policy__section ul{margin-top:var(--space-2);padding-left:var(--space-5)}
.policy__section li{line-height:1.65;list-style:disc}
.policy__section li + li{margin-top:var(--space-2)}
.policy__section a{color:var(--accent-ink);text-decoration:underline;text-underline-offset:2px}
.policy__section a:hover{color:var(--accent)}

/* =========================================================================
   Страница кейса — единый шаблон для всех проектов.
   Структура: шапка (лого + назад + CTA) → hero (тег, заголовок, лид,
   спека клиента, обложка) → Задача → Решение (+ чек-лист) → Результат
   (плитка под скриншоты) → Итог → CTA → соседние кейсы → футер.
   ========================================================================= */
/* position:relative — якорь для выпадающей панели меню ниже: она
   позиционирована absolute от края до края шапки. z-index поднимает панель
   над содержимым страницы, иначе её перекрывала бы обложка кейса. */
.case-header{position:relative;z-index:50;border-bottom:var(--hair) solid var(--line)}
.case-header__inner{display:flex;align-items:center;justify-content:space-between;gap:var(--space-5);height:var(--header-h)}
.case-header__nav{display:flex;align-items:center;gap:var(--space-6)}

/* Меню внутренних страниц (кейсы, список кейсов, политика, 404) — бургер на
   ВСЕХ ширинах, а не только на телефоне.
   На главной полный ряд ссылок помещается рядом с логотипом; здесь справа
   уже стоит «Все кейсы», и ряд из пяти пунктов рядом с ним не встанет.
   Панель повторяет телефонную с главной: та живёт в @media (max-width:720px)
   и сюда не достаёт, поэтому правила выписаны заново под .case-header. */
.case-header .nav__toggle,
.policy-header .nav__toggle{display:flex}
.case-header .nav__links,
.policy-header .nav__links{
  display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-2);
  position:absolute;top:100%;left:0;right:0;
  padding:var(--space-4) var(--edge) var(--space-6);
  background:var(--paper);border-bottom:var(--hair) solid var(--line);
  transform:translateY(-8px);opacity:0;pointer-events:none;
  transition:transform .3s var(--ease-signal),opacity .25s ease;
}
.case-header .nav__links.is-open,
.policy-header .nav__links.is-open{transform:none;opacity:1;pointer-events:auto}

/* Выше 720px панель становится компактной и прижимается под бургер. Во всю
   ширину она нужна только на телефоне, где палец и так рядом с кнопкой; на
   десктопе кнопка в правом верхнем углу, а список раскрывался у левого края —
   курсору приходилось идти через весь экран.
   right:var(--edge-left) — тот самый токен, что держит боковое поле всех
   секций: он равен расстоянию от края экрана до контентной колонки и выше
   1440px пересчитывается сам. Значит правый край панели встаёт ровно под
   правым краем бургера на любой ширине.
   Рамка по периметру вместо одной нижней линии: оторванный от краёв блок
   должен читаться как отдельная плашка. Сверху её нет — там граница шапки. */
@media (min-width:721px){
  .case-header .nav__links,
  .policy-header .nav__links{
    left:auto;right:var(--edge-left);
    width:max-content;min-width:13rem;
    padding:var(--space-4) var(--space-6) var(--space-5);
    border:var(--hair) solid var(--line);border-top:0;
  }
}
/* Зона нажатия у текстовых ссылок. Сама строка — 19–22px, это ниже минимума
   WCAG 2.2 (24×24) и вдвое ниже комфортного тач-минимума. Растягиваем не
   паддингом, а псевдоэлементом: паддинг сдвинул бы соседей в шапке кейса и в
   подвале, а absolute-подложка добирает высоту до 44px, ничего не сдвигая.
   На ≤720px .case-back скрыт, так что это правило работает ровно в тач-
   диапазоне 721–1024, где ссылку жмут пальцем. */
.case-back{position:relative;font-size:var(--fs-sm);color:var(--muted);transition:color .2s ease}
.case-back::after{content:"";position:absolute;inset:-11px -4px}
.case-back:hover{color:var(--ink)}

.case-hero{padding-top:var(--space-8)}
.case-hero__title{font-size:clamp(var(--fs-fluid-1),4.4vw,var(--fs-3xl));letter-spacing:-.02em;line-height:1.05;margin-top:var(--space-4);max-width:18ch}
.case-hero__lead{margin-top:var(--space-5);max-width:52ch;font-size:var(--fs-md);color:var(--ink);line-height:1.55}

.case-spec{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-6);
  margin-top:var(--space-8);padding-top:var(--space-5);padding-bottom:var(--space-8);
  border-top:var(--hair) solid var(--line);
}
.case-spec dt{font-size:var(--fs-xs);letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.case-spec dd{margin-top:var(--space-2);font-size:var(--fs-md)}

.case-cover{width:100%;aspect-ratio:16/10;overflow:hidden;background:var(--ink)}
.case-cover img{width:100%;height:100%;object-fit:cover;display:block}

.case-section{padding-block:var(--space-9)}
.case-section--tight{padding-top:0}
.case-section__grid{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,3fr);gap:var(--space-8)}
.case-section__head{align-self:start;position:sticky;top:96px}
.case-section__title{font-size:var(--fs-xl);line-height:1.1;letter-spacing:-.02em;margin-top:var(--space-3)}
.case-section__body p{max-width:60ch;line-height:1.65;color:var(--ink)}
.case-section__body p+p{margin-top:var(--space-4)}

.case-checklist{margin-top:var(--space-7);border-top:var(--hair) solid var(--line)}
.case-checklist li{
  display:flex;align-items:baseline;gap:var(--space-4);
  padding-block:var(--space-4);border-bottom:var(--hair) solid var(--line);
  font-size:var(--fs-md);
}
.case-checklist li::before{content:"—";color:var(--accent-ink);flex-shrink:0}

/* Галерея кейса — одна широкая картинка. Сетка была двухколоночной, а базовое
   соотношение плитки 4/3; ни то ни другое не срабатывало ни разу, потому что
   каждая плитка несла модификатор --wide с 16/9. Формат утверждён окончательно,
   поэтому 16/9 переехало в базовое правило, вторая колонка и модификатор убраны. */
.case-gallery{display:grid;gap:var(--space-5);margin-top:var(--space-8)}
.case-gallery__tile{
  position:relative;aspect-ratio:16/9;border:var(--hair) solid var(--line);overflow:hidden;  /* 16:9 на всех ширинах. Раньше ниже 860px контейнер переключался на 16:10, чтобы плитка не выглядела тонкой полосой на телефоне. Но изображения готовятся в 16:9, и object-fit:cover срезал лишние 11.1% ширины — по 5.6% с каждого края. Двадцать пикселей высоты не стоят отрезанного содержания, тем более что помнить про невидимые поля пришлось бы при подготовке каждой картинки. */
  display:flex;align-items:center;justify-content:center;
  background:repeating-linear-gradient(45deg, transparent 0 11px, color-mix(in srgb,var(--line) 60%, transparent) 11px 12px);
  color:var(--faint);font-size:var(--fs-sm);letter-spacing:.06em;text-align:center;padding:var(--space-4);
}
/* position:absolute;inset:0 — контейнер задумывался под текст-плейсхолдер
   ("скриншот проекта — добавить позже") с padding и flex-центровкой; когда
   внутри реальная картинка, абсолютное позиционирование заполняет её от
   границы padding-box до самой рамки, игнорируя тот padding — иначе вокруг
   фото виден зазор с полосатым фоном плейсхолдера (см. .case__img так же). */
.case-gallery__tile img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block}

.case-quote{margin-top:var(--space-7);padding-top:var(--space-7);border-top:var(--hair) solid var(--line);max-width:60ch}
.case-quote blockquote{font-size:var(--fs-lg);line-height:1.35;letter-spacing:-.01em}
.case-quote blockquote::before{content:"«";color:var(--accent-ink)}
.case-quote blockquote::after{content:"»";color:var(--accent-ink)}
.case-quote figcaption{margin-top:var(--space-5);font-size:var(--fs-sm)}
.case-quote .voice__name{font-weight:700}
.case-quote .voice__role{color:var(--muted)}

.case-cta{background:var(--ink);color:var(--paper);padding-block:var(--space-8);text-align:center}
/* line-height задан явно: без него наследовался body 1.6 — на 2-3 строках
   (мобильный, длинные CTA) строки расходились сильнее, чем у прочих заголовков */
.case-cta__title{font-size:clamp(var(--fs-xl), 4vw, var(--fs-2xl));letter-spacing:-.03em;line-height:1.1;color:var(--paper);max-width:20ch;margin-inline:auto}
/* Длинный CTA-заголовок в одну строку: снимаем меру 20ch, которая рассчитана
   на короткие фразы страниц кейсов (на мобильном перенос всё равно неизбежен) */
.case-cta__title--wide{max-width:none}
.case-cta .btn{margin-top:var(--space-6);border-color:var(--paper);background:var(--paper);color:var(--ink)}

.case-nextnav{
  display:flex;align-items:center;justify-content:space-between;gap:var(--space-5);
  padding-block:var(--space-6);border-bottom:var(--hair) solid var(--line);
}
.case-nextnav__label{font-size:var(--fs-xs);letter-spacing:.14em;text-transform:uppercase;color:var(--faint)}
.case-nextnav__name{font-family:var(--font-heading);font-size:var(--fs-lg);margin-top:var(--space-2);transition:color .2s ease}
.case-nextnav__arrow{font-size:var(--fs-xl);color:var(--faint);transition:transform .3s var(--ease-signal)}
/* Блок стоит последним перед футером, поэтому на тач-экране его :hover ловился
   ровно в момент долистывания страницы до конца — сдвиг стрелки дёргал кадр. */
@media (hover:hover) and (pointer:fine){
  .case-nextnav:hover .case-nextnav__name{color:var(--accent-ink)}
  .case-nextnav:hover .case-nextnav__arrow{transform:translateX(6px);color:var(--accent-ink)}
}

/* =========================================================================
   Разводящая страница кейсов (cases.html) — плитка та же, что на главной
   (.cases__list), сверху заголовок и фильтр по направлениям.
   ========================================================================= */
.cases-page{padding-block:var(--space-9)}
/* Заголовок страницы — крупнее H2-секций главной, на уровне h1 страницы кейса */
.cases-page__title{font-size:clamp(var(--fs-fluid-1),4.4vw,var(--fs-3xl));letter-spacing:-.03em;line-height:1.05}

/* Фильтр: кнопки-чипы. Активная — инверсия (чёрная плашка), как .btn на сайте */
.cases-filter{
  display:flex;flex-wrap:wrap;gap:var(--space-3);
  margin-top:var(--space-7);padding-bottom:var(--space-7);
  border-bottom:var(--hair) solid var(--line);
}
.cases-filter__btn{
  display:inline-flex;align-items:baseline;gap:8px;
  font-size:var(--fs-sm);line-height:1;color:var(--ink);
  padding:11px 16px;border:var(--hair) solid var(--line);background:transparent;
  transition:background .2s ease,color .2s ease,border-color .2s ease;
}
.cases-filter__btn.is-active{background:var(--ink);border-color:var(--ink);color:var(--paper)}
.cases-filter__count{font-size:var(--fs-xs);color:var(--faint)}
.cases-filter__btn.is-active .cases-filter__count{color:var(--paper);opacity:.6}

.cases-page .cases__list{margin-top:var(--space-8)}
/* Скрытие карточки фильтром: не display:none, а полное изъятие из потока
   через атрибут — grid сам переукладывает оставшиеся без дыр */
.case[hidden]{display:none}
.cases-empty{margin-top:var(--space-8);color:var(--muted);font-size:var(--fs-md)}

/* Кнопка «Все кейсы» под плиткой на главной */
.cases__more{margin-top:var(--space-8);display:flex;justify-content:center}

/* Вертикальный ритм на узких экранах — общий для всех страниц.
   Отступы шкалы задавались под десктоп, где заголовки 44–63px и вокруг них
   нужен воздух. На узком экране заголовки сжимаются резиновым clamp() до
   30–32px, а отступы оставались прежними: 96px над строкой высотой 31–34px,
   то есть втрое-вчетверо выше текста, который они подают. На странице кейсов
   от шапки до первой карточки набегал 391px — 39% экрана в пустоту.
   Ступени взяты из шкалы (96→48, 64→32), произвольных значений нет.
   Пропорции между секциями сохранены: было 96/64, стало 48/32, отношение
   1.5 в обоих случаях. */
@media (max-width:1024px){
  /* страница кейсов */
  .cases-page{padding-block:var(--space-7)}
  .cases-filter{margin-top:var(--space-6);padding-bottom:var(--space-6)}
  .cases-page .cases__list{margin-top:var(--space-6)}

  /* главная */
  .about,.cases,.contact{padding-block:var(--space-7)}
  .services,.voices{padding-block:var(--space-6)}
  .cases__head{margin-bottom:var(--space-6)}
  .voices__title{margin-bottom:var(--space-6)}
}

@media (max-width:860px){
  .case-spec{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:var(--space-5)}
  .case-section__grid{grid-template-columns:1fr;gap:var(--space-5)}
  .case-section__head{position:static}
}
@media (max-width:520px){
  .case-header__inner{height:auto;padding-block:var(--space-3);flex-wrap:nowrap}
  .case-header__nav{gap:var(--space-3)}
  .case-back{font-size:var(--fs-xs);white-space:nowrap}
}

/* =========================================================================
   Reveal при скролле (IntersectionObserver добавляет .in)
   ========================================================================= */
/* Баланс между двумя крайностями. 18px/.8s (изначально) — блок на быстром
   скролле «догонял» экран, читалось рывком. 10px/.5s — рывок ушёл, но темп
   появления контента стал такой бодрый, что вся страница ощущалась разогнанной.
   Середина: 12px/.62-.7s — движение спокойное, но всё ещё успевает за
   прокруткой, потому что стартует рано (пороги IO — в initReveal, script.js).
   Дистанцию подняли обратно 10→12px не для красоты: на большей длительности
   ход в 10px выглядит вялым — медленное движение на короткой дистанции. */
.reveal{opacity:0;transform:translateY(12px);transition:opacity .62s ease,transform .7s var(--ease-signal)}
.reveal.in{opacity:1;transform:none}
/* Каскад держим пропорционально длительности выше (~7% от неё на ступень),
   чтобы ступенька между соседними элементами не читалась сильнее, чем задумано. */
.reveal[data-d="1"]{transition-delay:.05s}
.reveal[data-d="2"]{transition-delay:.10s}
.reveal[data-d="3"]{transition-delay:.15s}

/* =========================================================================
   Адаптив
   ========================================================================= */
@media (max-width:1024px){
  /* Первый экран на планшете и телефоне — одна колонка на весь экран, видео
     и постер уходят под текст фоном. Слоёв три, и порядок важен:
       .hero__split        — фон-постер, виден мгновенно, пока видео не декодировано;
       .hero__visual       — видео поверх постера, absolute на всю сцену;
       .hero__split::after — затемнение ПОВЕРХ видео.
     Затемнение вынесено в псевдоэлемент, потому что фоновые слои .hero__split
     рисуются под её же потомками, то есть под видео.
     Высота — минимальная, а не жёсткая: на телефоне в альбомной ориентации
     (375–430px по высоте) и на 320×568 заголовок, лид и две кнопки в экран
     не помещаются, и при height:100svh кнопки вываливались из фона — белая
     на белое, алая поверх заголовка «О нас». Где содержимое влезает, экран
     по-прежнему ровно 100svh. */
  .hero__split{
    position:relative;
    grid-template-columns:1fr;height:auto;min-height:100vh;min-height:100svh;min-height:calc(var(--vh, 1svh) * 100);
    /* Адрес постера вынесен в переменную, чтобы телефонное правило ниже
       подменяло только его. */
    --hero-poster:url('assets/hero-visual-poster.webp');
    background-color:var(--ink);
    background-image:var(--hero-poster);
    background-size:cover;
    background-position:center;
    background-repeat:no-repeat;
  }
  .hero__visual{
    display:block;
    position:absolute;inset:0;z-index:0;
    background:none;   /* постер уже на .hero__split, второй раз не грузим */
  }
  /* z-index 50, а не 2: блок создаёт свой слой, и закреплённая шапка внутри
     него не может подняться выше этого слоя — со 2 её перекрывали бейджи
     карточек кейсов (.case__badge, тоже z-index:2). */
  .hero__content{position:relative;z-index:50;padding-bottom:var(--space-6)}
  /* Плавающая кнопка в углу видео наезжает на бургер, а тот же призыв уже
     стоит под заголовком (.hero__cta-contact, алая). */
  .hero__visual-cta{display:none}
  /* Два слоя затемнения, оба посчитаны по пикселям постера, а не на глаз.
     Нижний — ровные 35%: без него белый текст проходит 4.5:1 лишь на 87.5%
     площади (в светлой лавандовой зоне 1.49:1), с ним — на 97.9%.
     Верхний — тень под текстовой колонкой: у постера в верхней половине
     светлые пятна, и одних 35% там не хватало (2.87–3.15 у шапки, 4.30–4.36
     у лида при норме 4.5). Отсюда форма: 45% под шапкой, плато 10% через
     заголовок и лид, затухание к 84% высоты — нижняя шестая часть экрана,
     где сам дым, остаётся нетронутой. */
  .hero__split::after{
    content:"";position:absolute;inset:0;z-index:1;pointer-events:none;
    background-image:
      linear-gradient(to bottom,
        rgba(0,0,0,.45),
        rgba(0,0,0,.10) 200px,
        rgba(0,0,0,.10) 66%,
        rgba(0,0,0,0) 84%),
      linear-gradient(rgba(0,0,0,.35),rgba(0,0,0,.35));
  }
  .hero__title,.hero__lead{color:var(--paper)}
  /* Логотип и бургер белые только пока шапка лежит на градиенте. Когда
     initHeaderPin переводит её в фиксированную белую полосу, они обязаны
     вернуться в чернильный, иначе исчезнут на белом. */
  .site-header:not(.is-pinned) .brand{color:var(--paper)}
  .site-header:not(.is-pinned) .nav__toggle span{background:var(--paper)}
  /* Ссылки меню тоже лежат на градиенте — но только с 721px, где бургера ещё
     нет и они видны в строку. В покое они --muted (#6B6B6B), что на затемнённом
     градиенте даёт 1.55:1 при норме 4.5: меню фактически исчезало. Берём
     --inverse-label, уже существующий токен для вторичного текста на тёмном —
     5.30:1, и иерархия сохраняется: логотип белый и громче, ссылки тише.
     Нижняя граница 721px обязательна: ниже ссылки уезжают в выпадающую панель
     с БЕЛЫМ фоном, и светлый цвет сделал бы их невидимыми уже там. */
  @media (min-width:721px){
    .site-header:not(.is-pinned) .nav__link{color:var(--paper);opacity:.82}
  }
  /* Кнопки: первая белая, вторая алая */
  .hero__cta .btn{background:var(--paper);border-color:var(--paper);color:var(--ink)}
  .hero__cta .hero__cta-contact{background:var(--accent);border-color:var(--accent);color:var(--paper)}
  .hero__cta .hero__cta-contact:active{background:var(--accent-ink);border-color:var(--accent-ink)}
  .hero__cta-contact{display:inline-flex}
  .hero__title{font-size:clamp(var(--fs-fluid-2), 5.5vw, var(--fs-fluid-4))}
  .about__grid{grid-template-columns:1fr;gap:var(--space-6)}
  .about__head{position:static}
  /* Рельс с точками прогресса — часть sticky-механики десктопа (см. .about__head
     position:sticky выше). На мобильном head статичный, точки прокручиваются
     мимо экрана раньше, чем меняют состояние — бесполезный остаток. Вместе с
     ним убираем 56vh-паузы между пунктами (были нужны только для таймингов
     скролла под sticky-рельс) — на мобильном просто показываем все 4 пункта
     раскрытыми, как обычный список. */
  .ab__rail{display:none}
  .ab-step{min-height:auto;padding-block:var(--space-5);max-width:none;margin-inline:0}
  /* Пункты выезжают так же, как остальные блоки страницы — через общий .reveal
     (класс стоит в разметке, каскад data-d 1..3). Раньше здесь было opacity:1 и
     4 преимущества просто появлялись готовыми, пока всё вокруг выезжало.
     Пара правил прописана явно, а не оставлена на наследование от .reveal:
     у .ab-step.is-active (десктопное "дыхание") specificity 0,2,0 — выше, чем
     у голого .reveal{opacity:0}, и без этого шаг, который initAboutSteps
     пометил активным, проявлялся бы в обход reveal-анимации. */
  .ab-step.reveal{opacity:0}
  .ab-step.reveal.in{opacity:1}
  .ab-step__desc{opacity:1;max-height:none}
  /* Подпись автора отстояла от своей цитаты на 48px, а до следующей цитаты
     было 32px — имя визуально принадлежало не своему отзыву, а следующему,
     и блок читался слипшимся. Меняем пропорцию местами: внутри 16px, между
     отзывами 32px сверху и снизу от волосяной линии. Разделитель тот же, что
     между строками направлений и пунктами «О нас». */
  .voices__list{grid-template-columns:1fr;gap:var(--space-6)}
  .voice__by{padding-top:var(--space-4)}
  .voice + .voice{border-top:var(--hair) solid var(--line);padding-top:var(--space-6)}
  .contact__grid{grid-template-columns:1fr;gap:var(--space-6)}
}
@media (max-width:720px){
  /* Облегчённый постер — только телефонам. 1080x1620 против 1600x2400,
     143 КБ против 266: на телефоне это LCP-элемент, и 122 КБ экономии там
     заметнее всего. Планшетам 721–1024 остаётся полноразмерный файл: у них
     экран шире и плотность 2x, и уменьшённый постер пришлось бы растягивать
     почти вдвое.
     Почему не меньше: вес держит зерно, а не размер. Перекодирование в том же
     разрешении не даёт ничего — оригинал уже на q78. Уронить файл до ~80 КБ
     можно только уходом в 900px по ширине, а там зерно заметно плывёт
     (сравнивал кропы при рендере на 1170x2532). 1080 — последний размер, где
     оно ещё держится. */
  .hero__split{--hero-poster:url('assets/hero-visual-poster-mobile.webp')}

  .nav__links{
    display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-2);
    position:absolute;top:100%;left:0;right:0;
    padding:var(--space-4) var(--edge) var(--space-6);
    background:var(--paper);border-bottom:var(--hair) solid var(--line);
    transform:translateY(-8px);opacity:0;pointer-events:none;
    transition:transform .3s var(--ease-signal),opacity .25s ease;
  }
  .nav__links.is-open{transform:none;opacity:1;pointer-events:auto}
  .nav__toggle{display:flex}
  .site-header__inner{justify-content:space-between}
  .hero__title{font-size:clamp(var(--fs-fluid-1), 8vw, var(--fs-fluid-3))}
  /* Кнопки в столбик во всю ширину — телефонная раскладка, и живёт она до 720px,
     там же, где включается бургер и схлопываются сетки. Раньше правило стояло в
     медиазапросе ≤1024 вместе с градиентным фоном, и на планшете кнопка
     растягивалась до 691px (iPad портрет) и 922px (iPad альбом). На телефоне
     полная ширина оправдана — палец, узкий экран, зона большого пальца; на
     планшете это просто полоса во весь экран. Выше 720px возвращается обычный
     ряд с шириной по содержанию из базового правила .hero__cta. */
  .hero__cta{flex-direction:column;align-items:stretch}
  .hero__cta .btn{width:100%;justify-content:center}
  .service{grid-template-columns:1fr}
  .cases__list{grid-template-columns:1fr}
  .form__row{grid-template-columns:1fr}
  .footer__grid{flex-direction:column;gap:var(--space-6)}
  .footer__col{text-align:left}
  .footer__bottom{justify-content:flex-start}
}

/* Правило, прятавшее .case-back ниже 360px, снято 06.10.2026 вместе с кнопкой
   «Обсудить задачу». Пряталось оно из-за неё: три элемента в ряд с
   white-space:nowrap не влезали в 320px и давали горизонтальный скролл
   (+32px, замерено). Кнопку заменил бургер шириной 32px, и шапка на 320px
   занимает 139px из 320 — ссылка «Все кейсы» помещается с запасом. */

/* =========================================================================
   Десктоп — гасим reveal на пунктах "О нас"
   Класс .reveal стоит на .ab-step ради мобильной версии (см. блок max-width:1024).
   Здесь у секции своя механика: sticky-колонка слева + .is-active по центру
   вьюпорта. Если оставить reveal работать, .reveal.in{opacity:1} снял бы
   приглушение неактивных шагов и "дыхание" пропало бы — все 4 пункта стояли бы
   одинаково яркими. transition-delay от data-d тоже обнуляем: каскад нужен
   только мобильному списку, в sticky-механике он лишний.
   ========================================================================= */
/* Условие — точное дополнение к max-width:1024px, а не min-width:1025px:
   при зуме страницы вьюпорт бывает дробным (1024.5px), и пара 1024/1025
   оставила бы щель, в которой не работает ни мобильный блок, ни этот —
   там reveal остался бы включённым и дыхание пропало бы. */
@media not all and (max-width:1024px){
  .ab-step.reveal{opacity:.3;transform:none;transition:opacity .5s var(--ease-signal);transition-delay:0s}
  .ab-step.reveal.is-active{opacity:1}
}

/* =========================================================================
   Шире 1440 — продолжаем ту же сетку, а не упираемся в потолок.
   Диапазон 1024-1440 — эталон, его НЕ трогаем: там отступ идёт как 5vw и
   упирается в 64px, контент растёт вместе с окном (замерено: 1024 → 51px/5.0%,
   1280 → 64px/5.0%, 1440 → 64px/4.4%).
   Дальше вступал --maxw:1440px: контент замирал на 1312px, а поля по бокам
   раздувались — 144px на 1600, 304px на 1920, 624px на 2560 (24.4% ширины
   экрана в пустоту с каждой стороны). Сайт выглядел узкой колонкой посреди
   белого поля вместо той плотной вёрстки, что видна на 15".
   Снимаем потолок ширины и продолжаем эталонную пропорцию 64/1440 = 4.45%:
   отступ считается от ширины окна, контент занимает всё остальное.
   Потолок 128px — чтобы на 4-5K отступ не разъезжался бесконечно.
   --edge-left переопределяем явно: его исходная формула компенсировала
   центровку контейнера по --maxw, а теперь центровки нет и компенсировать
   нечего (иначе hero получил бы двойной отступ слева).
   ========================================================================= */
@media (min-width:1441px){
  :root{
    --maxw:none;
    --edge:clamp(64px, 4.45vw, 128px);
    --edge-left:var(--edge);

    /* Отступы переезжают в rem, чтобы расти вместе с типографикой (см. html
       ниже). Если оставить их в px, текст на широком экране станет крупнее, а
       воздух вокруг него — нет, и ритм макета поедет. Значения те же, просто
       выражены от базы 16px: 4px=.25rem … 128px=8rem.
       --edge НАМЕРЕННО остаётся в px/vw: он задаёт поле от края экрана и уже
       держит эталонную пропорцию 4.45%; привяжи его к шрифту — и при
       умеренном зуме поле схлопнется до 3.4% вместо эталонных 4.4%. */
    --space-1:.25rem; --space-2:.5rem; --space-3:.75rem; --space-4:1rem;
    --space-5:1.5rem; --space-6:2rem; --space-7:3rem; --space-8:4rem;
    --space-9:6rem; --space-10:8rem;
    --gutter:1.5rem;
    --header-h:4rem;
  }
  /* Типографика растёт вместе с шириной, но вполсилы: на 1440 ровно 16px (то
     есть эталон не сдвинут ни на пиксель), на 2560 — 21.5px. Полный
     пропорциональный зум дал бы 28.4px: макет стал бы точной копией эталона в
     масштабе 1.78x, но на экран влезало бы вдвое меньше, а 2560 логических
     пикселей не означают, что монитор во столько же раз дальше от глаз.
     Формула на rem, а не на голых vw: так пользовательская настройка базового
     размера шрифта в браузере продолжает работать. */
  html{font-size:clamp(1rem, calc(1rem + (100vw - 1440px) * .0049), 1.344rem)}
}

/* =========================================================================
   Широкие экраны — контент растёт вместе с контейнером, а не просто
   тонет в белых полях: крупнее заголовки, просторнее секции.
   ========================================================================= */
@media (min-width:1600px){
  :root{
    --fs-xl:2.125rem;   /* 34 */
    --fs-2xl:3.25rem;   /* 52 */
    /* в rem, а не в px (было 128/160): иначе эти два отступа выпали бы из
       масштабирования выше и на широких экранах остались бы единственными
       «замороженными» в общем ритме */
    --space-9:8rem;
    --space-10:10rem;
  }
  .hero__title{font-size:clamp(var(--fs-fluid-3), 4.4vw, var(--fs-fluid-5))}
}

/* =========================================================================
   prefers-reduced-motion — всё сразу в собранном состоянии
   ========================================================================= */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;transition-duration:.001ms!important;scroll-behavior:auto!important}
  .reveal{opacity:1;transform:none}
}
