/**
 * category-page.css — компоненты страниц каталога (три уровня).
 *
 * Подключается ПОСЛЕ theme-atlas.css. Тема задаёт палитру, типографику и
 * геометрию; этот файл — только новые блоки, которых не было в проекте.
 * Ничего из site.css / base.css / components.css не редактируется.
 *
 * ОБСЛУЖИВАЕТ ТРИ ШАБЛОНА (ТЗ NEW-TZ-...-SERP-SEO-v2-2026-08-06.md, §3
 * «catalog-shell» — общий набор компонентов и три конфигурации страницы):
 *
 *   catalog-hub.html     — хаб направления      (пример: металлообработка)
 *   catalog-branch.html  — товарная ветка       (пример: токарные станки)
 *   catalog-leaf.html    — конечная подкатегория (пример: токарные с ЧПУ)
 *
 * Файл разделён на две части:
 *   ЧАСТЬ 1 — компоненты, написанные для 04-category-stanki.html (первая
 *     редакция шаблона категории). Используются всеми тремя уровнями.
 *   ЧАСТЬ 2 — компоненты, добавленные под новое ТЗ: карточки веток и
 *     подкатегорий, навигация по операции, таблица сравнения типов,
 *     переключатели подборок, drawer фильтров, чипы активных фильтров,
 *     пагинация, панель сравнения, расширенная карточка товара, бренды
 *     с деталями, преимущества, статьи, пустые состояния, загрузка файла.
 *
 * БЕЗ JS. Все интерактивные состояния — нативные <details>, checkbox и radio.
 * Причина: ТЗ §8 требует, чтобы контент и навигация присутствовали в
 * исходном HTML и работали без выполнения скриптов, а §9 — клавиатурную
 * доступность. Готовые элементы формы дают и то, и другое из коробки.
 *
 * ДЕМО-НАПОЛНЕНИЕ. Это макеты: товары, значения фильтров, счётчики, кейсы и
 * логотипы — плейсхолдеры, показывающие форму и место будущих данных.
 * Реальной товарной базы в проекте нет, подставит её Codex при сборке.
 */

/* ███████████████████████████████████████████████████████████████
   ЧАСТЬ 1 — БАЗОВЫЕ КОМПОНЕНТЫ
   Написаны для 04-category-stanki.html, используются всеми уровнями.
███████████████████████████████████████████████████████████████ */

/* ═══════════════════════════════════════════════════════════════
   ПОМЕТКА ДЕМО-ДАННЫХ
═══════════════════════════════════════════════════════════════ */
.demo-flag {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: var(--sp-sm);
  padding: 10px 16px;
  border: 1px dashed var(--c-accent);
  border-radius: var(--radius);
  background: var(--c-accent-light);
  color: var(--c-accent-hover);
  font-size: var(--fz-sm);
}
.demo-flag::before {
  content: "";
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: var(--c-accent);
}
.demo-tag {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 4px;
  background: var(--c-accent-light);
  color: var(--c-accent-hover);
  font-size: var(--fz-xs);
  font-weight: var(--fw-bold);
}

/* ═══════════════════════════════════════════════════════════════
   HERO КАТЕГОРИИ — заголовок, интро, CTA и строка условий поставки
═══════════════════════════════════════════════════════════════ */
.cat-hero { padding: 72px var(--container-px) 40px; }
/* Размер не задаётся — приходит из шкалы h1 в theme-atlas.css (46px).
   Здесь остаётся только то, что специфично для первого экрана. */
.cat-hero h1 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: -0.02em;
  color: var(--c-text);
  max-width: 24ch;
  margin: 0 0 18px;
}
.cat-hero-lead {
  font-size: var(--fz-lg);
  line-height: 1.5;
  color: var(--c-text-muted);
  max-width: 78ch;
  margin: 0 0 var(--sp-md);
}
.cat-hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: var(--sp-md); }

/* Kicker-строка перед h1 — сейчас только на 404 («Ошибка 404» перед
   «Страница не найдена»). Без uppercase — правило проекта. */
.cat-hero-kicker {
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fz-sm);
  color: var(--c-accent-hover);
  margin-bottom: 8px;
}

/* Строка условий: наличие / под заказ / доставка / лизинг / ПНР */
.supply-line {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 28px;
  padding-top: var(--sp-sm);
  border-top: 1px solid var(--c-border);
  font-size: var(--fz-sm);
  color: var(--c-text-muted);
}
.supply-line span { display: flex; align-items: center; gap: 8px; }
.supply-line span::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--c-accent);
  flex: 0 0 auto;
}

/* ═══════════════════════════════════════════════════════════════
   ВЛОЖЕННЫЕ ТИПЫ — чипы-ссылки со счётчиком
═══════════════════════════════════════════════════════════════ */
.subtype-chips { display: flex; flex-wrap: wrap; gap: 10px; }
.subtype-chips a {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 9px 16px;
  border: 1px solid var(--c-border);
  border-radius: 999px;
  background: #fff;
  color: var(--c-text);
  font-size: var(--fz-sm);
  text-decoration: none;
  transition: border-color var(--transition), color var(--transition);
}
.subtype-chips a:hover { border-color: var(--c-accent); color: var(--c-accent); }
.subtype-chips .count { color: var(--c-text-light); font-size: var(--fz-xs); }

/* ═══════════════════════════════════════════════════════════════
   ТОВАРНАЯ ЗОНА — сайдбар фильтров + сетка карточек
═══════════════════════════════════════════════════════════════ */
.catalog-layout {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: var(--sp-lg);
  align-items: start;
}

/* ── Фильтры. Группы свёрнуты нативным <details> — без JS, доступно
   с клавиатуры из коробки. ── */
.filters {
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  padding: 8px 20px 16px;
  position: sticky;
  top: calc(var(--header-h) + 12px);
}
.filters-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 0 10px;
  border-bottom: 1px solid var(--c-border-light);
}
.filters-head b { font-size: var(--fz-base); }
.filters-reset {
  font-size: var(--fz-sm);
  color: var(--c-text-light);
  text-decoration: underline;
  text-decoration-thickness: 0.5px;
  text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
  text-underline-offset: 4px;
}
.filters-reset:hover { color: var(--c-accent); text-decoration-color: currentColor; }

.filter-group { border-bottom: 1px solid var(--c-border-light); }
.filter-group:last-child { border-bottom: none; }
.filter-group > summary {
  list-style: none;
  cursor: pointer;
  padding: 14px 0;
  font-size: var(--fz-sm);
  font-weight: var(--fw-bold);
  color: var(--c-text);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.filter-group > summary::-webkit-details-marker { display: none; }
.filter-group > summary::after {
  content: "";
  width: 8px;
  height: 8px;
  border-right: 1.5px solid var(--c-text-light);
  border-bottom: 1.5px solid var(--c-text-light);
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--transition);
}
.filter-group[open] > summary::after { transform: rotate(-135deg) translateY(-2px); }
.filter-group > summary:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.filter-body { padding-bottom: 14px; }

.filter-opt {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
  font-size: var(--fz-sm);
  color: var(--c-text-muted);
  cursor: pointer;
}
.filter-opt input { width: 16px; height: 16px; cursor: pointer; flex: 0 0 auto; }
.filter-opt:hover { color: var(--c-text); }
.filter-opt .n { margin-left: auto; color: var(--c-text-light); font-size: var(--fz-xs); }

/* Диапазоны — «от / до» */
.filter-range { display: flex; align-items: center; gap: 8px; }
.filter-range input {
  width: 100%;
  min-width: 0;
  padding: 7px 10px;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: var(--fz-sm);
  background: #fff;
  color: var(--c-text);
}
.filter-range input:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 1px; }
.filter-range span { color: var(--c-text-light); font-size: var(--fz-sm); }
.filter-unit { margin-top: 6px; font-size: var(--fz-xs); color: var(--c-text-light); }

/* ── Панель над сеткой: количество, сортировка, вид ── */
.catalog-toolbar {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  flex-wrap: wrap;
  margin-bottom: var(--sp-md);
}
.catalog-count { font-size: var(--fz-sm); color: var(--c-text-muted); }
.catalog-toolbar .spacer { flex: 1 1 auto; }
.catalog-sort {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fz-sm);
  color: var(--c-text-muted);
}
.catalog-sort select {
  padding: 7px 12px;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  background: #fff;
  font: inherit;
  font-size: var(--fz-sm);
  color: var(--c-text);
  cursor: pointer;
}
.catalog-sort select:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 1px; }
.view-switch { display: flex; gap: 4px; }
.view-switch button {
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--c-text-light);
  transition: color var(--transition), border-color var(--transition);
}
.view-switch button[aria-pressed="true"] { color: var(--c-accent); border-color: var(--c-accent); }
.view-switch button:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

/* ── Сетка и карточка товара ── */
.product-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-md);
}
.product-card {
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: border-color var(--transition);
}
.product-card:hover { border-color: var(--c-text-light); }
/* Фон белый: часть фотографий оборудования — PNG с прозрачностью, на сером
   у них просвечивает подложка. object-fit: contain + отступ, чтобы товар не
   упирался в края рамки. */
.product-media {
  position: relative;
  aspect-ratio: 4 / 3;
  background: #fff;
  border-bottom: 1px solid var(--c-border-light);
  display: grid;
  place-items: center;
}
.product-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 14px;
  box-sizing: border-box;
  display: block;
}
.product-media .demo-tag { position: absolute; top: 10px; left: 10px; z-index: 1; }

/* Карточка без фотографии: вместо пустой дыры — честная подпись.
   Реальных фото товаров в проекте нет, в шаблоне это штатное состояние. */
.product-media:not(:has(img))::after {
  content: "фото готовится";
  font-size: var(--fz-xs);
  color: var(--c-text-light);
  background: var(--c-bg-light);
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
}
.product-body { padding: 18px 20px 20px; display: flex; flex-direction: column; flex: 1 1 auto; }
.product-brand { font-size: var(--fz-xs); color: var(--c-text-light); margin-bottom: 4px; }
/* Размер — из шкалы h3 (18px). Раньше здесь стояло 16px, из-за чего
   заголовок карточки товара был мельче заголовка карточки раздела. */
.product-card h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.3;
  margin: 0 0 10px;
}
.product-card h3 a { color: var(--c-text); text-decoration: none; }
.product-card h3 a:hover { color: var(--c-accent); }

