/* ==========================================================================
   Reset + базовая типографика
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* Шапка прибита к верху окна, поэтому переход по якорю (#cases из футера и
     из меню) обязан останавливаться под ней, а не заводить цель ей за спину */
  scroll-padding-block-start: var(--header-height);
}

body {
  margin: 0;
  min-height: 100%;
  background-color: var(--color-bg-base);
  color: var(--color-text-primary);
  font-family: var(--font-family-base);
  font-weight: var(--font-weight-regular);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-text-md);
  /* Figma рендерит текст со сглаживанием — включаем то же, чтобы вес штриха
     совпадал с макетом на macOS */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
p,
figure,
dl,
dd {
  margin: 0;
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

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

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

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

/* --- Перенос строк -------------------------------------------------------
   В макете у текстовых слоёв стоит Wrap style: Balance — Figma разносит слова
   так, чтобы строки вышли примерно равной длины, а не набивает первую до упора
   и не оставляет в последней одно слово. В CSS это ровно text-wrap: balance.

   Высоты блоков от этого не разъезжаются: balance не добавляет строк, он
   раскладывает по тем же строкам, что и обычный перенос, — меняется только
   место разрыва. Поэтому числа, снятые с макета в tools/verify-layout.mjs,
   остаются в силе.

   :where — чтобы у правила не было веса: любой селектор блока перебивает его
   без !important. Hero под него формально попадает, но там ничего не меняет:
   в заголовке white-space: nowrap, в подписях pre (blocks/hero.css) — все
   переносы проставлены руками по макету, и балансировать нечего.

   Оговорка браузера: balance перебирает раскладки и потому работает не дальше
   шести строк, а за этим порогом молча отключается. На сайте за него выходят
   два текста, и оба только до 576: описание услуги (семь строк) и «Solution»
   в описании кейса (восемь). Первому ниже подставлен pretty, второму нет —
   почему, там же. */
:where(h1, h2, h3, h4, h5, h6, p, li, dt, dd, button, label) {
  text-wrap: balance;
}

/* --- Слово длиннее строки ------------------------------------------------
   Русский и сербский набирают то же самое длиннее английского, и отдельные
   слова перестают помещаться в строку целиком: «конфиденциальности» в
   заголовке правовой страницы на 320 — это 294px при колонке 272, а
   заголовок 404 на сербском на той же ширине вылезал за экран на пять
   пикселей. Слово не переносится, растягивает документ, и появляется
   горизонтальная прокрутка — на всей странице, не только в этом блоке.

   break-word разрывает слово только тогда, когда иначе никак: пока оно
   помещается, правило не делает ничего, и ни одна строка, снятая с макета,
   не меняется. Тем оно и отличается от anywhere, который ещё и уменьшает
   min-content — то есть менял бы раскладку сеток и колонок.

   Переносов по слогам (hyphens: auto) здесь нарочно нет: они переставили бы
   строки на всех ширинах, а сверка с макетом идёт по строкам.

   :where — то же, что и у правила выше: у него нет веса, и любой блок
   перебивает его без !important. */
:where(h1, h2, h3, h4, h5, h6, p, li, dt, dd, figcaption, blockquote) {
  overflow-wrap: break-word;
}

/* Описание услуги — единственное место, где за порогом balance есть что
   спасать: до 576 колонка узкая, абзац разливается на семь строк и в
   последней остаётся одно слово. pretty его подтягивает (последняя строка
   41 → 52), строк при этом не добавляя, — высота блока, снятая с макета,
   не меняется. С 576 описание умещается в пять строк, balance снова
   работает и выравнивает их куда лучше (разброс 436 → 52), поэтому правило
   и заперто медиазапросом.

   Исключение живёт здесь, а не в blocks/catalog.css: смысл у него не
   «каталог», а «порог шести строк», и читать его надо вместе с правилом выше.
   Тексты описаний лежат в data/categories.js и все примерно одной длины,
   так что граница держится не на одном тексте.

   Второму длинному тексту, .case-fact__value, pretty не поставлен намеренно.
   Там висячих слов нет, и pretty на замерах не меняет ни одной строки, зато
   отнял бы balance у соседних коротких абзацев того же класса: «Client» на
   425 укладывается в четыре строки, и balance сводит их разброс с 63 до 0. */
@media (max-width: 575px) {
  .catalog-head__intro {
    text-wrap: pretty;
  }
}

/* Общий контейнер: боковые поля 24px + предел ширины из макета.
   max() с безопасной зоной (css/tokens.css) — на случай телефона с вырезом,
   положенного набок: в портрете и на экране без выреза env() равен нулю и
   остаются те же 24 из макета. */
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-inline:
    max(var(--spacing-xl), var(--safe-inline-start))
    max(var(--spacing-xl), var(--safe-inline-end));
}

