/* --- 00-tokens.css --- */
/* ============================================================================
   filtrov.net / tokens.css
   Единственный источник правды по цвету, типографике и ритму.
   Только custom properties, никаких селекторов компонентов.
   Подключается первым. Контрасты в комментариях посчитаны по WCAG 2.1.
   ============================================================================ */

/* --- 1. ПРИМИТИВЫ. Из макетов пришло 27 значений, здесь 25.
       Выброшены: #9AA3B2 (2.54:1 на белом), #5FBF7F (тёмный зелёный
       на светлом фоне, около 2.2:1).
       Разделён: #C07A00 на текстовый #9E6300 и декоративный #C07A00.
       Скорректирован: #1F8A4C (4.38:1) на #1A7A43 (5.37:1). ------------- */
:root {
  /* Графит с синим подтоном (требование: не чистый чёрный) */
  --c-graphite-900: #12161F;
  --c-graphite-800: #181D27;
  --c-graphite-700: #212836;
  --c-graphite-600: #2E3646;
  --c-graphite-500: #3F4959;

  /* Сталь */
  --c-steel-700: #414B5C;  /* 8.80:1 на #FFF */
  --c-steel-500: #5F6A7D;  /* 4.96:1 на #F2F4F8 */
  --c-steel-400: #6C778A;  /* 4.52:1 на #FFF, 3.73:1 на графите: НА ТЁМНОМ НЕЛЬЗЯ */
  --c-steel-300: #8C97A9;  /* 5.72:1 на графите */
  --c-steel-200: #AAB3C4;  /* 8.00:1 на графите */

  /* Светлые поверхности */
  --c-white:     #FFFFFF;
  --c-paper-50:  #F7F8FB;
  --c-paper-100: #F2F4F8;
  --c-paper-200: #E9ECF2;
  --c-line-100:  #EDEFF3;
  --c-line-200:  #E3E6EC;
  --c-line-300:  #D3D8E2;
  --c-line-400:  #C4CAD5;

  /* Акценты */
  --c-yellow-400: #FFC64D;  /* фон кнопки, актив, бейдж */
  --c-amber-600:  #C07A00;  /* 3.49:1 -> ТОЛЬКО не-текст: 1px линии, маркеры */
  --c-amber-700:  #9E6300;  /* 4.95:1 -> текст и ссылки на светлом */
  --c-blue-600:   #2F5BD0;  /* 5.95:1 на белом */
  --c-blue-400:   #4C7DF0;  /* 4.42:1 на графите */

  /* Статусы */
  --c-green-600: #1A7A43;  /* 5.37:1 на белом */
  --c-green-300: #7FD79C;  /* 9.73:1 на графите */
  --c-red-600:   #B42318;  /* 6.57:1 на белом */
  --c-red-300:   #FF9B90;  /* 8.32:1 на графите */
}

/* --- 2. СЕМАНТИКА, СВЕТЛАЯ ПОВЕРХНОСТЬ.
       Объявлена ПАРОЙ селекторов, чтобы светлый блок можно было вернуть
       внутри тёмной секции: поле главного поиска в тёмном герое макета
       Поиска именно такое (#F2F4F8 на #181D27). Односторонний скоуп
       такую вёрстку сделать не позволяет. ------------------------------- */
:root,
[data-surface="light"] {
  --page-bg:       var(--c-paper-50);
  --surface:       var(--c-white);
  --surface-2:     var(--c-paper-100);
  --surface-3:     var(--c-paper-200);
  --surface-hover: var(--c-paper-100);

  --ink:           var(--c-graphite-800);  /* 16.9:1 */
  --ink-2:         var(--c-steel-700);     /*  8.8:1 */
  --ink-3:         var(--c-steel-400);     /*  4.5:1 */
  --ink-on-accent: var(--c-graphite-800);  /* 10.8:1 на жёлтом */

  --line:        var(--c-line-200);
  --line-soft:   var(--c-line-100);
  --line-strong: var(--c-line-300);
  --line-dashed: var(--c-line-400);

  --accent:      var(--c-yellow-400);
  --accent-text: var(--c-amber-700);
  --accent-line: var(--c-amber-600);
  --accent-2:    var(--c-blue-600);

  --ok:     var(--c-green-600);
  --warn:   var(--c-amber-700);
  --danger: var(--c-red-600);

  --field-bg:         var(--c-white);
  --field-line:       var(--c-line-300);
  --field-ink:        var(--c-graphite-800);
  --field-ph:         var(--c-steel-400);
  --field-line-focus: var(--c-blue-600);

  --focus-ring: var(--c-blue-600);
  --focus-halo: var(--c-paper-50);
}

/* Вложенная плашка светлее фона страницы: #6C778A на ней даёт 4.11:1,
   то есть провал. Здесь ink-3 усиливается. */
.surface-2, [data-surface="light-2"] { --ink-3: var(--c-steel-500); }

/* --- 3. ТЁМНАЯ ПОВЕРХНОСТЬ. Вешается атрибутом на секцию:
       <section class="section" data-surface="dark">
       Все компоненты внутри перекрашиваются сами. Это заменяет три подвала
       и две формы лида из макетов одним макросом. ------------------------ */
[data-surface="dark"] {
  --page-bg:       var(--c-graphite-800);
  --surface:       var(--c-graphite-800);
  --surface-2:     var(--c-graphite-700);
  --surface-3:     var(--c-graphite-600);
  --surface-hover: var(--c-graphite-700);

  --ink:           var(--c-white);
  --ink-2:         var(--c-steel-200);
  --ink-3:         var(--c-steel-300);   /* 5.72:1. НЕ #6C778A, там 3.73:1 */
  --ink-on-accent: var(--c-graphite-800);

  --line:        var(--c-graphite-600);
  --line-soft:   var(--c-graphite-600);
  --line-strong: var(--c-graphite-500);
  --line-dashed: var(--c-graphite-500);

  --accent:      var(--c-yellow-400);
  --accent-text: var(--c-yellow-400);    /* 10.8:1 на графите */
  --accent-line: var(--c-yellow-400);
  --accent-2:    var(--c-blue-400);

  --ok:     var(--c-green-300);
  --warn:   var(--c-yellow-400);
  --danger: var(--c-red-300);

  --field-bg:         var(--c-graphite-700);
  --field-line:       var(--c-graphite-500);
  --field-ink:        var(--c-white);
  --field-ph:         var(--c-steel-300);
  --field-line-focus: var(--c-yellow-400);

  --focus-ring: var(--c-yellow-400);
  --focus-halo: var(--c-graphite-900);

  background: var(--page-bg);
  color: var(--ink);
}

/* --- 4. ТИПОГРАФИКА ---------------------------------------------------- */
:root {
  --font-sans: 'Manrope', 'Manrope Fallback', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-mono: 'IBM Plex Mono', 'IBM Plex Mono Fallback', ui-monospace, 'SF Mono', Consolas, monospace;

  /* Manrope 500 в макетах не встречается ни разу: font-weight 500 всюду
     стоит рядом с IBM Plex Mono. */
  --fw-regular: 400;  --fw-semi: 600;  --fw-bold: 700;  --fw-black: 800;
  --fw-mono: 400;     --fw-mono-med: 500;

  /* Fluid-шкала. Заменяет собой все хаки site-anim.js вида
     [style*="font-size: 116px"]. ВНИМАНИЕ сборщику: пробелы вокруг +
     внутри clamp обязательны по спецификации, минификатор их не трогает. */
  --fs-d1: clamp(2.25rem, 1.05rem + 4.6vw, 4.5rem);    /*  36 -> 72  H1 карточки */
  --fs-d2: clamp(2rem,    1rem    + 4.1vw, 4rem);      /*  32 -> 64 */
  --fs-d3: clamp(1.75rem, 0.9rem  + 3.3vw, 3.25rem);   /*  28 -> 52 */
  --fs-d4: clamp(1.625rem,1rem    + 2.5vw, 2.75rem);   /*  26 -> 44  H2 секции */
  --fs-d5: clamp(1.375rem,1rem    + 1.5vw, 2rem);      /*  22 -> 32 */
  --fs-d6: clamp(1.25rem, 1.05rem + 0.8vw, 1.5rem);    /*  20 -> 24 */
  --fs-price: clamp(2rem, 1.3rem + 2.7vw, 3rem);       /*  32 -> 48  цена */

  --fs-xs: .75rem;  --fs-sm: .8125rem;  --fs-md: .875rem;
  --fs-base: .9375rem;  --fs-lg: 1rem;  --fs-xl: 1.0625rem;
  --fs-2xl: 1.125rem;   --fs-3xl: 1.1875rem;  --fs-4xl: 1.375rem;

  --fs-mono-3xs: .625rem;   --fs-mono-2xs: .6875rem;  --fs-mono-xs: .75rem;
  --fs-mono-sm: .8125rem;   --fs-mono-md: .875rem;
  --fs-mono-lg: 1rem;       --fs-mono-xl: 1.0625rem;

  --lh-tight: .92;  --lh-display: .98;  --lh-head: 1;
  --lh-snug: 1.3;   --lh-body: 1.55;    --lh-loose: 1.7;

  --ls-d1: -.05em;  --ls-d2: -.045em;  --ls-d3: -.04em;  --ls-d4: -.03em;
  --ls-tight: -.02em;
  --ls-mono-sm: .08em;  --ls-mono: .10em;  --ls-mono-lg: .12em;  --ls-mono-xl: .14em;
}

/* --- 5. РИТМ ----------------------------------------------------------- */
:root {
  --sp-2: 2px;   --sp-4: 4px;   --sp-6: 6px;   --sp-8: 8px;
  --sp-10: 10px; --sp-12: 12px; --sp-16: 16px; --sp-20: 20px;
  --sp-24: 24px; --sp-28: 28px; --sp-32: 32px; --sp-40: 40px;
  --sp-48: 48px; --sp-56: 56px; --sp-64: 64px; --sp-72: 72px; --sp-80: 80px;

  --container:     1600px;
  --gutter:        48px;
  --section-gap:   72px;
  --section-pad-y: 80px;
  --card-pad:      36px;
  --card-pad-sm:   24px;
  --grid-gap:      16px;
  --grid-gap-lg:   32px;

  --col-product: 240px;
  --col-tile:    260px;
  --col-split:   420px;
  --col-form:    200px;
}

/* --- 6. УПРАВЛЯЮЩИЕ ЭЛЕМЕНТЫ.
       В макетах было 12 высот (38..60). Сведено к шести плюс иконка. ---- */
:root {
  --h-xs: 40px; --h-sm: 44px; --h-md: 48px;
  --h-lg: 52px; --h-xl: 56px; --h-2xl: 60px;

  --h-icon-btn: 38px;
  --h-header: 80px;  --h-header-sm: 64px;
  --h-thumb: 56px;

  --pad-btn-sm: 0 18px;  --pad-btn-md: 0 24px;  --pad-btn-lg: 0 32px;
  --pad-field: 0 16px;   --pad-field-lg: 0 22px;

  --stepper-w: 148px;  --stepper-w-sm: 108px;
  --tap-min: 44px;     --tap-gap: 8px;   /* зазор между иконками шапки:
                                            при 2px из макета зоны 44px
                                            перекрываются и дают промахи */
}

/* --- 7. ГРАНИЦЫ, ТЕНИ, СЛОИ, ДВИЖЕНИЕ --------------------------------- */
:root {
  --bw: 1px;  --bw-2: 2px;
  --radius: 0;              /* в макетах скругления нулевые везде */
  --radius-pill: 100px;     /* единственное исключение: бейдж корзины */

  --shadow-card:   0 14px 34px rgba(24, 29, 39, .09);
  --shadow-header: 0 10px 30px rgba(10, 13, 18, .32);
  --shadow-pop:    0 24px 48px rgba(24, 29, 39, .18);

  --z-base: 0; --z-sticky-aside: 10; --z-dropdown: 20;
  --z-header: 60; --z-drawer: 90; --z-modal: 100; --z-toast: 120;

  --ease: cubic-bezier(.22, 1, .36, 1);
  --dur-fast: .16s;  --dur: .24s;  --dur-slow: .32s;  --dur-img: .7s;
  --reveal-step: 70ms;      /* потолок каскада 350 мс */

  --header-bg: rgba(24, 29, 39, .94);
  --header-blur: 16px;
}

/* --- 8. БРЕЙКПОИНТЫ. Значения нельзя вынести в переменные (@media их
       не читает), поэтому держим синхронно вручную: 1440/1180/900/720/480.
       --container на 1440 НЕ меняем: это max-width, он и так перестаёт
       связывать ниже 1696px, а одновременная смена container и gutter
       давала скачок ширины контента примерно на 145px. ------------------ */
@media (max-width: 1440px) { :root { --gutter: 40px; } }
@media (max-width: 1180px) {
  :root { --gutter: 32px; --section-gap: 56px; --section-pad-y: 56px;
          --card-pad: 28px; --grid-gap-lg: 24px; --col-split: 360px; }
}
@media (max-width: 900px) {
  :root { --gutter: 24px; --section-gap: 48px; --section-pad-y: 48px;
          --card-pad: 24px; --card-pad-sm: 20px; --grid-gap-lg: 20px;
          --col-product: 200px; --col-tile: 220px; }
}
@media (max-width: 720px) {
  :root { --gutter: 18px; --section-gap: 40px; --section-pad-y: 40px;
          --card-pad: 20px; --card-pad-sm: 16px; --grid-gap: 12px;
          --grid-gap-lg: 16px; --h-header: 64px; --h-2xl: 56px; --h-xl: 52px;
          --col-product: 150px; }
}
@media (max-width: 480px) { :root { --col-product: 100%; --stepper-w: 132px; } }

/* --- 9. ДОСТУПНОСТЬ ---------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  :root { --dur-fast: .01ms; --dur: .01ms; --dur-slow: .01ms;
          --dur-img: .01ms; --reveal-step: 0ms; }
}
@media (prefers-contrast: more) {
  :root { --ink-3: var(--c-steel-700); --line: var(--c-steel-400);
          --line-strong: var(--c-graphite-800); }
  [data-surface="dark"] { --ink-3: var(--c-steel-200); --line: var(--c-steel-300); }
}

/* --- 01-reset.css --- */
/* ============================================================================
   Ресет. Только то, что действительно чинит браузерные умолчания.
   Правило одно: ни в одном селекторе этого проекта нет outline: none.
   В макетах он стоял на каждом input, и клавиатурный пользователь терял
   каретку фокуса на форме заявки, то есть на единственном действии страницы.
   ============================================================================ */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, ol, ul {
  margin: 0;
}
ul[class], ol[class] { padding: 0; list-style: none; }

h1, h2, h3, h4, h5, h6 { font-size: inherit; font-weight: inherit; }

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

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

input, button, textarea, select {
  font: inherit;
  color: inherit;
  letter-spacing: inherit;
  margin: 0;
}
button { background: none; border: 0; padding: 0; cursor: pointer; }
textarea { resize: vertical; }
select { appearance: none; }

table { width: 100%; border-collapse: collapse; }
th { text-align: left; font-weight: inherit; }

/* Маркер <summary> рисуем сами: у стандартного нет цвета и он не поворачивается. */
summary { cursor: pointer; list-style: none; }
summary::-webkit-details-marker { display: none; }

[hidden] { display: none !important; }

/* Единый видимый фокус. Толщина 2px и отступ 2px читаются и на белой карточке,
   и на графите: --focus-ring переключается вместе с поверхностью. */
:focus-visible {
  outline: var(--bw-2) solid var(--focus-ring);
  outline-offset: 2px;
}

::selection { background: var(--c-yellow-400); color: var(--c-graphite-800); }
::placeholder { color: var(--field-ph); opacity: 1; }

/* Тач-подсветка Android поверх собственного :active выглядит как двойное нажатие. */
a, button, summary, label { -webkit-tap-highlight-color: transparent; }

/* --- 02-base.css --- */
/* ============================================================================
   База: шрифты, страница, контейнер, секции, типографические утилиты,
   каскад появления.
   ============================================================================ */

/* --- ШРИФТЫ. Self-host, ноль внешних запросов: Google Fonts это два
       preconnect, блокирующий CSS с чужого домена и передача IP посетителя
       за рубеж (ФЗ-152).
       Путь ../fonts/ верен и для static/css/*.css, и для собранного
       static/build/site.<hash>.css: оба каталога лежат на одном уровне. --- */
@font-face {
  font-family: 'Manrope';
  src: url('../fonts/manrope-var.woff2') format('woff2');
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('../fonts/plex-mono-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('../fonts/plex-mono-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* Фоллбеки с метриками. local() обязан включать Android-шрифты: без найденного
   local() семейство не создаётся вовсе и подгонка метрик теряет смысл. */
@font-face {
  font-family: 'Manrope Fallback';
  src: local('Roboto'), local('Arial'), local('Helvetica Neue');
  size-adjust: 103%; ascent-override: 98%; descent-override: 26%; line-gap-override: 0%;
}
@font-face {
  font-family: 'IBM Plex Mono Fallback';
  src: local('Roboto Mono'), local('Menlo'), local('Consolas');
  size-adjust: 97%; ascent-override: 100%; descent-override: 26%; line-gap-override: 0%;
}

/* --- СТРАНИЦА --------------------------------------------------------- */
html { background: var(--page-bg); }

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  background: var(--page-bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  font-weight: var(--fw-regular);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
}

.page-main { flex: 1 0 auto; }

/* Скип-линк. Виден только с клавиатуры, но виден полностью: сдвиг за экран
   на -100px вместо смены top прячет его и от фокуса тоже. */
.skip-link {
  position: absolute;
  top: 0; left: 0;
  z-index: var(--z-toast);
  transform: translateY(-120%);
  padding: var(--sp-12) var(--sp-20);
  background: var(--c-yellow-400);
  color: var(--c-graphite-800);
  font-weight: var(--fw-bold);
}
.skip-link:focus { transform: none; }

/* --- КОНТЕЙНЕР И СЕКЦИИ ------------------------------------------------ */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.section { padding-block: var(--section-gap); }
.section[data-surface="dark"] { padding-block: var(--section-pad-y); }
.section--tight { padding-block: var(--sp-40); }
.section--flush-top { padding-top: 0; }
.section--flush-bottom { padding-bottom: 0; }

/* Сетка с одной ручкой: по брейкпоинтам меняется только --col-min. */
.grid {
  display: grid;
  gap: var(--grid-gap);
  grid-template-columns: repeat(auto-fit, minmax(var(--col-min, 260px), 1fr));
}
.grid--tiles { --col-min: var(--col-tile); }
.grid--gap-lg { gap: var(--grid-gap-lg); }

/* Сплит карточки: слева галерея, справа коммерция. Ниже 1180 один столбец. */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--grid-gap-lg);
  align-items: start;
}
.split__aside {
  position: sticky;
  top: calc(var(--h-header) + var(--sp-16));
  z-index: var(--z-sticky-aside);
}
@media (max-width: 1180px) {
  .split { grid-template-columns: minmax(0, 1fr); }
  .split__aside { position: static; }
}

.stack { display: grid; gap: var(--grid-gap); align-content: start; }
.stack--sm { gap: var(--sp-10); }
.stack--lg { gap: var(--grid-gap-lg); }

/* --- ТИПОГРАФИКА ------------------------------------------------------- */
.mono {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono);
  font-variant-numeric: tabular-nums;
}

/* Моно-микроподпись НАЛИЧИЕ / ОТГРУЗКА / ГАРАНТИЯ. */
.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  font-weight: var(--fw-mono);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}
.eyebrow--accent { color: var(--accent-text); }

.lede {
  font-size: var(--fs-2xl);
  line-height: var(--lh-snug);
  color: var(--ink-2);
}

.t-d4 { font-size: var(--fs-d4); font-weight: var(--fw-black);
        line-height: var(--lh-head); letter-spacing: var(--ls-d3); }
.t-d5 { font-size: var(--fs-d5); font-weight: var(--fw-black);
        line-height: var(--lh-head); letter-spacing: var(--ls-d4); }
.t-d6 { font-size: var(--fs-d6); font-weight: var(--fw-bold);
        line-height: var(--lh-snug); letter-spacing: var(--ls-tight); }

.t-muted  { color: var(--ink-3); }
.t-quiet  { color: var(--ink-2); }
.t-ok     { color: var(--ok); }
.t-warn   { color: var(--warn); }
.t-danger { color: var(--danger); }
.t-nowrap { white-space: nowrap; }

/* Номера деталей приходят как есть, с пробелами и слэшами, и рвать их можно
   только в безвыходном положении. */
.pn { font-family: var(--font-mono); font-weight: var(--fw-mono-med);
      overflow-wrap: anywhere; }

.link {
  color: var(--accent-text);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
  transition: color var(--dur) var(--ease);
}
.link:hover { color: var(--ink); }
.link--quiet { color: var(--ink-2); text-decoration: none; }
.link--quiet:hover { color: var(--accent-text); }

/* Ссылка-стрелка справа от заголовка секции. */
.link--action {
  display: inline-block;
  padding-bottom: var(--sp-4);
  border-bottom: var(--bw) solid var(--accent-line);
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-xs);
  letter-spacing: var(--ls-mono);
  text-transform: uppercase;
  text-decoration: none;
}
.link--action:hover { color: var(--ink); border-bottom-color: currentColor; }

/* Три буллета условий магазина под ценой. */
.bullets { display: grid; gap: var(--sp-10);
           font-size: var(--fs-md); line-height: var(--lh-snug); color: var(--ink-2); }
.bullets__item { display: grid; grid-template-columns: 18px 1fr; gap: var(--sp-10); }
.bullets__item::before { content: "—"; color: var(--accent-line); }

/* --- ИКОНКИ ------------------------------------------------------------ */
.icon {
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.icon--filled { fill: currentColor; stroke: none; }

/* --- СЛУЖЕБНОЕ --------------------------------------------------------- */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- ПОЯВЛЕНИЕ.
       Элемент НИКОГДА не скрыт заранее: анимация навешивается классом только
       в момент входа во вьюпорт. Первый экран помечается data-seen и класса
       не получает, иначе animation-delay держал бы LCP-блок прозрачным. ---- */
@keyframes fx-in {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to   { opacity: 1; transform: none; }
}
.is-revealed { animation: fx-in .5s var(--ease) both; }
@media (prefers-reduced-motion: reduce) {
  .is-revealed { animation: none; }
}

/* --- 10-header.css --- */
/* ============================================================================
   Шапка. Прилипшая, тёмная, высота через --h-header (80 -> 64 на 720).
   Ниже 1280 навигация уходит в <details>: JS для меню не нужен вообще.
   ============================================================================ */

/* Сентинел высотой 1px перед шапкой: IntersectionObserver по нему заменяет
   обработчик scroll, который в макете срабатывал на каждый кадр прокрутки. */
.hdr-sentinel { height: 1px; margin-bottom: -1px; }

.hdr {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--header-bg);
  border-bottom: var(--bw) solid var(--c-graphite-600);
  transition: box-shadow var(--dur-slow) var(--ease);
}
@supports (backdrop-filter: blur(1px)) {
  .hdr { backdrop-filter: blur(var(--header-blur)); }
}
.hdr.is-stuck { box-shadow: var(--shadow-header); }

.hdr__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-24);
  height: var(--h-header);
  white-space: nowrap;
}

.hdr__logo { display: flex; align-items: center; flex: none; color: var(--c-white); }
/* Высота задаётся здесь, ширину держит viewBox: логотип 207x37, то есть
   пропорция 5.6:1, и при height 26px он занимает 145px. Ширину не задаём,
   иначе при смене логотипа поедут пропорции. */
.hdr__logo .logo { display: block; height: 26px; width: auto; }

/* flex: none, а не 1 1 auto: свободную ширину забирает поле поиска, а меню
   из шести пунктов ровно такое, какое есть. Ниже 1280 оно уезжает в бургер. */
.hdr__nav {
  display: flex;
  align-items: center;
  gap: var(--sp-24);
  flex: none;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
}

.hdr__link { color: var(--c-steel-200); transition: color var(--dur) var(--ease); }
.hdr__link:hover { color: var(--c-yellow-400); }
.hdr__link.is-active { color: var(--c-yellow-400); }

/* --- Поиск по номеру. Открытое поле прямо в строке шапки, между меню и
       иконками: flex: 1 1 auto забирает свободную ширину (см. комментарий
       у .hdr__nav). max-width не даёт полю растянуться во всю ширину на
       широких экранах, min-width держит его нажимаемым, когда нав уже
       уехал в бургер и свободного места стало больше, чем нужно полю. -- */
.hdr__find {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 80px;
  max-width: 420px;
  height: var(--tap-min);
  background: var(--c-graphite-700);
  border: var(--bw) solid var(--c-graphite-500);
}
.hdr__find:focus-within { border-color: var(--c-yellow-400); }

.hdr__find-input {
  width: 100%;
  height: 100%;
  padding-inline: var(--sp-10) var(--tap-min);
  background: transparent;
  border: 0;
  color: var(--c-white);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
}
.hdr__find-input::placeholder { color: var(--c-steel-300); }
/* Крестик очистки у type="search" в webkit рисуется тёмным по тёмному. */
.hdr__find-input::-webkit-search-cancel-button { filter: invert(1); opacity: .5; }
.hdr__find-input:focus-visible { outline: 0; }

/* Кнопка отправки — значок поверх поля, а не отдельный блок со своей
   шириной и подписью "Найти": подписанной кнопке в строке шапки не
   хватает места уже на планшете. */
.hdr__find-go {
  position: absolute;
  top: 0;
  right: 0;
  display: grid;
  place-items: center;
  width: var(--tap-min);
  height: 100%;
  color: var(--c-steel-200);
  cursor: pointer;
}
.hdr__find-go:hover { color: var(--c-yellow-400); }

.hdr__tools {
  display: flex;
  align-items: center;
  gap: var(--tap-gap);
  flex: none;
  margin-left: auto;
}

.hdr__phone {
  display: flex;
  align-items: center;
  margin-right: var(--sp-6);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-md);
  color: var(--c-white);
}
.hdr__phone:hover { color: var(--c-yellow-400); }

.hdr__icon-link {
  display: grid;
  place-items: center;
  width: var(--tap-min);
  height: var(--tap-min);
  color: var(--c-steel-200);
  transition: color var(--dur) var(--ease);
}
.hdr__icon-link:hover { color: var(--c-yellow-400); }

.hdr__cart { position: relative; }
/* Единственное скругление во всём проекте. */
.hdr__badge {
  position: absolute;
  top: 4px; right: 0;
  display: grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  background: var(--c-yellow-400);
  color: var(--c-graphite-800);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  line-height: 1;
}

/* --- Мобильное меню. Второй экземпляр навигации, а не переезд первого:
       переключать один и тот же <nav> между инлайном и содержимым <details>
       нельзя, UA скрывает содержимое сам. Невидимая копия из дерева
       доступности выпадает вместе с display: none, цены у дубля нет. ----- */