.product-status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  margin-bottom: 12px;
  font-size: var(--fz-xs);
  color: var(--c-text-muted);
}
.product-status::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--c-text-light);
}
.product-status.in-stock { color: var(--c-success); }
.product-status.in-stock::before { background: var(--c-success); }
.product-status.is-request { color: #2e5978; }
.product-status.is-request::before { background: #2e5978; }

.country-chip {
  display: inline-flex;
  align-items: flex-end;
  gap: 6px;
  line-height: 1;
}
.country-flag {
  display: inline-block;
  max-width: 20px;
  height: auto;
  margin-left: 6px;
  border: 1px solid #dbdbdb;
}

.pdp-keyspec-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.pdp-keyspec-flag {
  display: inline-block;
  max-width: 20px;
  height: auto;
  border: 1px solid #dbdbdb;
  flex-shrink: 0;
}

.engine-country-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.pdp-keyspecs dt {
  flex: 1 1 auto;
  min-width: 0;
}

.pdp-keyspecs dd {
  display: flex;
  flex: 0 1 50%;
  min-width: 0;
  max-width: 50%;
  justify-content: flex-end;
}

.pdp-keyspecs .engine-country-label {
  max-width: 100%;
  margin-left: auto;
}

.pdp-keyspecs .engine-country-label .country-flag {
  width: 20px;
  min-width: 20px;
  flex: 0 0 20px;
  margin-left: 0;
  box-sizing: border-box;
}

.pdp-keyspecs .engine-brand {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Параметры — таблица «характеристика / значение», tabular-nums для чисел */
.product-specs {
  margin: 0 0 14px;
  font-size: var(--fz-sm);
  font-variant-numeric: tabular-nums;
  border-top: 1px solid var(--c-border-light);
}
.product-specs div {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-bottom: 1px solid var(--c-border-light);
}
.product-specs dt { color: var(--c-text-light); }
.product-specs dd { margin: 0; color: var(--c-text); text-align: right; }

/* Цена. В шаблоне намеренно показаны все состояния: сумма от пяти до
   восьми знаков, «под заказ» и «по запросу» — чтобы было видно, как
   ведёт себя строка при разной длине и не ломается ли выравнивание
   кнопок под ней. tabular-nums держит разряды в одной сетке. */
.product-price {
  margin-top: auto;
  padding-top: 4px;
  font-size: var(--fz-lg);
  font-weight: var(--fw-bold);
  color: var(--c-text);
  font-variant-numeric: tabular-nums;
  line-height: 1.25;
}
.product-price small { display: block; font-weight: var(--fw-normal); font-size: var(--fz-xs); color: var(--c-text-light); margin-top: 3px; }
/* «По запросу» / «Под заказ» — не сумма, поэтому спокойнее по весу */
.product-price.is-request {
  font-size: var(--fz-base);
  font-weight: var(--fw-normal);
  color: var(--c-text-muted);
}
/* Кнопки в карточке — контурные, не заливные: шесть залитых акцентом кнопок
   в сетке перетягивают на себя весь экран и спорят с главным CTA страницы.
   Заливка акцентом остаётся только у действий первого экрана и формы.

   Правка 08.08 по замечанию владельца. Раньше это достигалось локальным
   переопределением .product-actions .btn-primary — белый фон, серая рамка,
   акцентный текст. Получался ТРЕТИЙ вид кнопки: не заливка и не .btn-outline,
   а что-то между, и «Подробнее» с «Получить КП» в одной карточке выглядели
   разными сущностями без причины. Переопределение снято, обе кнопки в
   карточке теперь обычный .btn-outline — тот же вариант, что у «Отправить ТЗ»
   на первом экране, с инверсией на наведении.

   Иерархию внутри карточки при этом не держим намеренно: главное действие
   страницы — форма подбора, а карточка ведёт либо к подробностям, либо к
   той же форме. Если владелец захочет выделить «Получить КП», это отдельное
   решение, и делать его нужно не третьим стилем, а заливкой. */
.product-actions { display: flex; align-items: center; gap: 10px; margin-top: 14px; }
.product-actions .btn { flex: 1 1 auto; padding: 9px 14px; font-size: var(--fz-sm); }

/* ═══════════════════════════════════════════════════════════════
   РОВНАЯ ВЫСОТА КАРТОЧЕК В СЕТКЕ — правка 08.08 по замечанию
   владельца «карточки растекаются по вертикали».

   Механика проблемы. .product-grid — грид, и элементы по умолчанию
   тянутся до высоты самого высокого в ряду. Внутри карточки у
   .product-price стоит margin-top: auto, чтобы цена и кнопки во всех
   карточках стояли на одной линии. Пока содержимое одинаковое, всё
   ровно. Но стоит одной карточке иметь на строку меньше характеристик,
   заголовок в одну строку вместо двух или не иметь маркеров видео/PDF —
   вся разница высот собирается в одну дыру посередине карточки, между
   описанием и ценой. Именно это и читается как «растеклась».

   Почему не убрать margin-top: auto. Тогда цены и кнопки встанут на
   разной высоте в каждой карточке, и сетку станет невозможно
   просматривать глазом по горизонтали — а это главное, ради чего
   листинг существует. Лечим не выравнивание цены, а разнобой блоков
   над ней.

   ПРАВИЛО ДЛЯ СБОРКИ (Codex): карточка в сетке выводит ФИКСИРОВАННОЕ
   число строк характеристик — три в обычном листинге, две в компактном.
   Недостающие значения обозначаются «—», а не пропускаются: это то же
   правило, что ТЗ §8 требует для таблицы сравнения. Данные в срезе
   неполные (характеристики есть у 31% товаров), поэтому «—» будет
   частым и штатным.
═══════════════════════════════════════════════════════════════ */
/* Две строки под заголовок: названия станков почти всегда переносятся,
   но не всегда — резерв убирает скачок высоты. */
.product-card h3 { min-height: 2.6em; }
/* Строка маркеров резервируется, даже когда маркеров нет: иначе
   карточка с видео и PDF выше соседей ровно на эту строку. */
.product-flags { min-height: 24px; }

/* ═══════════════════════════════════════════════════════════════
   КОМПАКТНЫЙ ЛИСТИНГ — .product-grid.is-compact
   Правка 08.08: «карточки „Похожие“ огромные».

   Обычная плитка рассчитана на листинг категории, где она главный объект
   страницы: три колонки на 1544px дают карточку в 500px и фотографию
   375px по высоте. На карточке товара тот же компонент стоит вспомогательным
   блоком внизу — и в прежнем размере перевешивал сам товар.

   Компактный вариант меняет только плотность: четыре колонки вместо трёх,
   фотография 3:2 вместо 4:3, меньше внутренних отступов, скрыта подпись
   подкатегории (в блоке «похожие» она у всех одна и та же, это шум).
   Размер заголовка НЕ трогается — он приходит из шкалы, а третье
   исключение из неё ради вспомогательного блока не оправдано.
═══════════════════════════════════════════════════════════════ */
/* ── Правка 08.08, вторая итерация: «почему плитка такая вытянутая».
   Дело было не в конкретной карточке — структурно все четыре одинаковы.
   Дело в пропорции. При четырёх колонках на 1544px плитка получает 365px
   ширины, а по высоте набиралось примерно 510px:

     фото 3:2 .......... 243
     строка бренда ...... 18
     заголовок (2 стр.) . 47
     статус ............. 20
     3 строки характ. ... 66
     строка маркеров .... 24
     цена ............... 22
     кнопка ............. 40
     отступы ............ 30

   То есть плитка была в полтора раза выше своей ширины — это и читается
   как «вытянутая». Срезаны три слоя, которые во вспомогательном блоке не
   нужны: фотография переведена с 3:2 на 16:10, строка маркеров скрыта
   (видео и PDF смотрят уже на самой странице товара), характеристик
   оставлено две вместо трёх. Стало около 449px против 365px ширины —
   плитка перестала быть колонкой. ── */
.product-grid.is-compact { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-sm); }
.is-compact .product-media { aspect-ratio: 16 / 10; }
.is-compact .product-media img { padding: 10px; }
.is-compact .product-body { padding: 14px 16px 16px; }
.is-compact .product-subtype { display: none; }
.is-compact .product-flags { display: none; }
.is-compact .product-specs { font-size: var(--fz-xs); margin-bottom: 10px; }
.is-compact .product-specs div { padding: 5px 0; }
.is-compact .product-price { font-size: var(--fz-base); }
.is-compact .product-actions { margin-top: 12px; }

@media (max-width: 1400px) {
  .product-grid.is-compact { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.btn-compare {
  flex: 0 0 auto;
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  background: #fff;
  color: var(--c-text-light);
  transition: color var(--transition), border-color var(--transition);
}
.btn-compare:hover { color: var(--c-accent); border-color: var(--c-accent); }
.btn-compare:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

.catalog-more { margin-top: var(--sp-md); text-align: center; }

/* ═══════════════════════════════════════════════════════════════
   ПОДБОРКИ, «КАК ВЫБРАТЬ», СЕРВИС, КЕЙСЫ
═══════════════════════════════════════════════════════════════ */
.collection-links { display: flex; flex-wrap: wrap; gap: 10px; }
.collection-links a {
  padding: 8px 14px;
  border-radius: var(--radius);
  background: #fff;
  border: 1px solid var(--c-border);
  color: var(--c-teal);
  font-size: var(--fz-sm);
  text-decoration: none;
  transition: border-color var(--transition), color var(--transition);
}
.collection-links a:hover { border-color: var(--c-accent); color: var(--c-accent); }

.howto-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-md) var(--sp-lg);
  counter-reset: howto;
}
.howto-item { padding-top: 14px; border-top: 2px solid var(--c-accent); }
.howto-item h3 {
  font-family: var(--font-display);
  font-weight: 600;
  margin: 0 0 6px;
}
.howto-item p { font-size: var(--fz-sm); color: var(--c-text-muted); margin: 0; }

.service-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--sp-sm);
}
.service-card {
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  padding: 20px;
}
.service-card h3 {
  font-family: var(--font-display);
  font-weight: 600;
  margin: 0 0 6px;
}
.service-card p { font-size: var(--fz-sm); color: var(--c-text-muted); margin: 0; }

.case-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-md); }
.case-card {
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  padding: 22px;
}
.case-card .case-branch { font-size: var(--fz-xs); color: var(--c-accent-hover); margin-bottom: 8px; }
.case-card h3 {
  font-family: var(--font-display);
  font-weight: 600;
  margin: 0 0 10px;
}
.case-meta { margin: 0; font-size: var(--fz-sm); }
.case-meta div { display: flex; gap: 10px; padding: 5px 0; border-top: 1px solid var(--c-border-light); }
.case-meta dt { color: var(--c-text-light); flex: 0 0 96px; }
.case-meta dd { margin: 0; color: var(--c-text); }

/* Заголовок секции: подпись + волосяная линия — тот же язык, что у
   «Производителей» на корне каталога, чтобы разделы читались единообразно. */
/* ВАЖНО: класс рассчитан на тег <h2> — размер приходит из шкалы (30px),
   своего font-size у класса больше нет. На <div> он останется без размера.
   Раньше здесь стояло 22px, из-за чего заголовок секции был мельче
   заголовка формы, хотя стоит уровнем выше. */
.sec-head {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 var(--sp-md);
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--c-text);
}
.sec-head::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--c-border);
}
.sec-note { margin: -14px 0 var(--sp-md); font-size: var(--fz-sm); color: var(--c-text-muted); }

/* ═══════════════════════════════════════════════════════════════
   FAQ
═══════════════════════════════════════════════════════════════ */
.faq-list { border-top: 1px solid var(--c-border); max-width: 90ch; }
.faq-item { border-bottom: 1px solid var(--c-border); }
.faq-item > summary {
  list-style: none;
  cursor: pointer;
  padding: 18px 40px 18px 0;
  position: relative;
  font-family: var(--font-display);
  font-size: var(--fz-base);
  font-weight: 600;
  color: var(--c-text);
}
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary::after {
  content: "";
  position: absolute;
  right: 8px; top: 24px;
  width: 9px; height: 9px;
  border-right: 1.5px solid var(--c-text-light);
  border-bottom: 1.5px solid var(--c-text-light);
  transform: rotate(45deg);
  transition: transform var(--transition);
}
.faq-item[open] > summary::after { transform: rotate(-135deg); top: 28px; }
.faq-item > summary:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.faq-item p { padding: 0 40px 18px 0; margin: 0; color: var(--c-text-muted); max-width: 80ch; }

/* Таблицы из редакционного контента: собственный скролл на узком экране,
   без горизонтального растягивания страницы. */
.compare-wrap {
  max-width: 100%;
  overflow-x: auto;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  background: #fff;
}
.compare-table {
  width: 100%;
  min-width: 620px;
  border-collapse: collapse;
  font-size: var(--fz-sm);
}
.compare-table th,
.compare-table td {
  padding: 12px 16px;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--c-border-light);
}
.compare-table th { font-weight: var(--fw-bold); background: var(--c-bg-light); }
.compare-table tr:last-child td { border-bottom: 0; }
.compare-table tbody tr:hover { background: var(--c-bg-light); }

/* ███████████████████████████████████████████████████████████████
   ЧАСТЬ 2 — КОМПОНЕНТЫ НОВОГО ТЗ (hub / branch / leaf)
███████████████████████████████████████████████████████████████ */

/* ═══════════════════════════════════════════════════════════════
   HERO С ИЗОБРАЖЕНИЕМ И СЧЁТЧИКАМИ
   ТЗ §4.2 п.2, §5.2 п.2, §6.2 — на первом экране обязательны
   изображение категории и счётчики (веток / подкатегорий / моделей).
═══════════════════════════════════════════════════════════════ */
.cat-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 420px;
  gap: var(--sp-lg);
  align-items: center;
}
.cat-hero-grid .cat-hero-lead { max-width: none; }
.cat-hero-media {
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  background: #fff;
  aspect-ratio: 4 / 3;
  display: grid;
  place-items: center;
  overflow: hidden;
}
/* Изображение первого экрана НЕ ленивое — ТЗ §8.10 п.6. */
.cat-hero-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 24px;
  box-sizing: border-box;
}
/* .cat-hero-media svg — та же зона, но для декоративной inline-иллюстрации
   без фотографии (шаблон 11, 404). object-fit к svg не относится, ширину
   держит сам viewBox. */
.cat-hero-media svg {
  width: 100%;
  height: 100%;
  padding: 24px;
  box-sizing: border-box;
}
.cat-hero-media:not(:has(img)):not(:has(svg))::after {
  content: "изображение категории готовится";
  font-size: var(--fz-sm);
  color: var(--c-text-light);
}