.container__inner {
  width: 100%;
  max-width: var(--container-max);
}

/* --- Движение по умолчанию ----------------------------------------------
   Одно правило на весь сайт: всё, что меняется, меняется за --duration-leave
   (300мс) и по общей кривой. До него переход объявлялся в каждом блоке
   отдельно, и всё, чему такого объявления не досталось, менялось щелчком.

   Универсальный селектор даёт наименьшую возможную специфичность (0,0,0),
   поэтому собственные переходы блоков его перебивают все до одного: там, где
   важны длительность или набор свойств (приход оверлея плитки — 420мс,
   раскрытие подписи по grid-template-rows, панель меню, переезд подчёркивания
   в фильтре каталога), ничего не изменилось. Правило работает ровно там, где
   своего перехода не было.

   `all`, а не список свойств: список пришлось бы вести и дополнять при каждой
   новой анимации, а смысл правила в том, что перечислять больше ничего не
   надо.

   Исключения — там, где геометрию элемента пишет JS и тут же её меряет: сама
   секция кейсов, её сетка, колонки параллакса и внутренний слой подписи (все
   четыре — в конце blocks/cases.css). Переход на них означал бы замер на
   промежуточном кадре — или, у секции, поехавшую высоту документа. */
*,
*::before,
*::after {
  transition: all var(--duration-leave) var(--ease-soft);
}

/* При reduce общий переход снимается — движение на сайте становится
   мгновенным. Специфичность та же (0,0,0), поэтому собственные переходы
   блоков этим правилом не гасятся: у каждого из них свой блок reduce. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none;
  }
}

/* Смена ширины окна — единственный момент, когда движения не должно быть
   вовсе, и на этот раз у всех без исключения. `all` выше — это в том числе
   раскладка: кегль, поля и число колонок меняются на брейкпоинтах, и без
   этого правила страница ехала бы к новой раскладке 300мс вместо того, чтобы
   сразу в ней оказаться. Класс на время ресайза вешает js/motion.js — там же
   подробно, почему по ширине, а не по высоте.

   !important и здесь, и у смены темы ниже: гасятся не только общий переход, но
   и собственные переходы блоков — иначе подпись кейса продолжала бы
   раскрываться, пока раскладка под ней уже другая. */
.page--resizing,
.page--resizing *,
.page--resizing *::before,
.page--resizing *::after {
  transition: none !important;
}

/* --- Переключение темы --------------------------------------------------
   Смена палитры — единственный момент, когда цвет меняется у всей страницы
   разом, и общих 300мс ей мало: она перекрашивает всё сразу, и на общей
   длительности это читается как щелчок. Поэтому на время переключения
   действует свой, вдвое более долгий переход — класс вешает js/theme.js и
   снимает его, когда движение закончилось.

   Универсальный селектор здесь уместен по той же причине, что и у общего
   правила выше, а !important — потому что этот перебивает и собственные
   переходы блоков: в момент смены темы они должны идти в один голос с ней.
   Перечислены только те свойства, которыми тема и отличается, плюс opacity —
   чтобы !important не подменил собственным переходам меню и плитки движение
   на мгновенное. Длительность своя, --duration-theme: смена темы нарочно
   вдвое медленнее остального движения на сайте.
   Остальные переходы (высота панели, раскрытие подписи кейса) на это время
   всё-таки замирают: цена — несработавшая анимация, если закрыть меню в тот
   же миг, когда переключил тему. */
.page--theme-switching,
.page--theme-switching *,
.page--theme-switching *::before,
.page--theme-switching *::after {
  transition:
    background-color var(--duration-theme) var(--ease-soft),
    color var(--duration-theme) var(--ease-soft),
    border-color var(--duration-theme) var(--ease-soft),
    box-shadow var(--duration-theme) var(--ease-soft),
    opacity var(--duration-theme) var(--ease-soft) !important;
}