.hdr__menu { position: relative; display: none; flex: none; margin-left: auto; }
.hdr__burger {
  display: grid;
  place-items: center;
  width: var(--tap-min);
  height: var(--tap-min);
  color: var(--c-steel-200);
}
.hdr__burger:hover { color: var(--c-yellow-400); }
.hdr__menu[open] .hdr__burger { color: var(--c-yellow-400); }

.hdr__drop {
  position: absolute;
  top: calc(100% + var(--sp-8));
  right: 0;
  z-index: var(--z-dropdown);
  display: grid;
  gap: var(--sp-2);
  min-width: 220px;
  padding: var(--sp-8);
  background: var(--c-graphite-700);
  border: var(--bw) solid var(--c-graphite-500);
  box-shadow: var(--shadow-header);
}
.hdr__drop .hdr__link {
  display: block;
  padding: var(--sp-10) var(--sp-12);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
}
.hdr__drop .hdr__link:hover { background: var(--c-graphite-600); }

/* Избранное и кабинет в бургере. Выше порога их место в строке справа, поэтому
   по умолчанию текстовый дубль не существует вовсе: display: none убирает узел
   и из дерева доступности, иначе скринридер читал бы оба экземпляра.
   Селектор из двух классов не для красоты: одиночный .hdr__drop-extra
   проигрывал бы по весу правилу .hdr__drop .hdr__link выше. */
.hdr__drop .hdr__drop-extra { display: none; }

/* Порог поднят с 1180 до 1280: шестой пункт меню ("Комплекты") и две иконки
   справа (избранное, кабинет) добавили строке около 200px. Число связано с
   составом NAV в app/web/__init__.py: пункт туда, порог сюда. */
@media (max-width: 1280px) {
  .hdr__nav { display: none; }
  .hdr__menu { display: block; }
  /* Иконки уезжают в бургер вместе с меню: ниже этого порога в строке остаются
     только поиск, телефон и список подбора, ровно как было до сведения. */
  .hdr__tools-extra { display: none; }
  .hdr__drop .hdr__drop-extra { display: block; }
}
/* Ниже 720 в строке остаются логотип, ТРИ цели нажатия (поиск, телефон,
   список) и бургер. Это 176px только на цели, плюс зазоры, плюс поля
   контейнера: на экране 360px логотипу приходится уступить. Ужимаем зазоры и
   кегль слова, а сам логотип делаем сжимаемым последним рубежом: подрезанное
   слово хуже, чем горизонтальная прокрутка всей страницы, но лучше её. */
@media (max-width: 720px) {
  .hdr__inner { gap: var(--sp-10); }
  .hdr__logo .logo { height: 21px; }
  .hdr__tools { gap: var(--sp-2); }
  .hdr__logo { flex: 0 1 auto; min-width: 0; overflow: hidden; }

  /* Поле сжимается сильнее, чем на планшете: три соседних цели нажатия
     (поиск, телефон, список) и бургер не сжимаются вовсе, всю тесноту
     принимают на себя поле и логотип, поле — первым. */
  .hdr__find { min-width: 64px; }
}
/* Ниже 900 телефон сворачивается в иконку tel:, а не исчезает: у половины
   заявок это единственный канал. Ссылка при этом обязана остаться зоной 44px. */
@media (max-width: 900px) {
  .hdr__phone-text { display: none; }
  .hdr__phone-icon { display: grid; place-items: center; }
  .hdr__phone {
    justify-content: center;
    width: var(--tap-min);
    height: var(--tap-min);
    margin-right: 0;
    color: var(--c-steel-200);
  }
}
@media (min-width: 901px) {
  .hdr__phone-icon { display: none; }
}

/* --- 11-footer.css --- */
/* ============================================================================
   Подвал. Три подвала макетов сведены к двум осям: variant (full|compact)
   и surface (dark|light), поэтому здесь нет ни одного цветового литерала,
   всё через семантические переменные.
   ============================================================================ */

.ftr {
  margin-top: auto;
  background: var(--surface);
  color: var(--ink-2);
  border-top: var(--bw) solid var(--line);
}

.ftr__cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--col-form), 1fr));
  gap: var(--sp-40);
  padding-block: var(--sp-56) var(--sp-28);
}

.ftr__brand { display: flex; align-items: center; color: var(--ink); }
/* В подвале логотип мельче, чем в шапке: он здесь опознавательный знак,
   а не точка входа. Цвет берётся из --ink, поэтому подвал может быть
   и тёмным, и светлым без второго файла. */
.ftr__brand .logo { display: block; height: 22px; width: auto; }

.ftr__about {
  margin-top: var(--sp-16);
  max-width: 300px;
  font-size: var(--fs-sm);
  line-height: var(--lh-loose);
  color: var(--ink-3);
}

.ftr__col { display: grid; gap: var(--sp-10); align-content: start; font-size: var(--fs-sm); }
.ftr__col-title { color: var(--ink); }
.ftr__link { color: var(--ink-2); transition: color var(--dur) var(--ease); }
.ftr__link:hover { color: var(--accent-text); }

.ftr__bottom {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-24);
  flex-wrap: wrap;
  padding-block: var(--sp-20) var(--sp-40);
  border-top: var(--bw) solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  /* В макете здесь #6C778A на графите: 3.73:1, провал AA.
     Юридическая строка и адрес обязаны читаться. */
  color: var(--ink-3);
}
.ftr__legal { display: flex; gap: var(--sp-20); flex-wrap: wrap; }
.ftr__legal a { color: inherit; text-decoration: underline;
                text-decoration-thickness: 1px; text-underline-offset: .18em; }
.ftr__legal a:hover { color: var(--accent-text); }

.ftr__contacts { display: flex; gap: var(--sp-24); flex-wrap: wrap; }
.ftr__contacts a { color: var(--ink-2); }
.ftr__contacts a:hover { color: var(--accent-text); }

/* Компактный вариант: одна строка, без колонок ссылок. */
.ftr--compact .ftr__bottom {
  padding-block: var(--sp-40);
  border-top: 0;
  font-size: var(--fs-mono-xs);
}

/* --- 12-hero.css --- */
/* ============================================================================
   Герой страницы: крошки, бренд-строка, H1, лид, правая колонка метрик.
   На герое НЕТ data-reveal: это кандидат в LCP.
   ============================================================================ */

.hero { padding-block: var(--sp-32) var(--sp-40); }
.hero--sm { padding-block: var(--sp-28) var(--sp-32); }
.hero--lg { padding-block: var(--sp-40) var(--sp-64); }

.hero__body {
  margin-top: var(--sp-28);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-40);
  align-items: end;
}
.hero__body--plain { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 1180px) {
  .hero__body { grid-template-columns: minmax(0, 1fr); align-items: start; }
}

.hero__top {
  display: flex;
  align-items: center;
  gap: var(--sp-16);
  flex-wrap: wrap;
}

.hero__title {
  margin-top: var(--sp-20);
  font-size: var(--fs-d1);
  font-weight: var(--fw-black);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-d1);
  /* Номера бывают длиной в 20 символов со слэшами: рвём где угодно,
     иначе H1 вылезает за контейнер на телефоне. */
  overflow-wrap: anywhere;
}
/* Номер длиннее 12 символов: ступень ниже, иначе на 360px строка занимает
   три ряда и лид уходит под сгиб. */
.hero__title--long { font-size: var(--fs-d3); letter-spacing: var(--ls-d3); }

/* Бренд перед номером в H1: тем же кеглем нельзя, съедает номер. */
.h1-brand {
  display: block;
  font-size: var(--fs-d6);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
  color: var(--ink-3);
}

@media (max-width: 720px) {
  /* Трекинг -0.05em на мелком кегле слепляет буквы. */
  .hero__title { letter-spacing: var(--ls-d4); }
}

.hero__lead {
  margin-top: var(--sp-16);
  max-width: 52ch;
  font-size: var(--fs-2xl);
  line-height: var(--lh-snug);
  color: var(--ink-2);
}

.hero__meta {
  display: flex;
  gap: var(--sp-32);
  flex-wrap: wrap;
}

/* Метка "Оригинал" и разделитель между брендом и меткой. */
.hero__brand { font-size: var(--fs-3xl); font-weight: var(--fw-black);
               letter-spacing: var(--ls-tight); color: var(--ink); }
.hero__brand:hover { color: var(--accent-text); }
.hero__sep { display: block; width: 1px; height: 18px; background: var(--line-strong); }

/* --- 20-button.css --- */
/* ============================================================================
   Кнопки. Четыре варианта, пять высот. Один и тот же класс на <button> и <a>,
   поэтому высота задаётся не padding-ом, а height + inline-flex.
   ============================================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-10);
  height: var(--h-md);
  padding: var(--pad-btn-md);
  border: var(--bw) solid transparent;
  border-radius: var(--radius);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  line-height: 1;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              transform var(--dur-slow) var(--ease);
}
.btn:active { transform: translateY(1px); }

.btn--sm { height: var(--h-sm); padding: var(--pad-btn-sm); font-size: var(--fs-md); }
.btn--md { height: var(--h-md); padding: var(--pad-btn-md); }
.btn--lg { height: var(--h-lg); padding: var(--pad-btn-lg); }
.btn--xl { height: var(--h-xl); padding: var(--pad-btn-lg); }
.btn--full { width: 100%; }

/* Жёлтый фон, графитовый текст: 10.8:1. */
.btn--primary {
  background: var(--accent);
  color: var(--ink-on-accent);
}
.btn--primary:hover { background: var(--c-graphite-800); color: var(--c-white); }
[data-surface="dark"] .btn--primary:hover { background: var(--c-white); color: var(--c-graphite-800); }

.btn--dark {
  background: var(--ink);
  color: var(--surface);
}
.btn--dark:hover { background: var(--accent); color: var(--ink-on-accent); }

.btn--outline {
  background: transparent;
  border-color: var(--line-strong);
  color: var(--ink);
}
.btn--outline:hover { border-color: var(--accent-line); color: var(--accent-text); }

.btn--ghost {
  background: transparent;
  color: var(--ink-2);
  padding: var(--pad-btn-sm);
}
.btn--ghost:hover { color: var(--accent-text); }

/* Отключённая кнопка обязана оставаться читаемой: серый на сером это
   не "недоступно", это "непонятно". */
.btn:disabled,
.btn.is-disabled {
  opacity: .55;
  cursor: not-allowed;
  transform: none;
}

.btn__icon { width: 20px; height: 20px; }

/* Квадратная иконочная кнопка (степпер и мелкие действия используют свои). */
.btn--icon {
  width: var(--h-icon-btn);
  height: var(--h-icon-btn);
  padding: 0;
}

/* --- 21-field.css --- */
/* ============================================================================
   Поля ввода, чекбокс, степпер количества.
   Ни одного outline: none. В макете он стоял на каждом input, и фокус
   на форме заявки был не виден вообще.
   ============================================================================ */

.field { display: grid; gap: var(--sp-10); min-width: 0; }

/* Подпись всегда есть и всегда <label for>. Плейсхолдер это пример значения,
   он исчезает при вводе и подписью работать не может. */
.field__label {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono);
  text-transform: uppercase;
  color: var(--ink-3);
}
.field__req { color: var(--accent-text); }

.field__input {
  width: 100%;
  height: var(--h-md);
  padding: var(--pad-field);
  background: var(--field-bg);
  border: var(--bw) solid var(--field-line);
  border-radius: var(--radius);
  color: var(--field-ink);
  font-size: var(--fs-lg);
  transition: border-color var(--dur) var(--ease);
}
.field__input:hover { border-color: var(--line-dashed); }
.field__input:focus { border-color: var(--field-line-focus); }
.field__input::placeholder { color: var(--field-ph); }

textarea.field__input { height: auto; min-height: 120px; padding: var(--sp-12) var(--sp-16); }

.field--lg .field__input { height: var(--h-xl); padding: var(--pad-field-lg);
                           font-size: var(--fs-xl); font-weight: var(--fw-semi); }
.field--sm .field__input { height: var(--h-sm); font-size: var(--fs-base); }
.field--mono .field__input { font-family: var(--font-mono); letter-spacing: var(--ls-mono-sm); }

.field__hint { font-size: var(--fs-sm); color: var(--ink-3); }
.field__error { font-size: var(--fs-sm); color: var(--danger); }
.field--invalid .field__input { border-color: var(--danger); }

/* --- ЧЕКБОКС. Согласие на обработку ПДн: НЕ предустановлен, обязателен,
       кликабельна вся подпись. accent-color красит нативный элемент,
       так что фокус и состояния остаются браузерными. ------------------- */
.check {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: var(--sp-10);
  align-items: start;
  max-width: 62ch;
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
  color: var(--ink-2);
  cursor: pointer;
}
.check__box {
  width: 18px;
  height: 18px;
  margin: 2px 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.check--sm { font-size: var(--fs-xs); color: var(--ink-3); }
.check a { color: var(--accent-text); text-decoration: underline;
           text-decoration-thickness: 1px; text-underline-offset: .18em; }

/* --- СТЕППЕР. Внутри настоящий <input type="number">: без него форма
       не работает без JS, а скринридер не читает значение.
       Кнопки скрыты до появления JS, чтобы не было мёртвых контролов
       (нативные спиннеры числового поля при этом остаются). ------------- */
.qty {
  display: inline-grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  width: var(--stepper-w);
  height: var(--h-xl);
  background: var(--field-bg);
  border: var(--bw) solid var(--field-line);
}
.qty:focus-within { border-color: var(--field-line-focus); }
.qty--sm { width: var(--stepper-w-sm); height: var(--h-md); }

.qty__btn {
  display: none;
  place-items: center;
  width: 44px;
  height: 100%;
  color: var(--ink-3);
  font-size: var(--fs-4xl);
  line-height: 1;
  transition: color var(--dur) var(--ease);
}
.js .qty__btn { display: grid; }
.qty__btn:hover { color: var(--accent-text); }
.qty__btn:disabled { opacity: .4; cursor: not-allowed; }

.qty__input {
  width: 100%;
  height: 100%;
  padding: 0 var(--sp-6);
  background: transparent;
  border: 0;
  color: var(--field-ink);
  font-family: var(--font-mono);
  font-size: var(--fs-2xl);
  font-variant-numeric: tabular-nums;
  text-align: center;
}
/* Нативные спиннеры прячем только когда есть свои кнопки. */
.js .qty__input::-webkit-outer-spin-button,
.js .qty__input::-webkit-inner-spin-button { appearance: none; margin: 0; }
.js .qty__input { appearance: textfield; }

/* --- ФОРМА ЗАЯВКИ ------------------------------------------------------ */
.lead__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
  gap: var(--section-gap);
  align-items: start;
}
@media (max-width: 900px) {
  .lead__grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-32); }
}

.lead__title {
  margin-top: var(--sp-16);
  font-size: var(--fs-d3);
  font-weight: var(--fw-black);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-d2);
}
.lead__text {
  margin-top: var(--sp-20);
  max-width: 52ch;
  font-size: var(--fs-xl);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

.lead__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--col-form), 1fr));
  gap: var(--sp-24);
}
.lead--card .lead__form { margin-top: var(--sp-20); }
.lead__comment { margin-top: var(--sp-20); }
.lead__consent { margin-top: var(--sp-20); }
.lead__submit  { margin-top: var(--sp-24); }

/* Ловушка для ботов. Уводим за экран, а не display: none: часть ботов
   пропускает скрытые поля, но заполняет всё, что есть в DOM.
   Для человека поле недостижимо: aria-hidden плюс tabindex -1. */
.lead__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- 22-card.css --- */
/* ============================================================================
   Карточка-плашка, шапка плашки, тёмный callout, плитка метрики.
   Все цвета через семантику, поэтому одна и та же плашка работает и на
   белом фоне карточки товара, и внутри тёмной секции.
   ============================================================================ */

.card {
  background: var(--surface);
  border: var(--bw) solid var(--line);
  border-radius: var(--radius);
}
.card--pad { padding: var(--card-pad); }
.card--pad-sm { padding: var(--card-pad-sm); }

/* Шапка плашки: моно-микроподпись плюс разделительная линия. */
.card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-16);
  padding: var(--sp-20) var(--sp-28);
  border-bottom: var(--bw) solid var(--line);
}
.card__title {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}
.card__body { padding: var(--sp-20) var(--sp-28); }
.card__foot { padding: var(--sp-16) var(--sp-28); border-top: var(--bw) solid var(--line); }

/* Ховер-подъём только там, где плашка кликабельна целиком. */
.card--link { transition: transform var(--dur-slow) var(--ease), box-shadow var(--dur-slow) var(--ease); }
.card--link:hover { transform: translateY(-2px); box-shadow: var(--shadow-card); }

/* Строка "ключ: значение" внутри плашки (наличие, отгрузка, склад). */
.deflist { display: grid; gap: var(--sp-12); font-size: var(--fs-lg); }
.deflist__row {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-16);
  padding-bottom: var(--sp-12);
  border-bottom: var(--bw) solid var(--line-soft);
}
.deflist__row:last-child { padding-bottom: 0; border-bottom: 0; }
.deflist__key { color: var(--ink-2); }
.deflist__val { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* --- CALLOUT. Тёмная плашка "Не уверены, что подойдёт?" ---------------- */
.callout { padding: var(--sp-32); }
.callout__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--accent-text);
}
.callout__title {
  margin-top: var(--sp-12);
  font-size: var(--fs-d6);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
.callout__text {
  margin-top: var(--sp-12);
  max-width: 52ch;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.callout__action { margin-top: var(--sp-20); }

/* --- KPI. Наличие / Отгрузка / Гарантия в герое и под ценой. ---------- */
.kpi { min-width: 0; }
.kpi__label {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono);
  text-transform: uppercase;
  color: var(--ink-3);
}
.kpi__value {
  margin-top: var(--sp-8);
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
.kpi__hint { margin-top: var(--sp-6); font-size: var(--fs-sm); color: var(--ink-3); }
.kpi--ok     .kpi__value { color: var(--ok); }
.kpi--warn   .kpi__value { color: var(--warn); }
.kpi--danger .kpi__value { color: var(--danger); }

/* --- 23-row.css --- */
/* ============================================================================
   Строка результата. ОДИН компонент вместо четырёх гридов из макетов:
   аналоги, кроссы, применяемость, результаты поиска. Различия задаются
   grid-template-areas, а не новой сеткой.
   На <=900 колонки перестраиваются, на <=720 строка становится блоком
   и получает подписи из data-label: горизонтальный скролл таблицы на
   телефоне это гарантированный отказ от чтения.
   ============================================================================ */

.rows {
  background: var(--surface);
  border: var(--bw) solid var(--line);
}

/* Шесть колонок: четыре текстовых, ячейка сохранения и переход на карточку.
   Шестая появилась вместе с кнопками "В список" и "В избранное" в выдаче, в
   таблице аналогов и в подборе по технике: до них собрать список подбора можно
   было практически только с карточки товара (см. macros/product.save_actions).
   Ширина ячейки сохранения фиксирована и равна сумме её содержимого: кнопка
   плюс зазор 8 плюс квадратная звёздочка 44. Дай ей долю fr, и на выдаче с
   длинным номером она отберёт ширину у самого номера. */
.rows__head,
.row-item {
  display: grid;
  grid-template-columns:
    minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, .8fr) 168px 132px;
  gap: var(--sp-16);
  align-items: center;
  padding: var(--sp-16) var(--sp-24);
}

.rows__head {
  padding-block: var(--sp-12);
  border-bottom: var(--bw) solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-3xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}

.row-item {
  border-bottom: var(--bw) solid var(--line-soft);
  transition: background-color var(--dur) var(--ease);
}
.row-item:last-child { border-bottom: 0; }
.row-item:hover { background: var(--surface-hover); }

.row-item__pn {
  font-family: var(--font-mono);
  font-size: var(--fs-xl);
  font-weight: var(--fw-mono-med);
  overflow-wrap: anywhere;
}
.row-item__brand { font-size: var(--fs-lg); color: var(--ink-2); }
.row-item__stock { font-family: var(--font-mono); font-size: var(--fs-sm); }
.row-item__price {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-tight);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.row-item__action { justify-self: end; }
.row-item__action .btn { width: 132px; }

/* Происхождение позиции ("аналог для WIX WL7296") в подборе по технике.
   Живёт ВНУТРИ ячейки номера, а не отдельной колонкой: колонок уже шесть,
   седьмая не влезает в 168px + 132px, а на <=720 строка становится блоком, и
   колонка всё равно превратилась бы в эту же подпись. Поэтому на узких
   экранах правило не нужно вовсе: подпись едет вместе с номером.
   nowrap здесь запрещён: "WIX WL7296 и ещё 2 номера" в 320 пикселей не лезет. */
.row-item__via {
  display: block;
  margin-top: var(--sp-6);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-sm);
  line-height: var(--lh-snug);
  color: var(--ink-3);
}

/* Разделитель подгрупп внутри одной таблицы: точная применяемость и аналоги
   по кроссу. Это блочный ребёнок .rows, а не строка грида: гридами являются
   .rows__head и .row-item по отдельности, сам .rows только рамка, поэтому
   grid-column здесь не нужен и на <=720 ничего не разъезжается. */
.rows__split {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-10);
  padding: var(--sp-12) var(--sp-24);
  background: var(--surface-2);
  border-bottom: var(--bw) solid var(--line);
}
.rows__split-title {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink);
}
.rows__split-note { font-size: var(--fs-sm); color: var(--ink-3); }

/* Компактный вариант без цены и кнопки: кроссы и применяемость. */
.row-item--compact,
.rows__head--compact {
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr) minmax(0, 1fr);
}

@media (max-width: 900px) {
  .rows__head { display: none; }
  .row-item {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: var(--sp-8);
  }
  .row-item__price { text-align: left; }
  /* Текстовых ячеек в левой колонке ровно четыре и у выдачи, и у аналогов
     (номер, бренд, тип или цена, наличие). Поэтому переход занимает первые
     две строки правой колонки, а сохранение встаёт под ним в третью: сложи
     их в одну клетку, и кнопки наложились бы друг на друга. */
  .row-item__action { grid-column: 2; grid-row: 1 / span 2; align-self: center; }
  .row-item__save { grid-column: 2; grid-row: 3 / span 2; align-self: center; }
}

@media (max-width: 720px) {
  .row-item { display: block; padding: var(--sp-16) var(--sp-16); }
  .row-item > * + * { margin-top: var(--sp-6); }
  /* Подпись колонки возвращается к значению: без шапки таблицы
     "24" и "Под заказ" неразличимы. */
  .row-item > [data-label]::before {
    content: attr(data-label) ": ";
    font-family: var(--font-mono);
    font-size: var(--fs-mono-3xs);
    letter-spacing: var(--ls-mono);
    text-transform: uppercase;
    color: var(--ink-3);
  }
  .row-item__action { margin-top: var(--sp-12); }
  .row-item__action .btn { width: 100%; }
  /* Строка стала блоком, колонок нет, и держать ими нечего: пустая ячейка
     сохранения у позиции без item_id добавила бы только отступ. */
  .row-item__save { margin-top: var(--sp-12); }
  .row-item__save:empty { display: none; }
  /* Разделитель подгрупп выравнивается по строке, а она здесь уже на 16. */
  .rows__split { padding-inline: var(--sp-16); }
}

/* --- 24-save.css --- */
/* ============================================================================
   СОХРАНЕНИЕ ПОЗИЦИИ: «в список подбора» и «в избранное».

   Одна пара кнопок на четыре места (строка выдачи поиска, плитка каталога,
   таблица аналогов на карточке, подбор по технике) и потому свой слой, а не
   правило внутри 23-row.css или 41-catalog.css: разметку кнопок отдаёт один
   макрос (macros/product.save_actions), и оформление у него обязано быть тоже
   одно. Две копии этих правил разъедутся на первой же правке высоты.

   Кнопки НЕ зависят от наличия цены: покупка требует цены, добавление в
   список нет. До этого слоя кнопка стояла под флагом has_priced, то есть её
   не было практически никогда: цена заведена у 5 позиций из 146 322.

   Ширину контейнера задаёт вызывающий (колонка сетки строки, подвал плитки,
   правая колонка карточки), здесь только раскладка внутри неё.
   ============================================================================ */

.save-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
}

/* Кнопка списка забирает всю свободную ширину, звёздочка остаётся квадратной.
   min-width: 0 обязателен: у .btn стоит white-space: nowrap, и без него флекс
   отказывается сжимать форму ниже ширины надписи, выдавливая звёздочку за
   край ячейки. */
.save-actions__add { flex: 1 1 auto; min-width: 0; }
.save-actions__add .btn { width: 100%; }
.save-actions__fav { flex: 0 0 auto; }

/* Звёздочка это квадрат по высоте своей кнопки, а не кнопка с текстом: подпись
   у неё только для скринридера. Высоту даёт модификатор размера (.btn--sm в
   списках, .btn--md в карточке), ширину из неё выводит aspect-ratio: одна
   запись размера вместо двух расходящихся. Обе высоты, 44 и 48, не меньше
   --tap-min, то есть зона нажатия на телефоне выдержана. */
.save-actions__star {
  aspect-ratio: 1;
  padding: 0;
}

/* Слот в подвале плитки каталога. Одинаков у всех плиток страницы, поэтому
   резервировать его флагом не нужно: цены соседних плиток стоят на одной
   линии сами. min-height остаётся ради позиции без item_id: у неё слот пуст,
   и без него подвал такой плитки был бы ниже соседних. Сам подвал
   (.ptile__foot) описан в 56-price.css вместе с ценой. */
.ptile__save { min-height: var(--h-sm); }

/* --- 25-misc.css --- */
/* ============================================================================
   Мелкие компоненты: крошки, заголовок секции, чип, алерт, плашка-заметка,
   спрайт.
   ============================================================================ */

/* --- КРОШКИ. Разделитель рисуется через ::before, потому что текстовый "/"
       скринридер читает вслух в каждом уровне. ------------------------- */
.crumbs { min-width: 0; }
.crumbs__list {
  display: flex;
  align-items: center;
  gap: var(--sp-10);
  flex-wrap: wrap;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}
.crumbs__item { display: flex; align-items: center; gap: var(--sp-10); }
.crumbs__item + .crumbs__item::before { content: "/"; color: var(--line-strong); }
.crumbs__link { color: var(--ink-3); }
.crumbs__link:hover { color: var(--accent-text); }
.crumbs__item[aria-current] { color: var(--ink); }

/* --- ЗАГОЛОВОК СЕКЦИИ ------------------------------------------------- */
.section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-40);
  flex-wrap: wrap;
}
.section-head__title {
  margin-top: var(--sp-16);
  font-size: var(--fs-d4);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d3);
}
.section-head + * { margin-top: var(--sp-28); }