/* ═══════════════════════════════════════════════════════════════
   ВАРИАНТ Б ПЕРВОГО ЭКРАНА — КАРТИНКА БЕЗ ПЛАШКИ (шаблон 2, на выбор)

   Базовый .cat-hero-media выше — «плашка»: белая карточка, рамка,
   фиксированный кадр 4:3, contain с паддингом 24px. Это правильно для
   логотипа бренда (шаблон 8) и иллюстрации 404 (шаблон 11) — там объект
   должен лежать НА подложке. Владелец попросил для направления обратное:
   картинка встроена в страницу, её фон совпадает с фоном шаблона.

   Реализовано модификаторами, базовые классы не тронуты: страницы 3/7/8/11
   используют .cat-hero-media без .is-plain и не меняются.

   ЗАЧЕМ mix-blend-mode: multiply. Файл hero-картинки — RGBA, но полностью
   непрозрачный: белый фон запечён в пиксели (углы 255,255,255,255). Фон
   же страницы не белый — var(--c-bg) = oklch(96.5% 0.004 75), тёплый
   почти-белый. Просто сняв рамку, мы получили бы белый прямоугольник на
   бежевом фоне с видимым швом — то есть ровно НЕ «встроенную» картинку.
   При multiply белый пиксель даёт цвет фона под собой, а тёмный станок
   остаётся тёмным: запечённый фон исчезает на любом фоне страницы.
   Настоящее решение — прозрачный PNG от владельца; до него это даёт
   визуально верный результат без перерисовки файла.
═══════════════════════════════════════════════════════════════ */
/* Ширина не фиксированная: на промежуточных экранах (1025–1300px, сетка
   ещё в две колонки) жёсткие 650px зажали бы текстовую колонку. clamp даёт
   максимум ~648px на широком мониторе и сам ужимается на узких. */
.cat-hero-grid.is-media-plain {
  grid-template-columns: minmax(0, 1fr) clamp(420px, 42%, 660px);
  align-items: start;
}
.cat-hero-media.is-plain {
  border: none;
  border-radius: 0;
  background: transparent;
  aspect-ratio: auto;
  overflow: visible;
}
.cat-hero-media.is-plain img {
  width: 100%;
  height: auto;
  padding: 0;
  object-fit: contain;
  mix-blend-mode: multiply;
  /* Компенсация пустого поля ВНУТРИ файла. Замер PNG: содержимое занимает
     y 87..752 из 813, сверху 87px пустоты. С multiply она невидима, поэтому
     одного align-items:start мало — станок начался бы заметно ниже H1.
     Процентные вертикальные отступы считаются от ШИРИНЫ контейнера, а
     87/1088 = 8% ширины файла, поэтому −8% гасит поле точно и на любой
     ширине колонки. Меняется одним числом, если нужно подвинуть. */
  margin-top: -8%;
}
/* Счётчики. tabular-nums — чтобы разряды не «плясали», когда Codex
   подставит реальные числа вместо демонстрационных. */
.count-line {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 40px;
  margin: 0 0 var(--sp-md);
  font-variant-numeric: tabular-nums;
}
.count-line b {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fz-2xl);
  font-weight: 600;
  line-height: 1.1;
  color: var(--c-text);
}
.count-line span { font-size: var(--fz-sm); color: var(--c-text-muted); }

/* ═══════════════════════════════════════════════════════════════
   NAV-CARD — одна карточка для двух ролей
   ТЗ §3: общий набор компонентов, а не три независимых макета.
     .branch-grid — товарные ветки на хабе      (ТЗ §4.2 п.3)
     .subcat-grid — конечные подкатегории ветки (ТЗ §5.2 п.3)
   Отличаются только сеткой и тем, какие поля заполнены.
═══════════════════════════════════════════════════════════════ */
.branch-grid,
.subcat-grid {
  display: grid;
  gap: var(--sp-md);
}
.branch-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.subcat-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.nav-card {
  position: relative;
  display: flex;
  flex-direction: column;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  transition: border-color var(--transition);
}
.nav-card:hover { border-color: var(--c-text-light); }

.nav-card-media {
  aspect-ratio: 16 / 10;
  background: #fff;
  border-bottom: 1px solid var(--c-border-light);
  display: grid;
  place-items: center;
}
.nav-card-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding: 16px;
  box-sizing: border-box;
  display: block;
}
/* Штатное состояние «нет изображения» — ТЗ §7.3. Пустого контейнера
   не оставляем: подпись честно объясняет, чего не хватает. */
.nav-card-media:not(:has(img))::after {
  content: "изображение готовится";
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  background: var(--c-bg-light);
  font-size: var(--fz-xs);
  color: var(--c-text-light);
}

.nav-card-body {
  padding: 20px;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
}
.nav-card h3 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: 1.25;
  margin: 0 0 6px;
}
.nav-card h3 a { color: var(--c-text); text-decoration: none; }
.nav-card h3 a:hover { color: var(--c-accent); }
/* Карточка кликабельна целиком — ТЗ §4.2: «Карточка целиком кликабельна,
   но вложенные ссылки должны оставаться самостоятельными». Растяжка от
   заголовка накрывает карточку, вложенные ссылки поднимаются над ней
   z-index'ом и сохраняют собственные адреса. */
.nav-card h3 a::after { content: ""; position: absolute; inset: 0; z-index: 0; }
.nav-card h3 a:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 3px; }

.nav-card-purpose {
  margin: 0 0 10px;
  font-size: var(--fz-sm);
  color: var(--c-text-muted);
}
.nav-card-count {
  margin-bottom: 12px;
  font-size: var(--fz-xs);
  color: var(--c-text-light);
  font-variant-numeric: tabular-nums;
}

/* 3–5 популярных вложенных ссылок (ветка) */
.nav-card-links {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.nav-card-links a {
  position: relative;
  z-index: 1;
  font-size: var(--fz-sm);
  color: var(--c-teal);
  text-decoration: underline;
  text-decoration-thickness: 0.5px;
  text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
  text-underline-offset: 4px;
  transition: color var(--transition), text-decoration-color var(--transition);
}
.nav-card-links a:hover { color: var(--c-accent); text-decoration-color: currentColor; }

/* 2–3 отличия или сценария применения (подкатегория) */
.nav-card-facts {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: var(--fz-xs);
  color: var(--c-text-muted);
}
.nav-card-facts li { position: relative; padding-left: 15px; }
.nav-card-facts li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 7px;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--c-accent);
}

/* В покое чёрный текст с волосяным подчёркиванием, при наведении —
   акцентный цвет и линия в полную силу. Начертание НЕ меняется.

   Правка 08.08 по замечанию владельца: раньше на наведении включался
   bold, и ссылка «прыгала». Причина скачка — не анимация, а сама смена
   веса: Onest подключён статическими весами (400/500/600/700), плавно
   переходить не по чему, а жирный текст шире обычного, поэтому ссылка
   меняет ширину под курсором. На корне каталога ссылка стоит отдельной
   строкой и сдвиг не был заметен; здесь она живёт внутри карточки, где
   изменение ширины видно сразу. Смены цвета для обратной связи
   достаточно — вес не трогаем. */
.nav-card-more {
  position: relative;
  z-index: 1;
  margin-top: auto;
  align-self: flex-start;
  font-size: var(--fz-sm);
  color: var(--c-text);
  text-decoration: underline;
  text-decoration-thickness: 0.5px;
  text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
  text-underline-offset: 4px;
  transition: color var(--transition), text-decoration-color var(--transition);
}
.nav-card-more:hover {
  color: var(--c-accent);
  text-decoration-color: currentColor;
}

/* ═══════════════════════════════════════════════════════════════
   НАВИГАЦИЯ ПО ПРОИЗВОДСТВЕННОЙ ОПЕРАЦИИ — ТЗ §4.2 п.4
   Вход для пользователя, который не знает названия станка.
   Ведёт на существующие канонические категории, новых URL не создаёт.
═══════════════════════════════════════════════════════════════ */
.op-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-sm);
}
.op-card {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding: 18px 20px;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
  text-decoration: none;
  transition: border-color var(--transition);
}
.op-card:hover { border-color: var(--c-accent); }
.op-card:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.op-card b {
  font-family: var(--font-display);
  font-size: var(--fz-base);
  font-weight: 600;
  color: var(--c-text);
}
.op-card span { font-size: var(--fz-xs); color: var(--c-text-muted); }

/* ═══════════════════════════════════════════════════════════════
   ПЕРЕКЛЮЧАТЕЛИ ПОДБОРОК — ТЗ §4.2 п.5
   «Популярные / В наличии / Новые поступления».
   Сделано на radio без JS: все три панели есть в исходном HTML
   (ТЗ §8.1 п.7), переключение доступно с клавиатуры.
═══════════════════════════════════════════════════════════════ */
.tabs { position: relative; }
/* Радиокнопки visually-hidden, но НЕ display:none — иначе теряют фокус. */
.tabs > input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.tabs-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: var(--sp-md);
}
.tabs-nav label {
  padding: 8px 16px;
  border: 1px solid var(--c-border);
  border-radius: 999px;
  background: #fff;
  font-size: var(--fz-sm);
  color: var(--c-text-muted);
  cursor: pointer;
  transition: border-color var(--transition), color var(--transition), background var(--transition);
}
.tabs-nav label:hover { border-color: var(--c-accent); color: var(--c-accent); }
.tab-panel { display: none; }

#tab-popular:checked  ~ .tabs-body .tab-popular,
#tab-instock:checked  ~ .tabs-body .tab-instock,
#tab-new:checked      ~ .tabs-body .tab-new { display: block; }

#tab-popular:checked ~ .tabs-nav label[for="tab-popular"],
#tab-instock:checked ~ .tabs-nav label[for="tab-instock"],
#tab-new:checked     ~ .tabs-nav label[for="tab-new"] {
  background: var(--c-accent);
  border-color: var(--c-accent);
  color: #fff;
}
/* Фокус показываем на подписи — сам input визуально скрыт. */
#tab-popular:focus-visible ~ .tabs-nav label[for="tab-popular"],
#tab-instock:focus-visible ~ .tabs-nav label[for="tab-instock"],
#tab-new:focus-visible     ~ .tabs-nav label[for="tab-new"] {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* ═══════════════════════════════════════════════════════════════
   БРЕНДЫ С ДЕТАЛЯМИ — ТЗ §4.2 п.6, §5.2 п.7
   Логотип, название, страна, количество моделей.
   .brand-tile из components.css остаётся для простого списка логотипов.
═══════════════════════════════════════════════════════════════ */
.brand-cards {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--sp-sm);
}
.brand-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 18px;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
  text-decoration: none;
  transition: border-color var(--transition);
}
.brand-card:hover { border-color: var(--c-accent); }
.brand-card:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.brand-card-logo { height: 32px; display: flex; align-items: center; }
.brand-card-logo img { max-height: 32px; max-width: 100%; object-fit: contain; }
/* Нет логотипа — на его месте название, а не пустая полоса (ТЗ §7.3). */
.brand-card-logo:not(:has(img))::after {
  content: attr(data-name);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fz-base);
  color: var(--c-text-light);
}
.brand-card b {
  font-family: var(--font-display);
  font-size: var(--fz-sm);
  font-weight: 600;
  color: var(--c-text);
}
.brand-card span {
  font-size: var(--fz-xs);
  color: var(--c-text-light);
  font-variant-numeric: tabular-nums;
}

/* ═══════════════════════════════════════════════════════════════
   ПЛАШКА БРЕНДА — .brand-plate (индекс производителей, шаблон 7)

   Вторая правка 09.08 — первая версия (лого + тихое имя + тихий счётчик
   одной колонкой) владельцу тоже не подошла, дал точное ТЗ построчно:
     1. текст названия из плашки убрать;
     2. саму плашку сделать цветной — логотип в родных цветах, без
        приглушения (раньше был grayscale → цвет на hover; теперь цвет
        сразу, hover больше не единственный способ узнать бренд);
     3. счётчик позиций — под плашкой, не внутри неё;
     4. название — всплывающей подсказкой у правого верхнего угла,
        СНАРУЖИ плашки, только при наведении/фокусе;
     5. имя дополнительно — в alt и title картинки (без этого без
        подсказки и без title бренд неотличим никак, кроме клика).

   Структура: .brand-plate — весь кликабельный блок (плашка + счётчик
   под ней). .brand-plate-box — сама цветная плашка, containing block
   для всплывашки. .brand-plate-tooltip — всплывашка, position:absolute
   от .brand-plate-box, aria-hidden — имя уже озвучено через alt/title
   на img, дублировать для скринридера не нужно.

   НЕ ПРОВЕРЕНО ВЖИВУЮ: у плиток из первого ряда и правого края сетки
   всплывашка «в правый верхний угол снаружи» может упереться в верх
   секции или в край контейнера — сеточных отступов достаточно
   (.brand-cards имеет gap, контейнер не обрезает overflow), но при
   реальном показе стоит посмотреть именно эти крайние плитки отдельно.
═══════════════════════════════════════════════════════════════ */
.brand-plate {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  text-decoration: none;
}
.brand-plate-box {
  position: relative;
  width: 100%;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
  box-sizing: border-box;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
  transition: border-color var(--transition);
}
.brand-plate:hover .brand-plate-box,
.brand-plate:focus-visible .brand-plate-box { border-color: var(--c-accent); }
.brand-plate:focus-visible { outline: none; }
.brand-plate:focus-visible .brand-plate-box { outline: 2px solid var(--c-accent); outline-offset: 2px; }