/* При reduce тема меняется мгновенно — как и всё остальное движение на сайте */
@media (prefers-reduced-motion: reduce) {
  .page--theme-switching,
  .page--theme-switching *,
  .page--theme-switching *::before,
  .page--theme-switching *::after {
    transition: none !important;
  }
}

/* --- Наведение ----------------------------------------------------------
   Одна политика на весь сайт, и состоит она из двух правил. Оба про то, что
   уже нарисовано: наведение ничего к элементу не добавляет, оно снимает.

     1. Есть обводка — по наведению она уходит.
     2. Обводки нет — по наведению элемент становится полупрозрачным.

   Отсюда и деление списков ниже: не по блокам, а по признаку «нарисована ли
   линия в покое». Первый список почти дословно повторяет перечисление в
   forced-colors в конце файла — и это не совпадение, там те же самые
   подчёркивания, просто увиденные с другой стороны.

   Правило живёт здесь, а не в блоках, по той же причине, что и forced-colors:
   это свойство сайта, а не каждого блока по отдельности. Заведи его поблочно —
   и первый же новый блок приедет без наведения вовсе.

   Длительность не указана нигде: её даёт общий переход выше — 300мс на всё.

   :focus-visible стоит рядом с :hover везде: с клавиатуры пункт обязан
   отзываться так же, как под курсором. Собственную рамку фокуса это не
   отменяет — она рисуется поверх.

   Наведения, которых правила не касаются, и почему:
     · оверлей плитки кейса (blocks/cases.css) — состояние из макета, целиком
       нарисованное в Figma, а не выведенное из этих двух правил. Подпись
       «View Case» внутри подчёркнута, но своей цели у наведения на неё нет:
       наводят на плитку, а подпись стоит по её центру — снимай с неё линию,
       и она пропадала бы ровно тогда, когда курсор дошёл до середины плитки,
       то есть почти всегда. Отдельная кнопка share рядом — другое дело, она
       в списке ниже;
     · поля формы и чекбокс согласия (blocks/contact.css) — у них состояние
       Hover тоже есть в макете, и обводка там не украшение, а сам контрол:
       снять её значило бы стереть поле;
     · пункты выпадающих списков — язык в шапке (blocks/lang.css) и бюджет в
       форме (blocks/contact.css). Там подсветка фоном — одна на мышь и на
       стрелки клавиатуры, то есть она же индикатор текущего пункта, а не
       отклик на курсор. */

/* Обводка есть — снимаем. Подчёркивания на сайте нарисованы inset-тенью
   (почему — у .header__link--underline в blocks/header.css), поэтому и
   снимаются тенью. Геометрия при этом не меняется вовсе: у inset-тени её и
   не было. */
.header__link--underline:hover,
.header__link--underline:focus-visible,
.menu__cta--underline:hover,
.menu__cta--underline:focus-visible,
.cookie__button:hover,
.cookie__button:focus-visible,
.notfound__link:hover,
.notfound__link:focus-visible,
.contact__submit:not([disabled]):hover,
.contact__submit:not([disabled]):focus-visible {
  box-shadow: none;
}

/* Два исключения из «тенью»: у ссылки на кейс и у имени контакта линия —
   настоящая рамка, и в макете она добавляет к высоте бокса свой пиксель
   (см. .case-link в blocks/case.css). Поэтому снимаем не рамку, а её цвет:
   убери рамку целиком — бокс на наведении становился бы на пиксель ниже и
   строка дёргалась бы под курсором. */
.case-link:hover,
.case-link:focus-visible,
.contact__person-name:hover,
.contact__person-name:focus-visible {
  border-block-end-color: transparent;
}

/* Третье: ссылки в тексте согласия подчёркнуты text-decoration — единственные
   на сайте. Прозрачный цвет, а не `none`: цвет переходом гасится плавно, а
   `none` щёлкнул бы. */
.contact__consent a:hover,
.contact__consent a:focus-visible {
  text-decoration-color: transparent;
}