/* --- ЧИП. Номер кросса, фильтр, метка. -------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-8);
  padding: var(--sp-10) var(--sp-12);
  background: var(--surface-2);
  border: var(--bw) solid var(--line);
  color: var(--ink-2);
  font-size: var(--fs-md);
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
a.chip:hover { border-color: var(--accent-line); color: var(--accent-text); }
.chip--mono { font-family: var(--font-mono); font-size: var(--fs-mono-sm); }
.chip--active { background: var(--accent); border-color: var(--accent); color: var(--ink-on-accent); }
.chip__count { color: var(--ink-3); font-family: var(--font-mono); font-size: var(--fs-mono-2xs); }
.chip--active .chip__count { color: var(--ink-on-accent); }

.chips { display: flex; flex-wrap: wrap; gap: var(--sp-10); }

/* --- АЛЕРТ. Полоса слева, без иконки-заливки: цвет не единственный
       носитель смысла, текст всегда начинается со слова состояния. ----- */
.alert {
  display: flex;
  gap: var(--sp-12);
  padding: var(--sp-16) var(--sp-20);
  background: var(--surface-2);
  border-left: 3px solid var(--line-strong);
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
  color: var(--ink-2);
}
.alert--info   { border-left-color: var(--accent-2); }
.alert--ok     { border-left-color: var(--ok); }
.alert--warn   { border-left-color: var(--accent-line); }
.alert--danger { border-left-color: var(--danger); }
.alert__icon { width: 20px; height: 20px; margin-top: 2px; color: var(--ink-3); }

/* Подпись под заглушкой изображения и под таблицей характеристик донора. */
.note {
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--ink-3);
}

/* Метка "Оригинал" рядом с брендом. */
.badge {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-xs);
  letter-spacing: var(--ls-mono);
  text-transform: uppercase;
  color: var(--accent-text);
}

/* Спрайт лежит первым элементом body и не должен занимать место.
   display: none убил бы <use> в Safari, поэтому прячем размерами. */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* --- РАЗВОРОТ ДЛИННОГО СПИСКА (<details>), применяемость и кроссы. ----- */
.more { margin-top: var(--sp-16); }
.more__summary {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-8);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-xs);
  letter-spacing: var(--ls-mono);
  text-transform: uppercase;
  color: var(--accent-text);
}
.more__summary::after { content: "+"; font-size: var(--fs-2xl); line-height: 1; }
.more[open] .more__summary::after { content: "−"; }
.more__body { margin-top: var(--sp-16); }

/* --- ПАГИНАЦИЯ. Один компонент на весь сайт: полные списки кроссов и
       применяемости на карточке, выдача поиска, листинг категории.
       Раньше их было три (.pager в 30-product, .srch-pages в 40-search,
       .pages в 41-catalog), причём .pages__step был побайтовой копией
       .pager__link, а текущая страница выглядела на /search и на
       /catalog/oil по-разному. Разметку рисует макрос site.pager. -------- */
.pager {
  display: flex;
  align-items: center;
  gap: var(--sp-24);
  flex-wrap: wrap;
  margin-top: var(--sp-32);
}
/* Внутри .stack расстояние держит gap грида, и собственный отступ сложился бы
   с ним (полные списки кроссов и применяемости на карточке товара). */
.stack > .pager { margin-top: 0; }
/* "Назад" и "Дальше". Это переходы, а не номера, поэтому оформлены как
   ссылка-подчёркивание, а не как цель-квадрат. */
.pager__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding-bottom: var(--sp-4);
  border-bottom: var(--bw) solid var(--accent-line);
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-xs);
  letter-spacing: var(--ls-mono);
  text-transform: uppercase;
}
.pager__link:hover { color: var(--ink); border-bottom-color: currentColor; }
.pager__pos { font-size: var(--fs-mono-sm); color: var(--ink-3); }

.pager__list {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  list-style: none;
}
.pager__num,
.pager__gap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-min);
  min-height: var(--tap-min);
  padding: 0 var(--sp-10);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}
a.pager__num { border: var(--bw) solid var(--line); color: var(--ink-2); }
a.pager__num:hover { border-color: var(--accent-line); color: var(--accent-text); }
/* Текущая страница залита акцентом, как .chip--active: одно состояние
   "выбрано" на весь сайт, а не рамка здесь и заливка там. */
.pager__num.is-active {
  background: var(--accent);
  border: var(--bw) solid var(--accent);
  color: var(--ink-on-accent);
  font-weight: var(--fw-mono-med);
}

/* Многоточие декоративно (aria-hidden), и на телефоне оно занимает место
   целой цели нажатия. Номера при этом остаются все. */
@media (max-width: 480px) {
  .pager__gap { min-width: var(--sp-12); padding: 0; }
}

/* --- 30-product.css --- */
/* ============================================================================
   Карточка товара: цена, блок покупки, заглушка изображения, характеристики,
   кросс-референс, применяемость, пагинация полных списков.
   Слой подхватывается сборщиком сам: имя начинается с числового префикса.
   ============================================================================ */

/* --- ЦЕНА.
       Цены нет ни у одной из 146 322 позиций, поэтому "Цена по запросу" это
       не заглушка, а основное состояние блока. Оно занимает ТУ ЖЕ высоту:
       min-height держит кнопку заявки на месте, когда у части позиций цена
       появится, а у соседних ещё нет. Без этого страница дёргается на глазах
       у покупателя ровно в момент решения. ------------------------------- */
.price {
  min-height: 78px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.price__value {
  font-size: var(--fs-price);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d3);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* Запрос цены это не цена: тот же блок, ступень мельче, без знака валюты.
   Кегль цены на строке "Цена по запросу" выглядит как ценник, которого нет. */
.price--empty .price__value {
  font-size: var(--fs-d5);
  letter-spacing: var(--ls-d4);
  color: var(--ink-2);
}

.price__old {
  margin-top: var(--sp-4);
  font-size: var(--fs-2xl);
  color: var(--ink-3);
  text-decoration: line-through;
}
.price__note { margin-top: var(--sp-6); font-size: var(--fs-sm); color: var(--ink-3); }

@media (max-width: 720px) { .price { min-height: 64px; } }

/* --- БЛОК ПОКУПКИ. Правая колонка сплита. ------------------------------ */
.buy { display: grid; gap: var(--sp-24); }
.buy__facts { font-size: var(--fs-md); }
.buy__terms { margin-top: var(--sp-4); }

/* Наличие набирается моно: "24 шт" и "Под заказ" стоят в одной колонке
   таблиц и списков, и разнобой начертаний читается как разнобой данных. */
.stock {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  font-variant-numeric: tabular-nums;
}

/* --- ЗАГЛУШКА ИЗОБРАЖЕНИЯ.
       aspect-ratio на контейнере плюс width/height на <img> дают нулевой CLS:
       место под картинку занято до её загрузки. -------------------------- */
.gal__frame {
  aspect-ratio: 1 / 1;
  display: grid;
  place-items: center;
  padding: var(--sp-40);
  background: var(--surface-2);
  border: var(--bw) solid var(--line);
}
.gal__img {
  width: 100%;
  max-width: 420px;
  height: auto;
  color: var(--ink-3);
}
.gal__note { margin-top: var(--sp-12); }

@media (max-width: 720px) { .gal__frame { padding: var(--sp-24); } }

/* --- ХАРАКТЕРИСТИКИ.
       Две колонки на широком экране: у фильтра их бывает под тридцать, и
       одной колонкой блок вытесняет кроссы за два экрана. ---------------- */
.specs__note { margin-bottom: var(--sp-16); }
/* Две колонки сеткой, а не свойством columns: .deflist это grid-контейнер,
   а на нём многоколоночная вёрстка не работает вовсе (проверено в браузере,
   таблица оставалась одноколоночной). */
.specs .deflist {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: var(--grid-gap-lg);
}
@media (max-width: 900px) { .specs .deflist { grid-template-columns: minmax(0, 1fr); } }

.prod-desc__body { font-size: var(--fs-lg); line-height: var(--lh-body); color: var(--ink-2); }
.prod-desc__body > * + * { margin-top: var(--sp-12); }
.prod-desc .eyebrow { margin-bottom: var(--sp-12); }

/* --- КРОСС-РЕФЕРЕНС.
       Свёртка по бренду: строка бренда, под ней его номера чипами.
       Сетка auto-fit, чтобы 12 брендов легли в три колонки на десктопе и в
       одну на телефоне без единого @media на саму сетку. ----------------- */
.cross {
  display: grid;
  gap: var(--grid-gap-lg);
  grid-template-columns: repeat(auto-fit, minmax(var(--col-tile), 1fr));
}
.cross__brand {
  padding-top: var(--sp-16);
  border-top: var(--bw-2) solid var(--line-strong);
}
.cross__name {
  display: flex;
  align-items: baseline;
  gap: var(--sp-8);
  flex-wrap: wrap;
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
}
.cross__count {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  color: var(--ink-3);
}
.cross__nums { margin-top: var(--sp-12); }
.cross__more { color: var(--ink-3); border-style: dashed; }

/* --- ПРИМЕНЯЕМОСТЬ. Марка плюс до шести моделей строкой. --------------- */
.fit {
  display: grid;
  gap: var(--grid-gap);
  grid-template-columns: repeat(auto-fit, minmax(var(--col-tile), 1fr));
}
.fit__make {
  padding: var(--sp-16) var(--sp-20);
  background: var(--surface);
  border: var(--bw) solid var(--line);
}
.fit__name {
  display: flex;
  align-items: baseline;
  gap: var(--sp-8);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
}
.fit__n {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  color: var(--ink-3);
}
.fit__models {
  margin-top: var(--sp-8);
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
  color: var(--ink-2);
}
.fit__source { margin-bottom: var(--sp-16); }

/* Цена аналога, которой нет: тем же местом, но не тем же весом, иначе
   "по запросу" в колонке цены читается как цена. */
.row-item__request {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  font-weight: var(--fw-mono);
  color: var(--ink-3);
}

/* Пагинация полных списков кроссов и применяемости переехала в 25-misc.css:
   тот же компонент нужен выдаче поиска и листингу категории. */

/* --- 40-search.css --- */
/* ============================================================================
   Поиск по номеру. Слой 40.

   Своего здесь ровно то, чего нет в общих слоях: строка поиска с подсказкой,
   боковой фасет, полоса уточнения типа и номерная пагинация. Строка выдачи
   переиспользует .rows / .row-item из 23-row.css целиком, поэтому на телефоне
   она сама превращается в блок с подписями из data-label.

   Все имена с префиксом srch-: фасеты понадобятся и каталогу, и брендам, и
   короткое .facet почти наверняка столкнулось бы с чужим слоем.
   ============================================================================ */

/* --- ГЕРОЙ. Тёмный, как в макете. Поле поиска внутри тёмной секции светлое:
       ради этого случая семантика в токенах и объявлена парой селекторов. -- */
.srch-hero__eyebrow { margin-top: var(--sp-28); }
.srch-hero .hero__title { margin-top: var(--sp-12); }

/* --- СТРОКА ПОИСКА -----------------------------------------------------
       Грид, а не флекс: подсказка (hint) занимает вторую строку целиком, а
       на узком экране кнопка уходит под поле, не ломая ширину поля. ------ */
.srch-box {
  margin-top: var(--sp-24);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-12);
  align-items: start;
}
.srch-box__field { position: relative; min-width: 0; }

.srch-box__input {
  width: 100%;
  height: var(--h-xl);
  padding: var(--pad-field-lg);
  /* Светлое поле на тёмном фоне: контраст текста 16.9:1. */
  background: var(--c-paper-100);
  border: var(--bw) solid var(--c-line-300);
  border-radius: var(--radius);
  color: var(--c-graphite-800);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-xl);
  font-weight: var(--fw-mono-med);
  letter-spacing: var(--ls-mono-sm);
  transition: border-color var(--dur) var(--ease);
}
.srch-box__input::placeholder {
  font-family: var(--font-sans);
  font-weight: var(--fw-regular);
  letter-spacing: normal;
  color: var(--c-steel-400);
}
.srch-box__input:hover { border-color: var(--c-line-400); }
.srch-box__input:focus { border-color: var(--accent); }
/* Крестик очистки в type=search Safari рисует поверх текста номера. */
.srch-box__input::-webkit-search-decoration,
.srch-box__input::-webkit-search-cancel-button { appearance: none; }

.srch-box__hint {
  grid-column: 1 / -1;
  max-width: 76ch;
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
  color: var(--ink-3);
}

@media (max-width: 720px) {
  .srch-box { grid-template-columns: minmax(0, 1fr); }
}

/* --- ПОДСКАЗКА. Рисуется скриптом, поэтому в разметке контейнер пуст.
       Без JS его нет вовсе, и hidden остаётся на месте. ----------------- */
.srch-box__sug {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: var(--z-dropdown);
  max-height: 380px;
  overflow-y: auto;
  background: var(--c-white);
  border: var(--bw) solid var(--c-line-300);
  box-shadow: var(--shadow-pop);
}
.srch-box__sug[hidden] { display: none; }

.srch-sug__item {
  display: flex;
  align-items: baseline;
  gap: var(--sp-12);
  width: 100%;
  padding: var(--sp-12) var(--sp-20);
  border-bottom: var(--bw) solid var(--c-line-100);
  color: var(--c-graphite-800);
  text-align: left;
  text-decoration: none;
}
.srch-sug__item:last-child { border-bottom: 0; }
/* Подсветка мышью и стрелками это одно и то же состояние: два разных фона
   на одном списке читаются как две разные позиции. */
.srch-sug__item:hover,
.srch-sug__item.is-current { background: var(--c-paper-50); color: var(--c-graphite-800); }
.srch-sug__pn {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-md);
  font-weight: var(--fw-mono-med);
  white-space: nowrap;
}
.srch-sug__meta {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-md);
  color: var(--c-steel-500);
}

/* --- ПОЛОСА УТОЧНЕНИЯ ТИПА -------------------------------------------- */
.srch-refine {
  padding: var(--sp-24);
  background: var(--surface);
  border: var(--bw) solid var(--line);
  border-left: 3px solid var(--accent-line);
}
.srch-refine__text {
  margin-top: var(--sp-8);
  max-width: 76ch;
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
  color: var(--ink-2);
}
.srch-refine__chips { margin-top: var(--sp-16); }

/* --- ШАПКА ВЫДАЧИ ------------------------------------------------------ */
.srch-bar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-24);
  flex-wrap: wrap;
  margin-bottom: var(--sp-24);
}
.srch-bar__found {
  display: flex;
  align-items: baseline;
  gap: var(--sp-10);
  flex-wrap: wrap;
  font-size: var(--fs-2xl);
  color: var(--ink-2);
}
.srch-bar__n {
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-d4);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.srch-bar__split { font-size: var(--fs-mono-sm); color: var(--ink-3); }

.srch-sort { display: flex; align-items: center; gap: var(--sp-12); flex-wrap: wrap; }

/* Переключатель "только с ценой". Стоит НАД списком и отдельной строкой, а не
   среди фасетов бренда сбоку: цена сейчас у единиц позиций, а когда её
   привезёт обмен с 1С, это станет первым вопросом покупателя.
   Оговорка про скрытые позиции появляется только при включённом фильтре: без
   неё короткий список выглядит как отсутствие товара, а не как отбор. */
.srch-only {
  display: flex;
  align-items: center;
  gap: var(--sp-12);
  flex-wrap: wrap;
  margin-bottom: var(--sp-20);
}
.srch-only__note { max-width: 70ch; }

/* --- РАСКЛАДКА: фасеты слева, выдача справа ----------------------------
       В РАЗМЕТКЕ выдача идёт ПЕРВОЙ, а фасеты вторыми, и налево их ставит
       grid-template-areas. Причина в порядке чтения: человек пришёл с
       номером и ждёт позиции, поэтому и скринридер, и Tab, и телефон в одну
       колонку обязаны дать сначала результат, а потом тридцать брендов
       высотой в экран. Расхождение с визуальным порядком остаётся только на
       десктопе и только по горизонтали. --------------------------------- */
.srch-layout {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  grid-template-areas: "aside main";
  gap: var(--grid-gap-lg);
  align-items: start;
}
.srch-aside { grid-area: aside; }
.srch-main  { grid-area: main; min-width: 0; }

@media (max-width: 1180px) {
  .srch-layout { grid-template-columns: 240px minmax(0, 1fr); }
}
@media (max-width: 900px) {
  .srch-layout {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "main" "aside";
  }
  .srch-facet__list { max-height: 260px; }
}

.srch-facet {
  background: var(--surface);
  border: var(--bw) solid var(--line);
}
.srch-facet__head {
  padding: var(--sp-20) var(--sp-24);
  border-bottom: var(--bw) solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}
.srch-facet__group { padding: var(--sp-20) var(--sp-24); }
.srch-facet__group + .srch-facet__group { border-top: var(--bw) solid var(--line-soft); }

/* Список брендов ограничен по высоте: у номера-хаба их бывают сотни, и
   боковая колонка длиной в три экрана уводит выдачу вниз. */
.srch-facet__list {
  margin-top: var(--sp-16);
  max-height: 420px;
  overflow-y: auto;
  display: grid;
  gap: var(--sp-2);
}
.srch-facet__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-12);
  /* 40px это не 44: элемент списка, а не самостоятельная кнопка, и при
     44px десять брендов занимают полэкрана. Промах здесь стоит одного
     лишнего клика, а не потерянной заявки. */
  min-height: 40px;
  padding: var(--sp-6) var(--sp-8);
  margin-inline: calc(-1 * var(--sp-8));
  color: var(--ink);
  font-size: var(--fs-lg);
  font-weight: var(--fw-semi);
  text-decoration: none;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.srch-facet__link:hover { background: var(--surface-hover); color: var(--accent-text); }
.srch-facet__link.is-active { color: var(--accent-text); }
/* Выбранный бренд помечен не только цветом: цвет не единственный носитель
   смысла, и на монохромной печати метка обязана остаться. */
.srch-facet__link.is-active .srch-facet__name::before {
  content: "×\00a0";
  color: var(--accent-line);
}
.srch-facet__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.srch-facet__count { flex: none; font-size: var(--fs-mono-2xs); color: var(--ink-3); }
.srch-facet__rest { margin-top: var(--sp-12); }

/* --- СТРОКА РЕЗУЛЬТАТА. Своего минимум: сетку и адаптив даёт .row-item. -- */
.srch-type { font-size: var(--fs-md); color: var(--ink-3); }

/* Метка "подходит вместо ВАШ_НОМЕР". Второй строкой под номером, тем же
   моно-начертанием, что метка "Оригинал" на карточке товара. */
.srch-via {
  display: block;
  margin-top: var(--sp-6);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-sm);
  line-height: var(--lh-snug);
  color: var(--ink-3);
}
.srch-via--direct { color: var(--ok); text-transform: uppercase; letter-spacing: var(--ls-mono); }

.srch-off {
  margin-left: var(--sp-8);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono);
  text-transform: uppercase;
  color: var(--warn);
}

.srch-cap { margin-top: var(--sp-16); }

/* --- ПУСТАЯ ВЫДАЧА ----------------------------------------------------- */
.srch-empty {
  padding: var(--card-pad);
  background: var(--surface);
  border: var(--bw) solid var(--line);
}
.srch-empty__text {
  margin-top: var(--sp-12);
  max-width: 68ch;
  font-size: var(--fs-xl);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.srch-empty__action { margin-top: var(--sp-24); }

/* Пагинация выдачи это общий компонент .pager из 25-misc.css. */

/* --- 41-catalog.css --- */
/* ============================================================================
   Каталог и категория: плитка категории, вход в каталог, фасеты, сетка
   товаров, номерная пагинация.
   Слой подхватывается сборщиком сам: имя начинается с числового префикса.
   Все цвета через семантику из 00-tokens.css, своих значений здесь нет.
   ============================================================================ */

/* --- ПЛИТКИ КАТЕГОРИЙ на /catalog.
       Снимок раздела лежит на подложке surface-2 и занимает фиксированную
       долю плитки: aspect-ratio даёт нулевой CLS, а высота в пикселях из
       макета ломалась бы на узких колонках. ------------------------------- */
.cat-grid {
  display: grid;
  gap: var(--grid-gap);
  grid-template-columns: repeat(auto-fill, minmax(var(--col-tile), 1fr));
}

/* minmax(0, 1fr), а не колонка по умолчанию. Неявная колонка грида это auto,
   то есть "не уже min-content и не шире max-content". У .cat-tile__frame
   задан aspect-ratio, и когда ряд сетки растягивает плитку по высоте соседа,
   max-content по ширине у такой рамки становится высота * 16 / 9. Колонка
   берёт эту ширину, дети вылезают за рамку плитки, и страница получает
   горизонтальную прокрутку на ровном месте. Замерено: 381px внутри плитки
   в 328px. Та же ловушка ждёт .ptile и .cat-entry, поэтому явная колонка
   стоит у всех трёх. */
.cat-tile {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  height: 100%;
  background: var(--surface);
  border: var(--bw) solid var(--line);
  color: var(--ink);
  transition: border-color var(--dur) var(--ease);
}
.cat-tile:hover { border-color: var(--ink); }

.cat-tile__frame {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  background: var(--surface-2);
  border-bottom: var(--bw) solid var(--line);
}
/* Заглушка одна на девять типов и по смыслу декоративна: размер держим
   скромным, иначе она читается как фотография товара. Снимок раздела наоборот
   заполняет рамку целиком: cover, потому что 16/9 фотографии и 16/9 рамки
   совпадают только на широкой колонке. */
.cat-tile__img { width: 96px; height: 96px; opacity: .9; }
.cat-tile__img--photo { width: 100%; height: 100%; object-fit: cover; opacity: 1; }

.cat-tile__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-10);
  padding: var(--card-pad-sm);
}
.cat-tile__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-12);
  min-width: 0;
}
.cat-tile__title {
  min-width: 0;
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
}
.cat-tile__count {
  flex: none;
  font-size: var(--fs-mono-sm);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.cat-tile__text { font-size: var(--fs-md); line-height: var(--lh-body); color: var(--ink-3); }

/* --- ВХОД В КАТАЛОГ: подбор по технике, бренды, поиск по номеру. -------- */
.cat-entries {
  display: grid;
  gap: var(--grid-gap);
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.cat-entry {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-10);
  align-content: start;
  height: 100%;
  padding: var(--card-pad);
  background: var(--surface);
  border: var(--bw) solid var(--line);
  color: var(--ink);
  transition: border-color var(--dur) var(--ease);
}
.cat-entry:hover { border-color: var(--ink); }
.cat-entry__title {
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
}
.cat-entry__text { font-size: var(--fs-md); line-height: var(--lh-body); color: var(--ink-3); }

/* --- ПРИЗЫВ. Две кнопки справа, на узком экране под заголовком. --------- */
.cat-cta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--grid-gap-lg);
  align-items: center;
}
.cat-cta__title {
  margin-top: var(--sp-12);
  font-size: var(--fs-d4);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d3);
}
.cat-cta__actions { display: flex; flex-wrap: wrap; gap: var(--sp-12); justify-content: flex-end; }
@media (max-width: 900px) {
  .cat-cta__actions { justify-content: flex-start; }
}

/* --- ТЕКСТОВЫЙ БЛОК. Ширина строки ограничена по читаемости, а не по сетке. */
.cat-about { max-width: 900px; }
.cat-about--inline { margin-top: var(--section-gap); }
.cat-about__text {
  margin-top: var(--sp-16);
  font-size: var(--fs-lg);
  line-height: var(--lh-loose);
  color: var(--ink-2);
}

/* --- РАСКЛАДКА КАТЕГОРИИ.
       Панель фасетов слева фиксированной колонкой, список тянется.
       На <=1180 фасеты уезжают наверх во всю ширину: боковая колонка в 300px
       на планшете съедает половину сетки товаров. -------------------------- */
.cat-layout {
  display: grid;
  grid-template-columns: 300px minmax(0, 1fr);
  gap: var(--grid-gap-lg);
  align-items: start;
}
@media (max-width: 1180px) {
  .cat-layout { grid-template-columns: minmax(0, 1fr); }
}

/* Липкая панель только там, где она помещается по высоте. */
.cat-side { position: sticky; top: calc(var(--h-header) + var(--sp-16)); }
@media (max-width: 1180px) { .cat-side { position: static; } }

/* --- ФАСЕТЫ ------------------------------------------------------------ */
/* Зона нажатия 44px без изменения высоты строки: отступы растят коробку,
   отрицательные поля возвращают её вклад в раскладку обратно. Иначе ссылка
   "Сбросить" это цель в 20px, то есть промах пальцем на телефоне. */
.facet__reset {
  font-size: var(--fs-sm);
  padding-block: var(--sp-12);
  margin-block: calc(var(--sp-12) * -1);
}

.facet__group { padding: var(--sp-20) var(--sp-28); border-bottom: var(--bw) solid var(--line); }
.facet__group:last-child { border-bottom: 0; }
.facet__title { margin-bottom: var(--sp-16); }

.facet__list { display: grid; gap: var(--sp-2); }
/* Строка фасета это ссылка на всю ширину: цель нажатия 44px по высоте,
   а не подчёркнутое слово в 15px, как было в макете. */
.facet__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-12);
  min-height: var(--tap-min);
  padding: var(--sp-6) var(--sp-8);
  margin-inline: calc(var(--sp-8) * -1);
  color: var(--ink-2);
  font-size: var(--fs-lg);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.facet__link:hover { background: var(--surface-2); color: var(--ink); }
.facet__link.is-active { color: var(--accent-text); font-weight: var(--fw-semi); }
.facet__name { overflow-wrap: anywhere; }
.facet__count {
  flex: none;
  font-size: var(--fs-mono-2xs);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.facet__link.is-active .facet__count { color: var(--accent-text); }

.facet__form { display: grid; gap: var(--sp-16); }
.facet__actions { margin-top: var(--sp-4); }
.facet__note { margin-top: var(--sp-16); }

/* Ниже 1180 панель перестаёт быть колонкой и ложится поперёк, над списком.
   Столбиком она там занимает около 950px высоты, то есть покупатель
   прокручивает целый экран фильтров, прежде чем увидит первый фильтр как
   товар. Раскладка в несколько колонок сжимает её примерно вчетверо и не
   требует ни JS, ни другой разметки. */
@media (max-width: 1180px) {
  .facet__list { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--sp-4); }
  /* В колонке счётчик прижат к правому краю строки и стоит под счётчиком
     соседа. В несколько колонок то же выравнивание уводит число к началу
     СЛЕДУЮЩЕГО бренда, и "14 203 HiFi Filter" читается как счётчик HiFi.
     Поэтому в этой раскладке число идёт сразу за своим названием. */
  .facet__link { justify-content: flex-start; gap: var(--sp-8); }
  .facet__name { flex: none; }
  .facet__form { grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); }
  .facet__actions { align-self: end; }
  .facet__note { max-width: 70ch; }
}
@media (max-width: 480px) {
  .facet__list { grid-template-columns: minmax(0, 1fr); }
}