.brand-plate-img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
/* Нет логотипа — честная подпись на месте картинки, не пустая плашка. */
.brand-plate-box:not(:has(img))::after {
  content: attr(data-name);
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--fz-xs);
  color: var(--c-text-light);
}

.brand-plate-count {
  font-size: 11px;
  color: var(--c-text-light);
  font-variant-numeric: tabular-nums;
}

/* Всплывашка с именем — правый верхний угол, СНАРУЖИ плашки.
   translate половину своей же ширины/высоты выносит её ровно за угол
   .brand-plate-box, а не поверх него. */
.brand-plate-tooltip {
  position: absolute;
  top: 0;
  right: 0;
  transform: translate(35%, -60%);
  padding: 4px 9px;
  background: var(--c-text);
  color: #fff;
  font-size: 11px;
  white-space: nowrap;
  border-radius: var(--radius-sm);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--transition);
  z-index: 2;
}
.brand-plate:hover .brand-plate-tooltip,
.brand-plate:focus-visible .brand-plate-tooltip { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .brand-plate-tooltip { transition: none; }
}

/* Коммерческие преимущества (ТЗ §4.2 п.8) — семь пунктов.
   Переиспользуют .service-card, отличается только сетка. */
.adv-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-sm);
}

/* ═══════════════════════════════════════════════════════════════
   ТАБЛИЦА СРАВНЕНИЯ ТИПОВ — ТЗ §5.2 п.4
   Помогает выбрать ветку, а не дублирует характеристики SKU.
   min-width заставляет таблицу скроллиться внутри своего контейнера,
   а не растягивать страницу (ТЗ §9: горизонтальный скролл).
═══════════════════════════════════════════════════════════════ */
.table-scroll {
  overflow-x: auto;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
}
.compare-types {
  width: 100%;
  min-width: 900px;
  border-collapse: collapse;
  font-size: var(--fz-sm);
}
.compare-types th,
.compare-types td {
  padding: 14px 18px;
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--c-border-light);
}
.compare-types thead th {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--c-text);
  background: var(--c-bg);
  white-space: nowrap;
}
.compare-types tbody th { font-weight: 600; color: var(--c-text); }
.compare-types tbody th a { color: var(--c-teal); text-decoration: none; }
.compare-types tbody th a:hover { color: var(--c-accent); text-decoration: underline; }
.compare-types td { color: var(--c-text-muted); }
.compare-types tbody tr:last-child th,
.compare-types tbody tr:last-child td { border-bottom: none; }

/* ═══════════════════════════════════════════════════════════════
   ЧИПЫ АКТИВНЫХ ФИЛЬТРОВ — ТЗ §5.2 «выбранные фильтры»
═══════════════════════════════════════════════════════════════ */
.filter-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: var(--sp-sm);
}
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px 5px 13px;
  border: 1px solid var(--c-border);
  border-radius: 999px;
  background: #fff;
  font-size: var(--fz-xs);
  color: var(--c-text-muted);
}
.filter-chip button {
  width: 17px;
  height: 17px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--c-bg);
  color: var(--c-text-light);
  font-size: 12px;
  line-height: 1;
  transition: background var(--transition), color var(--transition);
}
.filter-chip button:hover { background: var(--c-accent); color: #fff; }
.filter-chip button:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

/* ═══════════════════════════════════════════════════════════════
   МОБИЛЬНЫЙ DRAWER ФИЛЬТРОВ — ТЗ §9
   На десктопе сайдбар остаётся на месте (см. ЧАСТЬ 1). На ≤1024px
   панель уезжает в выдвижной слой, иначе одиннадцать групп фильтров
   встают ВЫШЕ товаров и до первого станка приходится долго крутить.
   Реализация на checkbox — без JS, работает с клавиатуры.
═══════════════════════════════════════════════════════════════ */
.filters-toggle-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
.filters-toggle {
  display: none;
  align-items: center;
  gap: 9px;
  padding: 8px 16px;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  background: #fff;
  font-size: var(--fz-sm);
  color: var(--c-text);
  cursor: pointer;
}
.filters-toggle:hover { border-color: var(--c-accent); color: var(--c-accent); }
/* Счётчик выбранных фильтров на кнопке — прямое требование ТЗ §6.4. */
.filters-toggle .n {
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  display: grid;
  place-items: center;
  border-radius: 10px;
  background: var(--c-accent);
  color: #fff;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.filters-close,
.filters-backdrop { display: none; }

/* ═══════════════════════════════════════════════════════════════
   ПАГИНАЦИЯ — ТЗ §8.6
   Обычные ссылки. «Показать ещё» остаётся UX-надстройкой сверху,
   но не заменяет доступные URL страниц (§8.6 п.3).
═══════════════════════════════════════════════════════════════ */
.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: var(--sp-md);
  font-variant-numeric: tabular-nums;
}
.pagination a,
.pagination span {
  min-width: 42px;
  height: 42px;
  padding: 0 14px;
  display: inline-grid;
  place-items: center;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  background: #fff;
  font-size: var(--fz-sm);
  color: var(--c-text);
  text-decoration: none;
  transition: border-color var(--transition), color var(--transition);
}
.pagination a:hover { border-color: var(--c-accent); color: var(--c-accent); }
.pagination a:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.pagination [aria-current="page"] {
  border-color: var(--c-accent);
  background: var(--c-accent);
  color: #fff;
}
.pagination .gap { border: none; background: none; color: var(--c-text-light); min-width: 20px; padding: 0; }

/* ═══════════════════════════════════════════════════════════════
   ПАНЕЛЬ СРАВНЕНИЯ — ТЗ §6.8
   До 4 моделей, панель закреплена. Отсутствующий слот — пунктир,
   значение не угадывается (§6.8: пустое обозначается «—»).
═══════════════════════════════════════════════════════════════ */
.compare-bar {
  position: sticky;
  bottom: 12px;
  z-index: 40;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 14px var(--sp-sm);
  margin-top: var(--sp-md);
  padding: 14px 18px;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}
.compare-bar-title { font-size: var(--fz-sm); font-weight: var(--fw-bold); }
.compare-bar-title span { font-weight: var(--fw-normal); color: var(--c-text-light); }
.compare-slots { display: flex; gap: 8px; flex: 1 1 auto; }
.compare-slot {
  position: relative;
  width: 62px;
  height: 50px;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  background: #fff;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.compare-slot img { width: 100%; height: 100%; object-fit: contain; padding: 5px; box-sizing: border-box; }
.compare-slot.is-empty { border-style: dashed; }
.compare-slot.is-empty::after { content: "—"; color: var(--c-text-light); font-size: var(--fz-sm); }
.compare-slot button {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 16px;
  height: 16px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--c-bg);
  color: var(--c-text-light);
  font-size: 11px;
  line-height: 1;
}
.compare-slot button:hover { background: var(--c-accent); color: #fff; }
.compare-slot button:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 1px; }

/* ═══════════════════════════════════════════════════════════════
   КАРТОЧКА ТОВАРА — ДОПОЛНЕНИЯ ПОД ТЗ §5.2 п.6 и §6.7
   Добавлены: модель, страна, подкатегория, артикул, маркеры видео/PDF,
   кнопка «Подробнее». Базовые стили карточки — в ЧАСТИ 1.
═══════════════════════════════════════════════════════════════ */
.product-line {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-bottom: 4px;
  font-size: var(--fz-xs);
  color: var(--c-text-light);
}
.product-line .product-brand { margin: 0; }
.product-line .sep { color: var(--c-border); }
.product-sku { margin-left: auto; font-variant-numeric: tabular-nums; }

/* Модель — отдельным полем: в B2B-каталоге по ней ищут и сравнивают,
   поэтому она не растворяется в общем названии (ТЗ §7.2). */
.product-model {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--c-text);
}
.product-subtype {
  display: inline-block;
  margin-bottom: 10px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--c-bg);
  font-size: 11px;
  color: var(--c-text-muted);
}
.product-flags { display: flex; gap: 6px; margin-bottom: 10px; }
.product-flag {
  display: inline-grid;
  place-items: center;
  padding: 2px 8px;
  border: 1px solid var(--c-border);
  border-radius: 5px;
  font-size: 11px;
  color: var(--c-text-light);
}
.product-actions { flex-wrap: wrap; }
.product-actions .btn { flex: 1 1 110px; }

/* ═══════════════════════════════════════════════════════════════
   БЫСТРЫЕ ПОДБОРКИ И SEO-ПОСАДОЧНЫЕ — ТЗ §6.2 п.3, §8.5
   Утверждённая посадочная визуально отделена от обычного фильтра:
   у неё постоянный URL и место в навигации, у фильтра — нет.
═══════════════════════════════════════════════════════════════ */
/* Правка 08.08: оранжевым остаётся только РАМКА. Оранжевый текст в покое
   убран — по общему правилу системы оранжевый означает реакцию на действие,
   а не признак элемента. Отличие посадочной от обычного фильтра рамка несёт
   ничуть не хуже, и это тот же язык, что у кнопки: акцентная рамка в покое,
   акцентная заливка на наведении. */
.collection-links a.is-landing {
  border-color: var(--c-accent);
}
.chips-legend {
  margin: 10px 0 0;
  font-size: var(--fz-xs);
  color: var(--c-text-light);
}
/* Подпись-легенда: слово-указатель набрано не цветом, а рамкой-образцом,
   чтобы не заводить ещё один оранжевый текст в покое. */
.chips-legend b {
  font-weight: var(--fw-normal);
  color: var(--c-text-muted);
  border-bottom: 2px solid var(--c-accent);
}

/* Подсказка у сложного параметра фильтра — ТЗ §6.5.
   Нативный title: работает без JS и без кастомного тултипа.

   У .filter-group > summary включён justify-content: space-between — с двумя
   элементами (текст + стрелка) это работает, но с тремя подсказка повисала
   бы ровно посередине строки. Поэтому у групп с подсказкой распорка
   переносится на стрелку. */
.filter-group > summary:has(.filter-hint) { justify-content: flex-start; }
.filter-group > summary:has(.filter-hint)::after { margin-left: auto; }

.filter-hint {
  margin-left: 6px;
  width: 16px;
  height: 16px;
  display: inline-grid;
  place-items: center;
  border: 1px solid var(--c-border);
  border-radius: 50%;
  font-size: 10px;
  color: var(--c-text-light);
  cursor: help;
}

/* ═══════════════════════════════════════════════════════════════
   ФОРМА ИНЖЕНЕРНОГО ПОДБОРА — ТЗ §4.2 п.7, §5.2 п.8, §6.10
   Предметные поля + загрузка чертежа/ТЗ.
═══════════════════════════════════════════════════════════════ */
.form-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 var(--sp-sm);
}
.form-grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.form-subhead {
  margin: var(--sp-sm) 0 12px;
  padding-top: var(--sp-sm);
  border-top: 1px solid var(--c-border);
  font-size: var(--fz-sm);
  font-weight: var(--fw-bold);
  color: var(--c-text-muted);
}
.upload-field {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 14px 16px;
  border: 1px dashed var(--c-border);
  border-radius: var(--radius);
  background: #fff;
}
.upload-field input[type="file"] { font: inherit; font-size: var(--fz-sm); color: var(--c-text-muted); max-width: 100%; }
.upload-field input[type="file"]:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.upload-field small { font-size: var(--fz-xs); color: var(--c-text-light); }

/* Правка 09.08 «нижний отступ карточки формы вровень с верхним» и парный
   ховер полей — перенесены отсюда в theme-atlas.css: этот файл не
   подключён на 1-catalog.html, а обе правки нужны всем формам сайта без
   исключения. Смотреть там же, где .form-control:focus. */