/* Четвёртое: подчёркивание выбранного фильтра каталога — псевдоэлемент,
   который умеет ехать (blocks/catalog.css). Снимаем его тем же способом,
   которым он появился, — линия уезжает туда, откуда пришла. */
.catalog-filter__item[aria-current]:hover .catalog-filter__label::after,
.catalog-filter__item[aria-current]:focus-visible .catalog-filter__label::after {
  transform: scaleX(0);
}

/* Обводки нет — гасим. Пункты шапки, у которых подчёркивание есть, из списка
   исключены явно: они уже в первом правиле, и гасить их заодно значило бы
   склеить два разных отклика в один. */
.footer__link:hover,
.footer__link:focus-visible,
.header__logo-link:hover,
.header__logo-link:focus-visible,
.header__link:not(.header__link--underline):hover,
.header__link:not(.header__link--underline):focus-visible,
.menu__link:hover,
.menu__link:focus-visible,
.breadcrumbs a:hover,
.breadcrumbs a:focus-visible,
.case-card__tag-link:hover,
.case-card__tag-link:focus-visible,
.catalog-filter__item:not([aria-current]):hover,
.catalog-filter__item:not([aria-current]):focus-visible {
  opacity: var(--opacity-hover);
}

/* Кнопка share на плитке приходит вместе с оверлеем и держит opacity сама
   (blocks/cases.css), поэтому её наведение приходится объявлять из того же
   состояния: .case-card.is-active .case-card__share — три класса, и правило
   выше их не перебило бы. */
.case-card.is-active .case-card__share:hover,
.case-card.is-active .case-card__share:focus-visible {
  opacity: var(--opacity-hover);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- Режим высокой контрастности -----------------------------------------
   Windows High Contrast (в CSS — forced-colors: active) заменяет всю палитру
   страницы системной и заодно выключает два приёма, на которых здесь держится
   половина интерфейса:

     background-color принудительно становится системным фоном. А иконки и
     логотип нарисованы не <img>, а маской по заливке currentColor
     (css/icons.css, blocks/header.css) — то есть в этом режиме они сливаются
     с фоном и исчезают. В шапке пропадает само лого, в меню — солнце и луна
     у переключателя темы, в форме — стрелка списка и галка согласия.

     box-shadow не рисуется вовсе. А подчёркивания на сайте сделаны именно
     тенью, а не рамкой, — чтобы не менять высоту бокса (почему, подробно у
     .header__link--underline в blocks/header.css). Вместе с тенью исчезает
     единственный признак, по которому пункт читается как нажимаемый.

   Чиним оба. Иконкам — forced-color-adjust: none и явный CanvasText: так они
   красятся системным цветом текста и видны на системном фоне. Подчёркиваниям
   — text-decoration вместо тени: она в этом режиме рисуется, а геометрию, в
   отличие от border, не трогает, и бокс остаётся тем же, что в макете.

   Правило собрано в одном месте, а не разложено по блокам, потому что это
   одна политика на весь сайт, а не свойство каждого из них по отдельности. */
@media (forced-colors: active) {
  .icon,
  .header__logo {
    forced-color-adjust: none;
    background-color: CanvasText;
  }

  .header__link--underline,
  .menu__cta--underline,
  .cookie__button,
  .notfound__link,
  .contact__submit,
  .case-card__action {
    text-decoration: underline;
  }

  /* И снимается по наведению тем же способом, каким нарисована. Правило
     «есть обводка — убрать» выше снимает тень, а тени в этом режиме нет
     вовсе — без этой пары наведение на подчёркнутых пунктах пропало бы
     заодно с ней.

     Ссылка на кейс и имя контакта сюда не попадают: у них линия — настоящая
     рамка, и её цвет в этом режиме принудительно системный, прозрачным его
     не сделать. Там наведение остаётся только рамкой фокуса. */
  .header__link--underline:hover,
  .header__link--underline:focus-visible,
  .menu__cta--underline:hover,
  .menu__cta--underline:focus-visible,
  .cookie__button:hover,
  .cookie__button:focus-visible,
  .notfound__link:hover,
  .notfound__link:focus-visible,
  .contact__submit:not([disabled]):hover,
  .contact__submit:not([disabled]):focus-visible {
    text-decoration: none;
  }
}