/* --- ПАНЕЛЬ НАД СПИСКОМ ------------------------------------------------ */
.cat-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-24);
  flex-wrap: wrap;
  padding-bottom: var(--sp-16);
  border-bottom: var(--bw) solid var(--line);
}
.cat-bar__count {
  font-size: var(--fs-mono-sm);
  letter-spacing: var(--ls-mono-sm);
  text-transform: uppercase;
  color: var(--ink-3);
}
.cat-sort { display: flex; align-items: center; gap: var(--sp-16); flex-wrap: wrap; }
.cat-sort__link {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  color: var(--ink-2);
  font-size: var(--fs-md);
}
.cat-sort__link:hover { color: var(--accent-text); }
.cat-sort__link.is-active {
  color: var(--ink);
  font-weight: var(--fw-semi);
  box-shadow: inset 0 -1px 0 var(--accent-line);
}

/* Выбранные фильтры строкой. Крестик декоративен, смысл несёт .vh-подпись. */
.cat-active { margin-top: var(--sp-16); }
.cat-active__x { color: var(--ink-3); }

/* --- СЕТКА ТОВАРОВ ----------------------------------------------------- */
.ptiles {
  margin-top: var(--sp-24);
  display: grid;
  gap: var(--grid-gap);
  grid-template-columns: repeat(auto-fill, minmax(var(--col-product), 1fr));
}

/* Плитка это блок, а НЕ ссылка. Раньше ссылкой был сам .ptile, но с
   появлением кнопки "В корзину" так больше нельзя: <button> внутри <form>
   внутри <a> это невалидная разметка, по которой браузеры расходятся, и клик
   по кнопке уходил бы на карточку товара. Теперь ссылка накрывает фотографию
   и номер (.ptile__link), а подвал с ценой и кнопкой лежит рядом с ней.

   Дорожки 1fr auto: ссылка забирает свободную высоту, подвал прижат к низу.
   Поэтому кнопки соседних плиток стоят на одной линии, даже когда номер у
   одной лёг в две строки, а у другой в одну. */
.ptile {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: 1fr auto;
  height: 100%;
  background: var(--surface);
  border: var(--bw) solid var(--line);
  color: var(--ink);
  transition: border-color var(--dur) var(--ease);
}
.ptile:hover { border-color: var(--ink); }

.ptile__link {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr;
}

.ptile__frame {
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  background: var(--surface-2);
  border-bottom: var(--bw) solid var(--line);
}
.ptile__img { width: 88px; height: 88px; opacity: .9; }

.ptile__body {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-6);
  align-content: start;
  padding: var(--sp-20);
}
/* Номер это одно слово, но встречаются номера в 20 знаков: рвём только
   по необходимости и только внутри плитки. */
.ptile__pn {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono-med);
  font-size: var(--fs-mono-lg);
  overflow-wrap: anywhere;
}
.ptile__brand { font-size: var(--fs-md); color: var(--ink-3); }
/* Цена, "Цена по запросу", наличие и кнопка живут в подвале плитки, и весь
   подвал описан в 56-price.css: там же лежат строка выдачи с ценой и строка
   цены в сравнении, то есть все три места, где список показывает деньги. */

/* На телефоне сетка становится столбцом, и квадратная плитка растягивает
   позицию до 380px высоты. Платить этим за заглушку, которая ничего не
   сообщает о товаре, нельзя: два десятка позиций превращаются в семь экранов
   прокрутки. Поэтому строка: маленькая заглушка слева, номер и бренд справа. */
@media (max-width: 480px) {
  .ptile__link { grid-template-rows: auto; grid-template-columns: 104px minmax(0, 1fr); }
  .ptile__frame {
    aspect-ratio: 1;
    border-bottom: 0;
    border-right: var(--bw) solid var(--line);
  }
  .ptile__img { width: 56px; height: 56px; }
  .ptile__body { padding: var(--sp-16); }
}

/* --- ПУСТОЙ СПИСОК. Оформленное состояние, а не отсутствие блока. ------- */
.cat-empty { margin-top: var(--sp-24); }
.cat-empty__title {
  font-size: var(--fs-d6);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
}
.cat-empty__text {
  margin-top: var(--sp-12);
  max-width: 60ch;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.cat-empty__action { margin-top: var(--sp-20); display: flex; flex-wrap: wrap; gap: var(--sp-12); }

/* Номерная пагинация это общий компонент .pager из 25-misc.css. */

/* --- 42-home.css --- */
/* ============================================================================
   Главная страница. Слой 42, подхватывается сборщиком по числовому префиксу.

   Здесь только то, чего нет в общих слоях. Герой, форма подбора, плитки
   разделов, сетка брендов, шаги, вопросы. Всё остальное на главной это уже
   готовые компоненты: .container, .section, .card, .chip, .kpi, .rows,
   .row-item, .deflist, .bullets, .field, .btn, .note, .eyebrow, .lede.
   Ни одного цвета мимо семантики: секции переключаются data-surface, и одни
   и те же правила работают и на графите, и на белом.
   ============================================================================ */

/* --- ГЕРОЙ -------------------------------------------------------------- */

.home-hero { padding-block: var(--sp-64) var(--sp-72); }

/* Строка маркеров над H1: город, бренды, позиции. Разделители это линии,
   а не символы: точка или слэш между числами читается скринридером вслух
   на каждом маркере. */
.home-hero__marks {
  display: flex;
  align-items: center;
  gap: var(--sp-16);
  flex-wrap: wrap;
}
.home-hero__mark { color: var(--accent-text); }
.home-hero__rule {
  flex: none;
  width: 40px;
  height: 1px;
  background: var(--line-strong);
}

/* Герой в две колонки: слева заголовок и подбор, справа трёхмерная модель
   (слой 47). Вторая колонка появляется, только когда модель действительно
   показана: пока блок помечен hidden, пустая колейка зря сжимала бы форму.
   Там, где :has не поддержан, остаётся одна колонка, а модель встаёт под
   формой во всю ширину: это рабочий запасной вид, а не поломка. */
.home-hero__grid { display: grid; }
@media (min-width: 1181px) {
  .home-hero__grid:has(.fx3d:not([hidden])) {
    grid-template-columns: minmax(0, 1fr) minmax(0, 500px);
    align-items: center;
    gap: var(--sp-64);
  }
}

.home-hero__title { max-width: 18ch; }

/* Лесенка из макета. На узком экране строки склеиваются обратно в абзац:
   три коротких блока по центру телефона выглядят как список, а не заголовок. */
.home-hero__line { display: block; }
.home-hero__line--muted { color: var(--ink-3); }
@media (max-width: 720px) {
  .home-hero__title { max-width: none; text-wrap: balance; }
  .home-hero__line { display: inline; }
}

.home-hero__lead { max-width: 56ch; }

/* --- ФОРМА ПОДБОРА В ГЕРОЕ.
       Требование заказчика: без подложки. Поля берут разметку и подписи у
       общего макроса ui.field, а плашку под ними снимает этот блок: фон
       прозрачный, рамка только снизу. Ни одного outline: none, фокус остаётся
       браузерным (:focus-visible из ресета) и виден поверх линии. ------- */
.home-find { margin-top: var(--sp-48); max-width: 860px; }

/* align-items: start обязателен. Без него колонки растягиваются по самой
   высокой, и поле с подсказкой (три ряда) вытягивает соседний селект (два):
   ряды auto делят лишнюю высоту, подпись селекта уезжает вниз на 15px, линии
   полей расходятся, а стрелка селекта (она отсчитывается от низа блока)
   оказывается на линии вместо середины поля. */
.home-find__fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  align-items: start;
  gap: var(--sp-32);
}

.home-find .field__input {
  background: transparent;
  border: 0;
  border-bottom: var(--bw) solid var(--field-line);
  border-radius: 0;
  padding-inline: 0;
}
.home-find .field__input:hover { border-bottom-color: var(--line-dashed); }
.home-find .field__input:focus { border-bottom-color: var(--field-line-focus); }

.home-find__go {
  margin-top: var(--sp-32);
  display: flex;
  align-items: center;
  gap: var(--sp-16);
  flex-wrap: wrap;
}

/* Стрелка селекта. appearance: none снят в ресете, поэтому маркер рисуем сами.
   Абсолютным позиционированием, а не ::after в потоке: .field это grid, и
   лишний элемент в нём стал бы третьей строкой под полем.
   Отсчёт идёт от НИЗА блока, поэтому field--select рассчитан на поле без
   подсказки. Появится hint, стрелка уедет на его строку. */
.field--select { position: relative; }
.field--select::after {
  content: "";
  position: absolute;
  right: var(--sp-12);
  bottom: calc(var(--h-md) / 2 - 3px);
  width: 9px;
  height: 9px;
  border-right: var(--bw-2) solid var(--ink-3);
  border-bottom: var(--bw-2) solid var(--ink-3);
  transform: rotate(45deg);
  pointer-events: none;
}
.field--select.field--lg::after { bottom: calc(var(--h-xl) / 2 - 3px); }
.field--select .field__input { padding-right: var(--sp-32); }
.home-find .field--select .field__input { padding-right: var(--sp-24); }

/* --- ДВУХКОЛОННЫЕ СЕКЦИИ (проверка номера, условия и вопросы) ----------- */
.home-split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(420px, 1fr));
  gap: var(--section-gap);
  align-items: start;
}
@media (max-width: 900px) {
  .home-split { grid-template-columns: minmax(0, 1fr); gap: var(--sp-40); }
}

.home-lookup__text { margin-top: var(--sp-24); max-width: 46ch; }

/* Кнопка стоит рядом с полем, а не под ним: подпись поля поднимает поле на
   свою высоту, поэтому выравнивание по низу, а не по центру. */
.home-lookup__form {
  margin-top: var(--sp-32);
  display: flex;
  align-items: flex-end;
  gap: var(--sp-12);
  flex-wrap: wrap;
  max-width: 520px;
}
.home-lookup__form .field { flex: 1 1 220px; }
.home-lookup__note { margin-top: var(--sp-16); }

/* --- ПЛИТКИ РАЗДЕЛОВ.
       Рамка 16/9 общая: снимок раздела заполняет её целиком, а SVG-заглушка
       (осталась у "прочих фильтров", снимка у них нет по смыслу) стоит по
       центру той же рамки. Одинаковая геометрия держит заголовки соседних
       плиток на одной линии. Изображение декоративное, alt пустой, а смысл
       несёт заголовок. -------------------------------------------------- */
.home-types { align-items: stretch; }

/* Четыре колонки, а не сколько поместится. auto-fit на мониторе шире 1520 px
   ставит пятую колонку, и семь разделов ложатся как 5 + 2: короткая вторая
   строка читается остатком, а не рядом. Четыре дают 4 + 3, две полные строки,
   и плитка на широком экране от этого крупнее, а не мельче: на 1920 рамка
   растёт с 275 до 352 px, а снимок в ней и трёхмерная модель только выигрывают.

   Ниже 1216 px правило снимается. Там четвёртая колонка становится уже 260 px,
   то есть уже минимума, заданного --col-tile, и дальше раскладку честнее
   считать auto-fit: он сам сложит три колонки, потом две. */
@media (min-width: 1216px) {
  .home-types { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* minmax(0, 1fr), а не колонка по умолчанию: у рамки появился aspect-ratio, и
   без явной колонки повторяется ровно та ловушка, что расписана над .cat-tile
   в 41-catalog.css. Замерено на плитке "прочих фильтров": рамка 347px внутри
   плитки 237px. */
.type-tile {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  gap: var(--sp-20);
  padding: var(--sp-20);
  color: inherit;
}
/* overflow: hidden не ради обрезки, а ради min-height: auto. Рамка это ячейка
   грида, и её автоматический минимум считается по содержимому: заглушка 240px
   растянула бы рамку выше пропорции и снова разъехалась с соседями. */
.type-tile__frame {
  display: grid;
  place-items: center;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--surface-2);
}
/* object-fit, а не height: auto: у рамки теперь фиксированная пропорция, и
   заглушка упирается в её высоту раньше, чем в ширину. contain сохраняет
   пропорцию рисунка, cover заполняет рамку снимком без полей. Отступ на самой
   картинке, а не на рамке: снимку он не нужен, заглушке нужен. */
.type-tile__img {
  min-width: 0;
  min-height: 0;
  width: 100%;
  height: 100%;
  padding: var(--sp-24);
  object-fit: contain;
  color: var(--ink-3);
}
.type-tile__img--photo { padding: 0; object-fit: cover; }
.type-tile__body { display: grid; align-content: start; gap: var(--sp-8); }
.type-tile__title {
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
}
.type-tile__n { font-size: var(--fs-mono-xs); color: var(--accent-text); }
.type-tile__blurb {
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
  color: var(--ink-2);
}
a.type-tile:hover .type-tile__title { color: var(--accent-text); }

/* --- МАРКИ ТЕХНИКИ. Чипы общего компонента, только крупнее: это основной
       способ входа в подбор, и 13px здесь мало для пальца. -------------- */
.home-makes .chip {
  min-height: var(--tap-min);
  padding: var(--sp-10) var(--sp-16);
  font-size: var(--fs-lg);
}
.home-makes__note { margin-top: var(--sp-20); max-width: 62ch; }

/* --- ТРИ ШАГА ----------------------------------------------------------- */
.home-steps { --col-min: 280px; counter-reset: none; }
.step {
  display: grid;
  align-content: start;
  gap: var(--sp-12);
  padding-top: var(--sp-20);
  border-top: var(--bw-2) solid var(--line-strong);
}
.step__n {
  font-size: var(--fs-mono-sm);
  letter-spacing: var(--ls-mono);
  color: var(--accent-text);
}
.step__title {
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
}
.step__text { font-size: var(--fs-lg); line-height: var(--lh-body); color: var(--ink-2); }

/* --- ДВА СЦЕНАРИЯ ------------------------------------------------------- */
.home-audience {
  display: grid;
  /* min(360px, 100%), а не голые 360px: на экране 375px колонка в контейнере
     шириной 339px не помещается, трек остаётся 360px, и страница уезжает
     вправо на 3px горизонтальной прокрутки. Тот же приём уже стоит в
     43-fitment.css и 45-cart.css. */
  grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr));
  gap: var(--grid-gap-lg);
  align-items: start;
}
.audience { display: grid; align-content: start; }
.audience__list { margin-top: var(--sp-20); }

/* --- ПОЧЕМУ НАХОДИТСЯ --------------------------------------------------- */
.home-why {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: var(--section-gap);
  align-items: start;
}
@media (max-width: 1180px) {
  .home-why { grid-template-columns: minmax(0, 1fr); gap: var(--sp-40); }
}
.home-why__text { max-width: 60ch; }
.home-why__kpis {
  display: grid;
  gap: var(--grid-gap-lg);
}
.home-why__kpis .kpi {
  padding-top: var(--sp-16);
  border-top: var(--bw) solid var(--line);
}
.home-why__kpis .kpi__hint { max-width: 46ch; }

/* --- БРЕНДЫ.
       Сетка на волосяных линиях: зазор в 1px и общий фон дают решётку без
       двойных рамок между плитками. Логотипов у нас пока нет ни одного,
       поэтому текстовая плитка это не запасной, а основной вид. --------- */
.brands {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--bw);
  background: var(--line);
  border: var(--bw) solid var(--line);
}
.brand-tile {
  display: grid;
  place-items: center;
  align-content: center;
  gap: var(--sp-10);
  min-height: 140px;
  padding: var(--sp-24);
  background: var(--surface);
  color: inherit;
  text-align: center;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.brand-tile:hover { background: var(--surface-hover); color: var(--accent-text); }
/* Высота фиксированная, а не max-height: коробка под логотип должна быть занята
   ДО загрузки картинки. Иначе плитка с логотипом и текстовая плитка (WIX, у
   которого файла нет) встают в сетке разной высоты, и на догрузке строка
   прыгает. Тот же разбор, что у .bplate в 44-brands.css. */
.brand-tile__logo {
  height: 56px;
  max-width: 100%;
  width: auto;
  object-fit: contain;
}
.brand-tile__name {
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-tight);
  overflow-wrap: anywhere;
}
.brand-tile__n { font-size: var(--fs-mono-2xs); color: var(--ink-3); }
.brand-tile:hover .brand-tile__n { color: inherit; }

/* --- УСЛОВИЯ И ВОПРОСЫ -------------------------------------------------- */
.home-docs__note { margin-top: var(--sp-16); max-width: 56ch; }

.home-faq { border-top: var(--bw) solid var(--line); }
.home-faq__item { border-bottom: var(--bw) solid var(--line); }

/* Маркер плюс и минус, как в .more: одна и та же идиома раскрытия на всём
   сайте. list-style снят в ресете. */
.home-faq__q {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-16);
  padding: var(--sp-20) 0;
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
  transition: color var(--dur) var(--ease);
}
.home-faq__q:hover { color: var(--accent-text); }
.home-faq__q::after {
  content: "+";
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-4xl);
  line-height: 1;
  color: var(--accent-text);
}
.home-faq__item[open] .home-faq__q::after { content: "\2212"; }
.home-faq__a {
  padding-bottom: var(--sp-20);
  max-width: 62ch;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* --- УЗКИЕ ЭКРАНЫ ------------------------------------------------------- */
@media (max-width: 720px) {
  .home-hero { padding-block: var(--sp-40) var(--sp-48); }
  .home-find { margin-top: var(--sp-32); }
  .home-find__fields { gap: var(--sp-24); }
  .home-find__go .btn { width: 100%; }
  .brands { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* --- 43-fitment.css --- */
/* ============================================================================
   Подбор по технике и по габаритам (/fitment).

   Все цвета через семантику из 00-tokens.css, поэтому блоки работают и на
   белой странице, и внутри тёмной секции. Своих значений цвета здесь нет
   ни одного.

   Сетка страницы повторяет .split карточки товара, но со своими пропорциями:
   в подборе левая колонка это списки на сотни строк, а правая только
   ссылка на список подбора, поэтому 1fr к 320px, а не 1fr к 1fr.
   ============================================================================ */

.fit-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: var(--grid-gap-lg);
  align-items: start;
  margin-top: var(--sp-24);
}
.fit-aside {
  display: grid;
  gap: var(--grid-gap);
  position: sticky;
  top: calc(var(--h-header) + var(--sp-16));
  z-index: var(--z-sticky-aside);
}
.fit-aside__note { margin: var(--sp-10) 0 var(--sp-16); }

@media (max-width: 1180px) {
  .fit-layout { grid-template-columns: minmax(0, 1fr); }
  /* Правая колонка уезжает под контент и перестаёт липнуть: липкий блок
     в одноколоночной раскладке закрывает результат. */
  .fit-aside { position: static; }
}

/* --- ПЕРЕКЛЮЧАТЕЛЬ РЕЖИМОВ.
       Ссылки, а не кнопки: режим это состояние адреса. Активный отличается
       не только цветом, но и заливкой с рамкой, а для скринридера
       aria-current. ------------------------------------------------------ */
.fit-modes { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.fit-mode {
  height: var(--h-lg);
  display: inline-flex;
  align-items: center;
  padding: var(--pad-btn-md);
  background: var(--surface);
  border: var(--bw) solid var(--line-strong);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.fit-mode:hover { border-color: var(--accent-line); color: var(--accent-text); }
.fit-mode.is-active {
  background: var(--c-graphite-800);
  border-color: var(--c-graphite-800);
  color: var(--c-white);
}

/* --- ПУТЬ ПО КАСКАДУ. Марка / Модель / Двигатель, каждый шаг это ссылка
       назад на свой уровень: модель почти всегда выбирают со второго раза. -- */
.fit-path {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  gap: var(--sp-10);
}
.fit-path__step {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-10) var(--sp-16);
  background: var(--surface);
  border: var(--bw) solid var(--line);
  min-width: 0;
  transition: border-color var(--dur) var(--ease);
}
.fit-path__step:hover { border-color: var(--accent-line); }
.fit-path__label {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-3xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}
.fit-path__value {
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  overflow-wrap: anywhere;
}
.fit-path__hint {
  align-self: center;
  font-size: var(--fs-sm);
  color: var(--ink-3);
}

/* --- ПОЯСНЕНИЕ ПОД ЗАГОЛОВКОМ ШАГА ------------------------------------- */
.fit-lede {
  margin-top: var(--sp-16);
  max-width: 68ch;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* --- ПОИСК ПО СПИСКУ ШАГА.
       Поле и кнопка выровнены по низу: подпись поля занимает свою строку,
       и align-items: end это единственный способ не разъехаться. ---------- */
.fit-find {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-12);
  align-items: end;
  margin-top: var(--sp-24);
}
.fit-find__go { min-width: 0; }

@media (max-width: 480px) {
  .fit-find { grid-template-columns: minmax(0, 1fr); }
  .fit-find__go .btn { width: 100%; }
}

/* --- УКАЗАТЕЛЬ ПО БУКВАМ.
       Ссылки, а не кнопки: раздел это адрес. Минимальная зона нажатия 44px
       по обеим осям, иначе на телефоне промахи по одной букве. ----------- */
.fit-abc {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-24);
  padding-top: var(--sp-20);
  border-top: var(--bw) solid var(--line);
}
.fit-abc__item {
  min-width: var(--tap-min);
  height: var(--tap-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: var(--sp-10);
  border: var(--bw) solid transparent;
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  letter-spacing: var(--ls-mono-sm);
  text-transform: uppercase;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.fit-abc__item:hover { border-color: var(--accent-line); color: var(--accent-text); }
.fit-abc__item.is-active {
  background: var(--surface-3);
  border-color: var(--line-strong);
  color: var(--ink);
}
.fit-abc__note { margin-top: var(--sp-16); }

/* --- СПИСОК ВАРИАНТОВ ШАГА --------------------------------------------- */
.fit-list { margin-top: var(--sp-24); }

.fit-letter + .fit-letter { margin-top: var(--sp-20); }
.fit-letter__title {
  padding-bottom: var(--sp-8);
  border-bottom: var(--bw) solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  font-weight: var(--fw-mono-med);
  letter-spacing: var(--ls-mono-lg);
  color: var(--accent-text);
}

/* min(220px, 100%) вместо 220px обязателен: minmax с жёстким минимумом шире
   контейнера растягивает грид и утаскивает в горизонтальный скролл всю
   страницу. Контейнер здесь это карточка внутри контейнера с полями, то есть
   на экране 280px под колонку остаётся 204px. */
.fit-opts {
  display: grid;
  gap: var(--sp-2);
  grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr));
  margin-top: var(--sp-12);
}
/* Модели и двигатели бывают длинными ("300 (Kettenbagger / Crawler
   Excavators)"), поэтому у них колонка шире. */
.fit-opts--wide {
  grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
}

.fit-opt { min-width: 0; }
.fit-opt__link {
  display: flex;
  align-items: baseline;
  gap: var(--sp-10);
  padding: var(--sp-10) var(--sp-12);
  border: var(--bw) solid transparent;
  color: var(--ink);
  transition: border-color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.fit-opt__link:hover { background: var(--surface-hover); border-color: var(--line); }
.fit-opt__name { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.fit-opt__sub {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  color: var(--ink-3);
}
.fit-opt__n {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

/* --- ФОРМА ГАБАРИТОВ ---------------------------------------------------- */
.fit-dim { margin-top: var(--sp-24); }
.fit-dim__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
  gap: var(--sp-20);
}
.fit-dim__go { margin-top: var(--sp-24); }

/* Нативная стрелка select-а остаётся: своя рисованная в макете не наследовала
   ни фокус, ни состояние disabled, а appearance: none без замены даёт
   невидимый контрол в Safari. Здесь только выравнивается высота с полями. */
.fit-select {
  height: var(--h-md);
  padding-right: var(--sp-8);
  cursor: pointer;
}

/* --- РЕЗУЛЬТАТ ---------------------------------------------------------- */
.fit-result { display: grid; gap: var(--grid-gap); }
.fit-result__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-24);
  flex-wrap: wrap;
  padding-bottom: var(--sp-16);
  border-bottom: var(--bw-2) solid var(--line-strong);
}
.fit-result__title { margin-top: var(--sp-8); overflow-wrap: anywhere; }
.fit-result__count {
  font-size: var(--fs-mono-sm);
  color: var(--ink-3);
  white-space: nowrap;
}

.fit-group__title {
  display: flex;
  align-items: baseline;
  gap: var(--sp-10);
  margin-bottom: var(--sp-12);
  font-size: var(--fs-d6);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
}
.fit-group__n {
  font-size: var(--fs-mono-2xs);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}
.fit-group + .fit-group { margin-top: var(--sp-28); }
/* Подпись про отсечку аналогов: стоит ПОД таблицей, а не в шапке группы,
   потому что отвечает на вопрос, который возникает после чтения списка. */
.fit-group__more { margin-top: var(--sp-10); }
/* Заголовок группы переносится: рядом с ним теперь стоит счётчик, и на узком
   экране «Охлаждающей жидкости 12» в одну строку не помещается. */
.fit-group__title { flex-wrap: wrap; }

/* Список подбора и оговорки на бумаге не нужны: печатают результат. */
@media print {
  .fit-modes, .fit-find, .fit-aside { display: none; }
}

/* --- 44-brands.css --- */
/* ============================================================================
   Бренды: плашка бренда, плитка списка, список брендов поиска, разбивка по
   типам, карточка бренда в правой колонке.
   Слой подхватывается сборщиком сам: имя начинается с числового префикса.

   Всё остальное на обеих страницах это уже существующие компоненты: .card,
   .grid, .split, .stack, .chips, .chip--mono, .eyebrow, .lede, .more,
   .link--action, .badge, .bullets, .alert, герой и крошки из macros/site.
   ============================================================================ */

/* --- ПЛАШКА БРЕНДА.
       Логотипов нет ни одного, поэтому основное состояние плашки это СЛОВО,
       а не картинка. Высота фиксирована в обоих состояниях: иначе плитка с
       логотипом и плитка с текстом стоят в сетке разной высоты, а на
       догрузке картинки строка прыгает. --------------------------------- */
.bplate {
  display: grid;
  place-items: center;
  width: 100%;
  height: 56px;
  min-width: 0;
}
.bplate--lg { height: 72px; place-items: center start; }

.bplate__img {
  max-width: 100%;
  width: auto;
  height: 56px;
  object-fit: contain;
}
.bplate--lg .bplate__img { height: 72px; }

/* Словесная плашка. Начертание то же, что у заголовков сайта: это НЕ попытка
   изобразить чужой логотип, а честная подпись названием.
   overflow-wrap: anywhere нужен из-за брендов вроде MANN-FILTER и
   Luber-finer: без него длинное имя вылезает за рамку плитки. */
.bplate__text {
  max-width: 100%;
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d4);
  text-transform: uppercase;
  text-align: center;
  overflow-wrap: anywhere;
  color: var(--ink);
}
.bplate--lg .bplate__text { font-size: var(--fs-d5); text-align: left; }

/* --- СЕТКА И ПЛИТКА БРЕНДА, КОТОРЫЙ МЫ ПРОДАЁМ.
       Имя bgrid, а НЕ brands: слой 42 (главная) уже держит свою .brands с
       собственным grid-template-columns и решёткой на волосяных линиях, и
       грузится он раньше. Одноимённый класс здесь означал бы, что плитки
       этой страницы верстает чужой слой. ------------------------------- */
.bgrid { --col-min: 260px; }

.btile {
  display: grid;
  gap: var(--sp-16);
  align-content: start;
  padding: var(--card-pad-sm);
  height: 100%;
  transition: border-color var(--dur) var(--ease), transform var(--dur-slow) var(--ease),
              box-shadow var(--dur-slow) var(--ease);
}
.btile:hover { border-color: var(--line-strong); }

/* Собственная подложка под плашкой: в макете логотип стоял в отдельном
   прямоугольнике, и на белой карточке это единственное, что отделяет
   плашку от подписи. */
.btile__mark {
  display: grid;
  place-items: center;
  padding: var(--sp-16);
  background: var(--surface-2);
}

.btile__name {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
  overflow-wrap: anywhere;
}

.btile__meta { font-size: var(--fs-md); color: var(--ink-3); }
.btile__n {
  font-family: var(--font-mono);
  font-weight: var(--fw-mono-med);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* --- БРЕНДЫ, ЧЬИ НОМЕРА МЫ ПРИНИМАЕМ В ПОИСКЕ.
       Строки в несколько колонок, а не плитки: рядом с названием стоит
       число, и пара "имя ... число" на волосяной линии читается как строка
       прайса, то есть ровно тем, чем и является. Порядок в сетке построчный,
       слева направо, и он же порядок по числу номеров. ------------------ */
.bkeys__lede { max-width: 68ch; margin-bottom: var(--sp-28); }

.bkeys {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0 var(--grid-gap-lg);
  border-top: var(--bw) solid var(--line);
}

.bkeys__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-16);
  padding: var(--sp-12) 0;
  border-bottom: var(--bw) solid var(--line-soft);
  font-size: var(--fs-lg);
}
.bkeys__name { min-width: 0; overflow-wrap: anywhere; }
.bkeys__n {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

/* --- РАЗБИВКА ПО ТИПАМ ФИЛЬТРОВ. ---------------------------------------- */
.btypes { display: grid; }

.btypes__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-16);
  padding: var(--sp-16) 0;
  border-bottom: var(--bw) solid var(--line-soft);
  font-size: var(--fs-xl);
}
.btypes__row:last-child { border-bottom: 0; padding-bottom: 0; }