/* ═══════════════════════════════════════════════════════════════
   ДВЕ КОЛОНКИ ФОРМЫ — .form-halves
   Правка 09.08 по указанию владельца, пока только на форме подбора
   6-card.html. Слева друг под другом строка контактов и комментарий,
   справа — вложение на всю высоту обеих строк слева.

   Высота правой колонки НЕ подогнана числом: .form-halves — grid с
   одной строкой на две колонки, align-items растягивает более короткую
   колонку по высоте более длинной автоматически (default stretch).
   Именно поэтому .form-col-upload — просто flex-колонка на всю высоту
   своей грид-ячейки, а не ячейка с фиксированным height.
═══════════════════════════════════════════════════════════════ */
.form-halves {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--sp-sm);
  /* Отступ до .form-actions ниже. Раньше его давал стандартный margin-bottom
     поля из base.css, но у последнего поля левой колонки он специально
     занулён (ниже) — без замены здесь кнопка с чекбоксом прилипала прямо
     к нижнему краю формы. 16px (--sp-sm) владельцу показалось мало,
     увеличено до 28px (--sp-md) — того же отступа, что стоит между
     секциями страницы (.section). */
  margin-bottom: var(--sp-md);
}
.form-col-fields { display: flex; flex-direction: column; }
/* .form-group уже даёт margin-bottom всем полям (base.css) — у последнего
   поля колонки эта нижняя отбивка снаружи колонки не нужна, иначе левая
   колонка становится на 16px выше правой и «равная высота» перестаёт
   быть равной на 16px. */
.form-col-fields > .form-group:last-child { margin-bottom: 0; }
.form-col-upload { display: flex; flex-direction: column; margin-bottom: 0; }
/* Зона тянется на всё оставшееся место колонки; список файлов и подсказка
   под ней остаются обычной высоты — растягивается только сама .dropzone. */
.form-col-upload .dropzone { flex: 1 1 auto; }
/* Правка 09.08: подтягиваем видимый текст подсказки к уже правильно
   посчитанной нижней границе колонки (см. .form-halves выше — колонка
   растянута по сетке до высоты левой, .dropzone забирает остаток через
   flex:1, .dropzone-hint идёт последним и упирается в тот же нижний край,
   что и поле «Комментарий»). Зазор, который оставался, — не ошибка
   раскладки, а «полулидинг»: line-height кладёт пустое место и над
   строкой, и под ней, поэтому видимый низ буквы всегда чуть выше истинной
   границы блока. line-height потеснее подтягивает текст книзу, ниже
   границы он в принципе уйти не может — направление подгонки однозначное. */
.form-col-upload .dropzone-hint,
.form-col-upload .dropzone-status { line-height: 1.3; }

/* .is-fill — зона .dropzone произвольной высоты вместо стандартной
   padding:18px. Текст-приглашение центрируется и по горизонтали,
   и по вертикали — иначе на высокой зоне он повисает у верхнего края. */
.dropzone.is-fill {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 96px;
}

/* ═══════════════════════════════════════════════════════════════
   ИКОНКА ФАЙЛА В СПИСКЕ ВЛОЖЕНИЙ — .dropzone-file-icon
   Правка 09.08: в архивном образце (old/style-guide.html) перед именем
   файла стоял символ 📎 — при переносе логики в общий catalog-form.js он
   потерялся. Возвращаю не эмодзи, а плоской инлайн-SVG на currentColor:
   эмодзи — единственный цветной элемент был бы на всей форме, остальные
   мелкие иконки в проекте (.doc-item, .product-flag, переключатель вида)
   везде плоские и приглушённые, не цветные.
   Иконку вставляет catalog-form.js при рендере каждой строки списка.
═══════════════════════════════════════════════════════════════ */
.dropzone-file-icon {
  flex: 0 0 auto;
  display: inline-flex;
  margin-right: 8px;
  color: var(--c-text-light);
}
.dropzone-file-icon svg { display: block; }

/* Плашка вокруг строки с файлом — правка 09.08: раньше просто строка
   текста, сливалась с остальным содержимым формы. Тот же язык, что у
   .product-flag / .doc-kind / .brand-tile — фон, волосяная рамка,
   скругление, а не новый приём. base.css .form-card .form-group даёт
   margin-bottom только прямым потомкам .form-group, поэтому отступ между
   несколькими плашками задан здесь явно. */
.dropzone-file-item {
  background: #fff;
  /* Светло-зелёная рамка — правка 09.08: файл в списке = уже успешно
     прикреплён, тот же зелёный, что у статуса «в наличии» (--c-success),
     только в светлом варианте — рамке не нужна полная насыщенность. */
  border: 1px solid var(--c-success-light);
  border-radius: var(--radius-sm);
  padding: 7px 10px;
  margin-bottom: 6px;
}
.dropzone-file-item:last-child { margin-bottom: 0; }

/* Строка «Прикреплено X из доступных Y» — размер зелёным (тот же смысл
   «успешно», что у рамки плашки выше), «доступно» — просто жирным, без
   цвета: это не статус, а нейтральный факт-предел. */
.dropzone-status-size { color: var(--c-success); }
.dropzone-status-max { font-weight: var(--fw-bold); }

/* ═══════════════════════════════════════════════════════════════
   DRAG-AND-DROP ЗОНА ВЛОЖЕНИЙ — .dropzone

   Правка 09.08 по запросу владельца: «богаче» поле файла на форме
   подбора 6-card.html, по образцу из архивного old/style-guide.html —
   drag-and-drop, список выбранных файлов с удалением по одному,
   проверка общего объёма.

   Сама зона (.dropzone, .dropzone-text, .dropzone-files,
   .dropzone-file-item, .dropzone-file-remove, .dropzone-hint) уже
   определена в components.css (не редактируется) и уже целиком на
   токенах темы — --c-accent, --c-border, --radius и т. д. — поэтому
   заново её тут не описываю, только то, чего там не было: состояние
   превышенного лимита и мелкая уборка пустого списка.

   Логику (drag&drop, добавление/удаление файла, пересчёт объёма) даёт
   catalog-form.js — общий контроллер на любой .dropzone внутри
   .catalog-request-form, а не привязанный к ID одной страницы, как
   было в архивном варианте.

   .upload-field выше НЕ удалён и не устарел формально: `2-`…`5-catalog-…`
   его пока используют, апгрейд до .dropzone — отдельный шаг для каждой
   формы, не выполнен заодно с этой правкой.
═══════════════════════════════════════════════════════════════ */
/* Пустой список не должен резервировать место — иначе под зоной остаётся
   восьмипиксельная пустота, пока ни один файл не выбран. */
.dropzone-files:empty { margin-top: 0; }
/* Тот же красный, что у остальных ошибок формы (.field-err, обязательная
   отметка) — не вводим второй оттенок для одного и того же смысла. */
.dropzone-status.is-error { color: #d32f2f; font-weight: var(--fw-bold); }

/* ═══════════════════════════════════════════════════════════════
   СВЯЗАННЫЕ МАТЕРИАЛЫ — ТЗ §5.2 п.12
═══════════════════════════════════════════════════════════════ */
.articles-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-md);
}
.article-card {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 20px;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  text-decoration: none;
  transition: border-color var(--transition);
}
.article-card:hover { border-color: var(--c-accent); }
.article-card:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.article-card .kicker { font-size: 11px; color: var(--c-accent-hover); }
.article-card b {
  font-family: var(--font-display);
  font-size: var(--fz-base);
  font-weight: 600;
  line-height: 1.3;
  color: var(--c-text);
}
.article-card span { font-size: var(--fz-sm); color: var(--c-text-muted); }

/* ═══════════════════════════════════════════════════════════════
   ПУСТЫЕ СОСТОЯНИЯ — ТЗ §7.3
   «Блок без данных скрывается или получает полезную альтернативу.
   Не оставлять пустые контейнеры.»
═══════════════════════════════════════════════════════════════ */
.empty-state {
  padding: 44px 28px;
  text-align: center;
  background: #fff;
  border: 1px dashed var(--c-border);
  border-radius: var(--radius-lg);
}
.empty-state b {
  display: block;
  margin-bottom: 8px;
  font-family: var(--font-display);
  font-size: var(--fz-lg);
  font-weight: 600;
  color: var(--c-text);
}
.empty-state p {
  margin: 0 auto var(--sp-sm);
  max-width: 62ch;
  font-size: var(--fz-sm);
  color: var(--c-text-muted);
}

/* ═══════════════════════════════════════════════════════════════
   ПЕРВЫЙ ЭКРАН ГЛАВНОЙ — .main-hero (шаблон 12)

   Отличается от .cat-hero тем, что это витрина, а не служебная шапка
   раздела: крупное изображение, декоративная подложка и появление с
   анимацией. Собственный компонент, а не модификатор .cat-hero, потому
   что там другая задача и другой вертикальный ритм.

   ПОЧЕМУ СВЕТЛЫЙ, А НЕ ТЁМНЫЙ БАННЕР. Тёмная подложка смотрелась бы
   эффектнее, но все изображения оборудования в проекте — с ЗАПЕЧЁННЫМ
   белым фоном, без альфа-канала (проверено декодированием: и рабочий
   hero-файл, и пять «исправленных» в review-assets). Единственный способ
   убрать этот фон без перерисовки — mix-blend-mode: multiply, а он
   работает только на светлом: на тёмном оборудование превратится в
   чёрное пятно. Тёмный вариант станет возможен, когда появятся честные
   PNG с alpha=0.

   Декоративная сетка — чертёжная точечная сетка на CSS-градиентах,
   растворяется к краям через mask-image. Без лишних тегов и без внешних
   файлов; цвет — тот же волосяной --c-border, никакой заливки акцентом.
═══════════════════════════════════════════════════════════════ */
.main-hero { padding: 64px var(--container-px) 56px; }
.main-hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(360px, 46%, 720px);
  gap: var(--sp-lg);
  align-items: center;
}
.main-hero h1 {
  font-family: var(--font-display);
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: -0.02em;
  color: var(--c-text);
  max-width: 18ch;
  margin: 0 0 18px;
}
.main-hero-lead {
  font-size: var(--fz-lg);
  line-height: 1.5;
  color: var(--c-text-muted);
  max-width: 62ch;
  margin: 0 0 var(--sp-md);
}
.main-hero-media { position: relative; }
.main-hero-media::before {
  content: "";
  position: absolute;
  inset: -8% -6% 4% -12%;
  background-image: radial-gradient(var(--c-border) 1.5px, transparent 1.6px);
  background-size: 18px 18px;
  -webkit-mask-image: radial-gradient(ellipse at 52% 50%, #000 52%, transparent 78%);
  mask-image: radial-gradient(ellipse at 52% 50%, #000 52%, transparent 78%);
  z-index: 0;
}
.main-hero-media img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: auto;
  display: block;
  mix-blend-mode: multiply;
}

/* Появление: заголовок → лид → фактоиды → кнопки → картинка.
   backwards, чтобы до старта элемент уже был в начальном состоянии и не
   моргал. Задержки нарастают, но держатся в пределах полусекунды —
   первый экран не должен заставлять себя ждать. */
@keyframes mainHeroRise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}
.main-hero-anim > * {
  animation: mainHeroRise 0.7s cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
}
.main-hero-anim > *:nth-child(1) { animation-delay: 0.04s; }
.main-hero-anim > *:nth-child(2) { animation-delay: 0.12s; }
.main-hero-anim > *:nth-child(3) { animation-delay: 0.20s; }
.main-hero-anim > *:nth-child(4) { animation-delay: 0.28s; }
.main-hero .main-hero-media { animation: mainHeroRise 0.8s cubic-bezier(0.22, 0.61, 0.36, 1) 0.16s backwards; }

@media (prefers-reduced-motion: reduce) {
  .main-hero-anim > *,
  .main-hero .main-hero-media { animation: none; }
}
@media (max-width: 1024px) {
  .main-hero-grid { grid-template-columns: 1fr; gap: var(--sp-md); }
  .main-hero h1 { max-width: none; }
  .main-hero-media { max-width: 560px; }
}
@media (max-width: 700px) {
  .main-hero { padding: 40px var(--container-px) 36px; }
}