.btypes__name { font-weight: var(--fw-semi); min-width: 0; overflow-wrap: anywhere; }
a.btypes__name:hover { color: var(--accent-text); }

.btypes__n {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-xs);
  letter-spacing: var(--ls-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}

/* --- КАРТОЧКА БРЕНДА В ПРАВОЙ КОЛОНКЕ. ---------------------------------- */
.bcard { display: grid; gap: var(--sp-24); }

.bcard__mark { padding-bottom: var(--sp-20); border-bottom: var(--bw) solid var(--line); }

.bcard__about {
  font-size: var(--fs-xl);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

.bcard__actions { display: grid; gap: var(--sp-12); }

/* --- НОМЕРА БРЕНДА. Сами чипы описаны в 25-misc, здесь только отбивка от
       микроподписи блока. Число номеров ограничено на стороне запроса
       (BRAND_NUMBERS), а не высотой блока: обрезка по высоте прячет ссылки
       от краулера, ради которого блок и стоит. ------------------------- */
.brand-nums { margin-top: var(--sp-16); }

@media (max-width: 720px) {
  .bplate { height: 48px; }
  .bplate--lg { height: 56px; }
  .bplate__img { height: 48px; }
  .bplate--lg .bplate__img { height: 56px; }
  .btypes__row { font-size: var(--fs-lg); }
}

/* --- 45-cart.css --- */
/* ============================================================================
   Список подбора, запрос счёта, сравнение. Слой 45.

   Ни одного цвета, кегля и отступа мимо токенов: слой обязан перекрашиваться
   вместе с data-surface, как и остальные. Скруглений нет, как везде в проекте
   (--radius: 0).

   Чего в этом слое НЕТ, потому что нет данных: полосы прогресса до бесплатной
   доставки, блока "К оплате", поля промокода, плиток "часто берут вместе".
   Правило простое: класса без разметки, а разметки без данных не заводим.
   ============================================================================ */

/* ------------------------------------------------------------ раскладка */

/* Список и сводка. Та же ось, что у .split на карточке товара, но колонка
   сводки уже: в ней нет ни формы, ни цены, только счётчики и одна кнопка. */
.cart,
.quote {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--col-split);
  gap: var(--grid-gap-lg);
  align-items: start;
}

.cart__aside,
.quote__aside { min-width: 0; }

@media (max-width: 1180px) {
  .cart,
  .quote { grid-template-columns: 1fr; }
}

/* Сообщение о том, чем кончилось добавление номера («нашли, добавили
   позицию», «добавили, подберём»). Стоит НАД списком, а не рядом со строкой:
   строка могла встать в конец длинного списка, и подпись у неё человек не
   увидит. Отбивка снизу своя, потому что .alert живёт в общем слое и про
   соседей этой страницы ничего не знает. */
.cart-notice { margin-bottom: var(--sp-24); }

/* ------------------------------------------------------------ строка списка */

.cart__list { background: var(--surface); }

/* Последняя колонка auto, а не фиксированные 260px: в ней лежит форма со
   степпером, кнопкой и крестиком, и ширина этой связки задана размерами
   управляющих элементов из токенов. Фиксированное число здесь пришлось бы
   пересчитывать каждый раз, когда меняется --stepper-w или подпись кнопки.
   Отдельной шапки таблицы у списка нет, поэтому auto ничему не мешает: каждая
   строка это свой грид, и шапка с ней всё равно не совпала бы по ширине
   последней колонки. */
.cart-row {
  display: grid;
  grid-template-columns: 32px 72px minmax(220px, 1fr) auto;
  gap: var(--sp-16);
  align-items: center;
  padding: var(--sp-16) var(--card-pad-sm);
  border-bottom: var(--bw) solid var(--line-soft);
  transition: background-color var(--dur) var(--ease);
}
.cart-row:hover { background: var(--surface-hover); }

/* Зона нажатия чекбокса 44px даже при визуальном размере 18px: колонка узкая,
   и промах по ней означает случайно снятую отметку сравнения. */
.cart-row__pick { display: grid; place-items: center; }
.cart-row__check {
  width: var(--sp-20);
  height: var(--sp-20);
  margin: calc((var(--tap-min) - var(--sp-20)) / -2) 0;
  padding: calc((var(--tap-min) - var(--sp-20)) / 2) 0;
  accent-color: var(--accent-line);
  cursor: pointer;
}

.cart-row__media {
  background: var(--surface-2);
  display: grid;
  place-items: center;
  padding: var(--sp-4);
  aspect-ratio: 1;
}
.cart-row__img { max-width: 100%; height: auto; }

.cart-row__main { min-width: 0; }

.cart-row__pn {
  font-family: var(--font-mono);
  font-size: var(--fs-xl);
  font-weight: var(--fw-mono-med);
  overflow-wrap: anywhere;
}

.cart-row__meta {
  margin-top: var(--sp-6);
  font-size: var(--fs-md);
  color: var(--ink-3);
}

.cart-row__state {
  margin-top: var(--sp-8);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-12);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
}

/* "Цена по запросу" стоит там же, где в макете стояла сумма строки, и тем же
   местом в чтении. Кегль мельче, потому что это не число: жирные 18px на
   словах "по запросу" читаются как цена, которой нет. */
.cart-row__price { color: var(--ink-2); }

/* Деньги строки: цена за штуку слева, сумма справа. Отдельная строка под
   наличием, и она есть у позиции с ценой и у позиции без неё одинаково.

   flex-basis: 100% обязателен. Без него короткое "Цена по запросу" встаёт в
   один ряд с плашкой наличия, а длинная связка "1 490 ₽ × 2 шт  2 980 ₽" не
   помещается и переносится сама: в смешанном списке соседние строки получали
   бы разную высоту без всякой причины. Колонка номеров по сетке 220px, и
   рассчитывать, что связка когда-нибудь поместится рядом с наличием, нельзя.

   Сумма прижата вправо у обоих видов: правый край строки это столбец денег,
   и глаз идёт по нему сверху вниз, не спотыкаясь о позиции без цены. */
.cart-row__money {
  flex-basis: 100%;
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: var(--sp-8) var(--sp-12);
  flex-wrap: wrap;
}
.cart-row__unit { margin-right: auto; }

/* Цена за штуку тише суммы: множитель это справка, а число, которое попадёт
   в счёт, одно, и это сумма. tabular-nums обязателен обоим: без него
   разрядные пробелы в столбце сумм пляшут по вертикали. */
.cart-row__unit {
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.cart-row__sum {
  color: var(--ink);
  font-weight: var(--fw-mono-med);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Минимум партии и кратность отгрузки. Строка рисуется, только когда они не
   равны единице, то есть сейчас не рисуется нигде: значения приедут из 1С. */
.cart-row__minqty {
  width: 100%;
  text-align: right;
  font-size: var(--fs-md);
  color: var(--ink-3);
}

.cart-row__warn {
  margin-top: var(--sp-8);
  font-size: var(--fs-md);
  color: var(--warn);
}

.cart-row__note {
  margin-top: var(--sp-8);
  padding-left: var(--sp-10);
  border-left: var(--bw-2) solid var(--line-strong);
  font-size: var(--fs-md);
  color: var(--ink-2);
}

/* Форма строки: степпер с кнопкой "Обновить" и крестик удаления.
   wrap обязателен: последняя колонка сетки это auto, то есть она отдаёт место
   тексту позиции, когда его перестаёт хватать. Без переноса кнопка вылезала бы
   за карточку вместо того, чтобы встать под степпер. */
.cart-row__form {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-12);
}

.cart-row__qty {
  display: flex;
  align-items: center;
  gap: var(--sp-8);
}

/* Степпер в строке ниже своего обычного роста: рядом стоит кнопка размера sm,
   и 56px против 44px читаются как съехавшая вёрстка. Ширина остаётся полной:
   размер --sm сужает степпер до 108px, а две его кнопки занимают по 44px,
   и на поле остаётся 20px, то есть двузначное количество обрезается. */
.cart-row__qty .qty { height: var(--h-sm); }

/* Кнопка нужна без JS: без неё изменённое количество некуда отправить.
   Прятать её при включённом скрипте нельзя, пока форму не отправляет сам
   степпер, а этим занимается общий site.js, а не этот раздел. */
.cart-row__apply { white-space: nowrap; }

.cart-row__remove {
  flex: none;
  width: var(--tap-min);
  height: var(--tap-min);
  display: grid;
  place-items: center;
  border: 0;
  background: transparent;
  color: var(--ink-3);
  font-size: var(--fs-4xl);
  line-height: 1;
  cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.cart-row__remove:hover { color: var(--danger); }

/* Своей верхней границы у подвала нет: её уже нарисовала нижняя граница
   последней строки, а две подряд дают двойную линию в 2 пикселя. */
.cart__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-16);
  padding: var(--sp-20) var(--card-pad-sm);
}

.cart__count {
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ----------------------------------------------- строка-запрос («подберём»)

   Номер, которого у нас нет. Сетка та же, что у позиции, и это главное здесь:
   строки двух видов стоят вперемешку в порядке добавления, и разъехавшиеся
   колонки превратили бы список в две таблицы, набранные внахлёст.

   Отличается строка тремя вещами, и все три несут смысл: приглушённая
   подложка (это не товар из каталога), рамка вместо миниатюры (фотографии
   нет и быть не может) и полное отсутствие денег (ни цены, ни слов «по
   запросу»: у соседней позиции они значат «товар есть, цена по запросу»). */
.cart-row--request { background: var(--surface-2); }
.cart-row--request:hover { background: var(--surface-hover); }

/* Заглушка на месте миниатюры. Квадрат тот же, что у .cart-row__media, и
   пунктир вместо заливки: сплошной серый блок читается как не загрузившаяся
   фотография, то есть как поломка. */
.cart-row__ghost {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  border: var(--bw) dashed var(--line-strong);
  color: var(--ink-3);
  font-size: var(--fs-2xl);
  line-height: 1;
}

/* Метка «подберём». Рамка, а не заливка цветом состояния: заливка встала бы в
   один ряд с плашками наличия у соседних строк, а это не наличие. */
.cart-row__badge {
  display: inline-block;
  padding: var(--sp-2) var(--sp-8);
  border: var(--bw) solid var(--accent-line);
  font-size: var(--fs-xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-2);
}

/* Объяснение под номером. Кегль как у остальных подписей строки: это не
   предупреждение об ошибке, а нормальное состояние половины B2B-списка. */
.cart-row__hint {
  margin-top: var(--sp-8);
  font-size: var(--fs-md);
  color: var(--ink-3);
}

/* Поле комментария внутри формы строки. Занимает всю ширину последней колонки
   и встаёт ПОД степпер: рядом с ним оно ужалось бы до трёх символов, а
   написать туда надо «нужен аналог на КамАЗ». */
.cart-row__notefield { flex-basis: 100%; }
.cart-row__notefield .field__input { height: var(--h-xs); font-size: var(--fs-md); }

@media (max-width: 900px) {
  .cart-head { display: none; }
  /* Чекбокс и картинка в первой строке, форма количества уходит под текст:
     четыре колонки на 700px дают степпер шириной в две цифры. */
  .cart-row {
    grid-template-columns: 32px 64px minmax(0, 1fr);
    row-gap: var(--sp-12);
  }
  .cart-row__form { grid-column: 2 / -1; justify-content: flex-start; }
  .cart-row__minqty { text-align: left; }
  .cart-row__remove { margin-left: auto; }
}

@media (max-width: 480px) {
  .cart-row { grid-template-columns: 32px minmax(0, 1fr); }
  .cart-row__media { display: none; }
  .cart-row__form { grid-column: 1 / -1; }
}

/* ------------------------------------------------------------ сводка списка */

.cart-sum__facts { margin-top: var(--sp-20); }

/* Сумма крупнее счётчиков: это единственное число в карточке, ради которого
   в неё смотрят. Подпись при этом может быть длинной ("Сумма по позициям с
   ценой"), поэтому она переносится, а число остаётся в одну строку. */
.cart-sum__total .deflist__key { color: var(--ink); }
.cart-sum__total .deflist__val {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-mono-med);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* НДС уже внутри суммы, поэтому строка подчинённая: это не второе слагаемое,
   а расшифровка первого. */
.cart-sum__vat,
.cart-sum__vat .deflist__val {
  font-size: var(--fs-md);
  color: var(--ink-3);
}

.cart-sum__note {
  margin-top: var(--sp-16);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--ink-3);
}

/* Строка "На подбор" в сводке. Отбита сверху, потому что отвечает не на тот
   же вопрос, что "Позиций": одно число про то, что мы продаём, второе про то,
   чего у нас нет, и сложить их нельзя. */
.cart-sum__pick { border-top: var(--bw) solid var(--line-soft); }

/* Оговорка про номера на подбор. Отбита слева линией: это не продолжение
   разговора про цену, а другое условие, и слить его с абзацем выше значит
   получить обещание "цену поставит менеджер" про товар, которого нет. */
.cart-sum__note--pick {
  padding-left: var(--sp-10);
  border-left: var(--bw-2) solid var(--accent-line);
}

.cart-sum__action { margin-top: var(--sp-24); }
.cart-sum__terms { margin-top: var(--sp-20); }

.cart-tools {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-12);
}
.cart-tools .btn { flex: 1 1 auto; }

.cart__hint { margin-top: var(--sp-4); }

/* --------------------------------------------------------- пустое состояние */

.cart-empty { text-align: center; }

.cart-empty__title {
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-d4);
}

.cart-empty__text {
  margin: var(--sp-12) auto 0;
  max-width: 46ch;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

.cart-empty__actions {
  margin-top: var(--sp-28);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-12);
}

/* ------------------------------------------------------------ запрос счёта */

.quote-form > * + * { margin-top: var(--sp-24); }

.quote-org { border: 0; padding: 0; margin: 0; }

.radios {
  margin-top: var(--sp-10);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-12);
}

/* Радиокнопка как плашка: два варианта стоят рядом и различимы, а зона
   нажатия равна всей плашке, а не кружку в 16px. */
.radio {
  display: flex;
  align-items: center;
  gap: var(--sp-10);
  min-height: var(--h-md);
  padding: 0 var(--sp-16);
  border: var(--bw) solid var(--field-line);
  background: var(--field-bg);
  color: var(--field-ink);
  font-size: var(--fs-lg);
  cursor: pointer;
}
.radio:hover { border-color: var(--line-strong); }
.radio:focus-within { border-color: var(--field-line-focus); }
.radio__box {
  width: var(--sp-16);
  height: var(--sp-16);
  accent-color: var(--accent-line);
}
/* :has поддержан во всех целевых браузерах; там, где нет, вариант просто не
   подсвечен рамкой, а выбор всё равно виден по самой радиокнопке. */
.radio:has(.radio__box:checked) { border-color: var(--accent-line); }

.quote-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: var(--sp-20);
}

/* Без align-self поля растягиваются на высоту ряда, а .field это сам по себе
   грид: лишняя высота уходит в его зазор, и поле с подсказкой ("10 цифр у
   организации") опускает свой input ниже соседнего без подсказки. В ряду из
   трёх полей это видно сразу. */
.quote-grid .field { align-self: start; }

@media (max-width: 720px) {
  .quote-grid { grid-template-columns: 1fr; }
}

.quote-items { list-style: none; margin: var(--sp-16) 0 0; padding: 0; }

.quote-items__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--sp-4) var(--sp-12);
  padding: var(--sp-12) 0;
  border-bottom: var(--bw) solid var(--line-soft);
}
.quote-items__row:last-child { border-bottom: 0; }

.quote-items__pn {
  font-size: var(--fs-lg);
  font-weight: var(--fw-mono-med);
  overflow-wrap: anywhere;
}
.quote-items__brand {
  grid-column: 1;
  font-size: var(--fs-md);
  color: var(--ink-3);
}
.quote-items__qty {
  grid-row: 1 / span 2;
  grid-column: 2;
  align-self: center;
  font-size: var(--fs-md);
  color: var(--ink-2);
  white-space: nowrap;
}

/* Правый столбец строки: количество, под ним сумма или "по запросу".
   Обёртка, а не два отдельных элемента сетки: у .quote-items__qty свои
   grid-row и grid-column, они нужны странице благодарности, где суммы нет
   вовсе. Внутри флекса эти свойства просто не действуют, и обе страницы
   пользуются одним классом без развилки. */
.quote-items__money {
  grid-row: 1 / span 2;
  grid-column: 2;
  align-self: center;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sp-4);
}

.quote-items__sum {
  font-size: var(--fs-lg);
  font-weight: var(--fw-mono-med);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* "по запросу" на месте суммы: строка без цены занимает ту же высоту, что
   строка с ценой, и состав запроса не разъезжается. */
.quote-items__request {
  font-size: var(--fs-md);
  color: var(--ink-3);
  white-space: nowrap;
}

/* Строка на подбор в составе запроса. Отбита слева, чтобы её было видно среди
   позиций одним взглядом: человек отправляет реквизиты и обязан заметить, что
   часть номеров мы ещё не нашли. */
.quote-items__row--pick { border-left: var(--bw-2) solid var(--accent-line); }
.quote-items__row--pick .quote-items__pn { padding-left: var(--sp-10); }

/* "подберём" на месте суммы. Слова НЕ те же, что у соседней строки без цены:
   там "по запросу" значит "товар есть, цену назовём", а здесь товара у нас
   нет. Одинаковые слова про разные состояния это самая дорогая ошибка на
   странице, поэтому и цвет свой. */
.quote-items__pick {
  font-size: var(--fs-md);
  color: var(--ink-2);
  white-space: nowrap;
}

/* Итог состава. Отделён линией сверху, как подвал, но стоит ДО него: сначала
   деньги, потом счётчики и ссылка "Изменить". */
.quote-sum {
  margin-top: var(--sp-16);
  padding-top: var(--sp-16);
  border-top: var(--bw) solid var(--line);
}

.quote-sum__row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-12);
  font-size: var(--fs-lg);
}
.quote-sum__row + .quote-sum__row { margin-top: var(--sp-8); }

.quote-sum__val {
  font-family: var(--font-mono);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-mono-med);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.quote-sum__row--vat {
  font-size: var(--fs-md);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

.quote-sum__note {
  margin-top: var(--sp-12);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--ink-3);
}

/* Оговорка про номера на подбор стоит вне блока .quote-sum (сумма бывает не
   всегда, а оговорка нужна всегда), поэтому отбивку сверху ей задаём здесь. */
.quote-sum__note--pick {
  margin-top: var(--sp-16);
  padding-left: var(--sp-10);
  border-left: var(--bw-2) solid var(--accent-line);
}

.quote-items__foot {
  margin-top: var(--sp-16);
  padding-top: var(--sp-16);
  border-top: var(--bw) solid var(--line);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-12);
  font-size: var(--fs-md);
  color: var(--ink-3);
}

/* -------------------------------------------------------------- сравнение */

.cmp__wrap { background: var(--surface); }

/* Прокрутка живёт ЗДЕСЬ, а не на странице: горизонтально ехать должна
   таблица, иначе на телефоне уезжает вся вёрстка вместе с шапкой. */
.cmp__scroll { overflow-x: auto; }

.cmp { min-width: 640px; }

.cmp__row {
  display: grid;
  grid-template-columns: var(--col-product) repeat(var(--cmp-n, 3), minmax(0, 1fr));
  gap: var(--sp-20);
  align-items: start;
  padding: var(--sp-16) var(--card-pad-sm);
  border-bottom: var(--bw) solid var(--line-soft);
}
/* Число колонок классом, а не инлайновым стилем: инлайновых стилей в
   шаблонах проекта нет ни одного, и заводить их ради одной цифры незачем. */
.cmp--1 .cmp__row { --cmp-n: 1; }
.cmp--2 .cmp__row { --cmp-n: 2; }
.cmp--3 .cmp__row { --cmp-n: 3; }
.cmp--4 .cmp__row { --cmp-n: 4; }

.cmp__row--head {
  align-items: end;
  padding-block: var(--sp-24);
  border-bottom: var(--bw) solid var(--line);
}

.cmp__row--foot {
  border-bottom: 0;
  border-top: var(--bw) solid var(--line);
  background: var(--surface-2);
  align-items: center;
}

.cmp__key {
  font-size: var(--fs-base);
  color: var(--ink-3);
}
.cmp__row--head .cmp__key {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-3xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
}

.cmp__cell {
  font-size: var(--fs-base);
  font-weight: var(--fw-semi);
  overflow-wrap: anywhere;
}
/* Прочерк тише значения: строка с одним заполненным столбцом из четырёх
   должна читаться как "тут есть данные", а не как четыре равных ячейки. */
.cmp__cell--empty { font-weight: var(--fw-regular); color: var(--ink-3); }

.cmp__col { min-width: 0; }

.cmp__pn {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-mono-med);
  overflow-wrap: anywhere;
}

.cmp__brand {
  margin-top: var(--sp-4);
  font-size: var(--fs-md);
  color: var(--ink-3);
}

.cmp__drop {
  display: inline-block;
  margin-top: var(--sp-10);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono);
  text-transform: uppercase;
  color: var(--accent-text);
}
.cmp__drop:hover { text-decoration: underline; }

.cmp__request {
  margin-bottom: var(--sp-10);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  color: var(--ink-2);
}

.cmp__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-12);
}

@media (max-width: 720px) {
  .cmp__row { gap: var(--sp-12); padding-inline: var(--sp-16); }
  .cmp { min-width: 560px; }
}

/* ------------------------------------------------------------------ печать */

/* Список подбора распечатывают и несут в бухгалтерию. Печатать степперы,
   крестики и кнопки на бумаге незачем. */
@media print {
  .cart-row__form,
  .cart-row__pick,
  .cart-tools,
  .cart-sum__action,
  .cmp__drop,
  .cmp__row--foot form { display: none; }
  .cart,
  .quote { grid-template-columns: 1fr; }
  .cmp__scroll { overflow-x: visible; }
  .cmp { min-width: 0; }
}

/* --- 46-static.css --- */
/* ============================================================================
   Служебные и текстовые страницы: /about, /contacts, /delivery, /warranty,
   /faq, /clients, /wholesale, /policy, /terms.

   Все цвета и отступы через токены 00-tokens.css. Собственных значений здесь
   ровно два: ширина колонки текста в ch (её нельзя выразить токеном ритма) и
   ширина колонки оглавления.
   ============================================================================ */

/* --- КОЛОНКА ТЕКСТА.
       65ch это не украшение: строка длиннее 75 знаков заставляет глаз терять
       начало следующей, а эти страницы читают целиком, а не сканируют. ----- */
.prose { max-width: 65ch; }

.prose__sec + .prose__sec { margin-top: var(--sp-40); }

.prose__h {
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
  /* Заголовок не должен остаться последней строкой на экране или на листе. */
  break-after: avoid;
}

.prose__p {
  margin-top: var(--sp-16);
  font-size: var(--fs-xl);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

.prose__list {
  margin-top: var(--sp-16);
  display: grid;
  gap: var(--sp-10);
}
.prose__li {
  display: grid;
  grid-template-columns: 18px 1fr;
  gap: var(--sp-10);
  font-size: var(--fs-lg);
  line-height: var(--lh-snug);
  color: var(--ink-2);
}
/* Маркер рисуется псевдоэлементом, а не list-style: у нативного маркера
   нельзя задать цвет отдельно от текста, а он тут декоративный. */
.prose__li::before { content: "—"; color: var(--accent-line); }

/* --- СЕТКА-ВОЛОСЯНКА. Из макета контактов: ячейки на белом, между ними
       ровно один пиксель фона. Отдельных border у ячеек нет, иначе на стыке
       получается двойная линия. --------------------------------------------- */
.hair {
  display: grid;
  gap: var(--bw);
  background: var(--line);
  border: var(--bw) solid var(--line);
  align-content: start;
}
.hair__cell { padding: var(--sp-28) var(--sp-32); background: var(--surface); }
.hair__value {
  display: block;
  margin-top: var(--sp-10);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semi);
  line-height: var(--lh-snug);
  color: var(--ink);
}
.hair__value--big {
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-d4);
}
a.hair__value:hover { color: var(--accent-text); }

/* --- КОНТАКТЫ: слева карточки, справа форма. ------------------------------ */
.contacts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: var(--grid-gap-lg);
  align-items: start;
}

/* --- РЕКВИЗИТЫ ОПЕРАТОРА. <dl> в две колонки, на узком экране в одну. ----- */
.req { display: grid; gap: var(--sp-12); }
.req__row {
  display: grid;
  grid-template-columns: minmax(0, 260px) minmax(0, 1fr);
  gap: var(--sp-16);
  padding-bottom: var(--sp-12);
  border-bottom: var(--bw) solid var(--line-soft);
}
.req__row:last-child { padding-bottom: 0; border-bottom: 0; }
.req__key { font-size: var(--fs-md); color: var(--ink-3); }
.req__val { font-size: var(--fs-lg); color: var(--ink); overflow-wrap: anywhere; }

/* Незаполненный реквизит. Цвет не единственный носитель смысла: в значении
   стоит слово «НЕ ЗАПОЛНЕНО», а моноширинное начертание отделяет заглушку от
   настоящего текста документа. */
.req__val--gap {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  letter-spacing: var(--ls-mono-sm);
  color: var(--danger);
}

@media (max-width: 720px) {
  .req__row { grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
}

/* --- ВОПРОСЫ И ОТВЕТЫ. Плашка с волосяными разделителями, без аккордеона:
       ответов девять и они короткие, а <details> заставил бы кликать девять
       раз и ломал бы поиск по странице (Ctrl+F не находит свёрнутое). ------ */
.faq__item { padding: var(--sp-28) var(--card-pad); }
.faq__item + .faq__item { border-top: var(--bw) solid var(--line-soft); }
.faq__q {
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
}
.faq__a { max-width: 80ch; }
/* Первый абзац ответа прижат к вопросу теснее, чем абзацы между собой. */
.faq__a .prose__p:first-child { margin-top: var(--sp-12); }

/* --- ЮРИДИЧЕСКИЙ ДОКУМЕНТ. ------------------------------------------------ */
.legal .prose { max-width: 78ch; }
.legal__date {
  font-size: var(--fs-mono-xs);
  letter-spacing: var(--ls-mono);
  text-transform: uppercase;
  color: var(--ink-3);
}
.legal > * + * { margin-top: var(--sp-24); }

/* Оглавление. Колонками, чтобы одиннадцать разделов не гнали читателя на
   второй экран раньше самого документа. */
.toc {
  padding: var(--sp-24) var(--sp-28);
  background: var(--surface-2);
  border-left: 3px solid var(--accent-line);
}
.toc__list {
  columns: 260px 2;
  column-gap: var(--sp-32);
  font-size: var(--fs-md);
}
.toc__list li { break-inside: avoid; margin-bottom: var(--sp-8); }
.toc__link { color: var(--ink-2); }
.toc__link:hover { color: var(--accent-text); }

/* --- ПОЛОСА С ПРИЗЫВОМ в конце текстовой страницы. ------------------------ */
.cta__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-32);
  flex-wrap: wrap;
}
.cta__title {
  margin-top: var(--sp-12);
  max-width: 20ch;
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d4);
  color: var(--ink);
}
.cta__text {
  margin-top: var(--sp-12);
  max-width: 52ch;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.cta__actions { display: flex; gap: var(--sp-12); flex-wrap: wrap; }

@media (max-width: 720px) {
  .cta__title { max-width: none; }
  /* Кнопки в столбец: две кнопки по 52px рядом на 360px не помещаются, а
     перенос одной из них выглядит поломкой. */
  .cta__actions { width: 100%; }
  .cta__actions > * { flex: 1 1 100%; }
}

/* --- ПЕЧАТЬ. Политику и гарантию распечатывают и подшивают к договору. ---- */
@media print {
  .toc, .cta { display: none !important; }
  .prose, .legal .prose { max-width: none; }
  .prose__sec, .faq__item, .req__row { break-inside: avoid; }
}

/* --- 47-filter3d.css --- */
/* ============================================================================
   Трёхмерная модель фильтра. Стоит в двух местах и в двух видах:

   .fx3d--hero  правая колонка героя главной, рядом с формой подбора;
   .fx3d--band  полоса во всю ширину под героем страницы «О компании».

   Обёртка .fx3d в разметке помечена hidden и открывается скриптом, когда тот
   получил контекст WebGL. Пустая тёмная колонка заметнее, чем её отсутствие,
   поэтому без JS и без ускорителя блока просто нет.
   ============================================================================ */

.fx3d__stage {
  position: relative;
  cursor: grab;
  /* Вертикальную прокрутку пальцем холст не отбирает: тащить модель можно
     мышью и пером, палец листает страницу. */
  touch-action: pan-y;
}
.fx3d__stage.is-grabbing { cursor: grabbing; }

/* Подложка под объектом. Она снимает ощущение вырезанной картинки: чёрный
   корпус на графитовом фоне без неё сливается с полосой нижней третью.
   Нейтрально-холодная, без цвета: подсветка фирменным жёлтым красит фильтр
   в тот оттенок, которого у него нет. */
.fx3d__stage::before {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(52% 58% at 50% 46%,
              rgba(210, 222, 245, .06), rgba(210, 222, 245, 0) 68%);
  pointer-events: none;
}

.fx3d__canvas {
  display: block;
  width: 100%;
  height: 100%;
  position: relative;
}

.fx3d__hint {
  margin-top: var(--sp-12);
  text-align: center;
  color: var(--ink-3);
}

/* --- КОЛОНКА В ГЕРОЕ ГЛАВНОЙ.
       Высота привязана к ширине окна, но не к высоте колонки: у соседа
       (форма подбора) высота своя, и равнять по ней значит растягивать
       холст под текст. ------------------------------------------------- */
.fx3d--hero .fx3d__stage { height: clamp(340px, 30vw, 500px); }

/* --- ПОЛОСА НА СТРАНИЦЕ «О КОМПАНИИ».
       Сверху отступа нет: полоса продолжает тёмный герой и читается с ним
       как одна композиция, а не как картинка под заголовком. ------------ */
.fx3d--band { padding-top: 0; }
.fx3d--band .fx3d__stage { height: clamp(300px, 42vw, 560px); }

/* На тачскрине тащить нечем: подсказка про мышь там врёт. */
@media (hover: none) {
  .fx3d__stage { cursor: auto; }
  .fx3d__hint { display: none; }
}

/* Ниже 1180 px колонка героя отбирает у формы подбора ряд полей, а форма на
   главной важнее картинки. Полоса на /about остаётся: там она ничего не
   теснит. */
@media (max-width: 1180px) {
  .fx3d--hero { display: none; }
}

/* ============================================================================
   ПЛИТКИ РАЗДЕЛОВ КАТАЛОГА на главной («Что нужно чаще всего»).

   Тот же шейдер и та же камера, что в герое, но семь моделей сразу и в
   маленькой рамке 16:9. Класс is-3d ставит скрипт, и ставит его только после
   первого удавшегося кадра: пока модель не нарисовалась, в рамке лежит
   фотография раздела, и рамка ни на миг не мигает пустотой.
   ============================================================================ */

.type-tile__frame { position: relative; }

.type-tile__canvas {
  display: block;
  width: 100%;
  height: 100%;
}

/* display: none, а не прозрачность. Рамка это grid с одной ячейкой, и
   невидимая, но живая картинка продолжала бы растягивать её по своей высоте,
   а холст лёг бы поверх второй ячейкой. */
.type-tile__frame.is-3d .type-tile__img { display: none; }

/* Печать: холст на бумаге пустой, а подпись без модели бессмысленна. В
   плитках на бумагу возвращается фотография, она печатается нормально. */
@media print {
  .fx3d { display: none; }
  .type-tile__canvas { display: none; }
  .type-tile__frame.is-3d .type-tile__img { display: block; }
}

/* --- 50-media.css --- */
/* ============================================================================
   Фотографии товаров. Слой появился, когда у позиций появились свои копии
   снимков: 84 274 адреса выкачаны к себе и пережаты в WebP трёх ширин
   (tools/fetch_images.py, tools/make_derivatives.py, sql/site/010_media.sql).

   Задача слоя ровно одна и она не декоративная: держать БОКС. Фотография
   есть у четырёх позиций из пяти, у пятой на её месте прежняя SVG-заглушка
   по типу фильтра, и пропорции у снимков разные (в базе есть и 180x148, и
   6523x4248). Если бокс задавать картинкой, сетка каталога и строки выдачи
   будут разной высоты и начнут перестраиваться по мере загрузки. Поэтому
   размер везде задаёт контейнер, а картинка вписывается в него через
   object-fit: contain.

   Атрибуты width и height в разметке при этом обязательны и стоят там же
   (макрос photo в macros/product.html): они дают браузеру пропорцию до
   загрузки файла, то есть нулевой сдвиг вёрстки.

   contain, а не cover: cover обрезает края, а у фильтра край это резьба и
   присоединительный размер, то самое, ради чего снимок и смотрят.

   Слой подхватывается сборщиком сам, номер 50 ставит его после компонентов
   (20-45) и до частных страниц (51+): здесь только модификаторы уже
   существующих боксов, перекрывать он ничего не должен.
   ============================================================================ */

/* --- ОБЩЕЕ ДЛЯ ВСЕХ КАРТИНОК ТОВАРА.
       Снимки пережаты на белом фоне (см. flatten в make_derivatives), поэтому
       подложка бокса тоже светлая: иначе на тёмной секции белый прямоугольник
       снимка висит заплаткой. -------------------------------------------- */
.gal__img--photo,
.ptile__img--photo,
.thumb__img--photo,
.cart-row__img--photo {
  object-fit: contain;
  /* Ниже своего оригинала мы не увеличиваем (make_derivatives не апскейлит),
     но мелкий снимок в 180px не должен растягиваться и боксом: он останется
     мелким и по центру. Это честнее мыла. */
  max-width: 100%;
  max-height: 100%;
}

/* --- ГЛАВНОЕ ФОТО КАРТОЧКИ. Бокс это .gal__frame из 30-product.css:
       квадрат с aspect-ratio и внутренним отступом. Заглушке там задан
       width: 100% с ограничением по max-width, а снимку это не годится:
       вертикальный кадр при width: 100% вылезет за нижнюю границу квадрата.
       Поэтому снимок ограничивается по обеим осям сразу. --------------- */
.gal__img--photo {
  width: auto;
  height: auto;
}

/* --- ПЛИТКА КАТАЛОГА. Рамка .ptile__frame это 4/3 с фоном и рамкой снизу.
       Заглушка в ней 88x88 по центру, снимок занимает рамку целиком, оставляя
       поля: фотография товара впритык к линии рамки читается как обрезанная. */
.ptile__img--photo {
  width: 100%;
  height: 100%;
  padding: var(--sp-12);
}

@media (max-width: 480px) {
  /* На телефоне плитка становится строкой с квадратом 104px слева. */
  .ptile__img--photo { padding: var(--sp-8); }
}

/* --- МИНИАТЮРА. Свой бокс, потому что раньше картинки ни в строке выдачи, ни
       в шапке сравнения не было вовсе и контейнера под неё тоже.
       48px это ровно та высота, которую ячейка номера занимает и без
       миниатюры (номер плюс метка "точное совпадение"), поэтому список не
       стал длиннее ни на пиксель. ------------------------------------- */
.thumb {
  flex: none;
  width: var(--sp-48);
  height: var(--sp-48);
  display: grid;
  place-items: center;
  background: var(--surface-2);
  border: var(--bw) solid var(--line);
  padding: var(--sp-2);
}
.thumb__img--photo { width: 100%; height: 100%; }
/* Заглушка это иконка, а не снимок: она занимает не весь бокс, иначе
   схематичный контур выглядит как фотография низкого качества. */
.thumb__img--stub { width: var(--sp-28); height: var(--sp-28); opacity: .9; }

/* --- СТРОКА ВЫДАЧИ.
       Миниатюра стоит ВНУТРИ ячейки номера, и это главное решение блока.
       .row-item из 23-row.css это общий компонент четырёх списков (аналоги,
       кроссы, применяемость, выдача поиска), и у него уже есть варианты со
       своими наборами колонок (--compact, --buy) плюс две перестройки по
       медиазапросам. Отдельная колонка под картинку означала бы переписать
       grid-template-columns каждому из них и держать их в согласии дальше.
       Здесь же сетка строки не меняется вообще, а вариантов может появиться
       сколько угодно. --------------------------------------------------- */
.row-item__pn--media {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-12);
}
/* min-width: 0 обязателен: без него длинный номер распирает флекс-элемент и
   ячейка вылезает за свою колонку вместо переноса. */
.row-item__pn-body { min-width: 0; }

@media (max-width: 720px) {
  /* Здесь 23-row.css дописывает подпись колонки псевдоэлементом
     ([data-label]::before). Во флекс-контейнере он становится обычным
     элементом и встал бы слева от миниатюры, поэтому отдаём ему целую строку:
     подпись сверху, миниатюра и номер под ней. */
  .row-item__pn--media::before { flex-basis: 100%; }
}

/* --- ШАПКА КОЛОНКИ СРАВНЕНИЯ. Миниатюра над номером. ------------------- */
.cmp__thumb { margin-bottom: var(--sp-12); }

/* --- СТРОКА СПИСКА ПОДБОРА. Бокс .cart-row__media уже был (квадрат с фоном
       и внутренним отступом), менять его не нужно. Заглушке правило не нужно
       вовсе: прежнее .cart-row__img (max-width: 100%, height: auto) даёт ей
       ровно тот же размер, что и до появления фотографий. --------------- */
.cart-row__img--photo { width: 100%; height: 100%; }

/* --- 51-auth.css --- */
/* ============================================================================
   Вход, регистрация, восстановление пароля: /login, /register, /reset.

   Слой маленький намеренно. Формы собраны из атомов, которые уже есть:
   .field и .check из 21-field.css, .btn из 20-button.css, .card из 22-card.css,
   .alert и .eyebrow из общих слоёв, ловушка из .lead__trap. Своего здесь ровно
   две вещи: раскладка «форма слева, объяснение справа» и типографика правой
   тёмной колонки.
   ============================================================================ */

/* --- РАСКЛАДКА.
       В макете это repeat(auto-fit, minmax(340px, 1fr)) внутри контейнера на
       1600px, то есть на широком экране поле «Пароль» растягивается на 760
       пикселей. Поле ввода шире примерно 560 читается плохо: глаз теряет связь
       между подписью слева и курсором справа, а на форме входа это два поля
       из двух. Поэтому весь блок ограничен по ширине и центрирован, а колонки
       делят его пополам. ------------------------------------------------- */
.auth {
  max-width: 1100px;
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: var(--grid-gap-lg);
  /* start, а не stretch: на регистрации форма вдвое выше объяснения, и
     растянутая на её высоту тёмная плашка выглядит пустым прямоугольником. */
  align-items: start;
}

/* Одна колонка раньше общего порога 720: две колонки по 340 плюс отступы
   перестают помещаться уже на 900, и до 720 форма ужимается до нечитаемой. */
@media (max-width: 900px) {
  .auth { grid-template-columns: minmax(0, 1fr); }
}

/* --- ЛЕВАЯ КАРТОЧКА С ФОРМОЙ. ------------------------------------------- */
.auth__form { display: grid; gap: var(--sp-20); }

/* Отбивка после сообщения об ошибке или успехе: .alert стоит вне формы. */
.auth__card > .alert { margin-bottom: var(--sp-24); }

/* Организация и ИНН в одной строке: это одна мысль, и разнесённые по разным
   строкам они выглядят как два независимых вопроса. ИНН у́же: в нём 10 или 12
   цифр, и поле во всю ширину обещает длинное значение. */
.auth__pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 200px);
  gap: var(--sp-16);
}
/* align-content обязателен. .field это грид из подписи, поля и подсказки, и в
   растянутой по высоте ячейке лишняя высота расходится между строками: у поля
   с подсказкой строк три, у поля без неё две, и сами инпуты встают на разной
   высоте. Видно это только когда подсказка есть ровно у одного из пары, то
   есть ровно здесь. */
.auth__pair > .field { align-content: start; }
@media (max-width: 480px) {
  .auth__pair { grid-template-columns: minmax(0, 1fr); }
}

.auth__consent { margin-top: var(--sp-4); }
.auth__submit  { margin-top: var(--sp-4); }

.auth__links {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-8) var(--sp-20);
  font-size: var(--fs-md);
}

/* Кнопки в столбец, а не в ряд: их две, обе полноширинные, и в ряду они дают
   разную длину слов на одной высоте. */
.auth__actions { display: grid; gap: var(--sp-12); margin-top: var(--sp-24); }

/* Имя вошедшего. Кегль как у заголовка плашки: это единственная строка на
   карточке, и мельче она читается как подпись к пустому месту. */
.auth__who {
  margin-top: var(--sp-12);
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d4);
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* --- ПРАВАЯ ТЁМНАЯ КОЛОНКА.
       data-surface="dark" на плашке перекрашивает всё внутри сам (токены), так
       что своих цветов здесь нет ни одного: ни у заголовка, ни у списка, ни у
       ссылки на телефон. ----------------------------------------------------- */
.auth__aside-title {
  margin-top: var(--sp-16);
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d4);
  color: var(--ink);
}

.auth__aside-text {
  margin-top: var(--sp-20);
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

.auth__points { margin-top: var(--sp-24); font-size: var(--fs-lg); }

/* Приписка «чего у нас нет». Мельче списка и приглушена: это оговорка, а не
   обещание, и выглядеть она должна именно оговоркой. */
.auth__note {
  margin-top: var(--sp-20);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--ink-3);
}
.auth__note + .auth__note { margin-top: var(--sp-12); }

/* --- ПЕЧАТЬ. Страницу входа печатают разве что по ошибке, но пароль в
       распечатке формы это лишний повод. ---------------------------------- */
@media print {
  .auth__aside { display: none !important; }
  .auth { grid-template-columns: minmax(0, 1fr); }
}

/* --- 52-account.css --- */
/* ============================================================================
   Личный кабинет, карточка запроса, избранное. Слой 52.

   Ни одного цвета, кегля и отступа мимо токенов: слой обязан перекрашиваться
   вместе с data-surface, как и остальные. Скруглений нет, как везде в проекте
   (--radius: 0).

   Чего в этом слое НЕТ, потому что нет данных: плиток оборота и
   задолженности, полосы использованного лимита, строки скидки, таблицы парка
   техники, ленты доставки из пяти шагов, ссылок на PDF. Правило то же, что в
   слое 45: класса без разметки, а разметки без данных не заводим.

   Раскладка повторяет .split карточки товара, но своим классом: у кабинета
   боковая колонка стоит СЛЕВА на сводке и СПРАВА на карточке запроса, а
   переставлять чужой .split местами значит трогать карточку товара.
   ============================================================================ */

/* ---------------------------------------------------------- меню кабинета */

/* Горизонтальная полоса вкладок из макета, но ссылками, а не кнопками:
   в макете разделы переключались скриптом, здесь это четыре разных адреса,
   которые обязаны открываться в новой вкладке и попадать в закладки. */
.acc-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-24);
  margin-bottom: var(--sp-28);
  border-bottom: var(--bw) solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
}

.acc-nav__link {
  padding-bottom: var(--sp-12);
  /* Прозрачная граница стоит и у неактивного пункта: иначе активный пункт
     выше остальных на два пикселя, и полоса вкладок дёргается при переходе. */
  border-bottom: var(--bw-2) solid transparent;
  color: var(--ink-3);
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.acc-nav__link:hover { color: var(--ink); }
.acc-nav__link.is-active { border-bottom-color: var(--ink); color: var(--ink); }

/* ------------------------------------------------------------- раскладка */

/* Узкая колонка слева (сводка) либо справа (карточка запроса). Модификатор,
   а не order и не :has: порядок колонок обязан совпадать с порядком чтения
   с клавиатуры, а order разводит их молча. Разметка в обоих случаях одна,
   меняется только то, какая колонка стоит в шаблоне первой. */
.acc {
  display: grid;
  grid-template-columns: var(--col-split) minmax(0, 1fr);
  gap: var(--grid-gap-lg);
  align-items: start;
}
.acc--main-first { grid-template-columns: minmax(0, 1fr) var(--col-split); }

.acc__side,
.acc__main { min-width: 0; }

@media (max-width: 1180px) {
  .acc,
  .acc--main-first { grid-template-columns: 1fr; }
  /* На узком экране главное всегда сверху: телефон менеджера над списком
     запросов оттеснил бы сам список за первый экран. Здесь order уместен:
     колонок больше нет, и порядок чтения задаётся именно им. */
  .acc__main { order: 0; }
  .acc__side { order: 1; }
}

/* ------------------------------------------------------------------ сводка */

.acc-id__name {
  margin-top: var(--sp-12);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-d4);
  line-height: var(--lh-tight);
  overflow-wrap: anywhere;
}

.acc-id__facts {
  margin-top: var(--sp-20);
  padding-top: var(--sp-20);
  border-top: var(--bw) solid var(--line);
  font-size: var(--fs-md);
}

.acc-id__action { margin-top: var(--sp-20); }

/* Выход выглядит ссылкой, но остаётся кнопкой формы: GET-ссылка на /logout
   разлогинивает любого, кто открыл страницу с картинкой на этот адрес.

   Своего сброса кнопки здесь нет намеренно. Фон, рамку, отступы и курсор
   снимает 01-reset.css, а начертание и подчёркивание даёт .link--action из
   02-base.css. Повтор сброса в этом слое перекрывал бы .link--action по
   специфичности, и единственная кнопка-ссылка на сайте выглядела бы обычным
   абзацем, в отличие от таких же ссылок в шапках карточек рядом. */
.acc-id__logout { margin-top: var(--sp-16); }

/* Блок вместо скидки, отсрочки и лимита. Тёмный, потому что в макете на этом
   месте стояла тёмная плашка менеджера: композицию сохраняем, содержание
   заменяем на правдивое. */
.acc-terms { display: flex; flex-direction: column; }

.acc-terms__title {
  margin-top: var(--sp-16);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-d4);
  line-height: var(--lh-tight);
  color: var(--ink);
}

.acc-terms__text {
  margin: var(--sp-12) 0 0;
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* margin-top: auto прижимает кнопки к низу плашки: рядом стоит соседняя
   карточка, и разная высота двух плашек в одной колонке читается как брак. */
.acc-terms__action {
  margin-top: auto;
  padding-top: var(--sp-24);
  display: grid;
  gap: var(--sp-10);
}

/* Плитки метрик. Одна линия между ними даётся зазором сетки на фоне рамки,
   как в макете: отдельных границ у плиток нет, иначе на стыке две линии. */
.acc-kpi {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: var(--bw);
  background: var(--line);
  border: var(--bw) solid var(--line);
}

.acc-kpi .kpi {
  background: var(--surface);
  padding: var(--sp-24) var(--sp-28);
}

.acc-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--grid-gap);
}

.acc-card__value {
  margin-top: var(--sp-12);
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-d3);
  line-height: 1;
}

.acc-card__unit {
  margin-left: var(--sp-6);
  font-size: var(--fs-md);
  font-weight: var(--fw-regular);
  letter-spacing: normal;
  color: var(--ink-3);
}

.acc-card__text {
  margin: var(--sp-12) 0 var(--sp-20);
  font-size: var(--fs-md);
  color: var(--ink-2);
}

/* --------------------------------------------------------- пустые состояния */

.acc-empty { text-align: center; }

.acc-empty__title {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-d4);
}

.acc-empty__text {
  margin: var(--sp-12) auto 0;
  max-width: 52ch;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

.acc-empty__actions {
  margin-top: var(--sp-24);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-12);
  justify-content: center;
}

.acc-note { margin-top: var(--sp-20); }

/* Привязка обращения по номеру. Отдельная карточка под списком запросов:
   собственного оформления ей не нужно, кроме отступа сверху и ширины поля.
   Поле узкое намеренно: в него вставляют uuid, а не адрес, и растянутое на
   всю карточку оно читается как поиск по истории. */
.acc-link { margin-top: var(--sp-24); }
.acc-link__text {
  margin-top: var(--sp-8);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.acc-link .acc-form { margin-top: var(--sp-16); }
.acc-link .field { max-width: 34rem; }

.acc-guest,
.acc-relogin { margin-bottom: var(--sp-24); }

/* ------------------------------------------------------------ список запросов */

/* Строка это ссылка целиком: попасть в номер запроса на телефоне мимо
   двадцати пикселей текста невозможно. */
.req-row {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.6fr) auto;
  gap: var(--sp-16);
  align-items: center;
  padding: var(--sp-16) var(--card-pad-sm);
  border-bottom: var(--bw) solid var(--line-soft);
  color: var(--ink);
  transition: background-color var(--dur) var(--ease);
}
.req-row:hover { background: var(--surface-hover); color: var(--ink); }
.req-list > .req-row:last-child { border-bottom: 0; }

.req-row__id { min-width: 0; display: grid; gap: var(--sp-4); }

.req-row__num {
  font-size: var(--fs-lg);
  font-weight: var(--fw-mono-med);
  white-space: nowrap;
}

.req-row__date {
  font-size: var(--fs-mono-xs);
  color: var(--ink-3);
  white-space: nowrap;
}

.req-row__body { min-width: 0; display: grid; gap: var(--sp-4); }

.req-row__form { font-size: var(--fs-md); font-weight: var(--fw-semi); }

.req-row__items {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-xs);
  color: var(--ink-3);
  overflow-wrap: anywhere;
}

/* Состояние словом, а не цветной точкой: цвет не должен быть единственным
   носителем смысла. Цвет здесь дублирует текст, а не заменяет его. */
.req-row__status {
  justify-self: end;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  text-align: right;
  color: var(--ink-3);
}
.req-row__status--ok { color: var(--ok); }
.req-row__status--warn { color: var(--warn); }
.req-row__status--info { color: var(--accent-2); }

@media (max-width: 720px) {
  .req-row { grid-template-columns: minmax(0, 1fr) auto; }
  .req-row__body { grid-column: 1 / -1; }
}

/* -------------------------------------------------------- карточка запроса */

.req-head__state { margin-bottom: var(--sp-20); }
.req-head__facts { font-size: var(--fs-md); }

.req-item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-16);
  padding: var(--sp-16) var(--card-pad-sm);
  border-bottom: var(--bw) solid var(--line-soft);
}
.req-items > .req-item:last-child { border-bottom: 0; }

.req-item__main { min-width: 0; }

.req-item__pn {
  font-size: var(--fs-xl);
  font-weight: var(--fw-mono-med);
  overflow-wrap: anywhere;
}

.req-item__meta {
  margin-top: var(--sp-6);
  font-size: var(--fs-md);
  color: var(--ink-3);
}

.req-item__note {
  margin-top: var(--sp-8);
  padding-left: var(--sp-10);
  border-left: var(--bw-2) solid var(--line-strong);
  font-size: var(--fs-md);
  color: var(--ink-2);
}