/* ═══════════════════════════════════════════════════════════════
   ПРОЦЕСС РАБОТЫ — .process-flow (шаблон 12, главная)

   Семь шагов: запрос → подбор → ТКП → логистика → ПНР → обучение →
   поддержка. Существующий .howto-grid не подошёл: он рассчитан на 3–4
   развёрнутых пункта с абзацем текста, семь коротких шагов в нём
   разваливаются и теряют порядок следования.

   auto-fit + minmax вместо фиксированных семи колонок: на широком экране
   выстраивается в одну линию, дальше сам переносится по 4/3/2, не требуя
   отдельного медиазапроса на каждую ступень. Соединительная линия —
   ::before у шага, а не общая полоса под блоком: при переносе строк общая
   полоса повисла бы в воздухе, а так линия всегда идёт между соседями.
═══════════════════════════════════════════════════════════════ */
.process-flow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--sp-md) 0;
}
.process-step {
  position: relative;
  padding: 0 var(--sp-sm);
}
/* Линия к предыдущему шагу. Не рисуется у первого в блоке. */
.process-step::before {
  content: "";
  position: absolute;
  top: 13px;
  right: 50%;
  width: 100%;
  height: 1px;
  background: var(--c-border);
}
.process-step:first-child::before { display: none; }
.process-step b {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin: 0 auto 12px;
  border-radius: 50%;
  background: #fff;
  border: 1px solid var(--c-border);
  font-family: var(--font-display);
  font-size: var(--fz-sm);
  font-weight: 600;
  color: var(--c-text-muted);
  font-variant-numeric: tabular-nums;
}
.process-step span {
  display: block;
  text-align: center;
  font-family: var(--font-display);
  font-size: var(--fz-sm);
  font-weight: 600;
  color: var(--c-text);
  line-height: 1.35;
}
.process-step small {
  display: block;
  margin-top: 6px;
  text-align: center;
  font-size: var(--fz-xs);
  color: var(--c-text-muted);
  line-height: 1.5;
}

/* ═══════════════════════════════════════════════════════════════
   ГЕОГРАФИЯ ДОСТАВКИ — .geo-map (шаблон 12, главная)

   ВАЖНО ПРО СОДЕРЖАНИЕ, А НЕ ПРО СТИЛЬ. Точки на карте НЕ являются
   подтверждённым списком городов: такого списка в проекте нет. Они без
   подписей и иллюстрируют географию ДОСТАВКИ («доставка по РФ и СНГ» —
   уже утверждённая формулировка из шаблона 2), а не присутствие компании
   и не филиалы. Подписывать точки городами или называть их офисами без
   подтверждения владельца нельзя.

   Сам контур РФ нарисован схематично: готового векторного ассета в
   проекте нет. Для боевой страницы его стоит заменить нормальной картой.
═══════════════════════════════════════════════════════════════ */
.geo-map {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 360px);
  gap: var(--sp-lg);
  align-items: center;
}
.geo-map-canvas {
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  background: #fff;
  padding: var(--sp-md);
}
.geo-map-canvas svg { width: 100%; height: auto; display: block; }
.geo-map-shape { fill: var(--c-bg-light); stroke: var(--c-border); stroke-width: 1.5; }
.geo-map-dot { fill: var(--c-accent); }
.geo-map-legend {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: var(--sp-sm);
  font-size: var(--fz-xs);
  color: var(--c-text-muted);
}
.geo-map-legend::before {
  content: "";
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--c-accent);
  flex: 0 0 auto;
}
@media (max-width: 900px) {
  .geo-map { grid-template-columns: 1fr; }
}

/* ═══════════════════════════════════════════════════════════════
   КОНТАКТНЫЕ ПОЛЯ — .contact-details (шаблон 10)

   Реальных телефона, email, адреса и юрлица в проекте пока нет —
   владелец их ещё не передал (см. NON-CATALOG-PAGES-LAUNCH.md). Каждое
   поле честно показывает .contact-pending вместо придуманного значения,
   тем же принципом, что .empty-state — не пустой контейнер, но и не
   выдуманный факт.
═══════════════════════════════════════════════════════════════ */
.contact-details {
  display: flex;
  flex-direction: column;
  gap: 0;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
}
.contact-row {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: var(--sp-sm);
  padding: 18px 24px;
  border-bottom: 1px solid var(--c-border);
}
.contact-row:last-child { border-bottom: none; }
.contact-row b {
  font-family: var(--font-display);
  font-size: var(--fz-sm);
  font-weight: 600;
  color: var(--c-text);
}
.contact-pending {
  font-size: var(--fz-sm);
  color: var(--c-text-light);
}
@media (max-width: 600px) {
  .contact-row { grid-template-columns: 1fr; gap: 4px; }
}

/* Карта второй колонкой (шаблон 10). Сама карта не встроена — реального
   адреса в проекте нет, а координаты придумывать нельзя (NON-CATALOG-
   PAGES-LAUNCH.md, п.3: «карта только при наличии реального публичного
   адреса»). Правая колонка держит .empty-state вместо карты до тех пор,
   пока владелец не подтвердит адрес. */