/* Количество там же, где в макете стояла сумма строки. Это единственное
   число, которое у нас есть, и оно не про деньги. */
.req-item__qty {
  flex: none;
  font-size: var(--fs-lg);
  color: var(--ink-2);
  white-space: nowrap;
}

.req-comment { border-top: var(--bw) solid var(--line); }
.req-comment__text {
  margin: var(--sp-10) 0 0;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
}

/* ------------------------------------------------------------------ лента */

.feed { padding: var(--sp-8) var(--card-pad-sm); }

.feed__item {
  display: grid;
  grid-template-columns: 150px minmax(0, 1fr);
  gap: var(--sp-20);
  padding: var(--sp-12) 0;
  border-bottom: var(--bw) solid var(--line-soft);
}
.feed__item:last-child { border-bottom: 0; }

.feed__at {
  font-size: var(--fs-mono-xs);
  color: var(--ink-3);
  white-space: nowrap;
}

.feed__body { min-width: 0; }
.feed__title { font-size: var(--fs-md); font-weight: var(--fw-semi); }

.feed__text {
  margin: var(--sp-6) 0 0;
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--ink-2);
  overflow-wrap: anywhere;
}

/* Своё сообщение отбито слева полосой: в ленте из десяти строк надо видеть,
   где говорил ты, а где менялось состояние заявки. */
.feed__item--own .feed__body {
  padding-left: var(--sp-12);
  border-left: var(--bw-2) solid var(--accent-line);
}

@media (max-width: 720px) {
  .feed__item { grid-template-columns: 1fr; gap: var(--sp-6); }
}

.req-form__submit { margin-top: var(--sp-16); }
.req-form + .note { margin-top: var(--sp-16); }

/* ------------------------------------------------------------------ профиль */

.acc-forms {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--grid-gap);
  align-items: start;
}

.acc-form__title {
  margin: var(--sp-10) 0 var(--sp-24);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-d4);
  line-height: var(--lh-tight);
}

.acc-form { display: grid; gap: var(--sp-20); }
.acc-form__submit { margin-top: var(--sp-4); }

/* ----------------------------------------------------------------- избранное */

.fav-row {
  display: grid;
  grid-template-columns: 72px minmax(220px, 1fr) auto;
  gap: var(--sp-16);
  align-items: center;
  padding: var(--sp-16) var(--card-pad-sm);
  border-bottom: var(--bw) solid var(--line-soft);
  transition: background-color var(--dur) var(--ease);
}
.fav-row:hover { background: var(--surface-hover); }
.fav-list > .fav-row:last-child { border-bottom: 0; }

.fav-row__media {
  background: var(--surface-2);
  display: grid;
  place-items: center;
  padding: var(--sp-4);
  aspect-ratio: 1;
}
.fav-row__img { max-width: 100%; height: auto; }

.fav-row__main { min-width: 0; }

.fav-row__pn {
  font-family: var(--font-mono);
  font-size: var(--fs-xl);
  font-weight: var(--fw-mono-med);
  overflow-wrap: anywhere;
}

.fav-row__meta {
  margin-top: var(--sp-6);
  font-size: var(--fs-md);
  color: var(--ink-3);
}

.fav-row__state {
  margin-top: var(--sp-8);
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-12);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
}

/* "Цена по запросу" стоит там же, где в макете стояла цена, и тем же местом
   в чтении. Кегль мельче, потому что это не число. */
.fav-row__price { color: var(--ink-2); }

.fav-row__warn {
  margin-top: var(--sp-8);
  font-size: var(--fs-md);
  color: var(--warn);
}

.fav-row__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-12);
}

.fav-row__remove {
  flex: none;
  width: var(--tap-min);
  height: var(--tap-min);
  display: grid;
  place-items: center;
  border: 0;
  background: transparent;
  color: var(--ink-3);
  font-size: var(--fs-4xl);
  line-height: 1;
  cursor: pointer;
  transition: color var(--dur) var(--ease);
}
.fav-row__remove:hover { color: var(--danger); }

@media (max-width: 900px) {
  .fav-row { grid-template-columns: 64px minmax(0, 1fr); row-gap: var(--sp-12); }
  .fav-row__actions { grid-column: 1 / -1; justify-content: flex-start; }
  .fav-row__remove { margin-left: auto; }
}

@media (max-width: 480px) {
  .fav-row { grid-template-columns: minmax(0, 1fr); }
  .fav-row__media { display: none; }
}

/* --- 53-kits.css --- */
/* ============================================================================
   Комплекты ТО: карточка комплекта в сетке, состав комплекта, шаги сборки.
   Слой подхватывается сборщиком сам: имя начинается с числового префикса.

   Всё остальное на обеих страницах это уже существующие компоненты: .card,
   .card--link, .grid, .split, .stack, .buy, .deflist, .bullets, .alert,
   .callout, .lede, .eyebrow, .mono, герой и крошки из macros/site.

   Чего здесь нет и не будет: строки цены комплекта и суммы. В макете под
   каждой карточкой стоит "18 400 ₽", но цены нет ни у одной из 146 322
   позиций, и класс под сумму означал бы вёрстку, которую нечем наполнить.
   ============================================================================ */

/* --- КАРТОЧКА КОМПЛЕКТА В СЕТКЕ.
       Ссылка целиком, поэтому все потомки инлайновые по разметке (span), а
       раскладку задаёт flex. Колонка, а не grid: подвал прижимается к низу
       через margin-top: auto, и карточки с четырьмя и шестью позициями
       стоят в ряду одинаковой высоты. ----------------------------------- */
.kgrid { --col-min: 340px; }

.kcard {
  display: flex;
  flex-direction: column;
  gap: var(--sp-20);
  padding: var(--card-pad);
  height: 100%;
  transition: border-color var(--dur) var(--ease), transform var(--dur-slow) var(--ease),
              box-shadow var(--dur-slow) var(--ease);
}
.kcard:hover { border-color: var(--line-strong); }

.kcard__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-16);
}

.kcard__title {
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d4);
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--ink);
}

/* Число позиций моно-микроподписью, как остальные счётчики сайта.
   Цвет из --accent-text, а не --accent-line: это ТЕКСТ, и на белом ему нужен
   контраст 4.95:1, который даёт только #9E6300. */
.kcard__count {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--accent-text);
}

/* Марка и модель отдельной строкой и только тогда, когда она НЕ повторяет
   заголовок: у комплекта "Komatsu PC300" техника и название совпадают, и
   вторая такая же строка выглядит как ошибка вёрстки. */
.kcard__vehicle {
  margin-top: calc(var(--sp-12) * -1);
  font-size: var(--fs-md);
  color: var(--ink-3);
}

.kcard__list { display: grid; gap: var(--sp-10); }

.kcard__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-12);
  padding-bottom: var(--sp-10);
  border-bottom: var(--bw) solid var(--line-soft);
  font-size: var(--fs-base);
}
.kcard__row:last-child { border-bottom: 0; padding-bottom: 0; }
.kcard__row--more { color: var(--ink-3); font-size: var(--fs-md); }

.kcard__role { color: var(--ink-2); min-width: 0; overflow-wrap: anywhere; }

.kcard__pn {
  flex: none;
  font-size: var(--fs-mono-sm);
  color: var(--ink-3);
}

.kcard__foot {
  margin-top: auto;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-16);
  padding-top: var(--sp-16);
  border-top: var(--bw) solid var(--line);
  font-size: var(--fs-md);
}

/* Не кнопка: карточка и так ссылка целиком, а <a> внутри <a> невозможен.
   Стрелка рисуется псевдоэлементом, чтобы не попасть в буфер при копировании
   и не читаться скринридером вслух. */
.kcard__go { font-weight: var(--fw-semi); color: var(--accent-text); }
.kcard__go::after { content: " \2192"; }

/* --- КАК СОБИРАЕТСЯ КОМПЛЕКТ.
       Блок работает и как объяснение при пустом разделе, и как ответ на
       "нужной техники нет" при заполненном. --------------------------- */
.kits-how__lede { max-width: 62ch; }

.kits-steps { display: grid; gap: var(--sp-24); }

.kits-steps__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-16);
  align-items: start;
}

/* Номер шага в квадрате, а не в круге: скруглений в макетах нет нигде,
   кроме значка корзины. */
.kits-steps__n {
  display: grid;
  place-items: center;
  width: var(--h-xs);
  height: var(--h-xs);
  background: var(--accent);
  color: var(--ink-on-accent);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-md);
  font-weight: var(--fw-mono-med);
}

.kits-steps__body { display: grid; gap: var(--sp-4); }
.kits-steps__title {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--ls-tight);
}
.kits-steps__text { font-size: var(--fs-lg); line-height: var(--lh-body); color: var(--ink-2); }

/* --- СОСТАВ КОМПЛЕКТА.
       Дивами, а не таблицей, по той же причине, что списки аналогов и
       кроссов (.rows в 23-row.css): таблица на узком экране уезжает в
       горизонтальную прокрутку, а этот список перестраивается. ---------- */
.kit-list { display: grid; border-top: var(--bw) solid var(--line); }

.kit-list__head,
.kit-list__row {
  display: grid;
  grid-template-columns: minmax(140px, 200px) minmax(0, 1fr) auto;
  gap: var(--sp-16);
  align-items: baseline;
  padding: var(--sp-16) 0;
  border-bottom: var(--bw) solid var(--line-soft);
}

.kit-list__head {
  padding-block: var(--sp-10);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}

.kit-list__role { font-weight: var(--fw-semi); }

.kit-list__item { display: grid; gap: var(--sp-4); min-width: 0; }
.kit-list__pn { font-size: var(--fs-xl); overflow-wrap: anywhere; }

/* Позиция выпала из ассортимента после пересборки каталога. Цвет не
   единственный носитель смысла: рядом стоит слово. */
.kit-list__gone { font-size: var(--fs-md); color: var(--warn); }
.kit-list__note { font-size: var(--fs-md); color: var(--ink-3); }

.kit-list__qty { font-size: var(--fs-mono-sm); color: var(--ink-2); white-space: nowrap; }

/* --- ПРАВАЯ КОЛОНКА КОМПЛЕКТА.
       Цены нет, поэтому на её месте стоит фраза тем же кеглем, каким на
       карточке товара набрана цена: это ответ на тот же вопрос. --------- */
.kit-buy__price {
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d4);
}
.kit-buy__note { font-size: var(--fs-md); line-height: var(--lh-body); color: var(--ink-3); }

/* --- ОБЛОЖКА. Её нет ни у одного комплекта, блок появляется только когда
       менеджер задал адрес картинки. -------------------------------------- */
.kit-cover { margin: 0; background: var(--surface-2); border: var(--bw) solid var(--line); }
.kit-cover img { display: block; width: 100%; height: auto; }

@media (max-width: 720px) {
  .kgrid { --col-min: 100%; }
  .kit-list__head { display: none; }
  .kit-list__row {
    grid-template-columns: minmax(0, 1fr) auto;
    row-gap: var(--sp-4);
  }
  /* Роль уезжает над номером и становится подписью строки: заголовков
     колонок на узком экране уже нет. */
  .kit-list__role { grid-column: 1 / -1; font-size: var(--fs-md); color: var(--ink-3); }
}

/* --- 54-content.css --- */
/* ============================================================================
   Разделы материалов: /blog, /cases, /careers и страница материала.

   Все цвета и отступы через токены 00-tokens.css. Собственных значений здесь
   ровно три, и все три в ch или px по причине, которую токеном не выразить:
   минимальная ширина карточки ленты, ширина колонки прозы и ширина колонки
   соседних материалов.

   Проза материала переиспользует .prose из 46-static.css: абзац, заголовок и
   список у текстовой страницы и у статьи это один и тот же объект, и вторая
   копия правил разъехалась бы на первой же правке кегля.
   ============================================================================ */

/* --- ЛЕНТА БЛОГА И КЕЙСОВ ------------------------------------------------- */

/* 320px, а не --col-tile (260): в карточке ленты живёт лид на две-три строки,
   и на 260 он превращается в столбик по три слова. */
.agrid { --col-min: 320px; }

.acard {
  display: flex;
  flex-direction: column;
  color: inherit;
  text-decoration: none;
}
.acard:hover { border-color: var(--line-strong); }

.acard__cover {
  background: var(--surface-2);
  border-bottom: var(--bw) solid var(--line);
}
.acard__cover img {
  display: block;
  width: 100%;
  height: 200px;
  object-fit: cover;
}

.acard__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-12);
  flex: 1;
  padding: var(--card-pad-sm);
}

.acard__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-16);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-3xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}

.acard__title {
  font-size: var(--fs-4xl);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
  color: var(--ink);
}
.acard__lead {
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* Прижимается к низу карточки, поэтому у карточек разной высоты подпись
   «Читать» стоит на одной линии. Стрелка рисуется здесь, а не в разметке:
   в тексте она читалась бы скринридером как «стрелка вправо». */
.acard__go {
  margin-top: auto;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--accent-text);
}
.acard__go::after { content: " →"; }

/* --- СПИСОК ВАКАНСИЙ ------------------------------------------------------ */

.vrow {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--grid-gap-lg);
  align-items: center;
  padding: var(--sp-32) var(--card-pad);
}
.vrow + .vrow { border-top: var(--bw) solid var(--line-soft); }

.vrow__title {
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  line-height: var(--lh-snug);
  letter-spacing: var(--ls-tight);
}
.vrow__title a { color: var(--ink); text-decoration: none; }
.vrow__title a:hover { color: var(--accent-text); }

/* Надзаголовок вакансии акцентный: «Уфа · офис · от 80 000 ₽» это то, ради
   чего строку вообще открывают. Цвет не единственный носитель смысла, это
   просто подпись, поэтому усиления формой здесь не нужно. */
.vrow__meta {
  margin-top: var(--sp-10);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--accent-text);
}
.vrow__lead {
  margin-top: var(--sp-12);
  max-width: 70ch;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.vrow__action { display: flex; justify-content: flex-end; }

@media (max-width: 720px) {
  .vrow { grid-template-columns: minmax(0, 1fr); gap: var(--sp-20); }
  .vrow__action { justify-content: stretch; }
  .vrow__action > * { flex: 1 1 100%; }
}

/* --- СТРАНИЦА МАТЕРИАЛА --------------------------------------------------- */

.material {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--col-split);
  gap: var(--grid-gap-lg);
  align-items: start;
}
@media (max-width: 1180px) {
  .material { grid-template-columns: minmax(0, 1fr); }
}

.material__cover {
  background: var(--surface-2);
  border: var(--bw) solid var(--line);
}
.material__cover img {
  display: block;
  width: 100%;
  max-height: 420px;
  object-fit: cover;
}

.material__date { margin-top: var(--sp-24); }

/* 68ch, а не 65ch как у текстовой страницы: у статьи кегль абзаца тот же, но
   читают её подряд и целиком, и чуть более длинная строка здесь выигрывает
   меньше переносов на средних экранах. */
.material__prose { margin-top: var(--sp-16); max-width: 68ch; }

/* Заголовки внутри тела. Отбивка сверху нужна своя: на текстовой странице
   разделы обёрнуты в .prose__sec и разводятся правилом «секция + секция», а
   тело материала это плоский поток абзацев, списков и заголовков, где h2
   иначе прилипает к предыдущему абзацу.
   Кегль тоже свой и на ступень крупнее, чем на текстовой странице: там
   заголовок это пункт документа, здесь это раздел статьи, который ищут
   глазами при перечитывании. */
.material__prose .prose__h { margin-top: var(--sp-40); font-size: var(--fs-d5); }
.material__prose h3.prose__h { margin-top: var(--sp-32); font-size: var(--fs-d6); }
.material__prose > :first-child { margin-top: 0; }

/* Ссылка внутри тела. Подчёркивание обязательно: цвет не единственный признак
   ссылки, а внутри абзаца её иначе не отличить от выделения. */
.material__prose a {
  color: var(--accent-text);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.material__prose a:hover { color: var(--ink); }

/* Нумерованный список. Маркер рисует счётчик, а не list-style: у нативного
   маркера нельзя задать цвет и начертание отдельно от текста, а сама раскладка
   пункта здесь grid (см. .prose__li в 46-static.css), и нативный маркер в ней
   не показывается вовсе. */
.prose__list--num { counter-reset: prose-num; }
.prose__li--num { counter-increment: prose-num; }
.prose__li--num::before {
  content: counter(prose-num) ".";
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  color: var(--accent-line);
}

.prose__quote {
  margin-top: var(--sp-20);
  padding: var(--sp-16) var(--sp-24);
  border-left: 3px solid var(--accent-line);
  background: var(--surface-2);
  font-size: var(--fs-xl);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* --- СОСЕДНИЕ МАТЕРИАЛЫ --------------------------------------------------- */

.material__aside { position: sticky; top: calc(var(--h-header) + var(--sp-16)); }
@media (max-width: 1180px) { .material__aside { position: static; } }

.nbr-list { margin-top: var(--sp-16); display: grid; gap: var(--sp-16); }
.nbr { display: grid; gap: var(--sp-6); text-decoration: none; }
.nbr__title {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-snug);
  color: var(--ink);
}
.nbr:hover .nbr__title { color: var(--accent-text); }
.nbr__date {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-3xs);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* --- ПЕЧАТЬ. Вакансию и статью распечатывают, чтобы отдать механику. ------- */
@media print {
  .material { grid-template-columns: minmax(0, 1fr); }
  .material__aside, .acard__go { display: none !important; }
  .material__prose { max-width: none; }
  .prose__sec, .prose__quote, .vrow { break-inside: avoid; }
}

/* --- 55-misc.css --- */
/* ============================================================================
   Благодарность за запрос счёта. Слой 55.

   Слой маленький намеренно. Раскладку страницы держит .quote из 45-cart.css
   (та же ось "содержимое плюс узкая сводка"), карточки, плашку, кнопки и
   список позиций дают существующие компоненты. Здесь только то, чего в
   проекте ещё не было: печать подтверждения, нумерованные шаги и подпись к
   строке состава.

   Ни одного цвета и кегля мимо токенов: страница обязана перекраситься
   вместе с data-surface, как и остальные.
   ============================================================================ */

/* --- ПОДТВЕРЖДЕНИЕ. Печать слева, текст справа. Квадрат, а не круг:
       --radius в проекте нулевой везде, кроме бейджа корзины. --------- */
.tnx {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-24);
  align-items: start;
}
.tnx__seal {
  display: grid;
  place-items: center;
  width: var(--h-2xl);
  height: var(--h-2xl);
  background: var(--accent);
  color: var(--ink-on-accent);
}
.tnx__title {
  font-size: var(--fs-d5);
  font-weight: var(--fw-black);
  line-height: var(--lh-head);
  letter-spacing: var(--ls-d4);
}
.tnx__lead {
  margin-top: var(--sp-12);
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
/* Оговорка про срок. Кегль и цвет даёт .note из 25-misc.css, здесь только
   отступ: свой у .note отсутствует, она стоит под заглушкой изображения. */
.tnx__note { margin-top: var(--sp-8); }

/* Номер обращения. Переносим по любому месту (overflow-wrap), потому что это
   uuid без пробелов: на телефоне он иначе распирает карточку и уводит всю
   страницу в горизонтальную прокрутку. user-select: all нужен ровно затем,
   ради чего номер и показан: одно нажатие выделяет его целиком под копирование
   в форму привязки. */
.tnx__number {
  margin-top: var(--sp-8);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  overflow-wrap: anywhere;
  user-select: all;
}

.tnx__number + .note { margin-top: var(--sp-8); }

.tnx__claim { margin-top: var(--sp-20); }

/* Печать уезжает над текстом раньше, чем ломается сетка страницы: при
   --card-pad 20px квадрат 60px съедает треть ширины карточки. */
@media (max-width: 480px) {
  .tnx { grid-template-columns: 1fr; gap: var(--sp-16); }
}

/* --- ШАГИ. Настоящий <ol>: порядок здесь смысловой, скринридер обязан
       прочитать "список из трёх пунктов", а не три абзаца. Число рисуем
       своё, потому что маркер списка не выравнивается по базовой линии
       текста и не берёт моно-начертание. -------------------------------- */
.steps {
  display: grid;
  gap: var(--sp-20);
  margin: var(--sp-20) 0 0;
  padding: 0;
  list-style: none;
}
.steps__item {
  display: grid;
  grid-template-columns: var(--h-xs) minmax(0, 1fr);
  gap: var(--sp-16);
  align-items: start;
  font-size: var(--fs-lg);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.steps__n {
  display: grid;
  place-items: center;
  width: var(--h-xs);
  height: var(--h-xs);
  border: var(--bw) solid var(--line);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-md);
  color: var(--ink-3);
}

/* --- КНОПКИ ПОД ШАГАМИ ------------------------------------------------- */
.tnx__actions { display: flex; flex-wrap: wrap; gap: var(--sp-12); }

/* Отступ от надзаголовка "Обращение". У .deflist своего верхнего поля нет:
   на карточке товара она стоит следом за ценой, где отступ даёт .buy. */
.tnx__facts { margin-top: var(--sp-16); }

/* --- ПОДПИСЬ К СТРОКЕ СОСТАВА. Комментарий снабженца к позиции
       ("нужен с обратным клапаном"). Колонка задана явно: в .quote-items__row
       вторую колонку занимает количество, и автоматическая расстановка
       положила бы подпись под него. --------------------------------------- */
.quote-items__note {
  grid-column: 1;
  font-size: var(--fs-sm);
  line-height: var(--lh-snug);
  color: var(--ink-3);
  overflow-wrap: anywhere;
}

/* --- 56-price.css --- */
/* ============================================================================
   ЦЕНА И ПОКУПКА В СПИСКАХ. Слой появился вместе с первыми ценами в
   site.offer и держит ровно одну задачу: список, в котором ОДНОВРЕМЕННО
   есть позиции с ценой и позиции без неё, обязан выглядеть собранным.

   Цена сейчас заведена у пяти позиций из 146 322, и обмен с 1С добавит их
   тысячами, но не все. То есть смешанный список это не переходное состояние
   и не край, а основной режим на годы вперёд. Отсюда всё, что ниже:

     * место под цену резервируется на ВСЮ страницу сразу (модификаторы
       .rows--priced и .row-item--priced приходят из шаблона по флагу
       has_priced), а не решается по каждой строке. Иначе строки стоят в двух
       разных сетках, колонки разъезжаются по вертикали, и список читается как
       сломанный;
     * "по запросу" стоит на месте цены и тем же выравниванием, но тише и
       мельче: тем же весом эта строка читается как ценник, которого нет.

   Кнопок сохранения этот слой НЕ касается: они стоят у каждой строки и каждой
   плитки независимо от цены, их раскладка живёт в 24-save.css. Здесь про них
   есть ровно одно правило, ширина колонки в семиколонной сетке.

   Свои стили здесь только у того, что добавила цена. Сетку строки, адаптив и
   подписи колонок по-прежнему даёт 23-row.css, плитку 41-catalog.css.
   ============================================================================ */

/* --- СТРОКА ВЫДАЧИ ПОИСКА С ЦЕНОЙ --------------------------------------

   Колонок семь: номер, бренд, тип, наличие, цена, сохранение, переход. Без
   цены их шесть (23-row.css). Шапка и строки получают одну и ту же дорожку
   одним правилом: две разошедшиеся записи это подписи не над своими
   колонками, и заметно это не сразу, а на выдаче с длинным номером.

   Переход тянется по своей колонке, а не фиксирован в 132px, как в
   23-row.css: колонка цены и колонка сохранения съедают ту ширину, на
   которой стоят номер и бренд.

   gap 12, а не 16: на семи колонках лишние четыре пикселя между ними это
   24 пикселя, отобранные у текста. ---------------------------------------- */
.rows--priced .rows__head,
.row-item--priced {
  grid-template-columns:
    minmax(0, 1.15fr) minmax(0, .85fr) minmax(0, .75fr) minmax(0, .8fr)
    minmax(88px, auto) 168px minmax(0, 124px);
  gap: var(--sp-12);
}

/* Подпись колонки цены выравнивается по самой цене, иначе она стоит над
   пустым местом слева от чисел. */
.rows__head-price { text-align: right; }

.row-item--priced .row-item__action .btn { width: 100%; }

/* --- 900 ... 1180. Семь колонок сюда уже не помещаются: боковая колонка
       фасетов забирает 240px, и на текст остаётся меньше сотни пикселей на
       колонку. Поэтому строка складывается в две: номер во всю высоту,
       бренд и наличие столбиком, цена и обе кнопки справа.

       Тип фильтра в этом диапазоне уходит: он единственный, что дублируется
       на странице полосой уточнения над списком, и терять его дешевле, чем
       номер или бренд. Шапка таблицы вместе с ним: подписывать двухрядную
       строку одной строкой заголовков нельзя, а подписи возвращает
       [data-label] из 23-row.css ниже 720. -------------------------------- */
@media (max-width: 1180px) {
  .rows--priced .rows__head { display: none; }

  .row-item--priced {
    grid-template-columns:
      minmax(0, 1.4fr) minmax(0, 1fr) minmax(88px, auto) 168px;
    grid-template-areas:
      "pn brand price save"
      "pn stock price act";
    row-gap: var(--sp-8);
    align-items: center;
  }
  .row-item--priced .row-item__pn     { grid-area: pn; }
  .row-item--priced .row-item__brand  { grid-area: brand; }
  .row-item--priced .row-item__stock  { grid-area: stock; }
  .row-item--priced .row-item__price  { grid-area: price; }
  .row-item--priced .row-item__save   { grid-area: save; }
  .row-item--priced .row-item__action { grid-area: act; }
  .row-item--priced .srch-type        { display: none; }
}

/* --- 720 ... 900. Две колонки: слева номер, бренд и наличие, справа цена и
       кнопки. Правило 23-row.css на этой ширине уводит "Открыть" в отдельную
       колонку через grid-row: 1 / span 2, и вместе с кнопкой покупки это
       давало бы наложение: обе кнопки в одной клетке. Поэтому здесь свои
       области. -------------------------------------------------------- */
@media (max-width: 900px) {
  .row-item--priced {
    grid-template-columns: minmax(0, 1fr) 168px;
    grid-template-areas:
      "pn    price"
      "brand save"
      "stock act";
    align-items: center;
  }
}

/* --- ПОДВАЛ ПЛИТКИ ТОВАРА В СЕТКЕ ---------------------------------------

   Цена, наличие и кнопка живут в подвале плитки, а не в её теле, по причине
   разметки: тело накрыто ссылкой на карточку, а <form> с кнопкой внутри
   ссылки это невалидная разметка, по которой браузеры расходятся.

   Подвал прижат к низу плитки (.ptile это grid с дорожками 1fr auto, см.
   41-catalog.css), поэтому кнопки соседних плиток стоят на одной линии
   независимо от того, во сколько строк лёг номер выше. ------------------- */
.ptile__foot {
  display: grid;
  gap: var(--sp-8);
  align-content: end;
  padding: var(--sp-16) var(--sp-20) var(--sp-20);
  border-top: var(--bw) solid var(--line-soft);
}

.ptile__price {
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-tight);
  font-variant-numeric: tabular-nums;
}