.contact-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 360px);
  gap: var(--sp-lg);
  align-items: stretch;
}
.contact-layout .empty-state {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
@media (max-width: 900px) {
  .contact-layout { grid-template-columns: 1fr; }
}

/* ═══════════════════════════════════════════════════════════════
   АДАПТИВ — проверять через www/templates/mobile-check.html
   (iframe со своим вьюпортом; --window-size=375 под Windows не работает,
   окно не сужается меньше ~500px — см. комментарий в theme-atlas.css)
═══════════════════════════════════════════════════════════════ */
@media (max-width: 1280px) {
  .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .service-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .branch-grid  { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .subcat-grid  { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .brand-cards  { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .adv-grid     { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 1100px) {
  .op-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ── Ключевая точка: товарная зона перестраивается, фильтры уходят
   в выдвижную панель (ТЗ §9). ── */
@media (max-width: 1024px) {
  .catalog-layout { grid-template-columns: 1fr; }
  .howto-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .case-grid { grid-template-columns: 1fr; }
  .cat-hero-grid { grid-template-columns: 1fr; gap: var(--sp-md); }
  .cat-hero-media { max-width: 420px; }
  /* Модификаторы варианта Б специфичнее (0,2,0), чем правила выше (0,1,0),
     и без явного переопределения оставили бы две колонки на телефоне. */
  .cat-hero-grid.is-media-plain { grid-template-columns: 1fr; }
  .cat-hero-media.is-plain { max-width: 560px; }
  /* В одну колонку картинка уходит ПОД текст — выравнивать по H1 больше
     нечего, а отрицательный отступ наехал бы на строку условий поставки. */
  .cat-hero-media.is-plain img { margin-top: 0; }
  .articles-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .filters-toggle { display: inline-flex; }
  .filters {
    position: fixed;
    z-index: 60;
    top: 0;
    bottom: 0;
    left: 0;
    width: min(360px, 88vw);
    max-width: 100%;
    border-radius: 0;
    border-top: none;
    border-bottom: none;
    border-left: none;
    overflow-y: auto;
    padding-bottom: 48px;
    transform: translateX(-100%);
    transition: transform 0.25s var(--ease-out);
  }
  .filters-toggle-input:checked ~ .catalog-layout .filters { transform: none; }

  .filters-close {
    display: grid;
    place-items: center;
    position: absolute;
    top: 12px;
    right: 12px;
    width: 32px;
    height: 32px;
    border: 1px solid var(--c-border);
    border-radius: 50%;
    background: #fff;
    color: var(--c-text-muted);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
  }
  .filters-close:hover { border-color: var(--c-accent); color: var(--c-accent); }

  .filters-toggle-input:checked ~ .catalog-layout .filters-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 59;
    background: oklch(22% 0.01 75 / 0.35);
    cursor: pointer;
  }
  /* Сам checkbox визуально скрыт — фокус показываем на кнопке. */
  .filters-toggle-input:focus-visible ~ .catalog-layout .filters-toggle {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
  }
}

@media (max-width: 900px) {
  .cat-hero { padding: 48px var(--container-px) 32px; }
  .cat-hero h1 { font-size: var(--fz-2xl); max-width: none; }
  .service-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .subcat-grid  { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .brand-cards  { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .adv-grid     { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .form-grid    { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .count-line   { gap: 12px 28px; }
  .count-line b { font-size: var(--fz-xl); }
}

@media (max-width: 700px) {
  .branch-grid { grid-template-columns: 1fr; }
  .op-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 600px) {
  .product-grid { grid-template-columns: 1fr; }
  .howto-grid, .service-grid { grid-template-columns: 1fr; }
  .subcat-grid, .adv-grid, .articles-grid { grid-template-columns: 1fr; }
  .brand-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .form-grid { grid-template-columns: 1fr; }
  .catalog-toolbar .spacer { display: none; }
  .catalog-toolbar { gap: 10px; }
  /* 22px, а не 18: базовый размер заголовка секции вырос до 30px вместе со
     шкалой, а 18px — это размер заголовка карточки (h3). Оставь мы прежнее
     значение, на узком экране секция и карточка сравнялись бы и иерархия
     пропала. Это исключение из шкалы — оно тут осознанное. */
  .sec-head { font-size: var(--fz-xl); }
  /* Кнопки первого экрана — в столбик и на всю ширину: у .btn в base.css
     white-space: nowrap, в две колонки длинная подпись упиралась в край. */
  .cat-hero-actions { flex-direction: column; align-items: stretch; }
  .cat-hero-actions .btn { width: 100%; }
  /* Панель сравнения: на узком экране слоты уходят на свою строку,
     иначе кнопка «Сравнить» выдавливается за край. */
  .compare-bar { gap: 12px; }
  .compare-slots { order: 3; width: 100%; flex: 0 0 auto; }
  .compare-bar .btn { width: 100%; }
  .pagination a, .pagination span { min-width: 38px; height: 38px; padding: 0 10px; }
}

/* ── Технический индекс производителей ─────────────────────────
   Направление → категория → логотип → число реально опубликованных
   позиций. Использует утверждённую плитку .brand-tile; дополнительные
   правила отвечают только за служебные подписи и группировку. */
.brand-direction-section .section-head a,
.brand-category-section h3 a { color: inherit; text-decoration: none; }
.brand-category-section { margin-top: var(--sp-lg); }
.brand-category-section h3 { margin-bottom: var(--sp-sm); }
.brand-index-tile {
  position: relative;
  flex-direction: column;
  gap: 8px;
  min-height: 104px;
  text-align: center;
}
.brand-index-tile strong { font-size: var(--fz-sm); font-weight: var(--fw-normal); color: var(--c-text); }
.brand-index-tile small { font-size: var(--fz-xs); color: var(--c-text-muted); }
.brand-index-tile:has(img) strong { display: block; }
.brand-category-links { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: var(--sp-md); }
.brand-position-summary { margin-top: var(--sp-md); }

/* Выезд панели фильтров — единственная анимация положения в файле.
   При отключённой анимации панель просто появляется на месте. */
@media (prefers-reduced-motion: reduce) {
  .filters { transition: none; }
}

/* ███████████████████████████████████████████████████████████████
   ЧАСТЬ 3 — КОМПОНЕНТЫ УРОВНЯ НАПРАВЛЕНИЯ
   ТЗ TZ-CLAUDE-CATALOG-STANKI-DIRECTION-SERP-2026-08-07.md,
   шаблон 2-catalog-napravlenie.html (роль catalog-direction,
   пример: «Станки» → /catalog/stanki/).

   Уровень выше хаба, поэтому здесь НЕТ ничего нового про товары и
   фильтры — карточки семейств собраны на уже существующем .nav-card
   и сетке .branch-grid из ЧАСТИ 2 (ТЗ §12: переиспользовать компоненты,
   а не делать четыре независимых шаблона). Добавлены только те четыре
   вещи, которых на уровне хаба не было:
     .nav-card-role   — роль элемента таксономии на карточке (ТЗ §3);
     .is-soon         — пункт списка без ссылки, раздел ещё не опубликован;
     .catalog-search  — место под поиск по каталогу (ТЗ §9);
     .trust-grid      — доказательства в блоке «Почему мы» (ТЗ §5 блок 11).
███████████████████████████████████████████████████████████████ */

/* ═══════════════════════════════════════════════════════════════
   РОЛЬ В ТАКСОНОМИИ НА КАРТОЧКЕ — ТЗ §3 блок 3
   «Карточки должны работать с ролями таксономии: hub / branch /
   leaf / cross_link». Главный практический случай — cross_link:
   альтернативный вход в уже существующий канонический раздел.
   Без пометки такая карточка читается как ещё одно семейство, и
   пользователь ждёт за ней новый ассортимент, а не тот же самый
   под другим срезом.

   Заглавных букв нет — правило владельца по всему проекту.
═══════════════════════════════════════════════════════════════ */
.nav-card-role {
  align-self: flex-start;
  margin-bottom: 8px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--c-bg);
  font-size: 11px;
  color: var(--c-text-muted);
}
/* Сквозной вход выделен акцентом: это единственная карточка в сетке,
   которая не добавляет новых URL, и её стоит отличать взглядом. */
.nav-card-role.is-cross {
  background: var(--c-accent-light);
  color: var(--c-accent-hover);
}

/* ═══════════════════════════════════════════════════════════════
   ПУНКТ «РАЗДЕЛ ГОТОВИТСЯ» — ТЗ §7 и §16 п.11
   Две группы направления («Строгальные и долбёжные станки»,
   «Станки для гибки и резки арматуры») готовы по контенту в
   src/content/stanki.json, но на прод не выведены — нет изображений
   (правило минимального релиза, STATE.md §1). Ссылку на них ставить
   нельзя: страницы ещё нет, получим 404 в основной навигации.
   Поэтому пункт выводится текстом с подписью, а не ссылкой.
═══════════════════════════════════════════════════════════════ */
.nav-card-links li.is-soon {
  font-size: var(--fz-sm);
  color: var(--c-text-light);
}
.nav-card-links li.is-soon::after {
  content: " — раздел готовится";
  font-size: var(--fz-xs);
}

/* ═══════════════════════════════════════════════════════════════
   ПОИСК ПО КАТАЛОГУ — ТЗ §9
   «Предусмотреть место под поиск. Если поиск пока не реализован,
   его отсутствие не блокирует запуск шаблона.» В проекте поиска нет:
   здесь зарезервировано место и форма-заготовка, обработчик — за Codex.
═══════════════════════════════════════════════════════════════ */
.catalog-search {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  max-width: 720px;
}
.catalog-search input[type="search"] {
  flex: 1 1 280px;
  min-width: 0;
  padding: 12px 18px;
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
  background: #fff;
  font: inherit;
  font-size: var(--fz-base);
  color: var(--c-text);
}
.catalog-search input[type="search"]::placeholder { color: var(--c-text-light); }
.catalog-search input[type="search"]:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 1px; }

/* ═══════════════════════════════════════════════════════════════
   ДОКАЗАТЕЛЬСТВА — ТЗ §5 блок 11 «Почему РосПромОборудование»
   ТЗ прямо запрещает «№1» и «лучшие цены» и требует опоры на
   проверяемые величины: число брендов, число моделей, подбор,
   сервис, ПНР, география, проекты. Отсюда форма «число + что это
   значит»: если величины нет — выводится пункт без числа, а не
   пустая плашка и не выдуманная цифра.
═══════════════════════════════════════════════════════════════ */
.trust-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-md) var(--sp-lg);
}
.trust-item { padding-top: 14px; border-top: 1px solid var(--c-border); }
.trust-item b {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fz-2xl);
  font-weight: 600;
  line-height: 1.1;
  color: var(--c-text);
  font-variant-numeric: tabular-nums;
}
.trust-item span {
  display: block;
  margin-top: 6px;
  font-family: var(--font-display);
  font-size: var(--fz-base);
  font-weight: 600;
  color: var(--c-text);
}
.trust-item small {
  display: block;
  margin-top: 5px;
  font-size: var(--fz-sm);
  color: var(--c-text-muted);
}
/* Пункт без числа: верхняя линия акцентная, чтобы строка не читалась
   как «здесь не загрузилось значение». Отступ под числом снимается —
   иначе подпись висит ниже соседей по строке и ряд идёт волной. */
.trust-item.no-number { border-top-color: var(--c-accent); }
.trust-item.no-number span { margin-top: 0; }

/* Кнопки под секцией (CTA блоков «Как подобрать» и «Быстрый выбор») */
.sec-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: var(--sp-md); }

/* ── Адаптив ЧАСТИ 3 (продолжение файла — ЧАСТЬ 4 ниже) ── */
@media (max-width: 1280px) {
  .trust-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .trust-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 600px) {
  .trust-grid { grid-template-columns: 1fr; gap: var(--sp-sm); }
  .catalog-search .btn { width: 100%; }
  .sec-actions { flex-direction: column; align-items: stretch; }
  .sec-actions .btn { width: 100%; }
}

/* ███████████████████████████████████████████████████████████████
   ЧАСТЬ 4 — СТРАНИЦА ТОВАРА (шаблон 6-card.html)

   Шестой уровень схемы владельца. URL плоский, вне дерева категорий:
   /catalog/stanki/<категория>/<бренд>-<модель>/ — рабочий flat URL
   в NEW-TZ-…-SERP-SEO-v2 §7.2. Хлебные крошки при этом показывают полный
   путь по категориям: адрес и навигация здесь намеренно расходятся.

   ОТДЕЛЬНОГО ТЗ НА ЭТУ СТРАНИЦУ НЕТ. Из ТЗ взяты состав обязательных полей
   (§7 «Карточка SKU») и приоритет ключевых характеристик; структура
   страницы выведена из данных парсинга и из уровней 1–5.

   КОМПОНЕНТЫ СПРОЕКТИРОВАНЫ ПОД РЕАЛЬНЫЕ ДАННЫЕ, а не под идеальные.
   Что показал разбор data/incoming/catalog-demo/2026-08-07 (463 карточки):
     — цена есть у 43%, бренд у 48%, характеристики у 31%;
     — описания обрезаны парсером на 600 символах, 83% обрываются на
       полуслове — блок описания не может быть главным носителем смысла;
     — у 55% карточек с характеристиками имена параметров ПОВТОРЯЮТСЯ
       внутри одной карточки, поэтому таблица не опирается на уникальность;
     — «галерея» у части карточек содержит превью ДРУГИХ станков, поэтому
       дополнительные ракурсы выводим только проверенные;
     — до 25 строк характеристик, поэтому таблица сворачиваемая.
███████████████████████████████████████████████████████████████ */

/* Утилита: элемент нужен скринридеру, но не должен занимать место.
   Не display:none и не visibility:hidden — оба убирают элемент из
   доступного дерева, а нужен ровно обратный эффект. Применяется к
   <caption> таблицы характеристик: заголовок у таблицы быть обязан,
   а визуально его роль уже играет заголовок секции. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ═══════════════════════════════════════════════════════════════
   ШИРИНА СТРАНИЦЫ — ОБЩАЯ, 1600px. НЕ СУЖАТЬ.

   История, чтобы не повторить. Первая версия карточки растягивала
   галерею на всю свободную ширину (сетка «1fr | 440px»), фотография
   получалась около 1100×800 и занимала весь первый экран, а мелкие
   снимки поставщиков при таком увеличении мылились. Второй попыткой я
   сузил ВСЮ страницу до 1180px — это была неправильная ручка: ширина
   макета едина для сайта, и карточка не может выбиваться из остальных
   страниц. Правильное место правки — колонка галереи, а не контейнер.

   Поэтому здесь страница живёт в общих 1600px, а размер фотографии
   ограничен фиксированной колонкой в сетке ниже.
═══════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════
   ПЕРВЫЙ ЭКРАН ТОВАРА — ТРИ ЗОНЫ
     560px  галерея              — фиксированная, фото 560×420
     1fr    название и параметры — примерно 508px
     380px  покупка              — липкая, следует за прокруткой

   Три зоны, а не две, именно из-за ширины 1600: при двух колонках одна
   из них неизбежно раздувается до 1000px и всё выглядит несоразмерно.
   Правая колонка задаёт вертикаль, к которой ниже по странице
   выравниваются документы и видео (.pdp-cols) — так у страницы
   появляется общая сетка вместо набора блоков разной ширины.
═══════════════════════════════════════════════════════════════ */
/* Зоны названы явно (grid-template-areas), а не заданы порядком в разметке.
   Без этого при сужении окна третья колонка молча уезжала в первую ячейку
   следующего ряда и блок покупки оказывался под галереей внахлёст с
   характеристиками. Именованные зоны делают сворачивание предсказуемым:
   на каждом пороге прямо написано, что где стоит. */
.pdp-grid {
  display: grid;
  grid-template-columns: 560px minmax(0, 1fr) 380px;
  grid-template-areas: "gal info buy";
  gap: var(--sp-lg);
  align-items: start;
}
.pdp-gallery { grid-area: gal; }
.pdp-info    { grid-area: info; min-width: 0; }
.pdp-aside   { grid-area: buy; }

/* Липкая колонка покупки: на длинной странице цена и кнопка остаются
   на виду, пока читаешь характеристики. */
.pdp-aside { position: sticky; top: calc(var(--header-h) + 12px); }

/* Блоки ниже первого экрана выравниваются по той же вертикали:
   слева содержимое, справа — колонка 380px под документы и видео. */
.pdp-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: var(--sp-lg);
  align-items: start;
}
.pdp-cols > * { min-width: 0; }
.pdp-side-block + .pdp-side-block { margin-top: var(--sp-md); }
.pdp-side-block h2 { font-size: var(--fz-lg); margin: 0 0 12px; }

/* Галерея НЕ липкая: липкой сделана колонка покупки справа. Две липкие
   колонки в одной сетке начинают жить каждая своей жизнью и визуально
   разъезжаются относительно текста между ними. */
/* 4:3 на 560px даёт 420px по высоте — фотография видна целиком и не
   выдавливает цену и характеристики за пределы первого экрана. */
.pdp-main {
  aspect-ratio: 4 / 3;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  display: grid;
  place-items: center;
  overflow: hidden;
}
/* ГЛАВНОЕ ФОТО — правка 08.08, третье замечание владельца «фото в плашке
   не влезает».

   Причина была в том, что я ФОРСИРОВАЛ размер: стояло width:100% и
   height:100%, то есть картинка обязана была занять всю плашку, а
   object-fit лишь подгонял пропорции. Для снимков поставщиков это плохо
   вдвойне: они мелкие и разного формата. Мелкий кадр растягивался до
   560px и мылился (отсюда прежняя жалоба на «обрезанную» фотографию), а
   вертикальный упирался в высоту и оставлял пустые поля по бокам.

   Стало: размер не навязывается. Картинка показывается в натуральную
   величину, если помещается, и уменьшается, если больше плашки. Мелкое
   фото остаётся резким, крупное занимает плашку целиком.
   Внутренний отступ уменьшен с 18 до 10px — фотография и так почти
   всегда со своими белыми полями. */
.pdp-main img {
  width: auto;
  height: auto;
  max-width: calc(100% - 32px);
  max-height: calc(100% - 32px);
  object-fit: contain;
  object-position: center center;
  margin: auto;
  padding: 0;
  box-sizing: border-box;
}
.pdp-main:not(:has(img))::after {
  content: "фотография готовится";
  font-size: var(--fz-sm);
  color: var(--c-text-light);
}
/* Четыре миниатюры, а не пять: у большинства карточек проверенных
   ракурсов один-два, и ряд из пяти пустых слотов выглядел как ошибка. */
.pdp-thumbs {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin-top: 12px;
}
.pdp-thumb {
  aspect-ratio: 1 / 1;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-sm);
  display: grid;
  place-items: center;
  overflow: hidden;
}
.pdp-thumb img { max-width: 100%; max-height: 100%; object-fit: contain; padding: 6px; box-sizing: border-box; }
.pdp-thumb:hover { border-color: var(--c-accent); }
.pdp-thumb:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

/* ═══════════════════════════════════════════════════════════════
   ПОП-АП ПРОСМОТР ФОТОГРАФИИ — без JS.

   Сделано на checkbox, как фильтры и переключатели подборок в этом же
   файле: скрипты в проекте не обязательны, а готовый элемент формы даёт
   клавиатуру и фокус из коробки. Схема на одну фотографию:
     input.zoom-toggle  → скрытый переключатель
     label.pdp-zoom     → сама фотография, открывает
     label.lightbox     → затемнение с крупным кадром, закрывает по клику
   Для нескольких кадров генератор повторяет тройку с новым id.

   ЧЕГО ЭТО РЕШЕНИЕ НЕ УМЕЕТ и что честно принять:
     — закрытия по Esc нет, для этого нужен скрипт;
     — прокрутка страницы под затемнением не блокируется;
     — листания стрелками между кадрами нет.
   Всё три лечатся скриптом на стороне Codex; разметку менять не придётся.
═══════════════════════════════════════════════════════════════ */
/* Переключатель скрыт визуально, но НЕ display:none — иначе теряет фокус. */
.zoom-toggle {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}
/* Обёртка занимает плашку целиком, чтобы кликабельной была вся площадь,
   а не только пиксели фотографии. Через display: contents не делаем:
   у интерактивных элементов он местами ломает доступность. */
.pdp-zoom {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  cursor: zoom-in;
}
/* Подсказка появляется при наведении на плашку: без неё непонятно, что
   фотография кликабельна. */
.pdp-main { position: relative; }
.pdp-zoom-hint {
  position: absolute;
  right: 12px;
  bottom: 12px;
  padding: 5px 12px;
  border: 1px solid var(--c-border);
  border-radius: 999px;
  background: #fff;
  font-size: var(--fz-xs);
  color: var(--c-text-muted);
  opacity: 0;
  transition: opacity var(--transition);
  pointer-events: none;
}
.pdp-main:hover .pdp-zoom-hint { opacity: 1; }
.zoom-toggle:focus-visible ~ .pdp-main { outline: 2px solid var(--c-accent); outline-offset: 3px; }
.zoom-toggle:focus-visible ~ .pdp-main .pdp-zoom-hint { opacity: 1; }

.lightbox {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 90;
  padding: 48px;
  background: oklch(22% 0.01 75 / 0.82);
  cursor: zoom-out;
  place-items: center;
}
.zoom-toggle:checked ~ .lightbox { display: grid; }
/* Кадр не растягивается сверх натуральной величины по той же причине,
   что и в плашке: апскейл мелкого снимка только портит его. */
.lightbox img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  background: #fff;
  border-radius: var(--radius);
}
.lightbox-close {
  position: absolute;
  top: 20px;
  right: 24px;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: #fff;
  color: var(--c-text);
  font-size: 22px;
  line-height: 1;
}
@media (prefers-reduced-motion: reduce) {
  .pdp-zoom-hint { transition: none; }
}
/* Слот под неподтверждённый ракурс: пунктир, а не пустой квадрат —
   пустой читается как не загрузившаяся картинка. */
.pdp-thumb.is-empty { border-style: dashed; background: var(--c-bg); }
.pdp-thumb.is-empty::after { content: "+"; color: var(--c-text-light); font-size: var(--fz-lg); }

.pdp-line {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin-bottom: 10px;
  font-size: var(--fz-sm);
  color: var(--c-text-light);
}
.pdp-line .sep { color: var(--c-border); }
.pdp-line .pdp-sku { margin-left: auto; font-variant-numeric: tabular-nums; }
.pdp-brand-link { color: var(--c-teal); }

/* Первый экран товара начинается выше, чем у категории: там заголовок
   держит воздух сверху, здесь сразу под шапкой должна быть фотография. */
.pdp .cat-hero { padding-top: 40px; }

/* ОСОЗНАННОЕ ИСКЛЮЧЕНИЕ ИЗ ШКАЛЫ ЗАГОЛОВКОВ.
   H1 карточки — 30px вместо 46px по шкале h1. Причины две: рядом стоит
   фотография высотой 360px, и заголовок уровня категории её перевешивал;
   название товара длиннее названия раздела и на 46px занимало три строки,
   отодвигая цену за пределы первого экрана. Правило шкалы допускает такое
   исключение, но требует, чтобы оно было названо — вот оно.
   Второе такое исключение в проекте: см. .sec-head на ≤600px. */
.pdp .cat-hero h1 { font-size: var(--fz-2xl); max-width: none; }
.pdp-h1 { margin: 0 0 10px; }
/* Модель — самостоятельное поле: в B2B по ней ищут и сравнивают. */
.pdp-model {
  display: inline-block;
  margin-bottom: 14px;
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--c-bg);
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--c-text);
  font-variant-numeric: tabular-nums;
}

.pdp-buy {
  margin: var(--sp-sm) 0;
  padding: 24px;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
}
.pdp-price {
  font-family: var(--font-display);
  font-size: var(--fz-2xl);
  font-weight: 600;
  line-height: 1.1;
  color: var(--c-text);
  font-variant-numeric: tabular-nums;
}
/* «Цена по запросу» — не сумма, поэтому спокойнее по весу (57% карточек) */
.pdp-price.is-request { font-size: var(--fz-xl); font-weight: var(--fw-normal); color: #2e5978; }
.pdp-price-note { margin: 6px 0 0; font-size: var(--fz-sm); color: var(--c-text-muted); }
.pdp-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: var(--sp-sm); }
.pdp-actions .btn { flex: 1 1 160px; }

/* Ключевые характеристики первого экрана — 5–7 строк по приоритету из
   ТЗ §7, а не первые попавшиеся из общей таблицы. */
.pdp-keyspecs {
  margin: var(--sp-sm) 0 0;
  font-size: var(--fz-sm);
  font-variant-numeric: tabular-nums;
  border-top: 1px solid var(--c-border);
}
.pdp-keyspecs div {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 9px 0;
  border-bottom: 1px solid var(--c-border-light);
}
.pdp-keyspecs dt { color: var(--c-text-light); }
.pdp-keyspecs dd { margin: 0; color: var(--c-text); text-align: right; }
.pdp-supply { margin-top: var(--sp-sm); }

/* ═══════════════════════════════════════════════════════════════
   ПОЛНАЯ ТАБЛИЦА ХАРАКТЕРИСТИК
   До 25 строк, поэтому хвост свёрнут в <details> — без JS.
   ВАЖНО: имена параметров в данных НЕ уникальны, поэтому таблица не
   группирует и не дедуплицирует строки — выводит как есть и помечает
   повтор, чтобы это не выглядело ошибкой вёрстки.
═══════════════════════════════════════════════════════════════ */
.spec-wrap {
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.spec-table { width: 100%; border-collapse: collapse; font-size: var(--fz-sm); font-variant-numeric: tabular-nums; }
.spec-table th,
.spec-table td { padding: 11px 16px; text-align: left; vertical-align: top; border-bottom: 1px solid var(--c-border-light); }
.spec-table th { width: 46%; font-weight: var(--fw-normal); color: var(--c-text-light); }
.spec-table td { color: var(--c-text); }
.spec-table tr:last-child th,
.spec-table tr:last-child td { border-bottom: none; }
.spec-table tr.is-dup th::after {
  content: "повтор";
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: 4px;
  background: var(--c-bg);
  font-size: 11px;
  color: var(--c-text-light);
}
.spec-more { padding: 0; border-top: 1px solid var(--c-border-light); }
.spec-more > summary {
  list-style: none;
  cursor: pointer;
  padding: 14px 16px;
  font-size: var(--fz-sm);
  color: var(--c-text);
  text-decoration: underline;
  text-decoration-thickness: 0.5px;
  text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
  text-underline-offset: 4px;
}
.spec-more > summary::-webkit-details-marker { display: none; }
.spec-more > summary:hover { color: var(--c-accent); text-decoration-color: currentColor; }
.spec-more > summary:focus-visible { outline: 2px solid var(--c-accent); outline-offset: -2px; }

/* ═══════════════════════════════════════════════════════════════
   ДОКУМЕНТЫ И ВИДЕО
   PDF есть у 19% карточек, видео у 3% — блоки необязательные и при
   отсутствии данных не выводятся вовсе.
═══════════════════════════════════════════════════════════════ */
.doc-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
.doc-item {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  background: #fff;
  border: 1px solid var(--c-border);
  border-radius: var(--radius);
  text-decoration: none;
  transition: border-color var(--transition);
}
.doc-item:hover { border-color: var(--c-accent); }
.doc-item:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
/* ДЕФЕКТ, ИСПРАВЛЕН 08.08 — почему селекторы здесь именные.
   Было правило `.doc-item span` (специфичность 0-1-1). Оно перехватывало
   и бейдж `.doc-kind` (0-1-0), потому что тот тоже <span>: бейдж получал
   display:block и уезжал на свою строку, наезжая на текст. Классический
   случай — селектор «тег внутри блока» ловит соседей по типу. Поэтому
   ниже каждый элемент адресуется своим классом, а селекторов по тегу
   внутри .doc-item нет вовсе. */
.doc-kind {
  flex: 0 0 auto;
  align-self: center;
  padding: 4px 9px;
  border: 1px solid var(--c-border);
  border-radius: 5px;
  font-size: 11px;
  line-height: 1.4;
  color: var(--c-text-light);
}
.doc-text { min-width: 0; }
.doc-title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fz-sm);
  font-weight: 600;
  line-height: 1.3;
  color: var(--c-text);
}
.doc-meta { display: block; margin-top: 3px; font-size: var(--fz-xs); color: var(--c-text-light); }

/* Соотношение задаёт контейнер — iframe тянется по нему, иначе на узком
   экране ролик вылезает за край. */
.video-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  max-width: 860px;
  border: 1px solid var(--c-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--c-bg);
}
.video-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* ═══════════════════════════════════════════════════════════════
   СЛУЖЕБНАЯ СНОСКА ОБ ИСТОЧНИКЕ
   Только для макета: откуда взята карточка и насколько уверенно она
   отнесена к подкатегории. На боевой странице блока быть не должно.
═══════════════════════════════════════════════════════════════ */
.src-note {
  margin-top: var(--sp-md);
  padding: 14px 18px;
  border: 1px dashed var(--c-border);
  border-radius: var(--radius);
  font-size: var(--fz-xs);
  color: var(--c-text-muted);
  line-height: 1.6;
}
.src-note b { color: var(--c-text); font-weight: var(--fw-bold); }
.src-note a { color: var(--c-teal); }

/* ═══════════════════════════════════════════════════════════════
   СОРАЗМЕРНОСТЬ БЛОКОВ НИЖЕ ПЕРВОГО ЭКРАНА
   Правка 08.08. Каждый блок раньше жил по своей ширине: текст тянулся
   во всю строку, таблица характеристик — тоже, видео разъезжалось,
   шесть сервисных карточек вставали 4 + 2 рваным рядом. Ниже — общая
   мера для всех блоков страницы товара.
═══════════════════════════════════════════════════════════════ */

/* Текст — по мере читаемости, а не по ширине колонки: строка в 200
   символов теряется при возврате глазом на следующую. */
.pdp .intro-text { max-width: 76ch; }
.pdp .sec-note { max-width: 76ch; }

/* Документы и видео живут в правой колонке 380px — своих ограничений
   ширины им не нужно, их задаёт сетка .pdp-cols. */
.pdp-side-block .doc-list { grid-template-columns: 1fr; }
.pdp-side-block .video-frame { max-width: none; }

/* Шесть сервисных карточек: 3 × 2 вместо рваного 4 + 2. */
.pdp .adv-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Пустое состояние оснастки — не во всю ширину, иначе заглушка выглядит
   как главный блок страницы. */
.pdp .empty-state { max-width: 900px; }

/* ── Пороги ЧАСТИ 4 ──
   1280px — это ещё десктоп (типовой ноутбук), поэтому порог поставлен
   на 1400: три зоны требуют 560 + 508 + 380 плюс два зазора, ниже
   примерно 1400 средняя колонка становится уже 380px и заголовок с
   характеристиками начинает ломаться. */
@media (max-width: 1400px) {
  /* Две колонки: галерея слева на два ряда, справа сверху название и
     параметры, под ними блок покупки. */
  .pdp-grid {
    grid-template-columns: 440px minmax(0, 1fr);
    grid-template-areas:
      "gal info"
      "gal buy";
    gap: var(--sp-md);
  }
  /* Липкость снимается: в двухрядной зоне она даёт рывок при прокрутке. */
  .pdp-aside { position: static; }
  .pdp-cols { grid-template-columns: minmax(0, 1fr); }
  .pdp-side-block .doc-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pdp-side-block .video-frame { max-width: 720px; }
  .pdp .adv-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* Ниже — мобильные ширины. По указанию владельца от 08.08 сейчас не
   прорабатываются: правила оставлены рабочими, но не вылизаны. */
@media (max-width: 1024px) {
  .pdp-grid {
    grid-template-columns: 1fr;
    grid-template-areas: "gal" "info" "buy";
  }
  .pdp-gallery { max-width: 560px; }
  .doc-list { grid-template-columns: 1fr; }
  /* Ниже этой ширины две колонки формы уже не помещаются рядом без
     сжатия полей — не полировка мобильной версии, а минимум, чтобы
     раскладка не ломалась (владелец просил не тратить время на мобильную
     версию отдельно, но не оставлять её битой). */
  .form-halves { grid-template-columns: 1fr; }
  .form-col-upload .dropzone { min-height: 140px; }
}
@media (max-width: 600px) {
  .pdp-thumbs { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .pdp-buy { padding: 18px; }
  .pdp-actions { flex-direction: column; }
  .pdp-actions .btn { width: 100%; }
  .pdp-line .pdp-sku { margin-left: 0; width: 100%; }
  .spec-table th, .spec-table td { padding: 9px 12px; }
  .spec-table th { width: 52%; }
}