/* "Цена по запросу" это ШТАТНОЕ состояние подавляющего большинства плиток,
   а не заглушка. Кегль ступенью мельче настоящей цены: тем же кеглем эта
   строка читается как ценник. */
.ptile__request {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
  color: var(--ink-3);
}

/* Слот кнопок сохранения (.ptile__save) живёт в подвале, но описан в
   24-save.css вместе с остальными тремя своими местами: от цены он не зависит,
   а этот слой про деньги. */

@media (max-width: 480px) {
  .ptile__foot { padding: var(--sp-12) var(--sp-16) var(--sp-16); }
}

/* --- СТРОКА ЦЕНЫ В СРАВНЕНИИ --------------------------------------------

   Строка рисуется, только когда цена есть хоть у одной сравниваемой позиции:
   четыре "по запросу" во всю ширину таблицы это не сравнение, а вопрос,
   почему пусто. Решает это шаблон, здесь только оформление.

   Цена набирается тем же кеглем, что номер в шапке колонки: колонку чисел
   читают сверху вниз, сравнивая, и разнобой кеглей этому мешает. ---------- */
.cmp__price {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-tight);
  font-variant-numeric: tabular-nums;
}

/* Отступ снизу у .cmp__request заведён под подвал таблицы, где следом идёт
   кнопка. В строке цены под ним ничего нет. */
.cmp__row:not(.cmp__row--foot) .cmp__request { margin-bottom: 0; }

/* --- 57-test.css --- */
/* ============================================================================
   ВРЕМЕННАЯ страница /test: позиции с заведённой ценой.
   Удаляется целиком вместе с роутом, когда цены поедут из 1С.

   Своего компонента списка здесь нет: строка это .rows / .row-item из
   23-row.css с миниатюрой из 50-media.css, ровно как в выдаче поиска. Ниже
   остаётся только то, чего у той строки нет: седьмая колонка (цена рядом с
   формой покупки) и сама форма.
   ============================================================================ */

/* Колонок на одну больше, чем у строки выдачи: там нет цены, а последняя
   колонка отдана под кнопку шириной 132px, куда поле количества не влезает.
   204px это не подобранное на глаз число, а сумма: поле 64 + зазор 8 +
   кнопка 132, где ширину кнопки жёстко задаёт .row-item__action .btn из
   23-row.css. Занизить её значит наложить цену на поле количества.

   Правило намеренно живёт в min-width: 901px. Ниже этого порога раскладку
   строки задаёт 50-media.css из своего @media, а этот файл подключается
   позже: безусловное объявление перебило бы его и на телефоне. */
@media (min-width: 901px) {
  .rows__head--test,
  .test-row {
    grid-template-columns:
      var(--sp-56) minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, .9fr) minmax(0, .8fr) 204px;
  }
}

/* Бренд и артикул стоят под номером, внутри его ячейки: отдельные колонки на
   пять строк списка съели бы ширину у цены и у формы. */
.test-row .row-item__brand,
.test-row__sku { display: block; }
.test-row .row-item__brand { font-family: var(--font-sans); font-size: var(--fs-md); }
.test-row__sku {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  color: var(--ink-3);
}

.test-row__type { font-size: var(--fs-md); color: var(--ink-3); }

/* Форма покупки. Кнопка .btn--sm высотой --h-sm, поле рядом обязано быть той
   же высоты, иначе строка выглядит собранной из двух разных элементов. */
.test-row__buy { display: flex; align-items: center; gap: var(--sp-8); }
.test-row__qty {
  width: 64px;
  height: var(--h-sm);
  padding-inline: var(--sp-8);
  text-align: center;
  border: var(--bw) solid var(--line);
  background: var(--surface);
  color: var(--ink);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-sm);
}
.test-row__qty:focus { border-color: var(--field-line-focus); }

.test-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-16);
  padding: var(--sp-16) 0;
  border-top: var(--bw) solid var(--line);
  font-size: var(--fs-lg);
}

@media (max-width: 720px) {
  /* Строка стала блоком, колонки больше нет. justify-self: end, который
     23-row.css ставит ячейке грида, Chrome применяет и в блочной раскладке:
     форма сжимается до содержимого и уезжает вправо, пока весь остальной
     текст строки прижат влево. Возвращаем её на общую левую границу. */
  .test-row .row-item__action {
    justify-self: stretch;
    margin-top: var(--sp-12);
  }
  .test-row__buy { justify-content: flex-start; }
}

/* --- 58-helper.css --- */
/* ============================================================================
   filtrov.net / 58-helper.css
   Виджет «Помощь инженера»: модальное окно и та же форма страницей /help.

   Все цвета и размеры взяты из токенов. Своё значение здесь одно: потолок
   ширины окна. Скруглений нет: --radius в проекте нулевой везде.

   Разметка и порядок: app/templates/macros/helper.html.
   ============================================================================ */

/* --- ОКНО И СТРАНИЦА ---------------------------------------------------- */

/* Закрытый <dialog> не отображается сам, поэтому при выключенном скрипте
   разметка окна никому не мешает. Ширину задаём здесь, а не на .hlp__box:
   у <dialog> своя ширина по содержимому, и без потолка окно растягивается на
   всю страницу. */
dialog.hlp {
  width: min(560px, calc(100vw - 2 * var(--sp-24)));
  max-width: none;
  max-height: min(92vh, 900px);
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  overflow: visible;
}

dialog.hlp::backdrop { background: rgba(10, 13, 18, .62); }

/* Форма страницей: тот же блок, но в потоке и с рамкой вместо тени. */
.hlp--page { width: 100%; max-width: 640px; }

.hlp__box {
  display: flex;
  flex-direction: column;
  max-height: inherit;
  background: var(--c-white);
  border: var(--bw) solid var(--c-line-200);
  box-shadow: var(--shadow-pop);
}

dialog.hlp .hlp__box { max-height: min(92vh, 900px); }

/* Тёмная шапка. data-surface="dark" уже перекрасил переменные внутри, здесь
   остаётся только фон и раскладка. */
.hlp__head {
  position: relative;
  flex: 0 0 auto;
  padding: var(--sp-24) var(--sp-56) var(--sp-20) var(--sp-24);
  background: var(--c-graphite-800);
}

.hlp__eyebrow {
  margin-bottom: var(--sp-10);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  font-weight: var(--fw-mono-med);
  letter-spacing: var(--ls-mono-lg);
  text-transform: uppercase;
  color: var(--c-yellow-400);      /* 10.8:1 на графите */
}

.hlp__title {
  margin: 0;
  font-size: var(--fs-d6);
  font-weight: var(--fw-black);
  letter-spacing: var(--ls-d4);
  line-height: var(--lh-snug);
  color: var(--c-white);
}

.hlp__lead {
  margin: var(--sp-10) 0 0;
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--c-steel-200);       /* 8.0:1 на графите */
}

.hlp__hours {
  margin: var(--sp-12) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-mono-2xs);
  letter-spacing: var(--ls-mono-sm);
  text-transform: uppercase;
  color: var(--c-steel-300);       /* 5.72:1 на графите */
}

/* Крестик. Зона нажатия 44px по обеим сторонам, иначе на телефоне закрыть
   окно попадают не с первого раза. */
.hlp__close {
  position: absolute;
  top: var(--sp-12);
  right: var(--sp-12);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-min);
  height: var(--tap-min);
  padding: 0;
  background: none;
  border: 0;
  color: var(--c-steel-200);
  cursor: pointer;
}

.hlp__close:hover { color: var(--c-white); }


/* --- ВКЛАДКИ ------------------------------------------------------------ */

/* Ссылки, а не кнопки: на /help без скрипта переключать панели больше нечем.
   Скрипт перехватывает нажатие и меняет панель на месте. */
.hlp__body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--sp-24);
  background: var(--c-white);
}

.hlp-tabs {
  display: flex;
  gap: var(--sp-4);
  margin: 0 0 var(--sp-20);
  border-bottom: var(--bw) solid var(--line);
}

.hlp-tabs__tab {
  flex: 1 1 0;
  min-height: var(--h-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 var(--sp-8);
  margin-bottom: -1px;
  border-bottom: var(--bw-2) solid transparent;

  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  color: var(--ink-2);
  text-decoration: none;
  text-align: center;
}

.hlp-tabs__tab:hover { color: var(--ink); }

.hlp-tabs__tab.is-active {
  color: var(--ink);
  border-bottom-color: var(--c-yellow-400);
}


/* --- ФОРМА -------------------------------------------------------------- */

.hlp-form { display: flex; flex-direction: column; gap: var(--sp-16); }

/* Две колонки там, где поля короткие (имя и телефон, марка и модель).
   Ниже 520px схлопываются: два поля в ряд на телефоне дают ввод в 12 знаков
   шириной. */
.hlp-form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-12);
}

@media (max-width: 520px) {
  .hlp-form__row { grid-template-columns: 1fr; }
}

.hlp-form__consent { margin-top: calc(-1 * var(--sp-4)); }
.hlp-form__go { margin-top: var(--sp-4); }

@media (max-width: 720px) {
  .hlp__head { padding: var(--sp-20) var(--sp-48) var(--sp-16) var(--sp-20); }
  .hlp__body { padding: var(--sp-20); }
}

/* Кнопка виджета висит поверх страницы, и на узком экране она накрывала
   управляющие элементы последней строки списка (замерено на 375x812 в
   /cart: перекрытие с кнопкой "убрать из списка"). Освобождаем под неё
   место в конце страницы, а не двигаем саму кнопку: угол это её место. */
@media (max-width: 720px) {
  body { padding-bottom: calc(52px + var(--sp-24)); }
}

/* --- 59-bulk.css --- */
/* ============================================================================
   Подбор по списку номеров. Слой 59.

   Своего здесь ровно то, чего нет в общих слоях: поле вставки, сводка разбора
   и строка предпросмотра с группой radio. Кнопки, карточки, плашки и поля
   переиспользуются из 20-button.css, 21-field.css и 22-card.css целиком.

   Все имена с префиксом bulk-.

   ГЛАВНОЕ РЕШЕНИЕ РАЗМЕТКИ: вариант это <label> с настоящим <input
   type="radio"> внутри, а не блок с обработчиком клика. Тогда выбор работает
   при выключенном JS, попадает в форму сам и читается скринридером как группа
   с именем строки (fieldset + legend). Сам кружок при этом виден: прятать
   индикатор выбора в списке, где выбирают, нельзя.
   ============================================================================ */

/* --- ПОЛЕ ВСТАВКИ ------------------------------------------------------- */
.bulk-paste { display: grid; gap: var(--sp-12); }

.bulk-paste__label {
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  color: var(--ink-2);
}

/* Моноширинный текст и увеличенный интерлиньяж: колонка номеров читается
   сверху вниз, и по ней глазом сверяют, всё ли вставилось. */
.bulk-paste__area {
  width: 100%;
  min-height: 140px;
  padding: var(--sp-12) var(--sp-16);
  background: var(--field-bg);
  border: var(--bw) solid var(--field-line);
  border-radius: var(--radius);
  color: var(--field-ink);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-md);
  line-height: var(--lh-body);
  letter-spacing: var(--ls-mono-sm);
  resize: vertical;
  transition: border-color var(--dur) var(--ease);
}
.bulk-paste__area::placeholder { color: var(--field-ph); }
.bulk-paste__area:focus-visible {
  outline: var(--bw-2) solid var(--focus-ring);
  outline-offset: 2px;
  border-color: var(--field-line-focus);
}

.bulk-paste__hint { font-size: var(--fs-md); color: var(--ink-3); margin: 0; }

.bulk-paste__action {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-16);
}

.bulk-go { display: flex; }

/* --- СВОДКА РАЗБОРА ----------------------------------------------------- */
.bulk-sum {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-8) var(--sp-12);
  padding-bottom: var(--sp-12);
  border-bottom: var(--bw) solid var(--line);
  color: var(--ink-2);
}
.bulk-sum__n {
  font-size: var(--fs-d6);
  font-weight: var(--fw-mono-med);
  color: var(--ink);
}
.bulk-sum__split {
  font-size: var(--fs-mono-sm);
  letter-spacing: var(--ls-mono-sm);
  color: var(--ink-3);
}

/* --- СТРОКА ПРЕДПРОСМОТРА ------------------------------------------------
       Две колонки: слева то, что написал человек, справа то, что мы поняли.
       Ровно так строку и проверяют: сверяют левое с правым. На узком экране
       колонки складываются, и левая остаётся сверху. ---------------------- */
.bulk-rows {
  display: grid;
  gap: var(--sp-12);
  margin-top: var(--sp-20);
}

.bulk-row {
  display: grid;
  grid-template-columns: minmax(180px, 220px) minmax(0, 1fr);
  gap: var(--sp-16) var(--sp-24);
  padding: var(--sp-16);
  /* Обнуляем браузерные поля fieldset: он здесь ради группировки radio для
     скринридера, а не ради рамки. */
  margin: 0;
  border: var(--bw) solid var(--line);
  background: var(--surface);
}

/* Строка, по которой ничего не нашлось, помечена цветом слева. Цвет не
   единственный носитель смысла: рядом стоит текст «Подберём по этому номеру». */
.bulk-row--none { border-left: 3px solid var(--accent-line); }
.bulk-row--text { border-left: 3px solid var(--line-strong); }

.bulk-row__ask { display: grid; gap: var(--sp-8); align-content: start; }

.bulk-row__raw {
  font-size: var(--fs-mono-lg);
  font-weight: var(--fw-mono-med);
  letter-spacing: var(--ls-mono-sm);
  color: var(--ink);
  overflow-wrap: anywhere;
}

.bulk-row__qty { display: flex; align-items: center; gap: var(--sp-8); }
.bulk-row__qty-input {
  width: 84px;
  height: var(--h-xs);
  padding: 0 var(--sp-10);
  background: var(--field-bg);
  border: var(--bw) solid var(--field-line);
  border-radius: var(--radius);
  color: var(--field-ink);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-md);
}
.bulk-row__qty-unit { font-size: var(--fs-md); color: var(--ink-3); }

.bulk-row__picks { display: grid; gap: var(--sp-2); align-content: start; }

/* --- ВАРИАНТ ------------------------------------------------------------ */
.bulk-pick {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-12);
  align-items: start;
  padding: var(--sp-10) var(--sp-12);
  border: var(--bw) solid transparent;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease);
}
.bulk-pick:hover { background: var(--surface-hover); }

.bulk-pick__radio { width: 18px; height: 18px; margin-top: 3px; flex: none; }
.bulk-pick__radio:focus-visible {
  outline: var(--bw-2) solid var(--focus-ring);
  outline-offset: 2px;
}

/* Выбранный вариант обведён рамкой, а не только подкрашен: подсветка фоном
   на светлой поверхности даёт около 1.1:1 и в списке из четырёх строк на
   глаз не читается. :has поддерживается всеми целевыми браузерами, а там,
   где нет, остаётся сам кружок radio. */
.bulk-pick:has(.bulk-pick__radio:checked) {
  border-color: var(--accent-line);
  background: var(--surface-2);
}

.bulk-pick__body { display: grid; gap: var(--sp-4); min-width: 0; }

.bulk-pick__pn {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-8);
  font-family: var(--font-mono);
  font-size: var(--fs-mono-md);
  font-weight: var(--fw-mono-med);
  letter-spacing: var(--ls-mono-sm);
  color: var(--ink);
}
.bulk-pick__pn--mute {
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  font-weight: var(--fw-regular);
  letter-spacing: normal;
  color: var(--ink-3);
}
.bulk-pick__brand {
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  font-weight: var(--fw-semi);
  letter-spacing: normal;
  color: var(--ink-2);
}

.bulk-pick__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-12);
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
  color: var(--ink-3);
}
.bulk-pick__via { color: var(--accent-text); }
.bulk-pick__via--direct { color: var(--ok); }
.bulk-pick__off { color: var(--danger); }

.bulk-pick__price {
  font-family: var(--font-mono);
  font-size: var(--fs-mono-md);
  font-weight: var(--fw-mono-med);
  color: var(--ink);
}
.bulk-pick__request {
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  font-weight: var(--fw-regular);
  color: var(--ink-3);
}

/* Строка, в которой номера не нашлось вовсе: объяснение вместо переключателя. */
.bulk-row__mute {
  margin: 0;
  padding: var(--sp-10) var(--sp-12);
  max-width: 68ch;
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
  color: var(--ink-3);
}

.bulk-row__all {
  padding: var(--sp-4) var(--sp-12);
  font-size: var(--fs-md);
  color: var(--accent-text);
}

/* --- ПОДВАЛ ФОРМЫ ------------------------------------------------------- */
.bulk-foot {
  display: grid;
  gap: var(--sp-12);
  justify-items: start;
  margin-top: var(--sp-24);
}
.bulk-foot__note { max-width: 62ch; }

/* --- УЗКИЙ ЭКРАН --------------------------------------------------------
       Колонки складываются. Количество уезжает в одну строку с номером:
       на телефоне это две коротких величины, и отдельная строка под каждую
       растягивает карточку вдвое. ---------------------------------------- */
@media (max-width: 720px) {
  .bulk-row { grid-template-columns: minmax(0, 1fr); gap: var(--sp-12); }
  .bulk-row__ask {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
  }
}

/* --- ПЕЧАТЬ -------------------------------------------------------------
       Распечатанный предпросмотр это лист согласования у снабженца: поле
       вставки и кнопки на нём лишние, а сами строки нужны. ---------------- */
@media print {
  .bulk-paste, .bulk-foot { display: none; }
  .bulk-row { break-inside: avoid; border-color: #000; }
}

/* ============================================================================
   ТОЧКИ ВХОДА В ПОДБОР НА СТРАНИЦЕ ПОИСКА.

   Лежат здесь, а не в 40-search.css, потому что это части одной и той же
   работы: ненайденный номер перестаёт быть тупиком. Правится всё вместе, и
   держать половину в чужом слое значит однажды поправить кнопку, забыв про
   полосу. Имена при этом с префиксом srch-: разметка принадлежит странице
   поиска, и селектор обязан читаться там, где он стоит.
   ============================================================================ */

/* Строка «Номеров много? Вставьте список целиком» под полем поиска в тёмном
   герое. ink-2 на графите это 8.8:1, ссылка берёт жёлтый accent-text (10.8:1). */
.srch-hero__bulk {
  margin-top: var(--sp-16);
  font-size: var(--fs-md);
  color: var(--ink-2);
}
.srch-hero__bulk a { color: var(--accent-text); }

/* Форма кнопки «Подберём». Ужимается до содержимого, чтобы кнопка не
   растягивалась во всю ширину карточки пустой выдачи. */
.srch-req { display: grid; gap: var(--sp-10); justify-items: start; }
.srch-req__note {
  max-width: 62ch;
  font-size: var(--fs-md);
  line-height: var(--lh-snug);
  color: var(--ink-3);
  margin: 0;
}

/* Полоса «Нужного нет в списке» под непустой выдачей. Текст и кнопка в строку,
   на узком экране в столбец: кнопка на всю ширину рядом с двумя строками
   текста читается как главное действие страницы, а главное здесь выдача. */
.srch-miss {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-16) var(--sp-24);
  padding: var(--sp-20) var(--sp-24);
  background: var(--surface-2);
  border: var(--bw) solid var(--line);
}
.srch-miss__text { min-width: 0; }
.srch-miss__note {
  margin-top: var(--sp-8);
  max-width: 68ch;
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--ink-2);
}

/* --- 60-legal.css --- */
/* Слой юридических страниц: уведомление о cookie, перечень cookie в политике,
   хвостовые ссылки документов.

   Отдельным слоем, а не дописан в 46-static.css, по одной причине: уведомление
   о cookie живёт на КАЖДОЙ странице сайта, а 46-static.css это слой текстовых
   страниц. Правило из чужого слоя однажды снесут вместе со слоем. */

/* --- УВЕДОМЛЕНИЕ О COOKIE. ------------------------------------------------
   Прижато к низу окна (sticky), но остаётся в потоке: fixed перекрывал бы
   кнопку «Запросить цену» на карточке товара ровно там, где палец её ищет.
   Именно поэтому здесь нет ни затемнения, ни блокировки прокрутки: наши
   cookie функциональные, соглашаться не с чем, а стена ради текста это
   помеха, а не забота.

   .cookie-ok на <html> ставит встроенный в <head> скрипт по куке (base.html):
   страница приезжает из кэша nginx одной копией на всех, и без этого правила
   уведомление моргало бы у того, кто его уже закрыл. */
.cookie-ok .cookie-note { display: none; }

.cookie-note {
  position: sticky;
  bottom: 0;
  z-index: var(--z-dropdown);
  border-top: 1px solid var(--line-strong);
  background: var(--surface-2);
}
.cookie-note__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-16);
  padding-top: var(--sp-12);
  padding-bottom: var(--sp-12);
  flex-wrap: wrap;
}
.cookie-note__text {
  margin: 0;
  max-width: 82ch;
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  color: var(--ink-2);
}
.cookie-note__link { color: var(--ink); text-decoration: underline; }
.cookie-note__form { margin: 0; }

@media (max-width: 640px) {
  /* Кнопка на всю ширину: на 360px «Понятно» рядом с двумя строками текста
     ужимается до зоны нажатия меньше 44px. */
  .cookie-note__form { width: 100%; }
  .cookie-note__btn { width: 100%; }
}

/* --- ПЕРЕЧЕНЬ COOKIE в разделе 9 политики. --------------------------------
   Пять колонок в 360px не помещаются, поэтому таблица прокручивается ВНУТРИ
   своей обёртки. Горизонтальная прокрутка страницы целиком это поломка. */
.prose__scroll {
  margin-top: var(--sp-16);
  overflow-x: auto;
  /* Полоса прокрутки не должна выглядеть обрывом текста: рамка показывает,
     что содержимое продолжается вбок. */
  border: 1px solid var(--line);
}
.ctab {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-md);
  line-height: var(--lh-body);
}
.ctab__th {
  padding: var(--sp-10) var(--sp-12);
  text-align: left;
  vertical-align: bottom;
  white-space: nowrap;
  border-bottom: 1px solid var(--line-strong);
  font-size: var(--fs-sm);
  color: var(--ink-3);
}
.ctab__td {
  padding: var(--sp-10) var(--sp-12);
  vertical-align: top;
  border-bottom: 1px solid var(--line-soft);
  color: var(--ink-2);
}
.ctab tr:last-child .ctab__td { border-bottom: 0; }
/* Обязательность отдельной строкой под назначением, а не шестой колонкой:
   на телефоне шестая колонка уезжает за край и её не находят. */
.ctab__need {
  display: block;
  margin-top: var(--sp-4);
  font-size: var(--fs-sm);
  color: var(--ink-3);
}

/* --- ХВОСТ ДОКУМЕНТА: ссылки на соседние документы. ----------------------- */
.legal__more {
  margin-top: var(--sp-24);
  font-size: var(--fs-md);
  color: var(--ink-3);
}
.legal__more a { color: var(--ink); text-decoration: underline; }

@media print {
  /* Уведомление о cookie в распечатанной политике это мусор. */
  .cookie-note { display: none !important; }
  .prose__scroll { overflow: visible; }
}

/* --- 90-print.css --- */
/* ============================================================================
   Печать. Снабженец печатает карточку и несёт её в цех или прикладывает
   к заявке. Печатаем номер, бренд, характеристики, кроссы, применяемость.
   Убираем всё, что на бумаге не работает: шапку, подвал, формы, кнопки.
   ============================================================================ */

@page { margin: 14mm; }

@media print {
  html, body { background: #FFFFFF !important; color: #000000 !important; }

  /* Тёмные секции на бумаге это залитый тонером лист. */
  [data-surface="dark"] {
    background: #FFFFFF !important;
    color: #000000 !important;
    --ink: #000000;
    --ink-2: #222222;
    --ink-3: #555555;
    --line: #BBBBBB;
    --line-soft: #DDDDDD;
    --surface: #FFFFFF;
  }

  .hdr, .hdr-sentinel, .ftr, .skip-link,
  form, .btn, .qty, .callout, .alert--info,
  [data-print="off"] { display: none !important; }

  /* ИСКЛЮЧЕНИЕ: строка списка подбора. Количество живёт в поле ввода ВНУТРИ
     формы строки, поэтому общее правило выше уносило с бумаги ровно то, ради
     чего список и печатают: сколько чего заказывать. Печать это единственный
     способ вынести список с сайта, распечатка без количеств бесполезна.
     Оба правила бьют общее по специфичности (класс против тега), !important
     у обоих, поэтому порядок в файле роли не играет. */
  .cart-row__form { display: block !important; }
  .cart-row__form .qty { display: inline-block !important; }

  /* Внутри формы оставляем только сами значения. Кнопка «Обновить» это .btn,
     её уже скрыло общее правило; шаговые кнопки и крестик удаления скрываем
     здесь, потому что .qty мы только что вернули. */
  .qty__btn, .cart-row__remove { display: none !important; }

  /* Поле ввода на бумаге читается как число, а не как пустая рамка. Стрелки
     счётчика убираем: на печати они рисуются, а нажать их нельзя. */
  .cart-row__form input {
    -webkit-appearance: none;
    appearance: none;
    width: auto !important;
    min-width: 0 !important;
    border: 0 !important;
    padding: 0 !important;
    background: none !important;
    color: #000000 !important;
    font-weight: 600;
  }

  /* Единица измерения. В форме её нет (её несёт подпись поля для
     скринридера), а на бумаге голое число рядом с ценой читается как вторая
     цена. */
  .cart-row__qty::after { content: " шт"; font-size: 9pt; }

  .container { max-width: none; padding-inline: 0; }
  .section { padding-block: 0 8mm; }
  .split { display: block; }
  .split__aside { position: static; }

  /* Каскад появления на печати оставил бы пустые места. */
  .is-revealed { animation: none !important; opacity: 1 !important; }

  /* Свёрнутые списки на бумаге разворачиваем: перелистнуть страницу нельзя.
     Два правила, потому что движки прячут содержимое по-разному: старые
     через display у неявного слота, новые через content-visibility. */
  details { display: block !important; }
  details > *:not(summary) { display: block !important; }
  details::details-content { content-visibility: visible !important; block-size: auto !important; }
  .more__summary { display: none !important; }

  .hero__title { font-size: 28pt; }
  .card, .rows { border: 1px solid #BBBBBB !important; box-shadow: none !important; }
  .row-item, .deflist__row, .card { break-inside: avoid; }
  h2, .section-head { break-after: avoid; }

  /* Адрес страницы на бумаге: иначе по распечатке невозможно вернуться. */
  .print-url::after { content: " (" attr(data-url) ")"; font-size: 9pt; }
}

