

/* Start:/local/templates/xcore_front/css/tokens.css?179264524827140*/
/* OWNS: design tokens — CSS custom properties on :root + html[data-theme=light|dark], plus the self-hosted @font-face for «XCore Mono» (mono с читаемым слэш-нулём, #92) and Onest (основной шрифт со своего сервера, #655). No .xf-* selectors. */
/* ============================================================
 * xcore_front — design tokens
 *
 * :root                    — shared (неизменное между темами).
 * html / [data-theme=light] — light pack (дефолт).
 * [data-theme=dark]        — dark pack: «Ночная вывеска» (#739).
 *
 * Тема выставляется FOUC-guard'ом в header.php до первого
 * paint. Правило (#707): тёмная — только если человек сам
 * включил её переключателем ('xcore-theme' = 'dark' в
 * localStorage), иначе светлая; системную
 * prefers-color-scheme не читаем. Атрибут пишется ТОЛЬКО для
 * тёмной — data-theme="dark" на <html>; у светлой атрибута
 * нет, и срабатывает голый `html` селектор.
 * Тёмная тема — один слой (#708): этот пакет плюс правила
 * html[data-theme="dark"] … в файлах-владельцах компонентов.
 * Отдельного файла скина и второго атрибута больше нет.
 *
 * Светлые значения — из _design/claude_design_v2; тёмные —
 * из «Ночной вывески» (DESIGN.md, Dark Theme).
 * ============================================================ */

/* ── Self-hosted mono — «XCore Mono» (#92) ──────────────────────
 * JetBrains Mono v2.304 (OFL) с ОДНОЙ правкой: ноль со слэшем вместо
 * точечного. Дефолтный точечный 0 JetBrains Mono в мелком размере
 * читался как «8» на ценах/SKU (телефоны ушли на Onest в #91; здесь —
 * read-only данные, остаются mono). Слэш «вшит» в дефолтный глиф
 * (фича `zero` запечена в outline), поэтому никаких font-feature-settings
 * по месту не нужно — все числа (var(--font-mono), ~150 исп.) получают
 * читаемый ноль разом, включая vendor-CSS конфигуратора.
 * Семья переименована: OFL запрещает переиспользовать имя «JetBrains
 * Mono» в модифицированной сборке. Сборка (воспроизводимо) + лицензия:
 * fonts/BUILD.md + fonts/OFL.txt. Файлы внутри шаблона → едут обычным
 * sync-template.sh (прецедент: FontAwesome у aspro_next), без ручного
 * rsync вне local/.
 * ?v= руками: filemtime-кэшбастер в header.php версионирует <link> на
 * CSS-файл, но НЕ url(/local/templates/xcore_front/css/) внутри @font-face — без ?v= пере-сборка шрифта
 * не подхватится (Cache-Control ~30 дней). Бампать дату при пере-сборке. */
@font-face {
  font-family: 'XCore Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/xcore-mono-Regular.woff2?v=20260622) format('woff2');
}
@font-face {
  font-family: 'XCore Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/xcore-mono-Medium.woff2?v=20260622) format('woff2');
}
@font-face {
  font-family: 'XCore Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/xcore-mono-SemiBold.woff2?v=20260622) format('woff2');
}

/* ── Self-hosted sans — Onest (#655) ─────────────────────────────
 * Раньше шёл с fonts.googleapis.com: блокирующий CSS с чужого сервера
 * и два лишних соединения до первого текста. Файлы — те же, что
 * отдавал Google (Onest 2.001, вариативный по толщине 100–900, OFL —
 * fonts/onest/OFL.txt): 7 подмножеств с теми же unicode-range и в том же
 * порядке, браузер качает только нужные (обычно cyrillic + latin).
 * Сборка — та, что Google отдаёт Windows: контуры и карта символов те же
 * (сверено fontTools), плюс таблица prep в 7 байт. Windows рисует текст
 * как раньше, Mac и iPhone prep не читают. FreeType (Android, Linux) с ней
 * считает шрифт хинтованным: при «лёгком» хинтинге — обычном для Chrome —
 * отличий нет (проверено FreeType 2.12), при полном хинтинге на Linux
 * буквы чуть другие — ради Windows этим пожертвовали.
 * Толщина — диапазон 300–800 с теми же границами, что запрос к Google
 * (wght@300;400;…;800 — шесть отдельных граней). Разница только у
 * промежуточных толщин (450 у Google округлялась бы до 500), а их на
 * сайте нет: в css/ и разметке только 300/400/500/600/700/normal/bold
 * (grep -rhoE 'font-weight:[^;]+' по шаблону). Вне диапазона (bolder → 900)
 * прижимается к краю, как и раньше.
 * cyrillic и latin предзагружаются из header.php — ?v= там и здесь обязан
 * совпадать, иначе предзагрузка не подхватится и файл скачается дважды.
 * Обновить шрифт — fonts/BUILD.md, раздел «Onest». */
/* cyrillic-ext */
@font-face {
  font-family: 'Onest';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/onest/onest-cyrillic-ext.woff2?v=20261021) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'Onest';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/onest/onest-cyrillic.woff2?v=20261021) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* math */
@font-face {
  font-family: 'Onest';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/onest/onest-math.woff2?v=20261021) format('woff2');
  unicode-range: U+0302-0303, U+0305, U+0307-0308, U+0310, U+0312, U+0315, U+031A, U+0326-0327, U+032C, U+032F-0330, U+0332-0333, U+0338, U+033A, U+0346, U+034D, U+0391-03A1, U+03A3-03A9, U+03B1-03C9, U+03D1, U+03D5-03D6, U+03F0-03F1, U+03F4-03F5, U+2016-2017, U+2034-2038, U+203C, U+2040, U+2043, U+2047, U+2050, U+2057, U+205F, U+2070-2071, U+2074-209F, U+20D0-20DC, U+20E1, U+20E5-20EF, U+2100-2112, U+2114-2115, U+2117-2121, U+2123-214F, U+2190, U+2192, U+2194-21AE, U+21B0-21E5, U+21F1-21F2, U+21F4-2211, U+2213-2214, U+2216-22FF, U+2308-230B, U+2310, U+2319, U+231C-2321, U+2336-237A, U+237C, U+2395, U+239B-23B7, U+23D0, U+23DC-23E1, U+2474-2475, U+25AF, U+25B3, U+25B7, U+25BD, U+25C1, U+25CA, U+25CC, U+25FB, U+266D-266F, U+27C0-27FF, U+2900-2AFF, U+2B0E-2B11, U+2B30-2B4C, U+2BFE, U+3030, U+FF5B, U+FF5D, U+1D400-1D7FF, U+1EE00-1EEFF;
}
/* symbols */
@font-face {
  font-family: 'Onest';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/onest/onest-symbols.woff2?v=20261021) format('woff2');
  unicode-range: U+0001-000C, U+000E-001F, U+007F-009F, U+20DD-20E0, U+20E2-20E4, U+2150-218F, U+2190, U+2192, U+2194-2199, U+21AF, U+21E6-21F0, U+21F3, U+2218-2219, U+2299, U+22C4-22C6, U+2300-243F, U+2440-244A, U+2460-24FF, U+25A0-27BF, U+2800-28FF, U+2921-2922, U+2981, U+29BF, U+29EB, U+2B00-2BFF, U+4DC0-4DFF, U+FFF9-FFFB, U+10140-1018E, U+10190-1019C, U+101A0, U+101D0-101FD, U+102E0-102FB, U+10E60-10E7E, U+1D2C0-1D2D3, U+1D2E0-1D37F, U+1F000-1F0FF, U+1F100-1F1AE, U+1F1E6-1F1FF, U+1F30D-1F30F, U+1F315, U+1F31C, U+1F31E, U+1F320-1F32C, U+1F336, U+1F378, U+1F37D, U+1F382, U+1F393-1F39F, U+1F3A7-1F3A8, U+1F3AC-1F3AF, U+1F3C2, U+1F3C4-1F3C6, U+1F3CA-1F3CE, U+1F3D4-1F3E0, U+1F3ED, U+1F3F1-1F3F3, U+1F3F5-1F3F7, U+1F408, U+1F415, U+1F41F, U+1F426, U+1F43F, U+1F441-1F442, U+1F444, U+1F446-1F449, U+1F44C-1F44E, U+1F453, U+1F46A, U+1F47D, U+1F4A3, U+1F4B0, U+1F4B3, U+1F4B9, U+1F4BB, U+1F4BF, U+1F4C8-1F4CB, U+1F4D6, U+1F4DA, U+1F4DF, U+1F4E3-1F4E6, U+1F4EA-1F4ED, U+1F4F7, U+1F4F9-1F4FB, U+1F4FD-1F4FE, U+1F503, U+1F507-1F50B, U+1F50D, U+1F512-1F513, U+1F53E-1F54A, U+1F54F-1F5FA, U+1F610, U+1F650-1F67F, U+1F687, U+1F68D, U+1F691, U+1F694, U+1F698, U+1F6AD, U+1F6B2, U+1F6B9-1F6BA, U+1F6BC, U+1F6C6-1F6CF, U+1F6D3-1F6D7, U+1F6E0-1F6EA, U+1F6F0-1F6F3, U+1F6F7-1F6FC, U+1F700-1F80B, U+1F810-1F847, U+1F850-1F859, U+1F860-1F887, U+1F890-1F8AD, U+1F8B0-1F8BB, U+1F8C0-1F8C1, U+1F8D0-1F8D8, U+1F900-1F90B, U+1F93B, U+1F946, U+1F984, U+1F996, U+1F9E9, U+1FA00-1FA6F, U+1FA70-1FA7C, U+1FA80-1FAC6, U+1FAC8, U+1FACC-1FADD, U+1FADF-1FAEB, U+1FAEF-1FAFA, U+1FB00-1FBFF;
}
/* vietnamese */
@font-face {
  font-family: 'Onest';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/onest/onest-vietnamese.woff2?v=20261021) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Onest';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/onest/onest-latin-ext.woff2?v=20261021) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Onest';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url(/local/templates/xcore_front/css/../fonts/onest/onest-latin.woff2?v=20261021) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --accent-h: 268;
  --radius: 14px;
  --density: 1;

  /* Множитель ДВИЖЕНИЯ (#330). 1 = обычно, 0 = системная настройка «уменьшить
     движение». Единственный гейт reduced-motion в шаблоне: любое правило, которое
     СДВИГАЕТ элемент (hover-lift, press-scale, зум картинки, выезд тоста), берёт
     величину через calc(... * var(--xf-motion, 1)) — при 0 смещение схлопывается
     в ноль, а позиционная часть трансформа (translateX(-50%), центрирование
     модалок) остаётся нетронутой.
     Почему множитель, а не общий блок с перечнем селекторов: селекторный список
     тихо протухает при переименовании класса (Playwright такое не ловит), а токен
     уезжает вместе с самим правилом. Почему не `* { transition: none }`: смена
     цвета/фона на hover должна остаться живой, а транзишены появления элементов
     — рабочими.
     Транзишены и @keyframes-анимации этим токеном НЕ гасятся — у них свои
     @media-блоки в файлах-владельцах (marquee брендов, спиннеры, скелетоны). */
  --xf-motion: 1;

  /* --grad (бренд-градиент linear-gradient(135deg, #5B3FE0, #8E6BFF, #C084FC))
     СНЯТ по решению владельца 2026-07-29 (#322): «фиолетовый градиент как
     главная кнопка — я ж вообще бы хотел его с сайта убрать максимально».
     Все 13 живых потребителей переведены на плоский var(--accent), модификатор
     .xf-btn--grad удалён. Возвращать токен не надо: DESIGN.md больше не держит
     для градиента ни одной легальной роли (см. The Flat-Surface Rule).
     Знак «X» в логотипе градиент сохраняет — это айдентика с внешним следом
     (микроразметка, профили Карт, печать), владелец решил не трогать.
     Функциональные градиенты (mask-fade скроллеров, скелетоны, скрим под
     текстом на фото) живут своей жизнью в файлах-владельцах — они несут
     функцию, а не настроение, и токеном никогда не пользовались. */

  --font-sans: 'Onest', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
  /* mono = self-hosted «XCore Mono» (слэш-ноль, см. @font-face выше, #92).
     Fallback-моно (ui-monospace/Menlo) имеют ПЛОСКИЙ — никогда не точечный —
     ноль, поэтому swap-FOUT не мигнёт «8»-образным глифом. */
  --font-mono: 'XCore Mono', ui-monospace, Menlo, monospace;

  --container: 1440px;
  --container-pad: 40px;
}

/* Телефонный рельс (#307). До этого мобайл наследовал десктопные 40px: на 390px
   экране в поля уходило 20.5% ширины, карточки каталога сжимались до 150px и
   имена товаров резались ровно там, где различаются варианты одной модели.
   Порог 767px — не с потолка, это канон шаблона: здесь же header.css сбрасывает
   десктопную шапку на мобильную, здесь же сетка товаров уходит в 2 колонки, и
   home.css фиксирует решение M24 «Tablet (768–900) rides desktop rails».
   16px — тоже не новое число: телефонные компоненты шаблона уже считают рельс
   шестнадцатым (product.css `.xf-similar__grid` — padding/scroll-padding 16px,
   configurator2.css `.xfc` — padding 20px 16px, доки чата/тостов —
   `max-width: calc(100vw - 32px)`). Токен был последним, кто остался на 40.
   Компенсации в шаблоне ходят через сам токен (`calc(var(--container-pad) * -1)`),
   поэтому full-bleed-полосы (галерея товара, «Похожие», конфигуратор) следуют
   за новым значением сами — правок на местах вызова не требуется. */
@media (max-width: 767px) {
  :root { --container-pad: 16px; }
}

/* Единственный переключатель reduced-motion в шаблоне (#330).
   PRODUCT.md (a11y) + DESIGN.md («Hover lift … на prefers-reduced-motion —
   отменяется»): движение гасим, состояние — нет. Кнопка на hover по-прежнему
   меняет цвет и тень, тост по-прежнему появляется — просто без поездки. */
@media (prefers-reduced-motion: reduce) {
  :root { --xf-motion: 0; }
}

/* Light — default */
html,
html[data-theme="light"] {
  --bg:        oklch(0.985 0.003 285);
  --bg-2:      oklch(0.965 0.005 285);
  --surface:   oklch(1 0 0);
  --surface-2: oklch(0.965 0.005 285);
  --surface-3: oklch(0.93  0.008 285);
  --line:      oklch(0.86  0.01  285);
  --line-soft: oklch(0.91  0.008 285 / 0.9);
  --text:      oklch(0.17  0.015 285);
  --text-2:    oklch(0.38  0.012 285);
  /* #372: было oklch(0.55 0.01 285) — 4.86:1 на --surface и 4.39:1 на
     --surface-2 при норме AA 4.5. Токен задумывался цветом тихой МЕТКИ, а
     живёт как цвет читаемого мелкого ТЕКСТА: «BYN» под каждой ценой
     (.xf-price__cur), плейсхолдер поля поиска, вся левая колонка
     характеристик карточки товара (.xf-specs__name, .xf-specs__section),
     кнопка-подсказка «?» (.xf-tip__btn). Тот же вывод, что у --warning в #319.
     Чинится сам токен, а не шесть точечных переопределений по зонам: иначе
     следующий потребитель --text-3 родился бы снова сломанным.
     Стало: 5.18 на --surface, 4.68 на --surface-2, 4.96 на --bg. Иерархия
     сохранена — ступень до --text-2 1.94:1 (было 2.06). --surface-3 остаётся
     4.21, но там, как и у --warning, не живёт ни один потребитель --text-3
     (свип по трём страницам в обеих темах); тянуть токен выше значило бы
     схлопнуть ступень до --text-2. */
  --text-3:    oklch(0.535 0.01  285);

  --accent:      oklch(0.55 0.23 var(--accent-h));
  --accent-2:    oklch(0.50 0.25 var(--accent-h));
  --accent-3:    oklch(0.45 0.25 var(--accent-h));
  --accent-dim:  oklch(0.55 0.23 var(--accent-h) / 0.10);
  --accent-line: oklch(0.55 0.23 var(--accent-h) / 0.30);
  /* Подчёркивание ссылки в тексте: справка, статья блога, текст лендинга,
     согласие в форме. Светлее --accent-line: под индиго-текстом линия едва
     заметна. Токен, а не литерал в четырёх файлах (#857): литерал на индиго
     268 красил подчёркивание и в тёмной. Тёмная пара — линия акцента. */
  --link-line:   oklch(0.68 0.22 var(--accent-h) / 0.3);

  --success: oklch(0.52 0.17 155);
  /* #319: было oklch(0.66 0.16 65) — 3.23:1 на белой карточке при кегле 11.5px,
     AA для обычного текста требует 4.5:1. Токен задумывался сигнальным цветом
     для плашек и точек, а живёт как цвет МЕЛКОГО ТЕКСТА: все пять потребителей
     (.xf-product-card__stock--none, .xf-cart-item__stock, .xfc-step__picked-warn,
     .xfc-compat__val/__dot) — метки 11–11.5px на --surface. Затемнён сам токен,
     а не заведён отдельный --warning-ink: у двух потребителей точка берёт цвет
     из currentColor текста, так что раздельные токены рассинхронили бы точку с
     подписью, а в конфигураторе точка стоит вплотную к тексту того же смысла.
     Стало 5.25:1 — и на всех подложках, куда метка теоретически может попасть,
     тоже выше AA: 4.99 на подтоне «Товара дня», 5.04 на --bg, 4.74 на
     --surface-2. Ниже 4.5 остаётся только --surface-3 (4.26), но там не живёт
     ни один потребитель — карточка не меняет фон даже на hover. Значение
     держится в одной полосе с соседями по семейству (--success 4.88, --danger
     4.79), чтобы «предупреждение» не читалось тяжелее «ошибки».
     Chroma 0.16 → 0.12 — не выцветание: СТАРОЕ значение выходило за sRGB и
     браузер обрезал его по каналу (рендерилось как #D37900, а не заказанные
     0.16); новое лежит внутри гамута с запасом. В границе гамута цвет считается
     одинаково во всех движках, поэтому замер контраста не зависит от того, как
     конкретный браузер приводит цвет в гамут.
     Тёмную пару НЕ трогаем: там 9.95:1 на карточке (замер 2026-10-22, #708). */
  --warning: oklch(0.54 0.12  65);
  --danger:  oklch(0.58 0.22  25);

  --header-bg:   oklch(1 0 0 / 0.82);
  --shadow-card: 0 16px 40px -12px oklch(0.3 0.05 285 / 0.14);

  /* #280 (2026-07-29, решение владельца): сняты --ambient-1/2 (два лиловых
     radial-пятна на body::before) и tonal-art токены --art-l1/l2/--art-glow-*.
     Их потребители — .xf-account__hero, .xf-help-cta, #assembly_info — переведены
     на плоский --surface-2. Фон системы: flat by default, без ambient-слоя. */
}

/* Dark — «Ночная вывеска» (#739), только по переключателю (#707).
   Принцип: неон — это свет, а не краска. Мир почти чёрный и почти без цвета
   (хрома ≤ 0.011, подтон 292 есть, но фиолетовым не читается: на нейтрали
   насыщенный цвет выглядит ярче). Ступени поверхностей — по светлоте, линии —
   белым с альфой: на почти чёрном так край виден, а подтон не копится. Красок
   две, у каждой своя роль: фиолет 296 — действие (--accent*), маджента 338 —
   свечение (--nx-*). Правило целиком — DESIGN.md, Dark Theme.
   До #708 здесь жил прежний тёмный пакет (индиго 285/268), поверх которого
   скин css/neon.css клал эти значения; покупатель видел только их, прежние
   были мёртвыми и сняты. Из прежнего пакета живы только сигнальные цвета.
   Контраст (замер 2026-10-22, OKLCH → sRGB с обрезкой по каналу, WCAG 2.x):
   --text на --bg 18.6:1; --text-2 на --surface 10.2:1; --text-3 на --surface
   6.6:1, на --surface-2 6.3, на --surface-3 5.6, на --bg 6.9 — AA для мелкого
   текста везде (#372 чинил прежнюю пару, где было 4.27); белая подпись на
   --accent 5.15:1, на --accent-3 (наведение) 6.4:1 (#337 — то же требование
   к девяти залитым accent'ом поверхностям); --accent-2 (текст и ссылки) на
   --surface 9.9:1; --accent как кольцо фокуса и рамка — 3.7:1 на --surface,
   выше барьера 3:1 для нетекстовых элементов. */
html[data-theme="dark"] {
  --bg:        oklch(0.135 0.008 292);
  --bg-2:      oklch(0.115 0.006 292);
  --surface:   oklch(0.172 0.009 292);
  --surface-2: oklch(0.200 0.010 292);
  --surface-3: oklch(0.245 0.011 292);
  --line:      oklch(1 0 0 / 0.12);
  --line-soft: oklch(1 0 0 / 0.075);
  --text:      oklch(0.975 0.003 292);
  --text-2:    oklch(0.80 0.008 292);
  --text-3:    oklch(0.68 0.008 292);

  /* Краска действия: кнопки, кольцо фокуса, рамка наведения. */
  --accent:      oklch(0.565 0.245 296);
  --accent-2:    oklch(0.80 0.11 300);
  --accent-3:    oklch(0.515 0.25 296);
  --accent-dim:  oklch(0.565 0.245 296 / 0.16);
  --accent-line: oklch(0.66 0.20 300 / 0.5);
  --link-line:   var(--accent-line);

  /* Сигнальные — тёмная пара (#319: 9.95:1 на карточке у --warning). */
  --success: oklch(0.76 0.16 155);
  --warning: oklch(0.80 0.15  70);
  --danger:  oklch(0.72 0.20  25);

  /* Свечение — только у того, что излучает, и только на главной (знак X,
     вторая строка вывески, значения фактов, цифры «Товара дня»):
     --nx-core — раскалённая сердцевина трубки (цвет текста, 15.6:1 на --bg),
     --nx-glow — её цветной ореол (text-shadow). За контур блока свет не
     выходит. --nx-edge — верхняя кромка, которой поверхности ловят свет
     (карточки, плитки, баннеры) — вместо свечения у всего остального.
     --nx-key — блик по верхней кромке залитой кнопки («клавиша с
     подсветкой», DESIGN.md → Key highlight) вместо цветной тени светлой. */
  --nx-core:  oklch(0.93 0.065 338);
  --nx-glow:  0 0 10px oklch(0.66 0.27 336 / 0.5), 0 0 30px oklch(0.60 0.27 332 / 0.3);
  --nx-edge:  inset 0 1px 0 oklch(1 0 0 / 0.07);
  --nx-key:   inset 0 1px 0 oklch(1 0 0 / 0.22);
  /* Белая плита под фото товара → лайтбокс. Плиту тонировать нельзя (чужой
     почти-белый фон JPEG проступит рамкой — DESIGN.md, Light-Is-Canonical),
     поэтому тон кладётся наложением multiply на плиту и фото сразу: плита чуть
     приглушена и гаснет к краям. Потребители — ::after у .xf-art--photo
     (home.css), .xf-product-gallery__thumb (product.css), .xf-cart-item__img
     с фото (cart.css). */
  --nx-lightbox:
    radial-gradient(130% 100% at 50% 40%, oklch(1 0 0 / 0) 50%, oklch(0.84 0.025 292 / 0.5) 100%),
    oklch(0.945 0.008 292);

  --header-bg:   oklch(0.135 0.008 292 / 0.92);
  --shadow-card: 0 18px 44px -18px oklch(0 0 0 / 0.7);
}

/* Sync native UA (scrollbars, autofill, form controls) to theme. */
html                        { color-scheme: light; }
html[data-theme="dark"]     { color-scheme: dark; }

/* End */


/* Start:/local/templates/xcore_front/css/base.css?179264519117225*/
/* OWNS: global resets (*, html, body), фон тёмной темы — свет и зерно на body::before/::after (#708), .xf-sr-only, .xf-toast(-*) (toast notifications), body.xf-bottombar-up (snackbar clearance over mobile money-bars), body.xf-bottombar-stacked и body.xf-bottombar-ahead (полоса карточки и корзины стоит НАД баннером cookie; основная кнопка ещё ниже экрана — #716, ставит app.js initMoneyBar; ahead читает chat.css), .xf-lazyfade (плавное появление ленивых img). */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

/* html bg duplicates body bg so iOS rubber-band / overscroll shows the theme
   colour instead of the UA default white. Essential for premium feel under
   viewport-fit=cover where the safe-area gutters are visible. color-scheme
   tells the UA how to render native controls (scrollbars, form widgets) and
   — critically on iOS Safari — gives the address-bar a hint to tint itself
   in sync with the page when meta[theme-color] doesn't re-read dynamically. */
html { background: var(--bg); color-scheme: light; overflow-x: clip; }
html[data-theme="dark"] { color-scheme: dark; caret-color: var(--accent-2); }

body {
  font-family: var(--font-sans);
  background: var(--bg);
  color: var(--text);
  font-feature-settings: "ss01", "cv11";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  min-height: 100vh;
  overflow-x: clip;
}

a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; padding: 0; }
input, select, textarea { font: inherit; color: inherit; }
::selection { background: var(--accent); color: #fff; }

.mono { font-family: var(--font-mono); }

.xf-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent-2);
}

/* Ambient backdrop (два лиловых radial-пятна на body::before) снят по решению
   владельца 2026-07-29, #280: «висячие лиловые градиенты». В светлой фон —
   плоский var(--bg). Токены --ambient-1/2 удалены из tokens.css.

   Тёмная тема («Ночная вывеска», #739) от плоского фона отступает намеренно —
   это её правило, DESIGN.md → Dark Theme. Фон держит html (правило выше), body
   прозрачный, под контентом два слоя:
   • свет — один мягкий свет сверху. Привязан к документу (absolute от
     начального блока), а не к экрану: уезжает вместе с верхом страницы, товары
     не плывут сквозь неподвижное пятно. На главной свет даёт сама вывеска
     (home.css, знак X) — общий там выключен;
   • зерно — постоянный слой 3.5% поверх фона: без него градиент света на
     тёмном рвётся на полосы и выглядит пластиковым. Обычное наложение, без
     blend-mode — фиксированный слой композитится один раз и не
     пересчитывается на прокрутке. */
html[data-theme="dark"] body { background: transparent; }
html[data-theme="dark"] body::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1400px;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(42% 34% at 50% -4%, oklch(0.40 0.06 292 / 0.35), transparent 100%);
}
html[data-theme="dark"]:has(.xf-pitch) body::before { background: none; }
html[data-theme="dark"] body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 1.4 -.2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-track { background: transparent; }
*::-webkit-scrollbar-thumb { background: var(--surface-3); border-radius: 8px; }
@media (hover: hover) {
  *::-webkit-scrollbar-thumb:hover { background: var(--line); }
}

/* Toast — transient feedback pinned bottom-right. Used by add-to-cart,
   can be reused by future flows (favorites, compare, etc.). */
#xf-toast-stack {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 9999;
  display: flex;
  flex-direction: column-reverse;
  pointer-events: none;
  max-width: calc(100vw - 32px);
}
.xf-toast {
  /* Inter-toast spacing via margin-top (NOT stack `gap`) so the space between
     toasts collapses WITH the leaving toast — stacked siblings glide instead
     of teleporting when one auto-dismisses (multi-toast case). */
  margin-top: 10px;
  padding: 12px 18px;
  border-radius: 12px;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--line-soft);
  box-shadow: var(--shadow-card);
  font-size: 14px;
  font-weight: 500;
  opacity: 0;
  transform: translateY(calc(12px * var(--xf-motion, 1)));
  transition: opacity .25s, transform .25s, max-height .28s ease, margin-top .28s ease, padding-top .28s ease, padding-bottom .28s ease;
  pointer-events: none;
  max-width: 360px;
}
.xf-toast.is-shown { opacity: 1; transform: translateY(0); }
/* Exit: collapse the box (height + top-margin) so any toasts stacked above
   slide down smoothly instead of snapping when this one is removed. JS pins
   max-height to the measured px first, then this animates it →0 (dismissToast). */
.xf-toast.is-leaving {
  opacity: 0;
  transform: translateY(calc(12px * var(--xf-motion, 1)));
  /* Smooth full collapse so stacked siblings glide down. Three pieces, all
     transitioned (see .xf-toast transition list):
       - max-height: pinned to measured px by dismissToast, then →0 INLINE
         (inline beats this class, so it lives in JS). With border-box this
         only shrinks the content area...
       - padding-top/bottom →0 — ...so the padding must collapse too, else the
         box floors at padding+border (~22px) and the siblings jump at removal;
       - margin-top →0 — closes the inter-toast gap.
     min-height:0 defeats the flex-item default (min-height:auto would override
     max-height and block the shrink). */
  padding-top: 0;
  padding-bottom: 0;
  margin-top: 0;
  min-height: 0;
  overflow: hidden;
  pointer-events: none;
}
.xf-toast--error {
  background: oklch(0.32 0.08 25);
  color: #fff;
  border-color: transparent;
}
.xf-toast--action {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 8px 10px 18px;
  pointer-events: auto;
}
.xf-toast__msg {
  flex: 1;
  min-width: 0;
}
.xf-toast__action {
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  color: var(--accent-2);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  padding: 8px 12px;
  border-radius: 8px;
  cursor: pointer;
  min-height: 36px;
  /* opacity — для действия, которое дорисовывается на уже показанном тосте
     («Сравнить с ИИ» после преflight'а, #430): фейд вместо щелчка. */
  transition: background .15s, color .15s, opacity .18s;
}
@media (hover: hover) {
  .xf-toast__action:hover { background: var(--accent-dim); }
}
.xf-toast__action:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
/* [#173] Акцентное доп-действие тоста («Сравнить с ИИ» после добавления в
   сравнение): заливка индиго, чтобы отличалось от текстового «Отменить» рядом
   и читалось как основное предложение. Рендерится ПЕРЕД «Отменить». */
.xf-toast__action--primary {
  background: var(--accent);
  color: #fff;
}
/* [#646] Тост с двумя действиями («Перейти в корзину» + «Отменить», «Сравнить
   с ИИ» + «Отменить»): рядом с текстом обе кнопки не помещаются, и текст
   сжимался в столбик по слову. Текст — строкой сверху, кнопки — строкой под
   ним, у правого края. Класс ставит app.js (offerExtraAction). */
.xf-toast--two {
  flex-wrap: wrap;
  row-gap: 4px;
}
.xf-toast--two .xf-toast__msg { flex-basis: 100%; }
.xf-toast--two .xf-toast__action--primary { margin-left: auto; }
/* [#646] Кнопки тоста — под палец, как остальные цели сайта на телефоне и
   планшете (44 px, та же граница 767, что у полос и чипов): было 36, а у
   тоста с «Перейти в корзину» это главное действие. */
@media (max-width: 767px) {
  .xf-toast__action { min-height: 44px; }
}
@media (hover: hover) {
  .xf-toast__action--primary:hover { background: var(--accent-2); color: #fff; }
  /* Тёмная (#857): там --accent-2 — фиолет для текста, белая подпись на нём
     1.9:1. Наведение залитой кнопки — --accent-3, как у .xf-btn: 6.4:1. */
  html[data-theme="dark"] .xf-toast__action--primary:hover { background: var(--accent-3); }
}
/* Money-bar up (PDP buy bar / cart mini-bar / preset-page bar): lift the
   snackbar above it so it never stacks on top of the primary CTA. app.js
   (initMoneyBar) and configurator-preset-page.js toggle body.xf-bottombar-up +
   measure --xf-bottombar-h (offsetHeight already includes the bar's own
   safe-area padding → no env() added here). Раньше правило жило только в
   ≤600 ниже, а полосы — до 767/991: на 601–767 тост «Добавлено в корзину»
   ложился на «В корзину» и «В избранное» поднятой полосы (замер на проде
   700×900: тост 818–876, полоса 831–900; #553). Ширина не нужна — класс
   ставится только там, где полоса существует. */
body.xf-bottombar-up #xf-toast-stack {
  bottom: calc(var(--xf-bottombar-h, 84px) + 12px);
}
/* Consent-бар поднят — тост над ним на любой ширине, как кружок чата
   (chat.css, body.xf-cookie-up .xf-chat). Раньше — только ≤600: шире тост
   стоял на 24 px от низа, внутри баннера (замер на проде 700×900: баннер
   121 px, 779–900; тост 818–876; ревью Codex #563). Идёт после money-bar:
   баннер накрывает и полосу, при обоих состояниях чистим его высоту.
   Мобильное правило в ≤600 ниже (fallback 120 px) — ещё позже, выигрывает там. */
body.xf-cookie-up #xf-toast-stack {
  bottom: calc(var(--xf-cookie-h, 72px) + 16px);
}
/* [#716] Полоса карточки товара стоит НАД баннером cookie (product.css), а не
   под ним, как полоса страницы конфигурации; с #786 так же стоит полоса
   корзины (cart.css). Тосту нужно чистить сумму двух высот, иначе «Добавлено
   в корзину» ложится на саму полосу. Класс xf-bottombar-stacked ставит app.js
   вместе с xf-bottombar-up для этих двух полос. Вес селектора выше обоих
   правил над ним и мобильного ниже — порядок в файле здесь не важен. */
body.xf-cookie-up.xf-bottombar-stacked #xf-toast-stack {
  bottom: calc(var(--xf-cookie-h, 120px) + var(--xf-bottombar-h, 84px) + 12px);
}
@media (max-width: 600px) {
  /* Docked snackbar: sit just above the safe area at the very bottom edge,
     full-width. (Previously lifted a flat +80px to clear the cart bar — but
     that offset applied on EVERY page, so on pages with no bottom bar the
     toast floated up into the middle on iOS, where env(safe-area) piles on
     top. Clearance now applies ONLY when a bottom obstruction is present,
     see the two rules below — mirrors the .xf-floating-bot contract.) */
  #xf-toast-stack {
    right: 12px;
    left: 12px;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  }
  .xf-toast { max-width: none; }
  /* Money-bar up — правило выше, вне этого блока (#553). */
  /* Consent bar up (z 9999, full-width, legally must stay tappable). It
     visually covers the money-bars, so clear ITS height. Last rule → wins
     on equal specificity when both states are active. Mirrors the FAB. */
  body.xf-cookie-up #xf-toast-stack {
    bottom: calc(var(--xf-cookie-h, 120px) + 16px);
  }
}
/* [#722] Первый экран карточки товара на телефоне: полоса «цена + В корзину»
   поднята, потому что основная кнопка ещё впереди (body.xf-bottombar-ahead,
   app.js initMoneyBar), — и над полосой стоит название товара: высоту фото
   подгоняют так, чтобы оно кончалось у её кромки (element.php, #722). Тост над
   полосой ложился на название на 3 секунды (замер на проде 390×664 и
   320×568: «Добавлено в корзину» поверх второй строки названия). В этом
   состоянии тост — сверху, под липкой шапкой (--xf-header-h меряет app.js):
   там фото и крошки, их на 3 секунды закрыть не жалко, и это ближе к значку
   корзины в шапке, куда товар «улетел». Отклик у пальца остаётся — кнопка в
   полосе сама становится «В корзине». Въезжает сверху вниз — та же величина
   12 px через множитель --xf-motion (DESIGN.md, The Motion-Multiplier Rule).
   Только страница товара (:has(.xf-pdp)): класс ahead ставит и полоса корзины
   (#786), а там над полосой не название, и тосту место прежнее. Состояние
   полосы — по классу на body от app.js, как у кружка чата (chat.css, #527),
   а не по её собственному классу. Ширина — вся ширина полосы (≤767): на
   601–767 тост справа шириной до 360 px так же ложился на название. Без
   :has() — прежнее место над полосой. Вес выше правила «баннер + полоса». */
@media (max-width: 767px) {
  body.xf-bottombar-up.xf-bottombar-ahead:has(.xf-pdp) #xf-toast-stack {
    top: var(--xf-header-h, 118px);
    bottom: auto;
    flex-direction: column;
  }
  body.xf-bottombar-ahead:has(.xf-pdp) .xf-toast { transform: translateY(calc(-12px * var(--xf-motion, 1))); }
  body.xf-bottombar-ahead:has(.xf-pdp) .xf-toast.is-shown { transform: translateY(0); }
  body.xf-bottombar-ahead:has(.xf-pdp) .xf-toast.is-leaving { transform: translateY(calc(-12px * var(--xf-motion, 1))); }
}

/* [#323] Плавное появление ленивых картинок. Класс вешает app.js ТОЛЬКО на
   НЕзагруженные к моменту инициализации img[loading=lazy]: кэшовые рисуются
   как есть, прятать нарисованное = мигать. Появление — не движение,
   --xf-motion не участвует. Именно animation, НЕ transition: шорткат
   transition затёр бы у картинки её собственный transition (hover-зум
   обложек плиток L1, home.css) — анимация в этот каскад не лезет. */
.xf-lazyfade { opacity: 0; }
.xf-lazyfade.is-loaded { opacity: 1; animation: xf-lazyfade-in .25s ease-out; }
@keyframes xf-lazyfade-in { from { opacity: 0; } to { opacity: 1; } }

/* End */


/* Start:/local/templates/xcore_front/css/layout.css?1792154827756*/
/* OWNS: .wrapper1, .wrapper_inner, .middle (+ .has-left/.has-right), #content, #panel (Bitrix-compat layout shell — non-xf-* by design, matches aspro DOM contract). */
.wrapper1 {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

#panel { position: relative; z-index: 1000; }

#content {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.wrapper_inner {
  width: 100%;
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--container-pad);
}

.middle { padding: 32px 0; }

.middle.has-left {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 32px;
  align-items: start;
}

.middle.has-right {
  display: grid;
  grid-template-columns: 1fr 280px;
  gap: 32px;
  align-items: start;
}

/* End */


/* Start:/local/templates/xcore_front/css/ui.css?179264525219093*/
/* OWNS: .xf-btn(-*), .xf-pill(-*) (base — specialized in content.css → .xf-pill-tab), .xf-price-* (listing price; NOTE .xf-price-range-* = filter inputs in catsection.css), .xf-section-head-*, .xf-floating-bot(__*), body.xf-cookie-up (FAB↑ над consent-баром), body.xf-bottombar-up→FAB-hide (#61; класс owned by base.css), .xf-skip, :focus-visible (global a11y), .xf-admin-pencil (admin-only) */
/* UI primitives — ported from shared.jsx (Pill, Price, Btn, SectionHead) */

/* M8.2 — global accessibility primitives.
   Single :focus-visible token обеспечивает консистентный accent-ring на всех
   интерактивных элементах (header iconbtn, brand-tile, search). Per-element
   :focus-visible rules в других CSS-файлах используют тот же accent — здесь
   single source of truth для дефолта. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Skip-link — первый focusable элемент на странице, прыгает на main#content.
   Скринридер-only пока не сфокусирован; на focus выезжает в left-top corner. */
.xf-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 1000;
  background: var(--accent);
  color: #fff;
  padding: 12px 20px;
  border-radius: 0 0 10px 0;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 14px;
  text-decoration: none;
  transition: none;
}
.xf-skip:focus,
.xf-skip:focus-visible {
  left: 0;
  outline: 2px solid var(--accent);
  outline-offset: -4px;
}


/* ─── Pill ────────────────────────────────────────────────── */
.xf-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.2px;
  font-family: var(--font-sans);
  background: var(--surface-2);
  color: var(--text-2);
  border: 1px solid var(--line-soft);
  white-space: nowrap;
}
.xf-pill--mono {
  font-family: var(--font-mono);
  text-transform: uppercase;
}
.xf-pill--accent {
  background: var(--accent-dim);
  color: var(--accent-2);
  border-color: var(--accent-line);
}
.xf-pill--success {
  background: oklch(0.76 0.16 155 / 0.14);
  color: oklch(0.84 0.14 155);
  border-color: oklch(0.76 0.16 155 / 0.35);
}
.xf-pill--danger {
  background: oklch(0.72 0.2 25 / 0.14);
  color: oklch(0.82 0.14 25);
  border-color: oklch(0.72 0.2 25 / 0.35);
}
.xf-pill--ghost {
  background: transparent;
  color: var(--text-3);
}

/* ─── Price (product-card listing) ───────────────────────────
   Column layout: the live price (what the buyer pays) on top —
   always present, so it lands on one line across a card row by
   itself. The optional pre-discount "was" sits struck BELOW it.
   No "РРЦ"/label: the strike alone reads as "before"; a label
   would claim an MSRP/former price we don't control (see the
   Price-strike invariant). Used only by xf_render_product_card. */
.xf-price {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
}
.xf-price__val {
  display: inline-block;
  /* Size to the CARD width (container query), not a fixed 24px: a fixed 24px
     overflowed the narrow 3-col-with-sidebar grid in the 768-1090px window and
     (with the old overflow:ellipsis) SILENTLY truncated normal prices like
     "1 266.00" → "1 266…". cqi scales the figure down on narrow cards so it
     always fits un-clipped; capped at 24px so wide cards keep the chosen size. */
  font-size: clamp(16px, 9cqi, 24px);
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.5px;
  line-height: 1;
  white-space: nowrap;        /* one line; never wrap and never ellipsis — the live price must be fully readable */
}
.xf-price__cur {
  color: var(--text-3);
  font-size: 0.65em;
  font-weight: 500;
  margin-left: 4px;
}
/* "Was" — struck, below the live price. Absent entirely on no-anchor
   cards (no reserved slot, no fake number): since the live price is
   the top line, it stays aligned regardless of whether "was" exists. */
.xf-price__was-line {
  display: flex;
  align-items: baseline;
  margin-top: 3px;
}
.xf-price__was {
  /* Onest, НЕ mono (перебивает mono, унаследованный от .xf-price): «вшитый»
     слэш-ноль XCore Mono поверх line-through дал бы двойное перечёркивание на
     мелком зачёркнутом числе. Старая цена — тихая, плоский овал Onest здесь
     честнее; точечного-нуля-как-8 проблемы у struck-цены нет. #92 */
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: 11px;
  font-weight: 500;
  color: var(--text-2);
  line-height: 1.1;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: currentColor;  /* var(--line) was 1.53:1 vs surface — below WCAG 3:1; the strike carries all the "before-price" meaning now that the label is gone, so tie it to the digit color (text-2) — quiet but legible */
}
/* "Цена по запросу" fallback — quieter, same column slot as the live price. */
.xf-price__val--request {
  font-size: 18px;
  font-weight: 500;
  color: var(--text-3);
  letter-spacing: -0.2px;
}

/* ─── Button ──────────────────────────────────────────────── */
.xf-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 16px;
  font-size: 14px;
  font-weight: 500;
  border-radius: 10px;
  border: 1px solid transparent;
  background: var(--accent);
  /* Без border-box градиент рисуется в padding-box и потом размазывается
     по transparent-border — на левом крае градиента (135deg, 0%-stop) видна
     1px-полоска чуть другого тона. С border-box градиент идёт от края. */
  background-origin: border-box;
  background-clip: border-box;
  -webkit-appearance: none;
  appearance: none;
  color: #fff;
  box-shadow: 0 6px 20px -8px var(--accent), 0 0 0 1px oklch(1 0 0 / 0.05) inset;
  transition: transform .15s, box-shadow .15s, background .15s, border-color .15s;
  white-space: nowrap;
  cursor: pointer;
}
@media (hover: hover) {
  .xf-btn:hover { transform: translateY(calc(-1px * var(--xf-motion, 1))); }
}

.xf-btn--sm { padding: 7px 12px; font-size: 13px; border-radius: 8px; gap: 6px; }
.xf-btn--lg { padding: 14px 22px; font-size: 15px; border-radius: 12px; gap: 10px; }

/* `.xf-btn--grad` снят вместе с токеном --grad по решению владельца 2026-07-29
   (#322): «фиолетовый градиент... я ж вообще бы хотел его с сайта убрать
   максимально». Все 12 мест разметки переведены на обычный `.xf-btn` — он и
   был Builder Indigo, градиент лишь накрывал его сверху. Возвращать модификатор
   не надо: иерархию на всех этих экранах держит пара primary/ghost. */
.xf-btn--ghost {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--line-soft);
  box-shadow: none;
}
.xf-btn--outline {
  background: transparent;
  color: var(--text);
  border-color: var(--line);
  box-shadow: none;
}
.xf-btn--subtle {
  background: var(--surface);
  color: var(--text-2);
  box-shadow: none;
}
.xf-btn--link {
  background: transparent;
  color: var(--accent-2);
  box-shadow: none;
  padding: 0;
}
.xf-btn--icon {
  padding: 0;
  width: 36px;
  height: 36px;
}
.xf-btn--icon.xf-btn--sm { width: 30px; height: 30px; }

/* Тёмная тема («Ночная вывеска», DESIGN.md → Dark Theme). Форма та же, что
   в светлой (10–12px, не пилюля); материал — ровный accent без цветной тени:
   залитая кнопка — «клавиша с подсветкой», блик по верхней кромке. Свет под
   кнопкой есть только у главной кнопки главной (.xf-pitch__cta, home.css).
   Наведение — на ступень глубже (--accent-3: белая подпись 6.4:1). Ghost и
   outline — прозрачные, с волосяной рамкой. Специфичность 0-2-1 / 0-3-1
   выше, чем у состояний светлой (:hover, модификаторы), — это часть
   поведения: правки состояний в тёмной сверяй с ней. */
html[data-theme="dark"] .xf-btn { box-shadow: none; }
html[data-theme="dark"] .xf-btn:where(:not(.xf-btn--ghost, .xf-btn--outline, .xf-btn--subtle, .xf-btn--link)) {
  box-shadow: var(--nx-key);
}
@media (hover: hover) {
  html[data-theme="dark"] .xf-btn:where(:not(.xf-btn--ghost, .xf-btn--outline, .xf-btn--subtle, .xf-btn--link)):hover {
    background: var(--accent-3);
  }
}
html[data-theme="dark"] .xf-btn--ghost,
html[data-theme="dark"] .xf-btn--outline {
  background: transparent;
  border-color: var(--line);
}

/* ─── Section head (eyebrow + H2 + optional link) ────────── */
.xf-section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 28px;
  flex-wrap: wrap;
}
.xf-section-head__eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent-2);
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.xf-section-head__eyebrow::before {
  content: "";
  display: inline-block;
  width: 28px;
  height: 1px;
  background: var(--accent-2);
}
.xf-section-head__title {
  margin: 0;
  font-size: 38px;
  font-weight: 600;
  letter-spacing: -1.2px;
  line-height: 1.05;
}
.xf-section-head__link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  color: var(--text-2);
  padding-bottom: 8px;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-section-head__link:hover { color: var(--text); }
}
/* Тёмная тема: моно-надпись над заголовком («ТОВАРЫ», «ПАРТНЁРЫ») дублирует
   сам заголовок — её нет (DESIGN.md → Dark Theme). В светлой остаётся
   (DESIGN.md → Label). */
html[data-theme="dark"] .xf-section-head__eyebrow { display: none; }

/* ─── Floating service bot (extended FAB) ──────────────────
   M31: была немая 56px-иконка → расширенная кнопка с подписью.
   Десктоп: pill «✦ Сервисный ИИ-бот», sub-строка (гарантия ·
   заказы · поддержка) раскрывается на hover/focus. Мобайл:
   icon-only кружок (подпись живёт в drawer; pill перекрыл бы
   sticky-CTA). Одноразовый entrance-cue, без вечной пульсации.
   Solid accent-индиго (gradient зарезервирован под xf-configurator-pill). */
.xf-floating-bot {
  position: fixed;
  right: 24px;
  bottom: 24px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  height: 56px;
  padding: 0 16px; /* симметричный внешний воздух слева/справа (было 18/15 — кривило) */
  border-radius: 28px;
  background: var(--accent);
  color: #fff;
  text-decoration: none;
  box-shadow: 0 8px 24px -8px var(--accent), 0 0 0 1px oklch(1 0 0 / 0.05) inset;
  z-index: 100;
  /* opacity/visibility в transition — для мобильного hide над sticky-баром
     (#61, ниже): плавный fade + выход из фокус-порядка. На десктопе opacity
     FAB не меняется, безвредно. prefers-reduced-motion (ниже) гасит весь
     transition → мгновенный snap, регресса для reduce нет. */
  transition: transform .2s, box-shadow .2s, bottom .3s ease, opacity .2s ease, visibility .2s;
  animation: xf-bot-in .45s ease;
}
/* Глиф sparkle нарисован с центром массы на ~1.7px выше центра вьюбокса
   (path: M12 3 … 12 17 → центр y=10 при центре бокса 12). Scoped-сдвиг
   опускает его к оптическому центру; глобальный xf_icon('sparkle') не трогаем. */
.xf-floating-bot__icon { display: inline-flex; flex: 0 0 auto; transform: translateY(1.5px); }
.xf-floating-bot__text {
  display: flex;
  flex-direction: column;
  justify-content: center;
  line-height: 1.15;
  min-width: 0;
}
.xf-floating-bot__title { font-size: 14px; font-weight: 600; white-space: nowrap; }
.xf-floating-bot__sub {
  font-size: 11px;
  white-space: nowrap;
  max-height: 0;
  opacity: 0;
  overflow: hidden;
  transition: max-height .2s ease, opacity .2s ease;
}
.xf-floating-bot:focus-visible .xf-floating-bot__sub { max-height: 1.2em; opacity: .85; }
@media (hover: hover) {
  .xf-floating-bot:hover {
    transform: translateY(calc(-2px * var(--xf-motion, 1)));
    box-shadow: 0 12px 28px -8px var(--accent), 0 0 0 1px oklch(1 0 0 / 0.05) inset;
  }
  .xf-floating-bot:hover .xf-floating-bot__sub { max-height: 1.2em; opacity: .85; }
}
.xf-floating-bot:active { transform: translateY(0) scale(calc(1 - 0.03 * var(--xf-motion, 1))); }
/* Тёмная тема: цветная тень под кнопкой гаснет — на почти чёрном нейтральная
   тень держит кнопку над страницей, а светится только вывеска главной. */
html[data-theme="dark"] .xf-floating-bot { box-shadow: 0 10px 28px -12px oklch(0 0 0 / 0.8); }
/* Cookie-consent clearance: пока нижний consent-бар поднят (первый визит),
   поднимаем FAB над ним — иначе на самом важном для discovery моменте подпись
   спрятана за баром (.xf-cookie z-index 9999, во всю ширину низа). app.js
   (initBotCookieClearance) ставит body.xf-cookie-up + меряет --xf-cookie-h по
   факту (высота бара плавает: 65px десктоп / ~117px мобайл). Нет JS → нет
   подъёма (graceful, как было — не регресс). bottom анимируется (.3s выше). */
body.xf-cookie-up .xf-floating-bot { bottom: calc(var(--xf-cookie-h, 72px) + 24px); }
@keyframes xf-bot-in {
  from { opacity: 0; transform: translateY(10px) scale(.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .xf-floating-bot { animation: none; transition: none; }
  .xf-floating-bot:hover { transform: none; }
  .xf-floating-bot__sub { transition: none; }
}
@media (max-width: 640px) {
  /* icon-only на мобайле — подпись в drawer; pill перекрыл бы sticky-CTA. */
  .xf-floating-bot {
    width: 52px;
    height: 52px;
    padding: 0;
    gap: 0;
    border-radius: 50%;
    justify-content: center;
    right: 16px;
    bottom: 16px;
  }
  .xf-floating-bot__text { display: none; }
  /* мобильный consent-бар выше (текст переносится) — больший fallback */
  body.xf-cookie-up .xf-floating-bot { bottom: calc(var(--xf-cookie-h, 120px) + 16px); }
  /* [#61] Нижний sticky-бар поднят (PDP «В корзину» / sticky-оформление
     корзины) → прячем бот. В момент активной покупки бар владеет thumb-зоной
     и нижним углом; второй accent-индиго кружок поверх бара ломал бы One-Voice
     и ловил мис-тапы по сердечку/CTA (FAB 52×52 полностью накрывал правый край
     бара). Зеркалит прецедент конфигуратора (body.page-configurator → display:
     none ниже). Бот остаётся в burger-drawer (header.php) и в CTA отзывов на
     PDP; возвращается на скролл вверх. Сигнал body.xf-bottombar-up +
     --xf-bottombar-h уже ставит app.js (initBuySticky/initCartSticky) — JS не
     трогаем. visibility:hidden + opacity-fade (idiom выше) = плавный уход на
     показе мгновенный, на скрытии после фейда, + выход из фокус-порядка. */
  body.xf-bottombar-up .xf-floating-bot {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(8px) scale(0.94);
  }
}
/* Конфигуратор: легаси-FAB остаётся скрыт (#201 держит гейт ТОЛЬКО здесь).
   .xf-floating-bot рендерится ТОЛЬКО в rollback-ветке footer.php
   ($xf_chat_live=false) и взаимоисключим с новым доком .xf-chat — на живом
   проде (xf_chat_live=true) он вообще не в DOM. #201 «вернул чат на конфигуратор»
   снял гейт у ЖИВОГО пути (.xf-chat/.xf-aichat/.bx-livechat-wrapper, см. chat.css/
   aichat.css); легаси-кружок держим скрытым — плодить второй тип FAB не нужно,
   а в rollback web-чат и кнопка #202 всё равно выключены (build-competent-поток
   отсутствует), телеграм-бот при этом достижим из burger-drawer (header.php).
   Раньше bump'или bottom до 160px (P0 c4646c8 + M4 P1#3). */
body.page-configurator .xf-floating-bot { display: none; }

/* [#36] Кнопка-карандаш «Изменить товар/раздел» (xf_admin_edit_pencil в
   helpers.php). Рендерится ТОЛЬКО админу/редактору в «Режиме правки» —
   на покупательский UI не влияет, One-Voice не трогает. */
.xf-admin-pencil {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border: 1px dashed var(--accent-line);
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--accent-2);
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .15s ease;
}
.xf-admin-pencil:hover { background: var(--accent-dim); }

/* End */


/* Start:/local/templates/xcore_front/css/header.css?179265388452926*/
/* OWNS: .xf-header-*, .xf-burger-btn, .xf-cart-btn(-*), .xf-catalog-btn, .xf-account-pill-*, .xf-configurator-pill-*, .xf-mcall(-*) (top chrome), .xf-theme-toggle(-*) (тумблер темы), .xf-drawer, .xf-drawer__* (мобильное меню-шторка; её подложка .xf-drawer-backdrop — в modal.css), .xf-search__icon + `.xf-search input` — САМО шапочное поле поиска (выпадашка подсказок .xf-search__dropdown и страница /search/ — за search.css; ВНИМАНИЕ: `.xf-search` объявлен в ОБОИХ файлах, здесь раскладка в ряду шапки, там position:relative как якорь выпадашки — правку `.xf-search` сверяй с search.css:6), + scroll-margin-top на цели skip-link'а `#content` (компенсация липкой шапки, #388 — величину меряет js/app.js в --xf-header-h) */
.xf-header {
  position: sticky;
  top: 0;
  z-index: 50;
}

/* Фон + блюр + нижний бордер на ::before, НЕ на самой шапке: backdrop-filter
   прямо на sticky-элементе мерцает при скролле (Chrome/Safari кадрами теряют
   блюр-слой — шапка моргает прозрачным, #323). Псевдослой не участвует в
   sticky-репозиционировании; z-index:-1 держит его под контентом шапки
   внутри её stacking context. Бордер тоже здесь: inset резолвится по
   padding box, бордер на самой шапке остался бы БЕЗ подложки — полупрозрачная
   линия над сырым контентом пульсирует при скролле (замерено в ревью #324);
   низ -1px возвращает линии её место. isolation на .xf-header не добавлять:
   она станет backdrop root и блюру будет нечего сэмплировать. */
.xf-header::before {
  content: '';
  position: absolute;
  inset: 0 0 -1px;
  z-index: -1;
  background: var(--header-bg);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border-bottom: 1px solid var(--line-soft);
  transform: translateZ(0);
}

/* [#388] Skip-link «К содержимому» приводит НЕ под липкую шапку.
   Замер живого /dev/ 2026-08-06: после клика по skip-link #content вставал на
   top = 0, а шапка накрывает первые 154 px (десктоп 1440) и 118 px (телефон
   390) — то есть посетителя с клавиатурой приводило ровно туда, куда не видно,
   и первый экран содержимого приходилось доскролливать руками. Смысл ссылки в
   обратном: миновать шапку и попасть в содержимое. Шапка липкая на всех
   страницах, а #content — цель skip-link'а везде, поэтому правило одно на сайт.
   Почему scroll-margin на цели, а не scroll-padding-top на <html>: второе
   красивее (чинит вообще все якорные переходы), но оно СКЛАДЫВАЕТСЯ с уже
   существующими per-anchor компенсациями — на карточке товара ссылка
   «Все характеристики →» ведёт на #specs, у которого свой scroll-margin-top
   172 px в CSS карточки, и переход отъехал бы на 334 px вместо 172
   (проверено замером: с scroll-margin якорь встаёт на 172, как и был).
   Глобальное правило станет уместным, когда такие точечные компенсации уйдут.
   Сам селектор #content принадлежит css/layout.css — здесь у него одно
   свойство, и причина у него шапочная (высота липкой шапки), поэтому правило
   живёт рядом с ней и объявлено в OWNS первой строкой файла.
   Величину меряет js/app.js (--xf-header-h): высота шапки зависит и от ширины
   экрана, и от страницы. Fallback'и — замеренные, работают до инициализации
   скрипта и без JS вовсе. +8 px — воздух, чтобы содержимое не прилипало к
   нижней кромке шапки. */
#content { scroll-margin-top: calc(var(--xf-header-h, 154px) + 8px); }
/* Тот же fallback для телефона (замер: 118 px). Живёт рядом с десктопным, а не
   в общем мобильном блоке ниже: пара «два запасных значения одной величины»
   читается только вместе. */
@media (max-width: 767px) {
  #content { scroll-margin-top: calc(var(--xf-header-h, 118px) + 8px); }
}

.xf-header__top { border-bottom: 1px solid var(--line-soft); }

.xf-header__row {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--container-pad);
  display: flex;
  align-items: center;
}

.xf-header__row--top {
  height: 38px;
  justify-content: space-between;
  font-size: 12.5px;
  color: var(--text-3);
}

.xf-header__row--top .xf-delivery,
.xf-header__row--top .xf-phone {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-2);
}
/* Телефон — контакт-действие (tel:), не data → Onest, не mono: JetBrains Mono
   рисует ноль с точкой (в мелком 0→8, клиенты звонили не туда). См. #91. */
.xf-header__row--top .xf-phone {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
}

/* [#191] Телефон-кнопка + попап контактов (паритет aspro-дропдауна).
   Попап — плавающая поверхность: shadow-card оправдан (как toast), flat-by-default
   не нарушаем. Номера — Onest + tabular-nums (#91), НЕ mono. */
.xf-phone-wrap { position: relative; }
.xf-header__row--top button.xf-phone {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  font-size: inherit;
}
.xf-header__row--top button.xf-phone:hover { color: var(--text); }
.xf-phone__chevron {
  display: inline-flex;
  color: var(--text-3);
  transition: transform 0.15s ease-out;
}
.xf-phone-wrap.is-open .xf-phone__chevron { transform: rotate(180deg); }
.xf-phone-pop {
  position: absolute;
  top: calc(100% + 10px);
  right: -8px;
  z-index: 60;
  min-width: 358px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  box-shadow: var(--shadow-card);
}
.xf-phone-pop__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 9px 12px;
  border-radius: 8px;
  text-decoration: none;
}
.xf-phone-pop__row:hover { background: var(--surface-2); }
.xf-phone-pop__num {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text);
}
.xf-phone-pop__num--tg {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.xf-phone-pop__note {
  font-size: 12px;
  color: var(--text-3);
  white-space: nowrap;
}
.xf-phone-pop__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 4px;
  padding: 9px 12px 7px;
  border-top: 1px solid var(--line-soft);
  font-size: 12px;
  color: var(--text-2);
}
.xf-phone-pop__meta svg { flex: 0 0 auto; color: var(--text-3); }
.xf-phone-pop__all {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 12px 8px;
  font-size: 12.5px;
  color: var(--accent-2);
  text-decoration: none;
}
.xf-phone-pop__all:hover { color: var(--accent-3); }
/* Тёмная (#858): --accent-3 там — заливка наведения кнопок, текстом на
   --surface 3.0:1. Ссылка в покое уже фиолетом текста, отклик — белый. */
html[data-theme="dark"] .xf-phone-pop__all:hover { color: var(--text); }
@media (prefers-reduced-motion: reduce) {
  .xf-phone__chevron { transition: none; }
}

.xf-header__top-right {
  display: inline-flex;
  align-items: center;
  gap: 16px;
}
.xf-header__top-right .xf-divider {
  width: 1px;
  height: 14px;
  background: var(--line);
}

/* M3: xf-utility-bot удалён из top-bar — бот переехал в floating-кнопку
   (см. footer.php + .xf-floating-bot в ui.css). Снимает один gradient в
   header'е (теперь только xf-configurator-pill держит gradient). */

.xf-header__row--main {
  height: 72px;
  gap: 28px;
}

.xf-logo {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  line-height: 1;
  color: var(--text);
  flex: 0 0 auto;
}
.xf-logo > svg {
  margin-right: -9px;
}
.xf-logo__dash {
  width: 10px;
  height: 4px;
  border-radius: 2px;
  background: currentColor;
  flex: 0 0 auto;
}
.xf-logo__text {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.6px;
  font-family: var(--font-sans);
  transform: translateY(-1px);
}

/* M6.2 redux: configurator-pill возвращён в исходную позицию рядом с лого, но
   в quieter форме — gradient + glow сняты. Прозрачный фон, тонкая
   accent-окантовка как subtle highlight для key-feature. Icon-tile внутри
   tinted flat. Hover — лёгкий accent-wash.
   (Оговорка «без var(--grad)» снята: с #322 градиента нет нигде, эта пилюля
   больше не исключение из правила, а просто общий случай.) */
.xf-configurator-pill {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 9px 14px 9px 7px;
  border-radius: 12px;
  background: transparent;
  border: 1px solid var(--accent-line);
  color: var(--text);
  font-size: 13px;
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
  transition: background .18s, border-color .18s, transform .18s;
  flex: 0 0 auto;
}
@media (hover: hover) {
  .xf-configurator-pill:hover {
    background: var(--accent-dim);
    border-color: var(--accent);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
}
.xf-configurator-pill:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.xf-configurator-pill.is-active {
  background: var(--accent-dim);
  border-color: var(--accent);
}
/* Тёмная тема: шапка спокойная — пилюля без accent-окантовки, волосяная
   рамка. Специфичность 0-2-1 выше :hover и .is-active (0-2-0): в тёмной
   пилюля не заливается ни на наведении, ни на странице конфигуратора — так
   было со скином с #707 и так остаётся. Сдвиг на наведении живёт. */
html[data-theme="dark"] .xf-configurator-pill {
  border: 1px solid var(--line);
  background: transparent;
}
.xf-configurator-pill__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--accent-dim);
  color: var(--accent);
  flex: 0 0 auto;
}
.xf-configurator-pill__label {
  display: inline-block;
}

.xf-search {
  flex: 1;
  position: relative;
  max-width: 560px;
}
.xf-search__icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--text-3);
  pointer-events: none;
  display: inline-flex;
}
.xf-search input {
  width: 100%;
  height: 46px;
  padding: 0 14px 0 40px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  font-size: 13.5px;
  color: var(--text);
  transition: border-color .15s;
}
/* [#373] Здесь БЫЛ `outline: none` — он гасил ГЛОБАЛЬНЫЙ :focus-visible
   (ui.css) персонально для поля поиска, специфичность 0,1,1 против 0,1,0.
   Поле оставалось единственным элементом страницы без кольца фокуса: из 40
   пройденных Tab'ом элементов главной кольцо отсутствовало ровно у него.
   Единственным индикатором была смена рамки на --accent-line, а это accent
   с альфой 0.30/0.35 — 1.55:1 в светлой теме и 1.75:1 в тёмной при норме
   1.4.11 = 3:1.

   Починено снятием ПЕРЕБИВАНИЯ, а не новым per-element правилом: инвариант
   проекта — «глобальный :focus-visible + .xf-skip; per-element focus-правила
   не писать». Поэтому ниже нет ни одного :focus-visible — просто некому
   больше гасить глобальное кольцо.

   Геометрию кольцо не ломает: outline рисуется вне border-box и не участвует
   в раскладке, вертикальных клиппящих overflow над полем нет (проверено на
   живом /dev/ — кольцо видно целиком). Второй экземпляр той же суппрессии
   жил в search.css (поле на странице /search/) — снят вместе с этим. */
.xf-search input::placeholder { color: var(--text-3); }
.xf-search input:focus { border-color: var(--accent-line); }
/* Тёмная тема: поле — дымка поверх фона, а не плита --surface; рамка --line
   (белый 12%) видна на почти чёрном. В фокусе — светлый фиолет --accent-2. */
html[data-theme="dark"] .xf-search input {
  background: oklch(1 0 0 / 0.04);
  border-color: var(--line);
}
html[data-theme="dark"] .xf-search input:focus { border-color: var(--accent-2); }

.xf-header__actions {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
}
.xf-iconbtn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px;
  border-radius: 10px;
  color: var(--text-2);
  transition: all .15s;
}
@media (hover: hover) {
  .xf-iconbtn:hover {
  background: var(--surface-2);
  color: var(--text);
}
}
.xf-iconbtn__count {
  position: absolute;
  top: 4px;
  right: 4px;
  background: var(--accent);
  color: #fff;
  font-size: 9.5px;
  font-weight: 600;
  font-family: var(--font-mono);
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
}
/* Wishlist badge — red, to match the emotional cue on card hearts. */
.xf-iconbtn[data-xf-wish-icon] .xf-iconbtn__count { background: #e11d48; }
/* Header pill — залогиненный статус (аватар+имя) рядом с иконками. */
.xf-account-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 12px 5px 5px;
  border-radius: 999px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  color: var(--text);
  font-size: 13px;
  transition: background .15s, border-color .15s;
  max-width: 180px;
}
@media (hover: hover) {
  .xf-account-pill:hover {
  background: var(--accent-dim);
  border-color: var(--accent-line);
}
}
.xf-account-pill__avatar {
  flex: 0 0 26px;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  font-weight: 600;
  font-size: 12px;
  letter-spacing: -0.3px;
}
.xf-account-pill__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 130px;
  line-height: 1;
}

.xf-header__actions .xf-divider {
  width: 1px;
  height: 28px;
  background: var(--line);
  margin: 0 6px;
}
.xf-cart-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px 11px 12px;
  border-radius: 10px;
  background: var(--accent);
  color: #fff;
  font-size: 13px;
  font-weight: 500;
  box-shadow: 0 6px 18px -8px var(--accent);
  transition: transform .15s, box-shadow .15s;
}
@media (hover: hover) {
  .xf-cart-btn:hover { transform: translateY(calc(-1px * var(--xf-motion, 1))); }
}
/* Тёмная тема: как у залитой .xf-btn (ui.css) — без цветной тени, блик по
   верхней кромке. */
html[data-theme="dark"] .xf-cart-btn { box-shadow: var(--nx-key); }
.xf-cart-btn__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  background: #fff;
  color: var(--accent-3);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  font-family: var(--font-mono);
  line-height: 1;
}

.xf-header__nav { border-top: 1px solid var(--line-soft); }
/* M9.3: row сделан компактнее (52→42), pills сняты hover-tint, nav-item
   text-only и легче — closes 4-я critique P2 #4 «top-nav competes with
   cat-grid». Quick-shortcut text strip, не pill-row. */
.xf-header__row--nav {
  height: 42px;
  gap: 2px;
  /* Rail-aligned: компенсируем 12px внутренний padding pill'ов
     (`.xf-catalog-btn`, `.xf-nav-item`) — glyph первого и последнего
     элемента встаёт на ту же вертикальную линию, что truck-иконка в
     topbar'е и SVG-лого в main-row'е (x = --container-pad). */
  padding-inline: calc(var(--container-pad) - 12px);
}

/* M3: tiered visibility — «Каталог» = primary entrypoint в megamenu;
   pill-row = тише, secondary shortcuts. Различие через size + color +
   icon-presence, не через одинаковый pill-уровень. */
.xf-catalog-btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  border-radius: 10px;
  color: var(--text-2);
  font-size: 13px;
  font-weight: 500;
  margin-right: 4px;
  transition: color .15s, background .15s;
  white-space: nowrap;
}
@media (hover: hover) {
  .xf-catalog-btn:hover { background: var(--surface); color: var(--text); }
}

/* M9.3: nav-item — pure text shortcut. Без pill-tint (background) и
   border-radius на hover, мельче font-size (12→11.5), уже padding (7→4),
   opacity + летающий accent-color вместо background-tint. */
.xf-nav-item {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  color: var(--text-3);
  font-size: 11.5px;
  letter-spacing: 0.1px;
  transition: color .15s;
  white-space: nowrap;
}
@media (hover: hover) {
  .xf-nav-item:hover { color: var(--accent-2); }
}
.xf-catalog-btn.is-open {
  color: var(--accent-2);
}

/* ───────────────────────────────────────────────────────────
   Megamenu panel — opened by .xf-catalog-btn
   ─────────────────────────────────────────────────────────── */

.xf-mega {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  background: var(--bg-2);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
  box-shadow: var(--shadow-card);
  z-index: 45;
  max-height: min(560px, 70vh);
  overflow: hidden;
}
.xf-mega__inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: 18px var(--container-pad) 22px;
}

.xf-mega__layout {
  display: grid;
  grid-template-columns: 272px 1fr;
  gap: 28px;
  min-height: 380px;
}

/* Left — vertical list of 16 top-level categories */
.xf-mega__nav {
  list-style: none;
  margin: 0;
  padding: 0;
  border-right: 1px solid var(--line-soft);
  padding-right: 12px;
  max-height: 500px;
  overflow-y: auto;
}
.xf-mega__nav-item {
  border-radius: 10px;
  transition: background .12s;
}
.xf-mega__nav-item.is-active {
  background: var(--surface);
}
@media (hover: hover) {
  .xf-mega__nav-item:hover {
  background: var(--surface);
}
}
.xf-mega__nav-item.is-active .xf-mega__nav-name { color: var(--text); font-weight: 500; }
.xf-mega__nav-item.is-active .xf-mega__nav-arrow { color: var(--accent-2); opacity: 1; }
.xf-mega__nav-link {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 12px;
  color: var(--text-2);
  font-size: 13.5px;
  line-height: 1.3;
}
@media (hover: hover) {
  .xf-mega__nav-link:hover { color: var(--text); }
}
.xf-mega__nav-icon {
  flex: 0 0 22px;
  width: 22px;
  height: 22px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-3);
}
.xf-mega__nav-item.is-active .xf-mega__nav-icon { color: var(--accent-2); }
@media (hover: hover) {
  .xf-mega__nav-item:hover .xf-mega__nav-icon { color: var(--accent-2); }
}
.xf-mega__nav-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.xf-mega__nav-arrow {
  flex: 0 0 auto;
  display: inline-flex;
  color: var(--text-3);
  opacity: 0;
  transition: opacity .12s, color .12s;
}
@media (hover: hover) {
  .xf-mega__nav-item:hover .xf-mega__nav-arrow { opacity: .7; }
}

/* Right — pane with hovered category's children */
.xf-mega__panes {
  position: relative;
  min-width: 0;
  padding: 4px 4px 4px 8px;
}
.xf-mega__pane { display: none; }
.xf-mega__pane.is-active { display: block; }

.xf-mega__pane-head {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
  color: var(--text);
  transition: color .15s;
}
@media (hover: hover) {
  .xf-mega__pane-head:hover { color: var(--accent-2); }
}
.xf-mega__pane-title {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.3px;
}
.xf-mega__pane-all {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  font-family: var(--font-mono, inherit);
}

.xf-mega__pane-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px 24px;
}
.xf-mega__pane-list a {
  display: block;
  padding: 6px 0;
  font-size: 13.5px;
  color: var(--text-2);
  line-height: 1.35;
  transition: color .12s;
}
@media (hover: hover) {
  .xf-mega__pane-list a:hover { color: var(--accent-2); }
}

@media (max-width: 1100px) {
  .xf-mega__layout { grid-template-columns: 240px 1fr; gap: 20px; }
  .xf-mega__pane-list { grid-template-columns: 1fr; }
}
@media (max-width: 860px) {
  .xf-mega { display: none; }
}

/* [#375] Планшетная полоса шорткатов: последний пункт уезжал за экран.
   Замер живой /dev/ 2026-08-06 при вьюпорте 768: содержимое полосы — 793px,
   «ТВ и видео» занимает x=711…793, то есть 25px за краем; overflow-x был
   visible, прокрутки не было, добраться до пункта было нельзя — он читался
   как «ТВ и вид…». Полоса ломается на всём отрезке 768…820 (793 + 28px
   правого рельса = 821 — столько ей нужно).

   Полоса на планшете — не украшение: `.xf-mega` тут уже спрятана (правило
   выше), а бургер появляется только ниже 768. То есть в окне 768…860 эти
   восемь ссылок — единственный путь в разделы каталога кроме «Каталога».
   Поэтому пункт нужно вернуть, а не спрятать.

   Из трёх вариантов issue (прокрутка / перенос на вторую строку / поднять
   порог показа) взята прокрутка: перенос удваивает высоту тихого
   вспомогательного ряда в липкой шапке на самом тесном экране и читается как
   случайный «сирота» на второй строке, а поднятый порог отнимает навигацию
   там, где её нечем заменить. Прокрутка держит высоту шапки и не зависит от
   длины подписей — `$xf_nav` живёт в header.php и может смениться.

   Fade-маска шириной ровно в рельс (28px = --container-pad - 12): в конце
   прокрутки под ней оказывается только сам рельс, последний пункт не
   притушён; в середине под маску уезжают пункты, и край читается как
   «дальше есть ещё». Кольцо фокуса не режется: пункт 22px в ряду 42px,
   кольцу с offset 2px остаётся 14px запаса сверху и снизу, а первому пункту
   слева — 24px рельса. */
@media (min-width: 768px) and (max-width: 860px) {
  .xf-header__row--nav {
    overflow-x: auto;
    scrollbar-width: none;
    scroll-padding-inline: calc(var(--container-pad) - 12px);
    mask-image: linear-gradient(to right, #000 calc(100% - (var(--container-pad) - 12px)), transparent 100%);
  }
  .xf-header__row--nav::-webkit-scrollbar { width: 0; height: 0; }
}

/* [M17b] Theme toggle — 56×24 pill со sliding knob. Живёт в
   xf-header__top-right после телефона. Default state (без
   data-theme на <html>) = light: knob справа над sun-слотом,
   moon-иконка видна слева как "куда поедешь". В dark — зеркально. */
.xf-theme-toggle {
  position: relative;
  display: inline-flex;
  align-items: stretch;
  width: 56px;
  height: 32px;
  padding: 0;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  cursor: pointer;
  transition: background .2s, border-color .2s;
  flex: 0 0 auto;
}
@media (hover: hover) {
  .xf-theme-toggle:hover { border-color: var(--accent-line); }
}
.xf-theme-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.xf-theme-toggle__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  color: var(--text-3);
  pointer-events: none;
  transition: opacity .2s;
}
/* Track icon = hint "куда поедешь" (dim), knob icon = current state (accent).
   Track at 0.45 разводит visual weight — current явно крикливее, target тише. */
.xf-theme-toggle__icon--moon { opacity: 0.45; }
.xf-theme-toggle__icon--sun  { opacity: 0; }
html[data-theme="dark"] .xf-theme-toggle__icon--moon { opacity: 0; }
html[data-theme="dark"] .xf-theme-toggle__icon--sun  { opacity: 0.45; }
@media (hover: hover) {
  .xf-theme-toggle:hover .xf-theme-toggle__icon { opacity: 0.7; }
}

.xf-theme-toggle__knob {
  position: absolute;
  top: 3px;
  left: 29px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  box-shadow: 0 2px 6px -1px oklch(0 0 0 / 0.3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: left .22s cubic-bezier(.4, 0, .2, 1);
  pointer-events: none;
}
html[data-theme="dark"] .xf-theme-toggle__knob { left: 3px; }

.xf-theme-toggle__knob-ico {
  position: absolute;
  display: inline-flex;
  transition: opacity .2s;
}
.xf-theme-toggle__knob-ico--sun  { opacity: 1; }
.xf-theme-toggle__knob-ico--moon { opacity: 0; }
html[data-theme="dark"] .xf-theme-toggle__knob-ico--sun  { opacity: 0; }
html[data-theme="dark"] .xf-theme-toggle__knob-ico--moon { opacity: 1; }

/* ───────────────────────────────────────────────────────────
   [M24-m1] Mobile burger + drawer
   Бургер спрятан на desktop, показан < 768px. Drawer — slide-in
   слева, хранит весь functional-overflow: каталог-категории,
   личные ссылки, контакты, theme-toggle.
   ─────────────────────────────────────────────────────────── */

.xf-burger-btn {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  padding: 0;
  margin-right: 2px;
  background: transparent;
  border: 0;
  border-radius: 10px;
  color: var(--text);
  cursor: pointer;
  transition: background .15s;
  flex: 0 0 auto;
}
@media (hover: hover) {
  .xf-burger-btn:hover { background: var(--surface-2); }
}
.xf-burger-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Drawer — полностью скрыт на desktop. На mobile живёт, но closed by default. */
.xf-drawer { display: none; }

/* [#301] Полоса «доставка + звонок» под шапкой. Живёт ТОЛЬКО на мобильном:
   на >=768px телефон и срок показывает топбар (.xf-header__row--top), дубль не нужен.
   Разметку эмитит header.php сразу после </header>, в нормальном потоке — полоса
   намеренно НЕ sticky: постоянный налог на высоту экрана дороже, чем её польза,
   а нужна она в первом экране. На главной header.php вешает --solo и не
   рендерит .xf-mcall__promise — остаётся одна кнопка (#313). */
.xf-mcall { display: none; }

/* ───────────────────────────────────────────────────────────
   Mobile layout (< 768px)
   ─────────────────────────────────────────────────────────── */

@media (max-width: 767px) {
  /* Прячем desktop-only ряды и mega-menu */
  .xf-header__top,
  .xf-header__nav,
  .xf-mega { display: none !important; }

  /* [#301] Вместе со скрытым .xf-header__top выше со страницы уходят ОБА телефона
     и обещание срока — для магазина, где заказ звонком рабочий канал, это потеря
     главного пути. Полоса возвращает ровно эти две вещи; на главной — одну,
     звонок: срок там уже стоит строкой факта под заголовком вывески (#313,
     решение владельца 2026-09-12), полоса с --solo несёт только кнопку.
     Раскладка В СТОЛБИК: обещание строкой, под ним кнопка звонка во всю ширину.
     Измерено, не угадано — на 390px «Доставка по РБ за 1-2 дня» и номер в один ряд
     не влезают, обещание ломалось на две строки. Столбик решает и это, и задачу
     владельца («кнопка звонка тут самое важное»): во всю ширину она крупнейшая
     цель экрана. */
  .xf-mcall {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    /* x-паддинг = .xf-header__row--main ниже (14px) — полоса встаёт по той же
       вертикали, что бургер слева и корзина справа. */
    padding: 10px 14px;
    border-bottom: 1px solid var(--line-soft);
  }

  .xf-mcall__promise {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    font-size: 12.5px;          /* = .xf-header__row--top, тот же голос utility-строки */
    line-height: 1.25;
    color: var(--text-2);
  }
  .xf-mcall__promise svg { flex: 0 0 auto; color: var(--text-3); }

  /* Кнопка звонка. Залитый индиго в этом же первом экране уже занят корзиной
     (.xf-cart-btn) — по One-Voice Indigo второй filled-CTA здесь запрещён:
     акцент живёт в рамке и иконке, номер набран ink'ом (контраст AA в обеих темах).
     Номер — Onest + tabular-nums, НЕ mono: tel: это действие, а не данные, а
     точечный ноль JetBrains Mono в мелком кегле читался как «8» (#91). */
  .xf-mcall__btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 44px;           /* touch target — палец, не курсор (низкий экран — 40, #821 ниже) */
    padding: 0 13px;
    border: 1px solid var(--accent-line);
    border-radius: 10px;
    background: var(--surface-2);
    color: var(--text);
    font-family: var(--font-sans);
    font-variant-numeric: tabular-nums;
    font-size: 14px;
    font-weight: 500;
    letter-spacing: -0.2px;
    white-space: nowrap;
    transition: transform .15s, border-color .15s;
  }
  .xf-mcall__btn-icon { display: inline-flex; color: var(--accent); }
  .xf-mcall__btn:active {
    transform: translateY(calc(1px * var(--xf-motion, 1)));
    border-color: var(--accent);
  }

  /* От 440px (крупные телефоны, планшеты до 767px) обещание и номер помещаются
     в один ряд — там столбик выглядел бы разреженным, а кнопка во всю ширину
     нелепо длинной. Порог взят от контента: ряду нужно ~401px + паддинги. */
  @media (min-width: 440px) {
    .xf-mcall {
      flex-direction: row;
      align-items: center;
      justify-content: space-between;
      gap: 12px;
      padding: 8px 14px;
    }
    .xf-mcall__btn { flex: 0 0 auto; }
    /* --solo (главная): обещания в ряду нет, и кнопка «по контенту» повисла бы
       слева узким островком на пустой полосе — растягиваем на всю ширину, как
       в столбике на <440px. Паддинги те же, что у полной полосы (8/14): высота
       ряда задана кнопкой (44px; на низком экране 40 и поля 4, #821), зазор
       gap при одном ребёнке не срабатывает — пустого места от снятого текста
       не остаётся. */
    .xf-mcall--solo .xf-mcall__btn { flex: 1 1 auto; }
  }

  /* [#704] Карточка товара на узком телефоне: полоса в ОДИН ряд, 61 px вместо
     89 (на низком экране — 49, #821 ниже). Столбик выше забирал у первого экрана карточки 28 px, которых не
     хватало цене и кнопке «В корзину» (замер: 375×812, баннер cookie открыт).
     Ничего не убрано: обещание срока встаёт в две короткие строки слева,
     звонок занимает всё остальное — он по-прежнему крупнейшая цель полосы.
     Ширина обещания задана от кегля (8.4em ≈ «Доставка по РБ» + запас): строка
     ломается после «РБ», а не оставляет «дня» сиротой, и без text-wrap: balance.
     Только карточка: на каталоге и в корзине столбик остаётся (#301).
     Признак страницы — секция .xf-pdp в <main>, соседнем с полосой; где :has()
     нет, полоса остаётся столбиком.
     Нижняя граница 360 px — от контента: ряду нужно 351 px (обещание 125 +
     зазор 12 + кнопка 186 + поля 28). Уже номер упирался в край экрана и на
     320 px терял последнюю цифру — там остаётся столбик. */
  @media (min-width: 360px) and (max-width: 439px) {
    .xf-mcall:has(~ main .xf-pdp) {
      flex-direction: row;
      align-items: center;
      gap: 12px;
      padding: 8px 14px;
    }
    .xf-mcall:has(~ main .xf-pdp) .xf-mcall__promise {
      flex: 0 0 auto;
      max-width: calc(20px + 8.4em);   /* 20px = иконка 14 + зазор 6 */
    }
    .xf-mcall:has(~ main .xf-pdp) .xf-mcall__btn { flex: 1 1 auto; }
  }

  @media (prefers-reduced-motion: reduce) {
    /* Сдвиг кнопки гасит --xf-motion в самом правиле (#330), здесь остаётся
       только транзишен — токен его не трогает. */
    .xf-mcall__btn { transition: none; }
  }

  /* Main-row → 2 строки: [burger][logo][actions] + [search full-width] */
  .xf-header__row--main {
    height: auto;
    min-height: 56px;
    padding: 10px 14px;
    gap: 8px;
    flex-wrap: wrap;
    row-gap: 10px;
  }

  .xf-burger-btn { display: inline-flex; }

  /* Лого компактнее — 28×22 SVG + text 22px */
  .xf-logo {
    gap: 3px;
    margin-right: auto;
  }
  .xf-logo > svg { width: 28px; height: 22px; margin-right: -7px; }
  .xf-logo__dash { width: 8px; height: 3px; }
  .xf-logo__text {
    font-size: 22px;
    letter-spacing: -0.4px;
    transform: translateY(0);
  }

  /* Configurator pill + account pill + user-btn + divider — переезжают в drawer */
  .xf-configurator-pill { display: none; }
  .xf-header__actions .xf-account-pill,
  .xf-header__actions > .xf-iconbtn[href$="auth/"],
  .xf-header__actions > .xf-iconbtn[href$="personal/"] { display: none; }
  .xf-header__actions .xf-divider { display: none; }

  .xf-header__actions { gap: 2px; margin-left: 0; }

  /* Cart → compact icon-only (без label), с count'ом рядом */
  .xf-cart-btn {
    padding: 9px 11px;
    gap: 6px;
    font-size: 12px;
  }
  .xf-cart-btn > span:not(.xf-cart-btn__count) { display: none; }
  .xf-cart-btn__count {
    min-width: 18px;
    height: 18px;
    font-size: 10.5px;
    padding: 0 5px;
  }

  /* Search → full-width отдельный ряд под верхней линией */
  .xf-search {
    order: 10;
    flex: 1 0 100%;
    max-width: 100%;
  }
  /* [#830] 16 px, не 14.5: Safari на iPhone увеличивает страницу, когда фокус
     попадает в поле мельче 16 px, и страница остаётся увеличенной. Высоту
     держит height (44; на низком экране 40, #821 ниже), поэтому поля по
     вертикали — 0: текст браузер ставит по центру поля сам, и на 40 px
     строка 16 px не обрезается. */
  .xf-search input {
    font-size: 16px;
    padding: 0 14px 0 40px;
    height: 44px;
    box-sizing: border-box;
  }

  /* [#821] Низкий телефон: шапка и полоса звонка плотнее. На iPhone SE в Safari
     (375×553) и на 320×568 до фото карточки уходило 213–241 px, и длинное
     название (4–5 строк) пряталось под липкую полосу «цена + В корзину»: фото
     уже сжато до минимума (#722), место берём у шапки. Что ужимается: поля и
     зазор рядов шапки (10 → 4/6 px), поля полосы звонка (10/8 → 4 px), высота
     поиска и кнопки звонка (44 → 40 px — обе во всю ширину экрана, по высоте
     палец не промахивается; исключение записано в DESIGN.md, Navigation).
     Величина плавно зависит от высоты экрана: --xf-low = 0 на экранах ниже
     620 px, 60 — от 680 px (там всё как было). Через svh, а не @media
     (max-height): svh — высота экрана с показанными панелями браузера, она не
     меняется при прокрутке, и шапка не прыгает, когда Safari прячет панели
     (так же сделана высота фото карточки, product.css #704). Где svh нет —
     всё как было. Крошки карточки ужимаются тем же счётом в product.css. */
  @supports (height: 100svh) {
    .xf-header,
    .xf-mcall { --xf-low: clamp(0px, calc(100svh - 620px), 60px); }
    .xf-header__row--main {
      padding-top: calc(4px + var(--xf-low) / 10);
      padding-bottom: calc(4px + var(--xf-low) / 10);
      row-gap: calc(6px + var(--xf-low) / 15);
    }
    .xf-search input { height: calc(40px + var(--xf-low) / 15); }
    .xf-mcall {
      padding-top: calc(4px + var(--xf-low) / 10);
      padding-bottom: calc(4px + var(--xf-low) / 10);
    }
    .xf-mcall__btn { min-height: calc(40px + var(--xf-low) / 15); }
    /* Полоса в один ряд (от 440 px; на карточке — от 360, #704): поля 8 → 4. */
    @media (min-width: 440px) {
      .xf-mcall {
        padding-top: calc(4px + var(--xf-low) / 15);
        padding-bottom: calc(4px + var(--xf-low) / 15);
      }
    }
    @media (min-width: 360px) and (max-width: 439px) {
      .xf-mcall:has(~ main .xf-pdp) {
        padding-top: calc(4px + var(--xf-low) / 15);
        padding-bottom: calc(4px + var(--xf-low) / 15);
      }
    }
  }
  /* [#821] Карточка на самом узком экране (< 360 px): номер рядом с обещанием
     не помещается (#704), и полоса стояла столбиком — обещание отдельной
     строкой над кнопкой (+24 px). Здесь обещание убираем, кнопка звонка
     остаётся во всю ширину, как на главной (--solo, #313). Цена: на 320 px
     срока доставки на первом экране карточки нет — строка срока у карточки
     ниже «В корзину» (и только у товаров в наличии). Иначе последняя строка
     длинного названия уходила под полосу — название важнее (#722). */
  @media (max-width: 359px) {
    .xf-mcall:has(~ main .xf-pdp) .xf-mcall__promise { display: none; }
  }

  /* Drawer visible container (closed by default via translateX) */
  .xf-drawer {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 100;
    pointer-events: none;
  }
  /* override native [hidden] так, чтобы transition мог работать —
     visibility управляется classList + pointer-events ниже */
  .xf-drawer[hidden] { display: block; }

  .xf-drawer__overlay {
    position: absolute;
    inset: 0;
    background: oklch(0 0 0 / 0.5);
    opacity: 0;
    transition: opacity .25s;
    pointer-events: none;
  }
  .xf-drawer__panel {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    width: 85vw;
    max-width: 360px;
    background: var(--bg);
    box-shadow: var(--shadow-card);
    display: flex;
    flex-direction: column;
    transform: translateX(-105%);
    transition: transform .28s cubic-bezier(.4, 0, .2, 1);
    pointer-events: auto;
    will-change: transform;
  }
  .xf-drawer.is-open { pointer-events: auto; }
  .xf-drawer.is-open .xf-drawer__overlay { opacity: 1; pointer-events: auto; }
  .xf-drawer.is-open .xf-drawer__panel   { transform: translateX(0); }
  /* Панель спрятана самим transform (без opacity) — множитель показал бы её
     насовсем; по The Motion-Multiplier Rule таким — точечный transition: none. */
  @media (prefers-reduced-motion: reduce) {
    .xf-drawer__panel { transition: none; }
  }

  .xf-drawer__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 16px;
    border-bottom: 1px solid var(--line-soft);
    flex: 0 0 auto;
  }
  .xf-drawer__logo {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    color: var(--text);
    line-height: 1;
  }
  .xf-drawer__logo > svg { margin-right: -7px; }
  .xf-drawer__logo-dash {
    width: 8px;
    height: 3px;
    border-radius: 2px;
    background: currentColor;
    flex: 0 0 auto;
  }
  .xf-drawer__logo-text {
    font-size: 22px;
    font-weight: 700;
    letter-spacing: -0.4px;
  }
  .xf-drawer__close {
    width: 44px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    background: var(--surface-2);
    border: 0;
    border-radius: 10px;
    color: var(--text-2);
    cursor: pointer;
    transition: background .15s, color .15s;
  }
  @media (hover: hover) {
  .xf-drawer__close:hover { background: var(--surface-3); color: var(--text); }
}

  .xf-drawer__body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 16px 14px 24px;
    display: flex;
    flex-direction: column;
    gap: 18px;
  }
  /* Баннер cookie лежит поверх шторки (z-index 9999) и закрывал её низ — а
     там тумблер темы, единственный вход в тёмную (#707). Даём докрутить
     содержимое выше баннера; высоту меряет app.js (--xf-cookie-h). */
  body.xf-cookie-up .xf-drawer__body { padding-bottom: calc(24px + var(--xf-cookie-h, 120px)); }

  /* Primary CTA — Конфигуратор ПК (единственный в drawer'е) */
  .xf-drawer__cta {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border-radius: 12px;
    /* #280: лиловый градиент (accent → hue 320 orchid) снят по решению
       владельца. Плоский accent-dim — токен сам переключает тему, поэтому
       отдельное dark-правило для rest-состояния больше не нужно. */
    background: var(--accent-dim);
    border: 1px solid var(--accent-line);
    color: var(--text);
    font-size: 14px;
    font-weight: 500;
  }
  .xf-drawer__cta.is-active {
    background: oklch(0.55 0.23 var(--accent-h) / 0.22);
  }
  /* Тёмная: подложка акцента темы (#857; был литерал на индиго 268 светлой). */
  html[data-theme="dark"] .xf-drawer__cta.is-active {
    background: var(--accent-dim);
  }
  .xf-drawer__cta-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: 9px;
    background: var(--accent);
    color: #fff;
    box-shadow: 0 0 14px -3px oklch(0.68 0.22 var(--accent-h) / 0.60);
    flex: 0 0 auto;
  }
  /* Тёмная: иконка-плашка без ореола — свет только у того, что излучает
     (DESIGN.md, The Night-Sign Rule); ореол был ещё и индиго 268 (#857). */
  html[data-theme="dark"] .xf-drawer__cta-icon { box-shadow: none; }

  /* Group: title + stacked rows */
  .xf-drawer__group {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .xf-drawer__group-title {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    color: var(--text-3);
    font-family: var(--font-mono);
    padding: 0 4px 8px;
  }

  /* Nav / row — one-line clickable list items */
  .xf-drawer__nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .xf-drawer__nav-item,
  .xf-drawer__row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 12px;
    border-radius: 10px;
    color: var(--text);
    font-size: 14px;
    transition: background .12s, color .12s;
  }
  .xf-drawer__nav-item { color: var(--text-2); }
  @media (hover: hover) {
  .xf-drawer__nav-item:hover,
.xf-drawer__row:hover { background: var(--surface); color: var(--text); }
}

  .xf-drawer__nav-icon,
  .xf-drawer__row-icon {
    flex: 0 0 22px;
    width: 22px;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-3);
  }
  .xf-drawer__nav-label,
  .xf-drawer__row-label { flex: 1 1 auto; }
  .xf-drawer__nav-arrow {
    flex: 0 0 auto;
    color: var(--text-3);
  }

  .xf-drawer__nav-item--all {
    border-top: 1px solid var(--line-soft);
    margin-top: 4px;
    padding-top: 14px;
  }
  .xf-drawer__nav-item--all .xf-drawer__nav-icon { color: var(--accent-2); }
  .xf-drawer__nav-item--all .xf-drawer__nav-label {
    font-weight: 500;
    color: var(--text);
  }

  .xf-drawer__row-icon--brand {
    background: var(--accent);
    color: #fff;
    border-radius: 7px;
    box-shadow: 0 0 10px -2px oklch(0.68 0.22 var(--accent-h) / 0.55);
  }
  /* Тёмная: без ореола, как иконка «Конфигуратор ПК» выше (#857). */
  html[data-theme="dark"] .xf-drawer__row-icon--brand { box-shadow: none; }
  /* Телефон в меню — контакт, не data → Onest (см. #91). Суффикс «--mono»
     в имени класса исторический; переименование — отдельная уборка. */
  .xf-drawer__row-label--mono {
    font-family: var(--font-sans);
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    letter-spacing: -0.2px;
  }
  .xf-drawer__row-count {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 22px;
    height: 22px;
    padding: 0 7px;
    background: var(--accent);
    color: #fff;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    font-family: var(--font-mono);
  }
  .xf-drawer__row-count--wish { background: #e11d48; }

  /* [#646] Подпись под названием строки — срок у «Доставки» (раньше отдельной
     строкой-подсказкой .xf-drawer__info без ссылки). */
  .xf-drawer__row-sub {
    display: block;
    margin-top: 1px;
    color: var(--text-3);
    font-size: 12.5px;
  }

  /* Theme toggle row — pinned к нижней границе body (внутри scroll-блока) */
  .xf-drawer__theme {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 12px 0;
    border-top: 1px solid var(--line-soft);
    margin-top: 6px;
  }
  .xf-drawer__theme-label {
    font-size: 14px;
    color: var(--text-2);
  }
}

/* Тёмная тема: кнопка звонка и «Конфигуратор ПК» в шторке — та же тихая
   рамка, что у пилюли конфигуратора в шапке: без accent-окантовки и заливки.
   Вне медиа-блока, как и было в скине; 0-2-1 выше :active звонка (0-2-0).
   Активную ячейку шторки (.is-active, 0-3-1, выше в блоке) не перебивает. */
html[data-theme="dark"] .xf-mcall__btn,
html[data-theme="dark"] .xf-drawer__cta {
  border: 1px solid var(--line);
  background: transparent;
}

/* Body scroll-lock when drawer is open */
body.xf-drawer-open { overflow: hidden; }

/* End */


/* Start:/local/templates/xcore_front/css/footer.css?17926538796313*/
/* OWNS: .xf-footer-* */
.xf-footer {
  /* M11.2-fix: 80→32. С M11.2 нижняя секция (brands marquee) сама дала
     80px top breath — старые 80px footer-margin сверху давали суммарно
     ~128px gap. Снижаем до 32, .middle padding-bottom и section padding
     добивают суммарный gap до ~80px. */
  margin-top: 32px;
  border-top: 1px solid var(--line-soft);
  background: var(--bg-2);
}

.xf-footer__inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: 56px var(--container-pad) 32px;
}

.xf-footer__grid {
  display: grid;
  grid-template-columns: 1.4fr 1.2fr 1fr 1fr 1.4fr;
  gap: 40px;
}
@media (max-width: 1100px) {
  .xf-footer__grid {
    grid-template-columns: 1fr 1fr 1fr;
  }
  .xf-footer__about,
  .xf-footer__col--connect { grid-column: span 3; }
}
@media (max-width: 640px) {
  .xf-footer__grid { grid-template-columns: 1fr 1fr; }
  .xf-footer__about,
  .xf-footer__col--connect { grid-column: span 2; }
}

.xf-footer__about .xf-logo {
  gap: 4px;
}
.xf-footer__about .xf-logo > svg {
  margin-right: -8px;
}
.xf-footer__about .xf-logo__dash {
  width: 8px;
  height: 3.5px;
}
.xf-footer__about .xf-logo__text {
  font-size: 27px;
  transform: translateY(-1px);
}
.xf-footer__about p {
  color: var(--text-2);
  font-size: 13.5px;
  line-height: 1.6;
  margin: 16px 0 20px;
  max-width: 320px;
}

.xf-footer__col-title {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1.2px;
  color: var(--text-2);
  font-family: var(--font-mono);
  margin-bottom: 16px;
}

.xf-footer__col ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.xf-footer__col a {
  font-size: 13.5px;
  color: var(--text-2);
  transition: color .15s;
}
@media (hover: hover) {
  .xf-footer__col a:hover { color: var(--text); }
}

/* [#316] Нижняя плита подвала — Onest, не mono. Здесь нет данных: название
   юрлица, номер свидетельства в составе фразы, юридический адрес, «Политика
   обработки персональных данных», «Публичный договор», «Настройки cookie»,
   подпись студии — это текст, который читают предложениями. Mono в шаблоне
   значит «перед тобой данные» (цена, SKU, таймстамп) — DESIGN.md,
   The Mono-as-Data Rule; набирая им нарратив, мы стирали этот сигнал в самом
   длинном куске копи на странице.
   Наверху колонок .xf-footer__col-title остаётся mono намеренно: это
   eyebrow-метка (11–12px, uppercase, трекинг), у правила явное исключение
   на single-tone метки.
   .xf-footer__contacts (телефон/почта) уже на Onest по #91 — контакт-
   аффорданс, а не данные; здесь ничего не меняется. */
.xf-footer__bottom {
  margin-top: 48px;
  padding-top: 24px;
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  gap: 14px;
  font-size: 12px;
  color: var(--text-2);
}
.xf-footer__legal {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  line-height: 1.5;
}
.xf-footer__bottom-links {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
}
.xf-footer__bottom a {
  color: var(--text-2);
  transition: color .15s;
}
@media (hover: hover) {
  .xf-footer__bottom a:hover { color: var(--text); }
}
/* Подпись студии: прижата вправо в нижнем ряду (auto-margin), при wrap уходит
   в поток. Hover-акцент на имени — единственный индиго футера (One-Voice). */
.xf-footer__credit {
  margin-left: auto;
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  white-space: nowrap;
}
.xf-footer__credit-name {
  transition: color .15s;
}
.xf-footer__credit-arrow {
  display: inline-block;
  transition: transform .15s;
}
@media (hover: hover) {
  .xf-footer__credit:hover .xf-footer__credit-name { color: var(--accent); }
  /* Тёмная (#858): имя — фиолетом текста; --accent на фоне подвала — 4.0:1. */
  html[data-theme="dark"] .xf-footer__credit:hover .xf-footer__credit-name { color: var(--accent-2); }
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .xf-footer__credit:hover .xf-footer__credit-arrow { transform: translate(1px, -1px); }
}
/* Клиренс от чат-кружка (.xf-chat: fixed, right 24 + 56px круг → занимает
   24–80px от правого края вьюпорта). Контейнер 1440 + пад 40 → до ~1520px
   вьюпорта правый край контента попадает под кружок; шире — центрирование
   само уводит контент из зоны. 52px = (80+12 зазор) − 40 пада. */
@media (min-width: 641px) and (max-width: 1559px) {
  .xf-footer__credit { margin-right: 52px; }
}
@media (max-width: 640px) {
  .xf-footer__credit { margin-left: 0; }
}

/* Кнопка отзыва согласия cookie — визуально неотличима от соседних ссылок-<a>. */
.xf-footer__cookie-settings {
  appearance: none;
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  color: var(--text-2);
  cursor: pointer;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-footer__cookie-settings:hover { color: var(--text); }
}

/* Селектор с .xf-footer__col чтобы перебить базовое .xf-footer__col ul. */
.xf-footer__col .xf-footer__contacts {
  margin: 14px 0 0;
  gap: 8px;
}
.xf-footer__contacts a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-2);
  /* Телефон/почта — контакт-действие, не data → Onest, не mono (см. #91). */
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-footer__contacts a:hover { color: var(--text); }
}

/* End */


/* Start:/local/templates/xcore_front/css/home.css?1792645227182604*/
/* OWNS: .xf-pitch* (вывеска первого экрана), в тёмной теме — с плитками фактов .xf-neon-* (include/home/pitch.php) и знаком X .xf-nx* + html[data-xf-nx] (живая вывеска #758: слои include/neon/x-core.php, погасшее состояние до первой отрисовки ставит FOUC-guard в header.php, поведение — js/neon-core.js), .xf-hero-*, .xf-trustbar-*, .xf-categrid-*, .xf-cat-*, .xf-catrail(__*) (полка разделов главной, #361 — рендер include/home/catgrid.php), .xf-day-* (Товар дня — карточка + свои .xf-day-price*), .xf-promo-*, .xf-strip-*, .xf-art-* (tonal placeholder), .xf-brand(-)-*, .xf-brands-*, .xf-home-*, .xf-product-card* (листинговая карточка товара целиком — единственное исключение из .xf-product-* у product.css; рендерится из xf_render_product_card, живёт на главной/каталоге/поиске/избранном) */
/* Home page blocks — Pitch, Hero, TrustBar, CategoryGrid, ProductStrip, SplitPromo, Brands */

/* M11.2: Asymmetric section spacing — page rhythm break.
   Pairs:  hero+trust (tight)  →  cats+products (medium)  →  promo+brands (wider).
   Page как будто «дышит глубже» от верха к низу. Раньше все sections были
   одинаковые 48+24=72px → 5 grid-секций подряд читались как «генерация». */
.xf-home__section          { padding: 48px 0 24px; }
.xf-home__section--hero    { padding: 28px 0 20px; }    /* tight after header */
.xf-home__section--trust   { padding: 24px 0; }         /* tight, group with hero */
/* --cats больше нет: секция «Популярные категории» заменена полкой разделов
   .xf-catrail со своим ритмом (#361), она не .xf-home__section. */
.xf-home__section--day     { padding: 24px 0; }         /* tight — single card между полкой разделов и products */
.xf-home__section--products{ padding: 48px 0 24px; }    /* medium */
.xf-home__section--promo   { padding: 72px 0 32px; }    /* wider — visual break */
.xf-home__section--brands  { padding: 80px 0 16px; }    /* big top breath, плотно к footer'у */

/* ─── Вывеска (первый экран, #300) ──────────────────────────
   Разметка — include/home/pitch.php. Стоит выше баннеров и говорит словами,
   что это за магазин. Всё под собственным префиксом .xf-pitch*; из чужого
   каскада трогается ровно одно — геометрия .xf-btn (ui.css) на своём
   экземпляре .xf-pitch__cta (grid-область, margin, на мобильном ширина и
   padding под тач-таргет). */

/* Плита стоит прямо на фоне страницы, без карточки и без заливки: Flat-Surface
   Rule — блок выделяется типографикой и линейками, а не подсветкой. Нижняя
   линейка = «край верстака»: выше говорит магазин, ниже начинается витрина. */
.xf-pitch {
  padding: 34px 0 26px;
  border-bottom: 1px solid var(--line-soft);
}

/* Композиция: вывеска + колофон. Слева имя товара крупно и кнопка под ним,
   справа за вертикальной линейкой — справка о магазине. Это грамматика газетной
   шапки, а не лендинг-hero (eyebrow → title → subtitle → ряд кнопок).
   Порядок в DOM (заголовок → справка → кнопка) сразу верный для мобильного;
   на десктопе кнопка встаёт под заголовок через grid-области. */
.xf-pitch__band {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(258px, 340px);
  grid-template-areas:
    "title note"
    "facts note"
    "cta   note";
  grid-template-rows: auto auto 1fr;
  column-gap: 56px;
  align-items: start;
}

/* Display главной. Кегль канонизирован владельцем (#156): 52px на десктопе,
   ~40px на мобильном. Своих значений тут не изобретаем — менять масштаб решает
   владелец, не агент. Tracking в em (-0.035em ≈ -1.8px на 52px), чтобы на
   мобильном кегле не переуплотнить. max-width в ch, а не px — мера едет вместе
   с кеглем; 25ch держит заголовок в двух строках на десктопе. */
.xf-pitch__title {
  grid-area: title;
  margin: 0;
  max-width: 25ch;
  font-size: 52px;
  line-height: 1.02;
  font-weight: 700;
  letter-spacing: -0.035em;
  color: var(--text);
  text-wrap: balance;
}
/* Тон ниже, не цвет: ink-secondary на display-кегле — 7:1 с запасом, при этом
   фраза распадается на «что продаём» и «откуда». Accent-индиго сюда не идёт —
   его бюджет на экране занят единственной кнопкой (One-Voice Indigo Rule).
   display:block, а не inline — смена тона обязана совпасть с переносом строки.
   Инлайном на узкой колонке она приходилась на середину строки («…и техника СО»)
   и читалась как случайность; блоком это осознанный второй регистр на всех
   ширинах, без зависимости от того, где сработал text-wrap. */
.xf-pitch__title-soft {
  display: block;
  color: var(--text-2);
}

/* Строка факта (#313): срок доставки — под заголовком, над кнопкой.
   Это само предложение магазина (PRODUCT.md → Positioning), поэтому голос —
   основной ink и 500, а не тон справки: справа колофон рассказывает, здесь —
   обещаем. Иконка — в тоне --text-3, как у топбара: маркер, а не акцент.
   Индиго сюда не идёт — бюджет экрана у единственной кнопки (One-Voice Indigo
   Rule). Кегль 15 — body-ступень DESIGN.md, тот же, что у справки: строка не
   должна спорить с display.
   Факт один: «Самовывоз в Минске, Пн–Сб» снят владельцем 2026-09-12 (по
   субботам не всегда — pitch.php). Поэтому контейнер — обычный абзац без
   flex/gap/list-reset: раскладку ряда из двух фактов держать не для чего, а
   разделитель между фактами теперь был бы огрызком. Вернутся факты — вернётся
   и flex с column-gap 24px. */
.xf-pitch__facts {
  grid-area: facts;
  margin: 18px 0 0;
}
.xf-pitch__fact {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  line-height: 1.4;
  font-weight: 500;
  color: var(--text);
}
.xf-pitch__fact svg {
  flex: 0 0 auto;
  color: var(--text-3);
}

/* Колофон. padding-top подобран так, чтобы первая строка справки оптически
   села на первую строку заголовка (52×1.02 против 15×1.6), а не совпала с ним
   по верхнему краю коробки. Линейка — 1px нейтраль, полная высота полосы:
   это разделитель колонок, а не цветная полоса-акцент (DESIGN.md → Don't:
   coloured border-left >1px). */
.xf-pitch__note {
  grid-area: note;
  position: relative;
  align-self: stretch;
  padding: 21px 0 2px 32px;
}
.xf-pitch__note::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6px;
  bottom: 6px;
  width: 1px;
  background: var(--line-soft);
}
.xf-pitch__note-p {
  margin: 0;
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-2);
  text-wrap: pretty; /* узкая колонка легко оставляет «дома.» сиротой в строке */
}
.xf-pitch__note-p + .xf-pitch__note-p {
  margin-top: 14px;
}
/* Ссылка «Отзывы о нас» в справке — тоном текста с подчёркиванием, не индиго:
   единственное индиго экрана — кнопка каталога (см. .xf-pitch__cta). */
.xf-pitch__link {
  color: var(--text);
  text-decoration: underline;
  text-decoration-color: var(--line);
  text-underline-offset: 3px;
  white-space: nowrap; /* «Все / отзывы о нас» рвалось на телефоне */
  transition: color .15s, text-decoration-color .15s;
}
@media (hover: hover) {
  .xf-pitch__link:hover {
    color: var(--accent-2);
    text-decoration-color: currentColor;
  }
}
/* Число — данные, поэтому mono (Mono-as-Data Rule) и тоном основного текста:
   единственное, что в справке должно цепляться глазом. word-spacing правит
   разряды: неразрывный пробел в моноширинном шрифте занимает целую ячейку, и
   «100 000» разваливалось на два числа — сжимаем до тонкой шпации. */
.xf-pitch__num {
  font-family: var(--font-mono);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  word-spacing: -0.22em;
  color: var(--text);
}

/* Единственная кнопка экрана и единственное индиго на нём — плоская primary
   (.xf-btn), без --lg. Вторая кнопка не нужна: каталог открывают шапка, плитки
   категорий сразу ниже и «Весь каталог» в их заголовке.
   (Здесь стояла оговорка «без --grad» — с #322 бренд-градиента в шаблоне нет
   вообще, выбирать больше не из чего.) */
.xf-pitch__cta {
  grid-area: cta;
  justify-self: start;
  margin-top: 26px; /* от строки фактов: она уже отступила от заголовка на 18 */
}

/* Планшет и узкий десктоп: колонки складываются в одну, вертикальная линейка
   становится горизонтальной над справкой — тот же приём разделения, повёрнутый
   на 90°. Кегль заголовка НЕ трогаем: на 768px колонка ~688px, и 52px
   укладываются в те же две строки — промежуточная ступень между 52 и 40 была бы
   кеглем вне рампы DESIGN.md. */
@media (max-width: 1023px) {
  .xf-pitch { padding: 30px 0 24px; }

  .xf-pitch__band {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas:
      "title"
      "facts"
      "note"
      "cta";
    grid-template-rows: auto auto auto auto;
  }

  .xf-pitch__note {
    max-width: 62ch;
    padding: 20px 0 0;
  }
  .xf-pitch__note::before {
    left: 0;
    right: 0;
    top: 0;
    bottom: auto;
    width: auto;
    height: 1px;
  }

  .xf-pitch__cta { margin-top: 22px; }
}

/* Mobile: .wrapper_inner держит var(--container-pad) по бокам — с #307 это 16px
   на телефоне, колонка 358px на 390 (было 40px и 310px).
   Заголовок отпускаем на всю колонку; кнопка — во всю ширину и с высотой
   ≥44px: это единственный тач-таргет блока и главный сценарий покупки
   (PRODUCT.md → Operating Context: основная масса смотрит с телефона). */
@media (max-width: 767px) {
  .xf-pitch { padding: 24px 0 22px; }

  /* 40px — канон мобильного display (#156, DESIGN.md §3: «на мобильном
     масштабируется к ~40px»). В машинной рампе DESIGN.md эта ступень не
     перечислена, поэтому детектор помечает её advisory — это осознанное
     исключение, а не самодеятельный кегль. Ниже 364px кегль плавает до 34
     (clamp — разбор в следующем комментарии).
     Две ступени на телефоне (#313, замечание владельца 2026-09-12 «переносы
     можно грамотнее»). Геометрия: в колонке 333px (375-вьюпорт) при 40px
     «Комплектующие» одно занимает 311px, «свой склад в Минске» — 391px.
     Значит, при едином кегле выбор только между «40px и четыре строки с
     повисшим „в Минске“» и «ужать всё до 33px, чтобы вторая фраза влезла в
     строку» (так было в #502) — во втором случае display падал ниже своего
     мобильного канона, а «и техника» оставалось короткой строкой, зажатой
     между двумя длинными (по правилу рваного края — худшая форма).
     Решение — не один кегль на обе фразы, а две роли рампы DESIGN.md:
       • «Комплектующие / и техника» — Display, 40px (канон #156), две строки
         «длинная + короткая» — естественный рваный край;
       • «свой склад в Минске» — Headline в мобильном масштабе (38px × 40/52
         ≈ 29 → 28px), 600, трекинг -0.03em (Tight-Display: чем мельче, тем
         свободнее), одной строкой; тон --text-2 остаётся из базового правила.
     Ступени 40 → 28 → 22 (заголовки секций на телефоне) читаются как три
     разных роли; 33 ↔ 26 (промо-карточка) такого зазора не давали.
     margin-top 8px (spacing.sm) — зазор между ролями заметно больше межстрочного
     внутри display (≈1px) и заметно меньше отступа до строки фактов (16px):
     подзаголовок держится за заголовок, а не за факты.
     clamp: на 320px колонка 288px — 40px не влезает («Комплектующие» 311),
     35px влезает (274). 11vw = 35.2px на 320, 39.6 на 360 (Android), 40px с
     ~364px; на 375+ это ровно 40px — канон, а не текучий кегль. Не 36px: с
     классическим скроллбаром (headless, Windows) колонка на 320 сужается до
     278, и 36px (282) вылезали на 4px — замерено Playwright 2026-09-12.
     Десктоп и планшет (≥768px) не трогаем: там 52px и обе фразы по строке,
     смена тона совпадает с переносом — иерархия ролей не нужна.
     Обкатано на /dev/ как модификатор --deck под d1-гейтом; по приёмке
     владельца (#313) гейт снят и модификатор слит в базовое правило. */
  .xf-pitch__title { max-width: none; font-size: clamp(34px, 11vw, 40px); }
  .xf-pitch__title .xf-pitch__title-soft {
    margin-top: 8px;
    font-size: 28px;
    font-weight: 600;
    line-height: 1.1;
    letter-spacing: -0.03em;
  }

  .xf-pitch__facts { margin-top: 16px; }

  .xf-pitch__note { padding-top: 18px; }
  .xf-pitch__note-p + .xf-pitch__note-p { margin-top: 12px; }

  .xf-pitch__cta {
    justify-self: stretch;
    width: 100%;
    padding: 13px 16px;
  }
}

/* ─── Вывеска в тёмной теме («Ночная вывеска», #739) ────────
   В тёмной первый экран — вывеска: тот же заголовок, плитки фактов вместо
   справки и строки срока, справа знак X с векторной платой. Единственное
   место сайта, где горит свечение (--nx-*, tokens.css) и светится фон, —
   отступление от Flat-Surface и Gradient-Restraint, записанное правилом
   тёмной темы (DESIGN.md → Dark Theme). Плитки и знак печатаются всем с
   атрибутом hidden (include/home/pitch.php): в светлой их нет, правила ниже
   их показывают (display сильнее hidden). До #708 блок жил в css/neon.css.

   Раскладка референса: заголовок → плитки фактов → кнопка, справа знак.
   Шрифт заголовка — наш обычный (владелец, #618); вес 600 вместо 700:
   белое на чёрном «расплывается» и выглядит жирнее, чем на белом.
   Секция изолирована: свет знака (z-index -1) ложится ЗА текст, а не
   поверх. Фильтр на обёртке знака сделал бы её отдельным слоем, и свет
   окрасил бы заголовок, — поэтому тень-свет висит на самом SVG. */
html[data-theme="dark"] .xf-pitch {
  position: relative;
  isolation: isolate;
  border-bottom: 0;
  padding-top: 56px;
  padding-bottom: 64px;
}
html[data-theme="dark"] .xf-pitch__facts,
html[data-theme="dark"] .xf-pitch__note { display: none; }
html[data-theme="dark"] .xf-pitch__band {
  grid-template-columns: minmax(0, 1fr) minmax(300px, 460px);
  grid-template-areas:
    "title art"
    "pills art"
    "cta   art";
  grid-template-rows: auto auto 1fr;
  column-gap: 56px;
  align-items: start;
}
html[data-theme="dark"] .xf-pitch__title { font-weight: 600; letter-spacing: -0.03em; }
/* Вторая строка — светящаяся надпись вывески: светлая сердцевина и цветной
   ореол, как трубка знака. Не градиент-текст и не плоская лаванда. */
html[data-theme="dark"] .xf-pitch__title-soft {
  color: var(--nx-core);
  text-shadow: var(--nx-glow);
}
html[data-theme="dark"] .xf-neon-art {
  display: block;
  position: relative;
  grid-area: art;
  align-self: center;
}
/* Свет вывески: центр — на знаке, на любом экране; спад многоступенчатый,
   чтобы край растворялся, а не обрывался. */
html[data-theme="dark"] .xf-neon-art::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 50%;
  width: 420%;
  aspect-ratio: 1.5;
  transform: translate(-50%, -50%);
  pointer-events: none;
  background: radial-gradient(closest-side, oklch(0.52 0.23 325 / 0.5), oklch(0.43 0.21 315 / 0.25) 30%, oklch(0.32 0.14 302 / 0.12) 60%, transparent 100%);
}

/* ── Живая вывеска (#758): знак X включается и живёт ──────────────
   Сюжет — имя магазина, X-Core: ядро в центре, от него по дорожкам платы к
   трубке бегут импульсы. Знак собран из слоёв (include/neon/x-core.php):
   ореол, плата, импульсы, свечение трубки, вспышка, белая трубка, искры.
   Каждый слой — свой элемент: прозрачность и сдвиг ведёт композитор,
   импульсы перерисовывают только свой лёгкий слой, слои с размытием не
   перерисовываются никогда. Поведение — js/neon-core.js; без скрипта знак
   просто горит. За курсором знак не двигается: глубина (трубка перед стеклом,
   поворот) ездила неестественно (Илья 2026-10-19) — курсору отвечает свет:
   импульсы к нему и разгорающееся ядро. */
html[data-theme="dark"] .xf-pitch {
  /* Погасшая вывеска (на секции: строка, цифры и кнопка — соседи знака, не
     потомки): тень текста той же формы, что --nx-glow, но пустая — так её
     можно плавно зажечь, — и кнопка без света под собой. */
  --nx-glow-off: 0 0 10px transparent, 0 0 30px transparent;
  --nx-cta-dark:
    inset 0 1px 0 oklch(1 0 0 / 0.28),
    inset 0 -1px 0 oklch(0 0 0 / 0.18),
    0 1px 0 oklch(0 0 0 / 0.4),
    0 16px 36px -14px oklch(0.56 0.25 300 / 0);
}
html[data-theme="dark"] .xf-nx {
  position: relative;
  display: block;
}
html[data-theme="dark"] .xf-nx__layer {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
/* Ореол в потоке задаёт размер всей сцене. */
html[data-theme="dark"] .xf-nx__layer--aura { position: relative; height: auto; }
html[data-theme="dark"] .xf-nx__layer--flare { opacity: 0; }
html[data-theme="dark"] .xf-nx__sparks {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* Свет ядра: разгорается, когда курсор рядом, и вспыхивает на каждом импульсе. */
html[data-theme="dark"] :is(.xf-nx__core, .xf-nx__flash) {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 15%;
  aspect-ratio: 1;
  translate: -50% -50%;
  border-radius: 50%;
  background: radial-gradient(closest-side, oklch(0.99 0.02 338 / 0.95), oklch(0.80 0.17 338 / 0.42) 42%, oklch(0.62 0.25 332 / 0.12) 72%, transparent);
  opacity: 0;
}
/* Голова импульса и вспышка в конце дорожки: место и размер ставит скрипт. */
html[data-theme="dark"] :is(.xf-nx__spark, .xf-nx__hit) {
  position: absolute;
  left: 0;
  top: 0;
  border-radius: 50%;
  opacity: 0;
  will-change: transform, opacity;
}
html[data-theme="dark"] .xf-nx__spark {
  background: radial-gradient(closest-side, oklch(1 0 0), oklch(0.92 0.08 338 / 0.9) 18%, oklch(0.74 0.22 336 / 0.4) 44%, oklch(0.62 0.26 332 / 0.12) 70%, transparent);
}
html[data-theme="dark"] .xf-nx__hit {
  background: radial-gradient(closest-side, oklch(0.98 0.03 338 / 0.9), oklch(0.74 0.22 336 / 0.35) 45%, transparent);
}

/* Волна по щелчку или касанию: доходит до трубки — трубка вспыхивает. */
html[data-theme="dark"] .xf-pitch.is-flare .xf-nx__layer--flare {
  animation: xf-nx-flare 900ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
}
@keyframes xf-nx-flare {
  0%, 30% { opacity: 0; }
  42% { opacity: 0.55; }
  100% { opacity: 0; }
}

/* Включение. Первый показ главной за визит и переключение на тёмную:
   вывеска стоит погасшей (data-xf-nx="off" ставит FOUC-guard в header.php до
   первой отрисовки) и зажигается — искра в ядре → ток по дорожкам → трубка
   «схватывает» → разгорается свет → загораются строка вывески, цифры и свет
   под кнопкой. Выдержки — кадрами внутри анимаций слоёв, не animation-delay:
   задержку Safari ведёт на главном потоке, и занятый загрузкой поток сорвал
   бы такт. Погасший вид — с атрибутом [data-xf-nx]: правила темы для строки,
   цифр и кнопки той же силы, без него он бы им проигрывал. При
   reduced-motion погасшего вида нет — вывеска горит сразу. */
@media (prefers-reduced-motion: no-preference) {
  html[data-theme="dark"][data-xf-nx] :is(.xf-nx__layer--aura, .xf-nx__layer--glow) { opacity: 0; }
  html[data-theme="dark"][data-xf-nx] .xf-nx__layer--board { opacity: 0.22; }
  html[data-theme="dark"][data-xf-nx] .xf-nx__layer--tube { opacity: 0.24; }
  html[data-theme="dark"][data-xf-nx] .xf-neon-art::before { opacity: 0; }
  html[data-theme="dark"][data-xf-nx] :is(.xf-pitch__title-soft, .xf-neon-pill b) {
    color: var(--text-2);
    text-shadow: var(--nx-glow-off);
  }
  html[data-theme="dark"][data-xf-nx] .xf-btn.xf-pitch__cta { box-shadow: var(--nx-cta-dark); }
  /* Страховка: скрипт не пришёл — вывеска загорается сама через 3,5 с.
     Успевший скрипт ставит "wait" (погасшая, без страховки) и правит сам;
     опоздавший ждёт, пока страховка доиграет (js/neon-core.js). */
  html[data-theme="dark"][data-xf-nx="off"] :is(.xf-nx__layer--aura, .xf-nx__layer--glow, .xf-nx__layer--board, .xf-nx__layer--tube),
  html[data-theme="dark"][data-xf-nx="off"] .xf-neon-art::before {
    animation: xf-nx-failsafe 600ms 3.5s both;
  }
  html[data-theme="dark"][data-xf-nx="off"] :is(.xf-pitch__title-soft, .xf-neon-pill b) { animation: xf-nx-failsafe-text 600ms 3.5s both; }
  html[data-theme="dark"][data-xf-nx="off"] .xf-btn.xf-pitch__cta { animation: xf-nx-cta-on 600ms 3.5s both; }

  html[data-theme="dark"] .xf-pitch.is-igniting .xf-nx__layer--board { animation: xf-nx-board-on 1800ms cubic-bezier(0.2, 0.7, 0.2, 1) both; }
  html[data-theme="dark"] .xf-pitch.is-igniting .xf-nx__layer--tube { animation: xf-nx-tube-on 1800ms ease-out both; }
  html[data-theme="dark"] .xf-pitch.is-igniting .xf-nx__layer--glow { animation: xf-nx-glow-on 1800ms ease-out both; }
  html[data-theme="dark"] .xf-pitch.is-igniting .xf-nx__layer--aura { animation: xf-nx-aura-on 1800ms cubic-bezier(0.16, 1, 0.3, 1) both; }
  html[data-theme="dark"] .xf-pitch.is-igniting .xf-neon-art::before { animation: xf-nx-room-on 1800ms cubic-bezier(0.16, 1, 0.3, 1) both; }
  /* Текст и тень кнопки — не композитор, а перерисовка: здесь обычная
     задержка допустима. */
  html[data-theme="dark"] .xf-pitch.is-igniting .xf-pitch__title-soft { animation: xf-nx-text-catch 520ms 930ms both; }
  html[data-theme="dark"] .xf-pitch.is-igniting .xf-neon-pill b { animation: xf-nx-text-on 520ms 1130ms both; }
  html[data-theme="dark"] .xf-pitch.is-igniting .xf-neon-pill:nth-child(2) b { animation-delay: 1210ms; }
  html[data-theme="dark"] .xf-pitch.is-igniting .xf-neon-pill:nth-child(3) b { animation-delay: 1290ms; }
  html[data-theme="dark"] .xf-pitch.is-igniting .xf-btn.xf-pitch__cta { animation: xf-nx-cta-on 700ms 1250ms cubic-bezier(0.16, 1, 0.3, 1) both; }
}
@keyframes xf-nx-failsafe { to { opacity: 1; } }
@keyframes xf-nx-failsafe-text { to { color: var(--nx-core); text-shadow: var(--nx-glow); } }
@keyframes xf-nx-board-on {
  0%, 7% { opacity: 0.22; }
  10% { opacity: 0.5; }
  44%, 100% { opacity: 1; }
}
/* «Схватывание» трубки: вспышка, провал, почти полный, провал, ровный свет —
   два провала за четверть секунды, не стробоскоп. */
@keyframes xf-nx-tube-on {
  0%, 40% { opacity: 0.24; }
  41.2% { opacity: 1; }
  43.2% { opacity: 0.28; }
  45.5% { opacity: 0.92; }
  48% { opacity: 0.45; }
  52%, 100% { opacity: 1; }
}
@keyframes xf-nx-glow-on {
  0%, 40.4% { opacity: 0; }
  41.8% { opacity: 0.85; }
  43.8% { opacity: 0.08; }
  46.2% { opacity: 0.75; }
  48.6% { opacity: 0.25; }
  56%, 100% { opacity: 1; }
}
@keyframes xf-nx-aura-on {
  0%, 41% { opacity: 0; }
  78%, 100% { opacity: 1; }
}
@keyframes xf-nx-room-on {
  0%, 42% { opacity: 0; }
  88%, 100% { opacity: 1; }
}
@keyframes xf-nx-text-catch {
  0% { color: var(--text-2); text-shadow: var(--nx-glow-off); }
  14% { color: var(--nx-core); text-shadow: var(--nx-glow); }
  30% { color: var(--text-2); text-shadow: var(--nx-glow-off); }
  52%, 100% { color: var(--nx-core); text-shadow: var(--nx-glow); }
}
@keyframes xf-nx-text-on {
  0% { color: var(--text-2); text-shadow: var(--nx-glow-off); }
  100% { color: var(--nx-core); text-shadow: var(--nx-glow); }
}
@keyframes xf-nx-cta-on { from { box-shadow: var(--nx-cta-dark); } }

/* Факты: три равные плитки «значение → подпись». Поверхность нейтральная,
   с кромкой; светится только значение — как цифры табло. Сетка 3 в ряд:
   прежние 2+1 растягивали третью плитку на всю ширину. */
html[data-theme="dark"] .xf-neon-pills {
  display: grid;
  grid-area: pills;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  margin: 36px 0 0;
  padding: 0;
  list-style: none;
  max-width: 660px;
}
html[data-theme="dark"] .xf-neon-pill {
  position: relative;
  display: flex;
  align-items: flex-start;
  padding: 16px 18px 17px;
  border: 1px solid oklch(1 0 0 / 0.10);
  border-radius: 14px;
  background: oklch(0.19 0.010 292 / 0.72);
  box-shadow: var(--nx-edge), 0 14px 30px -18px oklch(0 0 0 / 0.9);
  color: var(--text-2);
  font-size: 14.5px;
  line-height: 1.4;
  text-wrap: pretty;
  transition: border-color .18s, background-color .18s;
}
html[data-theme="dark"] .xf-neon-pill > :is(span, a) { display: block; }
html[data-theme="dark"] .xf-neon-pill b {
  display: block;
  margin-bottom: 4px;
  color: var(--nx-core);
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.1;
  white-space: nowrap;
  text-shadow: var(--nx-glow);
}
html[data-theme="dark"] .xf-neon-pill__label { display: block; }
/* Плитка с отзывами — ссылка на всю площадь; подпись подчёркнута, чтобы
   было видно, что это ссылка (обход 2026-10-05). */
html[data-theme="dark"] .xf-neon-pill a { color: inherit; text-decoration: none; }
html[data-theme="dark"] .xf-neon-pill a::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
html[data-theme="dark"] .xf-neon-pill a .xf-neon-pill__label {
  text-decoration: underline;
  text-decoration-color: oklch(1 0 0 / 0.4);
  text-underline-offset: 3px;
}
@media (hover: hover) {
  html[data-theme="dark"] .xf-neon-pill:has(a):hover {
    background: oklch(0.22 0.012 292 / 0.8);
    border-color: oklch(1 0 0 / 0.18);
  }
}

/* Главная кнопка главной — единственная со светом под собой: блик сверху,
   тень снизу, свет падает вниз «на стол». Ни градиента, ни ореола вокруг. */
html[data-theme="dark"] .xf-btn.xf-pitch__cta {
  grid-area: cta;
  margin-top: 32px;
  min-height: 52px;
  padding: 14px 30px;
  font-size: 15px;
  font-weight: 600;
  box-shadow:
    inset 0 1px 0 oklch(1 0 0 / 0.28),
    inset 0 -1px 0 oklch(0 0 0 / 0.18),
    0 1px 0 oklch(0 0 0 / 0.4),
    0 16px 36px -14px oklch(0.56 0.25 300 / 0.85);
}

/* Узкий десктоп и планшет в ландшафте (1024–1279): колонка знака на 460px
   оставляла фактам по ~135px, и «≈ 100 000» вылезало за край плитки. */
@media (min-width: 1024px) and (max-width: 1279px) {
  html[data-theme="dark"] .xf-pitch__band {
    grid-template-columns: minmax(0, 1fr) minmax(240px, 36%);
    column-gap: 40px;
  }
  html[data-theme="dark"] .xf-neon-pill { padding: 14px 14px 15px; }
  html[data-theme="dark"] .xf-neon-pill b { font-size: 24px; }
}

@media (max-width: 1023px) {
  html[data-theme="dark"] .xf-pitch__band {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "art" "title" "pills" "cta";
    grid-template-rows: none;
  }
  html[data-theme="dark"] .xf-neon-art {
    width: min(48%, 340px);
    margin: 0 0 20px -2%;
  }
}

/* Телефон — симметричная вывеска (Илья 2026-10-15, вариант «Г»): знак по
   центру, под ним заголовок по центру, факты — одна строка-табло из трёх
   колонок, кнопка. Прежний первый экран целиком уходил под вывеску —
   разделы каталога начинались ниже 1000px; так баннер возвращается на
   первый экран, а знак остаётся целым. Размер заголовка — канон владельца
   (#156), не трогаем. */
@media (max-width: 767px) {
  html[data-theme="dark"] .xf-pitch { padding-top: 20px; padding-bottom: 32px; }
  html[data-theme="dark"] .xf-pitch__band { text-align: center; justify-items: center; }
  html[data-theme="dark"] .xf-pitch__title { text-wrap: balance; }
  html[data-theme="dark"] .xf-neon-art { width: 34%; margin: 0 auto 14px; }
  html[data-theme="dark"] .xf-neon-art::before { width: 520%; }
  html[data-theme="dark"] .xf-neon-pills {
    width: 100%;
    gap: 0;
    margin-top: 20px;
    border: 1px solid oklch(1 0 0 / 0.10);
    border-radius: 14px;
    background: oklch(0.19 0.010 292 / 0.72);
    box-shadow: var(--nx-edge);
    overflow: hidden;
  }
  html[data-theme="dark"] .xf-neon-pill {
    justify-content: center;
    padding: 12px 6px 13px;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
    font-size: 12px;
    line-height: 1.3;
  }
  html[data-theme="dark"] .xf-neon-pill + .xf-neon-pill { border-left: 1px solid oklch(1 0 0 / 0.08); }
  html[data-theme="dark"] .xf-neon-pill b { font-size: 20px; margin-bottom: 3px; }
  html[data-theme="dark"] .xf-btn.xf-pitch__cta { width: 100%; margin-top: 18px; }
}
/* 320px: «≈ 100 000» на 20px упиралось в край колонки. */
@media (max-width: 359px) {
  html[data-theme="dark"] .xf-neon-pill b { font-size: 17px; }
}

/* ─── Hero ────────────────────────────────────────────────── */
/* M1: единый нейтральный фон под продуктовый баннер. Раньше было 3 radial-
   gradient'а с hue-swap по слайдам — сняли как «AI-pastel-reflex» (PRODUCT.md
   anti-ref #4 "Generic SaaS gradient template" + detector convergence). */
/* M9.1: высота приведена к prod-паттерну (1920×450 → ratio 4.27, prod hero
   ровно 450px tall при 1440 viewport). Dev wrapped в .wrapper_inner ~1350px,
   aspect-ratio один сам drive height по ширине контейнера (~316px). max-height
   capping для ультра-широких экранов (1920+ → height 450, как на проде).
   ВАЖНО: НЕТ min-height — иначе rectangle получает min-width = min-height ×
   ratio (360 × 4.27 = 1536px) и вылезает за границы контейнера. */
.xf-hero {
  position: relative;
  border-radius: 22px;
  overflow: hidden;
  border: 1px solid var(--line-soft);
  aspect-ratio: 1920 / 450;
  max-height: 450px;
  background: var(--surface-2);
}
.xf-hero__stripes {
  position: absolute;
  inset: 0;
  opacity: 0.05;
  color: var(--text-3);
  pointer-events: none;
}

.xf-hero__text {
  position: relative;
  padding: 36px 44px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.xf-hero__eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 2.5px;
  color: var(--accent-2);
  margin-bottom: 18px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.xf-hero__eyebrow::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--accent-2);
  box-shadow: 0 0 12px var(--accent);
  flex: 0 0 auto;
}
.xf-hero__title {
  margin: 0;
  font-size: 52px;
  line-height: 1;
  font-weight: 700;
  letter-spacing: -1.8px;
  color: var(--text);
}
.xf-hero__subtitle {
  margin: 14px 0 22px;
  font-size: 17px;
  color: var(--text-2);
  max-width: 460px;
  line-height: 1.4;
}
.xf-hero__ctas {
  display: flex;
  gap: 12px;
  align-items: center;
}
/* [#303] Точки-переключатели. Были 8×4 px: на фото-баннере это невидимая
   чёрточка, и покупатель не знал, что баннеров шесть. Теперь полоса 6px в
   22px-кликабельной зоне (WCAG 2.5.8 target size), а состояние читается прежде
   всего ШИРИНОЙ (18 → 34 px) — не одним цветом. Плашка-скрим под группой — в
   banner-режиме ниже. */
.xf-hero__dots {
  position: absolute;
  bottom: 28px;
  left: 44px;
  display: flex;
  align-items: center;
  gap: 8px;
  z-index: 3;
}
.xf-hero__dot {
  width: 18px;
  height: 24px; /* тач-цель ≥24px (WCAG 2.5.8 AA минимум, не через исключение) */
  display: flex;
  align-items: center;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  transition: width .3s;
}
.xf-hero__dot::after {
  content: "";
  display: block;
  width: 100%;
  height: 6px;
  border-radius: 3px;
  background: var(--line);
  transition: background .3s;
}
.xf-hero__dot.is-active {
  width: 34px;
}
.xf-hero__dot.is-active::after {
  background: var(--accent);
}

/* Media-слот — правая половина hero. Готов под product-PNG (transparent bg)
   через поле 'image' в hero.php. Если image пуст — рисует контурную
   fallback-иконку (xf-hero__placeholder), визуально явно «контента нет». */
.xf-hero__media {
  position: relative;
  margin: 24px 28px 24px 0;
  border-radius: 14px;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.xf-hero__image {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
}
.xf-hero__placeholder {
  width: 100%;
  height: 100%;
  min-height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-3);
  opacity: 0.5;
}
.xf-hero__placeholder svg {
  width: 72px;
  height: 72px;
}
.xf-hero__media-label {
  position: absolute;
  left: 20px;
  bottom: 20px;
  font-size: 11px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-3);
  z-index: 2;
}
.xf-hero__chips {
  position: absolute;
  top: 20px;
  right: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-end;
  z-index: 2;
}
.xf-hero__slide {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  opacity: 0;
  pointer-events: none;
  transition: opacity .5s ease;
}
.xf-hero__slide.is-active {
  opacity: 1;
  pointer-events: auto;
}

/* M10: Banner-mode hero — full-bleed image, без text-overlay из template.
   Banner self-contained (текст / brand / CTA впечатаны в картинку). Slide
   становится <a>, заменяя 2-column grid на single block. */
.xf-hero--banners .xf-hero__stripes { display: none; }
.xf-hero__slide--banner {
  display: block;
  grid-template-columns: none;
  color: inherit;
  text-decoration: none;
  /* Подложка на время ленивой подгрузки (#344): ручной клик по точке может
     показать слайд до onload картинки — нейтральная поверхность вместо пустоты. */
  background: var(--surface-3);
}

/* M11.3: nav-arrows + dots на banner-mode — переопределяем colors на
   universal-contrast (dark semi-transparent + white). Поверх любого
   banner'а (тёмного / светлого / nature) gives ≥3:1 contrast против
   default rgba(0,0,0,0.08) который сливался на тёмных banner'ах. */
.xf-hero--banners .xf-hero__nav {
  background: rgba(15, 17, 25, 0.55);
  border-color: rgba(255, 255, 255, 0.18);
  color: #fff;
}
@media (hover: hover) {
  .xf-hero--banners .xf-hero__nav:hover {
    background: rgba(15, 17, 25, 0.78);
    border-color: rgba(255, 255, 255, 0.32);
  }
}
html[data-theme="dark"] .xf-hero--banners .xf-hero__nav {
  background: rgba(15, 17, 25, 0.55);
}
@media (hover: hover) {
  html[data-theme="dark"] .xf-hero--banners .xf-hero__nav:hover {
    background: rgba(15, 17, 25, 0.78);
  }
}
/* [#303] Точки в banner-режиме: плашка-скрим + позиция вниз-по-центру.
   Плашка — тот же материал, что у стрелок выше (rgba(15,17,25,.55) + светлая
   рамка): поверх любого баннера (тёмного, светлого, «природного») полосы держат
   ≥3:1 — замерено, худший случай «полоса на белом фото» 3.3:1. Это скрим для
   читаемости поверх фото, а не декоративная подсветка (DESIGN.md, The
   Flat-Surface Rule); blur намеренно НЕ ставим — стекло по канону только у
   sticky-шапки.
   Позиция: слева нельзя — у баннеров текст и СВОЯ впечатанная кнопка стоят слева
   внизу, плашка накрывала «Открыть» / «Подробнее»; справа нельзя — там у баннера
   Jonsbo впечатан логотип. Центр свободен почти везде и совпадает с легаси:
   flexslider у aspro_next рисовал .flex-control-nav по центру снизу.
   translateX(-50%) — ПОЗИЦИОННЫЙ трансформ: множителем --xf-motion не гейтится
   (DESIGN.md, The Motion-Multiplier Rule, первое следствие). */
.xf-hero--banners .xf-hero__dots {
  left: 50%;
  transform: translateX(-50%);
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(15, 17, 25, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.18);
}
.xf-hero--banners .xf-hero__dot::after {
  background: rgba(255, 255, 255, 0.8);
}
.xf-hero--banners .xf-hero__dot.is-active::after {
  background: #fff;
}
.xf-hero__picture {
  display: block;
  width: 100%;
  height: 100%;
}
.xf-hero__slide--banner .xf-hero__image {
  display: block;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  object-fit: cover;
  object-position: center;
}

.xf-hero__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.08);
  border: 1px solid var(--line-soft);
  color: var(--text);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 4;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  transition: background .2s, border-color .2s, transform .2s;
}
html[data-theme="dark"] .xf-hero__nav { background: rgba(255, 255, 255, 0.06); }
@media (hover: hover) {
  .xf-hero__nav:hover {
  background: rgba(0, 0, 0, 0.14);
  border-color: var(--accent);
}
}
@media (hover: hover) {
  html[data-theme="dark"] .xf-hero__nav:hover { background: rgba(255, 255, 255, 0.14); }
}
.xf-hero__nav:active {
  transform: translateY(-50%) scale(calc(1 - 0.06 * var(--xf-motion, 1)));
}
.xf-hero__nav--prev { left: 20px; }
.xf-hero__nav--next { right: 20px; }

/* ─── TrustBar ────────────────────────────────────────────── */
.xf-trustbar {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
.xf-trustbar__item {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px 22px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
}
.xf-trustbar__icon {
  width: 42px;
  height: 42px;
  border-radius: 10px;
  background: var(--accent-dim);
  color: var(--accent-2);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--accent-line);
  flex: 0 0 auto;
}
.xf-trustbar__title {
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
}
.xf-trustbar__text {
  font-size: 12.5px;
  color: var(--text-3);
  margin-top: 2px;
}

/* M6+M11+M11.1: sub-hub macro-group title. v1 (M6) 12px JBM gray slишком
   тихий. v2 (M11) 15px sans weight 600 uppercase — лучше но всё ещё eyebrow
   label semantics. v3 (M11.1) drop uppercase, 18px, sentence-case — реальный
   H3 chapter heading weight. Conkurrent с main H1 не страшно, есть hierarchy
   через scale (h1 32px+ vs h3 18px). */
.xf-subhub__group-title {
  font-family: var(--font-sans);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  /* M11.2: bump top margin 36→48 — heavier title needs more breathing room
     от предыдущего grid'а. Plus faint top-rule (1px line-soft) для visual
     section break — критика flagged что 36px tight для 18px Onest 600. */
  margin: 48px 0 20px;
  padding-top: 20px;
  border-top: 1px solid var(--line-soft);
}
.xf-subhub__group-title:first-of-type {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
@media (max-width: 767px) {
  .xf-subhub__group-title {
    font-size: 15px;
    margin: 26px 0 12px;
  }
  .xf-subhub__group-title:first-of-type {
    margin-top: 0;
  }
}

/* ─── CategoryGrid ────────────────────────────────────────── */
.xf-catgrid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
}
/* M4: row-height uniformity. Без 1fr высота row'а sizing'уется по самому
   высокому tile (имя 1-3 строки → tile 242-283 px на desktop, рваные ряды).
   1fr forces все rows одной высоты — grid rhythm восстановлен. */
.xf-catgrid--16 {
  grid-auto-rows: 1fr;
}
.xf-cat-tile {
  position: relative;
  display: block;
  height: 140px;
  padding: 18px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  overflow: hidden;
  transition: border-color .2s, transform .2s, box-shadow .2s;
  color: var(--text);
}
/* M2: hover delight — lift + accent border + soft shadow + photo scale.
   Стандартный e-commerce category feedback. До этого был только border-color
   accent (слишком subtle для "actionable tile" semantic). */
@media (hover: hover) {
  .xf-cat-tile:hover {
    border-color: var(--accent-line);
    transform: translateY(calc(-2px * var(--xf-motion, 1)));
    box-shadow: var(--shadow-card);
  }
  .xf-cat-tile:hover .xf-cat-tile__cover-img { --crop-scale: 1.11; }
  /* [#323] Тёмная тема: --shadow-card (16px вниз, alpha .6) при hover-lift
     долетала через межплиточный зазор до плитки следующего ряда и мерцала
     на ней тёмной полосой при проводке курсора. Короче и мягче — до соседа
     не достаёт; elevation в dark несут бордер и lift. */
  html[data-theme="dark"] .xf-cat-tile:hover {
    box-shadow: 0 8px 20px -12px oklch(0.05 0.03 280 / 0.35);
  }
}
.xf-cat-tile--lg { height: 180px; padding: 22px; }
.xf-cat-tile--xl {
  height: 100%;
  min-height: 432px;
  padding: 30px;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  row-gap: 20px;
}

/* Подраздельная плитка (section.php): art сверху + body снизу.
   В отличие от landing-плиток с icon-badge'ом — тут art в потоке. */
.xf-cat-tile:has(.xf-cat-tile__art) { min-height: 200px; height: auto; padding: 16px; }
.xf-cat-tile__art {
  height: 88px;
  border-radius: 12px;
  overflow: hidden;
  margin-bottom: 12px;
}

/* Sub-hub icon redesign M1: cover-photo variant заменяет icon-абстракцию
   для nichevye sub-categories. object-fit:contain — товар целиком виден
   с padding по краям если aspect-ratio не совпадает с tile (e-commerce
   category preview standard). #fff backdrop matches L2 product card
   pattern (project DECISION lock — non-transparent photos accepted). */
.xf-cat-tile__art--cover {
  /* M2: height 140→152, padding 8→4 — product получает +28px вертикали
     и +8px горизонтали внутри cover area, меньше empty whitespace.
     M5: padding 4→2 — ещё +4px каждого направления для product display. */
  position: relative;
  height: 152px;
  background: #fff;
  padding: 2px;
  box-sizing: border-box;
}

/* M13 static curated covers — transparent PNG illustrations (через
   images/category-covers/<code>.png). Theme-aware: BG берёт var(--surface)
   из tile (white/dark), illustration «парит» с alpha. Никаких watermark /
   scale crop / mask — фотки уже clean. Padding щедрее (12px) — иллюстрации
   обычно с тёмным «телом», нужно breathing room. */
.xf-cat-tile__art--static {
  position: relative;
  height: 152px;
  background: transparent;
  padding: 12px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
}
.xf-cat-tile__static-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  display: block;
  transition: transform .25s ease;
}
@media (hover: hover) {
  .xf-cat-tile:hover .xf-cat-tile__static-img { transform: scale(calc(1 + 0.04 * var(--xf-motion, 1))); }
}
/* M3 watermark mask: catalog в большинстве scraped с onliner.by, у jpeg'ов
   выжжен watermark «catalog.onliner.by» в правом-нижнем углу. Радиальный
   gradient от bottom-right corner растворяет watermark в #fff backdrop, не
   создавая видимый прямоугольник fade'а на photo'у (был issue v1 — solid
   white block при 65%×20% слишком очевиден на dark-photo тайлах). v2 —
   tight radial 28%×11% от corner, soft fade, плотный только в самом углу. */
.xf-cat-tile__art--cover::after {
  content: '';
  position: absolute;
  right: 0;
  bottom: 0;
  width: 28%;
  height: 11%;
  background: radial-gradient(
    ellipse 100% 100% at 100% 100%,
    #fff 0%,
    rgba(255, 255, 255, 0.85) 30%,
    rgba(255, 255, 255, 0) 80%
  );
  pointer-events: none;
}
.xf-cat-tile__cover-img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  /* M12: универсальная bottom crop через scale + top-anchor — режет bottom 7%
     любого фото независимо от aspect ratio. Убирает catalog.onliner.by
     watermark (выжжен в bottom-right corner JPG) когда photo не fills frame
     и M3 mask миссит (часто на landscape photos с vertical whitespace).
     Hover state composes via CSS var (--crop-scale 1.07 base + 0.04 hover). */
  --crop-scale: 1.07;
  transform: scale(var(--crop-scale));
  transform-origin: top center;
  transition: transform .25s ease;
}
/* Text-only tile когда нет ни UF_CATALOG_ICON ни product photo
   (sub-секция пустая или все товары без picture'ов). Match height
   соседних photo tiles для grid consistency, vertically center content. */
.xf-cat-tile--text {
  min-height: 200px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* M2: icon badge на нейтральной поверхности. Раньше было 8 hue'-вариантов
   (lavender / cyan / mint / pink / magenta) — сняли как «AI-pastel-reflex».
   Различие плиток теперь только через icon. Hover поднимает acc'ent — единый. */
.xf-cat-tile__icon {
  position: absolute;
  right: 16px;
  bottom: 16px;
  width: 54px;
  height: 54px;
  border-radius: 14px;
  background: var(--surface-3);
  border: 1px solid var(--line-soft);
  color: var(--text-2);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .25s, border-color .25s, color .25s, transform .25s, box-shadow .25s;
  z-index: 2;
}
.xf-cat-tile--lg .xf-cat-tile__icon { width: 64px; height: 64px; }
.xf-cat-tile--xl .xf-cat-tile__icon {
  right: 44px;
  bottom: 44px;
  width: 108px;
  height: 108px;
  border-radius: 20px;
}
.xf-cat-tile__icon svg { width: 28px; height: 28px; }
.xf-cat-tile--lg .xf-cat-tile__icon svg { width: 34px; height: 34px; }
.xf-cat-tile--xl .xf-cat-tile__icon svg { width: 56px; height: 56px; }
.xf-cat-tile__icon-img {
  max-width: 70%;
  max-height: 70%;
  object-fit: contain;
}

/* M2: featured xl tile — neutral slab под UF_CATALOG_ICON image (или fallback
   SVG). Раньше использовался .xf-art c hue-driven pastel gradient — сняли.
   Slab выглядит как «рамка под product photo», как hero __media. */
.xf-cat-tile--xl .xf-cat-tile__art {
  grid-row: 2;
  justify-self: end;
  align-self: stretch;
  width: min(280px, 100%);
  height: 100%;
  max-height: 280px;
  min-height: 140px;
  border-radius: 18px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  transition: transform .25s;
  pointer-events: none;
}
.xf-cat-tile__art-img {
  max-width: 80%;
  max-height: 80%;
  object-fit: contain;
}
.xf-cat-tile__art-icon {
  color: var(--text-3);
  opacity: 0.5;
  display: flex;
  align-items: center;
  justify-content: center;
}
.xf-cat-tile__art-icon svg {
  width: 96px;
  height: 96px;
}
@media (hover: hover) {
  .xf-cat-tile--xl:hover .xf-cat-tile__art { transform: translateY(calc(-2px * var(--xf-motion, 1))); }
}

@media (hover: hover) {
  .xf-cat-tile:hover .xf-cat-tile__icon {
    background: var(--accent);
    border-color: transparent;
    color: #fff;
    transform: translateY(calc(-2px * var(--xf-motion, 1)));
    box-shadow: 0 12px 28px -10px oklch(0.5 0.22 268 / 0.45);
  }
}

.xf-cat-tile__body {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.xf-cat-tile--xl .xf-cat-tile__body { gap: 12px; grid-row: 1; align-self: start; }
.xf-cat-tile__name {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.3px;
  margin-top: 0;
  max-width: 70%;
}
/* M4 desktop: имя на cover-tile reserves ровно 2 line'а — короткие имена
   (1 строка) получают пустую вторую строку, длинные обрезаются до 2.
   Body высота predictable → tile heights uniform → grid rhythm clean.
   Mobile использует 3-line clamp из catalog.css base + tile auto-grow. */
.xf-cat-tile:has(.xf-cat-tile__art--cover) .xf-cat-tile__name {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  line-height: 1.35;
  min-height: calc(1.35em * 2);
  max-width: 100%;
}
/* M7: убираем redundant ↗ arrow с cover-photo tile'ов. Tile целиком =
   single <a> link с hover lift + accent border + photo scale — arrow
   ничего не добавляет к affordance. Оставляем на .xf-cat-tile--xl
   hero-variant (другой контекст, там arrow несёт визуальный вес). */
.xf-cat-tile:has(.xf-cat-tile__art--cover) .xf-cat-tile__arrow,
.xf-cat-tile:has(.xf-cat-tile__art--static) .xf-cat-tile__arrow,
.xf-cat-tile--text .xf-cat-tile__arrow {
  display: none;
}
.xf-cat-tile__count {
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-3);
  letter-spacing: 0;
}
.xf-cat-tile--lg .xf-cat-tile__name { font-size: 18px; }
.xf-cat-tile--xl .xf-cat-tile__name {
  font-size: 28px;
  margin-top: 12px;
  max-width: none;
}
.xf-cat-tile--xl .xf-cat-tile__desc { max-width: none; }
.xf-cat-tile__desc {
  margin-top: 8px;
  font-size: 14px;
  color: var(--text-2);
  max-width: 55%;
  line-height: 1.5;
}
.xf-cat-tile__arrow {
  position: absolute;
  top: 16px;
  right: 16px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--surface-3);
  color: var(--text-2);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .2s, color .2s;
  z-index: 3;
}
.xf-cat-tile--xl .xf-cat-tile__arrow { top: 28px; right: 28px; }
@media (hover: hover) {
  .xf-cat-tile:hover .xf-cat-tile__arrow {
  background: var(--accent);
  color: #fff;
}
}

/* ─── Полка разделов на главной (#361) ─────────────────────────────────────
   Разметка — include/home/catgrid.php. Плитки .xf-cat-tile выше живут дальше:
   их рисуют страницы каталога (landing.php, section.php), здесь они больше не
   используются.
   Что было на главной: eyebrow + H2 «Популярные категории» + 8 XL-плиток —
   466 px на десктопе и 927 px на телефоне, причём тот же список теми же словами
   в том же порядке уже стоит в липкой полосе шапки. Стало: одна строка на
   раздел, иконка без подложки, число без пилюли. Ничего не «выделяется» в
   покое — блок работает как указатель, а не как витрина, и accent
   (One-Voice Indigo Rule) появляется только под курсором. */
.xf-catrail {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  /* Вчетверо тише, чем секции .xf-home__section (56/24): полка примыкает к
     баннерам сверху и к «Товару дня» снизу, своей «главы» у неё нет. */
  padding: 20px 0 4px;
}

.xf-catrail__item {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 10px 14px;
  /* Card-On-Cream: поверхность светлее фона страницы, в обеих темах. */
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  color: var(--text);
  text-decoration: none;
  transition: border-color .18s, color .18s, transform .18s;
}

/* Тёмная тема: плитка ловит свет верхней кромкой (--nx-edge) — так ряд не
   растворяется в почти чёрном фоне, а плитка не начинает кричать. Рамка —
   та же --line-soft, на наведении — accent-line из правила ниже. (До #708
   здесь стояла рамка --line прежней тёмной темы; скин её перекрывал.) */
html[data-theme="dark"] .xf-catrail__item { box-shadow: var(--nx-edge); }

.xf-catrail__icon {
  flex: 0 0 20px;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-3);
  transition: color .18s;
}
.xf-catrail__icon svg { width: 20px; height: 20px; }
/* UF_CATALOG_ICON из админки — растр; contain, чтобы не растянуть непропорционально. */
.xf-catrail__icon-img { width: 20px; height: 20px; object-fit: contain; }

.xf-catrail__name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.25;
}

/* «7679 товаров»: цифра — данные (mono, Mono-as-Data Rule), слово — речь
   (Onest). Поэтому шрифт назначается не строке целиком, а её половинам. */
.xf-catrail__count {
  flex: 0 0 auto;
  /* Явно, а не наследованием: слово — речь, и шрифт речи задаётся здесь, чтобы
     mono с цифры на него не перетёк. */
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--text-3);
  white-space: nowrap;
}
.xf-catrail__num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

/* Тёмная тема: --text-3 на --surface даёт 4.28:1 при 12 px — ниже порога AA
   4.5:1 (замер живого /dev/ 2026-08-06; в светлой тот же токен даёт 4.85 и
   проходит). Поднимаем на один шаг до --text-2 → 8.2:1, и только в тёмной,
   чтобы утверждённая светлая тема осталась байт-в-байт прежней.
   Токен --text-3 не проходит AA в тёмной и в чужих местах страницы
   (плейсхолдер поиска, «BYN» в карточках) — это общая проблема токена, она
   чинится отдельно. [#708] Числа — прежней тёмной темы. В «Ночной вывеске»
   (#739) --text-3 на --surface даёт 6.6:1, --text-2 — 10.2:1; правило живое
   и держит прежний вид — снять его значит сменить вид. */
html[data-theme="dark"] .xf-catrail__count { color: var(--text-2); }

@media (hover: hover) {
  .xf-catrail__item:hover {
    border-color: var(--accent-line);
    color: var(--accent-2);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
  .xf-catrail__item:hover .xf-catrail__icon { color: var(--accent-2); }
}

/* Число идёт со словом («7679 товаров»), и строка «иконка + название + число»
   помещается в один ряд только на широкой плитке: ей нужно ~246 px (иконка 20 +
   зазор 10 + «Комплектующие» 113 + «7679 товаров» 75 + поля 28).
   Замер инъекцией на живом /dev/ 2026-08-06: 1440 → плитка 333 px, 1280 → 305,
   а вот 1024 → 229 и 768 → 223, и там название обрезается. Обрезать название
   у навигации нельзя — раздел должен называться целиком, — поэтому всё, что
   ниже 1280 px, раскладываем в два ряда: название над числом, тем же приёмом,
   что уже был на телефоне. Плитка растёт с 44 до 54 px. */
@media (max-width: 1279px) {
  .xf-catrail__item {
    display: grid;
    grid-template-columns: 20px minmax(0, 1fr);
    column-gap: 10px;
    row-gap: 2px;
    align-items: center;
  }
  .xf-catrail__icon { flex: none; grid-row: 1 / span 2; align-self: center; }
  .xf-catrail__name  { grid-column: 2; }
  .xf-catrail__count { grid-column: 2; }
}

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

/* Телефон — главный сценарий (PRODUCT.md → Operating Context), и именно там
   старая сетка стоила целый экран. Две колонки, число уходит под название:
   в ~175 px ширины строка «иконка + название + число» не помещается, а резать
   название многоточием у навигации нельзя.
   Метрики подобраны замером, не на глаз: самое длинное название
   («Комплектующие», перенести нельзя — одно слово) занимает 113 px в Onest
   14/500 и 105 px в 13/500. При 13 px, иконке 18 px, зазоре 8 и полях 10
   строке нужно 151 px — на экране 360 (Galaxy S-серия и вся её родня) колонка
   даёт 160, запас 9 px. Кегль 13 px здесь легален: DESIGN.md → Body Small,
   «secondary navigation». */
@media (max-width: 767px) {
  .xf-catrail {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    padding: 16px 0 4px;
  }
  .xf-catrail__item {
    display: grid;
    grid-template-columns: 18px minmax(0, 1fr);
    column-gap: 8px;
    row-gap: 2px;
    align-items: center;
    padding: 10px;
  }
  .xf-catrail__icon { flex: none; width: 18px; height: 18px; grid-row: 1 / span 2; align-self: center; }
  .xf-catrail__icon svg { width: 18px; height: 18px; }
  .xf-catrail__icon-img { width: 18px; height: 18px; }
  .xf-catrail__name  { grid-column: 2; font-size: 13px; }
  .xf-catrail__count { grid-column: 2; font-size: 11px; }
}

/* Ниже 360 px две колонки перестают вмещать «Комплектующие» при любом честном
   кегле (при 340 колонка = 150 px против нужных 151), а обрезать название
   нельзя. Такие экраны получают одну колонку и строку в один ряд — блок
   становится выше, но остаётся читаемым. */
@media (max-width: 359px) {
  .xf-catrail { grid-template-columns: minmax(0, 1fr); }
  .xf-catrail__item { display: flex; }
}

/* ─── Product art (neutral placeholder) ────────────────────
   M6: per-category hue rotation снят. Placeholder = единый neutral
   surface (Workshop Tile per DESIGN.md `Tinted-Neutral`), вариация
   между категориями держится только на иконке. inline `style="--h:"`
   из PHP остаётся в markup для совместимости, но больше не читается. */
.xf-art {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-3);
}
.xf-art__icon {
  position: relative;
  z-index: 1;
  color: var(--text-3);
  opacity: 0.85;
}
/* Dark theme: bump icon contrast to clear WCAG AA 3:1. With text-3 (L 0.58)
   on surface-3 (L 0.285) at opacity 0.85 the ratio is ~2.25:1 — icons fade
   into tile-art and the only tile differentiator becomes the name. text-2
   (L 0.75) with opacity 0.95 lands ~4.5:1. Light theme is fine (4.1:1).
   [#708] Числа — прежней тёмной темы. В «Ночной вывеске» (#739): text-3 при
   0.85 даёт на --surface-3 4.5:1, text-2 при 0.95 — 8.0:1; правило живое и
   держит прежний вид. */
html[data-theme="dark"] .xf-art__icon {
  color: var(--text-2);
  opacity: 0.95;
}
.xf-art__icon svg {
  display: block;
  width: 64px;
  height: 64px;
}
.xf-art--xl .xf-art__icon svg { width: 130px; height: 130px; }
.xf-art__label {
  position: absolute;
  bottom: 8px;
  left: 8px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.5px;
  color: var(--text-3);
  padding: 2px 6px;
  background: var(--surface);
  border-radius: 4px;
  border: 1px solid var(--line-soft);
  z-index: 2;
}
.xf-art__img {
  position: relative;
  z-index: 1;
  max-width: 80%;
  max-height: 80%;
  object-fit: contain;
}

.xf-art--photo {
  background: var(--surface);
}
/* Dark theme: pure-white backdrop под фото. Tried tinted oklch(0.92 0.005 285)
   to soften the «N фар на тёмной странице» effect (M2.1) — но фото в каталоге
   не имеют идеальной обрезки: у многих jpeg/png остаётся свой near-white
   background, который на тинтнутой подложке проступает видимым «бордюром»
   вокруг фото. На #fff чужой фото-бэкграунд сливается с карточной плитой —
   ragged-edge невидим. Trade-off: «фары» возвращаются как стилистический
   шум, но photo-edge cleanliness ценится выше (data-side issue фото нельзя
   быстро исправить, white plate is the pragmatic cover). */
html[data-theme="dark"] .xf-art--photo {
  background: #fff;
  isolation: isolate; /* multiply слоя ниже смешивается только с плитой, не со страницей */
}
.xf-art--photo::before,
.xf-art--photo::after {
  display: none;
}
/* Тёмная тема: белая плита → лайтбокс. Плита остаётся #fff (выше), тон
   кладётся наложением multiply на плиту и фото сразу — одним слоем на обоих,
   шва нет (проверено крупным планом, 2026-10-15). Плита чуть приглушена и
   гаснет к краям: «фара» на тёмной странице становится светящейся витриной.
   Слой — токен --nx-lightbox (tokens.css), тот же у миниатюр товара
   (product.css) и фото в корзине (cart.css). */
html[data-theme="dark"] .xf-art--photo::after {
  content: "";
  display: block;
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  mix-blend-mode: multiply;
  background: var(--nx-lightbox);
}

/* Photo product-card: wrapper schrink'ается до actual img-rect через
   container queries — даёт плашке guarantee позиционирование bottom-right
   ровно по углу самого фото (а не card-image-zone'ы). Так наша плашка
   физически перекрывает чужой watermark catalog.onliner.by, который
   выжжен в jpeg в правом-нижнем углу image-rect.
   container queries уже в стеке (см. M-cmp-mobile-hybrid). */
.xf-product-card__image {
  container-type: inline-size;
}
/* Wrapper к низу image-зоны: фото "стоят на одной полке", все плашки
   guarantee на одной горизонтальной линии независимо от aspect ratio фото. */
.xf-product-card .xf-art--photo {
  align-items: flex-end;
}
/* P1 fix: плашка .xf-product-card__guarantee (right:0/bottom:0 в этом wrap'е)
   шире wrap'а на портретных/узких фотках (wrap shrink'ается до photo via flex:0 0
   auto + max-width:80cqi на img). Overflow налево → клиппинг по image-area
   `overflow:hidden` («...антия 36 месяцев»). Min-width ≥ worst-case plaque actual
   width («Гарантия NN месяцев» ≈ 138px desktop / 120px mobile, с padding) —
   wrap не уже плашки, фото центрируется на чуть более широкой подложке (та же
   surface BG что у .xf-art--photo parent — визуально invisible). */
.xf-product-card .xf-art--photo .xf-art__photo-wrap {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  /* Без border-radius намеренно (#294): радиус клипал углы full-bleed
     рендеров — искажение силуэта товара. Скругление — на карточке, не на фото. */
  overflow: hidden;
  min-width: 140px;
  justify-content: center;
}
.xf-product-card .xf-art--photo .xf-art__img {
  display: block;
  width: auto;
  height: auto;
  max-width: 80cqi;
  max-height: 80cqi;
}

/* ─── Товар дня (#223, два состояния — #434) ───────────────
   Админ-управляемая витрина-«paper» с двумя состояниями. ОДИН товар — прежняя
   карточка: фото слева, инфо справа, ≤767px — стек (правила ниже до блока
   «Диптих» и есть это состояние; на проде оно пока единственное). ДВА товара —
   диптих `.xf-day--duo`: та же ОДНА рамка, внутри две панели через линию
   --line-soft; почему не две отдельные обведённые карточки и почему мобильная
   панель компактная — в комментарии самого блока «Диптих» ниже.
   Flat (Flat-By-Default), радиус 22px как hero/banner, 1px line-soft.
   Индиго ≤10%: только eyebrow + primary CTA. Градиента нет (Single-Gradient —
   он уже у hero). Цена — свои .xf-day-price* классы (НЕ .xf-price-* из ui.css:
   те завязаны на container-type cqi у карточки товара). Тёмная тема — через
   существующие токены (var(--surface)/--line-soft/--text*), отдельного блока
   не нужно. */
.xf-day {
  display: flex;
  gap: 44px;
  align-items: center;
  background: var(--surface);          /* workshop-card — светлее body bg */
  border: 1px solid var(--line-soft);
  /* Выделение витрины (#223, просьба владельца «заметнее»): индиго-обводка 45%
     + 4% индиго-подтон. Full border + background tint — приёмы, прямо
     разрешённые DESIGN.md (Don't про цветной border-left предлагает именно их).
     2px сплошного accent пробовали — читается как focus/selected и ломает
     One-Voice Indigo. Пары строк ниже — прогрессивное улучшение поверх базовых
     border/background выше: браузер без color-mix остаётся на line-soft/surface. */
  border: 1.5px solid color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 4%, var(--surface));
  border-radius: 22px;
  padding: 28px;
}

/* Фото — редундантная mouse-цель (aria-hidden в разметке): letterbox на
   surface-3, фото object-fit contain, как арт-плитки.
   Пропорции десктопа (critique #223 P1 «полупустая карточка»): медиа 46%
   (до 600px) + инфо-колонка max-width 640px → на 1440 карточка заполнена,
   правый провал ~40px вместо трети ширины. Медиа-кап меньше 380px не ставить —
   возвращает провал. */
.xf-day__media {
  flex: 0 0 46%;
  max-width: 600px;
  aspect-ratio: 16 / 11;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-3);
  border-radius: 16px;
  overflow: hidden;
}
.xf-day__media--empty { min-height: 200px; }
/* Под фото — чистый белый (тот же trade-off, что html[data-theme] у
   .xf-art--photo выше: baked near-white бэкграунды jpeg на тинтованной подложке
   дают рваный «бордюр»; на #fff край сливается). Пустой тайл остаётся тональным.
   [#361] Раньше правило стояло только под html[data-theme="dark"], и в светлой
   теме плита оставалась тонированной (--surface-3 ≈ #E8E8EE) — ровно тот же
   край фотографии был виден прямоугольником, проверено на /dev/ и на проде
   2026-08-06. Карточки товара ниже по странице в светлой теме уже кладут фото
   на чистый --surface, так что самый крупный снимок страницы был единственным
   с видимым краем. Теперь симметрично в обеих темах. */
.xf-day__media:not(.xf-day__media--empty) {
  background: #fff;
  /* [#420] Плита заодно — query-контейнер для фото и плашки гарантии; зачем
     именно size и почему только непустая плита — в блоке ниже. */
  container-type: size;
}
/* [#420] Плашка гарантии поверх фото «Товара дня». Визуал плашки — общий с
   карточкой листинга (.xf-product-card__guarantee ниже в этом же файле),
   здесь только позиционирование: её задача — физически перекрыть чужую
   ватермарку catalog.onliner.by, выжженную в jpeg.
   Замер живого фото (1000×594, /upload/iblock/605/…, 2026-08-19): ватермарка
   лежит в правом нижнем углу КАДРА — ~8%×2% кадра, отступ ~2.5% от правого
   края и ~3% от нижнего. Кадр центрирован на плите и уже её (max 88%), так
   что по углу плиты плашка ватермарку бы не задела: якорем должен быть кадр.
   Механика — та же, что у карточки: плита задаёт систему координат, обёртка
   ужимается до фактического image-rect (flex: 0 0 auto), а размер фото
   берётся от плиты в cq-единицах — процентам тут разрешаться не во что
   (высота обёртки auto). container-type: size, а НЕ inline-size: плита
   широкая (16/11), высоту надо мерить по cqb — 88cqi по высоте вылезло бы
   за плиту и фото обрезалось бы её overflow: hidden. Пустая плита
   (--empty) контейнером не делается: фото в ней нет, а size-containment
   лишний раз трогать её min-height незачем. */
.xf-day__photo-wrap {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  justify-content: center;
  max-width: 100%;
  /* Плашка прижата к right:0/bottom:0 обёртки и на узком/портретном кадре
     оказывается шире него — перелив ушёл бы влево и обрезался по overflow
     («…арантия 60 мес.»). Порог ≥ фактической ширины плашки: значения свойства
     в базе — «12/24/36/60 мес.», подпись при этом уже собственного
     min-width: 128px, то есть 128px и есть худший случай (замерено в браузере
     2026-08-19). 140px — тот же порог, что у .xf-art__photo-wrap карточки, с
     запасом на более длинное значение из 1С. Узкое фото при этом центрируется
     на чуть более широкой подложке — она прозрачная, видно ту же белую плиту. */
  min-width: 140px;
  overflow: hidden;
}
.xf-day__photo {
  max-width: 88cqi;
  max-height: 88cqb;
  width: auto;
  height: auto;
  object-fit: contain;
}

.xf-day__info {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 640px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 16px;
}

/* Eyebrow «Товар дня» — mono/uppercase/accent, дефис-риска слева (паттерн
   .xf-section-head__eyebrow), + карандаш админа в одну строку. */
.xf-day__head {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}
.xf-day__eyebrow {
  /* [#361] Тег — <h2> (у секции не было доступного имени, см. day.php).
     Типографику держит этот класс, поэтому вид от смены тега не меняется:
     гасим только собственный margin заголовка. */
  margin: 0;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent-2);
}
.xf-day__eyebrow::before {
  content: "";
  display: inline-block;
  width: 28px;
  height: 1px;
  background: var(--accent-2);
}

/* Имя-ссылка на PDP — единственная семантическая ссылка блока (title-типографика). */
.xf-day__name {
  font-size: 26px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.4px;
  color: var(--text);
  text-decoration: none;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-day__name:hover { color: var(--accent-2); }
}

/* Цена — живая сверху (mono), зачёркнутая ПОД ней (Onest, не mono — слэш-ноль
   поверх line-through дал бы двойное перечёркивание; см. price-strike.md §4). */
.xf-day-price {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
}
.xf-day-price__val {
  font-size: 34px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.5px;
  color: var(--text);
  white-space: nowrap;
}
.xf-day-price__cur {
  color: var(--text-3);
  font-size: 0.55em;
  font-weight: 500;
  margin-left: 5px;
}
.xf-day-price__was-line {
  display: flex;
  align-items: baseline;
  margin-top: 5px;
}
.xf-day-price__was {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-2);
  line-height: 1.1;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: currentColor;
}
.xf-day-price__val--request {
  font-size: 20px;
  font-weight: 500;
  color: var(--text-3);
  letter-spacing: -0.2px;
}

/* Таймер: подпись + тикающие цифры (mono, data) + .xf-sr-only честная дата.
   Тихий тональный чип (surface-2, line-soft) — визуальный якорь без urgency-
   окраски: никакого красного, только нейтральная плашка. */
.xf-day__timer {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 16px;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  background: var(--surface-2);
}
.xf-day__timer-label {
  font-size: 13px;
  color: var(--text-2);
}
/* [#521] «Осталось» + цифры — ОДИН элемент полосы: полоса переносит строку
   только между своими детьми, а группа — flex без wrap, своих детей по
   строкам не раскладывает. Подпись и цифры уезжают вниз вместе или
   остаются в строке вместе. Без обёртки на 768 при «15д» вниз срывались
   одни цифры. Если группе тесно и в своей строке (одиночная витрина на
   320 при двузначных часах), сжимается подпись: «До конца акции»
   переносится по словам рядом с цифрами — так же, как уже сейчас в сетке
   полосы #519 на этой ширине. «Осталось» диптиха — одно слово, ему
   хватает места и на 320. gap: inherit — зазор внутри
   группы тот же, что был между подписью и цифрами как соседями по полосе
   (12px одиночной витрины, 10px диптиха), ничего нового не вводим. */
.xf-day__countdown {
  display: flex;
  align-items: center;
  gap: inherit;
}
/* [#519] Плашка полосы. Текст у неё один из двух: «Акция» или — по галке
   владельца в элементе товара дня — «Минимальная цена в Беларуси» (решение
   владельца 2026-09-15: подпись ВМЕСТО «Акции», второй плашки в полосе нет).
   Вид от текста не зависит: та же обводка, тот же вес.
   Базовое правило нужно одиночной витрине: там «Акции» нет, а подпись по
   галке появляется — и без него она была бы голым текстом. Диптих правит
   поверх только кегль (.xf-day--duo .xf-day__tag ниже, #484). */
.xf-day__tag {
  display: inline-flex;
  align-items: center;
  padding: 4px 11px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--accent-line);
  color: var(--accent-2);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0.1px;
  white-space: nowrap;
}
.xf-day__clock {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
}
.xf-day__unit {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
}
.xf-day__num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.3px;
  color: var(--text);
}
.xf-day__sep {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  color: var(--text-3);
}
/* [#470] Единица дней прячется атрибутом hidden, пока дней ноль (ставит
   сервер, переключает app.js). Своё display: inline-flex у .xf-day__unit
   перебило бы UA-правило [hidden] — возвращаем его явно. */
.xf-day__unit[hidden] { display: none; }

.xf-day__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 2px;
}
/* CTA несёт свою иконку + подпись — совпадает с базовым .xf-btn (индиго). */
.xf-day__cart { gap: 8px; }
/* [#348] Двойная иконка и двойная подпись, переключаемые классом .is-added (его
   ставит app.js → applyCartBtnState) — тот же приём, что у листинговой
   .xf-product-card__cart-btn выше и у .xf-btn-buy на PDP (product.css).
   inline-flex на обёртках: без него svg сидит на baseline текста и подпись
   уезжает на пиксель вниз. */
.xf-day__cart-icon,
.xf-day__cart-label { display: inline-flex; align-items: center; }
.xf-day__cart:not(.is-added) .xf-day__cart-icon--check,
.xf-day__cart:not(.is-added) .xf-day__cart-label--added,
.xf-day__cart.is-added .xf-day__cart-icon--bag,
.xf-day__cart.is-added .xf-day__cart-label--idle { display: none; }
/* [#348] «В корзине» — нейтральный вид ровно теми же токенами, что у карточки и
   PDP (#326): ступень поверхности --surface-3, полный ink --text, полная линия
   --line. Зелёного здесь не было и не появляется — зелёный в шаблоне значит «в
   наличии» (.xf-product-card__stock--in), и кнопка «уже в корзине» им быть не
   должна. Состояние несут форма (галка), слово («В корзине») и ступень
   поверхности — три нецветовых признака, color-blind safe (PRODUCT.md).
   Glow базового .xf-btn гасим: он рисуется от --accent и на нейтральной кнопке
   читался бы как индиго-CTA. Заодно чинится иерархия — после добавления товара
   primary-действие на блоке закончилось, дальше «Подробнее» (DESIGN.md, The
   Single-Primary Rule). Hover-lift базовой .xf-btn не трогаем: клик по этой
   кнопке — навигация в корзину, кликабельность остаётся живой. */
.xf-day__cart.is-added {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--line);
  box-shadow: none;
}
/* Hover: поверхность остаётся нейтральной, «кликабельно» говорят индиго-ink
   --accent-2 и индиго-линия --accent-line — как у .is-added на карточке и на PDP.
   box-shadow: none повторён здесь намеренно: glow живёт на базовом .xf-btn
   (0,1,0) и его гасит правило покоя выше (0,2,0), но если у .xf-btn когда-нибудь
   появится свой hover-glow (у .xf-btn-buy на PDP он есть), это правило (0,3,0)
   не даст ему вернуться на нейтральную кнопку. */
@media (hover: hover) {
  .xf-day__cart.is-added:hover {
    background: var(--surface-3);
    color: var(--accent-2);
    border-color: var(--accent-line);
    box-shadow: none;
  }
}

@media (max-width: 767px) {
  .xf-day {
    flex-direction: column;
    align-items: stretch;
    gap: 20px;
    padding: 18px;
  }
  .xf-day__media {
    flex-basis: auto;
    max-width: none;
    width: 100%;
    aspect-ratio: 16 / 10;
  }
  .xf-day-price__val { font-size: 26px; }
  .xf-day__actions { flex-wrap: nowrap; }
  .xf-day__cart { flex: 1 1 0; }
}

/* ─── [#434] Диптих: два товара дня ────────────────────────
   Второе состояние блока. Рамка — та же самая: одна «бумага» с индиго-обводкой
   45%, подтоном 4%, радиусом 22 и паддингом 28. Секция остаётся ОДНИМ
   предметом на верстаке: две отдельные обведённые карточки — это «identical
   card grid» из DESIGN.md → Don't, плюс вторая индиго-поверхность поверх
   лимита One-Voice Indigo. Панели делит одна линия --line-soft в 1px.
   Раскладки: ≥1200 — две колонки, панель горизонтальная (плита слева), линия
   вертикальная; 768–1199 — те же две колонки, но панель вертикальная (плита во
   всю ширину колонки сверху, инфо под ней); ≤767 — панели друг под другом,
   линия горизонтальная, панель сжимается в «плита 112px | имя, наличие, цена»,
   а таймер и кнопки уходят строкой на всю ширину. Компактная мобила — не
   экономия ради экономии: два фото во всю ширину утащили бы полку категорий и
   «Что сейчас берут» на экран вниз, а мобильный — главный сценарий покупки
   (PRODUCT.md); оба товара должны быть видны без свайпа.

   ВЫРАВНИВАНИЕ ПАРЫ (≥768). Панели — одна вещь, а не два соседних объявления,
   поэтому наличие, цена, таймер и кнопки обязаны стоять на одной линии в обеих.
   Само собой это не выходит: имена разной длины (три строки против двух)
   сдвигали правый столбец на ~26px вверх. Ряды задаёт РАМКА, а панель садится
   на них через `grid-template-rows: subgrid`; .xf-day__info при этом
   растворяется (display: contents), чтобы её дети стали ячейками этих общих
   рядов. Ряд «наличия» получает высоту по самой высокой из двух панелей — и
   обе следующие строки стартуют с одной отметки.
   Порядок рядов в диптихе — не карточка товара, а объявление об акции:
   СРОК → товар → наличие → цена → кнопки. Отсчёт открывает панель полосой во
   всю её ширину (владелец, 2026-08-21: «таймер не виден, непонятно, что это
   акция»; во второй редакции чип сидел в текстовой колонке и читался как её
   элемент). Ряды: 1 полоса отсчёта, 2 плита (её занимает планшет; на ≥1200
   ряд пустой — там плита стоит слева и тянется по рядам 3–6), 3 имя,
   4 наличие, 5 цена, 6 кнопки, 7 карандаш.
   Вертикальный ритм несут margin'ы самих элементов, а НЕ row-gap трека: у
   товара может не быть наличия или срока, и пустой ряд с row-gap оставлял бы
   висящий зазор. Правило простое: зазор ПОД необязательным элементом (чип,
   плита) — его собственный margin-bottom, зазор НАД обязательным (наличие,
   цена, кнопки) — margin-top. Пропал элемент — пропал и его отступ; а раз
   отступы у обеих панелей одинаковые, выравнивание от них не страдает.
   Одиночные правила выше не тронуты — состояние «один товар» осталось
   прежним попиксельно. */
.xf-day--duo {
  display: grid;
  grid-template-columns: 1fr 1fr;
  /* Зазоров нет ни по одной оси: горизонтальный воздух вокруг разделителя дают
     паддинги панелей (линия стоит ровно посередине), вертикальный — margin'ы
     элементов внутри панели (см. шапку блока). */
  gap: 0;
  align-items: stretch;
}
/* Шапки у рамки нет (#470). Была (#434): <h2> «Товары дня» + подзаголовок
   «пока идёт отсчёт» — владелец 2026-09-02 снял: «просто занимает место, и
   так виден таймер». Заголовок остался только для скринридера (.xf-sr-only,
   абсолют — ячейки сетки не занимает), ряды рамки начинаются с панелей.
   .xf-day__eyebrow в диптихе не используется, его правила остаются
   одиночной витрине. */

.xf-day__item {
  min-width: 0;
  padding-right: 28px;
}
.xf-day__item + .xf-day__item {
  padding-right: 0;
  padding-left: 28px;
  border-left: 1px solid var(--line-soft);
}

/* Общая для обоих десктопных ярусов механика: ряды на рамке, панель — subgrid,
   инфо-колонка растворяется, ритм на margin'ах.
   Ряды панели: 1 — плита (на ≥1200 пустой: там плита стоит в своей колонке и
   span'ится по всем рядам), 2 — имя, 3 — наличие, 4 — цена, 5 — таймер,
   6 — кнопки, 7 — карандаш админа. Номера у обоих ярусов одни и те же, а
   колонка инфо-части адресуется с конца (-2 / -1): на ≥1200 это вторая колонка
   из двух, на 768–1199 — единственная. */
@media (min-width: 768px) {
  /* [#470] Ряда шапки нет: семь рядов панели — это и есть все ряды рамки,
     панель садится на них с первого. */
  .xf-day--duo { grid-template-rows: repeat(7, auto); }
  .xf-day--duo .xf-day__item {
    grid-row: 1 / -1;
    display: grid;
    grid-template-rows: subgrid;
    row-gap: 0;
    /* start, а не дефолтный stretch: ячейка ряда шириной во всю колонку
       растянула бы чип таймера в полосу-«поле» — в одиночной витрине он
       обжимает свой текст (там инфо-колонка была flex с align-items:
       flex-start). Плите растяжка нужна, ей возвращаем stretch. */
    justify-items: start;
  }
  .xf-day--duo .xf-day__info { display: contents; }
  .xf-day--duo .xf-day__media          { grid-column: 1 / 2; justify-self: stretch; }
  /* justify-self отменяет justify-items: start панели: полоса — единственная
     ячейка, которой нужна вся ширина, остальные жмутся к контенту. */
  .xf-day--duo .xf-day__timer          { grid-column: 1 / -1;  grid-row: 1; margin-bottom: 14px; justify-self: stretch; }
  .xf-day--duo .xf-day__name           { grid-column: -2 / -1; grid-row: 3; }
  .xf-day--duo .xf-product-card__stock { grid-column: -2 / -1; grid-row: 4; margin-top: 14px; }
  .xf-day--duo .xf-day-price           { grid-column: -2 / -1; grid-row: 5; margin-top: 14px; }
  .xf-day--duo .xf-day__actions        { grid-column: -2 / -1; grid-row: 6; margin-top: 16px; }
  .xf-day--duo .xf-day__admin          { grid-column: -2 / -1; grid-row: 7; margin-top: 14px; }
}

/* Браузер без subgrid (Chrome до 117, Safari до 16): рядов у панели нет, и
   она возвращается к колонке/строке на flex — пара перестаёт выравниваться
   построчно, но линия кнопок совпадает, как у карточек листинга
   (.xf-product-card__cart-btn выше — тот же приём margin-top: auto).
   padding-top держит минимальный зазор, когда свободного места нет и auto-
   margin схлопывается в ноль. */
@supports not (grid-template-rows: subgrid) {
  @media (min-width: 768px) {
    .xf-day--duo { grid-template-rows: none; }
    .xf-day--duo .xf-day__item { grid-row: auto; display: flex; flex-direction: column; }
    .xf-day--duo .xf-day__info {
      display: flex;
      flex-direction: column;
      align-items: flex-start;
      flex: 1 1 auto;
      min-width: 0;
    }
    .xf-day--duo .xf-day__actions { margin-top: auto; padding-top: 16px; }
  }
  @media (min-width: 1200px) {
    .xf-day--duo .xf-day__item { flex-direction: row; align-items: stretch; gap: 22px; }
    /* min-height: 100% из сеточного правила ниже здесь остаётся в силе и
       работает на нас: плита берёт высоту панели, прямоугольник сохраняется
       и без subgrid. */
    .xf-day--duo .xf-day__media { flex: 0 0 42%; min-width: 220px; align-self: center; }
  }
}

/* Пустая плита в диптихе не тянется до одиночных 200px — высоту задаёт
   пропорция. */
.xf-day--duo .xf-day__media--empty { min-height: 0; }
/* Фото на плите крупнее, чем в одиночной витрине (там 88cq): предмет — козырь
   секции, а в диптихе плита и так вдвое меньше. */
.xf-day--duo .xf-day__photo {
  max-width: 94cqi;
  max-height: 94cqb;
}

/* max-width снят (у одиночной он 640px), ритм колонки держат margin'ы
   элементов — см. шапку блока; gap здесь сложился бы с ними вдвое. */
.xf-day--duo .xf-day__info {
  max-width: none;
  gap: 0;
}
/* Карандаш админа — per-элемент, поэтому он в панели, а не в шапке рамки.
   Покупателю xf_admin_edit_pencil отдаёт пустоту, и обёртка в разметке тогда
   не печатается — лишней строки в сетке нет. */
.xf-day--duo .xf-day__admin { display: flex; }
/* В сетке место карандашу назначает grid-row, а в flex-состояниях
   (count=1, фолбэк без subgrid) он бы всплыл первым — он первый в разметке
   панели. order его возвращает вниз; в grid свойство игнорируется. */
.xf-day--duo .xf-day__admin { order: 1; }
/* Имя переносится целиком, без clamp: у видеокарт модель уезжает в третье
   слово, а обрезать её значит спрятать то, за чем покупатель пришёл. */
.xf-day--duo .xf-day__name {
  font-size: 20px;
  line-height: 1.25;
  letter-spacing: -0.3px;
  /* [#434 polish] Строки имени выровнены по длине: без balance модель рвётся
     по дефису внутри артикула («NE75070019K9-» / «GB2050S»), а последняя
     строка остаётся огрызком. Число строк то же, браузер без поддержки
     (Safari < 17.5) просто переносит как раньше. */
  text-wrap: balance;
}
.xf-day--duo .xf-day-price__val { font-size: 30px; }
/* Зачёркнутая — на пункт крупнее одиночных 14px: в диптихе она стоит рядом с
   30px-ценой и на 14px читалась как сноска. Ярлыка процентов нет и не будет
   (docs/price-strike.md). */
.xf-day--duo .xf-day-price__was { font-size: 15px; }
/* Чип отсчёта — «деловая шапка» панели, а не пятый элемент в столбце. В
   одиночной витрине он тональный (surface-2) и это работает: там он один на
   тонированной рамке. В диптихе тональный чип на тонированной рамке слился с
   фоном, а белая «бумага» с серой подписью читалась строкой метаданных
   (владелец 2026-09-02: «не считывается, что это акция»). Теперь полоса —
   объявление об акции: индиго-подтон 7% (fallback --accent-dim), плашка
   «Акция» слева, цифры крупнее подписи. Линии у полосы нет (distill): рамка
   блока → рамка полосы → белая плита были тремя вложенными карточками,
   полосу держит один подтон. Ни красного, ни оранжевого, ни пульсации: срок
   сообщается, а не давит (PRODUCT.md, честный тон). */
.xf-day--duo .xf-day__timer {
  background: var(--accent-dim);
  background: color-mix(in srgb, var(--accent) 7%, var(--surface));
  border: 1px solid transparent;
  border-radius: 12px;
  /* 9px + 24px-цифры + рамка ≈ 48 по высоте — полоса заметна, но не спорит
     с фото по весу. */
  padding: 9px 14px;
  gap: 10px;
}
/* Три вещи в полосе — две группы. Плашка одна слева: она называет («Акция»,
   а по галке владельца — «Минимальная цена в Беларуси», #519; плашка одна и
   та же, меняется только текст). «Осталось» + цифры — одна группа справа:
   она считает. Отжимает её к правому краю margin-left: auto у группы
   отсчёта (.xf-day__countdown с #521, до него — у подписи; до #519 это
   делал зеркальный margin-right: auto у плашки —
   результат тот же, но с длинной подписью, когда отсчёт уходит второй
   строкой, auto нужен именно на нём); space-between на самой полосе не
   годится — до #521 подпись и цифры разъехались бы друг от друга, а с
   группой она, одна во второй строке, встала бы влево. Подпись
   стояла вплотную к плашке и в том же индиго — две надписи одного веса
   читались одной фразой «Акция Осталось» (владелец 2026-09-03); подпись
   осталась нейтральной text-2 из базового правила, индиго в полосе одно
   слово. Плашка — тег, не кнопка (Pill-Is-Not-Button): белая «бумага» с
   индиго-линией и индиго-текстом, НЕ заливка — сплошная весила столько же,
   сколько CTA «В корзину» (владелец: «перебивает на себя внимание»,
   Single-Primary). Индиго-маркеров в панели два: плашка + CTA, как было
   (иконка часов + CTA) — One-Voice Indigo не сдвинут. */
/* [#484] 12 → 14px (Илья 2026-09-03: «плашка, „Осталось“ и д/ч/м слишком
   мелкие, явно больше»). Остальной вид плашки — в базовом .xf-day__tag выше:
   в диптихе и одиночной витрине она одна и та же, кроме кегля. */
.xf-day--duo .xf-day__tag { font-size: 14px; }
/* [#484] Подпись 13 → 15px и единицы 11 → 14px: при цифрах 24px они терялись,
   полоса читалась «одни цифры». Цифры не трогаем — иерархия та же. */
/* [#519] margin-left: auto — тот же отжим группы «Осталось + цифры» вправо,
   что раньше давал margin-right: auto у плашки «Акция» (см. комментарий
   выше). Работает одинаково и с подписью, и без неё, и когда подпись ушла
   второй строкой: в первой строке остаются плашка слева и отсчёт справа.
   [#521] Отжим висит на самой группе .xf-day__countdown, а не на подписи:
   ушла группа второй строкой — она и там прижата вправо, как у #519. */
.xf-day--duo .xf-day__timer-label { font-size: 15px; }
.xf-day--duo .xf-day__countdown { margin-left: auto; }
.xf-day--duo .xf-day__sep { font-size: 14px; }
/* [#479→#486] Цифры отсчёта цветные — владелец хотел «таймер заметнее».
   Сначала индиго (#479, 2026-09-03), затем по сравнению двух вариантов на
   /dev/ выбран зелёный (#486, 2026-09-04): тот же --success, что у
   «В наличии» — другого зелёного в системе нет; два зелёных в карточке
   (наличие + отсчёт) — осознанное решение владельца. Только цвет цифр:
   единицы «д/ч/м» и подпись «Осталось» нейтральные, размер не растёт. */
.xf-day--duo .xf-day__num { font-size: 24px; color: var(--success); }
/* [#489] Два решения владельца 2026-09-07 (обкатано на /dev/, гейт снят).
   1. CTA «В корзину» — тот же --success, что у цифр таймера: зелёный в блоке
   получает второй смысл (кроме «В наличии») — осознанное исключение из
   Single-Hue-per-Meaning по просьбе владельца, только внутри .xf-day--duo;
   остальные кнопки сайта — индиго. Glow базового .xf-btn перекрашен под ту же
   кнопку. Только светлая: в тёмной кнопка — обычная фиолетовая клавиша
   (блок «Товар дня в тёмной теме» ниже). Состояние .is-added не трогаем: оно
   нейтральное (см. .xf-day__cart.is-added выше). */
.xf-day--duo .xf-day__cart:not(.is-added) {
  background: var(--success);
  box-shadow: 0 6px 20px -8px var(--success), 0 0 0 1px oklch(1 0 0 / 0.05) inset;
}
/* 2. Разделитель панелей = обводка рамки: тот же 1.5px и тот же индиго 45%
   (см. .xf-day border выше; без color-mix — fallback line-soft остаётся от
   базового правила .xf-day__item + .xf-day__item). Мобильная раскладка
   (<768) переворачивает линию в border-top — см. мобильное правило ниже. */
@media (min-width: 768px) {
  .xf-day--duo .xf-day__item + .xf-day__item {
    border-left-width: 1.5px;
    border-left-color: color-mix(in srgb, var(--accent) 45%, transparent);
  }
}
/* ≥1200: панель горизонтальная — плита в первой колонке по рядам 3–6;
   инфо-часть во второй. Плита квадратная (#470): широкоформатным фото
   (оперативка, платы) достаются белые поля сверху и снизу, зато сам кадр
   шире, чем был на 16/11, а вертикальные (приставка) растут в полный рост.
   Плита не уже 240px, инфо-колонке остаётся ≥263px на 1200 — единственной
   кнопке хватает. */
@media (min-width: 1200px) {
  /* [#470 layout] Лишняя высота квадратной плиты над текстовой колонкой шла
     в ряды 3–6 поровну: ряд наличия высотой 29px под 14px-метку, кнопка
     отцентрована в своём ряду и её низ не совпадал с низом плиты на 7px.
     Остаток забирает один ряд — кнопки: имя, наличие и цена стоят плотной
     группой сверху, кнопка садится вниз, её низ = низ плиты. */
  .xf-day--duo { grid-template-rows: auto auto auto auto auto 1fr auto; }
  .xf-day--duo .xf-day__actions { align-self: end; }
  /* [#470] Плита: колонка 46% (было 42) с потолком 340px, квадрат вместо
     16/11 — на 1440 это 284×284 против прежних 259×225, фото крупнее по
     обеим осям (владелец: «раз места больше — увеличить фото»). Текстовой
     колонке на 1200 остаётся ≥263px, кнопке нужно меньше. */
  .xf-day--duo .xf-day__item {
    grid-template-columns: minmax(240px, min(46%, 340px)) minmax(0, 1fr);
    column-gap: 22px;
  }
  .xf-day--duo .xf-day__media {
    /* Плита занимает ровно ряды 3–6: верх плиты = верх имени, низ = низ
       кнопок, и панель складывается в прямоугольник [полоса] / [плита |
       текст]. Раньше плита тянулась по всем рядам и висела по центру между
       баром и кнопками — её края не совпадали ни с чем (владелец,
       2026-08-22: «layout странный, растянутый»).
       align-self: start + min-height: 100% вместо stretch: проценты меряются
       от площади рядов, поэтому при длинном тексте плита растягивается до
       низа кнопок, а при коротком (нет наличия, имя в строку) не проваливается
       ниже своих 16/11 — stretch отобрал бы у неё пропорцию. */
    grid-row: 3 / 7;
    align-self: start;
    min-height: 100%;
    aspect-ratio: 1 / 1;
  }
  /* Осталась одна панель — истёкшую снял app.js → initDayOffer и уменьшил
     data-xf-day-count. Панель занимает всю рамку и возвращает геометрию
     одиночной витрины: раз товар остался один, он и должен выглядеть как
     «Товар дня» (заголовок JS тоже переводит в единственное число), а не как
     половина диптиха, растянутая вдвое. Subgrid здесь не нужен и снят —
     выравнивать не с кем, а flex возвращает вертикальное центрирование
     инфо-колонки, как в одиночной витрине. */
  .xf-day--duo[data-xf-day-count="1"] { grid-template-columns: 1fr; }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__item {
    grid-template-rows: none;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 44px;
    padding-left: 0;
    padding-right: 0;
    border-left: 0;
  }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__info {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
    max-width: 640px;
  }
  /* Панель снова flex-строка: сеточные align-self/min-height плиты снимаем,
     иначе она прилипнет к верху и потащит за собой процентную высоту. */
  .xf-day--duo[data-xf-day-count="1"] .xf-day__media {
    flex: 0 0 46%;
    max-width: 600px;
    align-self: center;
    min-height: 0;
  }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__name { font-size: 26px; letter-spacing: -0.4px; }
  .xf-day--duo[data-xf-day-count="1"] .xf-day-price__val { font-size: 34px; }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__timer { padding: 10px 16px; gap: 12px; }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__num { font-size: 20px; }
}

/* [#519] Полоса с длинной подписью вместо «Акции»: где она не встаёт в одну
   строку с отсчётом, полоса становится сеткой в два ряда — плашка первым
   рядом слева, «Осталось» + цифры вторым рядом справа, ЦЕЛИКОМ.
   Сетка, а не перенос flex'ом: flex при нехватке места уносит вниз ПОСЛЕДНИЙ
   элемент строки, то есть цифры, — «Осталось» осталось бы наверху при плашке,
   оторванное от своих цифр. Сетка ставит границу там, где нужно нам, и плашка
   при этом остаётся по своему тексту (flex-basis: 100% растянул бы её в
   полосу-«поле» — так выглядела первая редакция #519).
   [#521] С группой .xf-day__countdown flex уносил бы вниз уже всю группу,
   но сетка остаётся: она кладёт два ряда заранее, с запасом (порог ниже),
   а не впритык, и прижимает группу вправо и в одиночной витрине, где
   отжима margin-left: auto нет.
   Порог 1280 взят замером на прод-главной (2026-09-15): полоса в половине
   диптиха — это ширина окна / 2 − 102 (1200 → 498px, 1280 → 538, 1440 → 618),
   а содержимое в одну строку просит 480px при двузначных днях и 494px при
   трёхзначных (акция на год — ACTIVE_TO ставит владелец, верхней границы у
   срока нет). На 1200 в самом тяжёлом случае остаётся 2px — не запас; 1280
   даёт 44px, этого хватает и на запасной шрифт, если Onest не загрузился.
   Ошибиться безопаснее в сторону двух рядов: лишняя строка — мелочь, а
   сорвавшиеся вниз цифры — сломанная полоса.
   Правило общее для обеих раскладок: в одиночной витрине плашка мельче и
   полоса обжимает свой текст, но перенос ей нужен тот же. */
@media (max-width: 1279px) {
  .xf-day__timer--claim {
    display: grid;
    /* Первый трек — распорка (1fr): она съедает свободное место и прижимает
       «Осталось» + цифры к правому краю, ту же работу в строке делает
       margin-left: auto. Плашка кладётся поверх всех треков и держится левого
       края — justify-content: end у полосы не годится, он утащил бы вправо и
       её (замер 2026-09-15: плашка отъезжала на 31–159px от края).
       [#521] «Осталось» + цифры теперь один узел .xf-day__countdown — ему
       один трек; зазор между ними держит сама группа (gap: inherit). */
    grid-template-columns: 1fr auto;
  }
  .xf-day__timer--claim .xf-day__tag {
    grid-column: 1 / -1;
    justify-self: start;
  }
  .xf-day__timer--claim .xf-day__countdown { grid-column: 2; }
}

@media (min-width: 768px) and (max-width: 1199px) {
  .xf-day--duo .xf-day__item {
    grid-template-columns: minmax(0, 1fr);
    padding-right: 24px;
  }
  .xf-day--duo .xf-day__item + .xf-day__item { padding-right: 0; padding-left: 24px; }
  .xf-day--duo .xf-day__media {
    grid-row: 2 / 3;
    width: 100%;
    min-width: 0;
    aspect-ratio: 16 / 10;
  }
  /* Плита в своём ряду, воздух под ней — её собственный margin-bottom: имя
     тут не всегда следующее (между ними может стоять чип отсчёта). */
  .xf-day--duo .xf-day__media { margin-bottom: 18px; }
  /* Осталась одна панель — одна колонка и прежняя горизонталь: вертикальная
     панель на всю рамку дала бы фото-баннер в пол-экрана. Subgrid снят по той
     же причине, что и на ≥1200. */
  .xf-day--duo[data-xf-day-count="1"] { grid-template-columns: 1fr; }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__item {
    grid-template-rows: none;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 28px;
    padding: 0;
    border-left: 0;
  }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__info {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
  }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__media {
    flex: 0 0 40%;
    width: auto;
    max-width: 420px;
    aspect-ratio: 16 / 11;
    align-self: center;
  }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__media { margin-bottom: 0; }
}

/* Узкий планшет: на 768 колонка панели даёт 315px, и в полосе отсчёта
   24px-цифры вместе с подписью в строку не помещаются — чип переносил
   отсчёт на вторую строку и вырастал вдвое (замер в браузере). До 900
   цифры 20px: с ними короткий отсчёт («5д 4ч») встаёт в одну строку,
   подпись не сокращается.
   [#521] Длинному отсчёту и 20px не хватает: у полосы на 768 внутри ~260px,
   а строка просит 265px уже при «15д 4ч» или «5д 14ч» и до ~290px при
   «120д 14ч». Дожимать кегль дальше не стали — цифры укрупняли по просьбе
   владельца (#484), а часы двузначные большую часть суток, так что и 18px
   не спасли бы. Вместо этого «Осталось» + цифры — один узел
   (.xf-day__countdown выше): не влезли — уходят второй строкой вместе,
   прижатые вправо, как в полосе #519. */
@media (min-width: 768px) and (max-width: 899px) {
  .xf-day--duo .xf-day__num { font-size: 20px; }
}

/* Мобила. .xf-day__info растворяется (display: contents), чтобы её дети стали
   ячейками сетки панели: иначе таймер и кнопки остались бы заперты в узкой
   правой колонке рядом с плитой. Ряды расставлены явно — состав колонки
   плавает (наличие и таймер бывают пустыми), на auto-раскладку тут полагаться
   нельзя. Панели здесь стоят друг под другом, выравнивать их построчно
   незачем — subgrid снимаем, у каждой панели своя сетка. Кнопки 50/50 и
   nowrap приходят из одиночного мобильного блока выше — он не завязан на
   модификатор и работает на обе раскладки. */
@media (max-width: 767px) {
  /* Одна колонка; row-gap рамки и padding-top второй панели равны, поэтому
     линия-разделитель идёт ровно посередине зазора. */
  .xf-day--duo {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    gap: 18px 0;
  }
  .xf-day--duo .xf-day__item {
    grid-row: auto;
    grid-template-rows: none;
    display: grid;
    grid-template-columns: 112px minmax(0, 1fr);
    column-gap: 14px;
    /* row-gap 0 — ритм на margin'ах элементов, как и на десктопе: чип
       отсчёта бывает не у каждого товара, и пустой ряд с gap оставлял бы
       висящий отступ над плитой. */
    row-gap: 0;
    align-items: start;
    padding-right: 0;
  }
  .xf-day--duo .xf-day__item + .xf-day__item {
    padding-left: 0;
    padding-top: 18px;
    border-left: 0;
    /* [#489] Та же линия, что рамка (см. блок выше): 1.5px индиго 45%. */
    border-top: 1px solid var(--line-soft);
    border-top: 1.5px solid color-mix(in srgb, var(--accent) 45%, transparent);
  }
  .xf-day--duo .xf-day__info { display: contents; }
  .xf-day--duo .xf-day__media {
    grid-column: 1;
    grid-row: 2 / span 3;
    margin-bottom: 0;
    width: 112px;
    min-width: 0;
    aspect-ratio: 1;
    /* start, а не растяжка на три ряда: иначе высоту задал бы трек, а не
       пропорция, и квадрат поехал бы. */
    align-self: start;
  }
  /* Чип отсчёта — первая строка панели во всю её ширину, дальше «плита |
     имя, наличие, цена», внизу кнопки. Тот же порядок чтения, что на
     десктопе: срок → товар → цена → действие. */
  .xf-day--duo .xf-day__timer          { grid-column: 1 / -1; grid-row: 1; margin-bottom: 12px; padding: 10px 12px; }
  .xf-day--duo .xf-day__num            { font-size: 20px; }
  .xf-day--duo .xf-day__name           { grid-column: 2; grid-row: 2; font-size: 16px; line-height: 1.3; }
  .xf-day--duo .xf-product-card__stock { grid-column: 2; grid-row: 3; margin-top: 8px; }
  .xf-day--duo .xf-day-price           { grid-column: 2; grid-row: 4; margin-top: 8px; }
  .xf-day--duo .xf-day-price__val      { font-size: 22px; }
  .xf-day--duo .xf-day__actions        { grid-column: 1 / -1; grid-row: 5; margin-top: 14px; }
  .xf-day--duo .xf-day__admin          { grid-column: 1 / -1; grid-row: 6; margin-top: 10px; }
  /* Плашка гарантии на 112-й плите не рисуется. Её работа (#420) — перекрыть
     ватермарку catalog.onliner.by, а та занимает ~8%×2% КАДРА: на плите 112px
     это примерно 8×2 пикселя, которые и так не прочесть. Закрывать нечего, а
     плашка при этом ложилась 9-м кеглем поперёк двух третей фото — на мобиле
     фото единственный опознавательный знак товара. В одиночной раскладке и в
     состоянии «остался один товар» плита во всю ширину, кадр крупный,
     ватермарка читаема — там плашка остаётся. */
  .xf-day--duo .xf-product-card__guarantee { display: none; }
  /* Осталась одна панель — плита во всю ширину и инфо колонкой под ней, то
     есть геометрия одиночной мобильной витрины. */
  .xf-day--duo[data-xf-day-count="1"] .xf-day__item {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 16px;
    padding-top: 0;
  }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__info {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    min-width: 0;
    gap: 16px;
  }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__media {
    width: 100%;
    aspect-ratio: 16 / 10;
    align-self: stretch;
    margin-bottom: 0;
  }
  /* Панель снова flex-колонка, ритм держит её gap — свои margin'ы сеточных
     рядов гасим, иначе сложатся с ним. */
  .xf-day--duo[data-xf-day-count="1"] .xf-day__timer { margin-bottom: 0; }
  .xf-day--duo[data-xf-day-count="1"] .xf-product-card__stock,
  .xf-day--duo[data-xf-day-count="1"] .xf-day-price,
  .xf-day--duo[data-xf-day-count="1"] .xf-day__actions,
  .xf-day--duo[data-xf-day-count="1"] .xf-day__admin { margin-top: 0; }
  .xf-day--duo[data-xf-day-count="1"] .xf-day__name { font-size: 26px; line-height: 1.2; letter-spacing: -0.4px; }
  .xf-day--duo[data-xf-day-count="1"] .xf-day-price__val { font-size: 26px; }
  .xf-day--duo[data-xf-day-count="1"] .xf-product-card__guarantee { display: block; }
}

/* ─── Товар дня в тёмной теме ─────────────────────────────────
   Нейтральная рамка с кромкой, без индиго-обводки и подтона, без света за
   контуром. Плашка «Акция» — залитая краска действия; полоса таймера —
   прозрачная с рамкой. Цифры отсчёта у владельца зелёные (#486/#489) — в
   тёмной светятся, как значения фактов вывески; кнопка «В корзину» —
   обычная фиолетовая клавиша, а не зелёная. Перебивает базовые правила
   выше специфичностью (0-2-1 … 0-4-1), в том числе мобильные. */
html[data-theme="dark"] .xf-day {
  border: 1px solid oklch(1 0 0 / 0.10);
  background: var(--surface);
  box-shadow: var(--nx-edge);
}
html[data-theme="dark"] .xf-day--duo .xf-day__item + .xf-day__item { border-color: var(--line-soft); }
html[data-theme="dark"] .xf-day__tag {
  border-color: transparent;
  background: var(--accent);
  color: #fff;
}
html[data-theme="dark"] .xf-day__timer {
  background: transparent;
  border-color: var(--line);
}
html[data-theme="dark"] .xf-day--duo .xf-day__num { color: var(--nx-core); text-shadow: var(--nx-glow); }
html[data-theme="dark"] .xf-day--duo .xf-day__cart:not(.is-added) {
  background: var(--accent);
  color: #fff;
  box-shadow: var(--nx-key);
}

/* ─── ProductStrip ────────────────────────────────────────── */
.xf-products__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 28px;
  gap: 20px;
  flex-wrap: wrap;
}
/* Rail freshness caption — sits opposite title, mono small text-3.
   Anchors «Только что завезли» / «Что сейчас берут» / etc. claims with
   concrete date so freshness isn't a vague promise. Applied via JBM mono
   to read as data, not narrative. */
.xf-products__freshness {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
  letter-spacing: 0.3px;
}
.xf-products__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
}
.xf-product-card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  overflow: hidden;
  transition: border-color .2s, transform .2s, box-shadow .2s;
  color: var(--text);
  display: flex;
  flex-direction: column;
  container-type: inline-size;   /* lets .xf-price__val size to the card width (cqi) so the price never overflows/clips on narrow grids */
}
@media (hover: hover) {
  .xf-product-card:hover {
  border-color: var(--accent-line);
  transform: translateY(calc(-2px * var(--xf-motion, 1)));
  box-shadow: var(--shadow-card);
}
}
/* Тёмная тема (везде, где есть карточка, в том числе на главной): поверхность
   ловит свет верхней кромкой; наведение — рамка акцента (правило выше) и свет,
   упавший под карточку, вместо тени. Фото — лайтбокс (.xf-art--photo выше),
   вставлен в рамку карточки с отступом. */
html[data-theme="dark"] .xf-product-card { box-shadow: var(--nx-edge); }
@media (hover: hover) {
  html[data-theme="dark"] .xf-product-card:hover {
    box-shadow: var(--nx-edge), 0 18px 40px -22px oklch(0.56 0.25 300 / 0.8);
  }
}
html[data-theme="dark"] .xf-product-card__image {
  margin: 8px 8px 0;
  border-radius: 10px;
}
/* Overlay-link covers the whole card for click-through to detail page.
   Cart button sits above (z-index:2) and intercepts its own clicks. */
.xf-product-card__link {
  position: absolute;
  inset: 0;
  z-index: 1;
  text-decoration: none;
  cursor: pointer;
}
.xf-product-card__link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: 16px;
}
/* Фото-зона и стикеры декоративны для кликов: img (z:1, позже __link в DOM →
   рисуется выше) и плашки z:2 перехватывали клик — карточка открывалась только
   ВОКРУГ картинки (баг Вадима 2026-06-10). Пропускаем клик сквозь них к
   __link-оверлею; интерактив не задет — like-icons и cart-btn сиблинги .xf-art
   (pointer-events наследуется только вниз). PDP-галерею (.xf-product-gallery,
   zoom) не трогаем — скоуп строго .xf-product-card. */
.xf-product-card .xf-art,
.xf-product-card__stickers {
  pointer-events: none;
}
.xf-product-card__image {
  position: relative;
  aspect-ratio: 1;
  overflow: hidden;
}
.xf-product-card__stickers {
  position: absolute;
  top: 12px;
  left: 12px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  z-index: 2;
  pointer-events: none;
}
/* [#304] Плашка тихая ПО УМОЛЧАНИЮ. Раньше «Хит», «Новинка» и «Рекомендуем»
   приезжали из одного справочника PROPERTY_HIT и все красились в accent —
   три индиго-плашки кричали в один голос и ни одна не выигрывала. Теперь
   accent выдаётся явно (--hit, только подпись «Хит»), а всё остальное —
   включая значение, которое админ заведёт завтра, — остаётся меткой.
   Геометрия/шрифт/трекинг у всех вариантов одинаковые: семья читается как
   одна система, в которой один участник повышен. Отличие только в заливке.
   Рамка --line, а не --line-soft из Pill-вокабуляра DESIGN.md: плашка лежит
   поверх произвольной фотографии, полупрозрачная рамка слилась бы с
   собственной непрозрачной заливкой плашки и край бы пропал. */
.xf-product-card__sticker {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--surface-2);
  color: var(--text-2);
  border: 1px solid var(--line);
  padding: 4px 9px;
  border-radius: 999px;
  line-height: 1;
}
.xf-product-card__sticker--hit {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}
.xf-product-card__sticker--sale {
  background: oklch(0.70 0.18 32);
  color: #fff;
  border-color: transparent;
}
.xf-product-card__guarantee {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 2;
  pointer-events: none;
  box-sizing: border-box;
  min-width: 128px;
  padding: 6px 12px;
  text-align: center;
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.005em;
  color: oklch(0.96 0.005 285);
  background: oklch(0.30 0.04 268);
  border-radius: 8px 0 0 0;
  line-height: 1.2;
  white-space: nowrap;
}
.xf-product-card__like-icons {
  position: absolute;
  top: 12px;
  right: 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  z-index: 2;
}
.xf-product-card__like-btn {
  width: 32px;
  height: 32px;
  border-radius: 10px;
  background: var(--surface);
  color: var(--text-2);
  border: 1px solid var(--line-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .2s, color .2s, border-color .2s, transform .12s, opacity .2s;
  cursor: pointer;
  padding: 0;
  font: inherit;
  opacity: 0.75;
}
@media (hover: hover) {
  .xf-product-card:hover .xf-product-card__like-btn { opacity: 1; }
}
@media (hover: hover) {
  .xf-product-card__like-btn:hover {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
  transform: scale(calc(1 + 0.05 * var(--xf-motion, 1)));
}
}
.xf-product-card__like-btn:active { transform: scale(calc(1 - 0.08 * var(--xf-motion, 1))); }
.xf-product-card__like-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.xf-product-card__like-btn.is-busy { opacity: .5; pointer-events: none; }
.xf-product-card__like-btn.is-active {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
  opacity: 1;
}
.xf-product-card__like-btn--wish.is-active {
  background: #e11d48;
}
@media (hover: hover) {
  /* Wishlist: red for the emotional "want it" anchor; compare keeps brand-accent. */
.xf-product-card__like-btn--wish:hover {
  background: #e11d48;
}
}
.xf-product-card__like-btn--wish.is-active svg { fill: currentColor; }
/* Тёмная тема: кнопки избранного и сравнения на плите — полупрозрачные, того
   же света, что лайтбокс: сплошные белые квадраты спорили с притушенной
   плитой. 0-2-1 выше :hover и .is-active (0-2-0): фон и рамка в тёмной от
   состояния не меняются, «в избранном» читается залитым сердцем. */
html[data-theme="dark"] .xf-product-card__like-btn {
  background: oklch(1 0 0 / 0.5);
  border-color: oklch(0.2 0.02 292 / 0.10);
  color: oklch(0.30 0.02 292);
}
/* [#306] Флекс-колонка + flex:1 теперь на всех ширинах (карточка сама уже
   flex-column): так .xf-product-card__foot может растянуться, а кнопка-бар
   прижаться к низу — панели «В корзину» встают на одну линию по всему ряду
   независимо от того, у кого есть зачёркнутая цена или строка рассрочки.
   Раньше это жило только в мобильном блоке. */
.xf-product-card__body {
  padding: 16px 18px 18px;
  display: flex;
  flex-direction: column;
  flex: 1;
}
.xf-product-card__stock-row {
  margin-bottom: 10px;
}
.xf-product-card__stock {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
.xf-product-card__stock-dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: currentColor;
  flex: 0 0 auto;
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 18%, transparent);
}
.xf-product-card__stock--in   { color: var(--success); }
.xf-product-card__stock--none { color: var(--warning); }
/* «Под заказ» (Товар дня, #302): тихий тон как PDP .is-out — это не тревога,
   а нормальное состояние; warning-тон зарезервирован «Нет в наличии». */
.xf-product-card__stock--preorder { color: var(--text-3); }
.xf-product-card__article {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--text-3);
  letter-spacing: 0;
  margin-top: 2px;
}
/* [#340] Резерв под имя товара: min-height == max-height == clamp — это НЕ
   косметика, а то, что держит цены на одной линии по всему ряду (карточки в
   ряду и так равны по высоте: grid stretch + margin-top:auto на кнопке-баре,
   но цена стоит сразу под именем и без фиксированного резерва «плавает»).
   Менять три числа только вместе.

   Десктоп остаётся на 3 строках — замер на 96 реальных именах каталога
   (видеокарты / ноутбуки / процессоры / охлаждение, x-core.by 2026-07-30)
   против настоящих ширин карточки:
     340px (листинг раздела, 3-col при sidebar 280) — 0 обрезанных, 4-я строка
       не нужна НИ ОДНОМУ имени;
     329px (сетка «Что сейчас берут», 4-col)        — 0 обрезанных;
     244–264px (поиск/избранное, auto-fill 220)     — 6% обрезанных.
   4-я строка добавила бы +19px пустоты 100% карточек раздела и главной ради
   6% на вторичных поверхностях — поэтому её здесь нет. Телефон — другое дело,
   см. мобильный блок в конце файла. */
/* Имя — над оверлей-ссылкой (z:1), иначе его не выделить и не скопировать
   (просьба Вадима 2026-10-01). Клик по имени ведёт на товар через app.js
   (initCardTitleLinks) — если человек не выделял текст. */
.xf-product-card__title {
  position: relative;
  z-index: 2;
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text);
  min-height: calc(1.4em * 3);
  max-height: calc(1.4em * 3);
  margin-bottom: 14px;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* [#315] Ключевая характеристика раздела — строка под именем (разметку
   ставит xf_render_product_card). Ровно одна строка: высота жёстко
   зафиксирована, как у имени выше (#340), иначе цены в ряду разъедутся у
   товара с незаполненными свойствами — а пустая строка тут штатна и обязана
   занимать ровно столько же.
   margin-top отрицательный не «прижимает красиво», а съедает часть 14px-
   отступа имени: строка добавляет карточке ~17px, а не ~30px. Схлопывания
   маргинов в флекс-колонке не бывает, так что 14 − 8 = 6px — это и есть
   итоговый зазор «имя → характеристика».
   nowrap + ellipsis: длинная подпись («полностью беспроводные») обрезается,
   но НЕ переносится — перенос сломал бы ту самую высоту.

   Шрифт — Onest, не mono, хотя половина значений числовая. The Mono-as-Data
   Rule запрещает mono для не-данных, а слот тут один на все разделы: у
   мониторов в нём «27" · 180 Гц», у клавиатур «игровая · механическая».
   Разный шрифт в одном и том же месте карточки по разделам читался бы как
   сбой, а не как контраст «данные / нарратив».
   Цвет --text-2, а не --text-3: у --text-3 на карточке 4.28:1 в тёмной теме —
   ниже порога 4.5 для мелкого текста (тот же разбор, что у строки рассрочки). */
.xf-product-card__spec {
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: 0.005em;
  color: var(--text-2);
  min-height: 1.35em;
  max-height: 1.35em;
  margin: -6px 0 10px;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* --off = раздела нет в словаре характеристик. В листинге раздела такие
   карточки все до одной (сетка однородна по разделу) — строку схлопываем
   целиком, чтобы «Кабели» не получили полосу пустоты. В смешанных сетках
   (главная / хаб / поиск / избранное) правило не действует: там сосед по ряду
   может быть из покрытого раздела, и пустой резерв дешевле съехавшей цены. */
.xf-secpage__grid .xf-product-card__spec--off { display: none; }
/* [#306] Подвал карточки — колонка на ВСЕХ ширинах: цена сверху, кнопка-бар
   внизу. Прежде на десктопе это был ряд «цена | иконка 36×36», и подпись туда
   не влезала: на самой узкой сетке (search / favorites — auto-fill minmax(220px))
   контент карточки ≈186px, а цена + подписанная кнопка требуют ~250px. Именно
   этот overflow и описывал прежний комментарий, из-за него подпись когда-то
   оставили только мобильной. Полноширинный бар не может переполнить строку ни
   на какой ширине, а цена в освободившемся ряду получает всю ширину карточки. */
.xf-product-card__foot {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  flex: 1;                   /* растянуться в теле-колонке, чтобы бар мог уехать вниз */
}
.xf-product-card__price-block {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* [#305] Рассрочка — нейтральная, НЕ зелёная. Зелёный на карточке значит
   ровно одно: «в наличии» (.xf-product-card__stock--in двумя строчками выше).
   Когда «от 42.5 BYN/мес» красилось тем же --success, зелёный переставал
   что-либо означать. Цвет — маркер, а не украшение (DESIGN.md §2).
   --text-2, а не --text-3: --text-3 даёт 4.38:1 в попапе рассрочки и 4.28:1
   на карточке в тёмной теме — ниже порога 4.5 для мелкого текста. --text-2
   держит 7.5–10:1 на всех трёх поверхностях. */
.xf-product-card__installment {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-2);
  letter-spacing: -0.01em;
  white-space: nowrap;
}
/* [#383] Срок плана-минимума: «от 58.1 BYN/мес · 48 мес». Тот же голос, что и
   сумма (наследует mono/--text-2) — срок объясняет число, а не соперничает с
   ним. На телефоне вся строка не влезает (карточка 2-кол. сетки даёт 131 px,
   строка со сроком — от 149 px), поэтому ниже 767px срок уходит второй
   строкой, а точка-разделитель прячется: перенос осознанный, не случайный. */
.xf-product-card__installment-sum,
.xf-product-card__installment-term { white-space: nowrap; }
/* [#306] Подписанный бар вместо безымянной иконки 36×36. Иконка сумки без
   подписи читалась как корзина-для-мусора — «положить в корзину» и «выбросить»
   рисуются почти одинаково, и только подпись снимает двусмысленность.
   Мобильный бар выглядел так и раньше; здесь он поднят в базу, а мобильный
   блок ниже остался только с размерами. */
.xf-product-card__cart-btn {
  position: relative;
  z-index: 2;
  width: 100%;
  height: 36px;
  margin-top: auto;          /* прижать бар к низу подвала → бары выравниваются по ряду */
  border-radius: 10px;
  background: var(--surface-2);
  color: var(--text-2);
  border: 1px solid var(--line-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  transition: background .2s, color .2s, border-color .2s, transform .12s;
  flex: 0 0 auto;
  cursor: pointer;
  padding: 0;
  font: inherit;
}
.xf-product-card__cart-label {
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
}
/* Двойная подпись и двойная иконка, переключаемые классом .is-added (его ставит
   app.js → applyCartBtnState). Тот же приём, что у .xf-btn-buy на PDP. */
.xf-product-card__cart-icon { display: inline-flex; align-items: center; }
.xf-product-card__cart-btn:not(.is-added) .xf-product-card__cart-label--added,
.xf-product-card__cart-btn:not(.is-added) .xf-product-card__cart-icon--check,
.xf-product-card__cart-btn.is-added .xf-product-card__cart-label--idle,
.xf-product-card__cart-btn.is-added .xf-product-card__cart-icon--bag {
  display: none;
}
@media (hover: hover) {
  .xf-product-card:hover .xf-product-card__cart-btn {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}
}
/* [#306] Кнопка стала полноширинной — scale(1.05/0.95) с иконки 36×36 давал бы
   «резину» на ~10px ширины (DESIGN.md §5: без bounce, hover-lift = translateY). */
@media (hover: hover) {
  .xf-product-card__cart-btn:hover { transform: translateY(calc(-1px * var(--xf-motion, 1))); }
}
/* translateY(0) — это ПОКОЙ (возврат из hover-lift), а не негейченное движение:
   под reduced-motion hover тоже даёт 0, так что множитель тут не нужен (#330). */
.xf-product-card__cart-btn:active { transform: translateY(0); }
.xf-product-card__cart-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.xf-product-card__cart-btn.is-busy { opacity: .6; pointer-events: none; }
/* [#326] «В корзине» — нейтральный бар, не зелёный. Полноширинная зелёная
   плашка была самым крупным цветным пятном карточки и означала НЕ наличие,
   тогда как зелёный на карточке зарезервирован ровно за ним
   (.xf-product-card__stock--in, 6px точка). Один цвет — одно значение
   (DESIGN.md §2: цвет это маркер, а не украшение).
   Состояние читается тремя нецветовыми признаками: галка вместо сумки,
   подпись «В корзине», ступень поверхности --surface-2 → --surface-3 плюс
   полная линия --line вместо --line-soft и полный ink --text вместо --text-2.
   Цвет ушёл — смысл остался, и он color-blind safe (PRODUCT.md).
   Хардкод oklch(0.72 0.18 145) снят: токены, ни одного литерала. */
.xf-product-card__cart-btn.is-added {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--line);
}
/* Hover по карточке заливает кнопку индиго (правило выше, специфичность 0,3,0).
   Для .is-added это было бы враньём: клик по ней — не «добавить», а переход в
   корзину, и заливка стёрла бы единственное отличие от соседней карточки. Тут
   0,4,0 — поверхность остаётся нейтральной, а «кликабельно» говорят индиго-ink
   и индиго-линия. */
@media (hover: hover) {
  .xf-product-card:hover .xf-product-card__cart-btn.is-added {
    background: var(--surface-3);
    color: var(--accent-2);
    border-color: var(--accent-line);
  }
}
/* Тёмная тема: «В корзину» — тихая рамка (белый 32%: контур 14% на тёмной
   карточке терялся), на наведении по карточке наливается акцентом. 0-2-1 /
   0-4-1 выше состояний светлой (.is-added 0-2-0, наведение 0-4-0): в тёмной
   «В корзине» в покое та же рамка, а на наведении тоже наливается — так
   работает с #707. */
html[data-theme="dark"] .xf-product-card__cart-btn {
  border: 1px solid oklch(1 0 0 / 0.32);
  background: transparent;
  color: var(--text);
}
@media (hover: hover) {
  html[data-theme="dark"] .xf-product-card:hover .xf-product-card__cart-btn {
    border-color: transparent;
    background: var(--accent);
    color: #fff;
  }
}
/* [#306] Правила .xf-product-card__cart-btn--under-order сняты как мёртвые:
   с листинговых гейтов helpers.php рисует кнопку только при !$underOrder
   (`if (!$underOrder)` вокруг <button>), так что модификатор не выставляется
   нигде — verified: `grep -rn "under-order" local/templates/xcore_front` даёт
   только этот комментарий и пояснение у .xf-btn-buy[data-xf-preorder] в
   product.css. Недоступный товар по-прежнему объясняется меткой
   .xf-product-card__stock--none в шапке карточки, а не видом кнопки. */

.xf-secpage__sort-submit {
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  padding: 7px 14px;
  background: var(--accent);
  color: #fff;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
  margin-left: 8px;
}
.xf-secpage__sort-submit:hover { background: var(--accent-2); }
.xf-secpage__sort-submit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ─── SplitPromo ──────────────────────────────────────────── */
/* M9.2: после снятия bot-card grid схлопывается к single column. Имя
   класса оставлено («split-promo»), чтобы при появлении новых promo
   слотов их можно было добавить, и grid снова станет 2-up. */
.xf-promo {
  display: grid;
  grid-template-columns: 1fr;
  gap: 14px;
}
.xf-promo__card {
  position: relative;
  padding: 40px 44px;
  min-height: 280px;
  background: var(--surface-2);
  border: 1px solid var(--accent-line);
  border-radius: 18px;
  overflow: hidden;
  transition: border-color .2s, background .2s;
  color: var(--text);
  display: block;
}
@media (hover: hover) {
  .xf-promo__card:hover { border-color: var(--accent); }
}
/* Компьютер справа — рендер с прозрачным фоном, по картинке на тему
   (include/home/promo.php). Корпус выше карточки: низ (кожух блока питания,
   ножки) уходит за нижний край под overflow: hidden. Целиком в 280 px он
   выходил мелкой иконкой на широком баннере, крупный читается как витрина.
   Рамка — правая половина карточки: картинка вписывается в неё (contain) и
   прижата к правому верхнему углу, так что на узкой карточке компьютер
   уменьшается, а не наезжает на заголовок. Тёмную копию прячем здесь,
   светлую — в блоке тёмной темы ниже. */
.xf-promo__art-wrap {
  position: absolute;
  top: 16px;
  left: 52%;
  right: clamp(20px, 7%, 120px);
  height: calc(100% + 56px);
  transition: transform .3s;
}
@media (hover: hover) {
  .xf-promo__card:hover .xf-promo__art-wrap { transform: translateY(calc(-6px * var(--xf-motion, 1))); }
}
.xf-promo__pc {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: right top;
}
.xf-promo__pc--dark { display: none; }
.xf-promo__body {
  position: relative;
  max-width: 440px;
}
.xf-promo__eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 2px;
  color: var(--accent-2);
  margin-bottom: 12px;
  text-transform: uppercase;
}
.xf-promo__title {
  margin: 0 0 14px;
  font-size: 36px;
  font-weight: 600;
  letter-spacing: -1.2px;
  line-height: 1.05;
}
.xf-promo__text {
  margin: 0 0 28px;
  font-size: 15px;
  color: var(--text-2);
  line-height: 1.5;
  max-width: 380px;
}
/* Планшет: заголовок в одну строку (~440 px) не оставлял компьютеру места —
   при 768 px они наезжали друг на друга. Текст сжимается в левую половину,
   заголовок ломается ровно на две строки, рамка компьютера сдвигается правее. */
@media (min-width: 768px) and (max-width: 1023px) {
  .xf-promo__body { max-width: 50%; }
  .xf-promo__title { text-wrap: balance; }
  .xf-promo__art-wrap { left: 56%; }
}
/* Тёмная тема: светящаяся плата. Плата — вектор в стиле знака X
   (images/neon/promo-pcb-1.svg, генератор scripts/neon-promo-pcb.py; 32 КБ,
   резкая на любом экране). Прежняя ИИ-текстура circuit-2.webp была мутной, а
   её правый, самый розовый край при cover повторялся плиткой и торчал полосой
   вдоль левого края карточки — отсюда no-repeat. Слева плату гасит скрим под
   текст (функциональный градиент читаемости — DESIGN.md его разрешает).
   Рамка нейтральная (0-2-1 выше :hover — на наведении не загорается), света
   за контуром нет; у кнопки своего света тоже нет: светится одна кнопка на
   экран, и это кнопка вывески. Моно-надпись над заголовком, как у заголовков
   секций (ui.css), в тёмной не показываем. */
html[data-theme="dark"] .xf-promo__card {
  border: 1px solid oklch(1 0 0 / 0.10);
  background:
    linear-gradient(100deg, oklch(0.135 0.008 292 / 0.96) 32%, oklch(0.135 0.008 292 / 0.3) 100%),
    url(/local/templates/xcore_front/css/../images/neon/promo-pcb-1.svg) center / cover no-repeat,
    var(--surface);
  box-shadow: var(--nx-edge);
}
html[data-theme="dark"] .xf-promo__eyebrow { display: none; }
/* Компьютер — чёрный корпус с фиолетовой подсветкой; светлую копию прячем.
   Свечение по контуру корпуса отделяет его от яркой платы за ним. */
html[data-theme="dark"] .xf-promo__pc--light { display: none; }
html[data-theme="dark"] .xf-promo__pc--dark {
  display: block;
  filter: drop-shadow(0 0 28px oklch(0.62 0.24 305 / 0.45));
}
/* Узкий экран: текст занимает левую колонку — под ним плата погашена
   сильнее, справа, за компьютером, светится. */
@media (max-width: 767px) {
  html[data-theme="dark"] .xf-promo__card {
    background:
      linear-gradient(100deg, oklch(0.135 0.008 292 / 0.94) 40%, oklch(0.135 0.008 292 / 0.5) 100%),
      url(/local/templates/xcore_front/css/../images/neon/promo-pcb-1.svg) center / cover no-repeat,
      var(--surface);
  }
}

/* ─── Brands ──────────────────────────────────────────────── */
.xf-brands {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 12px;
}
.xf-brand {
  aspect-ratio: 2.4;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  /* [#397] Глиф всегда в фирменном цвете бренда. Цвет приезжает inline-ом из
     include/home/brands.php (--xf-brand-ink, для тёмных брендов ещё
     --xf-brand-ink-dark); --text-3 остаётся фолбэком для бренда без цвета.
     Логотипы — единственное место системы, где живёт чужая палитра; правило
     The One-Voice Indigo Rule она не нарушает: это не accent сайта, а
     идентичность производителя, и индиго среди них не появляется. */
  color: var(--xf-brand-ink, var(--text-3));
  transition: color .2s, border-color .2s, transform .2s, background .2s;
  /* [#478] 18×24 → 12×20: глифу отдана почти вся высота плитки (см. svg ниже). */
  padding: 12px 20px;
  text-decoration: none;
}
/* Тёмная тема (светлая живёт на голом html, см. tokens.css). Свой ключ
   цвета только у брендов, чей фирменный цвет чёрный или почти чёрный: без
   него глиф сливается с плиткой. Логотипы остаются цветными — прямой запрос
   владельца (#397), в тёмной не гасим. Плитка ловит свет верхней кромкой;
   фон и рамка — те же, что в покое, и на наведении не меняются (0-2-1 выше
   :hover): плитку поднимает только сдвиг. */
html[data-theme="dark"] .xf-brand {
  color: var(--xf-brand-ink-dark, var(--xf-brand-ink, var(--text-3)));
  background: var(--surface);
  border-color: var(--line-soft);
  box-shadow: var(--nx-edge);
}
.xf-brand__logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
}
/* M9.4: max-height 50→65% (logos +30% relative). Закрывает «sparse logo
   wall» из 4-й критики P2 #5.
   [#478] 65% → 100% и padding плитки 18→12 (Илья 2026-09-03: «иконки брендов
   побольше», на прод). Логотипы — квадратные иконки 24×24, так что размер
   глифа диктует высота: в плитке 200×83 было ~30px, стало ~59px — вдвое.
   Плитка (aspect-ratio 2.4, 200px в ленте) не тронута. */
.xf-brand__logo svg {
  display: block;
  width: 100%;
  height: 100%;
  max-width: 80%;
  max-height: 100%;
  object-fit: contain;
}
/* [#397] Ховер больше НЕ подменяет цвет: подмена фирменного на индиго читалась
   как «логотип чужого бренда перекрасили в наш». Интерактивность держат рамка,
   подложка и подъём — цвет глифа под курсором не меняется. */
@media (hover: hover) {
  .xf-brand:hover {
    border-color: var(--accent-line);
    background: var(--surface-2);
    transform: translateY(calc(-2px * var(--xf-motion, 1)));
  }
}

/* M11.1: marquee mode. Override grid → flex-track с animation translateX.
   Track содержит 2 копии brands → translate -50% даёт seamless loop.
   overflow-x: clip (не hidden), чтобы hover translateY(-2px) и нижний
   1px бордера не обрезались сверху/снизу контейнера. Edge fades —
   absolute :before/:after, не зависят от overflow. */
.xf-brands--marquee {
  display: block;
  position: relative;
  overflow-x: clip;
}
.xf-brands--marquee::before,
.xf-brands--marquee::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 88px;
  z-index: 2;
  pointer-events: none;
}
.xf-brands--marquee::before {
  left: 0;
  background: linear-gradient(90deg, var(--bg) 0%, transparent 100%);
}
.xf-brands--marquee::after {
  right: 0;
  background: linear-gradient(270deg, var(--bg) 0%, transparent 100%);
}
.xf-brands__track {
  display: flex;
  gap: 12px;
  width: max-content;
  animation: xf-brand-marquee 70s linear infinite;
  will-change: transform;
}
@media (hover: hover) {
  .xf-brands--marquee:hover .xf-brands__track,
  .xf-brands--marquee:focus-within .xf-brands__track {
    animation-play-state: paused;
  }
}
/* [#323] Вне вьюпорта марквиза на паузе (класс вешает app.js через
   IntersectionObserver): не тикаем покадрово анимацию, которой не видно.
   Слой дорожки живёт из-за will-change и паузой не освобождается. */
.xf-brands--offscreen .xf-brands__track { animation-play-state: paused; }
.xf-brands--marquee .xf-brand {
  flex: 0 0 200px;
}
@keyframes xf-brand-marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
@media (prefers-reduced-motion: reduce) {
  .xf-brands__track { animation: none; }
  .xf-brands--marquee { overflow-x: auto; overflow-y: visible; }
}
/* Клавиатура видит то, на чём стоит фокус.
   Было: лента едет через translateX, и при переходе Tab-ом фокус-кольцо
   оказывается за краем экрана — замер живого /dev/ 2026-08-06: целиком видны
   4 ссылки из 12 на 1440 и 1 из 12 на 390, остальные стоят на x до 5072.
   Пауза по :focus-within (правило выше) проблему не решала: она останавливает
   ленту там, где её застал фокус, а не подводит ссылку к экрану.
   Стало: пока фокус внутри ленты, она перестаёт быть анимацией и становится
   обычным горизонтальным скроллером — ровно тем же, во что её превращает ветка
   prefers-reduced-motion выше. У скроллера есть прокрутка, и браузер сам
   подводит сфокусированную ссылку в зону видимости; доводит до конца
   initBrandsFocusScroll в js/app.js.
   Отдельного @media (hover: hover) здесь нет намеренно: пауза ленты спрятана
   под hover-медиа, то есть на тач-устройстве с клавиатурой не работает вовсе. */
.xf-brands--marquee:focus-within {
  overflow-x: auto;
  overflow-y: visible;
}
.xf-brands--marquee:focus-within .xf-brands__track {
  animation: none;
}

@media (max-width: 1024px) {
  .xf-brands { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 600px) {
  .xf-brands { grid-template-columns: repeat(3, 1fr); gap: 8px; }
  /* [#478] Мобильное зеркало: глиф во всю высоту, плитка та же. */
  .xf-brand { padding: 10px 14px; }
  .xf-brand__logo svg { max-height: 100%; }
}

/* ─── Mobile (≤ 767px) ──────────────────────────────────────
   Phone-only overrides for home blocks + shared product-card sizing.
   Tablet (768–900) rides desktop rails per M24 breakpoint decision. */
@media (max-width: 767px) {
  .xf-home__section        { padding: 32px 0 16px; }
  .xf-home__section--hero  { padding: 16px 0 16px; }
  .xf-home__section--trust { padding: 16px 0; }

  /* Hero: text over art, compact title, no side arrows. Swipe comes from JS.
     M9.1 mobile: override desktop aspect-ratio (1920/450). Иначе при mobile
     min-height 440 width forced до 1877 — overflow. На проде mobile-banner
     736×506 (ratio ~1.45) — почти квадратный, art-direction отдельная.
     Здесь оставляем content-driven height, image content team нарисует
     под mobile-формат отдельно. */
  .xf-hero {
    min-height: 440px;
    max-height: none;
    aspect-ratio: auto;
    border-radius: 18px;
  }
  /* M10 banner-mode mobile: aspect-ratio переключается на 736/506 (≈1.45),
     min-height сбрасывается — image drives height. На 375 wide → ~258px. */
  .xf-hero--banners {
    min-height: 0;
    aspect-ratio: 736 / 506;
  }
  .xf-hero__slide {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
  }
  .xf-hero__text { padding: 28px 24px 20px; }
  .xf-hero__eyebrow { margin-bottom: 12px; font-size: 11px; letter-spacing: 2px; }
  .xf-hero__title { font-size: 40px; letter-spacing: -1.5px; min-height: 0; }
  .xf-hero__subtitle { margin: 14px 0 20px; font-size: 15px; }
  .xf-hero__ctas .xf-btn:first-child { flex: 1; justify-content: center; }
  .xf-hero__ctas .xf-btn--ghost { display: none; }
  .xf-hero__art { margin: 0 24px 52px; border-radius: 14px; min-height: 160px; }
  /* [#303] На телефоне точки тоже по центру (правило banner-режима выше даёт
     left:50% + translateX), меняется только отступ от низа. */
  .xf-hero__dots { bottom: 14px; }
  .xf-hero__nav { display: none; }
  .xf-hero__chips { top: 12px; right: 12px; }

  /* CategoryGrid: featured full-width, 6 rest tiles in 2×3. */
  .xf-catgrid {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto auto auto auto;
    gap: 10px;
  }
  .xf-catgrid__featured {
    grid-column: 1 / -1;
    grid-row: auto;
  }
  /* M4 mobile: sub-hub (--16) grid не имеет featured layout, reset
     explicit rows — все ряды auto-generated, grid-auto-rows: 1fr
     из base equalizes их по самому высокому tile. */
  .xf-catgrid--16 {
    grid-template-rows: none;
  }
  .xf-cat-tile { height: 120px; padding: 14px; border-radius: 14px; }
  .xf-cat-tile__name { font-size: 14px; margin-top: 6px; max-width: 100%; }
  .xf-cat-tile__icon { width: 44px; height: 44px; right: 12px; bottom: 12px; border-radius: 11px; }
  .xf-cat-tile__icon svg { width: 22px; height: 22px; }
  .xf-cat-tile__arrow { top: 12px; right: 12px; width: 28px; height: 28px; }
  .xf-cat-tile__arrow svg { width: 12px; height: 12px; }

  /* Featured xl tile — сжатая высота, art-блок под body. */
  .xf-cat-tile--xl {
    height: auto;
    min-height: 260px;
    padding: 22px;
    grid-template-rows: auto 120px;
    row-gap: 14px;
  }
  .xf-cat-tile--xl .xf-cat-tile__name { font-size: 22px; margin-top: 8px; }
  .xf-cat-tile--xl .xf-cat-tile__desc { font-size: 13px; max-width: none; margin-top: 6px; }
  .xf-cat-tile--xl .xf-cat-tile__art {
    grid-row: 2;
    width: 100%;
    max-height: 120px;
    min-height: 100px;
    justify-self: stretch;
  }
  .xf-cat-tile--xl .xf-cat-tile__arrow { top: 20px; right: 20px; }

  /* TrustBar 2×2 */
  .xf-trustbar { grid-template-columns: 1fr 1fr; gap: 10px; }
  .xf-trustbar__item { padding: 14px 14px; gap: 12px; border-radius: 12px; }
  .xf-trustbar__icon { width: 36px; height: 36px; border-radius: 9px; }
  .xf-trustbar__title { font-size: 13px; }
  .xf-trustbar__text { font-size: 11.5px; }

  /* SplitPromo stack */
  .xf-promo { grid-template-columns: 1fr; gap: 10px; }
  .xf-promo__card { padding: 28px 22px; min-height: 220px; border-radius: 16px; }
  .xf-promo__title { font-size: 26px; margin-bottom: 10px; letter-spacing: -0.8px; }
  .xf-promo__text { font-size: 14px; margin-bottom: 20px; max-width: none; }
  /* [#361] Прежняя плашка с иконкой лежала поверх текста во всю ширину
     (замер 2026-08-06 при 390 px: 94 px наложения) и была скрыта. Компьютер
     поверх текста не кладём: карточка становится сеткой — заголовок во всю
     ширину (ровно две строки, без висящего «минут»), под ним строка текста
     слева и компьютер справа, кнопка отдельной строкой ниже обоих. Абсолютный
     компьютер у нижнего края сажал кнопку ему на угол. */
  .xf-promo__card {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas: "eyebrow eyebrow" "title title" "text art" "cta cta";
    column-gap: 12px;
  }
  .xf-promo__body { display: contents; }
  .xf-promo__eyebrow { grid-area: eyebrow; }
  .xf-promo__title { grid-area: title; text-wrap: balance; }
  .xf-promo__text { grid-area: text; align-self: center; }
  .xf-promo__card .xf-btn { grid-area: cta; justify-self: start; }
  .xf-promo__art-wrap { position: static; grid-area: art; height: 150px; margin-bottom: 20px; }
  .xf-promo__pc { width: auto; }

  /* Brands — 3 col grid (12 брендов → 4 ряда по 3) для phone. */
  .xf-brands { grid-template-columns: repeat(3, 1fr); gap: 8px; }
  .xf-brand { padding: 10px 14px; border-radius: 12px; }
  /* M9.4 mobile-зеркало: max-height 55→70%, max-width 70→80%.
     [#478] 70% → 100%: в ленте 160×67 глиф ~30 → ~47px. */
  .xf-brand__logo svg { max-width: 80%; max-height: 100%; }
  /* M11.1 mobile marquee: уже tile (160 vs desktop 200), уже edge-fade
     (32 vs 88), быстрее цикл (50 vs 70s — track короче, кажется similar). */
  .xf-brands--marquee::before,
  .xf-brands--marquee::after { width: 32px; }
  .xf-brands--marquee .xf-brand { flex: 0 0 160px; }
  .xf-brands__track { animation-duration: 50s; }

  /* ProductStrip → 2 col (home «Что сейчас берут» + везде где grid-common) */
  .xf-products__grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }

  /* Section heads — компактнее */
  .xf-section-head__title, .xf-products__head .xf-section-head__title {
    font-size: 22px;
    letter-spacing: -0.6px;
  }
  .xf-section-head__eyebrow { font-size: 10.5px; letter-spacing: 2px; }
  .xf-products__head { margin-bottom: 18px; gap: 12px; }

  /* Product card — укрупнить title/цену для 2-col сетки на phone. */
  .xf-product-card { border-radius: 14px; }
  /* Mobile foot is a column ending in a full-width cart bar. Make body fill
     the (grid-equal-height) card so the bar can pin to the bottom — keeps
     bars on one line across a row regardless of strike/installment presence,
     while the live price stays under the constant title at the top. */
  .xf-product-card__body { padding: 12px 14px 14px; display: flex; flex-direction: column; flex: 1; }
  .xf-product-card__stickers { top: 10px; left: 10px; gap: 5px; }
  .xf-product-card__sticker { font-size: 9.5px; padding: 3px 7px; }
  /* Guarantee overlay на mobile-карточке (2-col grid ≈ 175px wide):
     раньше 110px min-width = ~63% от ширины фото. Поджимаем до 88px
     ≈ 28% — плашка остаётся читаемой, фото не пропадает.
     [#420] Правило сужено селектором до карточки. Ту же плашку носит теперь
     фото «Товара дня», а там телефон даёт кадр во всю ширину (~258px) — сжимать
     подпись до 9px незачем, она осталась бы случайной мелочью на самом
     крупном снимке страницы. Для карточек селектор ничего не меняет: класс
     .xf-product-card__guarantee рисуется только внутри .xf-product-card
     (единственный рендер — helpers.php → xf_render_product_card). */
  .xf-product-card .xf-product-card__guarantee { min-width: 88px; font-size: 9px; padding: 3px 6px; letter-spacing: 0; border-radius: 6px 0 0 0; }
  /* P1 fix mobile: photo-wrap min-width ≥ worst-case plaque actual width
     («Гарантия NN месяцев» ≈ 120px при font 10px + padding 4×8). */
  .xf-product-card .xf-art--photo .xf-art__photo-wrap { min-width: 122px; }
  .xf-product-card__like-icons { top: 10px; right: 10px; gap: 6px; }
  .xf-product-card__like-btn { width: 44px; height: 44px; border-radius: 12px; opacity: 1; }
  .xf-product-card__like-btn svg { width: 18px; height: 18px; }
  .xf-product-card__stock { font-size: 11px; }
  /* [#340] 4 → 5 строк. Отличительная часть имени у комплектующих стоит в
     конце («…SE-224-XTS ARGB White» / «…SE-224-XTS Black»), и clamp резал
     именно её: соседние карточки выглядели одинаково.
     Замер на 96 реальных именах, карточка 174px (390px, 2-col, рельс 16px):
       4 строки — 31% имён обрезано, худший перелив 56px (≈3 строки);
       5 строк  — 11%, худший перелив 37px;
       6 строк  —  3%, но +37px к карточке (406→443).
     Взяли 5: 89-й процентиль имён влезает целиком, карточка растёт на 19px
     (406→425). Кегль оставлен 14px — он тут выбран под 2-col сетку телефона
     осознанно, а 13.5px дал бы всего +4 п.п. Резерв (min/max/clamp) меняем
     тремя числами разом — иначе цены в ряду разъедутся. */
  .xf-product-card__title {
    font-size: 14px;
    line-height: 1.35;
    min-height: calc(1.35em * 5);
    max-height: calc(1.35em * 5);
    margin-bottom: 12px;
    -webkit-line-clamp: 5;
    line-clamp: 5;
  }
  /* [#315] У имени на телефоне отступ снизу 12px против 14px на десктопе, и
     базовый -6px оставлял всего 6px между пятой строкой имени и строкой
     характеристики — тесно ровно там, где имя занимает весь резерв. */
  .xf-product-card__spec { margin: -2px 0 8px; }
  /* [#306] Колонка-подвал, полноширинный бар и подпись переехали в базу —
     тут остались только размеры под палец и 2-колоночную сетку телефона.
     Мобильный вид не меняется: те же 42px высоты, иконка 18px, подпись 13.5px. */
  .xf-product-card .xf-price__val { font-size: 20px; letter-spacing: -0.4px; }
  .xf-product-card__installment { font-size: 10.5px; white-space: normal; }
  .xf-product-card__installment-sep { display: none; }
  .xf-product-card__installment-term { display: block; }
  .xf-product-card__article { font-size: 10px; }
  .xf-product-card__cart-btn { height: 42px; }
  .xf-product-card__cart-btn svg { width: 18px; height: 18px; }
  .xf-product-card__cart-label { font-size: 13.5px; }
}

/* End */


/* Start:/local/templates/xcore_front/css/catalog.css?179265387015933*/
/* OWNS: .xf-catpage-*, .xf-catgrid-*, .xf-crumbs, .xf-brand-chip(s) (вкл. .xf-brand-chip__label/__pre), .xf-facet-chip(s), полосы чипов на телефоне [data-xf-chip-rail] и их состояния .is-fade-left/.is-fade-right (ставит app.js initChipRails, #646), .xf-mono (catalog L1 landing) */
/* Catalog landing — header + 4×4 equal-tile grid */

.xf-catpage__head {
  padding: 32px 0 16px;
}

.xf-crumbs {
  /* Sans, не mono. Navigation labels — не data (DESIGN.md Mono-as-Data).
     Раньше был --font-mono, читалось как «техно-декор» и на мобайле длинное
     имя товара в последнем крамбе уезжало в 3 строки моноширинной башней. */
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  font-size: 13px;
  color: var(--text-3);
  margin-bottom: 18px;
}
.xf-crumbs a { color: var(--text-3); transition: color .15s; }
@media (hover: hover) {
  .xf-crumbs a:hover { color: var(--text-2); }
}
.xf-crumbs > span:last-child { color: var(--text-2); }

.xf-catpage__title-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
}
.xf-catpage__title {
  margin: 0;
  font-size: 44px;
  font-weight: 600;
  letter-spacing: -1.5px;
  line-height: 1.05;
}
.xf-catpage__meta {
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--text-3);
  font-size: 13.5px;
}
.xf-catpage__meta .xf-mono { font-family: var(--font-mono); color: var(--text-2); }

.xf-catpage__lead {
  margin: 16px 0 0;
  font-size: 15px;
  color: var(--text-2);
  line-height: 1.5;
  max-width: 640px;
}

/* M4 P2: brand chips strip между L1 lede и tile grid. Quick-access
   shortcuts для visitor'ов с brand-bound intent (~ половина mass-market
   персоны). 1-click path к brand-filtered search вместо 3-click через
   tile → L2 → spec-filter. Subtle pill style — не competing с tile grid
   за внимание. Reusable pattern для всех L1 sub-hub'ов via PHP helper. */
/* M4 P2.1 (Носители critique 2026-05-17): quieter pass. Chips конкурировали
   с tile grid за внимание на L1 sub-hub'ах — font 13.5 + gap 8 + margin 18
   давало 60-80px вертикального weight'а ДО tile-grid (main answer). Chips —
   shortcut для minority intent (brand-bound), tiles — canonical. Inversion
   priority. Shrink font/gap/margin → reads как secondary row.
   Mobile rules (WCAG 44px tap target) — unchanged. */
.xf-brand-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 12px 0 0;
}
.xf-brand-chip {
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-2);
  text-decoration: none;
  transition: border-color .2s, background .2s, transform .15s, color .2s;
  white-space: nowrap;
}
/* #49: текущий SEO-лендинг в стрипе «Популярные категории» — state-маркер,
   не ссылка (Pill-Is-Not-Button): читаемый, но некликабельный. */
.xf-brand-chip.is-current {
  border-color: var(--accent-line);
  color: var(--text);
  font-weight: 600;
  cursor: default;
}

@media (hover: hover) {
  .xf-brand-chip:not(.is-current):hover {
    border-color: var(--accent-line);
    background: var(--accent-dim);
    color: var(--accent-2);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
}
.xf-brand-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Use-case facet chips (2026-05-19, impeccable critique P1/P2 fix): для
   flat-L1 hubs (без L2 tile-grid) approximates tile-grid affordance через
   chip strip с top-N facet shortcuts. Visual hierarchy: facet chips primary
   (mass-market nav, category-axis), brand chips secondary (brand-axis).
   Facet chips более prominent — accent-tinted bg + accent border + 600
   weight. Same pill shape для visual cohesion с brand-chips, но increased
   visual weight signals primary navigation role. */
.xf-facet-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 16px 0 0;
}
.xf-facet-chip {
  display: inline-flex;
  align-items: center;
  padding: 6px 12px;
  background: var(--accent-dim);
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--accent-2);
  text-decoration: none;
  transition: border-color .2s, background .2s, transform .15s, color .2s;
  white-space: nowrap;
}
@media (hover: hover) {
  .xf-facet-chip:hover {
    border-color: var(--accent);
    background: var(--accent-soft, var(--accent-dim));
    color: var(--accent);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
  /* Тёмная (#858): подпись остаётся фиолетом текста — было --accent на
     подложке 16%, 3.5:1; стало --accent-2 на 28%, 8.2:1. Отклик наведения —
     подложка на ступень плотнее (как у пилюли «в сравнении», catsection.css),
     рамка --accent и подъём. */
  html[data-theme="dark"] .xf-facet-chip:hover {
    background: color-mix(in srgb, var(--accent) 28%, transparent);
    color: var(--accent-2);
  }
}
.xf-facet-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Mobile: WCAG 44px tap target. Inherit brand-chip mobile rule (если есть
   media query ниже для .xf-brand-chip — facet chips наследуют через shared
   selector). */

/* #49: SEO-текст лендинга под листингом (zverushki_seofilter_seotag.SEO_DESCRIPTION —
   админ-HTML: h4/br/абзацы). Тихий читабельный блок: ограниченная ширина строки,
   приглушённый цвет — это справка, не контент листинга. */
.xf-catpage__seo {
  max-width: 760px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--text-2);
}
.xf-catpage__seo h1,
.xf-catpage__seo h2,
.xf-catpage__seo h3,
.xf-catpage__seo h4 {
  margin: 0 0 10px;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}
.xf-catpage__seo p { margin: 0 0 10px; }
.xf-catpage__seo ul,
.xf-catpage__seo ol { margin: 0 0 10px; padding-left: 20px; }

/* #809: ссылки в админ-HTML лендинга — текст над листингом (.xf-catpage__lead) и под
   ним (.xf-catpage__seo). base.css снимает с <a> цвет и подчёркивание, и без этого
   правила ссылка в тексте выглядит обычным текстом. Вид — как у ссылок в теле справки
   и статьи блога (content.css .xf-help-body a, blog.css .xf-blog-article__body a). */
.xf-catpage__lead a,
.xf-catpage__seo a {
  color: var(--accent-2);
  text-decoration: underline;
  text-decoration-color: var(--link-line);
  text-underline-offset: 3px;
  transition: color .15s, text-decoration-color .15s;
}
@media (hover: hover) {
  .xf-catpage__lead a:hover,
  .xf-catpage__seo a:hover {
    color: var(--text);
    text-decoration-color: var(--accent-2);
  }
}

/* Equal 4×4 grid — overrides the home hero-style 2fr featured layout. */
.xf-catgrid--16 {
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: auto;
}

/* Phone: 4 col плотно слепляет имена/pill'ы → 2 col как у product listing.
   Subsection-tile (с .xf-cat-tile__art) имеет :has-driven height 200px от
   home.css — на узком 2-col tile'е art съедает body, поэтому shrink art и
   разрешаем height auto. */
@media (max-width: 767px) {
  .xf-catgrid--16 { grid-template-columns: repeat(2, 1fr); gap: 12px; }

  .xf-cat-tile:has(.xf-cat-tile__art) {
    height: auto;
    min-height: 200px;
    padding: 14px;
  }
  .xf-cat-tile:has(.xf-cat-tile__art) .xf-cat-tile__art {
    height: 72px;
    margin-bottom: 10px;
  }
  /* M2: cover-variant получает большую art-зону на mobile. 72px унаследован
     от icon-only варианта (icon 36×36 свободно сидит в 56×56 inner); для
     product photo'ы это даёт 56px высоты — слишком тесно. 110px → ~94px inner
     → product читается.
     M4 mobile: name clamp = 2 lines с min-height 2 lines (через home.css
     M4 desktop rule). Длинные имена truncate'нутся (P2), но P0 row
     uniformity выигрывает — все tiles одинаковой высоты. M8 deferred:
     server-side short-aliases для names >30ch — отдельный backend task. */
  .xf-cat-tile:has(.xf-cat-tile__art) .xf-cat-tile__art--cover {
    height: 110px;
  }
  /* Static curated covers — transparent PNG illustrations с padding 12px вокруг
     товара (desktop rule). Generic .xf-cat-tile__art mobile rule даёт 72px
     (icon-size); для иллюстрации это даёт ~48px effective product = «общая
     иконка кажется маленькой» (user-reported на прит + красота). 130px высота
     + 12px padding = ~106px product height — читается на полную ширину 2-col
     grid. Tile min-height auto-grows до ~232px чтобы art не overflow'ил body —
     grid-auto-rows: 1fr equalizes rows в ряду. */
  .xf-cat-tile:has(.xf-cat-tile__art) .xf-cat-tile__art--static {
    height: 130px;
  }
  .xf-cat-tile:has(.xf-cat-tile__art--cover) .xf-cat-tile__name,
  .xf-cat-tile:has(.xf-cat-tile__art--static) .xf-cat-tile__name {
    -webkit-line-clamp: 2;
    line-clamp: 2;
  }

  /* M4 P3: brand chips mobile tap target — WCAG 2.5.5 recommended 44×44px.
     Base padding 7px давало ~33px height (under WCAG); bump до 11px + min-height
     44px = strict compliance. Gap 8→10 для finger separation между pills.
     2026-05-19: facet chips inherit identical mobile rule (same WCAG concern). */
  .xf-brand-chips,
  .xf-facet-chips { gap: 10px; }
  .xf-brand-chip,
  .xf-facet-chip {
    padding: 11px 16px;
    min-height: 44px;
  }
  /* [#646] Полоса чипов — одна строка с прокруткой вбок, а не стопка. Чипы
     «Популярных категорий» длинные («Видеокарты GeForce RTX 5060 Ti»), на
     телефоне каждый вставал отдельной строкой, и девять таких занимали весь
     первый экран раздела: первый товар у видеокарт был на 892 px, у
     процессоров — на 1000–1054. Полоса выходит под край экрана (как
     «Похожие» на карточке, product.css) — обрезанный чип у края и есть
     подсказка «дальше есть ещё». padding сверху и снизу — место кольцу
     фокуса, которое иначе срезала бы прокрутка. */
  .xf-brand-chips,
  .xf-facet-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    margin-inline: calc(var(--container-pad) * -1);
    padding: 4px var(--container-pad);
    scroll-padding-inline: var(--container-pad);
  }
  .xf-brand-chips::-webkit-scrollbar,
  .xf-facet-chips::-webkit-scrollbar { display: none; }
  .xf-brand-chip,
  .xf-facet-chip { flex: 0 0 auto; }
  /* Подсказка «дальше есть ещё»: край полосы растворяется с той стороны, где
     прячутся чипы (функциональный градиент-маска, DESIGN.md). Сторону ставит
     app.js (initChipRails) классами .is-fade-left / .is-fade-right; ширину
     растворения он читает из --xf-rail-fade. Без скрипта маски нет, а
     обрезанный у края чип остаётся подсказкой. */
  [data-xf-chip-rail] { --xf-rail-fade: 32px; }
  [data-xf-chip-rail].is-fade-right {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--xf-rail-fade)), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - var(--xf-rail-fade)), transparent);
  }
  [data-xf-chip-rail].is-fade-left {
    -webkit-mask-image: linear-gradient(to left, #000 calc(100% - var(--xf-rail-fade)), transparent);
    mask-image: linear-gradient(to left, #000 calc(100% - var(--xf-rail-fade)), transparent);
  }
  [data-xf-chip-rail].is-fade-left.is-fade-right {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 var(--xf-rail-fade), #000 calc(100% - var(--xf-rail-fade)), transparent);
    mask-image: linear-gradient(to right, transparent, #000 var(--xf-rail-fade), #000 calc(100% - var(--xf-rail-fade)), transparent);
  }
  /* Повтор имени раздела в начале чипа подборки («Видеокарты GeForce RTX…» под
     заголовком «Видеокарты») — визуально прячем, в тексте ссылки он остаётся
     (section.php, #646). */
  .xf-brand-chip__pre {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* L1 catalog-landing tile (icon-badge variant): hero-stack как у L2.
     Icon выходит из absolute → в потоке сверху на 100% ширины 72px высоты,
     имя под ним получает полную ширину без collision'а. Унификация ритма
     с subsection-grid'ом (тоже 72px art сверху). */
  .xf-cat-tile:not(:has(.xf-cat-tile__art)) {
    height: auto;
    min-height: 160px;
    padding: 14px;
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .xf-cat-tile:not(:has(.xf-cat-tile__art)) .xf-cat-tile__icon {
    position: static;
    width: 100%;
    height: 72px;
    border-radius: 12px;
  }
  .xf-cat-tile:not(:has(.xf-cat-tile__art)) .xf-cat-tile__icon svg {
    width: 36px;
    height: 36px;
  }
  .xf-cat-tile:not(:has(.xf-cat-tile__art)) .xf-cat-tile__icon-img {
    max-width: 50%;
    max-height: 60%;
  }
  .xf-cat-tile:not(:has(.xf-cat-tile__art)) .xf-cat-tile__body {
    flex: 1;
    margin-top: 0;
  }

  .xf-cat-tile__name {
    max-width: 100%;
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.3;
    font-size: 13.5px;
  }

  .xf-cat-tile .xf-pill { white-space: nowrap; }

  /* M4 P3 mobile (Носители critique 2026-05-17): tile subtitle на mobile —
     12px, мelt with name visually. */
  .xf-cat-tile__desc--compact {
    font-size: 12px;
    margin-top: 3px;
  }
}

/* M4 P3 critique Носители 2026-05-17: compact subtitle variant для subhub
   L2 tiles. Default `.xf-cat-tile__desc` (home.css:624) сделан под XL hero
   tile — 14px font + 55% max-width. На compact L2 tile (152-200px wide)
   нужен smaller font + full width + single line + text-3 (subtler tone).
   Adds NEW info без визуального competition с tile name (primary). */
.xf-cat-tile__desc--compact {
  margin-top: 4px;
  font-size: 12.5px;
  color: var(--text-3);
  max-width: 100%;
  line-height: 1.35;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* End */


/* Start:/local/templates/xcore_front/css/catsection.css?179265387551080*/
/* OWNS: .xf-secpage-*, .xf-filter-*, .xf-check-*, .xf-chip-* (filter chips — note .xf-chip also in search.css), .xf-filter-fab, .xf-price-range-*, .xf-facet-search-* (filter min/max inputs — base .xf-price-* lives in ui.css); плюс ОДНО правило `body.xf-filter-open .xf-chat` в блоке #363 внизу файла — z-order чужого лаунчера в состоянии открытой шторки, вид .xf-chat остаётся за chat.css */
/* Section page — filters sidebar + product grid with toolbar/pagination */

.xf-secpage {
  display: grid;
  grid-template-columns: 280px 1fr;
  gap: 32px;
  align-items: flex-start;
}

/* ─── Filters sidebar ─────────────────────────────────────── */
/* Sticky column with internal scroll + pinned actions bar at the bottom —
   the classic long-facet pattern so "Применить" is always reachable even
   in sections with 40+ smart-filter groups. Mobile falls back to a plain
   static column (see the @media block below). */
.xf-filters {
  position: sticky;
  top: 110px;
  max-height: calc(100vh - 130px);
  display: flex;
  flex-direction: column;
  gap: 0;
}
.xf-filters__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
  flex: 0 0 auto;
}
.xf-filters__body {
  flex: 1 1 auto;
  overflow-y: auto;
  /* Negative margin + matching padding so the scrollbar sits in a gutter
     and doesn't clip the group contents. */
  padding-right: 10px;
  margin-right: -10px;
  min-height: 0;
}
.xf-filters__actions {
  flex: 0 0 auto;
  display: flex;
  gap: 10px;
  padding-top: 14px;
  margin-top: 4px;
  border-top: 1px solid var(--line-soft);
  background: var(--bg);
  position: relative;
}
/* Fade the last bit of scroll content into the actions bar so it's clear
   there's more above than what fits. */
.xf-filters__actions::before {
  content: "";
  position: absolute;
  top: -20px;
  left: 0;
  right: 10px;
  height: 20px;
  pointer-events: none;
  background: linear-gradient(to top, var(--bg), rgba(255,255,255,0));
}
/* [#43] Desktop: keep «Применить» reachable without scrolling. Two parts,
   both verified on /dev at 762px vh (button was at y~984, below the fold):
   1) The panel's OWN position:sticky;top:110 was dead — .xf-secpage is
      align-items:flex-start, so the aside grid cell collapsed to the panel's
      height (no travel). align-self:stretch gives the cell the full row height
      → the panel pins → its flex-bottom actions stay in view while scrolling
      (without this the bar scrolled off at y~1400).
   2) At scroll-top the panel still sits in natural flow below the header with
      its bottom past the fold, so also pin the actions to the viewport bottom.
      Clear the consent bar via --xf-cookie-h (same mechanism as the FAB in
      ui.css) so the button isn't hidden behind it on first visit.
   Mobile (≤767) keeps its fixed bottom-sheet actions — left untouched below. */
@media (min-width: 768px) {
  .xf-secpage__filters { align-self: stretch; }
  .xf-filters__actions {
    position: sticky;
    bottom: 0;
    z-index: 2;
  }
  body.xf-cookie-up .xf-filters__actions { bottom: var(--xf-cookie-h, 72px); }
}
.xf-filters__title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}
.xf-filters__reset {
  font-family: var(--font-mono);
  font-size: 11.5px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-3);
  transition: color .15s;
}
@media (hover: hover) {
  .xf-filters__reset:hover { color: var(--text-2); }
}

.xf-filter-group {
  border-top: 1px solid var(--line-soft);
  padding: 14px 0;
}
.xf-filter-group:first-of-type { border-top: 0; padding-top: 14px; }
/* Native <details> summary as the group toggle — chevron from arrow-down. */
.xf-filter-group > summary {
  list-style: none;
  cursor: pointer;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 2px 0;
  user-select: none;
}
.xf-filter-group > summary::-webkit-details-marker { display: none; }
.xf-filter-group > summary::after {
  content: "";
  width: 14px;
  height: 14px;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239a98a6' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>") center/contain no-repeat;
  transform: rotate(-90deg);
  transition: transform .18s ease;
  flex: 0 0 14px;
}
.xf-filter-group[open] > summary::after { transform: rotate(0); }
.xf-filter-group[open] > summary { margin-bottom: 12px; }
/* Legacy non-<details> group title (still used by a couple of callers) */
.xf-filter-group > .xf-filter-group__title {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text);
  margin-bottom: 12px;
}
.xf-filter-group__options {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 260px;
  overflow-y: auto;
  padding-right: 4px;
}
.xf-filter-group__more {
  margin-top: 4px;
  padding-top: 4px;
}
.xf-filter-group__more > summary {
  list-style: none;
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 6px 0;
}
.xf-filter-group__more > summary::-webkit-details-marker { display: none; }
@media (hover: hover) {
  .xf-filter-group__more > summary:hover { color: var(--text-2); }
}
.xf-filter-group__more[open] > summary { color: var(--text-2); }
.xf-filter-group__more .xf-check + .xf-check { margin-top: 10px; }

/* Stock toggle — compact, sits just under the header (no group chrome). */
.xf-check--toggle {
  padding: 10px 0 6px;
}

/* Price range inputs */
.xf-price-range {
  display: flex;
  gap: 8px;
}
.xf-price-range__input {
  flex: 1;
  position: relative;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  transition: border-color .15s;
}
.xf-price-range__input:focus-within { border-color: var(--accent-line); }
/* [#363-r3] Подписи «ОТ»/«ДО». Два замера на живом /dev (2026-08-06):
   детектор impeccable ловит 10px как функциональный текст ниже порога 11px
   (33 срабатывания на странице, 32 из них — эти подписи), а --text-3 на
   --surface-2 в тёмной теме даёт 4.28:1 при норме AA 4.5. 11px + --text-2
   закрывает оба — тот же разбор, что у строки рассрочки в карточке, где
   --text-3 уже отвергли по этой причине. */
.xf-price-range__input > span {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 1px;
  color: var(--text-2);
  margin-bottom: 2px;
}
.xf-price-range__input input {
  width: 100%;
  background: transparent;
  border: 0;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text);
  padding: 0;
}
/* [#373] Здесь НЕ должно быть `outline: 0`. Прежняя суппрессия гасила
   ГЛОБАЛЬНЫЙ :focus-visible на 32 полях «ОТ/ДО» разом — это все числовые
   диапазоны фильтра. Единственным следом фокуса оставалась рамка
   обёртки-label, меняющаяся на --accent-line: замер 1.22:1 к соседнему цвету
   при норме 1.4.11 = 3:1, то есть индикатора фактически не было.
   Чиним снятием ПЕРЕБИВАНИЯ, а не новым :focus-visible: инвариант проекта —
   «глобальный :focus-visible + .xf-skip; per-element focus-правила не писать»
   (тот же приём, что у поля поиска в header.css:281).
   Кольцо рисуется вне border-box и раскладку не двигает. */
/* Hide number spinners */
.xf-price-range__input input::-webkit-outer-spin-button,
.xf-price-range__input input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.xf-price-range__input input[type=number] { appearance: textfield; -moz-appearance: textfield; }

/* Checkbox facet */
.xf-check {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  font-size: 13px;
  color: var(--text-2);
  user-select: none;
}
.xf-check input { position: absolute; opacity: 0; pointer-events: none; }
.xf-check__box {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  border-radius: 4px;
  border: 1.5px solid var(--line);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color .15s, background .15s;
}
.xf-check__box::after {
  content: "";
  width: 9px;
  height: 9px;
  background: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'><path d='M5 12l5 5L20 7'/></svg>") center/contain no-repeat;
  opacity: 0;
  transition: opacity .12s;
}
/* [#373-r3] Кольцо фокуса у чекбокса фильтра. Это ЕДИНСТВЕННОЕ место, где
   глобальный `:focus-visible` (ui.css) физически не может отрисоваться:
   настоящий <input> скрыт `opacity: 0` — outline на прозрачном элементе не
   красится вообще, а видимый квадрат `.xf-check__box` фокус не получает,
   потому что фокус не на нём.
   Проверено на живом /dev (1440, 2026-08-06): у сфокусированного input
   computed outline = `solid 2px oklch(0.55 0.23 268)`, при этом opacity 0 и
   бокс 13×13 — на экране не появляется ничего; у `.xf-check__box` outline,
   box-shadow и border-color не менялись. Клавиатурой фильтр — а это 175
   чекбоксов, главная поверхность страницы — проходился вслепую.
   Инвариант «per-element focus-правила не писать» тут не нарушается по духу:
   это не своя стилистика, а ПРОКСИ глобального кольца, значения повторяют
   ui.css один в один. Появится настоящий видимый input — правило удаляется. */
.xf-check input:focus-visible + .xf-check__box {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.xf-check input:checked + .xf-check__box {
  border-color: var(--accent);
  background: var(--accent);
}
.xf-check input:checked + .xf-check__box::after { opacity: 1; }
/* Тёмная тема: контур --line (белый 12%) на тёмной поверхности не читался как
   «это можно нажать». 45% белого — контур 4.5:1 к --surface. Отмеченный
   (0-3-1 выше) остаётся залитым акцентом. */
html[data-theme="dark"] .xf-check__box { border-color: oklch(1 0 0 / 0.45); }
.xf-check input:checked ~ .xf-check__label { color: var(--text); }
.xf-check__label { flex: 1; }
.xf-check__count {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-3);
}

.xf-filters__apply {
  flex: 1;
  margin-top: 0;
}

/* Длинные filter group labels (типа «Производитель графического процессора»)
   ломали row в sidebar из-за .xf-tip { white-space: nowrap } по умолчанию
   из product.css. Разрешаем wrap в контексте filter sidebar — текст
   переносится, (?) кнопка остаётся inline. */
.xf-secpage__filters .xf-filter-group > summary { min-width: 0; }
.xf-secpage__filters .xf-tip {
  white-space: normal;
  min-width: 0;
}

/* ─── Toolbar ─────────────────────────────────────────────── */
.xf-secpage__toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}
.xf-secpage__result {
  font-family: var(--font-mono);
  font-size: 12.5px;
  color: var(--text-2);
}
.xf-secpage__tools {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
}
.xf-secpage__sort-label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--text-3);
}
.xf-select {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  padding: 8px 34px 8px 12px;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text);
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239a98a6' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 14px;
  transition: border-color .15s;
}
@media (hover: hover) {
  .xf-select:hover { border-color: var(--line); }
}
.xf-select:focus { border-color: var(--accent-line); }
/* [#373] Здесь тоже нет `outline: 0` — по той же причине, что у полей
   диапазона: селект сортировки это единственный орган управления выдачей над
   сеткой, а рамка --accent-line как индикатор даёт 1.23:1 при норме 3:1.
   Кольцо приходит из глобального :focus-visible. */

/* M7: compare-set toolbar pill — accent-dim wash сигналит «у тебя есть
   незакрытое сравнение», link на /personal/compare/. Hidden when empty. */
.xf-secpage__compare-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 6px 10px;
  background: var(--accent-dim);
  color: var(--accent-2);
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 500;
  text-decoration: none;
  transition: background .15s, color .15s, border-color .15s;
}
@media (hover: hover) {
  /* Подпись на наведении — на ступень глубже покоя (#858): --accent на этой
     подложке давал 3.87:1, --accent-3 даёт 6.2:1 (покой — --accent-2, 5.45:1). */
  .xf-secpage__compare-pill:hover {
    background: oklch(0.55 0.23 var(--accent-h) / 0.18);
    color: var(--accent-3);
    border-color: var(--accent);
  }
  /* Тёмная (#857): литерал — индиго 268 светлой; пара — фиолет темы на
     ступень плотнее покоя (--accent-dim там 16%). Смесь в srgb: с прозрачным
     там нечему крутить оттенок, в любом движке выходит --accent на 28%.
     Цифра — фиолетом для текста: --accent текстом на этой подложке дал бы
     3.05:1 (на прежней — 3.4), --accent-2 — 8.1:1. */
  html[data-theme="dark"] .xf-secpage__compare-pill:hover {
    background: color-mix(in srgb, var(--accent) 28%, transparent);
    color: var(--accent-2);
  }
}
/* [#373-r3] Персональное правило фокуса снято: оно дословно повторяло
   глобальное `:focus-visible` из ui.css, а инвариант проекта — «глобальный
   :focus-visible + .xf-skip; per-element focus-правила не писать». Кольцо на
   пилюле сравнения не изменилось — теперь оно приходит из одного места. */
.xf-secpage__compare-count {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: -0.02em;
}
.xf-secpage__compare-label {
  color: var(--text-2);
}

/* ─── Active filter chips ─────────────────────────────────── */
.xf-secpage__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 14px;
}
.xf-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px 6px 12px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  font-size: 12.5px;
  color: var(--text-2);
  transition: border-color .15s, color .15s, background .15s;
}
@media (hover: hover) {
  .xf-chip:hover {
  border-color: var(--accent-line);
  color: var(--text);
  background: var(--surface-2);
}
}
.xf-chip__x {
  display: inline-flex;
  width: 16px;
  height: 16px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--text-3);
}
@media (hover: hover) {
  .xf-chip:hover .xf-chip__x { color: var(--text-2); }
}

/* ─── Product grid inside section (3-col due to sidebar) ─── */
.xf-secpage__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}

/* [#347] Узкий десктоп и планшет (768–1100) — 2 колонки.
   Ширина карточки тут = (W − 80 рельс − 280 сайдбар − 32 gap − 28 gap'ы сетки)/3,
   то есть (W − 420)/3: на 900 это 160px, на 768 — 116px. Карточка выходила УЖЕ
   телефонной (174px при 390px, 2-col) — сетка не помещалась, а не имя не влезало.
   Замер на 144 реальных именах каталога, десктопный резерв имени (3 строки,
   13.5px; планшет едет по десктопным рельсам, M24) — обрезано имён / худший
   перелив:
     3 колонки: 768 → 116px 96% / 189px;  900 → 160px 61% / 94px;
                1024 → 201px 35% / 56px; 1100 → 227px 17% / 37px; 1440 → 340px 0%.
     2 колонки: 768 → 181px 50% / 75px;  900 → 247px 10% / 37px;
                1024 → 309px  2% / 19px; 1100 → 347px  0%.
   Почему НЕ сайдбар: при 240px три колонки на 768 дают 129px (89% обрезано) —
   не лечит, а `.xf-tip__bubble` внутри фасетов вылезает из сайдбара на 22px.
   Почему НЕ auto-fill: одного minmax-минимума, который держит 3 колонки на 1440
   и даёт 2 на 900, не существует — любой такой минимум ломает десктоп на 4.
   Почему порог 1100, а не 1023: на 1024 три колонки — это всё те же 201px и 35%
   обрезанных, то есть баг переехал бы на пиксель выше порога. 1100 — уже
   имеющийся в шаблоне порог «десктоп сузился, роняем колонку»: на нём
   header.css сжимает раскладку мега-меню, footer.css роняет подвал с 5 колонок
   на 3, configurator.css убирает боковую колонку. Сверху порога 3 колонки
   стартуют с 227px, снизу 2 колонки упираются в 347px ≈ канонические 340px
   десктопной карточки — обе стороны стыка живут в норме.
   Рельс, сайдбар и gap не тронуты; резерв имени — в home.css, здесь его нет
   (см. комментарий ниже, #340). */
@media (min-width: 768px) and (max-width: 1100px) {
  .xf-secpage__grid { grid-template-columns: repeat(2, 1fr); }
}
/* [#340] Снят кросс-файловый дубль `.xf-secpage__grid .xf-product-card__title
   { min-height: 52px }` (и его мобильный близнец 58px ниже). Резерв под имя
   живёт в home.css одним блоком min-height == max-height == clamp; здешние
   числа отстали от него и перебивали ТОЛЬКО min-height (специфичность 0,2,0
   против 0,1,0), из-за чего коробка имени гуляла внутри ряда, а цены вместе с
   ней: замер на листинге раздела дал разброс 5px на десктопе и 18px на
   телефоне (58 против 76). Владелец `.xf-product-card__title` — home.css;
   ширину карточки раздела задаёт сетка выше, высоту имени — не задаём.

   [#315] По тому же принципу здесь НЕТ и правила для строки характеристик.
   Оно есть — `.xf-secpage__grid .xf-product-card__spec--off { display: none }`
   — но лежит в home.css рядом с самим `.xf-product-card__spec`: `.xf-secpage__grid`
   тут только скоуп, а субъект правила — карточка. Если ищешь, почему в
   листинге раздела строка характеристик схлопывается, а на главной нет —
   grep по `__spec--off` в css/, файл один. */

.xf-secpage__empty {
  padding: 40px 24px;
  text-align: center;
  background: var(--surface);
  border: 1px dashed var(--line-soft);
  border-radius: 14px;
  color: var(--text-2);
  font-size: 14px;
}
.xf-secpage__empty a { color: var(--accent-2); margin-left: 4px; }

/* ─── Pagination ──────────────────────────────────────────── */
.xf-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--line-soft);
  gap: 16px;
  flex-wrap: wrap;
}
.xf-pagination__meta {
  font-size: 12.5px;
  color: var(--text-3);
}
.xf-pagination__pages {
  display: flex;
  gap: 4px;
  align-items: center;
}
.xf-pagination__page {
  min-width: 36px;
  height: 36px;
  padding: 0 10px;
  border-radius: 8px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background .15s, color .15s, border-color .15s;
}
@media (hover: hover) {
  .xf-pagination__page:hover { border-color: var(--line); color: var(--text); }
}
.xf-pagination__page.is-active {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}
.xf-pagination__gap {
  color: var(--text-3);
  font-family: var(--font-mono);
  padding: 0 4px;
}

/* ─── Filter sheet — FAB + close + overlay (mobile-only controls) ───
   DOM is rendered unconditionally in section.php leaf branch; display is
   CSS-gated so desktop users never see them and assistive trees stay
   clean. Mobile styles live in the @media block below. */
.xf-filter-fab,
.xf-filter-sheet__overlay,
.xf-filter-sheet__head { display: none; }

/* ─── Mobile (≤ 767px) ──────────────────────────────────────
   Sidebar → bottom-sheet slide-up; product grid 2-col;
   toolbar + pagination компактнее; кнопка «Фильтры» у низа экрана
   открывает sheet (как она стоит — у правила .xf-filter-fab ниже). */
@media (max-width: 767px) {
  .xf-secpage {
    grid-template-columns: 1fr;
    gap: 12px;
    /* Был запас 88px под fixed-кнопку «Фильтры»; кнопка теперь стоит над
       листанием (#776, .xf-filter-fab ниже) — остаётся воздух до подвала. */
    padding-bottom: 24px;
  }
  /* [#646] Цель перехода «сразу к результату» (section.php, скрипт листинга,
     п. 5): колонка встаёт под липкую шапку, а не за ней. Величина — та же,
     что у #content (header.css, #388). */
  .xf-secpage__content { scroll-margin-top: calc(var(--xf-header-h, 118px) + 8px); }

  /* Filters aside → fixed bottom sheet. Hidden by default, body class flips it in. */
  .xf-secpage__filters {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: 85vh;
    max-height: 85vh;
    z-index: 80;
    background: var(--bg);
    border-radius: 20px 20px 0 0;
    box-shadow: 0 -18px 40px -12px rgba(0, 0, 0, 0.3);
    transform: translateY(100%);
    transition: transform .3s cubic-bezier(.4, 0, .2, 1);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    /* iOS home-indicator safe-area — внутренний отступ снизу, чтобы
       кнопка «Применить» не пряталась под системной полосой. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  body.xf-filter-open .xf-secpage__filters { transform: translateY(0); }
  /* Шторку фильтра прячет САМ transform (opacity/visibility тут нет), поэтому
     множитель --xf-motion к ней неприменим — обнулив смещение, мы бы показали
     её насовсем. Гасим только транзишен: шторка появляется мгновенно, без
     выезда на 85vh. Транзишен здесь только на transform (см. выше), так что
     ничего кроме движения не теряем (#330). */
  @media (prefers-reduced-motion: reduce) {
    .xf-secpage__filters { transition: none; }
  }

  /* Sheet head — title + close, pinned top. */
  .xf-filter-sheet__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--line-soft);
    flex: 0 0 auto;
  }
  .xf-filter-sheet__title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text);
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .xf-filter-sheet__close {
    width: 44px;
    height: 44px;
    border-radius: 12px;
    background: var(--surface-2);
    border: 1px solid var(--line-soft);
    color: var(--text-2);
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 0;
  }

  /* Form inside sheet — full-height flex column, body scrolls, actions pinned. */
  .xf-filters {
    position: static;
    max-height: none;
    height: 100%;
    flex: 1 1 auto;
    padding: 14px 18px 16px;
    min-height: 0;
  }
  .xf-filters__head { display: none; }
  .xf-filters__body {
    overflow-y: auto;
    padding-right: 6px;
    margin-right: -6px;
  }
  .xf-filters__actions {
    padding-top: 12px;
    margin-top: 10px;
    background: var(--bg);
  }
  .xf-filters__actions::before {
    top: -16px;
    height: 16px;
    right: 0;
  }

  /* Overlay — sits below sheet, above content. */
  .xf-filter-sheet__overlay {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 79;
    background: oklch(0 0 0 / 0.5);
    opacity: 0;
    pointer-events: none;
    transition: opacity .28s ease;
  }
  body.xf-filter-open .xf-filter-sheet__overlay {
    opacity: 1;
    pointer-events: auto;
  }
  body.xf-filter-open { overflow: hidden; }
  /* [#363-r3] Утечка скролла из открытой шторки. Замер на живом /dev
     (390×844, iPhone UA, 2026-08-06): открываем шторку на позиции 1201px,
     докручиваем список фильтров до конца — и каталог ПОД шторкой уезжает на
     5401px. Покупатель закрывает шторку и оказывается на другом конце
     страницы: выглядит как поломка сайта, а не как своё действие.
     Причина двойная. `body { overflow: hidden }` выше не держит, потому что
     скроллит документ `html` (проверено: `document.scrollingElement === html`),
     а `overscroll-behavior` у всех трёх вложенных скроллеров — `auto`, то
     есть докрутка до края честно передаётся наверх (scroll chaining).
     Лечим оба конца: цепочку рвём `contain` на каждом скроллере шторки,
     документ запираем через `:has()` — класс висит на body, а запирать надо
     html. `:has()` есть во всех браузерах, куда ходит этот сайт; если он не
     поддержан, остаётся прежнее поведение, не хуже текущего. */
  html:has(> body.xf-filter-open) { overflow: hidden; }
  body.xf-filter-open .xf-secpage__filters,
  body.xf-filter-open .xf-filters__body,
  body.xf-filter-open .xf-filter-group__options,
  body.xf-filter-open .xf-filter-more__body { overscroll-behavior: contain; }

  /* Кнопка «Фильтры» — пилюля по центру низа экрана. [#776] Была fixed и
     лежала на листании, пока оно проезжало низ экрана: на 360 ряд
     переносится, и «Далее» второй строкой вставала ровно под неё. Теперь
     липкая: в разметке стоит между списком и листанием (section.php), пока
     список проезжает — прижата к низу экрана, у конца списка встаёт на своё
     место над листанием и уходит вверх вместе с ним; на «Назад/Далее» и на
     подвал не ложится.
     Липкая не поднимается выше начала своей колонки, а в разделах с тегами
     колонка начинается ниже первого экрана — там кнопка пропала бы с первого
     экрана. Поэтому, пока колонка ниже, скрипт листинга (section.php, п. 4)
     ставит data-xf-filter-float, и кнопка висит fixed, как раньше; переход
     в липкую — в той же точке экрана, без скачка.
     safe-area — чтобы не уезжала под iOS home-indicator. */
  .xf-filter-fab {
    display: flex;
    width: max-content;
    margin: 20px auto 0;
    position: sticky;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    z-index: 70;
    align-items: center;
    gap: 8px;
    padding: 12px 18px;
    background: var(--accent);
    color: #fff;
    border: 0;
    border-radius: 999px;
    font-family: var(--font-sans);
    font-size: 13.5px;
    font-weight: 600;
    letter-spacing: 0.02em;
    box-shadow: 0 14px 30px -10px oklch(0.45 0.22 270 / 0.55);
    cursor: pointer;
    transition: transform .15s, box-shadow .15s;
  }
  .xf-filter-fab:active { transform: scale(calc(1 - 0.03 * var(--xf-motion, 1))); }
  .xf-filter-fab[data-xf-filter-float] {
    position: fixed;
    left: 50%;
    margin: 0;
    transform: translateX(-50%);
  }
  .xf-filter-fab[data-xf-filter-float]:active { transform: translateX(-50%) scale(calc(1 - 0.03 * var(--xf-motion, 1))); }
  /* Над баннером cookie, как кружок чата и тосты (chat.css, base.css): у
     первого визита баннер лежал поверх кнопки. */
  body.xf-cookie-up .xf-filter-fab { bottom: calc(var(--xf-cookie-h, 120px) + 16px); }
  .xf-filter-fab svg { width: 16px; height: 16px; }
  .xf-filter-fab__badge {
    font-family: var(--font-mono);
    font-size: 11.5px;
    min-width: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.22);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    letter-spacing: 0;
  }
  /* Hide badge when empty (when N=0 no element is rendered, so this is a noop) */
  .xf-filter-fab__badge:empty { display: none; }

  /* Product grid — 2 col. Keep .xf-product-card tweaks from home.css
     (высота имени тоже оттуда — см. комментарий у .xf-secpage__grid, #340). */
  .xf-secpage__grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }

  /* Page head / breadcrumbs */
  .xf-catpage__head { margin-bottom: 4px; }
  .xf-catpage__title { font-size: 26px; letter-spacing: -0.8px; }
  .xf-catpage__title-row { flex-wrap: wrap; gap: 8px; }
  /* Inline-flow on mobile + slash via ::before, не как отдельный sibling.
     Когда слеш — отдельный flex/inline child, browser breaks между «/» и
     следующим элементом, и слеш висит сиротой в конце строки. Решение
     keeps existing markup (<span>/</span> across personal/order/landing),
     hides separator-spans on mobile (even children — see crumbs index
     pattern: a, span, a, span, [a|span]) и добавляет «/» через ::before
     на нечётных не-первых детях — слеш становится частью inline-run'а
     анкора и переносится с ним вместе. */
  .xf-crumbs { font-size: 12px; display: block; line-height: 1.6; }
  .xf-crumbs > a, .xf-crumbs > span { display: inline; }
  .xf-crumbs > *:nth-child(even) { display: none; }
  .xf-crumbs > *:nth-child(odd):not(:first-child)::before {
    content: "/";
    margin-right: 6px;
    color: var(--text-3);
  }

  /* Toolbar: result сверху, sort full-width снизу. */
  .xf-secpage__toolbar { flex-direction: column; align-items: stretch; gap: 10px; margin-bottom: 14px; }
  .xf-secpage__tools { justify-content: space-between; gap: 10px; }
  /* [#830] Сортировка — 16 px: iPhone увеличивает страницу на фокусе поля
     мельче 16 px (выпадающий список тоже). Поля по вертикали 9 вместо 10 —
     высота почти та же (41 против 39). */
  .xf-select { flex: 1; padding: 9px 34px 9px 12px; font-size: 16px; }
  .xf-secpage__sort-label { flex: 0 0 auto; }

  /* Chips row — wrap как есть, просто компактнее */
  .xf-secpage__chips { gap: 6px; margin-bottom: 10px; }
  .xf-chip { padding: 5px 8px 5px 10px; font-size: 12px; }

  /* Pagination — компактнее */
  .xf-pagination {
    flex-direction: column;
    align-items: stretch;
    margin-top: 22px;
    padding-top: 18px;
    gap: 10px;
  }
  .xf-pagination__meta { text-align: center; font-size: 11.5px; }
  .xf-pagination__pages { justify-content: center; flex-wrap: wrap; }
  .xf-pagination__page { min-width: 44px; height: 44px; padding: 0 10px; font-size: 13.5px; }
  .xf-check { min-height: 44px; }
}
/* Тёмная тема: тень «Фильтров» — от краски действия тёмной (296), а не от
   индиго светлой (270); блик по верхней кромке, как у залитых кнопок. */
html[data-theme="dark"] .xf-filter-fab {
  box-shadow: var(--nx-key), 0 14px 30px -12px oklch(0.50 0.24 296 / 0.6);
}

/* ══════════════════════════════════════════════════════════════════════
   [#363/#384] ЛИСТИНГ И ФИЛЬТР — узлы, добавленные редизайном листинга.
   Скоуп `body.site_d1` снят при GO владельца (#384): правила работают на
   обоих сайтах. Правила ниже сознательно ПЕРЕБИВАЮТ базовые той же
   специфичностью и держатся на порядке в файле — не переносить выше.
   Разметку этих узлов рендерит section.php.
   ══════════════════════════════════════════════════════════════════════ */

/* ─── «Ещё N характеристик» — хвост фильтра ───────────────────────────
   Не прячем ничего насовсем: первые 10 групп на месте, остальное — в один
   ряд. Читается как ящик, а не как кнопка (Pill-Is-Not-Button): плоская
   поверхность, никакого акцента, вес держит только иконка-шеврон. */
.xf-filter-more {
  border-top: 1px solid var(--line-soft);
  margin-top: 4px;
}
.xf-filter-more__summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 14px 0;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text-2);
  transition: color .15s;
}
.xf-filter-more__summary::-webkit-details-marker { display: none; }
.xf-filter-more__summary::after {
  content: "";
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>") center/contain no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>") center/contain no-repeat;
  transform: rotate(-90deg);
  transition: transform .18s;
}
.xf-filter-more[open] > .xf-filter-more__summary::after { transform: rotate(0); }
.xf-filter-more[open] > .xf-filter-more__summary { color: var(--text); }
@media (hover: hover) {
  .xf-filter-more__summary:hover { color: var(--text); }
}
/* Первая группа внутри ящика не должна рисовать вторую линию поверх
   границы самого ящика. */
.xf-filter-more__body > .xf-filter-group:first-of-type { border-top: 0; }

/* ─── Поиск по значениям внутри длинной группы ────────────────────────
   Живёт НАД списком: у .xf-filter-group__options свой скролл на 260px, и
   уехавшее вместе с ним поле поиска было бы бесполезно. */
.xf-facet-search {
  margin: 0 0 10px;
}
.xf-facet-search__input {
  width: 100%;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--text);
  /* [#373] `outline: 0` тут нет намеренно: гасить глобальное фокус-кольцо
     на поиске по значениям фильтра было бы нечем оправдать. */
  transition: border-color .15s;
  -webkit-appearance: none;
  appearance: none;
}
.xf-facet-search__input::placeholder { color: var(--text-3); }
.xf-facet-search__input:focus { border-color: var(--accent-line); }
.xf-facet-search__input::-webkit-search-cancel-button { cursor: pointer; }
.xf-facet-search__none {
  margin: 2px 0 0;
  font-size: 12.5px;
  color: var(--text-3);
}

/* ─── Значение, под которое ничего нет ────────────────────────────────
   Счётчик 0 появляется при комбинации фильтров. Раньше такой пункт
   кликался и уводил в пустой результат; теперь он нажатие не принимает и
   говорит об этом не только цветом — курсором и снятой интерактивностью
   (color-blind safe: состояние не передаётся одним лишь цветом). */
.xf-check--empty {
  cursor: not-allowed;
  color: var(--text-3);
}
.xf-check--empty .xf-check__box { opacity: .45; }
.xf-check--empty .xf-check__label { text-decoration: line-through; text-decoration-thickness: 1px; }

/* ─── «Применить» отвечает на действие ────────────────────────────────
   Класс xf-filters--live вешает скрипт листинга: без JS кнопка остаётся
   обычной primary, как была. Пока форму не трогали, применять нечего —
   кнопка держится тихой (заодно это минус индиго на экране, см. The
   One-Voice Indigo Rule). Первое же изменение делает её primary. */
.xf-filters--live .xf-filters__apply {
  background: var(--surface-2);
  color: var(--text-2);
  border: 1px solid var(--line-soft);
}
.xf-filters--live .xf-filters__apply.is-dirty {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}
@media (hover: hover) {
  .xf-filters--live .xf-filters__apply:hover { color: var(--text); }
  .xf-filters--live .xf-filters__apply.is-dirty:hover { color: #fff; }
}

/* ─── «Применить» больше не съедает низ списка ────────────────────────
   На десктопе панель действий приколота к низу экрана (#43, чтобы кнопка
   была достижима без прокрутки), а при поднятом cookie-баннере — ещё на
   72px выше (--xf-cookie-h). Панель фильтра при этом длиннее экрана, так
   что кнопка висит ПОВЕРХ живого списка и перехватывает нажатия.
   Замер на живом /dev 2026-08-06, мониторы, 1440×950, страница прокручена,
   список фильтра домотан до конца: строка «Ещё 43 характеристики» стоит на
   826–871, кнопка — на 839–879. elementFromPoint во всех трёх точках
   строки возвращает `xf-btn xf-filters__apply`; настоящий клик мышью в её
   центр отправляет форму и перезагружает страницу вместо раскрытия ящика
   (`navigated: true`, `moreOpen: false`). То есть 43 характеристики из 56
   недостижимы мышью — и именно у ПЕРВОГО визита, потому что поднимает
   кнопку в список тот самый cookie-баннер. У вернувшегося посетителя
   баннера нет, кнопка стоит ниже и ящик открывается.
   Лечим причиной: список резервирует под панель собственное дно, и ни одна
   строка под неё больше не попадает (после правки все три точки строки
   возвращают summary, `moreOpen: true`, перезагрузки нет). Заодно убираем
   границу-обманку «здесь список кончился» — за ней ещё 40 брендов — и
   удлиняем растворение с 20 до 44px: обрыв становится затуханием. Кольцо
   фонового цвета вокруг кнопки — не тень (flat-by-default не нарушаем), а
   подложка, чтобы подпись читалась над затухающей строкой.
   Только ≥768px: в мобильной шторке панель стоит своим дном и не всплывает
   (замер: actions y=775 при экране 844, ничего не перекрыто). */
@media (min-width: 768px) {
  .xf-filters__body {
    padding-bottom: calc(64px + var(--xf-cookie-h, 0px));
    scroll-padding-bottom: calc(64px + var(--xf-cookie-h, 0px));
  }
  .xf-filters__actions {
    border-top: 0;
    background: transparent;
  }
  .xf-filters__actions::before {
    top: -44px;
    height: 44px;
  }
  .xf-filters__apply {
    box-shadow: 0 0 0 5px var(--bg);
  }
}

/* ─── Пагинация: «Назад» / «Далее» ────────────────────────────────────
   Шаг на соседнюю страницу — самое частое действие на 22 страницах, и до
   сих пор у него не было кнопки. Ghost, не primary: главное действие на
   экране одно (The Single-Primary Rule). */
.xf-pagination__step {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 36px;
  padding: 0 12px;
  border-radius: 9px;
  border: 1px solid var(--line-soft);
  font-size: 13px;
  color: var(--text-2);
  transition: color .15s, border-color .15s;
}
.xf-pagination__step svg { flex: 0 0 auto; }
@media (hover: hover) {
  .xf-pagination__step:not(.is-disabled):hover { color: var(--text); border-color: var(--line); }
}
.xf-pagination__step.is-disabled {
  opacity: .4;
  pointer-events: none;
}

/* [#363-r2 → #372] Локальный обход снят. Он красил номер активной страницы
   тёмной подписью, потому что белым по осветлённому accent'у выходило 3.20:1.
   В #372 accent тёмной темы для d1 потемнел (0.68 → 0.58, tokens.css:288), и
   расклад перевернулся: белая подпись даёт 4.52:1, тёмная — 4.37:1. Обход стал
   хуже дефолта, поэтому его тут больше нет — тёмная тема берёт белую подпись
   из общего правила .xf-pagination__page.is-active. */

/* ─── Мобильное ───────────────────────────────────────────────────── */
@media (max-width: 767px) {
  /* Заголовок группы — главный орган управления в шторке с десятками
     групп, а его высота была 21px при 44px у чекбоксов и пагинации
     (замер 2026-08-06 на живом /dev). Растим ЗОНУ НАЖАТИЯ, а не высоту
     шторки: 23 добавленных пикселя забираем обратно у собственных
     отступов группы. Итог по мониторам — 44px палец и при этом
     плотнее, чем было. */
  .xf-filter-group { padding: 2px 0; }
  .xf-filter-group > summary {
    min-height: 44px;
    display: flex;
    align-items: center;
  }
  .xf-filter-group[open] > summary { margin-bottom: 6px; }
  .xf-filter-more__summary { min-height: 48px; }
  .xf-filter-group__more > summary { min-height: 44px; display: flex; align-items: center; }
  .xf-facet-search__input { padding: 11px 12px; font-size: 16px; /* <16px → iOS зумит страницу на фокусе */ }
  /* [#830] «от — до» цены и числовых свойств — тоже 16 px (было 13): та же
     причина. Оба диапазона фильтра в section.php — одна и та же разметка
     .xf-price-range__input, других живых копий у фильтра каталога нет
     (у конфигуратора свой, #798). */
  .xf-price-range__input input { font-size: 16px; }
  .xf-pagination__step { height: 44px; padding: 0 14px; }

  /* [#363-r2] Пальцевые цели в собственном потоке фильтра. Замер на живом
     /dev 2026-08-06, 390×844: «Фильтры» 121×41, «Применить» 354×40, чип
     активного фильтра 358×33. Три самых нажимаемых контрола сценария
     «открыть → выбрать → применить → снять» — и все три ниже 44px. Строка
     значения (.xf-check) и крестик шторки уже 44, так что добираем только
     отставших. */
  .xf-filter-fab { min-height: 44px; }
  .xf-filters__apply { min-height: 44px; }
  .xf-chip { min-height: 44px; }

  /* Сброс в шапке шторки (разметку даёт section.php). */
  .xf-filter-sheet__reset {
    margin-left: auto;
    margin-right: 12px;
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    font-family: var(--font-mono);
    font-size: 11.5px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--text-3);
  }

  /* Лаунчер живого чата (.xf-chat, chat.css) стоит fixed z-index:100 и
     перекрывал правый край «Применить» в открытой шторке: elementFromPoint
     на 95% ширины кнопки возвращал чат, а не кнопку (замер 2026-08-06,
     мониторы, iPhone 13). Пока шторка открыта — чат уходит. Это
     единственное правило блока, дотягивающееся до чужого класса:
     .xf-chat принадлежит chat.css, здесь мы меняем не его вид, а порядок
     на экране в состоянии .xf-filter-open, которым владеет этот файл. */
  body.xf-filter-open .xf-chat { display: none; }
}

/* End */


/* Start:/local/templates/xcore_front/css/product.css?1792653888108983*/
/* OWNS: .xf-product-* КРОМЕ .xf-product-card-* (листинговая карточка — home.css), .xf-specs-*, .xf-lightbox-*, .xf-similar-*, .xf-reviews-* (on PDP), .xf-buy-*, .xf-btn-buy(-*), .xf-btn-contact, .xf-tip-*, .xf-key-* (сводка «Ключевое» в hero), .xf-pdp (скоуп-класс страницы товара) и .xf-pdp--tight (первый экран без строки «бренд · артикул» над названием, ставит скрипт в element.php — #722) */
/* Product card — hero (gallery + info) + specs + description + similar rail */

/* Кросс-нота (#384, промоушен превью #364/#381 — бывший css/pdp-preview.css,
   влит сюда и удалён). Файл сужает несколько ЧУЖИХ классов до страницы товара;
   все такие правила несут скоуп `.xf-pdp` — его печатает include/catalog/element.php
   на .xf-catpage__head и .xf-home__section. Без скоупа они потекли бы на каталог
   и главную, поэтому снимать его нельзя:
     catalog.css     — .xf-catpage__head (отступы шапки на телефоне)
     home.css        — .xf-home__section, .xf-art--photo, .xf-art__img
     catalog.css     — .xf-crumbs (класс его; телефонная раскладка крошек — в
                       catsection.css): на карточке прячем хвост-имя товара,
                       с #704 строка одна, с прокруткой
     layout.css      — .middle (верхний отступ сужается только когда её прямой
                       ребёнок — шапка карточки товара)
     ui.css          — .xf-admin-pencil (место карандаша админа в колонке
                       товара на телефоне, #704)
   Список сверен по css/ 2026-10-12 — при новом чужом селекторе дополнять.
   Обратная связь: css/header.css читает .xf-pdp как признак страницы товара
   (`.xf-mcall:has(~ main .xf-pdp)`, #704) — класс со страницы не снимать. */

.xf-product {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: 48px;
  align-items: flex-start;
  margin-bottom: 56px;
}

/* ─── Gallery ─────────────────────────────────────────────── */
.xf-product-gallery {
  display: flex;
  flex-direction: column;
  gap: 14px;
  position: sticky;
  top: 180px;
}
.xf-product-gallery__main {
  aspect-ratio: auto;
  width: 100%;
  max-width: 560px;
  margin: 0 auto;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 18px;
  overflow: hidden;
  position: relative;
  container-type: inline-size;
}
/* Icon-fallback (no photo) keeps square; photo-case plate adapts to image. */
.xf-product-gallery__main:not(:has(.xf-art--photo)) { aspect-ratio: 1; }
.xf-product-gallery__main .xf-art {
  width: 100%;
  height: 100%;
}
.xf-product-gallery__main .xf-art--photo {
  aspect-ratio: auto;
  min-height: 380px;
  padding: 30px 20px;
  box-sizing: border-box;
  display: grid;
  place-items: center;
}
/* Wrapper schrink'ает до actual img-rect — плашка guarantee
   позиционируется bottom-right самого фото (а не угла __main square),
   физически перекрывает чужой watermark catalog.onliner.by в правом-нижнем
   углу image-rect. Container-type на __main + cqi на img разрывают circular
   percentage в inline-flex wrapper'е. Радиуса на wrap'е нет намеренно (#294):
   он клипал углы full-bleed рендеров — товар с прямыми углами выглядел
   скруглённым. Скругление живёт на хроме (__main 18px), не на пикселях фото. */
.xf-product-gallery__main .xf-art--photo .xf-art__photo-wrap {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  overflow: hidden;
}
.xf-product-gallery__main .xf-art--photo .xf-art__img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 360px;
}
.xf-product-gallery__guarantee {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 2;
  pointer-events: none;
  box-sizing: border-box;
  min-width: 148px;
  padding: 7px 14px;
  text-align: center;
  font-family: var(--font-sans);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.005em;
  color: oklch(0.96 0.005 285);
  background: oklch(0.30 0.04 268);
  border-radius: 10px 0 0 0;
  line-height: 1.2;
  white-space: nowrap;
}
/* ─── Открывашка лайтбокса над главным фото (#381) ─────────────
   Лайтбокс открывался только кликом по самому фото: с клавиатуры в него было
   не попасть вовсе, а мышью единственной подсказкой служил курсор zoom-in —
   то есть подсказка появлялась уже после наведения. Кнопка даёт и таб-стоп, и
   видимую аффорданс. Место — свободный верхний-правый угол плиты: плашка
   гарантии сидит в нижнем-правом (выше по файлу), а фото центрировано внутри
   падинга 30/20, так что кнопка почти всегда лежит на фоне плиты, не на снимке.
   Тень не ставим (DESIGN.md, The Flat-By-Default Rule) — читаемость держит
   рамка + тональная ступень surface. */
.xf-pdp .xf-product-gallery__zoom {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text-2);
  cursor: zoom-in;
  transition: background .15s, color .15s, border-color .15s, transform .15s;
}
.xf-pdp .xf-product-gallery__zoom:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--line);
  transform: translateY(calc(-1px * var(--xf-motion, 1)));
}
.xf-pdp .xf-product-gallery__zoom:active { transform: translateY(0); }

/* Per-thumb плашка в ≤767-карусели (#9); на desktop миниатюры 74px — скрыта,
   wrap нейтрален через display:contents. */
.xf-product-gallery__thumb-wrap { display: contents; }
.xf-product-gallery__guarantee--thumb { display: none; }
.xf-product-gallery__thumbs {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}
.xf-product-gallery__thumb {
  width: 74px;
  height: 74px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  overflow: hidden;
  cursor: pointer;
  transition: border-color .15s, transform .15s;
}
@media (hover: hover) {
  .xf-product-gallery__thumb:hover { border-color: var(--line); transform: translateY(calc(-1px * var(--xf-motion, 1))); }
}
.xf-product-gallery__thumb.is-active { border-color: var(--accent-line); }
.xf-product-gallery__thumb img {
  width: 100%; height: 100%; object-fit: contain;
}
/* Тёмная тема: миниатюра (на телефоне это и есть карусель фото) — тот же
   лайтбокс, что у .xf-art--photo (home.css): белая плита и тон наложением
   multiply поверх плиты и фото сразу, слой — --nx-lightbox (tokens.css). */
html[data-theme="dark"] .xf-product-gallery__thumb {
  position: relative;
  background: #fff;
  isolation: isolate;
}
html[data-theme="dark"] .xf-product-gallery__thumb::after {
  content: "";
  display: block;
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  mix-blend-mode: multiply;
  background: var(--nx-lightbox);
}

/* ─── Info column ─────────────────────────────────────────── */
.xf-product-info__eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-3);
  display: inline-flex;
  align-items: center;
  gap: 2px;
}
.xf-product-info__article { color: var(--accent-2); }

.xf-product-info__title {
  margin: 10px 0 18px;
  font-size: 32px;
  font-weight: 600;
  letter-spacing: -0.8px;
  line-height: 1.15;
  color: var(--text);
}

.xf-product-info__meta {
  display: flex;
  gap: 18px;
  align-items: center;
  flex-wrap: wrap;
  font-size: 13.5px;
  color: var(--text-3);
  margin-bottom: 28px;
}
.xf-product-info__stock::before {
  content: "●";
  margin-right: 6px;
  font-size: 9px;
  vertical-align: middle;
}
.xf-product-info__stock.is-in  { color: var(--success); }
.xf-product-info__stock.is-out { color: var(--text-3); }
.xf-product-info__stock.is-out::before { content: "○"; }

/* ─── Наличие + срок получения одной строкой (#364) ────────────
   PRODUCT.md: «наличие и срок получения — это само предложение, а не сноска
   мелким шрифтом». Срок берётся из уже согласованной плашки доставки — новых
   обещаний покупателю здесь не появляется.

   Вес пары (#381-r3). Замерено на живой странице: наличие 13.5 px/400, срок
   13.5 px/400 `--text-3` (4.65:1) — самое слабое начертание блока, слабее
   артикула рядом. При том что Positioning называет ровно эту пару единственным
   честным преимуществом магазина. Поднимаем на ступень: 14 px, срок `--text-2`,
   наличие полужирным. Формулировки НЕ трогаем — обещание покупателю остаётся
   тем же, меняется только его вес на экране. */
.xf-pdp .xf-product-info__stock { font-size: 14px; font-weight: 600; }
.xf-pdp .xf-product-info__ready {
  color: var(--text-2);
  font-size: 14px;
}
.xf-pdp .xf-product-info__ready::before {
  content: "·";
  margin-right: 8px;
  color: var(--line);
}

.xf-product-info__price-row {
  padding: 22px 0 26px;
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: 24px;
}
.xf-product-info__price {
  font-family: var(--font-mono);
  font-size: 36px;
  font-weight: 600;
  letter-spacing: -1px;
  color: var(--text);
  line-height: 1;
  display: inline;
}
.xf-product-info__price-cur {
  font-size: 18px;
  color: var(--text-3);
  margin-left: 6px;
  font-weight: 500;
}
.xf-product-info__price-old {
  display: block;            /* struck "was" BELOW the live price (matches the card treatment + the Price-strike invariant «под живой ценой»), not inline-beside */
  margin: 5px 0 0;
  /* Onest, не mono: «вшитый» слэш-ноль XCore Mono + line-through = двойное
     перечёркивание. Старая цена тихая → плоский овал Onest. Парно с
     .xf-price__was (ui.css). #92 */
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: 16px;
  font-weight: 500;
  color: var(--text-2);
  line-height: 1.1;
  text-decoration: line-through;
  text-decoration-thickness: 1px;
  text-decoration-color: currentColor;
}
.xf-product-info__installment {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin: -8px 0 24px;
}
/* [#305] Нейтральный, не --success: зелёный на PDP закреплён за наличием
   (.xf-product-info__stock.is-in выше по файлу). Пара соседних строк одним
   цветом читалась как «рассрочка = что-то в наличии». */
.xf-product-info__installment-price {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-2);
  letter-spacing: -.2px;
  line-height: 1.3;
}
.xf-product-info__installment-toggle {
  background: transparent;
  border: none;
  padding: 0;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  color: var(--accent-2);
  cursor: pointer;
  transition: opacity .15s;
}
@media (hover: hover) {
  .xf-product-info__installment-toggle:hover { opacity: .7; }
}
/* Мишень «Варианты покупки» (#364). Замерено: 132.4×17 px. WCAG 2.2 AA (2.5.8)
   просит 24×24, палец хочет 44. Приём тот же, что у «Все характеристики →»
   ниже: высоту добираем паддингом, а строку держим на месте отрицательным
   полем — растёт область нажатия, а не отступ между блоками. */
.xf-pdp .xf-product-info__installment-toggle {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin: -13px 0;
}
/* [#383] .xf-product-info__installment-term («· 48 мес») — без своего правила:
   срок наследует голос суммы. Хук-класс держим для будущих правок и для
   сверки с попапом ниже (там срок в COLUMN_3_CALC_TEMPLATE, тоже «мес»). */
.xf-product-info__installment-src .installment__link { display: none; }
/* Попап dev-seo:installment (.installment__* — карточка/таблица/форма) перекрашен
   в installment.css; здесь только inline-блок .xf-product-info__installment-*. */

/* Variant switcher (M22 pilot — БП «Мощность»). Single-select toggle group
   (Pill-Is-Not-Button): чипы — маркеры состояния, текущий active (не ссылка),
   соседи навигируют на отдельные карточки (Onliner-style cross-card jump).
   Числа = data → JetBrains Mono. Эстетика общая с .xf-chip (search.css), но
   класс свой — namespace .xf-product-* (без коллизии с .xf-chip). */
.xf-product-info__variants {
  margin: -8px 0 24px;
}
.xf-product-info__variants-label {
  display: block;
  margin-bottom: 9px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-3);
}
.xf-product-info__variants-row {  /* <ul> — нативная list/listitem семантика; сбрасываем маркеры */
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.xf-product-info__variants-row > li { display: flex; }   /* li-обёртка не ломает flex-раскладку чипов */
.xf-product-info__variant {
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  padding: 7px 13px;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: 13.5px;
  font-weight: 500;
  letter-spacing: -.2px;
  text-decoration: none;
  transition: background .15s, border-color .15s, color .15s;
  white-space: nowrap;
}
.xf-product-info__variant-unit {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-3);
}
@media (hover: hover) {
  .xf-product-info__variant:hover {
    border-color: var(--accent-line);
    color: var(--text);
  }
}
.xf-product-info__variant.is-active {
  background: var(--accent-dim);
  border-color: var(--accent-line);
  color: var(--accent-2);
  cursor: default;
}
.xf-product-info__variant.is-active .xf-product-info__variant-unit {
  color: var(--accent-2);
  opacity: .9;          /* было .75 → не дотягивало AA на активной плашке (3.63:1) */
}

/* ── Цвет-ось (корпуса): свотч + слово. Модификатор --color на контейнере
   (element.php, когда axis==='color'). Слово = Onest (Mono-as-Data: mono только
   для чисел/SKU; цвет — слово). Свотч = КОНТЕНТ-цвет товара (инлайн hex, XSS-гард
   в PHP), индиго не расходует. Обводка свотча = inset hairline (Flat-By-Default,
   не elevation). grep 'swatch' в css/ = 0 до этого (нет коллизии). */
.xf-product-info__variants--color .xf-product-info__variant {
  font-family: var(--font-sans);
  font-size: 13px;
  letter-spacing: 0;
  gap: 7px;
  align-items: center;          /* центр по свотчу, не baseline */
  padding: 6px 13px 6px 8px;    /* слева теснее — свотч уравновешивает */
}
.xf-product-info__variant-swatch {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
}
/* одиночный кружок; цвет приходит инлайн style="background:#.." */
.xf-product-info__variant-swatch-dot {
  width: 14px;
  height: 14px;
  border-radius: 999px;
  background: var(--surface-3);                       /* fallback, если hex не пришёл */
  box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.14);    /* hairline — белый/серебро видны на surface */
}
/* комбо (2 цвета): две половинки одного кружка, шов по центру */
.xf-product-info__variant-swatch--combo .xf-product-info__variant-swatch-dot:first-child {
  border-radius: 999px 0 0 999px;
}
.xf-product-info__variant-swatch--combo .xf-product-info__variant-swatch-dot:last-child {
  border-radius: 0 999px 999px 0;
  margin-left: -1px;                                  /* стык без щели */
}
/* прозрачный — градиент классом (linear-gradient не проходит inline hex-гард) */
.xf-product-info__variant-swatch-dot--clear {
  background: linear-gradient(135deg, #e9eef2 0%, #cdd6dd 50%, #e9eef2 100%);
}
@media (hover: hover) {
  .xf-product-info__variants--color .xf-product-info__variant:hover {
    border-color: var(--accent-line);
    color: var(--text);
  }
}
/* активный цвет: индиго-плашка общая с БП (.is-active выше); кружок усиливаем
   белым кольцом, чтобы не растворился в --accent-dim */
.xf-product-info__variants--color .xf-product-info__variant.is-active .xf-product-info__variant-swatch-dot {
  box-shadow: inset 0 0 0 1px oklch(0 0 0 / 0.18), 0 0 0 1.5px var(--surface);
}
/* тёмная тема: чёрный свотч (#1a1a1a) и его чёрный inset-ring растворяются в
   тёмной поверхности чипа — даём светлое кольцо, чтобы кружок читался. */
html[data-theme="dark"] .xf-product-info__variant-swatch-dot {
  box-shadow: inset 0 0 0 1px oklch(1 1 1 / 0.22);
}
html[data-theme="dark"] .xf-product-info__variants--color .xf-product-info__variant.is-active .xf-product-info__variant-swatch-dot {
  box-shadow: inset 0 0 0 1px oklch(1 1 1 / 0.28), 0 0 0 1.5px var(--surface);
}
/* мобайл: тап-таргет чипа до 44px (стандарт PDP, как .xf-btn-icon) */
@media (max-width: 767px) {
  .xf-product-info__variant { min-height: 44px; align-items: center; }
}
/* сосед недоступен (CATALOG_AVAILABLE='N') — приглушённый маркер, НЕ ссылка:
   его карточка теперь 404 (availability gate), поэтому рендерится <span>, не <a>.
   Ряд опций единый на всех сёстрах (симметрия); недоступный виден, но не кликается. */
.xf-product-info__variant--oos { opacity: .42; cursor: default; }

/* Preorder disclosure — над actions row, только если xf_in_stock === false */
.xf-product-info__preorder-note {
  display: flex;
  gap: 10px;
  align-items: center;
  padding: 12px 14px;
  margin: 0 0 14px;
  background: oklch(0.96 0.018 75);
  border: 1px solid oklch(0.88 0.04 75);
  border-radius: 12px;
  color: oklch(0.32 0.06 65);
  font-size: 13.5px;
  line-height: 1.4;
}
.xf-product-info__preorder-note svg { flex-shrink: 0; color: oklch(0.50 0.10 60); }
.xf-product-info__preorder-note strong { font-weight: 600; }

.xf-product-info__actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 24px;
  align-items: stretch;
}

/* Secondary CTA рядом с primary "Купить под заказ" — link на телефон.
   Под-заказ flow: primary жмёт корзину (fallback 409 → toast); secondary —
   прямой контакт по конкретному товару (отдельный intent от шапочного phone). */
.xf-btn-contact {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-width: 150px;
  padding: 0 22px;
  background: transparent;
  border: 1.5px solid var(--line);
  border-radius: 14px;
  color: var(--text);
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 14.5px;
  text-decoration: none;
  cursor: pointer;
  transition: border-color .15s, color .15s, background .15s;
  white-space: nowrap;
  box-sizing: border-box;
}
.xf-btn-contact svg { flex-shrink: 0; }
@media (hover: hover) {
  .xf-btn-contact:hover { border-color: var(--accent-line); color: var(--accent); background: var(--surface); }
  /* Тёмная (#858): подпись — фиолетом текста, --accent на --surface дал бы 3.7:1. */
  html[data-theme="dark"] .xf-btn-contact:hover { color: var(--accent-2); }
}
.xf-btn-buy {
  flex: 1;
  min-width: 200px;
  padding: 14px 22px;
  background: var(--accent);
  color: #fff;
  border: 1px solid transparent;
  border-radius: 12px;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 500;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  box-shadow: 0 6px 20px -8px var(--accent);
  transition: transform .15s, box-shadow .15s;
}
@media (hover: hover) {
  .xf-btn-buy:hover { transform: translateY(calc(-1px * var(--xf-motion, 1))); box-shadow: 0 10px 28px -8px var(--accent); }
}
.xf-btn-buy.is-busy { opacity: .7; pointer-events: none; }
/* Dual-state: bag+«В корзину» default, check+«В корзине» when added. Тот же
   язык, что у листинговой .xf-product-card__cart-btn — карточка и PDP говорят
   одинаково. */
.xf-btn-buy__icon, .xf-btn-buy__label { display: inline-flex; align-items: center; }
.xf-btn-buy:not(.is-added) .xf-btn-buy__icon--check,
.xf-btn-buy:not(.is-added) .xf-btn-buy__label--added { display: none; }
.xf-btn-buy.is-added .xf-btn-buy__icon--bag,
.xf-btn-buy.is-added .xf-btn-buy__label--idle { display: none; }
/* [#326] Зелёная заливка снята здесь ровно по той же причине, что и на карточке
   (наследство: обе поверхности красились одним хардкодом oklch(0.72 0.18 145)).
   Зелёный в шаблоне значит «в наличии» — на PDP это .xf-product-info__stock
   рядом; кнопка «уже в корзине» им быть не должна.
   Заодно чинится иерархия: после добавления товара primary-действие на
   странице закончилось, и кнопка честно опускается до нейтральной —
   DESIGN.md, The Single-Primary Rule. Glow гасим оба (покой и hover): он
   рисуется от --accent и на нейтральной кнопке читался бы как индиго-CTA.
   Материал --surface-3 (а не --surface-2) — тот же, что у карточки, и это
   единственная ступень, которая читается на всех трёх подложках PDP: страница
   --bg 0.985, липкий бар снизу тоже --bg; --surface-2 (0.965) на них почти
   исчезал бы, держась на одной рамке. */
.xf-btn-buy.is-added {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--line);
  box-shadow: none;
}
@media (hover: hover) {
  .xf-btn-buy.is-added:hover {
    box-shadow: none;
    color: var(--accent-2);
    border-color: var(--accent-line);
  }
}
/* M1 preorder visual variant: same primary affordance, но calmer background
   и без glow shadow, чтобы in-stock «Купить» и preorder «Купить под заказ»
   не выглядели идентично при beat-the-copy скимминге. Парная листинговая
   версия (.xf-product-card__cart-btn--under-order) снята в #306 как мёртвая:
   на карточке кнопка недоступного товара вообще не рисуется. Здесь, на PDP,
   вариант живой. JS уже выставляет
   data-xf-preorder="1" атрибут (см. element.php buy-button render).
   --accent-dim = 10% alpha индиго; rendered over surface = light indigo tint.
   Не используем color-mix(accent, surface) — pure-white surface (oklch 1 0 0)
   имеет undefined hue, browser идёт short arc через 348°, получается pink. */
.xf-btn-buy[data-xf-preorder="1"] {
  background: var(--accent-dim);
  color: var(--accent-2);
  box-shadow: none;
  border: 1px solid var(--accent-line);
}
@media (hover: hover) {
  .xf-btn-buy[data-xf-preorder="1"]:hover {
    background: oklch(0.55 0.23 var(--accent-h) / 0.18);
    box-shadow: none;
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
  /* Тёмная (#857): литерал выше — индиго 268 светлой. Пара — фиолет темы на
     ступень плотнее покоя (--accent-dim там 16%); смесь в srgb — оттенок не
     уезжает ни в одном движке. Без :not(.is-added) правило (0-4-1) перебило
     бы наведение «В корзине» (0-4-0, #326, ниже). */
  html[data-theme="dark"] .xf-btn-buy[data-xf-preorder="1"]:not(.is-added):hover {
    background: color-mix(in srgb, var(--accent) 28%, transparent);
  }
}
/* [#326] Preorder-товар, уже лежащий в корзине. Атрибутный селектор считается
   в той же колонке, что и класс: .xf-btn-buy[data-xf-preorder="1"] и
   .xf-btn-buy.is-added весят одинаково (0,2,0), спор решал порядок в файле —
   побеждал preorder, который ниже. Из-за этого кнопка уже добавленного
   preorder-товара продолжала выглядеть активным CTA (при зелёном .is-added до
   #326 было ровно то же — это не регресс, а невылеченный край; проверено в
   браузере, не выведено из специфичности на бумаге).
   Отдаём победу состоянию явно (0,3,0): товар в корзине — primary-действие
   закончилось, и «под заказ» рассказывает не кнопка, а плашка «Товар под
   заказ. Привезём за 7-14 дней» прямо над ней (element.php). Замысел M1
   («покупка in-stock и preorder не должны выглядеть одинаково») не задет: он
   про ПОКОЙ кнопки, а не про состояние «уже добавлено». */
.xf-btn-buy[data-xf-preorder="1"].is-added {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--line);
}
@media (hover: hover) {
  .xf-btn-buy[data-xf-preorder="1"].is-added:hover {
    background: var(--surface-3);
    color: var(--accent-2);
    border-color: var(--accent-line);
  }
}
/* Тёмная тема: без цветной тени — ни в покое, ни на наведении (0-2-1 выше
   :hover), как у .xf-btn (ui.css). «В корзине» — рамка фирменного фиолетового:
   состояние видно, главной кнопкой не кричит (контур 14% на тёмном терялся). */
html[data-theme="dark"] .xf-btn-buy { box-shadow: none; }
html[data-theme="dark"] .xf-btn-buy.is-added { border-color: oklch(0.66 0.20 300 / 0.85); }
.xf-btn-icon {
  width: 48px;
  height: 48px;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  color: var(--text-2);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
@media (hover: hover) {
  .xf-btn-icon:hover { color: var(--text); border-color: var(--line); background: var(--surface-2); }
}
.xf-btn-icon.is-busy { opacity: .5; pointer-events: none; }
.xf-btn-icon.is-active {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}
.xf-btn-icon[data-xf-wish].is-active { background: #e11d48; }
.xf-btn-icon[data-xf-wish].is-active svg { fill: currentColor; }

.xf-product-info__benefits {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 10px;
}
.xf-product-info__benefit {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  font-size: 12.5px;
  color: var(--text-2);
  line-height: 1.45;
}
.xf-product-info__benefit svg { color: var(--accent-2); flex: 0 0 auto; margin-top: 1px; }
/* #648: «Условия возврата» — ссылка; base.css снимает у ссылок подчёркивание. */
.xf-product-info__benefit a { text-decoration: underline; text-underline-offset: 2px; }
/* Осталась одна плашка — срок получения уехал в строку наличия (#364), — поэтому
   занимает ряд целиком, а не половину с пустым слотом рядом. */
.xf-pdp .xf-product-info__benefits--solo { grid-template-columns: 1fr; }

/* ─── «Ключевое»: краткая сводка характеристик в hero (#364) ───
   Дыра, которую закрывает блок: в правой колонке hero нет НИ ОДНОГО факта
   о товаре — покупатель с прайс-агрегатора видит имя, цену, наличие и должен
   проскроллить ~900px до первой цифры. Строки берутся из той же таблицы
   характеристик (include/catalog/element.php), ничего не досочиняется. */
.xf-pdp .xf-key {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
}
.xf-pdp .xf-key__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 10px;
}
.xf-pdp .xf-key__eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-3);
}
/* Ссылка нейтральная, не индиго: на этом экране акцент уже держат кнопка
   «В корзину» и ссылка «Варианты покупки» — третья индиговая точка размывает
   направление (DESIGN.md, The One-Voice Indigo Rule).
   Высота ссылки была 16 px при ширине 137 — по пальцу на телефоне это промах
   (WCAG 2.5.5: 44×44; 2.2 AA 2.5.8: 24×24). Добираем вертикаль паддингом, а
   строку в шапке блока держим на месте отрицательным полем: увеличивается
   область нажатия, а не отступ между «Ключевое» и первой строкой. */
.xf-pdp .xf-key__all {
  font-size: 12.5px;
  color: var(--text-2);
  text-decoration: none;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin: -14px 0;
}
.xf-pdp .xf-key__all:hover { color: var(--accent-2); text-decoration: underline; }
/* auto-колонка = ширина самого длинного названия: значение встаёт сразу за ним,
   без «поля» между парой, которое приходится перепрыгивать глазом. column-gap:0
   и отступ внутри названия — иначе разделительная линия рвётся на зазоре колонок
   и ряд читается как две отдельные строки. */
.xf-pdp .xf-key__list {
  margin: 0;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: 0;
}
.xf-pdp .xf-key__name,
.xf-pdp .xf-key__val {
  margin: 0;
  padding: 7px 0;
  font-size: 13px;
  line-height: 1.4;
  border-top: 1px solid var(--line-soft);
}
.xf-pdp .xf-key__list > .xf-key__name:first-child,
.xf-pdp .xf-key__list > .xf-key__name:first-child + .xf-key__val {
  border-top: 0;
}
.xf-pdp .xf-key__name { color: var(--text-3); padding-right: 26px; }
.xf-pdp .xf-key__val {
  color: var(--text);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* ─── Section heads inside product page ───────────────────── */
.xf-product-section {
  margin-bottom: 56px;
}

/* ─── Tabs: Характеристики / Описание / Отзывы ─────────────── */
/* Progressive enhancement: no-JS → все панели видны (styled section подряд),
   JS добавляет .xf-product-tabs--js → прячет неактивные и показывает
   только .xf-product-tabs__panel.is-active. Nav скрыт до появления JS —
   без переключателя он дезинформирует. */
.xf-product-tabs {
  margin-bottom: 56px;
}
/* Цель хэша #specs (id вешает include/catalog/element.php) для ссылки
   «Все характеристики →» из «Ключевого». Липкая шапка перекрывает верх
   страницы, поэтому нативный скролл к якорю должен встать ниже неё: 154 px
   шапки на десктопе + воздух. Иначе таб-строка «Характеристики» уезжает
   ПОД шапку и клик выглядит как промах. Телефонное значение — в ≤767 ниже. */
.xf-pdp .xf-product-tabs { scroll-margin-top: 172px; }
.xf-product-tabs__nav {
  display: none;
  gap: 4px;
  border-bottom: 1px solid var(--line-soft);
  margin-bottom: 28px;
  overflow-x: auto;
  scrollbar-width: none;
}
.xf-product-tabs__nav::-webkit-scrollbar { display: none; }
.xf-product-tabs--js .xf-product-tabs__nav { display: flex; }
.xf-product-tabs__tab {
  position: relative;
  flex: 0 0 auto;
  background: transparent;
  border: 0;
  padding: 14px 20px;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 500;
  color: var(--text-2);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-product-tabs__tab:hover { color: var(--text); }
}
.xf-product-tabs__tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -4px; border-radius: 8px; }
.xf-product-tabs__tab.is-active { color: var(--text); }
.xf-product-tabs__tab.is-active::after {
  content: "";
  position: absolute;
  left: 16px; right: 16px; bottom: -1px;
  height: 2px;
  background: var(--accent);
  border-radius: 2px 2px 0 0;
}
.xf-product-tabs__count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  padding: 0 8px;
  height: 20px;
  background: color-mix(in srgb, var(--accent-2) 12%, transparent);
  color: var(--accent-2);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  border-radius: 999px;
}
.xf-product-tabs__panel { display: block; }
.xf-product-tabs--js .xf-product-tabs__panel { display: none; }
.xf-product-tabs--js .xf-product-tabs__panel.is-active { display: block; }

/* ─── Видео: YouTube-плеер по клику (facade) ─────────────────
   Постер с ytimg (статика, без кук) + кнопка ▶; настоящий iframe
   (youtube-nocookie) подставляет app.js initProductVideo ТОЛЬКО по
   клику — никаких кук Google до действия пользователя. Панель скрыта
   JS-ом (.xf-product-tabs--js → display:none), поэтому lazy-постер
   обычно тянется лишь при открытии вкладки; cookieless в любом случае. */
.xf-product-video {
  display: grid;
  gap: 20px;
  max-width: 760px;
}
.xf-product-video__facade,
.xf-product-video__frame {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  border-radius: var(--radius);
  background: var(--surface-3);
}
.xf-product-video__facade {
  position: relative;
  overflow: hidden;
  cursor: pointer;
}
.xf-product-video__poster {
  width: 100%;
  height: 100%;
  object-fit: cover;                 /* кропаем 4:3 hqdefault под 16:9 */
  display: block;
}
.xf-product-video__facade::after {   /* затемнение под кнопку — один градиент */
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, transparent 45%, oklch(0.18 0.02 285 / 0.4));
  opacity: 0;
  transition: opacity .2s;
}
.xf-product-video__play {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: inline-flex;
  pointer-events: none;
  transition: transform .2s;
}
.xf-product-video__play-bg  { fill: oklch(0.2 0.02 285 / 0.72); transition: fill .2s; }
.xf-product-video__play-tri { fill: #fff; }
.xf-product-video__facade:focus-visible .xf-product-video__play-bg { fill: var(--accent); }
@media (hover: hover) {
  .xf-product-video__facade:hover::after { opacity: 1; }
  .xf-product-video__facade:hover .xf-product-video__play { transform: translate(-50%, -50%) scale(calc(1 + 0.06 * var(--xf-motion, 1))); }
  .xf-product-video__facade:hover .xf-product-video__play-bg { fill: var(--accent); }
}
.xf-product-section__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  margin-bottom: 20px;
}
.xf-product-section__eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--accent-2);
  margin-bottom: 8px;
}
.xf-product-section__eyebrow::before {
  content: "";
  display: inline-block;
  width: 22px;
  height: 1px;
  background: var(--accent-2);
  vertical-align: middle;
  margin-right: 8px;
}
.xf-product-section__title {
  margin: 0;
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.7px;
  line-height: 1.15;
  color: var(--text);
}

/* ─── Specs table ─────────────────────────────────────────── */
.xf-specs {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  overflow: hidden;
}
.xf-specs__row {
  display: grid;
  grid-template-columns: 1fr 1.2fr;
  gap: 24px;
  padding: 14px 22px;
  border-top: 1px solid var(--line-soft);
  align-items: baseline;
}
.xf-specs__row:first-child { border-top: 0; }
.xf-specs__row:nth-child(odd) { background: var(--bg-2); }
/* Тёмная тема: --bg-2 темнее карточки и страницы — полоска зебры читалась
   дырой. В тёмной полоска чуть светлее поверхности. */
html[data-theme="dark"] .xf-specs__row:nth-child(odd) { background: oklch(1 0 0 / 0.028); }
/* Значение ближе к названию (#364). Было: строка 1358 px, имя занимало 586 px,
   значение начиналось на 632-м пикселе — «Тип … игровой» читалось через
   полполя. Колонка имени фиксируется, значение встаёт сразу за ней; хвост
   строки остаётся пустым, это нормально. Только на широком: на планшете и
   телефоне колонки и так узкие. */
@media (min-width: 901px) {
  .xf-specs__row {
    grid-template-columns: minmax(180px, 300px) minmax(0, 1fr);
    column-gap: 32px;
  }
}
/* Контраст названия строки (#364). Замерено на живой странице (обе темы, 1440
   и 390): при `--text-3` 13.5 px название проваливает AA на ПОЛОСАТЫХ рядах —
   светлая 4.38:1 на `--bg-2` (нужно 4.5), тёмная 4.28:1 на `--surface`. То есть
   в каждой теме половина строк ниже нормы, просто разная половина (полоску даёт
   `:nth-child(odd)` выше). Тот же промах был у заголовка группы и счётчика в
   спойлере. Лечим токеном, а не подобранным числом: `--text-2` даёт 8–10:1 в
   обеих темах. Иерархия «название тише значения» цела — значение это `--text`,
   между ними по-прежнему две ступени. Правило без скоупа `.xf-pdp`: все три
   класса рендерит только карточка товара (grep по шаблону — element.php). */
.xf-specs__name {
  font-size: 13.5px;
  color: var(--text-2);
}
.xf-specs__value {
  font-size: 13.5px;
  color: var(--text);
  overflow-wrap: anywhere;
}
/* Заголовок секции внутри слитой таблицы (#276): строка-разделитель между
   группами строк («Основные», «Интерфейсы», …) — данные 1С, не хардкод. */
.xf-specs__section {
  padding: 18px 22px 8px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: var(--text-2);   /* контраст — см. .xf-specs__name выше (#364) */
  border-top: 1px solid var(--line-soft);
}
.xf-specs__section:first-child { border-top: 0; }
.xf-specs__section + .xf-specs__row { border-top: 0; }
/* Примечание внутри таблицы (#622): строка описания без «Ключ: значение»
   («Тип микросхем NAND может варьироваться в зависимости от поставки») —
   текст во всю ширину, без колонки названия. Правило ниже медиа-блока
   колонок выше: та же специфичность, побеждает порядок. */
.xf-specs__row--note { grid-template-columns: minmax(0, 1fr); }
.xf-specs__note {
  font-size: 13.5px;
  color: var(--text);
  overflow-wrap: anywhere;
}

/* Legal docs (адреса, импортёр, сервисная поддержка) — collapsible под
   основной таблицей. По закону должны быть, но не должны забивать data-rows. */
.xf-specs-legal {
  margin-top: 12px;
}
/* Классификация/метки (#364) — служебная таксономия фильтров, а не
   характеристика товара. Прячем под тот же <details>, что и юр-данные (ничего
   не удалено). Класс из СВОЕЙ семьи .xf-key-*: визуал берём у .xf-specs-legal,
   но чужое пространство имён не занимаем. */
.xf-pdp .xf-key-aux { margin-top: 12px; }
.xf-specs-legal__summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 16px;
  border-radius: 12px;
  border: 1px solid var(--line-soft);
  background: var(--bg-2);
  color: var(--text-2);
  font-size: 13px;
  font-weight: 500;
  user-select: none;
  transition: background .15s;
}
.xf-specs-legal__summary::-webkit-details-marker { display: none; }
.xf-specs-legal__summary::marker { content: ''; }
.xf-specs-legal__summary:hover { background: var(--surface-2); }
.xf-specs-legal__icon {
  position: relative;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}
.xf-specs-legal__icon::before,
.xf-specs-legal__icon::after {
  content: '';
  position: absolute;
  background: var(--text-3);
  transition: opacity .15s;
}
.xf-specs-legal__icon::before {
  /* horizontal bar */
  top: 50%;
  left: 0;
  width: 100%;
  height: 1.5px;
  margin-top: -0.75px;
}
.xf-specs-legal__icon::after {
  /* vertical bar (forms +) */
  top: 0;
  left: 50%;
  width: 1.5px;
  height: 100%;
  margin-left: -0.75px;
}
.xf-specs-legal[open] .xf-specs-legal__icon::after { opacity: 0; }
.xf-specs-legal__label { flex: 1; }
.xf-specs-legal__count {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 1px;
  color: var(--text-2);   /* контраст — см. .xf-specs__name выше (#364) */
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  padding: 2px 8px;
}
.xf-specs-legal__rows {
  margin-top: 10px;
}

/* M3 «Использовать в сборке» configurator CTA — secondary button above spec table.
   Connects PDP specs к paid webprostor.configurator. Calm neutral-outline стиль:
   индиго-family убран чтобы не competed с primary «В корзину» (R2 critique
   flagged визуальный конфликт двух одинаково-цветных CTA). Иконка остаётся
   с accent-tint чтобы маркировать как «builder-tool action», не «buy». */
.xf-specs__configurator-cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 16px;
  padding: 10px 18px;
  background: var(--surface);
  color: var(--text-2);
  border: 1px solid var(--line);
  border-radius: 12px;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: background .15s, color .15s, border-color .15s, transform .15s;
}
.xf-specs__configurator-cta svg {
  color: var(--accent-2);
}
.xf-specs__configurator-cta:hover {
  background: var(--bg-2);
  color: var(--text);
  border-color: var(--line);
  transform: translateY(calc(-1px * var(--xf-motion, 1)));
}

/* M3 glossary tooltips: CSS-only inline tooltip, revealed на hover/focus
   маленькой (?) кнопки. Bubble — dark surface (inverse) над термином. */
.xf-tip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}
.xf-tip__btn {
  position: relative;   /* база для расширенной мишени ::after ниже */
  width: 16px;
  height: 16px;
  border: 1px solid var(--line-soft);
  background: var(--surface-2);
  color: var(--text-3);
  border-radius: 999px;
  cursor: help;
  padding: 0;
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color .15s, border-color .15s, background .15s;
}
.xf-tip__btn:hover,
.xf-tip__btn:focus-visible {
  color: var(--accent-2);
  border-color: var(--accent-line);
  background: var(--accent-dim);
}
/* [#394] Мишень «?»: сама кнопка 16×16 — ниже даже нижнего порога WCAG 2.2 AA
   (2.5.8 = 24×24), не говоря про палец. Растим ОБЛАСТЬ НАЖАТИЯ псевдоэлементом,
   а не габарит кнопки: она стоит инлайном и в значении характеристики, и в
   заголовке группы фильтров — рост габарита раздвинул бы строку таблицы и
   строку сайдбара. inset -6px даёт 28×28. Правило живёт в файле-владельце
   `.xf-tip-*` и охватывает ВСЕ экземпляры (карточка товара и сайдбар фильтров).
   Пузырь подсказки красится поверх: он идёт в DOM после кнопки и несёт
   z-index 20. Клик по расширенной зоне остаётся кликом по кнопке, поэтому
   `onclick="event.stopPropagation()"` в сайдбаре по-прежнему не даёт
   свернуть <details>. */
.xf-tip__btn::after {
  content: '';
  position: absolute;
  inset: -6px;
}
/* [#381] Здесь БЫЛ `outline: none` внутри :focus-visible-блока выше — он гасил
   глобальное кольцо (ui.css:9) персонально для кнопки глоссария: 0,2,0 против
   0,1,0. Единственным признаком фокуса оставалась смена цвета и рамки на
   --accent-line, а это accent с альфой, 1.55:1 в светлой и 1.75:1 в тёмной при
   норме WCAG 1.4.11 = 3:1. Строка снята — кольцо рисует глобальное правило,
   и по инварианту проекта per-element focus-стиль тут не заводится. */
.xf-tip__bubble {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(calc(4px * var(--xf-motion, 1)));
  width: 260px;
  padding: 10px 14px;
  background: var(--text);
  color: var(--surface);
  border-radius: 10px;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.4;
  text-align: left;
  white-space: normal;
  pointer-events: none;
  opacity: 0;
  transition: opacity .15s, transform .15s;
  z-index: 20;
}
.xf-tip__bubble::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 5px solid transparent;
  border-top-color: var(--text);
}
/* translateY(0) — конечная (показанная) позиция, а не негейченное движение:
   стартовые 4px в .xf-tip__bubble множатся на --xf-motion, поэтому под
   reduced-motion старт и финиш совпадают — подсказка просто проявляется
   через opacity, без подъезда (#330). translateX(-50%) — центрирование. */
.xf-tip:hover .xf-tip__bubble,
.xf-tip:focus-within .xf-tip__bubble {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}

/* JS-positioned (filter sidebar) flip: bubble ПОД trigger → arrow вверху
   bubble, смотрит ВВЕРХ на (?). См. initFilterTooltipPositioning в app.js. */
.xf-tip__bubble--flipped::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: var(--text);
}

/* ─── Description ─────────────────────────────────────────── */
.xf-product-desc {
  font-size: 15px;
  line-height: 1.7;
  color: var(--text-2);
  max-width: 820px;
}
.xf-product-desc p { margin: 0 0 14px; }
.xf-product-desc ul, .xf-product-desc ol { margin: 0 0 14px; padding-left: 22px; }
.xf-product-desc li { margin-bottom: 6px; }
.xf-product-desc h3, .xf-product-desc h4 {
  font-size: 17px;
  font-weight: 600;
  color: var(--text);
  margin: 22px 0 10px;
}
.xf-product-desc strong, .xf-product-desc b { color: var(--text); font-weight: 600; }
.xf-product-desc a { color: var(--accent-2); }
@media (hover: hover) {
  .xf-product-desc a:hover { color: var(--accent); }
  /* Тёмная (#858): --accent текстом на фоне вкладки — 3.9:1. Наведение как
     у ссылок в тексте справки и блога: текст --text, подчёркивание
     --accent-2. Подчёркивание и держит ссылку ссылкой: белым здесь набран и
     жирный текст. */
  html[data-theme="dark"] .xf-product-desc a:hover {
    color: var(--text);
    text-decoration: underline;
    text-decoration-color: var(--accent-2);
    text-underline-offset: 3px;
  }
}

/* ─── Reviews (M21-D3, read-only) ──────────────────────────── */
.xf-reviews__count {
  display: inline-block;
  margin-left: 10px;
  padding: 2px 10px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--accent-2);
  background: color-mix(in oklch, var(--accent-2) 12%, transparent);
  border-radius: 999px;
  vertical-align: 4px;
}
.xf-reviews__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
  max-width: 820px;
}
.xf-reviews__item {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  padding: 18px 22px;
}
.xf-reviews__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}
.xf-reviews__author {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  overflow-wrap: anywhere;
}
.xf-reviews__date {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
  white-space: nowrap;
}
.xf-reviews__body {
  font-size: 14.5px;
  line-height: 1.6;
  color: var(--text-2);
  overflow-wrap: anywhere;
}
.xf-reviews__body p { margin: 0 0 10px; }
.xf-reviews__body p:last-child { margin-bottom: 0; }
.xf-reviews__body a { color: var(--accent-2); }
@media (hover: hover) {
  .xf-reviews__body a:hover { color: var(--accent); }
  /* Тёмная (#858): то же, что у ссылки в описании выше (--accent на карточке
     отзыва — 3.7:1). */
  html[data-theme="dark"] .xf-reviews__body a:hover {
    color: var(--text);
    text-decoration: underline;
    text-decoration-color: var(--accent-2);
    text-underline-offset: 3px;
  }
}
.xf-reviews__body img {
  max-width: 100%;
  height: auto;
  border-radius: 10px;
  margin: 6px 0;
}
.xf-reviews__empty {
  max-width: 820px;
  padding: 18px 22px;
  background: var(--bg-2);
  border: 1px dashed var(--line-soft);
  border-radius: 14px;
  font-size: 14px;
  color: var(--text-3);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
}
.xf-reviews__empty-msg {
  margin: 0;
  color: var(--text-3);
}
/* ─── Reviews — on-site submit form (#32, replaced Telegram CTA) ─── */
.xf-reviews__form {
  max-width: 820px;
  margin-top: 18px;
  padding: 20px 22px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  display: grid;
  gap: 14px;
}
.xf-reviews__form-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
}
.xf-reviews__field {
  display: grid;
  gap: 6px;
}
.xf-reviews__label {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
}
.xf-reviews__input {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 14px;
  font-family: inherit;
  font-size: 14.5px;
  color: var(--text);
  background: var(--bg-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  transition: border-color .15s, background .15s;
}
.xf-reviews__input::placeholder { color: var(--text-3); }
/* [#830] Телефон: 16 px — на поле мельче iPhone увеличивает страницу при фокусе. */
@media (max-width: 767px) {
  .xf-reviews__input { font-size: 16px; }
}
.xf-reviews__input:focus {
  border-color: var(--accent-2);
  background: var(--surface);
}
/* [#381] Здесь БЫЛ второй экземпляр той же суппрессии, что у .xf-tip__btn:
   `outline: none` гасил глобальное кольцо на полях формы отзыва. Индикатором
   оставалась только смена рамки, а поля формы — ровно то место, где
   клавиатурный посетитель обязан видеть, куда он попал. Строка снята. */
.xf-reviews__textarea {
  resize: vertical;
  min-height: 96px;
  line-height: 1.55;
}
.xf-reviews__form-foot {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}
.xf-reviews__submit {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  cursor: pointer;
  padding: 11px 22px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  color: #fff;
  background: var(--accent);
  border-radius: 12px;
  transition: background .15s, transform .15s, opacity .15s;
}
@media (hover: hover) {
  .xf-reviews__submit:hover {
    background: var(--accent-2);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
  /* Тёмная (#857): --accent-2 там — фиолет для текста (белая подпись 1.9:1);
     наведение залитой кнопки — --accent-3, как у .xf-btn. */
  html[data-theme="dark"] .xf-reviews__submit:hover { background: var(--accent-3); }
}
.xf-reviews__submit:disabled {
  opacity: .55;
  cursor: default;
  transform: none;
}
.xf-reviews__hint {
  margin: 0;
  font-size: 12.5px;
  color: var(--text-3);
}
.xf-reviews__form.is-busy { opacity: .75; }
/* После успешной отправки сворачиваем поля — остаётся только success-нота.
   Через класс, т.к. у полей display:grid/flex перебивает атрибут [hidden]. */
.xf-reviews__form.is-sent .xf-reviews__form-title,
.xf-reviews__form.is-sent .xf-reviews__field,
.xf-reviews__form.is-sent .xf-reviews__form-foot,
.xf-reviews__form.is-sent .xf-reviews__hp { display: none; }
.xf-reviews__note {
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-2);
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--bg-2);
  border: 1px solid var(--line-soft);
}
.xf-reviews__note.is-error {
  color: var(--danger);
  border-color: color-mix(in oklch, var(--danger) 35%, var(--line-soft));
}
.xf-reviews__note.is-success {
  color: var(--accent-2);
  background: var(--accent-dim);
  border-color: var(--accent-line);
}
/* honeypot — вне потока, недоступно людям, видно ботам-автозаполнятелям */
.xf-reviews__hp {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ─── Модерация отзывов (#97) — рендерится ТОЛЬКО модератору форума ──────
   Неодобренный отзыв виден лишь модератору (pre-moderation): пунктир + плашка
   «на проверке». Кнопки одобрить/скрыть/удалить — действия, не состояния. */
.xf-reviews__item--pending {
  border-style: dashed;
  border-color: color-mix(in oklch, var(--danger) 30%, var(--line-soft));
  background: var(--bg-2);
}
.xf-reviews__pending-badge {
  flex: none;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .02em;
  color: var(--text-3);
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  padding: 2px 9px;
  white-space: nowrap;
}
.xf-reviews__mod {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
}
.xf-reviews__mod-btn {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  padding: 8px 14px;
  border-radius: 10px;
  border: 1px solid var(--line-soft);
  background: var(--surface);
  color: var(--text-2);
  transition: background .15s, border-color .15s, color .15s;
}
@media (hover: hover) {
  .xf-reviews__mod-btn:hover { border-color: var(--line); color: var(--text); }
}
.xf-reviews__mod-btn--approve {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}
@media (hover: hover) {
  .xf-reviews__mod-btn--approve:hover { border-color: var(--accent-2); background: var(--accent-2); color: #fff; }
  /* Тёмная (#857): --accent-2 там — фиолет для текста (белая подпись 1.9:1). */
  html[data-theme="dark"] .xf-reviews__mod-btn--approve:hover { border-color: var(--accent-3); background: var(--accent-3); }
}
.xf-reviews__mod-btn--del { color: var(--danger); }
@media (hover: hover) {
  .xf-reviews__mod-btn--del:hover {
    border-color: color-mix(in oklch, var(--danger) 45%, var(--line-soft));
    color: var(--danger);
  }
}
/* Состояние карточки решает, какие кнопки показать: одобренный отзыв можно
   «Скрыть», ожидающий — «Одобрить». «Удалить» доступно всегда. */
.xf-reviews__mod-btn--approve { display: none; }
.xf-reviews__item--pending .xf-reviews__mod-btn--approve { display: inline-flex; }
.xf-reviews__item--pending .xf-reviews__mod-btn--hide { display: none; }
.xf-reviews__item.is-busy { opacity: .6; pointer-events: none; }

/* ─── Similar rail ─────────────────────────────────────────── */
.xf-similar__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
}

/* ─── Gallery dots (mobile scroller indicator) ───────────── */
/* Hidden on desktop — thumbs are active-state carrier there. Markup rendered
   unconditionally when count > 1 so CSS alone can swap visibility. */
.xf-product-gallery__dots { display: none; }
.xf-product-gallery__dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--line);
  transition: background .2s, width .2s;
}
.xf-product-gallery__dot.is-active {
  background: var(--accent);
  width: 18px;
}

/* ─── Sticky buy-bar — shell hidden on desktop ───────────── */
.xf-buy-sticky { display: none; }

/* ─── Tablet stack ────────────────────────────────────────── */
@media (max-width: 900px) {
  .xf-product { grid-template-columns: 1fr; gap: 24px; }
  .xf-product-gallery { position: static; }
  .xf-product-info__benefits { grid-template-columns: 1fr; }
  .xf-specs__row { grid-template-columns: 1fr; gap: 4px; padding: 12px 16px; }
  .xf-similar__grid { grid-template-columns: repeat(2, 1fr); }
  .xf-product-info__title { font-size: 24px; }
  .xf-product-info__price { font-size: 28px; }
}

/* ─── Phone (≤767) ────────────────────────────────────────── */
@media (max-width: 767px) {
  /* Hero: compact typography, single col already set at 900. */
  .xf-product { gap: 20px; }
  /* top: auto сбрасывает унаследованное top: 180px из desktop sticky-правила,
     иначе галерея уезжает на 180px вниз на телефоне. */
  .xf-product-gallery { position: relative; top: auto; }
  .xf-product-gallery__guarantee { min-width: 130px; font-size: 10.5px; padding: 6px 11px; letter-spacing: 0; }
  .xf-product-info__title { font-size: 22px; line-height: 1.2; margin: 8px 0 14px; }
  .xf-product-info__meta { font-size: 12.5px; gap: 14px; margin-bottom: 20px; }
  .xf-product-info__price-row { padding: 18px 0 20px; margin-bottom: 18px; }
  .xf-product-info__price { font-size: 26px; }
  .xf-product-info__price-cur { font-size: 14px; }
  .xf-product-info__price-old { font-size: 15px; margin-top: 4px; }

  /* Actions stack — buy fullwidth, icons inline below. */
  .xf-product-info__actions { gap: 8px; margin-bottom: 20px; }
  .xf-btn-buy { min-width: 0; flex: 1 1 100%; height: 52px; padding: 0 18px; }
  .xf-btn-icon { width: 44px; height: 44px; }

  /* Gallery: main hidden, thumbs become full-width scroll-snap carousel.
     When count > 1, thumbs ALWAYS rendered (see element.php condition).
     When count === 1, .xf-product-gallery__main is the only visible asset
     and stays as-is. */
  .xf-product-gallery__thumbs {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    gap: 0;
    margin: 0 calc(var(--container-pad) * -1);
    padding: 0;
    scrollbar-width: none;
  }
  .xf-product-gallery__thumbs::-webkit-scrollbar { display: none; }
  .xf-product-gallery__thumb {
    flex: 0 0 100%;
    width: 100%;
    height: auto;
    aspect-ratio: 1;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: var(--surface);
    scroll-snap-align: center;
    scroll-snap-stop: always;
    /* Слайд центрирует shrink-wrap с фото; container-type даёт cqi для
       img-ограничений (разрыв circular % — как на карточках, home.css). */
    container-type: inline-size;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .xf-product-gallery__thumb.is-active { border-color: transparent; }
  /* Wrap ужимается до фактического img-rect — per-thumb плашка гарантии
     (right:0/bottom:0 из базового класса) сидит на углу самого фото и
     перекрывает watermark на КАЖДОМ слайде (#9). min-width ≥ ширины плашки,
     иначе на узких фото она клиппится overflow'ом (прецедент карточек). */
  .xf-product-gallery__thumb-wrap {
    display: inline-flex;
    position: relative;
    flex: 0 0 auto;
    justify-content: center;
    min-width: 130px;
    overflow: hidden;
  }
  .xf-product-gallery__thumb img {
    width: auto;
    height: auto;
    max-width: calc(100cqi - 48px);
    max-height: calc(100cqi - 48px);
  }
  .xf-product-gallery__guarantee--thumb { display: block; }

  /* When thumbs carousel is present, hide the "main" slot on mobile. */
  .xf-product-gallery:has(.xf-product-gallery__thumbs) .xf-product-gallery__main {
    display: none;
  }
  /* Fallback for :has-less browsers — thumbs only render if count > 1. */

  /* Dots show under carousel. */
  .xf-product-gallery__dots {
    display: flex;
    justify-content: center;
    gap: 6px;
    margin-top: 14px;
  }

  /* Specs — еще компактнее, list-style без cards padding. */
  .xf-specs { border-radius: 12px; }
  .xf-specs__row { padding: 10px 14px; font-size: 13px; }
  .xf-specs__section { padding: 14px 14px 6px; font-size: 10.5px; }

  /* Tabs: fade-edges for horizontal-scroll affordance. */
  .xf-product-tabs--js .xf-product-tabs__nav {
    mask-image: linear-gradient(90deg, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%);
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 16px, #000 calc(100% - 16px), transparent 100%);
    padding: 0 4px;
    margin-bottom: 20px;
    /* iOS horizontal-scroll affordance: explicit pan-x + legacy momentum flag. */
    touch-action: pan-x;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
  }
  .xf-product-tabs__tab { padding: 12px 14px; font-size: 14px; }
  .xf-product-tabs__count { font-size: 10.5px; min-width: 20px; height: 18px; padding: 0 7px; }
  .xf-product-section__title { font-size: 22px; letter-spacing: -0.5px; }
  .xf-product-section__eyebrow { font-size: 10.5px; }

  /* Related rail: 2 full cards + peek 3rd, edge-to-edge horizontal scroll.
     Overrides 900-tier 2-col grid. Width math: (100vw - pad*2 - gap*2) / 2.25
     gives ~150px per card at 390px viewport → title/цена/кнопка «В корзину»
     помещаются в 1 строку, совпадает с listing 2-col ритмом. */
  .xf-similar__grid {
    grid-template-columns: none;
    grid-template-rows: auto;
    grid-auto-flow: column;
    grid-auto-columns: calc((100vw - 52px) / 2.25);
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: 16px;
    gap: 10px;
    scrollbar-width: none;
    margin: 0 calc(var(--container-pad) * -1);
    padding: 4px 16px;
  }
  .xf-similar__grid::-webkit-scrollbar { display: none; }
  .xf-similar__grid > * { scroll-snap-align: start; min-width: 0; }

  /* Sticky buy-bar — fixed bottom, slide-up reveal when hero actions
     scroll off-screen. Twins-sync via app.js handles state.
     Triple-guard hidden state:
       • transform off-screen (visual slide)
       • opacity 0 (belt — iOS Safari sometimes leaves fixed elems behind
         address bar at `bottom: 0` where translate math doesn't fully clear)
       • pointer-events none (no accidental tap).
     Transition gated behind .is-initialized so the element doesn't animate
     into its hidden state at first paint. */
  .xf-buy-sticky {
    display: block;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 50;
    background: color-mix(in oklch, var(--bg) 92%, transparent);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid var(--line-soft);
    padding: 10px 14px calc(10px + env(safe-area-inset-bottom, 0px));
    transform: translateY(calc(100% + env(safe-area-inset-bottom, 0px)));
    opacity: 0;
    pointer-events: none;
  }
  .xf-buy-sticky.is-initialized {
    transition: transform .22s ease-out, opacity .18s ease-out;
  }
  .xf-buy-sticky.is-visible {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
  }
  .xf-buy-sticky__inner {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .xf-buy-sticky__price {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-size: 16px;
    font-weight: 600;
    color: var(--text);
    letter-spacing: -0.3px;
    line-height: 1;
    display: flex;
    align-items: baseline;
    gap: 4px;
  }
  .xf-buy-sticky__price-cur {
    font-size: 11px;
    color: var(--text-3);
    font-weight: 500;
  }
  .xf-buy-sticky__price-val--empty {
    font-size: 13px;
    font-family: var(--font-sans);
    color: var(--text-3);
    font-weight: 500;
  }
  .xf-buy-sticky__btn {
    flex: 1 1 auto;
    min-width: 0;
    height: 48px;
    padding: 0 14px;
    font-size: 14px;
  }
  .xf-buy-sticky__wish {
    flex: 0 0 auto;
    width: 48px;
    height: 48px;
  }

  /* Add breathing room at page-end so sticky bar doesn't cover last
     content — equivalent to sticky height + safe-area. */
  .xf-home__section:last-of-type { padding-bottom: calc(72px + env(safe-area-inset-bottom, 0px)); }

  /* ─── Первый экран карточки товара на телефоне (#364) ────────
     Было: цена на 913-м пикселе при вьюпорте 844 — покупатель, пришедший
     с прайс-агрегатора «проверить цену и наличие», не видит ни того, ни
     другого без скролла. Ужимаем только собственную вертикаль карточки.
     Блок стоит последним в ≤767-секции намеренно: правила здесь должны
     перекрывать десктопные того же веса выше по файлу. */

  /* Шапка страницы + крошки + верх секции давали 116 px пустоты в первых 360 px
     экрана: 32 (.middle) + 32 (padding-top крошек) + 16+4 (низ крошек) + 32
     (верх секции товара) — воздух между блоками, а не сами блоки. Он и уносил
     цену за сгиб (замерено на живом /dev/: цена 806-885 при вьюпорте 844).
     Собственные отступы страницы товара, чужой каскад не трогаем: `.middle`
     (layout.css), `.xf-catpage__head` (catalog.css) и `.xf-home__section`
     (home.css) сужаются ТОЛЬКО под скоупом `.xf-pdp` — без него правила
     потекли бы на каталог и главную.
     После правки: крошки 217, фото 274-532, «В наличии» 636, срок 668,
     цена 694-773 — первый экран отдаёт то, ради чего пришёл посетитель
     с прайс-агрегатора (PRODUCT.md, Operating Context). */
  .middle:has(> .xf-pdp) { padding-top: 0; }
  .xf-catpage__head.xf-pdp { padding-top: 10px; padding-bottom: 8px; margin-bottom: 0; }
  .xf-home__section.xf-pdp { padding-top: 12px; }

  .xf-pdp .xf-product { gap: 14px; }
  /* Фото ужато на 26 px — оно остаётся крупнейшим элементом экрана (256 px при
     ширине 390), но перестаёт держать цену и наличие под сгибом. */
  .xf-pdp .xf-product-gallery__main .xf-art--photo { min-height: 256px; padding: 16px 14px; }
  .xf-pdp .xf-product-gallery__main .xf-art--photo .xf-art__img { max-height: 236px; }
  /* Палец, а не курсор: 38 px мимо цели WCAG 2.5.5 (44×44). Плита тут одна на
     весь экран, места хватает. Виден этот угол только когда фото ровно одно —
     при 2+ фото __main на телефоне скрыт (правило выше в этой же секции). */
  .xf-pdp .xf-product-gallery__zoom { width: 44px; height: 44px; top: 8px; right: 8px; }
  .xf-pdp .xf-product-info__title { margin: 6px 0 10px; }
  .xf-pdp .xf-product-info__meta { margin-bottom: 10px; }
  .xf-pdp .xf-product-info__price-row { padding: 14px 0 16px; margin-bottom: 14px; }
  /* Липкая шапка на телефоне ниже (118 px против 154) — якорю нужен свой отступ. */
  .xf-pdp .xf-product-tabs { scroll-margin-top: 132px; }
  .xf-pdp .xf-key { margin-top: 16px; padding-top: 14px; }
  .xf-pdp .xf-key__name { padding-right: 16px; }
  /* Своя строка: в один ряд с «В наличии» хвост срока уходил под плавающий
     кружок чата (fixed, правый нижний угол) — на первом экране его край
     ложится ровно на эту строку. Плюс два такта читаются спокойнее одного. */
  .xf-pdp .xf-product-info__ready { flex: 0 0 100%; margin-top: 2px; }
  .xf-pdp .xf-product-info__ready::before { content: none; }

  /* Последняя крошка — имя текущего товара, слово в слово повторяющее H1
     двумя экранными строками ниже. На телефоне это ~75 px первого экрана,
     потраченные на дубль. Скрываем только визуальный хвост: BreadcrumbList
     в JSON-LD (head) живёт отдельно, разметку и путь наверх это не трогает.
     Разделитель перед ней НЕ трогаем: цепочка всегда a,[span,a]…,span,span —
     предпоследний ребёнок чётный, а `.xf-crumbs > *:nth-child(even)`
     (catsection.css:686, тот же брейкпоинт) уже прячет все разделители. */
  .xf-pdp .xf-crumbs > span:last-child { display: none; }
}

/* ─── Первый экран карточки на телефоне: название, цена и «В корзину» (#704) ──
   Замер на живом s1 (375×812, видеокарта, баннер cookie открыт): название с
   733-го пикселя — под баннером, цена на 894-м, «В корзину» на 1035-м. На
   баннер отвечают ~7% посетителей (docs/cookie-consent.md), то есть для
   остальных экран кончается на его верхней кромке. Товарная реклама ведёт на
   карточку телефонного посетителя, который не видит ни цены, ни названия.

   Что меняется (только ≤767, десктоп и планшет не тронуты):
   1. Фото — полоса фиксированной высоты вместо квадрата во всю ширину. Квадрат
      отдавал снимку 365 px, из которых пейзажный кадр (видеокарта 317×142)
      занимал меньше половины — остальное было белыми полями.
   2. Крошки — одна строка с прокруткой вместо двух, без пустого поля под ними.
   3. Порядок в колонке: название → цена → наличие → «В корзину». Срок
      получения, переключатель вариантов и рассрочка уходят ПОД кнопку.
      Разметка та же, что на десктопе: порядок задаёт `order`.
   После правки (375×812, тот же товар — три строки названия и зачёркнутая
   цена, баннер открыт): название 420–499, цена 501–536, «В корзину» 586–634,
   кромка баннера 650. Без зачёркнутой цены кнопка на 21 px выше.
   Чего правка НЕ достаёт: название в четыре строки (кнопка на 10–17 px под
   кромкой на 375×812 и 360×800), товар «под заказ» (плашка срока над кнопкой
   уводит её под баннер) и экраны ниже 700 px.
   Блок стоит ПОСЛЕ блока #364 намеренно: перекрывает его правила того же веса
   порядком в файле — не переносить выше. Обкатан на /dev/ под скоупом
   `:where(body.site_d1)`, скоуп снят при выкатке 2026-10-12. */
@media (max-width: 767px) {
  /* Высота фото-полосы: 44% ширины экрана, 160–300 px. 160 px на 360 —
     потолок, при котором кнопка ещё над баннером (360×800). */
  .xf-pdp { --xf-pdp-photo-h: clamp(160px, 44vw, 300px); }
  /* На настоящем телефоне экран ниже эмулятора на панели браузера: iPhone 13 в
     Safari — 664 px вместо 844, Android 360×800 в Chrome — около 672. Там фото
     уступает место тексту. Внизу стоит баннер cookie (87 px, #715). Два режима:
       • экран от 712 px — кнопка «В корзину» помещается в потоке: фото =
         100svh − 562 (низ кнопки = фото + 469; внизу баннер 87 + 6 воздуха),
         но не больше обычной полосы по ширине (правило выше);
       • ниже 712 px кнопку даёт липкая полоса «цена + В корзину» (#716), и
         фото занимает ровно столько, чтобы название в три строки кончалось у
         верхней кромки полосы: 100svh − 498 (низ названия = фото + 334;
         8 воздуха, полоса 69, баннер 87). Иначе под кромку попадала половина
         строки цены: цифры, срезанные полосой прямо над её же ценой, выглядят
         поломкой.
     Переключает режимы второй min(): его правая часть при 100svh < 712px
     огромна и не мешает, а от 712px падает ниже 150 и уступает первому режиму.
     Медиа-запрос по высоте не годится — он дёргал бы карточку, когда браузер
     прячет панели. svh, а не vh/dvh: это высота экрана с показанными панелями,
     она не меняется при прокрутке. Где svh нет, остаётся правило выше.
     Счёт — для названия в три строки: в две строки цена в потоке видна над
     полосой (и повторена в ней), в четыре — последняя строка уходит под
     кромку. Пороги раньше: 597 / 531 при баннере 122 px (первая выкатка #715),
     637 / 544 при баннере 162 px (#704).
     [#722] Это стартовое значение и запасной путь без JS. Под настоящее
     название (две строки, четыре, перенос шрифтом) высоту до первой
     отрисовки подгоняет скрипт сразу за полосой в element.php — те же два
     режима по живым размерам; он пишет --xf-pdp-photo-h в style у .xf-pdp.
     Нижний предел — 72 px (было 120): на экранах ниже ~620 px (iPhone SE в
     Safari — 553) при 120 px полоса закрывала название, а название важнее
     фото — по нему покупатель узнаёт, что он смотрит. */
  @supports (height: 100svh) {
    .xf-pdp {
      --xf-pdp-photo-h: clamp(72px, max(min(calc(100svh - 562px), clamp(160px, 44vw, 300px)), min(calc(100svh - 498px), calc((712px - 100svh) * 100 + 150px))), 300px);
    }
  }

  /* Крошки: одна строка от края до края экрана, прокрутка пальцем. Поля
     скроллера равны рельсу страницы, маска гасит текст только в этих полях —
     в покое (начало или конец цепочки) под ней ничего нет. Цепочку к ближней
     категории докручивает скрипт в element.php: посетителю с рекламы нужна
     именно она («другие видеокарты»), а не «Главная». */
  .xf-catpage__head.xf-pdp { padding-top: 0; padding-bottom: 0; }
  .xf-pdp .xf-crumbs {
    margin: 0 calc(var(--container-pad) * -1);
    padding: 8px var(--container-pad) 7px;
    white-space: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--container-pad), #000 calc(100% - var(--container-pad)), transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0, #000 var(--container-pad), #000 calc(100% - var(--container-pad)), transparent 100%);
  }
  .xf-pdp .xf-crumbs::-webkit-scrollbar { display: none; }
  /* Мишень ссылки — 30 px по высоте вместо 15: поля строчного элемента высоту
     строки не меняют, растёт только область нажатия. */
  .xf-pdp .xf-crumbs > a { padding: 8px 0 7px; }
  /* [#821] Низкий экран: поля строки крошек 8/7 → 4/3 px (мишень ссылки —
     26 px, выше 24 по WCAG 2.5.8). Тот же счёт по svh, что у шапки
     (header.css, --xf-low: 0 ниже 620 px, 60 от 680): на 320×568 эти 8 px —
     запас между последней строкой названия и липкой полосой. */
  @supports (height: 100svh) {
    .xf-catpage__head.xf-pdp { --xf-low: clamp(0px, calc(100svh - 620px), 60px); }
    .xf-pdp .xf-crumbs {
      padding-top: calc(4px + var(--xf-low) / 15);
      padding-bottom: calc(3px + var(--xf-low) / 15);
    }
    .xf-pdp .xf-crumbs > a {
      padding-top: calc(4px + var(--xf-low) / 15);
      padding-bottom: calc(3px + var(--xf-low) / 15);
    }
  }
  .xf-home__section.xf-pdp { padding-top: 0; }

  /* Фото. Слайд карусели (2+ фото) и плита единственного фото — одной высоты. */
  .xf-pdp .xf-product { gap: 8px; }
  .xf-pdp .xf-product-gallery { gap: 8px; }
  .xf-pdp .xf-product-gallery__thumb { aspect-ratio: auto; height: var(--xf-pdp-photo-h); }
  .xf-pdp .xf-product-gallery__thumb img { max-height: calc(var(--xf-pdp-photo-h) - 20px); }
  .xf-pdp .xf-product-gallery__dots { margin-top: 0; }
  .xf-pdp .xf-product-gallery__main .xf-art--photo { min-height: var(--xf-pdp-photo-h); padding: 10px 14px; }
  .xf-pdp .xf-product-gallery__main .xf-art--photo .xf-art__img { max-height: calc(var(--xf-pdp-photo-h) - 20px); }
  /* В низкой полосе вертикальный кадр (корпус, телефон) уже плашки гарантии:
     при высоте 140 его ширина ~90 px против 130 у плашки, и обёртка с
     overflow: hidden срезала плашке левый край («рантия 12 мес.»). Тот же
     приём, что у слайда карусели (.xf-product-gallery__thumb-wrap выше). */
  .xf-pdp .xf-product-gallery__main .xf-art--photo .xf-art__photo-wrap { min-width: 130px; justify-content: center; }
  .xf-pdp .xf-product-gallery__main:not(:has(.xf-art--photo)) { aspect-ratio: auto; height: var(--xf-pdp-photo-h); }

  /* Колонка товара. .xf-product-info__meta растворяется (display: contents),
     чтобы «В наличии» и срок получения встали в колонку порознь: наличие —
     над кнопкой, срок — под ней. JS на .xf-product-info__meta не опирается
     (grep по js/ и include/ — 0 обращений); якорь липкой полосы —
     .xf-product-info__actions — остаётся обычным блоком.
     Скринридер читает по DOM: название, наличие, срок, цена, рассрочка,
     варианты, кнопка — порядок тоже осмысленный. Фокус с клавиатуры без
     reading-flow идёт так же, по DOM, то есть «Варианты покупки» раньше
     кнопки, стоящей на экране выше; reading-flow (Chrome 137+) возвращает
     ему видимый порядок, остальные браузеры строку пропускают. */
  .xf-pdp .xf-product-info { display: flex; flex-direction: column; reading-flow: flex-visual; }
  .xf-pdp .xf-product-info__meta { display: contents; }
  .xf-pdp .xf-product-info__eyebrow { order: 1; }
  /* balance: строки названия выходят примерно равной длины, и правый край
     последней не упирается в поле — туда встаёт кружок чата (см. ниже). Число
     строк от этого не растёт; где свойства нет, перенос остаётся обычным, а
     название от кружка страхует общий страж (js/app.js initChatGuard, #643). */
  .xf-pdp .xf-product-info__title { order: 2; margin: 5px 0 7px; text-wrap: balance; }
  /* Линейки над и под ценой сняты: на телефоне цена, наличие и кнопка — одна
     группа, и держит её близость, а не рамка. */
  .xf-pdp .xf-product-info__price-row { order: 3; padding: 0; margin: 0; border: 0; }
  /* Наличие — своей строкой под ценой и слева. Правый край над кнопкой
     остаётся пустым намеренно: туда страж (#381; с #643 — общий, js/app.js)
     поднимает кружок чата, когда тот налезает на «В корзину» — на 64 px над
     кнопкой. Строка цены, строка наличия и поля между ними дают эту высоту
     и без зачёркнутой цены, так что кружок не садится на название. Исключение
     — «Цена по запросу» без строки наличия: там кружок достаёт до конца
     последней строки названия. */
  .xf-pdp .xf-product-info__stock { order: 4; margin-top: 5px; }
  /* «Под заказ» — над кнопкой, как и было: срок 7–14 дней покупатель читает
     ДО действия (разрыв «обещание ↔ факт», CLAUDE.md). */
  .xf-pdp .xf-product-info__preorder-note { order: 5; margin: 12px 0 0; }
  .xf-pdp .xf-product-info__actions { order: 6; margin: 10px 0 0; }
  /* 48 px — высота той же кнопки в липкой полосе (.xf-buy-sticky__btn): близнецы
     одного роста, а первому экрану это 4 px. */
  .xf-pdp .xf-product-info__actions .xf-btn-buy { height: 48px; }
  /* Переключатель вариантов — ПОД кнопкой. Чип не настраивает покупку, а уводит
     на соседнюю карточку (docs/variant-switcher.md): «В корзину» кладёт ровно
     тот товар, чьё название и цена стоят выше. Над кнопкой ряд чипов (~84 px)
     уносил её под баннер cookie на каждой карточке с вариантами. Контракт JS
     (data-xf-variant-anchor / -link) от места блока не зависит: app.js меряет
     его положение на экране, а не порядок в колонке. */
  .xf-pdp .xf-product-info__variants { order: 8; margin: 16px 0 0; }
  /* Срок получения — первой строкой под рядом кнопок, раньше вариантов и
     рассрочки. Над кнопкой он стоил ей 23 px на 375 и 41 px на 360 (строка
     ломается надвое) — кнопка уходила под баннер. Доставку «за 1-2 дня» на
     первом экране держит полоса под шапкой (.xf-mcall), наличие — строка над
     кнопкой; ниже сгиба остаётся только час самовывоза. */
  .xf-pdp .xf-product-info__ready { order: 7; flex: 0 0 auto; margin-top: 14px; }
  .xf-pdp .xf-product-info__installment { order: 9; margin: 12px 0 0; }
  .xf-pdp .xf-product-info__benefits { order: 10; margin-top: 18px; }
  .xf-pdp .xf-key { order: 11; }
  .xf-pdp .xf-product-info .xf-admin-pencil { order: 12; align-self: flex-start; margin-top: 12px; }

  /* «Ключевое»: колонка названий — не шире 52% строки. Базовое правило даёт ей
     `auto`, то есть ширину самого длинного названия; на телефоне одно длинное
     название съедало всю строку, и значения ломались по букве в столбик.
     Замер на живом /dev/ 2026-10-12, 375 px: видеокарта RTX 3060 («Базовая
     (референсная) частота графического процессора») — названия 343 px,
     значения 0 px, блок высотой 1108 px сразу под кнопкой «В корзину»;
     Palit RTX 5070 — значениям 60 px, корпус — 97 px. Длинное название теперь
     переносится, значению остаётся не меньше 48% строки. На десктопе колонка
     товара шире 500 px и правило не нужно. */
  .xf-pdp .xf-key__list { grid-template-columns: fit-content(52%) minmax(0, 1fr); }
}

/* ─── Липкая полоса «цена + В корзину» над баннером cookie (#716) ──────────
   Полоса стояла на bottom: 0 ПОД баннером согласия (z-index 9999 против 50):
   у тех, кто на баннер не ответил, — а это ~93% посетителей — её не было видно
   вовсе, ни при прокрутке, ни на первом кадре. Теперь она встаёт на верхнюю
   кромку баннера; высоту баннера меряет app.js (--xf-cookie-h), вместе с ним
   полоса и опускается, когда выбор сделан.
   Когда полоса поднята — решает app.js (initMoneyBar, opts.below): основной
   кнопки нет на экране, ни над верхом, ни под баннером. На экране настоящего
   телефона так выходит уже на первом кадре.
   Нижнее поле под жест «домой» полосе над баннером не нужно — низ экрана
   держит баннер.
   [#720] На кромку баннера полоса встаёт, только когда показана (.is-visible);
   спрятанная стоит ПОД нижним краем окна всегда. Раньше спрятанная полоса
   поднималась вместе с баннером и стояла ровно там, где живёт кружок чата
   (390×664: полоса 577–646, кружок 596–648).
   bottom у полосы не анимируется (в #716 ехал .3s вместе с баннером): кружок
   чата встаёт над её КОНЕЧНЫМ местом сразу (chat.css), и полоса должна быть
   там же, а не в пути. Если страница какое-то время не рисовалась (вкладка
   открыта в фоне), таймер баннера срабатывал без кадров, а переходы стояли на
   старте — на первых кадрах полоса и кружок ехали навстречу друг другу, и
   кружок оказывался на «В избранное». Въезжает полоса по-прежнему своим
   transform — снизу, из-за баннера.
   Обкатано на /dev/ под скоупом d1, скоуп снят при выкатке 2026-10-12. */
@media (max-width: 767px) {
  body.xf-cookie-up .xf-buy-sticky { padding-bottom: 10px; }
  body.xf-cookie-up .xf-buy-sticky.is-visible { bottom: var(--xf-cookie-h, 0px); }
}
/* Наличие под ценой в полосе (#716): цена и наличие — одна колонка слева,
   кнопка справа. Цвета те же, что у строки наличия героя
   (.xf-product-info__stock): зелёный занят под «в наличии», «под заказ» —
   нейтральный. Слово само несёт смысл — цвет его только подкрепляет. */
@media (max-width: 767px) {
  /* Сетка, а не flex с переносом: у flex-контейнера с wrap ширина «по
     содержимому» считается как у одной строки — цена + наличие в ряд, — и блок
     цены отъедал у кнопки 57 px (замер на превью 390 px: кнопка 135 px вместо
     192; на 320 px подпись «В корзину» обрезалась). В сетке ширина блока —
     ширина самой цены. */
  .xf-buy-sticky__price {
    display: grid;
    grid-template-columns: auto auto;
    justify-content: start;
    align-items: baseline;
    column-gap: 4px;
    row-gap: 4px;
  }
  .xf-buy-sticky__stock {
    grid-column: 1 / -1;
    font: 500 11.5px/1.2 var(--font-sans);
    letter-spacing: 0;
    color: var(--text-3);
  }
  .xf-buy-sticky__stock.is-in { color: var(--success); }
  /* Верхняя кромка полосы — мягкая: строка, попавшая под неё наполовину
     (зачёркнутая цена, четвёртая строка названия), гаснет, а не обрезается
     по линейке. Градиент функциональный — та же роль, что у гашения краёв
     скроллера (DESIGN.md, The Flat-Surface Rule); цвет — фон самой полосы. */
  .xf-buy-sticky::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    bottom: 100%;
    height: 12px;
    background: linear-gradient(to top, color-mix(in oklch, var(--bg) 92%, transparent), transparent);
    pointer-events: none;
  }
}
/* На 320 px кнопке полосы остаётся 124 px, а «В корзину» со значком нужно 130:
   подпись ломалась в две строки («В / корзину»). Раньше это видели только
   после прокрутки, теперь полоса стоит на первом кадре. Поля и зазоры ужаты,
   подпись не переносится; «Купить под заказ» длиннее — там уходит значок. */
@media (max-width: 359px) {
  .xf-buy-sticky__inner { gap: 8px; }
  .xf-buy-sticky__btn { padding: 0 8px; gap: 6px; white-space: nowrap; }
  .xf-buy-sticky__btn[data-xf-preorder="1"] .xf-btn-buy__icon { display: none; }
}

/* ─── Первый экран карточки под настоящее название (#722) ─────────────────
   Высоту фото до первой отрисовки подгоняет скрипт в element.php (сразу за
   липкой полосой): название кончается у кромки полосы, цена не повторяется
   над полосой. Здесь — две вещи, без которых маленькое фото выглядит
   поломкой.
   1. .xf-pdp--tight ставит тот же скрипт, когда название не помещается над
      полосой и при самом низком фото (72 px): строка «бренд · артикул» уходит
      из-над названия под кнопку, первой строкой перед сроком получения. Бренд
      и так стоит в названии, артикул остаётся на странице.
   2. Плашка гарантии растёт вместе с фото: при фото от 126 px она та же, что
      была (10.5 px, поля 6/11, ширина от 130), ниже — компактнее (поля 3/6,
      ширина от 84, кегль до 10 px — мельче не читается), иначе на 72–100 px
      она закрывала половину снимка (замер на проде 375×553, фото 82 px).
      Только плашки галереи — у лайтбокса своя (там фото во весь экран). */
@media (max-width: 767px) {
  .xf-pdp.xf-pdp--tight .xf-product-info__eyebrow { order: 7; margin-top: 14px; }
  .xf-pdp .xf-product-gallery .xf-product-gallery__guarantee {
    font-size: clamp(10px, calc(var(--xf-pdp-photo-h) / 12), 10.5px);
    padding: clamp(3px, calc(var(--xf-pdp-photo-h) / 21), 6px) clamp(6px, calc(var(--xf-pdp-photo-h) / 11.5), 11px);
    min-width: clamp(84px, calc(var(--xf-pdp-photo-h) * 1.04), 130px);
  }
}

/* ─── Lightbox ────────────────────────────────────────────────────── */
/* Fullscreen image inspect, открывается по клику на main image (desktop)
   или thumb (mobile). Закрытие: ESC / X / клик по фону. Prev-Next через
   data-xf-gallery JSON-список (см. element.php inline script). */
.xf-lightbox {
  position: fixed;
  inset: 0;
  /* z-index 10000 чтобы перекрыть cookie-banner (yam.user.cookies сидит
     на z-index: 9999 — classic stack-hack, по-хорошему вся stacking-логика
     должна быть на токенах --z-modal/--z-toast/--z-cookie, но это отдельный
     рефакторинг). Lightbox это fullscreen modal: пользователь не должен
     видеть consent-overlay поверх инспекции картинки. */
  z-index: 10000;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 56px 24px;
  /* Полностью непрозрачный фон (alpha 1.0): через 0.94 alpha белый cookie-banner
     просвечивал из-под лайтбокса узкой полосой внизу. Modal должен блокировать
     визуально, не только pointer-events. */
  background: oklch(0.12 0.015 285);
}
.xf-lightbox.is-open { display: flex; }
.xf-lightbox__img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  touch-action: pinch-zoom;
  user-select: none;
  -webkit-user-drag: none;
}
.xf-lightbox__btn {
  position: absolute;
  z-index: 2;   /* над плашкой гарантии: на широком кадре телефона она заходит под стрелку */
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  /* Subtle dark outline + shadow чтобы кнопка читалась на любой картинке
     (включая светлые с белым фоном, где var(--surface) сливается). */
  box-shadow: 0 0 0 1px oklch(0 0 0 / 0.18), 0 6px 16px oklch(0 0 0 / 0.35);
  transition: background .15s, transform .15s;
}
.xf-lightbox__btn:hover { background: var(--surface-2); }
/* Тёмная тема: стрелки, крестик и счётчик были тёмными на тёмном — светлая
   дымка с рамкой и белый знак (счётчик — правило ниже, после своего базового). */
html[data-theme="dark"] .xf-lightbox__btn {
  background: oklch(1 0 0 / 0.14);
  border-color: oklch(1 0 0 / 0.36);
  color: oklch(1 0 0);
}
@media (hover: hover) {
  html[data-theme="dark"] .xf-lightbox__btn:hover { background: oklch(1 0 0 / 0.24); }
}
.xf-lightbox__close { top: 20px; right: 20px; }
.xf-lightbox__nav--prev { left: 20px; top: 50%; transform: translateY(-50%); }
.xf-lightbox__nav--next { right: 20px; top: 50%; transform: translateY(-50%); }
.xf-lightbox__counter {
  position: absolute;
  z-index: 2;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 12px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.5px;
}
html[data-theme="dark"] .xf-lightbox__counter {
  background: oklch(1 0 0 / 0.14);
  border-color: oklch(1 0 0 / 0.36);
  color: oklch(1 0 0);
}
/* #754: плашка гарантии в лайтбоксе. Вид — общий с плашкой на фото страницы
   (.xf-product-gallery__guarantee выше), здесь только угол и размер; оба
   считает скрипт в element.php (lbPlace) по прямоугольнику самого кадра.
   Размер: вотермарк catalog.onliner.by выжжен в файл в ПИКСЕЛЯХ ФАЙЛА, а не
   долей кадра. Замер 2026-10-16 на шести файлах шириной 460–1000 px (SSD
   Kingston A400 700×490, Samsung PM897 581×406, Deepcool PF750 460×366,
   G.Skill Aegis 1000×232, Patriot Viper Xtreme 880×752, ID-Cooling SE-214
   786×908): текст 118×11 px с ореолом ~4 px, всюду в 23 px от правого края и
   17 px от нижнего; с ореолом он занимает угол 150×34 px файла. Плашка в
   пикселях файла — шрифт 16px: 206×39 px, запас по высоте ~5 px. Отрисованная
   плашка = это × s (кадр на экране / файл), то есть растёт с картинкой ровно
   как вотермарк. Пол — размер плашки на странице (11.5px, на телефоне 10.5px):
   на узком экране кадр ужат вчетверо, и плашка из s вышла бы нечитаемой —
   вотермарк там мельче, перекрыт с лихвой. Кнопки и счётчик — над плашкой
   (z-index 2). */
.xf-lightbox .xf-lightbox__guarantee {
  z-index: 1;
  right: var(--xf-lb-plate-r, 0px);
  bottom: var(--xf-lb-plate-b, 0px);
  font-size: max(11.5px, calc(16px * var(--xf-lb-plate-s, 0)));
  min-width: 12.9em;
  padding: .61em 1.22em;
  border-radius: .87em 0 0 0;
}

/* Make main image и thumbs clickable hint. */
.xf-product-gallery__main .xf-art--photo,
.xf-product-gallery__main .xf-art__img { cursor: zoom-in; }

@media (max-width: 767px) {
  .xf-product-gallery__thumb { cursor: zoom-in; }
  .xf-lightbox { padding: 56px 8px 64px; }
  .xf-lightbox__btn { width: 40px; height: 40px; }
  .xf-lightbox__close { top: 8px; right: 8px; }
  .xf-lightbox__nav--prev { left: 4px; }
  .xf-lightbox__nav--next { right: 4px; }
  .xf-lightbox .xf-lightbox__guarantee { font-size: max(10.5px, calc(16px * var(--xf-lb-plate-s, 0))); }
}
@media (prefers-reduced-motion: reduce) {
  .xf-lightbox__btn { transition: none; }
  .xf-product-video__facade::after,
  .xf-product-video__play,
  .xf-product-video__play-bg { transition: none; }
  .xf-product-video__facade:hover .xf-product-video__play { transform: translate(-50%, -50%); }
  /* [#364] Липкая панель покупки едет 69 px и под reduce (замерено покадрово
     на живом /dev/: ход одинаковый с редукцией и без). Прячет её собственный
     transform, поэтому множителем `--xf-motion` её гасить нельзя — CLAUDE.md
     для этого случая предписывает точечный `transition: none`. Селектор
     повторяет `.xf-buy-sticky.is-initialized` (там объявлен сам transition):
     при меньшей специфичности отмена не сработала бы. */
  .xf-buy-sticky.is-initialized { transition: none; }
}

/* End */


/* Start:/local/templates/xcore_front/css/installment.css?179264523113382*/
/* OWNS: .installment__* — xcore_front restyle попапа dev-seo:installment (PDP «Варианты покупки в рассрочку и кредит»). Cross-ref: product.css владеет .xf-product-info__installment-* (inline «от N/мес» + скрытие .installment__link). */
/* ==========================================================================
   installment.css — нативный вид попапа рассрочки/кредита (issue #58).

   Компонент `dev-seo:installment` ОБЩИЙ с продом (aspro_next тоже его рендерит),
   поэтому его .default/style.css не трогаем. Этот файл — часть бандла xcore_front
   (header.php), который грузится ТОЛЬКО под шаблоном xcore_front → прод не
   затрагивается (тот же приём, что #modalProducts в modal.css и bitrix-форма
   в form.css). Только перекрашиваем/переразмечаем output, разметку и поведение
   (script.js) не трогаем.

   Скоуп — `.installment` (корень компонента, только PDP element.php). Двойной
   класс `.installment .installment__*` даёт 2-0-0 специфичности и кладёт 1-0-0
   правила .default/style.css при любом порядке загрузки. Где компонент ставит
   !important (кнопка, картинка) — бьём своим !important.

   Модальное окно повторяет язык #modalProducts (тот же скрим/blur/surface/close),
   форма — язык .xf-field/.xf-btn. Light — канон; dark работает через токены.
   ========================================================================== */

/* ---------- Overlay / scrim (как #modalProducts) ---------- */
.installment .installment__popup {
  background: oklch(0.08 0.02 285 / 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  padding: 16px;
}

/* ---------- Карточка попапа ---------- */
.installment .installment__content {
  width: 100%;
  max-width: 720px;
  max-height: min(90vh, 860px);
  margin: 0;
  padding: 24px;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--line-soft);
  border-radius: 18px;
  box-shadow: var(--shadow-card);
  overflow-x: hidden;
  overflow-y: auto;
}

/* ---------- Лоадер ---------- */
.installment .installment__loader {
  background: color-mix(in oklch, var(--surface) 80%, transparent);
}
.installment .installment__loader_active:after {
  border-color: var(--accent);
  border-bottom-color: transparent;
}

/* ---------- Шапка: заголовок + крестик ---------- */
.installment .installment__top {
  margin-bottom: 20px;
  gap: 16px;
}
.installment .installment__title {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.3px;
  line-height: 1.2;
  color: var(--text);
}
.installment .installment__close {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  transition: background .15s, border-color .15s;
}
.installment .installment__close:before,
.installment .installment__close:after {
  left: 50%;
  top: 50%;
  width: 15px;
  height: 1.5px;
  margin: -0.75px 0 0 -7.5px;
  background: var(--text-2);
}
@media (hover: hover) {
  .installment .installment__close:hover {
    background: var(--surface-3);
    border-color: var(--accent-line);
  }
  .installment .installment__close:hover:before,
  .installment .installment__close:hover:after {
    background: var(--text);
  }
}

/* ---------- Товар (картинка + имя + «от N/мес») ---------- */
.installment .installment__middle {
  align-items: center;
  gap: 16px;
  padding-bottom: 16px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line-soft);
}
.installment .installment__picture {
  margin: 0 !important;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
}
.installment .installment__product { min-width: 0; } /* flex-child guard — длинные имена не распирают строку */
.installment .installment__name {
  margin: 0 0 4px;
  font-family: var(--font-sans);
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
  overflow-wrap: anywhere;
}
@media (hover: hover) {
  .installment .installment__name:hover { color: var(--accent-2); }
}
/* [#305] Нейтральный, не --success — третья и последняя точка, где «от N/мес»
   красилось в зелёный наличия. Правило «зелёный = только наличие» не работает
   с исключениями, поэтому попап приведён к тому же --text-2, что карточка и
   PDP. Скоуп файла — xcore_front, компонент dev-seo:installment общий с
   aspro_next, но эти правила до aspro не доезжают. */
.installment .installment__from {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.2px;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}
.installment .installment__from .installment__link_bold { font-weight: 600; }

/* ---------- Заголовки колонок (eyebrow-метки → mono) ---------- */
.installment .installment__heading .installment__column {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-3);
}

/* ---------- Список предложений ---------- */
/* padding: 0 — у <ul> свой отступ браузера: строки уезжали вправо от шапки
   колонок (#644, на телефоне — на треть ширины). */
.installment .installment__table { margin: 12px 0 0; padding: 0; }
/* Шапка колонок — с теми же боковыми полями, что у строки (padding 14px +
   рамка 1px), иначе «ЕЖЕМЕСЯЧНЫЙ ПЛАТЁЖ» стоит не над суммами. */
.installment .installment__heading { padding: 0 15px; }
.installment .installment__item {
  align-items: center;
  padding: 12px 14px;
  margin: 0 0 10px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  transition: background .15s, border-color .15s;
}
@media (hover: hover) {
  .installment .installment__item:hover {
    background: var(--accent-dim);
    border-color: var(--accent-line);
  }
}
.installment .installment__table .installment__item .installment__column:first-child span {
  color: var(--text);
  font-weight: 500;
  text-decoration: none;
}
.installment .installment__column img { border-radius: 6px; }
/* числовые значения = data → JetBrains Mono */
.installment .installment__calc {
  font-family: var(--font-mono);
  color: var(--text);
  letter-spacing: -0.2px;
  font-variant-numeric: tabular-nums;
}
.installment .installment__calc span,
.installment .installment__column:first-child + .installment__column span {
  font-family: var(--font-mono);
  font-weight: 600;
}
.installment .installment__total {
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: -0.1px;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}

/* Телефон: компонент прячет логотипы банков (<=768px) — по логотипу человек
   и узнаёт свою карту (#644). Ставим его над названием: в строку рядом с
   именем на 390px места нет. Второй столбец («первый платёж 0») скрыт
   компонентом вместе с шапкой — оставляем так. */
@media (max-width: 768px) {
  .installment .installment__table .installment__column:first-child img {
    display: block;
    margin: 0 0 6px !important;
  }
}

/* ---------- Дисклеймер ---------- */
.installment .installment__quote {
  padding: 14px 0 20px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-3);
  border-top: 1px solid var(--line-soft);
}

/* ---------- Форма заявки (центральная правка issue #58) ---------- */
.installment .installment__form { padding: 4px 0 8px; }
.installment .installment__form form {
  width: 100%;
  align-items: stretch;
  gap: 14px;
}
.installment .installment__form fieldset {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-inline-size: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
.installment .installment__form fieldset + fieldset { margin-top: 0; }
.installment .installment__form label {
  width: auto;
  margin: 0;
  text-align: left;
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-2);
}
.installment .installment__form input[type="text"] {
  width: 100%;
  box-sizing: border-box;
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  -webkit-appearance: none;
  appearance: none;
  transition: border-color .15s, background .15s, box-shadow .15s;
}
.installment .installment__form input[type="text"]::placeholder { color: var(--text-3); }
/* [#406] Рамка и свечение ниже — вторичный признак, не индикатор; кольцо
   приходит из глобального ui.css. `outline` тут не гасить. */
.installment .installment__form input[type="text"]:focus-visible {
  border-color: var(--accent);
  background: var(--surface-3);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent) 22%, transparent);
}

/* Сабмит — единственный accent-fill в попапе (One-Voice Indigo). Перебиваем
   !important кнопки из .default/style.css (height/line-height/padding).
   Скоуп `[type="submit"]` — с #60 в .installment__form лежит ещё и
   <button.installment__back> (type="button"), его этот accent-fill не касается. */
.installment .installment__form button[type="submit"] {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  margin-top: 6px;
  height: auto !important;
  padding: 13px 22px !important;
  line-height: 1.2 !important;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: #fff;
  background: var(--accent);
  background-origin: border-box;
  border: 1px solid transparent;
  border-radius: 10px;
  -webkit-appearance: none;
  appearance: none;
  box-shadow: 0 6px 20px -8px var(--accent), 0 0 0 1px oklch(1 0 0 / 0.05) inset;
  cursor: pointer;
  transition: transform .15s, box-shadow .15s, background .15s;
}
/* Тёмная (#857), как у .xf-btn: вместо цветной тени — блик по верхней кромке
   (DESIGN.md, The Night-Sign Rule); наведение — --accent-3: --accent-2 там
   фиолет для текста, белая подпись на нём 1.9:1. */
html[data-theme="dark"] .installment .installment__form button[type="submit"] { box-shadow: var(--nx-key); }
@media (hover: hover) {
  .installment .installment__form button[type="submit"]:hover {
    background: var(--accent-2);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
  html[data-theme="dark"] .installment .installment__form button[type="submit"]:hover { background: var(--accent-3); }
}
@media (prefers-reduced-motion: reduce) {
  .installment .installment__form button[type="submit"] { transition: background .15s; }
  .installment .installment__form button[type="submit"]:hover { transform: none; }
}

/* iOS Safari зумит вьюпорт при фокусе, если font-size < 16px */
@media (max-width: 767px) {
  .installment .installment__title { font-size: 19px; }
  .installment .installment__form input[type="text"] {
    font-size: 16px;
    padding: 11px 13px;
  }
}

/* ---------- Ссылка «Вернуться к выбору предложений» ---------- */
.installment .installment__back {
  margin-top: 16px;
  padding: 8px 10px 8px 18px; /* увеличенная зона тапа на мобиле */
  border-bottom: 0;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--text-2);
}
.installment .installment__back:before {
  border-left: 1.5px solid var(--text-2);
  border-bottom: 1.5px solid var(--text-2);
}
.installment .installment__back:after { background: var(--text-2); }
@media (hover: hover) {
  .installment .installment__back:hover { color: var(--accent-2); }
  .installment .installment__back:hover:before {
    border-left-color: var(--accent-2);
    border-bottom-color: var(--accent-2);
  }
  .installment .installment__back:hover:after { background: var(--accent-2); }
}

/* ---------- Успех ---------- */
.installment .installment__success {
  padding: 24px 10px 32px;
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 500;
  text-align: center;
  color: var(--success);
}

/* End */


/* Start:/local/templates/xcore_front/css/cart.css?179264120217644*/
/* OWNS: .xf-cart-* */
/* M6: cart page (/dev/basket/) */

.xf-cart__title {
  margin: 18px 0 0;
  font-size: 38px;
  font-weight: 600;
  letter-spacing: -1.2px;
  line-height: 1.05;
  display: inline-flex;
  align-items: baseline;
  gap: 14px;
}
.xf-cart__title-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 10px;
  background: var(--accent-dim);
  color: var(--accent-2);
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1;
  position: relative;
  top: -6px;
}

.xf-cart {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: 28px;
  align-items: start;
}

.xf-cart__list {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* ─── Cart item row ─────────────────────────────────────────── */
.xf-cart-item {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr) 140px 130px 36px;
  gap: 18px;
  align-items: center;
  padding: 16px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}

.xf-cart-item__img {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 96px;
  height: 96px;
  background: var(--surface-2);
  border-radius: 10px;
  overflow: hidden;
  color: var(--text-3);
}
.xf-cart-item__img img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
/* Тёмная тема: фото товара — тот же лайтбокс, что у .xf-art--photo
   (home.css): белая плита и тон наложением multiply поверх плиты и фото
   сразу, слой — --nx-lightbox (tokens.css). Белая плита — только при фото:
   без него там иконка на обычной тёмной подложке. */
html[data-theme="dark"] .xf-cart-item__img:has(img) {
  position: relative;
  background: #fff;
  isolation: isolate;
}
html[data-theme="dark"] .xf-cart-item__img:has(img)::after {
  content: "";
  display: block;
  position: absolute;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  mix-blend-mode: multiply;
  background: var(--nx-lightbox);
}

.xf-cart-item__main {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.xf-cart-item__eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--text-3);
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  row-gap: 2px;
}
.xf-cart-item__article {
  color: var(--text-3);
  white-space: nowrap;
}
.xf-cart-item__name {
  font-size: 15px;
  font-weight: 500;
  color: var(--text);
  text-decoration: none;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
@media (hover: hover) {
  .xf-cart-item__name:hover { color: var(--accent-2); }
}
.xf-cart-item__price {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-2);
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  row-gap: 2px;
}
.xf-cart-item__price-cur {
  color: var(--text-3);
  font-size: 0.85em;
  white-space: nowrap;
}
.xf-cart-item__price-each {
  font-family: var(--font-sans);
  color: var(--text-3);
  font-size: 12px;
  margin-left: 4px;
  white-space: nowrap;
}

/* ─── Qty stepper ───────────────────────────────────────────── */
.xf-cart-item__qty form {
  display: inline-flex;
  align-items: center;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  overflow: hidden;
}
.xf-cart-item__qty-btn {
  width: 36px;
  height: 36px;
  border: 0;
  background: transparent;
  color: var(--text);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  transition: background .15s, color .15s;
}
@media (hover: hover) {
  .xf-cart-item__qty-btn:hover:not(:disabled) { background: var(--surface-3); color: var(--accent-2); }
}
.xf-cart-item__qty-btn:disabled { color: var(--text-3); cursor: not-allowed; }
.xf-cart-item__qty-val {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  width: 36px;
  text-align: center;
}

/* ─── Sum ────────────────────────────────────────────────────── */
.xf-cart-item__sum {
  font-family: var(--font-mono);
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
  text-align: right;
  display: inline-flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 6px;
}
.xf-cart-item__sum-cur { color: var(--text-3); font-size: 0.7em; font-weight: 500; }

/* ─── Remove ────────────────────────────────────────────────── */
.xf-cart-item__remove button {
  width: 36px;
  height: 36px;
  border: 0;
  background: transparent;
  color: var(--text-3);
  border-radius: 8px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background .15s, color .15s;
}
@media (hover: hover) {
  .xf-cart-item__remove button:hover { background: oklch(0.72 0.2 25 / 0.14); color: var(--danger); }
}

/* ─── Недоступный товар ─────────────────────────────────────── */
/* CATALOG_AVAILABLE='N' после добавления (availability gate: его PDP
   отдаёт 404): ряд приглушён, qty-степпер disabled, живым остаётся
   только «удалить». Метка — словарь избранного («Нет в наличии»,
   warning + dot, см. .xf-product-card__stock--none в home.css);
   сама метка не глушится — это объяснение, оно должно читаться. */
.xf-cart-item--dead .xf-cart-item__img,
.xf-cart-item--dead .xf-cart-item__eyebrow,
.xf-cart-item--dead .xf-cart-item__name,
.xf-cart-item--dead .xf-cart-item__price,
.xf-cart-item--dead .xf-cart-item__qty,
.xf-cart-item--dead .xf-cart-item__sum { opacity: 0.55; }
.xf-cart-item__stock {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
  color: var(--warning);
}
.xf-cart-item__stock-dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: currentColor;
  flex: 0 0 auto;
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 18%, transparent);
}

/* ─── Clear-cart row ────────────────────────────────────────── */
.xf-cart-item.is-removing {
  opacity: 0.4;
  pointer-events: none;
  position: relative;
  transition: opacity .2s;
}
.xf-cart-item.is-removing::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    -45deg,
    transparent 0,
    transparent 8px,
    color-mix(in oklch, var(--text) 6%, transparent) 8px,
    color-mix(in oklch, var(--text) 6%, transparent) 14px
  );
  border-radius: inherit;
  pointer-events: none;
}

.xf-cart__clear-row {
  margin-top: 4px;
  text-align: right;
}
.xf-cart__clear {
  background: transparent;
  border: 0;
  color: var(--text-3);
  font-family: var(--font-sans);
  font-size: 13px;
  cursor: pointer;
  padding: 8px 4px;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-cart__clear:hover { color: var(--danger); }
}

/* ─── Summary aside ─────────────────────────────────────────── */
.xf-cart__summary {
  position: sticky;
  top: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 22px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}
.xf-cart__summary-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent-2);
  margin-bottom: 6px;
}
.xf-cart__summary-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 14px;
  color: var(--text-2);
}
.xf-cart__summary-line span:last-child {
  font-family: var(--font-mono);
  color: var(--text);
}
.xf-cart__summary-line--soft span:last-child {
  font-family: var(--font-sans);
  color: var(--text-3);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0;
}
.xf-cart__summary-divider {
  height: 1px;
  background: var(--line-soft);
  margin: 6px 0;
}
.xf-cart__summary-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 12px;
}
.xf-cart__summary-total > span:first-child {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-2);
  font-family: var(--font-sans);
  letter-spacing: 0;
}
.xf-cart__summary-total-val {
  font-family: var(--font-mono);
  font-size: 24px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.4px;
}
.xf-cart__summary-total-cur { font-size: 0.6em; color: var(--text-3); margin-left: 4px; }

.xf-cart__order { width: 100%; margin-top: 4px; }
/* [#642] Последний товар висит в «Удалено — Восстановить», итог 0: оформлять
   нечего. Кнопка уходит в нейтраль без glow и подъёма, клик глушит js/app.js
   (syncOrderCta). aria-disabled, а не снятая ссылка: «Восстановить» вернёт
   кнопку как была. :root поднимает специфичность над hover тёмной темы
   (ui.css, 0-3-1) — иначе на наведении она снова загоралась бы. */
:root .xf-btn.xf-cart__order[aria-disabled="true"],
:root .xf-btn.xf-cart-sticky__btn[aria-disabled="true"] {
  background: var(--surface-2);
  color: var(--text-3);
  border-color: var(--line-soft);
  box-shadow: none;
  transform: none;
  cursor: not-allowed;
}
.xf-cart__summary-note {
  margin-top: 10px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-3);
}
.xf-cart__summary-trust {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}
.xf-cart__summary-trust-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-3);
}
.xf-cart__summary-trust-row svg {
  flex: 0 0 14px;
  color: var(--text-3);
}
/* #648: «Условия возврата» — ссылка; base.css снимает у ссылок подчёркивание. */
.xf-cart__summary-trust-row a { text-decoration: underline; text-underline-offset: 2px; }

/* ─── Empty cart ────────────────────────────────────────────── */
.xf-cart-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 56px 20px;
  text-align: center;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}
.xf-cart-empty__icon {
  color: var(--text-3);
  margin-bottom: 18px;
  opacity: 0.6;
}
.xf-cart-empty__title {
  margin: 0 0 8px;
  font-size: 24px;
  font-weight: 600;
}
.xf-cart-empty__text {
  margin: 0 0 22px;
  color: var(--text-2);
  font-size: 14px;
}

/* ─── Sticky mini-bar (mobile only) ─────────────────────────── */
/* Reuses m3.6 triple-guard contract: transform+opacity+pointer-events
   hidden in base WITHOUT transition; .is-initialized enables transitions
   (added after first IO callback); .is-visible ставит app.js initMoneyBar:
   итоги ушли за верх экрана ИЛИ (#786) «Оформить заказ» в итогах ещё ниже
   видимой части — под низом окна или под баннером cookie. */
.xf-cart-sticky { display: none; }

@media (max-width: 767px) {
  /* ─── Cart grid → single column ──────────────────────────── */
  .xf-cart {
    grid-template-columns: 1fr;
    gap: 14px;
    padding-bottom: 84px; /* breathing room for sticky bar */
  }

  /* ─── Item card: 2-row layout ────────────────────────────── */
  /* row 1: img 72 | main (brand·art, name, price); row 2: qty | sum.
     Remove (×) absolutely positioned top-right of card. */
  .xf-cart-item {
    grid-template-columns: 72px minmax(0, 1fr);
    grid-template-rows: auto auto;
    gap: 10px 14px;
    padding: 14px;
    position: relative;
  }
  .xf-cart-item__img {
    grid-row: 1;
    grid-column: 1;
    width: 72px;
    height: 72px;
  }
  .xf-cart-item__main {
    grid-row: 1;
    grid-column: 2;
    padding-right: 36px; /* reserve for absolutely-positioned × */
  }
  .xf-cart-item__qty {
    grid-row: 2;
    grid-column: 1 / -1;
    justify-self: start;
  }
  .xf-cart-item__sum {
    grid-row: 2;
    grid-column: 1 / -1;
    justify-self: end;
    align-self: center;
    font-size: 17px;
  }
  .xf-cart-item__remove {
    position: absolute;
    top: 6px;
    right: 6px;
  }

  /* Qty stepper tap-targets from m2.4 already applied; keep. */
  .xf-cart-item__qty-btn { width: 44px; height: 44px; font-size: 20px; }
  .xf-cart-item__qty-val { width: 40px; }
  .xf-cart-item__remove button { width: 44px; height: 44px; }

  /* ─── Summary aside → static block below list ────────────── */
  .xf-cart__summary {
    position: static;
    top: auto;
  }

  /* ─── Sticky mini-bar ─────────────────────────────────────── */
  .xf-cart-sticky {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 14px calc(12px + env(safe-area-inset-bottom, 0px));
    background: color-mix(in oklch, var(--surface) 94%, transparent);
    backdrop-filter: saturate(140%) blur(14px);
    -webkit-backdrop-filter: saturate(140%) blur(14px);
    border-top: 1px solid var(--line-soft);
    box-shadow: 0 -8px 24px -14px oklch(0.1 0.02 270 / 0.25);

    /* hidden state — no transition in base (m3.6 contract) */
    transform: translateY(calc(100% + env(safe-area-inset-bottom, 0px)));
    opacity: 0;
    pointer-events: none;
  }
  .xf-cart-sticky.is-initialized {
    transition: transform .22s ease-out, opacity .18s ease-out;
  }
  .xf-cart-sticky.is-visible {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
  }
  .xf-cart-sticky__total {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .xf-cart-sticky__label {
    font-family: var(--font-sans);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0;
    color: var(--text-3);
  }
  .xf-cart-sticky__val {
    font-family: var(--font-mono);
    font-size: 18px;
    font-weight: 600;
    color: var(--text);
    line-height: 1.1;
    white-space: nowrap;
  }
  .xf-cart-sticky__val-cur {
    font-size: 0.7em;
    color: var(--text-3);
    margin-left: 3px;
    font-weight: 500;
  }
  .xf-cart-sticky__btn {
    flex: 0 0 auto;
    min-height: 44px;
    padding: 0 16px;
    white-space: nowrap;
  }
  /* [#786] Над баннером cookie, как полоса карточки (product.css, #716/#720):
     полоса видна с первого кадра (app.js initCartSticky), а у первого визита
     низ экрана занят баннером. Показанная полоса встаёт на его верхнюю кромку,
     спрятанная остаётся под нижним краем окна; bottom не анимируется — кружок
     чата встаёт над конечным местом полосы сразу (chat.css). Отступ под
     home-indicator над баннером не нужен — его держит баннер. */
  body.xf-cookie-up .xf-cart-sticky { padding-bottom: 12px; }
  body.xf-cookie-up .xf-cart-sticky.is-visible { bottom: var(--xf-cookie-h, 0px); }
  /* [#786] Пока полоса поднята, фокус с клавиатуры не должен уходить под неё
     (и под баннер): степпер «+» нижней строки иначе оставался за полосой.
     --xf-cookie-h живёт на body, корню её не видно — берём с запасом. */
  html:has(.xf-cart-sticky.is-visible) { scroll-padding-bottom: calc(84px + env(safe-area-inset-bottom, 0px)); }
  html:has(> body.xf-cookie-up .xf-cart-sticky.is-visible) { scroll-padding-bottom: 240px; }
  /* Полосу прячет её собственный transform — множителем --xf-motion её не
     погасить; под reduce гасим сам выезд (как .xf-buy-sticky, product.css #364). */
  @media (prefers-reduced-motion: reduce) {
    .xf-cart-sticky.is-initialized { transition: none; }
  }
}

/* End */


/* Start:/local/templates/xcore_front/css/auth.css?179264121913235*/
/* OWNS: .xf-auth-* (+ базовый .xf-field — коллизия с order.css, см. её OWNS) */
/* auth pages — /dev/auth/ + /registration/ + /forgot-password/ + /change-password/ */

.xf-auth {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(380px, 460px);
  gap: 40px;
  align-items: start;
}
.xf-auth--single {
  grid-template-columns: minmax(0, 540px);
  justify-content: center;
}

/* ─── Side panel ────────────────────────────────────────────── */
.xf-auth__side {
  padding: 24px 8px;
}
.xf-auth__side-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent-2);
  margin-bottom: 14px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.xf-auth__side-eyebrow::before {
  content: "";
  display: inline-block;
  width: 28px;
  height: 1px;
  background: var(--accent-2);
}
.xf-auth__side-title {
  margin: 0 0 24px;
  font-size: 30px;
  font-weight: 600;
  letter-spacing: -1.2px;
  line-height: 1.15;
  max-width: 22ch;
}
.xf-auth__side-list,
.xf-auth__side-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.xf-auth__side-list li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  font-size: 14px;
  color: var(--text-2);
  line-height: 1.45;
}
.xf-auth__side-list li svg {
  color: var(--accent-2);
  margin-top: 2px;
  flex-shrink: 0;
}
.xf-auth__side-steps {
  gap: 14px;
}
.xf-auth__side-steps li {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  font-size: 14px;
  color: var(--text-2);
  line-height: 1.45;
}
.xf-auth__step-num {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent-dim);
  color: var(--accent-2);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ─── Card ──────────────────────────────────────────────────── */
.xf-auth__card {
  padding: 30px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}

.xf-auth__eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent-2);
  margin-bottom: 8px;
}
.xf-auth__title {
  margin: 0 0 8px;
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -1px;
}
.xf-auth__sub {
  margin: 0 0 20px;
  color: var(--text-2);
  font-size: 14px;
  line-height: 1.5;
}

/* ─── Tabs ──────────────────────────────────────────────────── */
.xf-auth__tabs {
  display: inline-flex;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  padding: 4px;
  margin-bottom: 22px;
  width: 100%;
}
.xf-auth__tab {
  flex: 1;
  text-align: center;
  padding: 9px 14px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text-2);
  border-radius: 8px;
  text-decoration: none;
  transition: background .15s, color .15s;
}
@media (hover: hover) {
  .xf-auth__tab:hover { color: var(--text); }
}
.xf-auth__tab.is-active {
  background: var(--surface-3);
  color: var(--text);
}
.xf-auth__tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ─── Notice ────────────────────────────────────────────────── */
.xf-auth__notice {
  padding: 12px 14px;
  border-radius: 10px;
  font-size: 13px;
  line-height: 1.45;
  margin-bottom: 16px;
}
.xf-auth__notice--ok {
  background: color-mix(in oklch, var(--success) 12%, transparent);
  color: var(--success);
  border: 1px solid color-mix(in oklch, var(--success) 35%, transparent);
}
.xf-auth__notice--err {
  background: color-mix(in oklch, var(--danger) 12%, transparent);
  color: var(--danger);
  border: 1px solid color-mix(in oklch, var(--danger) 35%, transparent);
}
/* Ссылка в тексте уведомления: телефон в ошибке оформления (#841), «Войдите или
   восстановите пароль» (#835). Цвет — от уведомления, ссылку выдаёт подчёркивание;
   номер не рвётся на две строки. */
.xf-auth__notice a {
  color: inherit;
  font-weight: 600;
  white-space: nowrap;
  text-decoration: underline;
  text-underline-offset: 2px;
}
@media (hover: hover) { .xf-auth__notice a:hover { text-decoration: none; } }

/* ─── Form ──────────────────────────────────────────────────── */
.xf-auth__form {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.xf-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.xf-field__label {
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text-2);
  font-family: var(--font-sans);
  text-transform: none;
  letter-spacing: 0;
}
.xf-field__input {
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  transition: border-color .15s, background .15s, box-shadow .15s;
}
/* [#406] Рамка и свечение ниже — не индикатор фокуса (22% альфы = 1.34:1 при
   норме WCAG 1.4.11 = 3:1), индикатор это глобальное кольцо ui.css. `outline`
   тут не гасить. */
.xf-field__input:focus-visible {
  border-color: var(--accent);
  background: var(--surface-3);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent) 22%, transparent);
}

.xf-field__hint {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-2);
}

.xf-field--invalid .xf-field__input {
  border-color: var(--danger);
  background: color-mix(in oklch, var(--danger) 6%, var(--surface-2));
}
.xf-field--invalid .xf-field__input:focus-visible {
  border-color: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--danger) 25%, transparent);
}
.xf-field__error {
  display: block;
  margin-top: 4px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--danger);
}
/* #81: error-span на чекауте — постоянный live-region (role="alert"), пустой
   без ошибки. Пустой контейнер прячем, чтобы не давал визуальный зазор
   (margin/line-box). Не «новый визуал» — companion к a11y-изменению,
   сохраняет прежний вид поля без ошибки. */
.xf-field__error:empty {
  display: none;
}

.xf-btn.is-loading {
  cursor: wait;
  opacity: 0.7;
  pointer-events: none;
}

/* ─── Captcha (#835) ────────────────────────────────────────── */
/* Капча Битрикса в формах входа/регистрации/восстановления — появляется, только
   когда её требует ядро. Картинка + «обновить» тем же жестом, что на форме
   обращений (form.css). Серверную картинку не инвертируем и в dark — рискует
   читаемостью; JPEG без прозрачности закрывает плашку целиком, так что фон —
   только заглушка на время загрузки, тонированный нейтрал (Tinted-Neutral Rule).
   Обновление — [data-xf-captcha-reload] в app.js. */
.xf-auth__captcha-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.xf-auth__captcha-img {
  display: block;
  width: 180px;
  height: 40px;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  background: var(--surface-2);
}
.xf-auth__captcha-reload {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text-3);
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
@media (hover: hover) {
  .xf-auth__captcha-reload:hover {
    color: var(--accent);
    border-color: var(--accent-line);
  }
}
.xf-auth__captcha-reload svg { display: block; }
.xf-auth__captcha-reload.loading {
  pointer-events: none;
  opacity: 0.6;
}

.xf-auth__details {
  margin: -2px 0 0;
}
.xf-auth__details-summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  font-size: 13px;
  color: var(--text-2);
  user-select: none;
}
.xf-auth__details-summary::-webkit-details-marker { display: none; }
.xf-auth__details-summary::before {
  content: "+";
  display: inline-flex;
  width: 18px;
  height: 18px;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  color: var(--accent-2);
  background: var(--accent-dim);
  border-radius: 5px;
  transition: transform .15s, background .15s;
}
.xf-auth__details[open] .xf-auth__details-summary::before {
  transform: rotate(45deg);
}
.xf-auth__details-summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}
.xf-auth__details[open] .xf-auth__row-fields {
  margin-top: 10px;
}

@media (max-width: 767px) {
  /* iOS Safari zooms viewport on focus if input font-size < 16px. */
  .xf-field__input { font-size: 16px; padding: 10px 12px; }

  .xf-auth {
    grid-template-columns: minmax(0, 1fr);
    gap: 14px;
    /* Cookie consent banner (position:fixed bottom) ~140px tall on phone.
       Reserve safe area so submit clears it on first paint for short forms. */
    padding-bottom: 160px;
  }
  .xf-auth__card { order: 1; padding: 18px 16px; }
  .xf-auth__side { order: 2; padding: 8px 0 0; }
  .xf-auth[data-mode="login"] .xf-auth__side { display: none; }
  .xf-auth__side-title {
    font-size: 22px;
    letter-spacing: -0.6px;
    margin-bottom: 14px;
  }
  .xf-auth__side-list { gap: 8px; }
  .xf-auth__side-list li { font-size: 13px; }
  .xf-auth__title { font-size: 22px; letter-spacing: -0.6px; }

  /* Compress form rhythm on mobile to fit register (5 fields + details)
     above the cookie banner. */
  .xf-auth__form { gap: 8px; }
  .xf-auth__tabs { margin-bottom: 12px; padding: 3px; }
  .xf-auth__details-summary { padding: 6px 0; font-size: 12.5px; }
  .xf-auth__notice { margin-bottom: 12px; }

  /* Touch-target ≥44px on mobile (WCAG 2.5.5 AAA, 2.5.8 AA = 24px). */
  .xf-auth__tab { padding: 13px 14px; }
  .xf-auth__submit { scroll-margin-bottom: 180px; }
}

.xf-auth__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: -2px;
}
.xf-auth__row-fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
@media (max-width: 520px) {
  .xf-auth__row-fields { grid-template-columns: 1fr; }
}
.xf-checkbox {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--text-2);
  cursor: pointer;
  min-height: 32px;
  padding: 6px 0;
  margin: -6px 0;
}
.xf-checkbox input {
  accent-color: var(--accent);
  width: 16px;
  height: 16px;
}
.xf-checkbox input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

.xf-auth__link {
  color: var(--accent-2);
  font-size: 13px;
  text-decoration: none;
  padding: 8px 0;
  margin: -8px 0;
}
@media (hover: hover) {
  .xf-auth__link:hover { text-decoration: underline; }
}
/* Тёмная тема: «Забыли пароль?» светлым фиолетом без подчёркивания
   переставала выглядеть ссылкой — подчёркнута всегда, тихой линией. */
html[data-theme="dark"] .xf-auth__link {
  text-decoration: underline;
  text-decoration-color: oklch(1 0 0 / 0.4);
  text-underline-offset: 3px;
}
.xf-auth__link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

.xf-auth__submit { width: 100%; margin-top: 6px; }

.xf-auth__small {
  margin: 14px 0 0;
  font-size: 12px;
  color: var(--text-3);
  line-height: 1.5;
}
.xf-auth__small a {
  color: var(--accent-2);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: color-mix(in oklch, var(--accent-2) 45%, transparent);
}
.xf-auth__small a:hover,
.xf-auth__small a:focus-visible {
  text-decoration-color: var(--accent-2);
}
.xf-auth__small a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 3px;
}

.xf-auth__actions {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 8px;
}

/* End */


/* Start:/local/templates/xcore_front/css/order.css?179264121014026*/
/* OWNS: .xf-order-*, .xf-choice-*, .xf-field-* (form fields on order page; базовый .xf-field живёт в auth.css — коллизия отмечена там же) */
/* M7: order page (/dev/order/) */

.xf-order {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: 28px;
  align-items: start;
}

.xf-order__main {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* ─── Block (numbered section) ──────────────────────────────── */
.xf-order__block {
  padding: 24px 26px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}
.xf-order__block-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent-2);
  margin-bottom: 6px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}
.xf-order__block-eyebrow::before {
  content: "";
  display: inline-block;
  width: 28px;
  height: 1px;
  background: var(--accent-2);
}
.xf-order__block-title {
  margin: 0 0 18px;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.4px;
  line-height: 1.2;
}

/* ─── Contacts grid ─────────────────────────────────────────── */
.xf-order__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.xf-field--wide { grid-column: 1 / -1; }
.xf-field__req {
  color: var(--accent-2);
  font-family: var(--font-mono);
  margin-left: 2px;
}
.xf-field__input--area {
  resize: vertical;
  min-height: 88px;
  line-height: 1.45;
  font-family: var(--font-sans);
}

/* ─── Buyer-profile selector (#220 фаза 2): «Мои адреса» ────────
   Компактный select над полями контактов, виден только при 2+ профилях.
   Стиль зеркалит .xf-field__input (Onest, нейтральный), не двигает грид.
   Шеврон — mask по var(--text-3): тонируется темой, без второго ассета. */
.xf-order__profiles {
  margin-bottom: 16px;
}
/* [#220 ревью] Селектор с сервера идёт с hidden-атрибутом — снимает его JS на
   init. Без JS он инертен (select без name, POST несёт hidden-input с профилем
   префилла) и только путал бы. Явное правило — тот же паттерн, что
   .xf-choice[hidden] ниже: авторский display не должен перебить UA-hidden. */
.xf-order__profiles[hidden] { display: none; }
.xf-order__profiles > .xf-field__label {
  display: block;
  margin-bottom: 5px;
}
.xf-order__profiles-control {
  position: relative;
}
.xf-order__profiles-control::after {
  content: "";
  position: absolute;
  right: 14px;
  top: 50%;
  width: 16px;
  height: 16px;
  transform: translateY(-50%);
  pointer-events: none;
  background-color: var(--text-3);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 9l6 6 6-6' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / contain no-repeat;
}
.xf-order__profiles-select {
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  padding: 12px 40px 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 10px;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.2;
  cursor: pointer;
  transition: border-color .15s, background .15s, box-shadow .15s;
}
/* [#406] Рамка и свечение ниже — вторичный признак, не индикатор; кольцо
   приходит из глобального ui.css. `outline` тут не гасить. */
.xf-order__profiles-select:focus-visible {
  border-color: var(--accent);
  background: var(--surface-3);
  box-shadow: 0 0 0 3px color-mix(in oklch, var(--accent) 22%, transparent);
}

/* ─── Radio-cards for delivery / payment ────────────────────── */
.xf-order__choices {
  display: grid;
  gap: 10px;
}
/* Есть описания (доставка) → 1 колонка, чтобы не было асимметрии в
   парных ячейках когда одно описание короче другого. Нет описаний
   (оплата) → 2 колонки, компакт. */
.xf-order__choices:has(.xf-choice__desc) {
  grid-template-columns: 1fr;
}
.xf-order__choices:not(:has(.xf-choice__desc)) {
  grid-template-columns: 1fr 1fr;
}
@media (max-width: 720px) {
  .xf-order__choices:not(:has(.xf-choice__desc)) { grid-template-columns: 1fr; }
}

.xf-choice {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 14px 16px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  cursor: pointer;
  transition: background-color .2s ease, border-color .2s ease;
}
@media (hover: hover) {
  .xf-choice:hover { background: var(--surface-3); }
}
/* [#33/#34] hidden-атрибут обязан прятать и эти элементы: их авторский
   display:flex (здесь и .xf-field в auth.css) сильнее UA-правила [hidden] */
.xf-choice[hidden],
.xf-field[hidden] { display: none; }

/* [#72] Заблокированный по сумме заказа способ доставки: неактивный, не
   кликабельный. Нейтральный (без accent) — это не выбор, а недоступное
   состояние; цвет берёт на себя подсказка-порог .xf-choice__lock. */
.xf-choice--locked { cursor: not-allowed; }
.xf-choice--locked .xf-choice__dot { opacity: .4; }
.xf-choice--locked .xf-choice__name { color: var(--text-3); }
@media (hover: hover) {
  .xf-choice--locked:hover { background: var(--surface-2); }
}
.xf-choice__lock {
  margin-top: 2px;
  font-size: 12px;
  font-weight: 500;
  color: var(--text-2);
  line-height: 1.4;
}
.xf-choice input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.xf-choice__dot {
  margin-top: 3px;
  width: 16px;
  height: 16px;
  border-radius: 999px;
  border: 1.5px solid var(--line-soft);
  background: var(--surface);
  flex-shrink: 0;
  transition: border-color .2s ease, box-shadow .2s ease;
  position: relative;
}
.xf-choice__body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
  flex: 1;
}
.xf-choice__name {
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  line-height: 1.35;
}
/* Описание — условия способа (цена, сроки, порог) — видно ЦЕЛИКОМ у каждого
   способа, выбран он или нет (#642). Раньше невыбранный обрезался по 2 строки
   с фейдом и раскрывался только выбором: покупатель сравнивал способы по
   «От 20 BYN, точная сумма зависит от расстояния, габаритов и…», то есть не
   дочитывал условие, от которого зависит цена. Тексты короткие (до 3 строк на
   телефоне), поэтому честнее показать всё сразу, чем прятать за кликом.
   Цвет --text-2 в обоих состояниях: --text-3 на 12px не держит AA (≈4.4:1 на
   --surface-2, ниже на hover), а это не подпись, а условие сделки. */
.xf-choice__desc {
  font-size: 12px;
  color: var(--text-2);
  line-height: 1.5;
}

/* Interactive state driven by actual radio, not PHP-side class.
   Needs `:has()` — supported in all modern browsers. */
.xf-choice:has(input:checked) {
  background: var(--accent-dim);
  border-color: var(--accent-line);
}
.xf-choice:has(input:checked) .xf-choice__dot {
  border-color: var(--accent-2);
  box-shadow: inset 0 0 0 4px var(--accent-2);
}
.xf-choice:has(input:checked) .xf-choice__name { color: var(--text); }
.xf-choice input:focus-visible + .xf-choice__dot {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}
/* Тёмная тема: контур --line-soft на тёмной карточке не читался как «это
   можно нажать» — способы доставки выглядели справкой. 45% белого — контур
   4.5:1 к --surface. Выбранный (0-3-1 выше) остаётся в акценте. */
html[data-theme="dark"] .xf-choice__dot { border-color: oklch(1 0 0 / 0.45); }

/* ─── Summary aside ─────────────────────────────────────────── */
.xf-order__summary {
  position: sticky;
  top: 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 22px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  max-height: calc(100vh - 40px);
  overflow-y: auto;
}

.xf-order__summary-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 2px 0 4px;
  max-height: 320px;
  overflow-y: auto;
}
.xf-order__summary-item {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr);
  gap: 10px;
  padding: 8px 0;
  align-items: center;
}
.xf-order__summary-item + .xf-order__summary-item {
  border-top: 1px solid var(--line-soft);
  padding-top: 10px;
}
.xf-order__summary-img {
  width: 44px;
  height: 44px;
  background: var(--surface-2);
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  color: var(--text-3);
}
.xf-order__summary-img img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.xf-order__summary-body { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.xf-order__summary-name {
  font-size: 12.5px;
  line-height: 1.3;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-decoration: none;
  transition: color 0.15s ease;
}
a.xf-order__summary-name:hover {
  color: var(--accent-2);
}
.xf-order__summary-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 11.5px;
}
.xf-order__summary-qty { color: var(--text-3); }
.xf-order__summary-sum { color: var(--text); font-weight: 500; }
.xf-order__summary-cur { color: var(--text-3); font-size: 0.85em; margin-left: 2px; }

.xf-order__submit {
  width: 100%;
  margin-top: 6px;
}

/* ─── Success screen ────────────────────────────────────────── */
.xf-order-success {
  max-width: 620px;
  margin: 0 auto;
  padding: 36px 28px 40px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  text-align: center;
}
.xf-order-success__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 72px;
  height: 72px;
  margin-bottom: 18px;
  background: var(--accent-dim);
  color: var(--accent-2);
  border: 1px solid var(--accent-line);
  border-radius: 999px;
}
.xf-order-success__title {
  margin: 0 0 10px;
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.6px;
  line-height: 1.2;
}
.xf-order-success__sub {
  margin: 0 auto 24px;
  max-width: 44ch;
  color: var(--text-2);
  line-height: 1.5;
}

.xf-order-success__card {
  text-align: left;
  padding: 18px 20px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 22px;
}
.xf-order-success__line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 13.5px;
  color: var(--text-2);
}
.xf-order-success__val {
  font-family: var(--font-mono);
  color: var(--text);
  font-weight: 500;
  text-align: right;
  max-width: 62%;
  overflow-wrap: anywhere;
}
.xf-order-success__divider {
  height: 1px;
  background: var(--line-soft);
  margin: 4px 0;
}
.xf-order-success__total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-top: 2px;
}
.xf-order-success__total > span:first-child {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--text-2);
}
.xf-order-success__total-val {
  font-family: var(--font-mono);
  font-size: 24px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.4px;
}
.xf-order-success__total-cur { font-size: 0.6em; color: var(--text-3); margin-left: 4px; }

.xf-order-success__actions {
  display: inline-flex;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}

/* ─── Responsive ────────────────────────────────────────────── */
@media (max-width: 767px) {
  /* Main grid → single column, summary drops below form */
  .xf-order { grid-template-columns: 1fr; }
  .xf-order__summary {
    position: static;
    max-height: none;
  }
  /* Contacts → 1-col, pay-choices (no-desc) → 1-col, tighter padding */
  .xf-order__grid { grid-template-columns: 1fr; }
  .xf-order__block { padding: 18px 16px; }
  .xf-order__choices:not(:has(.xf-choice__desc)) {
    grid-template-columns: 1fr;
  }
  /* Radio cards: bigger tap target on phone */
  .xf-choice { padding: 16px; }
}

/* End */


/* Start:/local/templates/xcore_front/css/account.css?179215482727187*/
/* OWNS: .xf-account-* */
/* M8 — /dev/personal/: сайдбар + контент */

.xf-account {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: 28px;
  align-items: start;
}

/* Solo-режим: гость на public-страницах (compare/favorites) — sidebar
   спрятан, контент уходит на full-width. На mobile уже 1-col без модификатора. */
.xf-account--solo {
  grid-template-columns: minmax(0, 1fr);
}

/* Compare-режим (#172): таблице сравнения нужен full-width — на десктопе
   прячем сайдбар даже залогиненному (навигация остаётся в хлебных крошках:
   ссылка «Личный кабинет»). Прятать можно только десктопную колонку: на
   ≤767px aside — верхние scroll-tabs (см. @media ниже), ширину контенту
   не отъедает, а без него пропали бы навигация кабинета и «Выйти». */
@media (min-width: 768px) {
  .xf-account--compare {
    grid-template-columns: minmax(0, 1fr);
  }
  .xf-account--compare > .xf-account__side {
    display: none;
  }
}

/* ─── Sidebar ──────────────────────────────────────────────── */
.xf-account__side {
  position: sticky;
  top: 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding: 22px 18px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}

.xf-account__who {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 16px;
  border-bottom: 1px solid var(--line-soft);
}
.xf-account__avatar {
  flex: 0 0 42px;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: #fff;
  font-weight: 600;
  font-size: 16px;
  letter-spacing: -0.5px;
}
.xf-account__avatar--guest {
  background: var(--surface-2);
  color: var(--text-3);
  border: 1px solid var(--line-soft);
}
.xf-account__who--guest .xf-account__email {
  color: var(--accent-2);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
}
@media (hover: hover) {
  .xf-account__who--guest .xf-account__email:hover { color: var(--text); }
}
.xf-account__who-meta {
  min-width: 0;
  flex: 1;
}
.xf-account__name {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  line-height: 1.3;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* E-mail пользователя — контакт-аффорданс, не data → Onest, не mono
   (точечный ноль JetBrains Mono читается как 8; гостевой вариант уже sans). #91 */
.xf-account__email {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  color: var(--text-3);
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.xf-account__nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.xf-account__nav-link {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: 10px;
  font-size: 14px;
  color: var(--text-2);
  transition: background .15s, color .15s;
}
@media (hover: hover) {
  .xf-account__nav-link:hover {
  background: var(--surface-2);
  color: var(--text);
}
}
.xf-account__nav-link.is-active {
  background: var(--accent-dim);
  color: var(--accent-2);
}
.xf-account__nav-link svg { flex: 0 0 auto; }

.xf-account__logout {
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}
.xf-account__logout-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--text-3);
  font-family: var(--font-sans);
  font-size: 14px;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
@media (hover: hover) {
  .xf-account__logout-btn:hover {
  background: oklch(0.72 0.20 25 / 0.08);
  color: oklch(0.82 0.14 25);
  border-color: oklch(0.72 0.2 25 / 0.35);
}
}

/* ─── Content container ───────────────────────────────────── */
.xf-account__content {
  display: flex;
  flex-direction: column;
  gap: 28px;
  min-width: 0;
}

.xf-account__hero {
  padding: 28px;
  /* Плоский нейтральный surface вместо accent-tinted заливки (#280): блок
     держит accent через рамку и eyebrow, не через лиловый градиент. */
  background: var(--surface-2);
  border: 1px solid var(--accent-line);
  border-radius: var(--radius);
}
.xf-account__hero-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--accent-2);
}
.xf-account__hero-eyebrow::before {
  content: "";
  display: inline-block;
  width: 24px;
  height: 1px;
  background: var(--accent-2);
  vertical-align: middle;
  margin-right: 10px;
}
.xf-account__hero-title {
  margin: 12px 0 8px;
  font-size: 32px;
  font-weight: 600;
  letter-spacing: -1px;
  line-height: 1.1;
}
.xf-account__hero-sub {
  margin: 0;
  color: var(--text-2);
  font-size: 14px;
}

/* ─── Action cards (dashboard, new-user empty) ───────────── */
.xf-account__actions {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.xf-account__action {
  display: grid;
  grid-template-columns: 44px minmax(0, 1fr) 20px;
  gap: 14px;
  align-items: center;
  padding: 18px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  color: var(--text);
  transition: border-color .15s, transform .15s, background .15s;
}
@media (hover: hover) {
  .xf-account__action:hover {
    border-color: var(--accent-line);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
  .xf-account__action:hover .xf-account__action-arrow { color: var(--accent-2); }
}
.xf-account__action-icon {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-dim);
  color: var(--accent-2);
}
.xf-account__action-body { min-width: 0; }
.xf-account__action-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.2px;
  margin-bottom: 4px;
}
.xf-account__action-sub {
  font-size: 13px;
  color: var(--text-2);
  line-height: 1.4;
}
.xf-account__action-arrow {
  color: var(--text-3);
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  transition: color .15s;
}

/* ─── Section wrapper ─────────────────────────────────────── */
.xf-account__section {
  padding: 24px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}
.xf-account__section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 18px;
}
.xf-account__section-title {
  margin: 0;
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.5px;
}
.xf-account__section-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  color: var(--text-2);
  transition: color .15s;
}
@media (hover: hover) {
  .xf-account__section-link:hover { color: var(--text); }
}
.xf-account__section-meta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
}

/* ─── Product grid (favorites/compare) ─────────────────────── */
.xf-account__product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}

/* ─── Orders list ─────────────────────────────────────────── */
.xf-account__orders {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
.xf-account__orders--list {
  grid-template-columns: 1fr;
}
.xf-account__order {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  transition: border-color .15s, transform .15s;
}
@media (hover: hover) {
  .xf-account__order:hover {
  border-color: var(--accent-line);
  transform: translateY(calc(-1px * var(--xf-motion, 1)));
}
}
.xf-account__order--row {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.xf-account__order-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.xf-account__order-num {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.5px;
  color: var(--text-2);
}
.xf-account__order-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  color: var(--text-3);
  font-size: 12px;
}
.xf-account__order-meta span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.xf-account__order-price {
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.2px;
}
.xf-account__order-cur {
  font-size: 11px;
  color: var(--text-3);
  margin-left: 3px;
}

/* ─── Empty state ─────────────────────────────────────────── */
.xf-account-empty {
  text-align: center;
  padding: 40px 20px;
}
.xf-account-empty__icon {
  display: inline-flex;
  width: 76px;
  height: 76px;
  border-radius: 50%;
  background: var(--surface-2);
  align-items: center;
  justify-content: center;
  color: var(--text-3);
  margin-bottom: 16px;
}
.xf-account-empty__title {
  margin: 0 0 6px;
  font-size: 20px;
  font-weight: 600;
}
.xf-account-empty__text {
  margin: 0 0 20px;
  color: var(--text-2);
  font-size: 14px;
}

/* ─── Pagination ──────────────────────────────────────────── */
.xf-account__pager {
  display: flex;
  align-items: center;
  gap: 4px;
  justify-content: center;
  margin-top: 20px;
}
.xf-account__pager-link {
  min-width: 36px;
  height: 36px;
  padding: 0 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-2);
  transition: border-color .15s, color .15s, background .15s;
}
@media (hover: hover) {
  .xf-account__pager-link:hover {
  border-color: var(--accent-line);
  color: var(--text);
}
}
.xf-account__pager-link.is-active {
  background: var(--accent-dim);
  border-color: var(--accent-line);
  color: var(--accent-2);
}
.xf-account__pager-gap {
  min-width: 28px;
  height: 36px;
  display: inline-flex;
  align-items: flex-end;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text-3);
  padding-bottom: 4px;
  user-select: none;
}

/* ─── Order detail ────────────────────────────────────────── */
.xf-account__detail-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  margin-bottom: 22px;
  flex-wrap: wrap;
}
.xf-account__detail-date {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
  margin-top: 6px;
}
.xf-account__detail-chips {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.xf-account__detail-grid {
  display: grid;
  grid-template-columns: 1fr 320px;
  gap: 24px;
  align-items: start;
}
.xf-account__detail-items {
  display: flex;
  flex-direction: column;
}
.xf-account__item {
  display: grid;
  grid-template-columns: 80px 1fr auto;
  gap: 16px;
  align-items: center;
  padding: 16px 0;
  border-bottom: 1px solid var(--line-soft);
}
.xf-account__item:last-child { border-bottom: 0; }
.xf-account__item-img {
  width: 80px;
  height: 80px;
  border-radius: 10px;
  background: var(--surface-2);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-3);
}
.xf-account__item-img img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.xf-account__item-body { min-width: 0; }
.xf-account__item-name {
  display: block;
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  margin-bottom: 4px;
  transition: color .15s;
}
@media (hover: hover) {
  a.xf-account__item-name:hover { color: var(--accent-2); }
}
.xf-account__item-meta {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
}
.xf-account__item-x {
  color: var(--text-3);
  margin: 0 6px;
}
.xf-account__item-sum {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}
.xf-account__item-cur {
  font-size: 11px;
  color: var(--text-3);
  margin-left: 3px;
}

.xf-account__detail-side {
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.xf-account__detail-card {
  padding: 18px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
}
.xf-account__detail-card-title {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 10px;
}
.xf-account__detail-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding-bottom: 14px;
  margin-bottom: 14px;
  border-bottom: 1px solid var(--line-soft);
  font-size: 14px;
  color: var(--text-2);
}
.xf-account__detail-total-val {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.5px;
}
.xf-account__detail-total-cur {
  font-size: 12px;
  color: var(--text-3);
  margin-left: 3px;
}
.xf-account__detail-line {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  font-size: 13px;
}
.xf-account__detail-line-label {
  color: var(--text-3);
  flex-shrink: 0;
}
.xf-account__detail-line-val {
  color: var(--text);
  text-align: right;
  word-break: break-word;
}
.xf-account__detail-back { align-self: flex-start; }

/* ─── Profile form ────────────────────────────────────────── */
.xf-account__form-helper {
  margin: 0 0 16px;
  max-width: 560px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-2);
}
.xf-account__form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-width: 560px;
}
.xf-account__form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
}
.xf-account__form-actions {
  margin-top: 8px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* ─── Address book (#220) ─────────────────────────────────────
   Профили покупателя (b_sale_user_props) карточками. Карточка светлее body,
   но темнее секции (surface-2) — «paper on the bench» держим на product-card,
   тут вложенная плитка, как orders/detail-side. */
.xf-account__addr-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}
.xf-account__addr-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 18px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
}
.xf-account__addr-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.xf-account__addr-card-title {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  min-width: 0;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.2px;
  line-height: 1.3;
  color: var(--text);
}
/* Пин — нейтральный text-3, не индиго: акцент на каждой карточке тиражирует
   индиго и ломает One-Voice Indigo (impeccable #222). */
.xf-account__addr-card-title svg { flex: 0 0 auto; color: var(--text-3); margin-top: 2px; }
/* Длинное ФИО переносится до 2 строк, не режется ellipsis'ом в одну
   (impeccable #222: «Иванов Иван Иванович» обрезался уже на десктопе).
   Страховка для 3+ строк — title= на span в addresses.php. */
.xf-account__addr-card-title span {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.xf-account__addr-card-actions {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
}
.xf-account__addr-del { margin: 0; }
.xf-account__addr-act {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 8px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--text-2);
  cursor: pointer;
  transition: background .15s, color .15s;
}
.xf-account__addr-act svg { flex: 0 0 auto; }
@media (hover: hover) {
  .xf-account__addr-act:hover { background: var(--surface); color: var(--text); }
  /* Удаление — та же danger-тональность, что у кнопки «Выйти» в сайдбаре. */
  .xf-account__addr-act--del:hover {
    background: oklch(0.72 0.20 25 / 0.08);
    color: oklch(0.82 0.14 25);
  }
}
.xf-account__addr-fields {
  margin: 0;
  padding-top: 12px;
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.xf-account__addr-row {
  display: grid;
  grid-template-columns: 116px minmax(0, 1fr);
  gap: 12px;
  align-items: baseline;
}
.xf-account__addr-key {
  margin: 0;
  font-size: 12px;
  color: var(--text-3);
}
.xf-account__addr-val {
  margin: 0;
  font-size: 13px;
  color: var(--text);
  word-break: break-word;
}

/* ─── Mobile logout (phone-only duplicate, rendered below content) ─── */
.xf-account__logout-mobile { display: none; }

/* ─── Responsive (phone ≤ 767) ─────────────────────────────
   M24-m5 — unified phone layout для /dev/personal/*.
   Shell: sidebar hidden who/logout, nav → horizontal scroll-tabs
   (m3.2 pattern: pan-x + mask-fade + momentum-scroll).
   Cards/forms: 1-col sections, 2-row stacked cards (m4.1 parity).
   Product-grid: 2-col (listing parity).
   Breakpoint унифицирован на 767 с проекта. */
@media (max-width: 767px) {
  /* Section wrapper: sjimaem vertical padding, чтобы tabs-bar
     не висел далеко под крошками (catpage__head сверху уже даёт
     свой padding + margin у .xf-crumbs). */
  .xf-home__section--account { padding: 8px 0 24px; }

  /* ─── Shell: single column ──────────────────────────────── */
  .xf-account {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }

  /* ─── Sidebar → top scroll-tabs bar ──────────────────────── */
  .xf-account__side {
    position: static;
    padding: 0;
    gap: 0;
    background: transparent;
    border: 0;
    border-radius: 0;
  }
  .xf-account__who,
  .xf-account__logout { display: none; }

  /* Tabs-bar: fade-маски здесь НЕ статические — JS
     (initAccountTabs в app.js) вешает .is-fade-left / .is-fade-right
     по scroll-позиции, чтобы edge-fade не «съедал» крайние pill'ы
     когда скроллить в ту сторону уже некуда. */
  .xf-account__nav {
    flex-direction: row;
    flex-wrap: nowrap;
    width: 100%;
    gap: 6px;
    overflow-x: auto;
    overflow-y: hidden;
    touch-action: pan-x;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    -ms-overflow-style: none;
    padding: 2px 4px;
  }
  .xf-account__nav.is-fade-left.is-fade-right {
    mask-image: linear-gradient(90deg,
      transparent 0, #000 14px,
      #000 calc(100% - 14px), transparent 100%);
    -webkit-mask-image: linear-gradient(90deg,
      transparent 0, #000 14px,
      #000 calc(100% - 14px), transparent 100%);
  }
  .xf-account__nav.is-fade-left:not(.is-fade-right) {
    mask-image: linear-gradient(90deg, transparent 0, #000 14px);
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14px);
  }
  .xf-account__nav.is-fade-right:not(.is-fade-left) {
    mask-image: linear-gradient(90deg, #000 calc(100% - 14px), transparent 100%);
    -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 14px), transparent 100%);
  }
  .xf-account__nav::-webkit-scrollbar { display: none; }
  .xf-account__nav-link {
    flex: 0 0 auto;
    padding: 11px 14px;
    border-radius: 999px;
    border: 1px solid var(--line-soft);
    background: var(--surface);
    font-size: 13px;
    white-space: nowrap;
    min-height: 44px;
  }
  .xf-account__nav-link.is-active {
    background: var(--accent-dim);
    border-color: var(--accent-line);
  }

  /* ─── Mobile logout (rendered outside sidebar) ──────────── */
  .xf-account__logout-mobile {
    display: block;
    margin-top: 8px;
  }
  .xf-account__logout-mobile .xf-account__logout-btn {
    width: 100%;
    min-height: 44px;
    padding: 12px;
  }

  /* ─── Content gap ───────────────────────────────────────── */
  .xf-account__content { gap: 16px; }

  /* ─── Hero — compact ────────────────────────────────────── */
  .xf-account__hero { padding: 20px 18px; }
  .xf-account__hero-title {
    font-size: 24px;
    letter-spacing: -0.5px;
    margin: 8px 0 6px;
  }
  .xf-account__hero-sub { font-size: 13px; }

  /* ─── Action cards — 1 col stack ────────────────────────── */
  .xf-account__actions { grid-template-columns: 1fr; gap: 10px; }
  .xf-account__action { padding: 14px 16px; gap: 12px; }
  .xf-account__action-icon { width: 40px; height: 40px; border-radius: 10px; }

  /* ─── Section wrapper — tighter padding ─────────────────── */
  .xf-account__section { padding: 18px 16px; }
  .xf-account__section-head { margin-bottom: 14px; }
  .xf-account__section-title { font-size: 20px; }

  /* ─── Orders list (dashboard + orders) ──────────────────── */
  .xf-account__orders { grid-template-columns: 1fr; gap: 10px; }
  .xf-account__order {
    padding: 14px;
    border-radius: 14px;
  }
  /* Row-variant (orders.php): revert to column stack on phone.
     Head wraps naturally (num + status pills), meta = date ↔ price. */
  .xf-account__order--row {
    flex-direction: column;
    align-items: stretch;
    flex-wrap: nowrap;
    gap: 10px;
  }
  .xf-account__order--row .xf-account__order-head { row-gap: 6px; }

  /* ─── Order detail — 1 col ──────────────────────────────── */
  .xf-account__detail-grid { grid-template-columns: 1fr; gap: 18px; }
  .xf-account__detail-head { gap: 10px; margin-bottom: 16px; }
  .xf-account__detail-head .xf-account__section-title { font-size: 22px; }

  /* Item → 2-row 3-col stacked (m4.1 cart-item parity).
     [img 72px] [name (spans col 2/3)]      ← row 1
     [img]     [meta]              [sum]    ← row 2
     img занимает обе строки col 1, name spans row1 across col 2/3,
     meta sits под name, sum справа той же строки. */
  .xf-account__item {
    grid-template-columns: 72px 1fr auto;
    grid-template-rows: auto auto;
    align-items: center;
    row-gap: 6px;
    column-gap: 12px;
    padding: 14px 0;
  }
  .xf-account__item-img {
    width: 72px;
    height: 72px;
    grid-row: 1 / span 2;
    grid-column: 1;
    align-self: center;
  }
  .xf-account__item-body {
    grid-row: 1;
    grid-column: 2 / span 2;
    align-self: end;
  }
  .xf-account__item-name { font-size: 13px; margin-bottom: 0; }
  .xf-account__item-meta {
    grid-row: 2;
    grid-column: 2;
    font-size: 12px;
    align-self: baseline;
  }
  .xf-account__item-sum {
    grid-row: 2;
    grid-column: 3;
    justify-self: end;
    align-self: baseline;
    font-size: 14px;
  }

  .xf-account__detail-back {
    align-self: stretch;
    justify-content: center;
    width: 100%;
  }

  /* ─── Profile form — 1 col ──────────────────────────────── */
  .xf-account__form-row { grid-template-columns: 1fr; }
  .xf-account__form { max-width: 100%; }
  .xf-account__form-actions .xf-btn { width: 100%; }

  /* ─── Address book — 1 col, tap-friendly actions ──────────
     minmax(0,1fr), НЕ 1fr: у `1fr` (= minmax(auto,1fr)) пол — min-content
     трека; карточка не ужималась ниже собственного min-content (заголовок
     nowrap + действия) и вылезала за вьюпорт. Заголовок и действия на телефоне
     кладём в столбик — так действия не отжимают имя в узкую полоску. */
  .xf-account__addr-list { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .xf-account__addr-card-head {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  .xf-account__addr-card-actions { justify-content: flex-end; }
  .xf-account__addr-act { padding: 8px 10px; min-height: 44px; }
  .xf-account__addr-row { grid-template-columns: 96px minmax(0, 1fr); gap: 10px; }

  /* ─── Favorites / Compare grid — 2 col (listing parity) ─── */
  .xf-account__product-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 10px;
  }

  /* ─── Empty state — compact ─────────────────────────────── */
  .xf-account-empty { padding: 28px 12px; }
  .xf-account-empty__icon { width: 64px; height: 64px; }
  .xf-account-empty__title { font-size: 18px; }

  /* ─── Pager — tap-target 44×44 ────────────────────────── */
  .xf-account__pager-link { min-width: 44px; height: 44px; }
}

/* End */


/* Start:/local/templates/xcore_front/css/search.css?179264524418350*/
/* OWNS: .xf-search-*, .xf-search-page-*, .xf-sugg-*, .xf-pager-*, .xf-chip-* (search chips — note .xf-chip also in catsection.css) */
/* ───────────────────────────────────────────────────────────
   Header search — autocomplete dropdown
   ─────────────────────────────────────────────────────────── */

.xf-search { position: relative; }

.xf-search__dropdown {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  background: var(--bg-2);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  box-shadow: var(--shadow-card);
  max-height: 560px;
  overflow-y: auto;
  z-index: 60;
  padding: 6px;
}

.xf-sugg__group + .xf-sugg__group {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid var(--line-soft);
}

.xf-sugg__label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--text-3);
  padding: 8px 10px 6px;
}

.xf-sugg__item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 10px;
  border-radius: 10px;
  color: var(--text);
  text-decoration: none;
  transition: background .12s;
}
.xf-sugg__item.is-hover {
  background: var(--surface);
}
@media (hover: hover) {
  .xf-sugg__item:hover {
  background: var(--surface);
}
}

.xf-sugg__img {
  flex: 0 0 48px;
  width: 48px;
  height: 48px;
  border-radius: 8px;
  background: var(--surface-2);
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.xf-sugg__img img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.xf-sugg__img-fallback {
  width: 22px;
  height: 22px;
  border-radius: 4px;
  background: var(--surface-3);
}

.xf-sugg__body {
  flex: 1;
  min-width: 0;
}
.xf-sugg__name {
  font-size: 13.5px;
  line-height: 1.35;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.xf-sugg__meta {
  margin-top: 2px;
  font-size: 11.5px;
  color: var(--text-3);
  display: flex;
  align-items: center;
  gap: 2px;
}
.xf-sugg__brand {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-2);
}
.xf-sugg__art {
  font-family: var(--font-mono);
  color: var(--text-3);
}

.xf-sugg__price {
  flex: 0 0 auto;
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.2px;
  white-space: nowrap;
  padding-left: 8px;
}

.xf-sugg__item--section {
  gap: 10px;
  padding: 7px 10px;
  color: var(--text-2);
  font-size: 13px;
}
.xf-sugg__dot {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--accent-2);
  flex: 0 0 auto;
}

.xf-sugg__empty {
  padding: 20px 14px;
  text-align: center;
  color: var(--text-3);
  font-size: 13px;
}

.xf-sugg__all {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 4px;
  padding: 10px;
  border-top: 1px solid var(--line-soft);
  color: var(--accent-2);
  font-size: 13px;
  font-weight: 500;
  /* Прижат к низу скролл-области: с 10 подсказками (#114) список выше
     max-height дропдауна, без sticky ссылка «Все результаты (N)» уходила
     под скролл. -6px компенсирует padding контейнера; фон непрозрачный
     (--bg-2 без альфы), чтобы карточки не просвечивали. */
  position: sticky;
  bottom: -6px;
  background: var(--bg-2);
}
/* [#309] «Все результаты» — такая же опция списка, как подсказки, и стрелками
   до неё доходят. Без этого правила последний ↓ визуально терял выделение:
   подсветка была только у .xf-sugg__item. */
.xf-sugg__all.is-hover {
  color: var(--text);
  background: var(--surface);
}
@media (hover: hover) {
  .xf-sugg__all:hover {
  color: var(--text);
  background: var(--surface);
}
}

/* ───────────────────────────────────────────────────────────
   Search results page — /dev/search/
   ─────────────────────────────────────────────────────────── */

/* РЕЛЬС (#341): боковой рельс держит `.wrapper_inner` (layout.css) — он уже
   даёт max-width: var(--container) + margin auto + padding var(--container-pad).
   Раньше `.xf-search-page` повторял весь этот контракт, будучи ВНУТРИ него, и
   рельс складывался вдвое: 80px в полях вместо 40 на десктопе (H1 начинался с
   x=80 против 40 на каталоге), 32 вместо 16 на телефоне. Здесь остаются только
   ВЕРТИКАЛЬНЫЕ отступы; горизонтальные — не наши. Ограничения меры текста
   (`60ch`/`70ch` у empty-state и hint ниже) это не трогает — они про длину
   строки, а не про рельс. */
.xf-search-page {
  padding: 32px 0 80px;
}

.xf-search-page__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  margin-bottom: 20px;
}
.xf-search-page__title {
  font-size: 32px;
  font-weight: 600;
  letter-spacing: -0.8px;
  line-height: 1.1;
  margin: 0;
  color: var(--text);
}
.xf-search-page__meta {
  font-family: var(--font-mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--text-3);
}

.xf-search-page__form {
  display: flex;
  gap: 8px;
  margin-bottom: 28px;
  max-width: 640px;
}
.xf-search-page__form input {
  flex: 1;
  padding: 12px 16px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  font-size: 14px;
  color: var(--text);
  transition: border-color .15s;
  font-family: inherit;
}
/* [#373] Здесь БЫЛ второй экземпляр той же суппрессии, что в header.css:
   `outline: none` гасил глобальный :focus-visible на поле поиска — тут на
   странице /search/. Единственным индикатором фокуса оставалась рамка
   --accent-line (1.55:1 / 1.75:1 при норме 1.4.11 = 3:1). Снят; новое
   per-element правило взамен НЕ пишем — инвариант проекта «глобальный
   :focus-visible + .xf-skip», кольцо приходит из ui.css само. */
.xf-search-page__form input:focus { border-color: var(--accent-line); }
.xf-search-page__form button {
  padding: 12px 22px;
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 12px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  font-family: inherit;
  transition: transform .15s, box-shadow .15s;
}
/* Тёмная (#857): цветной тени у кнопок нет — блик по верхней кромке, как у
   .xf-btn (DESIGN.md, The Night-Sign Rule). 0-2-2 выше :hover ниже (0-2-1):
   на наведении тот же блик, а не тень. */
html[data-theme="dark"] .xf-search-page__form button { box-shadow: var(--nx-key); }
@media (hover: hover) {
  .xf-search-page__form button:hover {
  transform: translateY(calc(-1px * var(--xf-motion, 1)));
  box-shadow: 0 6px 18px -8px var(--accent);
}
}

.xf-search-page__hint,
.xf-search-page__empty {
  padding: 48px 16px;
  text-align: center;
  color: var(--text-3);
  font-size: 14px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
}

/* No-result state: message + section-hints (если есть) + CTA exit-routes.
   max-width на msg чтобы строка не уходила за 75ch — соблюдаем shared
   design law «Cap body line length at 65-75ch» из SKILL.md. */
.xf-search-page__empty-msg {
  margin: 0 auto 24px;
  max-width: 60ch;
  font-size: 15px;
  line-height: 1.5;
  color: var(--text-2);
}
.xf-search-page__hint {
  margin-left: auto;
  margin-right: auto;
  max-width: 70ch;
}
.xf-search-page__empty-actions {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
}
@media (max-width: 480px) {
  .xf-search-page__empty-actions { flex-direction: column; align-items: stretch; }
  .xf-search-page__empty-actions .xf-btn { width: 100%; }
}

/* Controls bar: sort-форма + chip «Только в наличии» на одной строке справа,
   sort-pill (активная не-relevance сортировка) встаёт рядом с select. */
.xf-search-page__controls {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 10px 16px;
  margin: 0 0 16px;
}
.xf-search-page__sort {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  flex-wrap: wrap;
}

/* Chip-фильтр. Ghost dans default, accent-tinted фон в is-active.
   Стрелка-точка слева как inline state-indicator. Это action-element
   (toggle filter), не state-marker — поэтому border + цветной фон при
   active, в отличие от inert .xf-pill. */
.xf-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-2);
  font-size: 13px;
  font-weight: 500;
  text-decoration: none;
  transition: background .15s, border-color .15s, color .15s;
  white-space: nowrap;
}
.xf-chip__dot {
  width: 7px;
  height: 7px;
  border-radius: 999px;
  background: var(--text-3);
  flex: 0 0 auto;
  transition: background .15s;
}
@media (hover: hover) {
  .xf-chip:hover {
    border-color: var(--accent-line);
    color: var(--text);
  }
}
.xf-chip.is-active {
  background: var(--accent-dim);
  border-color: var(--accent-line);
  color: var(--accent-2);
}
.xf-chip.is-active .xf-chip__dot {
  background: var(--success, oklch(0.52 0.17 155));
}

/* Sort-pill: removable label рядом с select, показывающий какой sort
   активен. Появляется только при не-relevance sort'е. ×-кликабельный
   крестик возвращает к relevance (page=null тоже сбрасывается). */
.xf-sort-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 8px 5px 12px;
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  background: var(--accent-dim);
  color: var(--accent-2);
  font-size: 12.5px;
  font-weight: 500;
  text-decoration: none;
  transition: background .15s, border-color .15s;
  white-space: nowrap;
}
/* Mono-as-Data rule (DESIGN.md): UI копи sort'а это narrative, не data
   (не SKU / не цена / не timestamp) → sans. Inherits .xf-sort-pill font. */
.xf-sort-pill__label { letter-spacing: 0; }
.xf-sort-pill__x {
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  font-size: 14px;
  line-height: 1;
}
@media (hover: hover) {
  .xf-sort-pill:hover {
    background: color-mix(in srgb, var(--accent) 14%, var(--surface));
    border-color: var(--accent);
  }
  .xf-sort-pill:hover .xf-sort-pill__x {
    background: color-mix(in srgb, var(--accent) 22%, transparent);
  }
}

.xf-search-page__sections {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 20px;
}
/* In-empty variant: section-hints показываем в empty-state посередине,
   с отступом перед exit-route'ами. Текст-блок empty центрирован, поэтому
   и пилюли тоже центрируем. */
.xf-search-page__sections--in-empty {
  justify-content: center;
  margin: 0 auto 24px;
  max-width: 600px;
}
.xf-search-page__sections-label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 1.4px;
  color: var(--text-3);
  margin-right: 4px;
}
.xf-search-page__sect {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  background: var(--accent-dim);
  border: 1px solid var(--accent-line);
  border-radius: 999px;
  color: var(--accent-2);
  font-size: 13px;
  line-height: 1;
  text-decoration: none;
  transition: background .15s, transform .15s;
}
@media (hover: hover) {
  .xf-search-page__sect:hover {
  background: var(--accent);
  color: #fff;
  transform: translateY(calc(-1px * var(--xf-motion, 1)));
}
}
.xf-search-page__sect-count {
  font-family: var(--font-mono);
  font-size: 11px;
  color: inherit;
  opacity: .7;
}
/* [#27 2026-06-11] Активный фасет-чип бренд-режима (?section=<id>): заливка
   accent + белый текст, как hover, но постоянная. Specificity = base+class,
   держится поверх и --brand модификатора declaration-order'ом. */
.xf-search-page__sect.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}

/* M2.1 / M2.2 Pass B 2026-05-19: ghost-variant chip для brand-row и popular-L1.
   Section-chip остаётся accent-dim (primary intent hint, strong signal).
   Brand / popular-L1 chip — neutral surface (alternate pivot, weaker signal).
   Закрывает P2-1 baseline gap «5 accent-surface families normalize accent»:
   section-chip накачан, brand-chip даёт восстановление breathing room.
   NB cascade: `.xf-search-page__sect--brand` имеет ту же specificity, что и
   базовый `.xf-search-page__sect` выше — override держится declaration-order'ом.
   Сохраняй порядок (base → modifier) при будущих refactor'ах. */
.xf-search-page__sect--brand {
  background: var(--surface);
  border-color: var(--line-soft);
  color: var(--text-2);
}
@media (hover: hover) {
  .xf-search-page__sect--brand:hover {
    background: var(--surface-2);
    border-color: var(--accent-line);
    color: var(--text);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
}
.xf-search-page__sect--brand .xf-search-page__sect-count {
  color: var(--text-3);
  opacity: 1;
}

/* Popular-L1 chip без count — sections-label «Популярные категории» +
   plain name. Шире padding для лучшей readability на empty-state. */
.xf-search-page__sections--popular .xf-search-page__sect--brand {
  padding: 8px 14px;
  font-size: 13px;
}

.xf-search-page__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}

.xf-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 32px;
  flex-wrap: wrap;
}
.xf-pager__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 36px;
  height: 36px;
  padding: 0 10px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  color: var(--text-2);
  font-size: 13px;
  font-family: var(--font-mono);
  transition: all .15s;
}
@media (hover: hover) {
  .xf-pager__link:hover {
  background: var(--surface-2);
  color: var(--text);
}
}
.xf-pager__link.is-active {
  background: var(--accent-dim);
  color: var(--accent-2);
  border-color: var(--accent-line);
}
/* Prev/Next arrows: same size grid as numbered links, чуть мягче border
   и font-weight для визуального отличия — это движение, не позиция. */
.xf-pager__arrow {
  color: var(--text-3);
  font-size: 16px;
  line-height: 1;
}
.xf-pager__ellipsis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 24px;
  height: 36px;
  color: var(--text-3);
  font-size: 14px;
  user-select: none;
}

@media (max-width: 767px) {
  .xf-pager__link { min-width: 44px; height: 44px; font-size: 13.5px; }
  .xf-pager__ellipsis { height: 44px; min-width: 18px; }
}

/* Mobile typeset — H1 поджимается, line-length cap'аются, controls
   укладываются в одну строку с переносом. Critique nudge: «H1 wraps
   awkwardly on mobile, pushes first card below the fold». */
@media (max-width: 480px) {
  /* Только вертикали — рельс у контейнера (#341). До #341 это правило несло
     var(--container-pad) и задвоение на ≤480 гасил трейлинг-override в конце
     файла; override убран вместе с дублем контракта, поэтому padding-inline
     здесь обязан остаться нулевым. */
  .xf-search-page { padding: 20px 0 60px; }
  .xf-search-page__head {
    align-items: flex-start;
    gap: 6px;
    margin-bottom: 16px;
  }
  .xf-search-page__title {
    font-size: 24px;
    letter-spacing: -0.4px;
    line-height: 1.15;
  }
  .xf-search-page__meta { font-size: 11px; }
  .xf-search-page__controls {
    justify-content: flex-start;
  }
  .xf-search-page__sort { width: 100%; }
  .xf-chip { width: 100%; justify-content: center; }
}

/* #307 снимал это задвоение телефонным override'ом `padding-inline: 0` в конце
   файла. С #341 гасить нечего — дублирующий контракт убран из самого
   `.xf-search-page` (см. «РЕЛЬС» выше), включая shorthand в блоке ≤480, ради
   которого override и стоял последним. Правило удалено. */

/* End */


/* Start:/local/templates/xcore_front/css/modal.css?179264123385188*/
/* OWNS: #modalProducts (Bootstrap 3 modal от webprostor.configurator), .xf-drawer, .xf-filter (mobile filter drawer), .xf-pc, .xfsf (smart-filter polish + drawer chrome — desktop sidebar + mobile drawer share class space) */
/* ============================================================
   M11 — #modalProducts: тёмный overlay + xcore-карточки товара
   Модалка инжектится в <body> скриптом webprostor.configurator,
   поэтому селекторы берут высокую специфику от #modalProducts.
   ============================================================ */

/* ---------- Shell + overlay ---------- */
body.modal-open {
  overflow: hidden;
}
#modalProducts.modal {
  background: oklch(0.08 0.02 285 / 0.72);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
#modalProducts .modal-dialog {
  max-width: 1280px;
  margin: 32px auto;
}
/* M3c: более ощутимая modal open/close. Bootstrap 3 .fade анимирует только
   opacity (0.15s linear); накладываем transform на .modal-dialog, чтобы close
   имел видимый momentum, а не opacity-snap. */
#modalProducts.fade .modal-dialog {
  transition:
    transform 0.22s cubic-bezier(0.16, 1, 0.3, 1),
    opacity   0.22s ease-out;
  transform: scale(0.96) translateY(-10px);
  opacity: 0;
}
#modalProducts.fade.in .modal-dialog,
#modalProducts.fade.show .modal-dialog {
  transform: scale(1) translateY(0);
  opacity: 1;
}
#modalProducts .modal-content {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 18px;
  box-shadow:
    0 40px 120px -20px oklch(0 0 0 / 0.6),
    0 0 0 1px oklch(1 0 0 / 0.02);
  overflow: hidden;
  color: var(--text);
}
#modalProducts .modal-header {
  background: transparent;
  border-bottom: 1px solid var(--line-soft);
  padding: 20px 28px;
  display: flex;
  align-items: center;
  gap: 16px;
}
#modalProducts .modal-title {
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.3px;
  color: var(--text);
  margin: 0;
  flex: 1;
}
#modalProducts .close {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  color: var(--text-2);
  opacity: 1;
  font-size: 24px;
  line-height: 1;
  text-shadow: none;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background .15s, color .15s, border-color .15s;
  padding: 0;
  margin: 0;
  float: none;
}
#modalProducts .close:focus {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--accent-line);
}
@media (hover: hover) {
  #modalProducts .close:hover {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--accent-line);
}
}
#modalProducts .close span {
  display: inline-block;
  line-height: 1;
}
#modalProducts .modal-body {
  background: transparent;
  color: var(--text);
  padding: 22px 28px 28px;
  max-height: calc(100vh - 140px);
  overflow-y: auto;
}

/* ---------- Scrollbar внутри модалки ---------- */
#modalProducts .modal-body::-webkit-scrollbar {
  width: 8px;
}
#modalProducts .modal-body::-webkit-scrollbar-track {
  background: transparent;
}
#modalProducts .modal-body::-webkit-scrollbar-thumb {
  background: var(--surface-3);
  border-radius: 4px;
}
@media (hover: hover) {
  #modalProducts .modal-body::-webkit-scrollbar-thumb:hover {
  background: var(--line);
}
}

/* ---------- Колонки модалки ---------- */
/* Левая колонка (фильтр) = sticky + independent scroll. Модалка-контейнер
   скроллит общую высоту, а левую колонку — своим внутренним scroller'ом,
   чтобы при длинном фильтре не нужно было возвращаться в самый верх, чтобы
   докрутить карточки справа. */
#modalProducts .modal-body > .row { margin: 0; }
#modalProducts .modal-body > .row > .col-md-3 {
  padding: 0 22px 0 0;
  position: sticky;
  top: 0;
  align-self: flex-start;
  max-height: calc(100vh - 180px);
  overflow-y: auto;
  overflow-x: hidden;
}
#modalProducts .modal-body > .row > .col-md-9 {
  padding-left: 22px;
  min-width: 0;
}
/* Делаем row flex чтобы align-self: flex-start работал на .col-md-3 */
#modalProducts .modal-body > .row {
  display: flex;
  flex-wrap: wrap;
}
/* Тонкий скроллбар левой колонки */
#modalProducts .modal-body > .row > .col-md-3::-webkit-scrollbar { width: 6px; }
#modalProducts .modal-body > .row > .col-md-3::-webkit-scrollbar-track { background: transparent; }
#modalProducts .modal-body > .row > .col-md-3::-webkit-scrollbar-thumb {
  background: var(--surface-3);
  border-radius: 3px;
}

/* ---------- Sort panel (#sortPanel.navbar) ---------- */
/* Исходный DOM: .navbar > .container-fluid > .row > .sort.col-md-9 + .view-type.col-md-3
   Внутри .sort: <p.navbar-text>Сортировать</p> + <ul.nav.navbar-nav#sort><li class="active|..">
   Переделываем через CSS в компактную горизонтальную pill-строку:
   [Сортировать:] [pill] [pill (active ↑)] [pill] [pill]
   Убираем sticky — модалка сама скроллится. */
#modalProducts #sortPanel.navbar.sticky-top,
#modalProducts #sortPanel.navbar {
  position: static !important;
  top: auto !important;
  z-index: auto !important;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  padding: 10px 14px;
  margin: 0 0 14px 0;
  min-height: auto;
  border-width: 1px;
}
#modalProducts #sortPanel .container-fluid { padding: 0; }
#modalProducts #sortPanel > .container-fluid > .row {
  margin: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 14px;
}
/* Хайдим переключатель вида (сетка/список/таблица) — в модалке не нужен. */
#modalProducts #sortPanel .view-type { display: none !important; }

/* Сорт-группа: label + pill-список в одну строку */
#modalProducts #sortPanel .sort {
  padding: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  flex: 1;
  min-width: 0;
  width: auto !important;   /* перебить col-xs-6/col-md-9 */
  max-width: 100%;
  margin: 0;
}
#modalProducts #sortPanel .sort .navbar-text {
  margin: 0;
  padding: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-3);
  line-height: 1;
}
/* Кастомизируем содержимое .navbar-text — иногда туда пишется слово с пробелом в конце */
/* ---------- Sort: SEGMENTED CONTROL (M25) ----------
   Один связный track из equal-width сегментов — НЕ аддитивные пиллы.
   Resolves Pill-Is-Not-Button: сортировка = single-choice + direction sub-action,
   поэтому читается как ОДИН контрол (общий shell + 1px divider'ы + единственный
   tonal-fill active), а не ряд независимых тегов. CSS-only поверх нативного
   <ul#sort> list-markup; ноль PHP/JS изменений — сохраняет #sort>li.active (M3c),
   ASC/DESC href-flip, AJAX-reload. Sign-off пользователя на «Segment»-примитив: M25. */
#modalProducts #sortPanel .sort #sort.nav {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;                   /* flex (не grid): игнорирует whitespace-текстовые ноды,
                                      которые в grid становятся phantom-item'ом и ломают раскладку */
  flex-wrap: nowrap;
  align-items: stretch;
  float: none;
  flex: 1 1 0;                     /* basis 0 + grow: реально заполняет .sort до max-width */
  min-width: 0;
  max-width: 460px;                /* desktop: компактный left-aligned track, не на всю колонку */
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  overflow: hidden;                /* клип углов сегментов к shell'у */
  isolation: isolate;
}
#modalProducts #sortPanel .sort #sort.nav > li {
  margin: 0;
  padding: 0;
  float: none;
  list-style: none;
  display: flex;
  flex: 1 1 0;                     /* equal-width сегменты */
  min-width: 0;
  box-sizing: border-box;
}
/* Hairline divider между сегментами (≤1px нейтральный structural — не запрещённый цветной stripe) */
#modalProducts #sortPanel .sort #sort.nav > li + li {
  border-left: 1px solid var(--line-soft);
}
#modalProducts #sortPanel .sort #sort.nav > li > a {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  flex: 1;
  min-width: 0;
  padding: 8px 10px;               /* min-height ~36px touch-comfort */
  border: 0;
  border-radius: 0;
  font-size: 12.5px;
  line-height: 1.1;
  color: var(--text-2);
  background: transparent;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background .15s, color .15s;
  cursor: pointer;
}
@media (hover: hover) {
  #modalProducts #sortPanel .sort #sort.nav > li > a:hover {
    background: var(--surface-3);
    color: var(--text);
    text-decoration: none;
  }
}
/* Active segment = единственный tonal-fill + weight.
   State-not-by-color-alone держится тремя каналами: fill + weight 600 + chevron. */
#modalProducts #sortPanel .sort #sort.nav > li.active > a {
  background: var(--accent-dim);
  color: var(--accent-2);
  font-weight: 600;
}
@media (hover: hover) {
  #modalProducts #sortPanel .sort #sort.nav > li.active > a:hover {
    background: var(--accent-dim);
    color: var(--accent-2);
    filter: none;
  }
}
/* Focus-ring SAFE внутри overflow:hidden shell — inset, чтобы крайние сегменты не клипались */
#modalProducts #sortPanel .sort #sort.nav > li > a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
  border-radius: 6px;
}
/* Текст-метка всегда видна (M25: это лейбл сегмента, не прячем на mobile) */
#modalProducts #sortPanel .sort #sort.nav > li > a > span {
  display: inline !important;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Field-type иконка (fa-font/star/cube/rouble) скрыта — сегмент только текст */
#modalProducts #sortPanel .sort #sort.nav > li > a > i.fa:not(.fa-level-up):not(.fa-level-down) {
  display: none;
}
/* Стрелка направления ↑/↓ на активном сегменте (реальный <i> после клика по сортировке) */
#modalProducts #sortPanel .sort #sort.nav > li > a > i.fa-level-up::before {
  content: "↑";
  font-family: inherit;
}
#modalProducts #sortPanel .sort #sort.nav > li > a > i.fa-level-down::before {
  content: "↓";
  font-family: inherit;
}
#modalProducts #sortPanel .sort #sort.nav > li > a > i.fa-level-up,
#modalProducts #sortPanel .sort #sort.nav > li > a > i.fa-level-down {
  font-size: 12px;
  font-style: normal;
  line-height: 1;
  color: var(--accent-2);
  margin-left: 1px;
  position: relative;              /* якорь для встречной стрелки-подсказки (M25b ниже) */
}
/* M25: default-state direction cue. На первом open active рендерится БЕЗ <i>
   (server/JS race — тикет отдельно), поэтому показываем ↑ на active сегменте
   ТОЛЬКО когда реальной fa-level стрелки нет. :has guard'ит от дублирования
   как только реальная стрелка появляется; без поддержки :has селектор
   отбрасывается → no regression (как сегодня — просто без default-стрелки). */
#modalProducts #sortPanel .sort #sort.nav > li.active > a:not(:has(> i.fa-level-up)):not(:has(> i.fa-level-down))::after {
  content: "↑";
  font-family: inherit;
  font-size: 12px;
  line-height: 1;
  color: var(--accent-2);
  margin-left: 1px;
}
/* M25b (#23, codex round): affordance обратимости. Клик по активному сегменту
   флипает ASC↔DESC (сервер отдаёт flipped href). Встречная приглушённая стрелка
   видна ПОСТОЯННО (rest .35): на touch hover'а нет — hover-only cue главная
   persona с телефона не открыла бы никогда. Под hover-средой при наведении
   усиливается до .55. Rest НАМЕРЕННО показывает дым-стрелку (↑ + дым-↓) — это
   осознанный отход от «rest как раньше»: честная информация об обратимости,
   не шум. Встречная сидит absolute на <i> (рендерится в пустоте trailing-&nbsp;
   + right-padding сегмента) — ноль layout-shift в любом состоянии.
   Семантика продублирована в title/aria-label (ajax.php, active-ветка).
   ::after здесь — на <i>, НЕ на <a> (там живёт :has-гард выше) — не конфликтуют.
   Своих :focus-visible-правил в M25b нет (инвариант CLAUDE.md) — клавиатурному
   пользователю постоянный cue виден и так; сегментный inset-ринг выше — это
   пре-существующий M25, сознательное исключение из инварианта (глобальный
   ринг клипается overflow:hidden трека). */
#modalProducts #sortPanel .sort #sort.nav > li.active > a > i.fa-level-up::after { content: "↓"; }
#modalProducts #sortPanel .sort #sort.nav > li.active > a > i.fa-level-down::after { content: "↑"; }
#modalProducts #sortPanel .sort #sort.nav > li.active > a > i.fa-level-up::after,
#modalProducts #sortPanel .sort #sort.nav > li.active > a > i.fa-level-down::after {
  font-family: inherit;
  position: absolute;
  left: 100%;
  top: 0;
  padding-left: 2px;
  opacity: .35;
  transition: opacity .15s;
}
@media (hover: hover) {
  #modalProducts #sortPanel .sort #sort.nav > li.active > a:hover > i.fa-level-up::after,
  #modalProducts #sortPanel .sort #sort.nav > li.active > a:hover > i.fa-level-down::after {
    opacity: .55;
  }
}
@media (prefers-reduced-motion: reduce) {
  #modalProducts #sortPanel .sort #sort.nav > li.active > a > i.fa-level-up::after,
  #modalProducts #sortPanel .sort #sort.nav > li.active > a > i.fa-level-down::after {
    transition: none;
  }
}
/* M25 phone (≤640): 4 equal сегмента в одну строку не влезают по русским лейблам
   (≈73px/cell обрезает «по названию»), поэтому ровная 2×2 grid — выровнено по обеим
   осям, читаемо, дороже по высоте на один ряд. Divider'ы перестраиваем под 2 колонки. */
@media (max-width: 640px) {
  #modalProducts #sortPanel .sort #sort.nav {
    flex-wrap: wrap;                            /* 2×2: перенос на второй ряд */
    max-width: none;
    width: 100%;
  }
  #modalProducts #sortPanel .sort #sort.nav > li {
    flex: 1 1 50%;                              /* 2 сегмента в ряд (border-box: бордюр внутри 50%) */
  }
  #modalProducts #sortPanel .sort #sort.nav > li + li { border-left: 0; }
  #modalProducts #sortPanel .sort #sort.nav > li:nth-child(2n) {
    border-left: 1px solid var(--line-soft);   /* вертикальный divider между колонками */
  }
  #modalProducts #sortPanel .sort #sort.nav > li:nth-child(n+3) {
    border-top: 1px solid var(--line-soft);    /* горизонтальный divider между рядами */
  }
}

/* ---------- Поиск по названию ---------- */
/* BS3 даёт .form-group > .input-group > (.input-group-addon + .form-control).
   Визуально это «две коробки в ряд» — прячем addon в input через absolute,
   чтобы поле выглядело как один control. */
#modalProducts .form-group,
#modalProducts .input-group,
#modalProducts .input-group-addon {
  background: transparent;
}
/* Поиск стоит ВНЕ .webprostor-configurator-catalog-section. Gap задаём
   ТРЕМЯ путями с !important, чтобы никакой внешний CSS (aspro/webprostor
   core, или старый cached modal.css) не смог перетереть: */
#modalProducts .form-group {
  margin-bottom: 20px !important;
  margin-top: 0 !important;
}
/* Ещё 12px сверху у самой секции — итого ~32px между полем поиска и первой
   карточкой. */
#modalProducts .webprostor-configurator-catalog-section {
  margin-top: 12px !important;
  padding-top: 0 !important;
}
/* Нижняя страховка — отступ прямо перед сеткой. */
#modalProducts .xf-pc-grid {
  margin-top: 0 !important;
}
/* BS3 .input-group = display:table с table-cell'ами — плохо скинится в grid/flex.
   Прячем магнифайер-addon (визуально лишний; placeholder «Поиск по названию»
   сам объясняет назначение). Инпут оставляем full-width. */
#modalProducts .form-group > .input-group {
  display: block;
  width: 100%;
}
#modalProducts .form-group > .input-group > .input-group-addon {
  display: none !important;
}
/* float:none обязательно — BS3 на `.input-group .form-control` вешает
   `float: left` (нужно для table-layout внутри input-group). При переводе
   IG на display:block float выбрасывает инпут из потока, IG и form-group
   схлопываются в 0 высоты, grid визуально липнет к инпуту без зазора. */
#modalProducts #quick_search,
#modalProducts input[type="search"],
#modalProducts .form-group input[type="text"] {
  display: block;
  float: none;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  color: var(--text);
  padding: 10px 14px;
  font-size: 13.5px;
  width: 100%;
  transition: border-color .15s, background .15s;
}
/* [#406] Свечение 15% альфы здесь и в правилах ниже (.form-control, .xfsf__input)
   — вторичный признак, не индикатор; индикатор это глобальное кольцо ui.css. */
#modalProducts #quick_search:focus,
#modalProducts input[type="search"]:focus,
#modalProducts .form-group input[type="text"]:focus {
  background: var(--surface);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px oklch(0.68 0.22 var(--accent-h) / 0.15);
}
#modalProducts input::placeholder { color: var(--text-3); }

/* ---------- Product grid ---------- */
/* width:100% форсим — в Chrome display:grid внутри .col-md-9 без явной ширины
   резолвится в 0 (track'и схлопываются в "0px 0px 0px"). */
#modalProducts .xf-pc-grid {
  display: grid;
  width: 100%;
  gap: 14px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
#modalProducts .xf-pc-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
#modalProducts .xf-pc-grid--1 { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 991px) {
  #modalProducts .xf-pc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  #modalProducts .xf-pc-grid { grid-template-columns: 1fr; }
}

/* Сбрасываем BS-раскладку из исходного template — мы не используем .row/.col-xs-6,
   но на случай внешней вставки обнуляем negative margins. */
#modalProducts .webprostor-configurator-catalog-section > .row {
  margin: 0;
}

/* ---------- Product card ---------- */
#modalProducts .xf-pc-card {
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  color: var(--text);
  transition: border-color .15s, transform .15s, box-shadow .15s;
  min-width: 0;
}
@media (hover: hover) {
  #modalProducts .xf-pc-card:hover {
  border-color: var(--accent);
  box-shadow:
    0 0 0 1px var(--accent),
    0 0 24px -10px oklch(0.68 0.22 var(--accent-h) / 0.20),
    0 12px 28px -16px oklch(0 0 0 / 0.35);
}
}
#modalProducts .xf-pc-card--selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), 0 0 32px -12px oklch(0.68 0.22 var(--accent-h) / 0.35);
}
#modalProducts .xf-pc-card--muted {
  opacity: 0.55;
}

/* Thumbnail */
#modalProducts .xf-pc-card__thumb {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 10;
  background: var(--surface);
  border-bottom: 1px solid var(--line-soft);
  text-decoration: none;
  color: var(--text-3);
}
#modalProducts a.xf-pc-card__thumb { cursor: pointer; }
#modalProducts .xf-pc-card__thumb-img {
  max-width: 80%;
  max-height: 80%;
  object-fit: contain;
}
#modalProducts .xf-pc-card__thumb-ph {
  color: var(--text-3);
  opacity: 0.5;
}
#modalProducts .xf-pc-card__thumb-ph svg { display: block; }
#modalProducts .xf-pc-card__quick {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: transparent;
}

/* Body */
#modalProducts .xf-pc-card__body {
  padding: 14px 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
  min-width: 0;
}
#modalProducts .xf-pc-card__title-link {
  color: var(--text);
  text-decoration: none;
}
@media (hover: hover) {
  #modalProducts .xf-pc-card__title-link:hover .xf-pc-card__title {
  color: var(--accent-2);
}
}
#modalProducts .xf-pc-card__title {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.1px;
  color: var(--text);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
#modalProducts .xf-pc-card__text {
  font-size: 12.5px;
  color: var(--text-2);
  line-height: 1.45;
}
#modalProducts .xf-pc-card__specs {
  margin: 0;
  padding: 10px 0 0 0;
  border-top: 1px dashed var(--line-soft);
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 12px;
  color: var(--text-2);
}
#modalProducts .xf-pc-card__spec {
  display: flex;
  gap: 6px;
  align-items: baseline;
  min-width: 0;
}
#modalProducts .xf-pc-card__spec-key {
  margin: 0;
  color: var(--text-3);
  flex: 0 0 auto;
}
#modalProducts .xf-pc-card__spec-key::after { content: ":"; }
#modalProducts .xf-pc-card__spec-val {
  margin: 0;
  color: var(--text-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 1;
}

/* Foot: status, price, cta */
#modalProducts .xf-pc-card__foot {
  padding: 12px 16px 14px;
  border-top: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  gap: 10px;
  background: var(--surface);
}

/* Availability badge (reuse BS badge class for JS compat, override look) */
#modalProducts .xf-pc-card__status.badge {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 10px 3px 8px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: 0.1px;
  background: transparent;
  color: var(--text-3);
  border: 1px solid var(--line-soft);
  text-transform: none;
}
#modalProducts .xf-pc-card__status-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}
/* «в наличии» — яркий zелёный как на проде: активный цвет на фоне легким glow на точке. */
#modalProducts .xf-pc-card__status--ok {
  color: oklch(0.82 0.2 152);
  border-color: oklch(0.55 0.2 152 / 0.55);
  background: oklch(0.36 0.14 152 / 0.22);
}
#modalProducts .xf-pc-card__status--ok .xf-pc-card__status-dot {
  background: oklch(0.82 0.2 152);
  box-shadow: 0 0 0 3px oklch(0.82 0.2 152 / 0.2);
}
#modalProducts .xf-pc-card__status--off {
  color: oklch(0.8 0.15 75);
  border-color: oklch(0.5 0.12 75 / 0.5);
  background: oklch(0.32 0.08 75 / 0.18);
}
#modalProducts .xf-pc-card__status--off .xf-pc-card__status-dot {
  background: oklch(0.8 0.15 75);
}

/* Remove FA icons from availability (we use the dot) */
#modalProducts .xf-pc-card__status .fa { display: none; }

/* Price */
#modalProducts .xf-pc-card__price.price {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  font-family: var(--font-mono);
  color: var(--text);
}
#modalProducts .xf-pc-card__price-value.catalog-price {
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.2px;
}
#modalProducts .xf-pc-card__price-old {
  font-size: 12.5px;
  color: var(--text-3);
  text-decoration: line-through;
}

/* CTA — issue #76: ghost/outline вместо solid-fill (One-Voice Indigo: акцент ≤10% поверхности).
   КОРЕНЬ ФЛИКА: модуль ставит класс btn-<BOOTSTRAP_THEME> (напр. btn-success).
   BS3 для .btn-<theme>:active даёт `background-image: none` + свой background-color +
   inset box-shadow → override нужен во ВСЕХ hover/focus/active состояниях + !important,
   чтобы BS3 inset не пробивался.
   ВНИМАНИЕ к порядку: .btn-info (и .btn) тут матчат и кнопки --selected/--remove ниже.
   Их блоки (≈742/766) перебивают этот ghost ТОЛЬКО потому, что идут ПОЗЖЕ в файле
   (равная специфичность 1,2,0 + !important → решает source-order). НЕ переставлять блоки
   выше --selected/--remove — иначе ghost тихо съест их фон/рамку (Playwright не ловит).
   [#406] !important в тройке ниже нужен ПРОТИВ BS3-инсетов на background/
   box-shadow — на `outline` он не распространяется: кольцо фокуса глобальное
   (ui.css), гасить его здесь нечем и не надо. */
#modalProducts .xf-pc-card__cta.btn,
#modalProducts .xf-pc-card__cta.btn-success,
#modalProducts .xf-pc-card__cta.btn-primary,
#modalProducts .xf-pc-card__cta.btn-info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 16px;
  font-size: 13px;
  font-weight: 500;
  border-radius: 10px;
  text-decoration: none;
  line-height: 1.2;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color .18s ease, border-color .18s ease, box-shadow .18s ease, opacity .15s;
  background-color: transparent !important;
  background-image: none !important;
  color: var(--accent-2) !important;
  border: 1px solid var(--accent) !important;
  width: 100%;
  position: relative;
  box-shadow: none !important;
  text-shadow: none;
}
#modalProducts .xf-pc-card__cta.btn:focus,
#modalProducts .xf-pc-card__cta.btn:focus-visible,
#modalProducts .xf-pc-card__cta.btn:active,
#modalProducts .xf-pc-card__cta.btn.active,
#modalProducts .xf-pc-card__cta.btn:active:focus,
#modalProducts .xf-pc-card__cta.btn.focus,
#modalProducts .xf-pc-card__cta.btn-success:focus,
#modalProducts .xf-pc-card__cta.btn-success:active,
#modalProducts .xf-pc-card__cta.btn-success.active,
#modalProducts .xf-pc-card__cta.btn-success:active:focus,
#modalProducts .xf-pc-card__cta.btn-primary:focus,
#modalProducts .xf-pc-card__cta.btn-primary:active,
#modalProducts .xf-pc-card__cta.btn-primary.active,
#modalProducts .xf-pc-card__cta.btn-primary:active:focus {
  color: var(--accent-2) !important;
  background-color: var(--accent-dim) !important;
  background-image: none !important;
  filter: none;
  text-decoration: none;
  border: 1px solid var(--accent) !important;
  box-shadow: none !important;
}
@media (hover: hover) {
  #modalProducts .xf-pc-card__cta.btn:hover,
#modalProducts .xf-pc-card__cta.btn-success:hover,
#modalProducts .xf-pc-card__cta.btn-primary:hover {
  color: var(--accent-2) !important;
  background-color: var(--accent-dim) !important;
  background-image: none !important;
  filter: none;
  text-decoration: none;
  border: 1px solid var(--accent) !important;
  box-shadow: none !important;
}
}
/* Rhythm gap #76: 12px between stepper row and CTA.
   .xf-pc-card__form is a plain block form (not flex/grid), so gap is inert → margin-top. */
#modalProducts .xf-pc-card__form .xf-pc-card__cta {
  margin-top: 12px;
}
#modalProducts .xf-pc-card__cta.btn-block {
  display: inline-flex;
  width: 100%;
}
#modalProducts .xf-pc-card__cta-icon {
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 15px;
  line-height: 1;
}
#modalProducts .xf-pc-card__cta-label {
  font-family: inherit;
}

/* Selected state: мягкий accent-dim,
не градиент.
   Нужен !important на background-image — иначе унаследует grad от базового правила. */
#modalProducts .xf-pc-card__cta--selected.btn,
#modalProducts .xf-pc-card__cta--selected.btn:focus,
#modalProducts .xf-pc-card__cta--selected.btn:active {
  background-image: none !important;
  background-color: var(--accent-dim) !important;
  color: var(--accent-2) !important;
  border: 1px solid var(--accent-line) !important;
  cursor: default;
  pointer-events: none;
  filter: none !important;
  box-shadow: none !important;
}
@media (hover: hover) {
  #modalProducts .xf-pc-card__cta--selected.btn:hover {
  background-image: none !important;
  background-color: var(--accent-dim) !important;
  color: var(--accent-2) !important;
  border: 1px solid var(--accent-line) !important;
  cursor: default;
  pointer-events: none;
  filter: none !important;
  box-shadow: none !important;
}
}
#modalProducts .xf-pc-card__cta--remove.btn,
#modalProducts .xf-pc-card__cta--remove.btn:focus,
#modalProducts .xf-pc-card__cta--remove.btn:active,
#modalProducts .xf-pc-card__cta--remove.btn.active {
  background-image: none !important;
  background-color: oklch(0.32 0.1 25 / 0.25) !important;
  border: 1px solid oklch(0.45 0.14 25 / 0.4) !important;
  color: oklch(0.82 0.14 25) !important;
  font-size: 12px;
  padding: 8px 14px;
  filter: none !important;
  box-shadow: none !important;
}
@media (hover: hover) {
  #modalProducts .xf-pc-card__cta--remove.btn:hover {
  background-image: none !important;
  background-color: oklch(0.32 0.1 25 / 0.25) !important;
  border: 1px solid oklch(0.45 0.14 25 / 0.4) !important;
  color: oklch(0.82 0.14 25) !important;
  font-size: 12px;
  padding: 8px 14px;
  filter: none !important;
  box-shadow: none !important;
}
}
@media (hover: hover) {
  #modalProducts .xf-pc-card__cta--remove.btn:hover {
  background-color: oklch(0.38 0.12 25 / 0.35) !important;
  color: oklch(0.88 0.12 25) !important;
}
}

/* Quantity controls */
#modalProducts .xf-pc-card__qty {
  display: grid;
  grid-template-columns: 32px 1fr 32px;
  gap: 2px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  overflow: hidden;
}
#modalProducts .xf-pc-card__qty-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-2);
  text-decoration: none;
  font-size: 14px;
  background: transparent;
  transition: background .12s;
  user-select: none;
}
@media (hover: hover) {
  #modalProducts .xf-pc-card__qty-btn:hover {
  background: var(--surface-3);
  color: var(--text);
  text-decoration: none;
}
}
/* [#406] У поля нет ни рамки, ни фона — кольцо фокуса единственный признак,
   что курсор здесь. `outline` не гасить. */
#modalProducts .xf-pc-card__qty-input {
  background: transparent;
  border: 0;
  color: var(--text);
  text-align: center;
  font-family: var(--font-mono);
  font-size: 13px;
  min-width: 0;
  padding: 6px 0;
}
#modalProducts .xf-pc-card__qty-total {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-3);
  text-align: right;
}
#modalProducts .xf-pc-card__qty-total span { display: inline !important; }

/* Compare checkbox */
#modalProducts .xf-pc-card__compare {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-3);
  cursor: pointer;
  margin: 0;
  font-weight: 400;
}
#modalProducts .xf-pc-card__compare input[type="checkbox"] {
  accent-color: var(--accent);
  cursor: pointer;
}

/* Empty state */
#modalProducts .xf-pc-empty {
  padding: 40px 24px;
  text-align: center;
  color: var(--text-3);
  background: var(--surface-2);
  border: 1px dashed var(--line-soft);
  border-radius: 14px;
}
/* M3b: branched empty-state — message + recovery CTA */
#modalProducts .xf-pc-empty__msg {
  margin: 0 0 16px;
  color: var(--text-2);
  font-size: 14px;
  line-height: 1.5;
}
#modalProducts .xf-pc-empty__cta {
  display: inline-flex;
  align-items: center;
  padding: 8px 16px;
  border-radius: 10px;
  font-size: 13px;
  font-weight: 500;
  background: transparent;
  border: 1px solid var(--accent);
  color: var(--accent);
  cursor: pointer;
  transition: background .15s, color .15s;
}
@media (hover: hover) {
  #modalProducts .xf-pc-empty__cta:hover {
    background: var(--accent);
    color: #fff;
  }
}

/* Pager: wraps Bitrix NAV_STRING (генерит .nav-current-page / .modern-page-current и др.) */
#modalProducts .xf-pc-pager {
  margin: 0;
  color: var(--text-3);
  font-size: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 4px;
  padding: 8px 0;
}
#modalProducts .xf-pc-pager--bottom {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--line-soft);
}
#modalProducts .xf-pc-pager .navigation-title,
#modalProducts .xf-pc-pager .modern-page-navigation-title {
  display: none;
}
/* Pager.
   DOM webprostor.configurator-шаблона: .bx-pagination > .bx-pagination-container
   > ul > li > (a | span). И <a>, и <span> — интерактивная капсула; вложенный
   span внутри <a> — ТОЛЬКО текст (иначе получаем «пилюлю в пилюле»).
   Нормализуем через li.list-style:none + отменяем ul-padding BS3. */
#modalProducts .webprostor-configurator-pagination .bx-pagination,
#modalProducts .webprostor-configurator-pagination .bx-pagination-container {
  margin: 0;
  padding: 0;
}
#modalProducts .webprostor-configurator-pagination ul,
#modalProducts .xf-pc-pager ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: center;
}
#modalProducts .webprostor-configurator-pagination li,
#modalProducts .xf-pc-pager li {
  list-style: none;
  margin: 0;
  padding: 0;
  display: inline-flex;
}
/* Капсула = прямой ребёнок li (а если li одиноко — li само). */
#modalProducts .webprostor-configurator-pagination li > a,
#modalProducts .webprostor-configurator-pagination li > span,
#modalProducts .xf-pc-pager li > a,
#modalProducts .xf-pc-pager li > span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  height: 34px;
  padding: 0 10px;
  border-radius: 8px;
  color: var(--text-2);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  text-decoration: none;
  font-family: var(--font-mono);
  font-size: 12.5px;
  margin: 0;
  transition: background .15s, color .15s, border-color .15s;
}
/* Вложенный span ВНУТРИ <a> — просто текст, не капсула. */
#modalProducts .webprostor-configurator-pagination li > a > span,
#modalProducts .webprostor-configurator-pagination li > a > b,
#modalProducts .xf-pc-pager li > a > span,
#modalProducts .xf-pc-pager li > a > b {
  display: contents;
  background: transparent;
  border: 0;
  padding: 0;
  min-width: 0;
  height: auto;
  font: inherit;
  color: inherit;
}
@media (hover: hover) {
  #modalProducts .webprostor-configurator-pagination li > a:hover,
#modalProducts .xf-pc-pager li > a:hover {
  background: var(--surface-3);
  color: var(--text);
  border-color: var(--accent-line);
  text-decoration: none;
}
}
#modalProducts .webprostor-configurator-pagination li.bx-active > span,
#modalProducts .webprostor-configurator-pagination li.bx-active > a,
#modalProducts .xf-pc-pager li.bx-active > span,
#modalProducts .xf-pc-pager li.bx-active > a,
#modalProducts .webprostor-configurator-pagination .current,
#modalProducts .xf-pc-pager .current,
#modalProducts .xf-pc-pager .modern-page-current {
  background: var(--accent-dim);
  color: var(--accent-2);
  border-color: var(--accent-line);
  font-weight: 600;
}

/* ---------- Ambient поправки для всей подсистемы форм в модалке ---------- */
#modalProducts .form-control {
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  color: var(--text);
  border-radius: 10px;
}
#modalProducts .form-control:focus {
  background: var(--surface);
  border-color: var(--accent);
  box-shadow: 0 0 0 3px oklch(0.68 0.22 var(--accent-h) / 0.15);
}
#modalProducts label {
  color: var(--text-2);
  font-weight: 500;
}
#modalProducts a {
  color: var(--accent-2);
}

/* ---------- Левая колонка фильтра (catalog.smart.filter, template webprostor.configurator)
   Template override: local/templates/xcore_front/components/bitrix/catalog.smart.filter/webprostor.configurator/
   Базовый CSS модуля (#JCSmartFilterConfigurator.bx-filter .bx-filter-block {display:none;} и т.д.)
   остаётся включённым — он обеспечивает accordion (show/hide через .bx-active на родителе).
   Наши стили таргетят уникальные xfsf__* классы, чтобы не конфликтовать. ---------- */

#modalProducts #productFilterRight { color: var(--text); }
#modalProducts .xfsf,
#modalProducts .xfsf__section {
  background: transparent;
  color: var(--text);
}
#modalProducts .xfsf__form { margin: 0; }
#modalProducts .xfsf__list { margin: 0; padding: 0; }

/* Карточка одного параметра. Без overflow:hidden — smart.filter JS иногда
   оставляет .bx-filter-block с фиксированной height, которая обрезает опции. */
#modalProducts .xfsf__box {
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  margin-bottom: 10px;
  padding: 0;
  float: none !important;
  width: 100% !important;
}
/* Перебить col-lg-12 margin/padding из оригинального шаблона */
#modalProducts .xfsf__box.list-group-item { margin-bottom: 10px; border: 1px solid var(--line-soft); }

/* M3a: в dark поднимаем filter-block ещё на одну tonal-ступень и усиливаем border,
   иначе он сливается с modal-bg (--surface 0.195 vs --surface-2 0.235 — слабый дельта).
   [#708] Числа — прежней тёмной (в «Ночной вывеске» 0.172 / 0.200). Правило
   спит вместе со всем блоком #modalProducts: под xcore_front модалку не
   создаёт никто (её рисует script.js вытесненного configurator.onepage — см.
   header.php про configurator.css), скин её не перекрывал. Оставлено, как и
   configurator.css, на случай возврата старого конфигуратора. */
html[data-theme="dark"] #modalProducts .xfsf__box,
html[data-theme="dark"] #modalProducts .xfsf__box.list-group-item {
  background: var(--surface-3);
  border-color: var(--line);
}

#modalProducts .xfsf__title {
  padding: 12px 14px;
  color: var(--text);
  font-weight: 600;
  font-size: 13px;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
  user-select: none;
  line-height: 1.3;
}
@media (hover: hover) {
  #modalProducts .xfsf__title:hover { color: var(--text); }
}
#modalProducts .xfsf__title-name {
  flex: 1;
  min-width: 0;
}
#modalProducts .xfsf__chev {
  flex: none;
  color: var(--text-3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  transition: transform .2s;
  /* default = ▼ (SVG native direction); flip to ▲ on .bx-active via unscoped rule below.
     M24a: убран rotate(-90deg) — нужен один pattern для desktop sidebar и mobile drawer (portal'd to body — `#modalProducts` scope не matchит). */
  font-family: inherit !important;   /* блокируем унаследованный FontAwesome (если .fa-angle-up подклеен) */
}
#modalProducts .xfsf__chev::before,
#modalProducts .xfsf__chev::after {
  display: none !important;
  content: none !important;
}
/* M24a: unscoped — applies к desktop sidebar (под #modalProducts) И mobile drawer (portal'd to body).
   bx-active ставит smartFilter.hideFilterProps когда box раскрыт. */
.xfsf__box:not(.bx-active) .xfsf__body { display: none; }

#modalProducts .xfsf__body {
  padding: 0 14px 12px;
}
#modalProducts .xfsf__content {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
}

/* Range: два инпута + dash + слайдер */
#modalProducts .xfsf__range {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  grid-auto-rows: auto;
  align-items: center;
  gap: 8px;
  margin: 0;
  float: none;
}
#modalProducts .xfsf__range-cell {
  padding: 0 !important;
  margin: 0;
  width: auto !important;
  max-width: none !important;
  float: none !important;
}
#modalProducts .xfsf__range-dash {
  color: var(--text-3);
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 1;
  user-select: none;
}
#modalProducts .xfsf__input {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  color: var(--text);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 12.5px;
  font-family: var(--font-mono);
  width: 100%;
  box-sizing: border-box;
  height: auto;
  line-height: 1.3;
  transition: border-color .15s, background .15s;
}
/* ВНИМАНИЕ: `.xfsf__input` объявлен в этом файле ДВАЖДЫ — здесь под
   #modalProducts и бесскоупно в блоке M23 (≈2075). Правку фокуса/рамки
   применять к обеим копиям, иначе чинится только один из двух контекстов
   (десктоп-сайдбар / мобильная шторка). */
#modalProducts .xfsf__input:focus {
  border-color: var(--accent);
  background: var(--surface);
  box-shadow: 0 0 0 3px oklch(0.68 0.22 var(--accent-h) / 0.15);
}
#modalProducts .xfsf__slider-wrap {
  grid-column: 1 / -1;
  padding: 8px 4px 2px;
  width: auto !important;
  max-width: none !important;
  float: none !important;
}
/* jQuery UI slider — тёмная тема */
#modalProducts .xfsf__slider.ui-slider {
  position: relative;
  height: 4px;
  background: var(--surface-3);
  border: 0;
  border-radius: 999px;
  margin: 0;
}
#modalProducts .xfsf__slider.ui-slider .ui-slider-range {
  background: var(--accent);
  height: 100%;
  border-radius: 999px;
  position: absolute;
  top: 0;
}
#modalProducts .xfsf__slider.ui-slider .ui-slider-handle {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--text);
  border: 2px solid var(--accent);
  box-shadow: 0 2px 6px oklch(0 0 0 / 0.35);
  top: -6px;
  margin-left: -8px;
  cursor: grab;
  /* [#406] `.ui-state-focus` ниже — не индикатор фокуса: тем же правилом
     красится `.ui-state-active`, то есть фокус и перетаскивание неотличимы.
     Ручка focusable (jQuery UI ставит tabindex), `outline` не гасить. */
}
#modalProducts .xfsf__slider.ui-slider .ui-slider-handle.ui-state-focus,
#modalProducts .xfsf__slider.ui-slider .ui-slider-handle.ui-state-active {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px oklch(0.68 0.22 var(--accent-h) / 0.25);
  cursor: grabbing;
}
@media (hover: hover) {
  #modalProducts .xfsf__slider.ui-slider .ui-slider-handle:hover {
  background: var(--accent);
  border-color: var(--accent);
  box-shadow: 0 0 0 4px oklch(0.68 0.22 var(--accent-h) / 0.25);
  cursor: grabbing;
}
}

/* Опции (checkbox/radio list) */
#modalProducts .xfsf__options {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin: 0;
  padding: 0;
}
#modalProducts .xfsf__option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  color: var(--text-2);
  cursor: pointer;
  font-weight: 400;
  font-size: 12.5px;
  line-height: 1.4;
  margin: 0;
  border: 0;
}
@media (hover: hover) {
  #modalProducts .xfsf__option:hover { color: var(--text); }
}
#modalProducts .xfsf__option--disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
#modalProducts .xfsf__option-input {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
  padding: 0 !important;
  background: transparent !important;
}
#modalProducts .xfsf__option-text {
  flex: 1;
  min-width: 0;
  color: inherit;
  overflow-wrap: anywhere;
  word-break: break-word;
}
#modalProducts .xfsf__option-count {
  margin-left: auto;
  color: var(--text-3);
  font-family: var(--font-mono);
  font-size: 11.5px;
  flex: none;
}

/* Чекбоксы внутри .xfsf__option_input — inputs скрыты-styled, наш custom уже применяется
   через глобальный #modalProducts input[type="checkbox"] выше. Ограничим display для radio — custom radio */
#modalProducts .xfsf__option input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  min-width: 16px;
  border: 1.5px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  cursor: pointer;
  position: relative;
  margin: 0 0 0 0;
  transition: border-color .15s;
  box-sizing: border-box;
  flex: none;
}
#modalProducts .xfsf__option input[type="radio"]:checked {
  border-color: var(--accent);
}
#modalProducts .xfsf__option input[type="radio"]:checked::after {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--accent);
}
/* inputs у checkboxes уже кастомизированы глобальным правилом выше */

/* Dropdown (display_type=P) */
#modalProducts .xfsf__dropdown { width: 100%; }
#modalProducts .xfsf__dropdown-block {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  padding: 8px 34px 8px 12px;
  cursor: pointer;
  font-size: 12.5px;
  color: var(--text-2);
  transition: border-color .15s;
}
@media (hover: hover) {
  #modalProducts .xfsf__dropdown-block:hover { border-color: var(--accent-line); }
}
#modalProducts .xfsf__dropdown-arrow {
  position: absolute;
  right: 12px;
  top: 50%;
  width: 8px;
  height: 8px;
  border-right: 1.5px solid var(--text-3);
  border-bottom: 1.5px solid var(--text-3);
  transform: translateY(-70%) rotate(45deg);
  background: transparent;
}
#modalProducts .xfsf__dropdown-popup {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 10;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 8px;
  margin-top: 4px;
  max-height: 260px;
  overflow-y: auto;
  box-shadow: 0 12px 32px -12px oklch(0 0 0 / 0.5);
}
#modalProducts .xfsf__dropdown-list { list-style: none; margin: 0; padding: 6px 0; }
#modalProducts .xfsf__dropdown-list li { margin: 0; }
#modalProducts .xfsf__dropdown-item {
  display: block;
  padding: 7px 12px;
  color: var(--text-2);
  font-size: 12.5px;
  cursor: pointer;
  border: 0;
  margin: 0;
}
@media (hover: hover) {
  #modalProducts .xfsf__dropdown-item:hover { background: var(--surface-3); color: var(--text); }
}
#modalProducts .xfsf__dropdown-item--selected { background: var(--accent-dim); color: var(--accent-2); }
#modalProducts .xfsf__dropdown-item--disabled { opacity: 0.45; cursor: not-allowed; }

/* Кнопки применения/сброса */
#modalProducts .xfsf__actions {
  margin-top: 12px;
  padding: 0;
}
#modalProducts .xfsf__buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 0;
}
#modalProducts .xfsf__btn.btn {
  padding: 10px 16px;
  border-radius: 10px;
  font-size: 12.5px;
  font-weight: 500;
  border: 0;
  cursor: pointer;
  transition: background-color .15s, filter .15s, box-shadow .15s;
  line-height: 1.2;
  background-image: none;
}
#modalProducts .xfsf__btn--apply.btn {
  background: var(--accent) !important;
  background-color: var(--accent) !important;
  color: #fff !important;
  flex: 1;
}
#modalProducts .xfsf__btn--apply.btn:focus {
  filter: brightness(1.08);
  color: #fff !important;
  box-shadow: 0 6px 24px -10px oklch(0.68 0.22 var(--accent-h) / 0.6);
}
@media (hover: hover) {
  #modalProducts .xfsf__btn--apply.btn:hover {
  filter: brightness(1.08);
  color: #fff !important;
  box-shadow: 0 6px 24px -10px oklch(0.68 0.22 var(--accent-h) / 0.6);
}
}
#modalProducts .xfsf__btn--reset.btn {
  background: var(--surface) !important;
  border: 1px solid var(--line-soft) !important;
  color: var(--text-2) !important;
}
#modalProducts .xfsf__btn--reset.btn:focus {
  background: var(--surface-3) !important;
  color: var(--text) !important;
  border-color: var(--accent-line) !important;
}
@media (hover: hover) {
  #modalProducts .xfsf__btn--reset.btn:hover {
  background: var(--surface-3) !important;
  color: var(--text) !important;
  border-color: var(--accent-line) !important;
}
}

/* [#43] Desktop: pin the «Показать»/«Сбросить» bar to the bottom of the filter
   column so apply is reachable without scrolling the column (reproduced: button
   at y~1661). The scroll container is .col-md-3 (modal.css:131-139,
   max-height:calc(100vh-180px); overflow-y:auto); none of .xfsf/.xfsf__form/
   .xfsf__list carry overflow, so sticky resolves against .col-md-3 and pins.
   Mirrors the mobile drawer's sticky CTA (#productFilterRight .xfsf__actions).
   z-index stays ONLY on this inner bar — NEVER on .col-md-3 (M22.2 trap, 1707).
   .xfsf__list padding clears the bar so the last option is never hidden behind
   it. min-width:992px aligns exactly with the mobile boundary (@max-width:991).
   The consent bar (.xf-cookie, z:9999) renders OVER the modal (z:990) and would
   hide the pinned bar on first visit. Fix by SHRINKING the column by the bar's
   height when consent is up (--xf-cookie-h, like the catalog/FAB; 96px fallback
   for desktop since the configurator hides the FAB so app.js may not measure it)
   — so the bar sits at the column's true, higher bottom with NO filter box
   peeking below it (lifting the bar alone left a gap that showed stray options). */
@media (min-width: 992px) {
  #modalProducts .xfsf__actions {
    position: sticky;
    bottom: 0;
    z-index: 2;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--line-soft);
    background: var(--surface);
  }
  #modalProducts .xfsf__list {
    padding-bottom: 52px;
  }
  body.xf-cookie-up #modalProducts .modal-body > .row > .col-md-3 {
    max-height: calc(100vh - 180px - var(--xf-cookie-h, 96px));
  }
}

/* Popup с количеством элементов — прячем (модалка сама перезагружает результат) */
#modalProducts .xfsf__popup,
#modalProducts #modef.bx-filter-popup-result {
  display: none !important;
}

/* Контейнер-modef — тонкие <span> для JS-совместимости, прячем визуально */
#modalProducts .bx-filter-container-modef { display: none !important; }

/* ---------- Custom checkboxes ----------
   Scoped к модалке (фильтр + compare). BS3 дефолтные чекбоксы ломаются, если их
   style'ить — собираем свои через appearance:none + SVG mask для галочки. */
#modalProducts input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  width: 16px;
  height: 16px;
  min-width: 16px;
  min-height: 16px;
  border: 1.5px solid var(--line);
  border-radius: 4px;
  background: var(--surface);
  cursor: pointer;
  position: relative;
  display: inline-block;
  vertical-align: middle;
  margin: 0 6px 0 0;
  flex: none;
  transition: border-color .15s, background .15s;
  box-sizing: border-box;
  padding: 0;
}
@media (hover: hover) {
  #modalProducts input[type="checkbox"]:hover {
  border-color: var(--accent-line);
}
}
#modalProducts input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
#modalProducts input[type="checkbox"]:checked {
  background-color: var(--accent);
  border-color: var(--accent);
}
/* Галочка через SVG */
#modalProducts input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='3.5 8.5 6.8 11.8 12.5 5.2'/></svg>");
  background-size: 14px 14px;
  background-position: center;
  background-repeat: no-repeat;
}
/* Метки у чекбоксов + мягкий layout */
#modalProducts .bx_filter_param_label,
#modalProducts .bx-filter-param-label,
#modalProducts .bx_filter_input_container label,
#modalProducts .bx-filter-input-container label {
  display: flex;
  align-items: center;
  gap: 4px;
  color: var(--text-2);
  font-size: 12.5px;
  font-weight: 400;
  padding: 4px 0;
  cursor: pointer;
  line-height: 1.4;
  margin: 0;
}
@media (hover: hover) {
  #modalProducts .bx_filter_param_label:hover,
#modalProducts .bx-filter-param-label:hover,
#modalProducts .bx_filter_input_container label:hover,
#modalProducts .bx-filter-input-container label:hover {
  color: var(--text);
}
}
/* Счётчик количества — приглушённый */
#modalProducts .bx_filter_param_text,
#modalProducts .bx-filter-param-text {
  color: var(--text-3);
  font-family: var(--font-mono);
  font-size: 11.5px;
  margin-left: auto;
}

/* Числовые инпуты price-range */
#modalProducts .bx_filter_input_container input[type="text"],
#modalProducts .bx-filter-input-container input[type="text"],
#modalProducts .bx_filter_input_container input[type="number"],
#modalProducts .bx-filter-input-container input[type="number"] {
  background: var(--surface);
  border: 1px solid var(--line-soft);
  color: var(--text);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: 12.5px;
  font-family: var(--font-mono);
  width: 100%;
}

/* Grid 2-колонки для пары min/max в price-filter — если модуль даёт свою раскладку */
#modalProducts .bx_filter_parameters_box_container_block,
#modalProducts .bx-filter-parameters-box-container-block {
  display: flex;
  gap: 8px;
  align-items: center;
}

/* ============================================================
   M2a — Mobile picker foundation (≤640px)
   ============================================================
   На mobile dialog становится full-bleed sheet, чтобы фильтр+товары
   получили весь экран. Bootstrap уже даёт position:fixed на .modal
   + body.modal-open{overflow:hidden}; здесь чиним layout dialog/content,
   делаем header sticky, и меняем scale-up transition на slide-up.

   Блок в конце файла — намеренно, чтобы override'нул базовые правила
   .modal-content/.modal-header/.modal-body выше по cascade. */
@media (max-width: 640px) {
  /* M22.4 belt-and-suspenders: kill backdrop-filter на .modal мобильно.
     `backdrop-filter: blur(8px)` (modal.css:14) — desktop visual flourish,
     создаёт stacking context на .modal. После portal drawer уехал at body,
     уже не дерётся за stacking с .modal — но эта строчка остаётся ловушкой
     на случай если кто-то будущем добавит fixed elem внутрь modal'а.
     На full-screen mobile modal blur невидим визуально. */
  #modalProducts.modal {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
  #modalProducts .modal-dialog {
    max-width: none;
    width: 100%;
    margin: 0;
    min-height: 100vh;
    min-height: 100dvh;
  }
  /* M22.3 HOTFIX: kill transform на .modal-dialog мобильно.
     Любой transform (включая identity translateY(0) или Bootstrap base
     translate(0,-25%)) делает .modal-dialog containing block для
     position:fixed детей — drawer + backdrop позиционируются ОТНОСИТЕЛЬНО
     .modal-dialog (5100px scroll-height), а не viewport. На iOS Safari
     это вдобавок ломает stacking: paint order игнорирует z-index внутри
     transformed parent в некоторых комбинациях, и backdrop (z:1055)
     рисуется поверх drawer'а (z:1060). User видит grey-dead drawer
     вместо white filter panel. `transform: none !important` отключает
     M2a's 16px slide-up (на mobile modal full-screen, slide невидим),
     drawer + backdrop возвращаются к normal viewport-fixed semantics. */
  #modalProducts.fade .modal-dialog,
  #modalProducts.fade.in .modal-dialog,
  #modalProducts.fade.show .modal-dialog {
    transform: none !important;
    transition: none !important;
  }
  #modalProducts .modal-content {
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    box-shadow: none;
  }
  #modalProducts .modal-header {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--surface);
    padding: 14px 16px;
  }
  #modalProducts .modal-title {
    font-size: 18px;
  }
  #modalProducts .modal-body {
    padding: 14px 16px 24px;
    flex: 1;
    max-height: none;
  }
} /* end of phone-only sheet foundation block (≤640) */

/* M24d Step 5: drawer mechanics lifted from 640 → 991 (BS3-lg).
   At 641-991 col-md-3 sidebar collapses by BS3 (modal.css base @ ≤991),
   but desktop sidebar CSS doesn't apply там — drawer = single working
   pattern. Same selectors as phone, just wider breakpoint. */
@media (max-width: 991px) {
  /* ---------- M2b: filter drawer ----------
     #productFilterRight выезжает справа slide-in'ом; backdrop — реальный DOM
     элемент (.xf-drawer-backdrop) injected once в JS. Контейнер col-md-3 в
     in-flow с trigger-кнопкой #collapseFilter; продукты на полный экран.
     M22.4 portal: drawer + backdrop живут at document.body via JS — селекторы
     БЕЗ #modalProducts prefix. Прежние правила с #modalProducts ... перестали
     цеплять portal'd elements (filter ушёл из modal DOM-tree), что приводило
     к grey-dead drawer'у на iOS Safari через все три предыдущих фикса.
     На desktop ничего не трогаем — все правила в @media (max-width: 640px),
     desktop CSS выше по cascade. */
  #modalProducts .col-xs-12.col-md-3 {
    position: relative;
    width: 100%;
    margin: 0 0 16px;
    padding: 0;
  }
  #modalProducts .col-xs-12.col-md-9 {
    width: 100%;
    margin: 0;
  }
  /* M22.4: убран #modalProducts prefix — drawer portal'd в document.body.
     M23: width 88%→80% (backdrop tap-area 12%→20%), padding убран в пользу
     flex-chain с .xfsf__list scrollable inside, drawer container overflow:hidden. */
  #productFilterRight {
    display: flex !important;
    flex-direction: column;
    position: fixed;
    top: 0;
    right: 0;
    z-index: 1060;
    height: 100vh;
    height: 100dvh;
    width: 80%;
    max-width: 360px;
    background: var(--surface);
    border-left: 1px solid var(--line-soft);
    box-shadow: -10px 0 30px -10px oklch(0 0 0 / 0.4);
    transform: translateX(100%);
    transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
    overflow: hidden;
    padding: 0;
  }
  body.xf-filter-drawer-open #productFilterRight {
    transform: translateX(0);
  }
  /* Drawer спрятан самим transform — по The Motion-Multiplier Rule точечный
     transition: none под reduce (как .xf-drawer__panel и шторка фильтра). */
  @media (prefers-reduced-motion: reduce) {
    #productFilterRight { transition: none; }
  }
  .xf-drawer-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1055;
    background: oklch(0 0 0 / 0.5);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.2s, visibility 0.2s;
  }
  body.xf-filter-drawer-open .xf-drawer-backdrop {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }
  #modalProducts #collapseFilter,
  #modalProducts #collapseFilter:hover,
  #modalProducts #collapseFilter:focus,
  #modalProducts #collapseFilter:active {
    background: var(--accent);
    background-image: none;
    color: #fff;
    border: none;
    border-radius: 10px;
    padding: 10px 18px;
    text-transform: none;
    font-family: var(--font-sans, system-ui);
    font-weight: 500;
    font-size: 15px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    box-shadow: none;
    text-shadow: none;
  }
  #modalProducts #collapseFilter:hover { opacity: 0.92; }
  #modalProducts .hidden-md.hidden-lg .text-center { margin: 0 0 12px; text-align: left; }
  #modalProducts .hidden-md.hidden-lg .text-center hr { display: none; }
}

/* ============================================================
   M22.1 — Picker polish (≤991px = mobile + tablet portrait)
   ============================================================
   M24d Step 5: lifted from 640 → 991. Body reflow (search row + tiny filter
   icon top-right + sort tabs) applies в drawer-mode range. Card 96px-thumb
   layout вырезано в отдельный 640 block ниже — на tablet нужны regular 2-col
   cards, не horizontal compact.
   Reorders modal-body to deliver primary task first:
     [search input + tiny filter icon top-right]
     [sort tabs]
     [product cards — compact horizontal grid]

   Changes vs M2a/M2b baseline:
   - Filter column collapses to floating 40×40 icon-only button anchored
     top-right of .modal-body > .row (overrides M2b's full-width accent CTA).
   - Main column becomes flex-column with explicit order so search row sits
     above sort tabs (DOM order is sort → search → products).
   - Cards switch from full-width vertical to 96px-thumb horizontal grid;
     specs capped at 3 most-relevant.
   - «+ Выбрать» CTA hidden — .btn-add already wired on thumb+title links,
     so card click triggers AJAX select. Title-link::after stretches over the
     card for full-area tap. Selected/remove states stay visible. */
@media (max-width: 991px) {
  /* Row → relative anchor for absolute filter column */
  #modalProducts .modal-body > .row {
    position: relative;
  }

  /* Filter column → floating wrapper for the icon-button.
     productFilterRight (drawer) is already position:fixed via M2b, so this
     parent collapse doesn't affect drawer behavior.
     M22.2 HOTFIX: z-index MUST be `auto` here. Two sources combine to
     create a stacking context that traps the drawer (z:1060) below
     modal-level backdrop (z:1055):
       1) The component's own style.css sets `.sticky-top { z-index: 103 }`
          on the column wrapper.
       2) Adding any z-index here (M22.1 had z:5) likewise creates context.
     Either creates a stacking context → backdrop ends up above the drawer,
     intercepting clicks on «Показать». `z-index: auto` (explicit override
     of the component's :103) keeps `.col-md-3` non-stacking.
     position:absolute alone is enough to layer above .col-md-9 siblings
     (positioned-with-auto-z paints above non-positioned in normal flow). */
  #modalProducts .modal-body > .row > .col-xs-12.col-md-3 {
    position: absolute;
    top: 0;
    right: 0;
    width: auto;
    margin: 0;
    padding: 0;
    z-index: auto;
    min-height: 0;
    max-height: none;
    overflow: visible;
  }
  #modalProducts .col-xs-12.col-md-3 .hidden-md.hidden-lg {
    margin: 0;
  }
  #modalProducts .col-xs-12.col-md-3 .hidden-md.hidden-lg .text-center {
    text-align: right;
    margin: 0;
    padding: 0;
  }
  #modalProducts .col-xs-12.col-md-3 .hidden-md.hidden-lg .text-center hr {
    display: none;
  }

  /* Main column → flex-column with reorder.
     M22.2: position:static (override BS3's .col-* default position:relative)
     so this column lives in the non-positioned paint group (group 3) below
     the absolutely-positioned filter column (col-md-3 in group 6). Without
     this, both columns sit in group 6 and DOM order wins — col-md-9 paints
     after col-md-3 and would cover the filter icon. */
  #modalProducts .modal-body > .row > .col-xs-12.col-md-9 {
    display: flex;
    flex-direction: column;
    width: 100%;
    padding-left: 0;
    position: static;
  }
  #modalProducts .col-xs-12.col-md-9 > .form-group {
    order: 1;
    margin-bottom: 12px;
    padding-right: 50px;       /* clear space for absolute filter icon */
  }
  #modalProducts .col-xs-12.col-md-9 > #sortPanel {
    order: 2;
    margin-bottom: 14px;
  }
  /* M22.2: include .xf-pc-empty in the order: 3 group.
     When itemsCount === 0, template renders .xf-pc-empty.alert as a direct
     child of .col-md-9 (no catalog-section wrapper), so without this rule
     the empty alert defaults to order: 0 and lands at the top — covering
     the absolute-positioned filter icon and intercepting its clicks. */
  #modalProducts .col-xs-12.col-md-9 > .webprostor-configurator-catalog-section,
  #modalProducts .col-xs-12.col-md-9 > .xf-pc-empty {
    order: 3;
  }

  /* Filter button → 40×40 icon-only, tonal (overrides M2b solid-accent rule) */
  #modalProducts #collapseFilter,
  #modalProducts #collapseFilter:hover,
  #modalProducts #collapseFilter:focus,
  #modalProducts #collapseFilter:active {
    width: 40px;
    height: 40px;
    min-width: 40px;
    padding: 0;
    background: var(--accent-dim);
    background-image: none;
    color: var(--accent-2);
    border: 1px solid var(--accent-line);
    border-radius: 10px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0;
    box-shadow: none;
    text-shadow: none;
    font-size: 0;
    line-height: 1;
  }
  #modalProducts #collapseFilter > span {
    display: none;
  }
  #modalProducts #collapseFilter > i.fa-filter {
    font-size: 16px;
    margin: 0;
    color: inherit;
  }
} /* end of drawer-mode picker polish block (≤991) */

/* M24d Step 5: phone-only card layout (≤640). 96px-thumb horizontal compact
   grid не подходит для tablet — там 2-col regular cards с full-width thumb
   от @media (max-width: 991px) @370 в этом же файле. */
@media (max-width: 640px) {
  /* Card → grid layout: thumb left, body+foot stacked right */
  #modalProducts .xf-pc-card {
    display: grid;
    grid-template-columns: 96px 1fr;
    grid-template-areas:
      "thumb body"
      "thumb foot";
    grid-template-rows: auto auto;
    position: relative;
  }
  #modalProducts .xf-pc-card__thumb {
    grid-area: thumb;
    aspect-ratio: 1;
    width: 96px;
    border-bottom: 0;
    border-right: 1px solid var(--line-soft);
    align-self: stretch;
  }
  #modalProducts .xf-pc-card__body {
    grid-area: body;
    padding: 10px 14px 6px;
    gap: 6px;
    min-width: 0;
  }
  #modalProducts .xf-pc-card__foot {
    grid-area: foot;
    padding: 6px 14px 10px;
    background: transparent;
    border-top: 1px dashed var(--line-soft);
    gap: 8px;
  }
  #modalProducts .xf-pc-card__title {
    font-size: 13.5px;
    -webkit-line-clamp: 2;
  }
  #modalProducts .xf-pc-card__specs {
    border-top: 0;
    padding-top: 4px;
    gap: 3px;
    font-size: 11.5px;
  }
  /* Cap at 3 specs — low-signal ones (Кэш L2/L3, Модельный ряд, Кодовое название) drop off */
  #modalProducts .xf-pc-card__spec:nth-of-type(n+4) {
    display: none;
  }

  /* «+ Выбрать» CTA hidden — card-click selects via .btn-add wired on thumb/title.
     ИСКЛЮЧЕНИЕ: в quantity-режиме (USE_QUANTITY=Y) фоллбэк-тап по карточке НЕ существует —
     template.php рендерит title-link только при $clickable (USE_PRODUCT_QUANTITY != 'Y'),
     а submit-кнопка внутри form.xf-pc-card__form — единственный способ добавить. Прячем её —
     на телефоне в портрете товар не добавить вообще. Поэтому возвращаем кнопку, когда она
     внутри формы (quantity-режим). Не-quantity (голый <a>, есть тап по карточке) — остаётся скрытой. */
  #modalProducts .xf-pc-card__cta.btn-add {
    display: none !important;
  }
  #modalProducts .xf-pc-card__form .xf-pc-card__cta.btn-add {
    display: inline-flex !important;
  }
  /* Selected/remove states stay visible (rare per-card state, not 42-times noise) */
  #modalProducts .xf-pc-card__cta--selected,
  #modalProducts .xf-pc-card__cta--remove {
    padding: 6px 12px;
    font-size: 12.5px;
    width: auto;
    align-self: flex-start;
  }

  /* Whole-card tap target via title-link stretch.
     Title-link is a direct child of body which sits in a positioned card,
     so ::after with inset:0 covers the full card. */
  #modalProducts .xf-pc-card__title-link::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 1;
  }
  /* Keep interactive non-link children above the stretch */
  #modalProducts .xf-pc-card__status,
  #modalProducts .xf-pc-card__cta--selected,
  #modalProducts .xf-pc-card__cta--remove,
  #modalProducts .xf-pc-card__compare {
    position: relative;
    z-index: 2;
  }
  /* Quick-view link (.xf-pc-card__quick) inside thumb stays clickable */
  #modalProducts .xf-pc-card__quick {
    z-index: 3;
  }
}

/* ============================================================
   M23 — smart-filter polish + drawer chrome
   ============================================================
   .xfsf-* классы рендерит наш override
   local/templates/xcore_front/components/bitrix/catalog.smart.filter/
     webprostor.configurator/template.php
   Markup есть давно (xfsf__box, xfsf__title, xfsf__chev, xfsf__body, xfsf__range,
   xfsf__range-input, xfsf__input, xfsf__options, xfsf__option, xfsf__option-input,
   xfsf__option-text, xfsf__option-count, xfsf__dropdown, xfsf__dropdown-block,
   xfsf__dropdown-popup, xfsf__dropdown-list, xfsf__dropdown-item, xfsf__actions,
   xfsf__buttons, xfsf__btn, xfsf__btn--apply, xfsf__btn--reset, xfsf__popup).
   CSS-полировка для xf-design не было — заполняем сейчас.

   Mobile drawer chrome: .xfsf__drawer-header (наш новый wrapper из template.php) +
   sticky CTA через flex-chain + collapse-default через JS (xfApplyMobileFilterCollapse
   в script.js — не CSS, потому что Bitrix smart-filter JS меняет inline display
   и .bx-active class при click — наш CSS не может побороть inline style).

   Применимо: desktop sidebar (catalog.smart.filter inside .col-md-3) И mobile drawer
   (productFilterRight portal'd в body). Полировка ungated; chrome rules
   gated by @media (max-width: 640px). */

/* Group container — tonal divider between filter groups */
.xfsf__box {
  border-bottom: 1px solid var(--line-soft);
  padding: 12px 0;
}
.xfsf__box:last-child { border-bottom: 0; }

/* Group title — tappable header for expand/collapse */
.xfsf__title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  user-select: none;
  padding: 4px 0;
  -webkit-tap-highlight-color: transparent;
}
.xfsf__title-name {
  font-family: var(--font-sans, Onest, system-ui, sans-serif);
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.2px;
}
.xfsf__chev {
  display: inline-flex;
  color: var(--text-2);
  transition: transform 0.2s ease;
  flex-shrink: 0;
  margin-left: 8px;
}
/* M24a: только .bx-active управляет rotation (smartFilter.hideFilterProps toggleит её).
   .xfsf__box--open PHP-render'ит initially для autoExpanded boxes, но JS не sync'ит —
   если оставить второй селектор, после первого collapse box 0/1 chev остался бы ▲ при свернутом body. */
.xfsf__box.bx-active .xfsf__chev {
  transform: rotate(180deg);
}
.xfsf__body {
  padding-top: 10px;
}

/* Range inputs — Цена, Дата выхода etc */
.xfsf__range {
  display: flex;
  align-items: center;
  gap: 8px;
}
.xfsf__range-cell { flex: 1; }
.xfsf__range-input { display: block; }
.xfsf__range-dash {
  color: var(--text-3);
  font-family: var(--font-sans, Onest, system-ui, sans-serif);
  flex-shrink: 0;
}
/* Вторая копия `.xfsf__input` — первая под #modalProducts (≈1205). */
.xfsf__input {
  display: block;
  width: 100%;
  padding: 9px 11px;
  font-family: var(--font-mono, "XCore Mono", ui-monospace, Menlo, monospace);
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  transition: border-color 0.15s, box-shadow 0.15s;
  -webkit-appearance: none;
  appearance: none;
}
.xfsf__input::placeholder {
  color: var(--text-3);
  opacity: 1;
}
.xfsf__input:focus,
.xfsf__input:focus-visible {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}

/* Checkbox / radio options — grid layout */
.xfsf__options {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px 12px;
}
.xfsf__option {
  display: flex;
  align-items: flex-start;
  cursor: pointer;
  user-select: none;
  padding: 6px 0;
  -webkit-tap-highlight-color: transparent;
}
.xfsf__option-input {
  display: inline-flex;
  align-items: flex-start;
  gap: 8px;
  flex: 1;
  min-width: 0;
}
.xfsf__option-input input[type="checkbox"],
.xfsf__option-input input[type="radio"] {
  -webkit-appearance: none;
  appearance: none;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: 4px;
  cursor: pointer;
  position: relative;
  transition: border-color 0.15s, background 0.15s;
}
.xfsf__option-input input[type="radio"] {
  border-radius: 50%;
}
.xfsf__option-input input[type="checkbox"]:checked,
.xfsf__option-input input[type="radio"]:checked {
  border-color: var(--accent);
  background: var(--accent);
}
/* M24a: SVG-galka — единый подход для desktop sidebar (под #modalProducts) и mobile drawer (portal'd).
   #productFilterRight matches на обоих surface'ах. Inset:0 + background:center auto-центрирует. */
#productFilterRight input[type="checkbox"]:checked::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><polyline points='3.5 8.5 6.8 11.8 12.5 5.2'/></svg>");
  background-size: 12px 12px;
  background-position: center;
  background-repeat: no-repeat;
  /* unset M23-style border/transform на случай если осели от старых правил */
  border: 0;
  transform: none;
  width: auto;
  height: auto;
}
#productFilterRight input[type="radio"]:checked::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #fff;
  transform: translate(-50%, -50%);
}
.xfsf__option-input input:focus-visible {
  box-shadow: 0 0 0 3px var(--accent-dim);
}
.xfsf__option-text {
  font-family: var(--font-sans, Onest, system-ui, sans-serif);
  font-size: 14px;
  color: var(--text);
  line-height: 1.4;
  word-break: break-word;
}
.xfsf__option-count {
  font-family: var(--font-mono, "XCore Mono", ui-monospace, Menlo, monospace);
  font-size: 11px;
  color: var(--text-3);
  margin-left: 4px;
  white-space: nowrap;
}
.xfsf__option--disabled,
.xfsf__option-input.disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Dropdown — single-select group */
.xfsf__dropdown { position: relative; }
.xfsf__dropdown-block {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 9px 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
  font-family: var(--font-sans, Onest, system-ui, sans-serif);
  font-size: 14px;
  color: var(--text);
}
.xfsf__dropdown-text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xfsf__dropdown-arrow {
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--text-2);
  margin-left: 8px;
  flex-shrink: 0;
}
.xfsf__dropdown-popup {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 240px;
  overflow-y: auto;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  margin-top: 4px;
  z-index: 10;
  box-shadow: 0 6px 18px -8px oklch(0 0 0 / 0.18);
}
.xfsf__dropdown-list { list-style: none; margin: 0; padding: 4px 0; }
.xfsf__dropdown-item {
  display: block;
  padding: 8px 12px;
  font-family: var(--font-sans, Onest, system-ui, sans-serif);
  font-size: 14px;
  color: var(--text);
  cursor: pointer;
}
.xfsf__dropdown-item:hover { background: var(--surface-2); }
.xfsf__dropdown-item--selected {
  background: var(--accent-dim);
  color: var(--accent-2);
  font-weight: 500;
}
.xfsf__dropdown-item--disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Action buttons — Применить / Сбросить */
.xfsf__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 11px 18px;
  font-family: var(--font-sans, Onest, system-ui, sans-serif);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.1px;
  border-radius: 10px;
  cursor: pointer;
  transition: background 0.15s, opacity 0.15s, border-color 0.15s;
  border: 1px solid transparent;
  text-transform: none;
  text-shadow: none;
  box-shadow: none;
}
.xfsf__btn--apply {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}
.xfsf__btn--apply:hover,
.xfsf__btn--apply:focus { opacity: 0.92; }
/* M24d: dim Apply CTA when filtered count = 0. Not disabled attribute
   (iOS pointer-events ловушка — пользователь не может сбросить через Сбросить
   если Apply disabled). Set via JS xfUpdateApplyCount() через data-xf-empty. */
.xfsf__btn--apply[data-xf-empty="1"] {
  opacity: 0.55;
  cursor: not-allowed;
}
.xfsf__btn--apply[data-xf-empty="1"]:hover,
.xfsf__btn--apply[data-xf-empty="1"]:focus { opacity: 0.55; }
/* M24d Step 6 (polish): subtle pulse on Apply CTA during AJAX in-flight.
   Bitrix's smartFilter.click ставит set_filter.disabled=true перед AJAX
   и сбрасывает после (catalog.smart.filter/.../script.js:80-82, :235).
   :disabled selector цепляет именно этот период — кадр animations stops
   automatically когда disabled=false. Opacity 0.7→1.0 + 600ms loop —
   barely perceptible signal что-то происходит, не «I added animation»
   reflex. Respects prefers-reduced-motion. */
.xfsf__btn--apply:disabled {
  animation: xf-apply-pulse 0.6s ease-in-out infinite alternate;
  cursor: progress;
}
@keyframes xf-apply-pulse {
  from { opacity: 0.7; }
  to { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .xfsf__btn--apply:disabled {
    animation: none;
    opacity: 0.85;
  }
}
.xfsf__btn--reset {
  background: var(--surface);
  color: var(--text);
  border-color: var(--line);
}
.xfsf__btn--reset:hover,
.xfsf__btn--reset:focus {
  background: var(--surface-2);
  border-color: var(--text-3);
}
.xfsf__btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Drawer header — hidden on desktop (sidebar context),
   visible inside mobile drawer (chrome). */
.xfsf__drawer-header { display: none; }

/* ---------- Drawer chrome (≤991px = mobile + tablet portrait) ---------- */
/* M24d Step 5: lifted from 640 → 991. Drawer chrome (header, X-close,
   sticky Apply CTA, options layout) applies wherever drawer activates. */
@media (max-width: 991px) {
  /* Flex-chain так что .xfsf__list scrollable inside drawer,
     .xfsf__drawer-header (top) + .xfsf__actions (bottom) flex-shrink:0 fixed.
     #productFilterRight уже overflow:hidden (см. M23 update в M2b блоке выше). */
  #productFilterRight .xfsf {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  #productFilterRight .xfsf__section {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  #productFilterRight .xfsf__form {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
  #productFilterRight .xfsf__list {
    flex: 1;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    min-height: 0;
    padding: 8px 16px 16px;
    margin: 0;
  }

  /* Drawer header — title + inline X close */
  #productFilterRight .xfsf__drawer-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-shrink: 0;
    padding: 14px 12px 14px 16px;
    border-bottom: 1px solid var(--line-soft);
    background: var(--surface);
  }
  #productFilterRight .xfsf__drawer-title {
    font-family: var(--font-sans, Onest, system-ui, sans-serif);
    font-size: 17px;
    font-weight: 600;
    color: var(--text);
    margin: 0;
    letter-spacing: -0.3px;
  }
  #productFilterRight .xfsf__drawer-close {
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    color: var(--text-2);
    border-radius: 10px;
    cursor: pointer;
    padding: 0;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.15s, color 0.15s;
  }
  #productFilterRight .xfsf__drawer-close:hover,
  #productFilterRight .xfsf__drawer-close:focus-visible {
    background: var(--surface-2);
    color: var(--text);
  }
  #productFilterRight .xfsf__drawer-close:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  /* Sticky-at-bottom CTA bar — Применить + Сбросить */
  #productFilterRight .xfsf__actions {
    flex-shrink: 0;
    background: var(--surface);
    border-top: 1px solid var(--line-soft);
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
    margin: 0;
  }
  #productFilterRight .xfsf__buttons {
    display: flex;
    gap: 8px;
    width: 100%;
  }
  #productFilterRight .xfsf__btn--reset {
    flex: 0 0 auto;
    min-width: 100px;
  }
  #productFilterRight .xfsf__btn--apply {
    flex: 1;
  }
  /* Hide legacy float popup on mobile — replaced by sticky CTA */
  #productFilterRight .xfsf__popup {
    display: none !important;
  }

  /* Mobile: single-column options layout where 2-column doesn't fit */
  #productFilterRight .xfsf__options {
    grid-template-columns: 1fr;
  }
  /* Show "Бренд"-style short option rows in two columns when room allows */
  #productFilterRight .xfsf__box[data-xfsf-shortrows] .xfsf__options {
    grid-template-columns: 1fr 1fr;
  }
}

/* End */


/* Start:/local/templates/xcore_front/css/content.css?179264521449289*/
/* OWNS: .xf-help-*, .xf-about-*, .xf-contacts-*, .xf-staff-*, .xf-vacancy-*, .xf-license-*, .xf-review-*, .xf-req-* (реквизит-строки: контакты сотрудников/страниц), .xf-empty-cta, .xf-back-link, .xf-pill-tab(s) (shared chip primitive), .xf-faq(-*) (аккордеон «Вопросы и ответы», #19)
   NOT-OWNS: .xf-blog-* (→ blog.css), .xf-news-* (→ news.css). Контент-bucket для info-страниц M13/M15/M16/M19 — не путать с блог/news статьями. */
/* ==========================================================================
   content.css — M13: инфо-страницы Помощь (Доставка, Оплата, Гарантия, хаб).

   Скоупится в .xf-help-hero и .xf-help-body — не трогает другие `.middle`
   страницы. Типографика рассчитана на сырой HTML из `index.php` (p/ul/li/b/a)
   без структурной переразметки.
   ========================================================================== */

/* ---------- Колонка инфо-страницы: одна пара краёв на всё ----------
   Каждый блок инфо-страницы (hero, вводный абзац, body, сетки, таблицы, CTA)
   стоит на одних и тех же левом и правом краях. Колонка — 960 (структура:
   сетки карточек, реквизиты, две колонки) или 720 (проза: доставка, оферта,
   политика); внутренний отступ 24px кладёт края контента на 912 / 672.
   Читаемость строки держим выбором колонки и кеглем, а не max-width на
   тексте внутри широкой колонки: такой край висит посреди сетки, и глаз
   читает его как ошибку вёрстки (замечание Ильи 2026-09-28: вводный абзац
   «О компании» и «Отзывов» обрывался на 70% колонки, под ним карточки шли
   до края). Блок с рамкой/фоном (CTA) сужается до контента колонки, а не
   стоит на её внешней границе.
   На телефоне внутренний отступ снимается: края встают на рельс контейнера
   (--container-pad 16px, #307), туда же, где шапка и CTA — раньше контент
   стоял на 40px, а CTA на 16px. Список блоков — в @media в самом конце файла
   (раньше по каскаду базовые padding'и блоков ниже перебили бы его). */

/* ---------- Hero (eyebrow + title + pill-tabs) ---------- */

.xf-help-hero {
  max-width: 960px;
  margin: 48px auto 32px;
  padding: 0 24px;
}

/* Subpage (Оплата/Доставка/Гарантия, форма Обращений): hero на ту же
   колонку что body (720px) — иначе hero-содержимое (pills, title) и
   body-content стоят на разных левых колонках из-за центрирования. */
.xf-help-hero--narrow {
  max-width: 720px;
}

/* Вводный абзац под H1 («О компании», «Отзывы»): 2–3 строки, на всю
   ширину колонки. Кегль 20px — ступень между заголовком 40px и телом 15px;
   на 912px это ~85 знаков в строке: длинно для прозы, нормально для двух
   строк вступления (правило 65–75ch в DESIGN.md — про Body). Абзац длиннее
   трёх строк сюда не ставить — это уже проза, ей место в .xf-help-body.
   Ширина — от колонки, не в ch: ch считается от шрифта, и подмена системного
   шрифта на Onest переломила бы строки заново (#277, замер layout-shift). */
.xf-help-intro {
  margin: -12px 0 0;
  font-size: 20px;
  line-height: 1.5;
  color: var(--text-2);
  text-wrap: pretty;
}

.xf-help-eyebrow {
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0 0 12px;
}

.xf-help-title {
  font-family: var(--font-sans);
  font-size: 40px;
  font-weight: 600;
  letter-spacing: -0.8px;
  line-height: 1.15;
  color: var(--text);
  margin: 0 0 28px;
}

/* ---------- Pill-tabs (3 чипа: Оплата / Доставка / Гарантия) ---------- */

.xf-pill-tabs {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin: 0;
  padding: 0;
  list-style: none;
}

.xf-pill-tab {
  display: inline-flex;
  align-items: center;
  padding: 8px 18px;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 13.5px;
  font-weight: 500;
  text-decoration: none;
  transition: background .15s, border-color .15s, color .15s;
}
@media (hover: hover) {
  .xf-pill-tab:hover {
  background: oklch(from var(--surface-2) calc(l + 0.03) c h);
  border-color: var(--accent-line);
  color: var(--text);
}
}
.xf-pill-tab--active {
  background: var(--accent);
  background-origin: border-box;
  background-clip: border-box;
  border-color: transparent;
  color: #fff;
}
@media (hover: hover) {
  .xf-pill-tab--active:hover {
  background: var(--accent);
  background-origin: border-box;
  background-clip: border-box;
  border-color: transparent;
  color: #fff;
}
}

/* ---------- Body: контент-типографика ---------- */
/* Скоуп `.xf-help-body *` чтобы стилизовать сырой HTML из index.php без
   переразметки. Сам body — одноколонник с читаемой шириной. */

.xf-help-body {
  max-width: 720px;
  margin: 0 auto 80px;
  padding: 0 24px;
  color: var(--text-2);
  font-size: 15px;
  line-height: 1.7;
}

.xf-help-body p,
.xf-help-body div {
  margin: 0 0 14px;
}
.xf-help-body p:empty,
.xf-help-body div:empty,
.xf-help-body p > br:only-child {
  margin: 0;
}
.xf-help-body ul,
.xf-help-body ol {
  margin: 0 0 18px;
  padding-left: 24px;
}
.xf-help-body li {
  margin: 0 0 6px;
  padding-left: 2px;
}
.xf-help-body li::marker {
  color: var(--accent-2);
}

.xf-help-body b,
.xf-help-body strong {
  color: var(--text);
  font-weight: 600;
}

.xf-help-body a {
  color: var(--accent-2);
  text-decoration: underline;
  text-decoration-color: var(--link-line);
  text-underline-offset: 3px;
  transition: color .15s, text-decoration-color .15s;
}
@media (hover: hover) {
  .xf-help-body a:hover {
  color: var(--text);
  text-decoration-color: var(--accent-2);
}
}

/* Контакты (tel:/mailto:) — действие, не data → Onest, не mono.
   JetBrains Mono рисует ноль с точкой (в мелком 0→8, клиенты звонили
   не туда). Телефон/почта вне Mono-as-Data. См. #91. */
.xf-help-body .xf-req-list a[href^="tel:"],
.xf-help-body .xf-req-list a[href^="mailto:"] {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  letter-spacing: -0.1px;
}

/* Aspro-страницы часто содержат inline `style="font-family: var(--ui-font-family-primary)"`.
   Перебиваем, чтобы шрифт был наш Onest. */
.xf-help-body [style*="font-family"] {
  font-family: var(--font-sans) !important;
}

/* Скрываем пустые <br> цепочки — в исходных файлах их много ради верстки */
.xf-help-body br + br + br {
  display: none;
}

/* ---------- Hub: 3 карточки-ссылки (на /help/index.php) ---------- */

.xf-help-hub {
  max-width: 960px;
  margin: 0 auto 80px;
  padding: 0 24px;
}

.xf-help-hub__cards {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  margin: 0 0 48px;
}

@media (max-width: 768px) {
  .xf-help-hub__cards { grid-template-columns: 1fr; }
  .xf-help-hero { margin-top: 28px; }
  .xf-help-title { font-size: 30px; }
  .xf-help-intro { font-size: 17px; }
}

.xf-help-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 28px 24px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  color: var(--text);
  text-decoration: none;
  transition: border-color .15s, background .15s, transform .15s;
}
@media (hover: hover) {
  .xf-help-card:hover {
  border-color: var(--accent-line);
  background: oklch(from var(--surface-2) calc(l + 0.02) c h);
  transform: translateY(calc(-2px * var(--xf-motion, 1)));
  color: var(--text);
}
}

.xf-help-card__icon {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  border-radius: 10px;
  color: #fff;
  flex-shrink: 0;
}

.xf-help-card__title {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.2px;
  margin: 8px 0 0;
}

.xf-help-card__desc {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-3);
  margin: 0;
}

.xf-help-card__arrow {
  margin-top: auto;
  padding-top: 12px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--accent-2);
}

/* ---------- CTA block «Остались вопросы» ---------- */

/* Рамка CTA — на краях контента колонки (912 / 672), а не на её внешней
   границе 960: иначе блок торчит на 24px за карточки и таблицы над ним.
   На телефоне — рельс контейнера (см. «Колонка инфо-страницы»). */
.xf-help-cta {
  width: calc(100% - 48px);
  max-width: 912px;
  margin: 24px auto 80px;
  padding: 32px 28px;
  /* Плоский нейтральный surface (#280): accent-tinted заливка снята по решению
     владельца — блок маркирован рамкой --accent-line, а не лиловым градиентом. */
  background: var(--surface-2);
  border: 1px solid var(--accent-line);
  border-radius: 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
}
/* Под прозовой колонкой 720 (Оплата/Доставка/Гарантия — footer.php). */
.xf-help-cta--narrow { max-width: 672px; }

.xf-help-cta__text {
  flex: 1 1 300px;
}

.xf-help-cta__title {
  font-size: 20px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  margin: 0 0 6px;
}

.xf-help-cta__desc {
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--text-3);
  margin: 0;
}

.xf-help-cta__btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 22px;
  background: var(--accent);
  border: 0;
  border-radius: 10px;
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  transition: transform .15s, box-shadow .15s;
  flex-shrink: 0;
}
/* Тёмная (#857): цветной тени у кнопок нет — блик по верхней кромке, как у
   .xf-btn (DESIGN.md, The Night-Sign Rule). 0-2-1 выше :hover ниже (0-2-0):
   на наведении тот же блик вместо тени на индиго 268. */
html[data-theme="dark"] .xf-help-cta__btn { box-shadow: var(--nx-key); }
@media (hover: hover) {
  .xf-help-cta__btn:hover {
  color: #fff;
  transform: translateY(calc(-1px * var(--xf-motion, 1)));
  box-shadow: 0 8px 24px -8px oklch(0.55 0.2 var(--accent-h) / 0.5);
}
}

/* Под 560px кнопка съезжает вниз и тянется на всю ширину — иначе текст и
   кнопка перетягивают друг друга в flex-wrap, особенно на 320px (iPhone SE). */
@media (max-width: 560px) {
  .xf-help-cta {
    flex-direction: column;
    align-items: flex-start;
    padding: 24px 20px;
  }
  /* В column-режиме basis 300px из базового правила стал бы ВЫСОТОЙ
     текстового блока → пустая полоса между текстом и кнопкой. */
  .xf-help-cta__text {
    flex: 0 0 auto;
  }
  .xf-help-cta__btn {
    width: 100%;
    justify-content: center;
  }
}

/* ==========================================================================
   M15: About-страница (/company/) — принципы, реквизиты, подразделы.
   Реюзает .xf-help-hero/-title/-eyebrow/-intro/-cta из info-шелла.
   Все блоки — на колонке 960 (см. «Колонка инфо-страницы» в начале файла).
   ========================================================================== */

/* ---------- Принципы (3 карточки: Честно / Открыто / По-человечески) ---------- */
.xf-about-values {
  max-width: 960px;
  margin: 0 auto 72px;
  padding: 0 24px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
.xf-about-value {
  padding: 28px 24px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.xf-about-value__num {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  border-radius: 10px;
  color: #fff;
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 600;
}
.xf-about-value__title {
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  margin: 6px 0 0;
}
.xf-about-value__desc {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-3);
  margin: 0;
}

/* ---------- Блок («Реквизиты», «Подразделы») ---------- */
.xf-about-block {
  max-width: 960px;
  margin: 0 auto 48px;
  padding: 0 24px;
}
.xf-about-block__title {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.3px;
  color: var(--text);
  margin: 0 0 20px;
}

/* ---------- Реквизиты: dl с row-grid ---------- */
.xf-req-list {
  margin: 0;
  padding: 0;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  overflow: hidden;
}
.xf-req-row {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 20px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--line-soft);
}
.xf-req-row:last-child { border-bottom: 0; }
.xf-req-row dt {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-3);
  margin: 0;
}
.xf-req-row dd {
  font-family: var(--font-mono);
  font-size: 13.5px;
  color: var(--text);
  margin: 0;
  letter-spacing: -0.1px;
}
/* Контакт-ссылки в реквизитах (телефон/почта сотрудника) — Onest, не mono:
   точечный ноль JetBrains Mono читается как 8. Прочие dd-значения — данные,
   остаются mono. См. #91. */
.xf-req-row dd a[href^="tel:"],
.xf-req-row dd a[href^="mailto:"] {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
}

/* ---------- Подразделы: grid с auto-fit (2 до 3 линков) ---------- */
.xf-about-links {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px;
}
.xf-about-link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 18px 20px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  color: var(--text);
  text-decoration: none;
  font-size: 14px;
  font-weight: 500;
  transition: border-color .15s, background .15s, transform .15s;
}
@media (hover: hover) {
  .xf-about-link:hover {
  border-color: var(--accent-line);
  background: oklch(from var(--surface-2) calc(l + 0.02) c h);
  transform: translateY(calc(-1px * var(--xf-motion, 1)));
  color: var(--text);
}
}
.xf-about-link__arrow {
  font-family: var(--font-mono);
  color: var(--accent-2);
  font-size: 13px;
}

/* ---------- Mobile ---------- */
@media (max-width: 768px) {
  .xf-about-values { grid-template-columns: 1fr; }
  .xf-about-links { grid-template-columns: 1fr; }
  .xf-req-row { grid-template-columns: 1fr; gap: 4px; padding: 12px 16px; }
}

/* ---------- Section heads в .xf-help-body (h2 primary, h3 secondary) ---------- */
/* h2 — основной section head для help-страниц и оферты.
   h1 (страница) → h2 (секции тела) → h3 (внутри disclosure / sub-sections).
   Не пропускать уровни — a11y outline. */
.xf-help-body h2 {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.3px;
  color: var(--text);
  margin: 40px 0 16px;
}
.xf-help-body h2:first-child { margin-top: 0; }
/* Когда первый ребёнок body — lede-карточка (warranty), h2 после неё уже
   получает достаточно воздуха от lede margin-bottom — гасим duplicate gap. */
.xf-help-body > .xf-help-lede + h2 { margin-top: 16px; }

/* Сетка карточек («Оплата») и раскрывашка («Гарантия») внутри body — на краях
   прозы, без своего отступа и своей ширины. Раньше body под них расширялся
   до 960 через :has(), а hero с пилюлями оставался на 720: заголовок,
   карточки и CTA стояли на трёх разных левых краях (379 / 283 / 235px на
   1440), и при переключении вкладок Оплата↔Доставка↔Гарантия контент прыгал.
   2×2 карточки оплаты на 672px — по 328px, текст в них на 2–4 строки. */
.xf-help-body > .xf-contacts-grid,
.xf-help-body > .xf-help-disclosure {
  max-width: none;
  padding: 0;
}

.xf-help-body h3 {
  font-family: var(--font-sans);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  margin: 24px 0 12px;
}
.xf-help-body h3:first-child { margin-top: 0; }

/* ==========================================================================
   M16: Страница контактов (/contacts/) — 4-card grid + Yandex map + CTA.
   Реюзает .xf-help-hero/-title/-eyebrow/-cta из info-шелла.
   ========================================================================== */

.xf-contacts-grid {
  max-width: 960px;
  margin: 0 auto 32px;
  padding: 0 24px;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 16px;
}

.xf-contacts-card {
  padding: 24px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.xf-contacts-card__icon {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  border-radius: 10px;
  color: #fff;
}

.xf-contacts-card__title {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  margin: 6px 0 2px;
}

.xf-contacts-card__main {
  font-size: 15px;
  line-height: 1.5;
  color: var(--text);
  margin: 0;
}

/* Часы — моноспейс для выравнивания цифр. */
.xf-contacts-hours {
  margin: 4px 0 0;
  padding: 0;
  display: grid;
  gap: 4px;
  font-family: var(--font-mono);
  font-size: 13px;
}
.xf-contacts-hours > div {
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: 12px;
}
.xf-contacts-hours dt {
  color: var(--text-3);
  margin: 0;
}
.xf-contacts-hours dd {
  color: var(--text);
  margin: 0;
}

/* Список телефонов / email на странице «Контакты». Значения — контакт-аффорданс
   → Onest (см. .xf-contacts-list a ниже, #91), не mono. */
.xf-contacts-list {
  margin: 4px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.xf-contacts-list li {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}
/* Телефон/почта на странице «Контакты» — контакт-действие, не data → Onest,
   не mono (точечный ноль JetBrains Mono читается как 8). См. #91. */
.xf-contacts-list a {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: 14.5px;
  color: var(--text);
  text-decoration: none;
  letter-spacing: -0.1px;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-contacts-list a:hover { color: var(--accent-2); }
}
.xf-contacts-list span {
  font-size: 12px;
  color: var(--text-3);
}

/* Карта — фул-ширина контейнера 960 с rounded-border. */
.xf-contacts-map {
  max-width: 960px;
  margin: 0 auto 48px;
  padding: 0 24px;
}
.xf-contacts-map iframe {
  display: block;
  width: 100%;
  height: 420px;
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  background: var(--surface-2);
}

/* Заглушка карты (#589): iframe Яндекса ставит app.js (initConsentEmbeds) —
   по согласию на «Аналитику» или по клику «Показать карту». До того на его
   месте этот блок; min-height = высота iframe, подстановка не сдвигает вёрстку.
   [hidden] прячет и саму заглушку, и кнопку до JS: display ниже и у .xf-btn
   перебили бы UA-правило. */
.xf-contacts-map [hidden] { display: none; }
.xf-contacts-map__gate {
  min-height: 420px;
  padding: 32px 24px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  text-align: center;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
}
.xf-contacts-map__icon {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-3);
  border-radius: 10px;
  color: var(--text-2);
}
.xf-contacts-map__addr {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  margin: 6px 0 0;
  text-wrap: balance;
}
.xf-contacts-map__note {
  max-width: 44ch;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-3);
  margin: 0;
  text-wrap: balance;
}
.xf-contacts-map__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 12px 24px;
  margin-top: 12px;
}
.xf-contacts-map__link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 14px;
  font-weight: 500;
  color: var(--accent-2);
  text-decoration: none;
}
@media (hover: hover) {
  .xf-contacts-map__link:hover { text-decoration: underline; text-underline-offset: 3px; }
}

@media (max-width: 768px) {
  .xf-contacts-grid { grid-template-columns: 1fr; }
  .xf-contacts-map iframe { height: 320px; }
  .xf-contacts-map__gate { min-height: 320px; padding: 24px 20px; }
}

/* ==========================================================================
   Help-page lede block + collapsible legal-text disclosure.
   .xf-help-lede — выделенный первый параграф (warranty: «куда срочно звонить»).
   .xf-help-disclosure — <details> для прятки длинного юридического текста под
   summary-клик, оставляя actionable summary над fold.
   ========================================================================== */

.xf-help-body > .xf-help-lede {
  padding: 16px 18px;
  background: var(--surface-2);
  border: 1px solid var(--accent-line);
  border-radius: 12px;
  margin: 0 0 32px;
  font-size: 14.5px;
  line-height: 1.55;
}

.xf-help-disclosure {
  max-width: 960px;
  margin: 0 auto 48px;
  padding: 0 24px;
}
.xf-help-disclosure > summary {
  list-style: none;
  cursor: pointer;
  padding: 18px 20px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  transition: border-color .15s, background .15s;
}
.xf-help-disclosure > summary::-webkit-details-marker { display: none; }
.xf-help-disclosure > summary::after {
  /* +/− chevron в Onest, не mono — это control, не data. Mono-as-Data scope. */
  content: "+";
  font-size: 20px;
  font-weight: 300;
  line-height: 1;
  color: var(--accent-2);
  transition: transform .2s;
}
.xf-help-disclosure[open] > summary::after {
  content: "−";
}
@media (hover: hover) {
  .xf-help-disclosure > summary:hover {
    border-color: var(--accent-line);
    background: oklch(from var(--surface-2) calc(l + 0.02) c h);
  }
}
.xf-help-disclosure__body {
  padding: 20px 4px 4px;
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--text-2);
}
.xf-help-disclosure__body p {
  margin: 0 0 14px;
}
.xf-help-disclosure__body strong {
  color: var(--text);
  font-weight: 600;
}
.xf-help-disclosure__body ol {
  margin: 0 0 14px 18px;
  padding: 0;
}
.xf-help-disclosure__body li {
  margin-bottom: 6px;
}
.xf-help-disclosure__body li::marker {
  color: var(--text-3);
}

/* ==========================================================================
   Override .xf-contacts-card inside .xf-help-body: убираем accent gradient с
   icon-tiles (Gradient-Restraint — иначе на одной payment-странице копится
   4 grad-иконки + active pill + footer CTA), и поднимаем surface до
   --surface-3 для надёжного Card-On-Cream delta в light theme.
   ========================================================================== */

.xf-help-body .xf-contacts-card {
  background: var(--surface-3);
}
.xf-help-body .xf-contacts-card__icon {
  background: var(--accent-dim);
  color: var(--accent-2);
}

/* ==========================================================================
   M19: Company subpages (staff/vacancy/reviews/licenses).
   Реюзают .xf-help-hero/-title/-eyebrow + .xf-help-body; добавляют свои
   карточные сетки. Single-Gradient / Tinted-Neutral / Mono-as-Data сохраняем.
   ========================================================================== */

/* ---------- Staff: grid карточек 1×3 (фиксированные 3 колонки) ---------- */
/* Раньше auto-fit minmax(260px, 1fr) — при росте до 4-7 сотрудников давало
   orphan rows (3+3+1 lonely). 3 фиксированных колонок + 1-col под 768 —
   стабильнее независимо от content count. */
.xf-staff-grid {
  max-width: 960px;
  margin: 0 auto 80px;
  padding: 0 24px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
@media (max-width: 768px) {
  .xf-staff-grid { grid-template-columns: 1fr; }
}

.xf-staff-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 24px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
}

.xf-staff-card__avatar {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  object-fit: cover;
  margin-bottom: 4px;
  background: var(--surface-3);
}
.xf-staff-card__avatar--placeholder {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-dim);
  color: var(--accent-2);
  font-family: var(--font-sans);
  font-size: 28px;
  font-weight: 600;
  text-transform: uppercase;
}

.xf-staff-card__name {
  font-family: var(--font-sans);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  margin: 4px 0 0;
}

.xf-staff-card__role {
  font-size: 13.5px;
  color: var(--text-3);
  margin: 0;
}

.xf-staff-card__contacts {
  list-style: none;
  padding: 0;
  margin: 8px 0 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Телефон/почта сотрудника — контакт-действие, не data → Onest, не mono
   (точечный ноль JetBrains Mono читается как 8). См. #91. */
.xf-staff-card__contacts a {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  color: var(--text-2);
  text-decoration: none;
  letter-spacing: -0.1px;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-staff-card__contacts a:hover { color: var(--accent-2); }
}

/* ---------- Back-link («← Все сотрудники») для detail-страниц ---------- */
.xf-back-link {
  max-width: 960px;
  margin: 0 auto 48px;
  padding: 0 24px;
  font-family: var(--font-mono);
  font-size: 13px;
}
.xf-back-link a {
  color: var(--accent-2);
  text-decoration: none;
}
@media (hover: hover) {
  .xf-back-link a:hover { color: var(--text); text-decoration: underline; }
}

/* ---------- Subtitle под H1 (например должность на staff-detail) ---------- */
.xf-help-hero .xf-help-subtitle {
  font-size: 15px;
  color: var(--text-3);
  margin: 8px 0 0;
}

/* ---------- Licenses: грид документов с секционными заголовками ---------- */
.xf-license-grid {
  max-width: 960px;
  margin: 0 auto 64px;
  padding: 0 24px;
}
.xf-license-section {
  margin-bottom: 40px;
}
.xf-license-section:last-child { margin-bottom: 0; }
.xf-license-section__title {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.3px;
  color: var(--text);
  margin: 0 0 20px;
}
.xf-license-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}
/* <figure>, не <a>: detail-страницы лицензий на проде 404 (broken Bitrix SEF),
   поэтому карточка чисто декларативная — image + caption, без click-through. */
.xf-license-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px;
  margin: 0;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  color: var(--text);
}
/* contain — документы разной пропорции (квадрат-сертификат, вертикаль-разрешение),
   crop бы исказил содержимое. Padding 8px ВНУТРИ contain давал double letterbox
   (outer от contain + inner от padding) — убран. Outer контейнер уже даёт
   surface-3 фон, документ читаемо «лежит на плане». */
.xf-license-card__thumb {
  width: 100%;
  height: 240px;
  object-fit: contain;
  background: var(--surface-3);
  border-radius: 8px;
  display: block;
}
.xf-license-card__title {
  font-family: var(--font-sans);
  font-size: 13.5px;
  line-height: 1.4;
  font-weight: 500;
  color: var(--text);
  margin: 0;
}

/* ---------- License detail: большое фото ---------- */
.xf-license-detail {
  max-width: 720px;
  margin: 0 auto 48px;
  padding: 0 24px;
  text-align: center;
}
.xf-license-detail__img {
  max-width: 100%;
  height: auto;
  border-radius: 14px;
  border: 1px solid var(--line-soft);
  background: var(--surface-2);
}

/* ---------- Vacancy: стек карточек на 720 (как .xf-help-body) ---------- */
/* gap 24px (не 16) — даёт визуальный воздух между карточками при росте
   inline DETAIL_TEXT (вакансии могут быть 50+ строк job-spec). При gap 16
   сливались бы в одну стену текста. */
.xf-vacancy-list {
  max-width: 720px;
  margin: 0 auto 80px;
  padding: 0 24px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}
.xf-vacancy-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 24px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
}
.xf-vacancy-card__header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.xf-vacancy-card__title {
  font-family: var(--font-sans);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  margin: 0;
}
/* Pay-pill: mono-as-data + accent-dim chip. Не gradient — Gradient-Restraint. */
.xf-vacancy-card__pay {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--accent-2);
  background: var(--accent-dim);
  padding: 4px 10px;
  border-radius: 999px;
  white-space: nowrap;
}
.xf-vacancy-card__preview {
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-2);
  margin: 0;
}
.xf-vacancy-card__preview p { margin: 0 0 8px; }
.xf-vacancy-card__preview p:last-child { margin: 0; }
/* ---------- Empty-CTA (reviews пусто, vacancy пусто) ---------- */
.xf-empty-cta {
  max-width: 720px;
  margin: 0 auto 80px;
  padding: 36px 28px;
  text-align: center;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
}
.xf-empty-cta__title {
  font-family: var(--font-sans);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  margin: 0 0 8px;
}
.xf-empty-cta__desc {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-3);
  margin: 0 0 20px;
}
.xf-empty-cta__btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 22px;
  background: var(--accent);
  color: #fff;
  text-decoration: none;
  border-radius: 10px;
  font-size: 14px;
  font-weight: 600;
  transition: transform .15s, box-shadow .15s;
}
/* Тёмная (#857): блик по кромке вместо цветной тени — как у .xf-help-cta__btn. */
html[data-theme="dark"] .xf-empty-cta__btn { box-shadow: var(--nx-key); }
@media (hover: hover) {
  .xf-empty-cta__btn:hover {
    color: #fff;
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
    box-shadow: 0 8px 24px -8px oklch(0.55 0.2 var(--accent-h) / 0.5);
  }
}

/* ---------- Reviews (когда контент появится) ---------- */
.xf-review-list {
  max-width: 720px;
  margin: 0 auto 64px;
  padding: 0 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.xf-review-card {
  display: grid;
  grid-template-columns: 56px 1fr;
  gap: 16px;
  padding: 20px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
}
/* Avatar — neutral surface, не accent. При populated state 5+ отзывов
   accent-dim круги набирались бы в >10% поверхности индиго (нарушает
   One-Voice Indigo Rule). surface-3 — нейтральный, безопасный. */
.xf-review-card__avatar {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--surface-3);
}
.xf-review-card__author {
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  margin: 0;
}
.xf-review-card__meta {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
  margin: 2px 0 8px;
}
.xf-review-card__body {
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-2);
  margin: 0;
}
.xf-review-card__body p:first-child { margin-top: 0; }
.xf-review-card__body p:last-child { margin-bottom: 0; }

/* Plain «Хотите тоже поделиться?» link под review list — заменяет второй
   gradient CTA (нарушал Single-Gradient + One-Voice Indigo на populated
   state). Empty-state CTA остаётся gradient'ом — он там один на странице. */
.xf-review-add {
  max-width: 720px;
  margin: 24px auto 64px;
  padding: 0 24px;
  text-align: center;
  font-size: 14px;
  color: var(--text-3);
}
.xf-review-add a {
  margin-left: 4px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--accent-2);
  text-decoration: none;
}
@media (hover: hover) {
  .xf-review-add a:hover { color: var(--text); text-decoration: underline; }
}

/* ---------- Отзывы с площадок (#277): /company/reviews/ ----------
   Слева — площадки (Onliner с живой цифрой, Яндекс, Google), справа — блок
   отзывов Яндекса за заглушкой. Одна панель со строками, а не три одинаковые
   карточки: у площадок разный объём данных, и это видно. Под 900px — колонкой.
   Края: hero, вводный абзац, колонки и CTA стоят на одной линии — общее
   правило инфо-шелла («Колонка инфо-страницы» в начале файла), здесь его
   впервые и нашли (#277).
   Ширины текста — в px, не в ch: ch считается от шрифта, и при подмене
   системного шрифта на Onest строки переламывались бы заново — блок прыгал
   на 26px (замер layout-shift, #277). */

.xf-review-layout {
  max-width: 960px;
  margin: 0 auto 48px;
  padding: 0 24px;
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: 32px;
  align-items: start;
}
.xf-review-layout__title {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.3px;
  color: var(--text);
  margin: 0 0 16px;
}

.xf-review-sources__list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
}
.xf-review-src {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 20px 22px 14px;
  border-bottom: 1px solid var(--line-soft);
}
.xf-review-src:last-child { border-bottom: 0; }
.xf-review-src__name {
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
  margin: 0;
}
/* Две цифры Onliner равного веса, у каждой — своя подпись срока (рейтинг — за
   год, число — за всё время). Цифра — данные → mono (Mono-as-Data), размер
   Numeric --lg: это строка площадки, не «герой-метрика». В DOM подпись идёт
   первой (dt → dd, так её и прочтёт скринридер), на экране цифра над подписью. */
.xf-review-src__facts {
  align-self: stretch;
  display: flex;
  flex-wrap: wrap;
  gap: 12px 24px;
  margin: 6px 0 2px;
}
.xf-review-src__fact {
  flex: 1 1 120px;
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-end;
  gap: 6px;
}
.xf-review-src__fact dt {
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--text-2);
}
.xf-review-src__value {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.3px;
  line-height: 1;
  color: var(--text);
}
/* Запятая в mono занимает целую клетку — «5 ,0»; поджимаем. */
.xf-review-src__value--rating { letter-spacing: -0.08em; }
.xf-review-src__desc {
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-2);
  margin: 0;
}
.xf-review-src__stamp {
  font-size: 12px;
  color: var(--text-3);
  margin: 0;
}
.xf-review-src__stamp time { font-family: var(--font-mono); font-size: 11.5px; }
/* padding-block — тач-цель ≥30px при строке 18px (текстовая ссылка на телефоне). */
.xf-review-src__link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding-block: 6px;
  font-size: 14px;
  font-weight: 500;
  color: var(--accent-2);
  text-decoration: none;
}
@media (hover: hover) {
  .xf-review-src__link:hover { text-decoration: underline; text-underline-offset: 3px; }
}

/* Блок Яндекса: iframe ставит app.js (initConsentEmbeds) — по согласию на
   «Аналитику» или по клику. Сам iframe и его содержимое — Яндекса, не трогаем
   (условия API Карт п. 5.1.1–5.1.2); наше — только рамка вокруг. Высота 800 —
   размер из кода «Поделиться → Виджет с отзывами»; список отзывов внутри
   Яндекс не прокручивает, так что меньше — значит меньше видимых отзывов.
   Заглушка компактная; высоту iframe она держит заранее только там, где блок
   встанет сам при загрузке — [data-xf-embed-auto] ставит инлайн-скрипт в
   news.php по согласию из cookie. По клику сдвиг — ответ на действие
   посетителя, CLS его не считает.
   [hidden] — как у карты «Контактов»: display ниже и у .xf-btn перебили бы UA-правило. */
.xf-review-ya [hidden] { display: none; }
.xf-review-ya > iframe {
  display: block;
  width: 100%;
  height: 800px;
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  background: var(--surface);
}
.xf-review-ya__gate {
  padding: 36px 28px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  text-align: center;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
}
.xf-review-ya[data-xf-embed-auto] > .xf-review-ya__gate { min-height: 800px; }
.xf-review-ya__icon {
  width: 40px;
  height: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-3);
  border-radius: 10px;
  color: var(--text-2);
}
.xf-review-ya__lead {
  max-width: 380px;
  font-size: 16px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--text);
  margin: 6px 0 0;
  text-wrap: balance;
}
.xf-review-ya__note {
  max-width: 360px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-3);
  margin: 0;
  text-wrap: balance;
}
.xf-review-ya__actions {
  display: flex;
  justify-content: center;
  max-width: 100%;
  margin-top: 12px;
}
/* На 320px подпись кнопки длиннее колонки — переносим, а не вылезаем за рамку. */
.xf-review-ya__actions .xf-btn {
  max-width: 100%;
  white-space: normal;
  text-align: center;
}
.xf-review-ya__foot { margin: 8px 0 0; }

.xf-review-cta .xf-help-cta__desc { max-width: 560px; }
.xf-review-cta .xf-help-cta__desc a {
  color: var(--accent-2);
  text-decoration: underline;
  text-underline-offset: 3px;
}

@media (max-width: 900px) {
  .xf-review-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 40px;
  }
  .xf-review-ya > iframe { height: 720px; }
  .xf-review-ya[data-xf-embed-auto] > .xf-review-ya__gate { min-height: 720px; }
  .xf-review-ya__gate { padding: 28px 20px; }
}
/* Уже 340px колонка тоньше минимума виджета Яндекса (300px) — iframe на всю
   ширину экрана, через тот же токен рельса, что full-bleed-полосы шаблона. */
@media (max-width: 340px) {
  .xf-review-ya > iframe {
    width: calc(100% + 2 * var(--container-pad));
    margin-inline: calc(var(--container-pad) * -1);
    border-radius: 0;
    border-inline: 0;
  }
}

/* ==========================================================================
   #19: «Вопросы и ответы» (/info/faq/) — аккордеон на нативных <details>.
   Разметка — include/info/faq.php. Живёт внутри .xf-help-body (колонка 720,
   рельс телефона и типографика ответа — оттуда). Группа — список с волосяными
   разделителями, а не стопка отдельных плашек: двадцать одинаковых карточек
   подряд читаются как сетка-клон (DESIGN.md, Don't). Клавиатура и скринридер —
   нативные: summary фокусируется, Enter/Space раскрывает, кольцо фокуса —
   глобальное :focus-visible (ui.css), своего не пишем. Движения нет: плюс
   превращается в минус сменой прозрачности, не поворотом.
   Специфичность .xf-faq .x (0-2-0) нужна, чтобы перебить `.xf-help-body div`
   (margin 14px) и `.xf-help-body a` (цвет и подчёркивание ссылок прозы).
   ========================================================================== */
.xf-faq .xf-faq-group { margin: 0 0 40px; }
.xf-faq .xf-faq-group:last-child { margin-bottom: 0; }
.xf-faq .xf-faq-group__desc { margin: -4px 0 16px; color: var(--text-3); font-size: 14px; }
.xf-faq .xf-faq-group__desc > :last-child { margin-bottom: 0; }

.xf-faq .xf-faq-list {
  margin: 0;
  border-top: 1px solid var(--line);
}
.xf-faq .xf-faq-item {
  margin: 0;
  border-bottom: 1px solid var(--line);
  /* Якорь /info/faq/#faq-… не уезжает под липкую шапку (как #content, header.css). */
  scroll-margin-top: calc(var(--xf-header-h, 154px) + 8px);
}
.xf-faq-item__q {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 0;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.45;
  letter-spacing: -0.1px;
  color: var(--text);
  transition: color .15s;
}
.xf-faq-item__q::-webkit-details-marker { display: none; }
.xf-faq-item__icon {
  flex: 0 0 16px;
  /* на высоту первой строки вопроса: (16 × 1.45 − 16) / 2 */
  margin-top: 3.6px;
  fill: none;
  stroke: var(--text-3);
  stroke-width: 1.6;
  stroke-linecap: round;
  transition: stroke .15s;
}
.xf-faq-item__icon-v { transition: opacity .15s; }
.xf-faq-item[open] > .xf-faq-item__q .xf-faq-item__icon-v { opacity: 0; }
/* Индиго — только значок (открытый вопрос и наведение): текст вопроса остаётся
   чернилами, иначе на экране копится акцент (One-Voice Indigo). */
.xf-faq-item[open] > .xf-faq-item__q .xf-faq-item__icon { stroke: var(--accent-2); }
@media (hover: hover) {
  .xf-faq-item__q:hover .xf-faq-item__icon { stroke: var(--accent-2); }
}

/* Ответ: проза .xf-help-body (p/ul/ol/strong/a), правый отступ — под иконку,
   чтобы текст не заходил под колонку плюса и край ответа совпадал с вопросом. */
.xf-faq .xf-faq-item__a {
  margin: -6px 0 0;
  padding: 0 32px 20px 0;
}
.xf-faq .xf-faq-item__a > :last-child { margin-bottom: 0; }
.xf-faq .xf-faq-item__action { margin: 16px 0 0; }
/* `.xf-help-body a` перебивает у кнопки цвет, подчёркивание и transition —
   возвращаем их. Подпись кнопки пишут в админке: длинная переносится, а не
   вылезает за колонку на 320px (как у кнопки Яндекса на «Отзывах»). */
.xf-faq .xf-faq-item__action .xf-btn {
  color: var(--text);
  text-decoration: none;
  transition: transform .15s, box-shadow .15s, background .15s, border-color .15s;
  max-width: 100%;
  white-space: normal;
  text-align: left;
}

@media (max-width: 767px) {
  .xf-faq .xf-faq-item { scroll-margin-top: calc(var(--xf-header-h, 118px) + 8px); }
  .xf-faq-item__q { padding: 16px 0; }
  .xf-faq .xf-faq-item__a { padding-right: 0; }
}

/* ---------- Колонка инфо-страницы на телефоне: рельс контейнера ----------
   Правило — в начале файла («Колонка инфо-страницы»). Блок стоит последним
   в файле, чтобы базовые `padding: 0 24px` блоков выше не перебили его по
   каскаду. Новый блок инфо-шелла со своим `max-width` + `padding: 0 24px`
   добавляй и сюда — иначе на телефоне он встанет на 40px, а соседи на 16px. */
@media (max-width: 767px) {
  .xf-help-hero,
  .xf-help-body,
  .xf-help-hub,
  .xf-help-disclosure,
  .xf-about-values,
  .xf-about-block,
  .xf-contacts-grid,
  .xf-contacts-map,
  .xf-staff-grid,
  .xf-back-link,
  .xf-license-grid,
  .xf-license-detail,
  .xf-vacancy-list,
  .xf-review-list,
  .xf-review-add,
  .xf-review-layout { padding-inline: 0; }
  .xf-help-cta { width: auto; }
}

/* End */


/* Start:/local/templates/xcore_front/css/form.css?179264521811903*/
/* OWNS: .xf-form-*, .xf-contact-strip-*, .xf-help-body--form (form-page modifier of help-body) */
/* ==========================================================================
   form.css — M14: форма Обращений (bitrix:form.inline / form.result.new).

   Скоуп в `.xf-help-body--form` (обёртка из header.php) и `.xf-form-wrap`
   (локальная обёртка над компонентом). CSS не трогает структуру разметки
   от aspro-forked template — только перекрашивает/переформатирует output.
   BS3 grid `.col-md-7/5` нейтрализован в одну колонку (720px слишком узко
   для сплита).
   ========================================================================== */

/* ---------- Preamble + Contact Strip ---------- */

.xf-form-intro {
  margin: 0 0 24px;
  color: var(--text-2);
  font-size: 15px;
  line-height: 1.7;
}
.xf-form-intro b,
.xf-form-intro strong {
  color: var(--text);
  font-weight: 600;
}

.xf-contact-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 28px;
  align-items: baseline;
  margin: 0 0 32px;
  padding: 16px 20px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
}
.xf-contact-strip__item {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  color: var(--text);
  text-decoration: none;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-contact-strip__item:hover {
  color: var(--accent-2);
}
}
.xf-contact-strip__label {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-3);
}
/* Номер телефона в полоске контактов — контакт-действие, не data → Onest,
   не mono (точечный ноль JetBrains Mono читается как 8). __label (eyebrow)
   остаётся mono. См. #91. */
.xf-contact-strip__value {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  font-size: 13.5px;
  letter-spacing: -0.2px;
}
.xf-contact-strip__hours {
  margin-left: auto;
  font-size: 12.5px;
  color: var(--text-3);
}

/* ---------- Form wrapper overrides ---------- */
/* bitrix:form рисует `.maxwidth-theme > .form.inline.SID > .form_head/.form_body/.form_footer`
   с col-md грида. Отключаем это всё — сайт не несёт BS3, показываем в одну колонку. */

.xf-form-wrap .maxwidth-theme {
  max-width: none;
  padding: 0;
}
.xf-form-wrap .form.inline {
  display: block;
}
.xf-form-wrap .form_body,
.xf-form-wrap .form_footer {
  display: block;
}
.xf-form-wrap .row {
  display: block;
  margin: 0;
}
.xf-form-wrap .col-md-7,
.xf-form-wrap .col-md-5 {
  width: 100%;
  padding: 0;
  float: none;
}
.xf-form-wrap .clearboth {
  display: none;
}
.xf-form-wrap .form_head {
  display: none; /* заголовок страницы уже есть в hero */
}

/* ---------- Fields: label + input wrapper ---------- */
.xf-form-wrap .form-control {
  display: block;
  margin: 0 0 16px;
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
  height: auto;
}
.xf-form-wrap .form-control label {
  display: block;
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: -0.1px;
  color: var(--text-2);
}
.xf-form-wrap .form-control label .star,
.xf-form-wrap .form-control label .required-star {
  color: var(--accent-2);
  margin-left: 2px;
}

/* ---------- Inputs / textarea ---------- */
.xf-form-wrap input[type="text"],
.xf-form-wrap input[type="tel"],
.xf-form-wrap input[type="email"],
.xf-form-wrap textarea {
  display: block;
  width: 100%;
  padding: 11px 14px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14.5px;
  line-height: 1.5;
  box-shadow: none;
  transition: border-color .15s, background .15s;
}
.xf-form-wrap textarea {
  min-height: 120px;
  resize: vertical;
}
/* [#830] Телефон: поля формы 16 px — на поле мельче iPhone увеличивает
   страницу при фокусе и оставляет её увеличенной (форма обращений). */
@media (max-width: 767px) {
  .xf-form-wrap input[type="text"],
  .xf-form-wrap input[type="tel"],
  .xf-form-wrap input[type="email"],
  .xf-form-wrap textarea { font-size: 16px; }
}
/* [#406] Рамка --accent-line ниже — не индикатор фокуса (1.55:1 при норме
   WCAG 1.4.11 = 3:1), индикатор это глобальное кольцо ui.css. Не гасить. */
.xf-form-wrap input[type="text"]:focus,
.xf-form-wrap input[type="tel"]:focus,
.xf-form-wrap input[type="email"]:focus,
.xf-form-wrap textarea:focus {
  border-color: var(--accent-line);
  background: oklch(from var(--surface) calc(l + 0.02) c h);
}
.xf-form-wrap input::placeholder,
.xf-form-wrap textarea::placeholder {
  color: var(--text-3);
}

/* ---------- Captcha ---------- */
/* Кластер капчи — grid в одну max-content-колонку: подпись / картинка+reload /
   инпут. Инпут растягивается ровно на ширину ряда картинки — края совпадают
   на любой ширине экрана и при любом размере серверной картинки. */
.xf-form-wrap .captcha-row {
  display: grid;
  grid-template-columns: max-content;
  justify-content: start;
  gap: 8px;
  margin: 6px 0 18px;
}
.xf-form-wrap .captcha-row > label {
  margin: 0;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
}
.xf-form-wrap .captcha_image {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
}
/* Белая плашка остаётся белой и в dark: капча — функциональный гейт, invert
   серверной картинки рискует читаемостью (жест .xf-art--photo, Light-Is-Canonical). */
.xf-form-wrap .captcha_image img {
  display: block;
  height: 40px;
  border-radius: 10px; /* как у инпутов формы — кластер единым радиусом */
  border: 1px solid var(--line-soft);
  background: #fff;
}
/* Кнопка «обновить картинку». Прод-aspro её рисует спрайтом + main.js; у нас —
   токены + inline-SVG + handler [data-xf-captcha-reload] в app.js. */
.xf-form-wrap .captcha_reload {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--line-soft);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text-3);
  cursor: pointer;
  transition: color .15s ease, border-color .15s ease;
}
.xf-form-wrap .captcha_reload:hover {
  color: var(--accent);
  border-color: var(--accent-line);
}
.xf-form-wrap .captcha_reload svg {
  display: block;
}
.xf-form-wrap .captcha_reload.loading {
  pointer-events: none;
  color: var(--text-3);
}
.xf-form-wrap .captcha_reload.loading svg {
  animation: xf-captcha-spin .6s linear infinite;
}
@keyframes xf-captcha-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .xf-form-wrap .captcha_reload.loading svg { animation: none; }
}
.xf-form-wrap input.captcha {
  display: block;
  width: 100%;
  height: 40px;
  padding-top: 8px;
  padding-bottom: 8px;
}

/* ---------- License / consent ---------- */
.xf-form-wrap .licence_block {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 8px 0 20px;
  padding: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-3);
}
.xf-form-wrap .licence_block input[type="checkbox"] {
  margin: 3px 0 0;
  flex-shrink: 0;
  width: 16px;
  height: 16px;
  accent-color: var(--accent-2);
  cursor: pointer;
}
.xf-form-wrap .licence_block label {
  flex: 1;
  margin: 0;
  cursor: pointer;
  color: var(--text-3);
  font-size: 12.5px;
  font-weight: 400;
}
.xf-form-wrap .licence_block label a {
  color: var(--accent-2);
  text-decoration: underline;
  text-decoration-color: var(--link-line);
  text-underline-offset: 2px;
}
@media (hover: hover) {
  .xf-form-wrap .licence_block label a:hover {
  text-decoration-color: var(--accent-2);
}
}

/* ---------- Required-message hint + footer note rows ---------- */
.xf-form-wrap .form_footer > .form-control {
  margin: 10px 0 16px;
  font-size: 12.5px;
  color: var(--text-3);
}

/* ---------- Submit + Reset buttons ---------- */
.xf-form-wrap input[type="submit"],
.xf-form-wrap button[type="reset"] {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 12px 24px;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  border: 0;
  border-radius: 10px;
  cursor: pointer;
  transition: transform .15s, box-shadow .15s, border-color .15s, color .15s;
  box-shadow: none;
}
.xf-form-wrap input[type="submit"] {
  background: var(--accent);
  background-origin: border-box;
  background-clip: border-box;
  color: #fff;
}
/* Тёмная (#857): цветной тени у кнопок нет — блик по верхней кромке, как у
   .xf-btn (DESIGN.md, The Night-Sign Rule). 0-3-2 выше :hover ниже (0-3-1):
   на наведении тот же блик вместо тени на индиго 268. */
html[data-theme="dark"] .xf-form-wrap input[type="submit"] { box-shadow: var(--nx-key); }
@media (hover: hover) {
  .xf-form-wrap input[type="submit"]:hover {
  transform: translateY(calc(-1px * var(--xf-motion, 1)));
  box-shadow: 0 8px 24px -8px oklch(0.55 0.2 var(--accent-h) / 0.5);
  color: #fff;
}
}
.xf-form-wrap button[type="reset"] {
  background: transparent;
  border: 1px solid var(--line-soft);
  color: var(--text-2);
  margin-left: 8px;
}
@media (hover: hover) {
  .xf-form-wrap button[type="reset"]:hover {
  border-color: var(--accent-line);
  color: var(--text);
  background: transparent;
}
}

/* ---------- Form result banner (success / error) ---------- */
.xf-form-wrap .form_result {
  margin: 0 0 20px;
  padding: 14px 16px;
  border-radius: 10px;
  font-size: 14px;
  line-height: 1.5;
}
.xf-form-wrap .form_result.error {
  background: oklch(0.28 0.12 25 / 0.25);
  border: 1px solid oklch(0.55 0.2 25 / 0.4);
  color: oklch(0.88 0.1 25);
}
.xf-form-wrap .form_result.success {
  background: oklch(0.28 0.1 155 / 0.22);
  border: 1px solid oklch(0.55 0.15 155 / 0.4);
  color: oklch(0.88 0.08 155);
}

/* jQuery-validate field error label */
.xf-form-wrap label.error {
  display: block;
  margin: 4px 0 0;
  font-size: 12px;
  font-weight: 400;
  color: oklch(0.72 0.19 25);
}

@media (max-width: 560px) {
  .xf-contact-strip__hours { margin-left: 0; width: 100%; }
}

/* Сервисный ИИ-бот — контекстный канал на странице обращений (M31.1).
   Тихий callout: accent-dim тинт + accent-line (НЕ сплошной accent — тише
   глобального FAB, бережёт One-Voice Indigo). Бот = второй канал рядом с
   формой, не замена. Markup гейтнут в help/obrashcheniya.php. */
.xf-form-bot-cta {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin: 0 0 32px;
  padding: 14px 16px;
  background: var(--accent-dim);
  border: 1px solid var(--accent-line);
  border-radius: 12px;
  font-size: 14px;
  line-height: 1.55;
  color: var(--text-2);
}
.xf-form-bot-cta__icon { flex: 0 0 auto; color: var(--accent-2); margin-top: 2px; }
.xf-form-bot-cta a {
  color: var(--accent-2);
  font-weight: 600;
  text-decoration: none;
}
.xf-form-bot-cta a:hover { text-decoration: underline; }

/* End */


/* Start:/local/templates/xcore_front/css/blog.css?179265386619943*/
/* OWNS: .xf-blog-* */
/* ============================================================
 * blog.css — M18 blog templates (xcore_front)
 * Surfaces: list root, section list, article detail.
 * Pill row reuses .xf-pill-tabs / .xf-pill-tab from content.css.
 *
 * РЕЛЬС (#341). Блоки списка (hero, pills, featured, grid, pagination,
 * related) НЕ держат боковой рельс сами: они рендерятся внутри
 * `.wrapper_inner`, а он уже даёт и `max-width: var(--container)`, и
 * `margin: 0 auto`, и `padding: 0 var(--container-pad)` (layout.css).
 * Раньше каждый блок повторял этот контракт — писались как самостоятельные
 * страницы, а вставлены внутрь контейнера, — и рельс складывался вдвое:
 * 80px в полях вместо 40 на десктопе, 32 вместо 16 на телефоне. Блог
 * выглядел уже каталога и главной, ничем этого не заслужив.
 * Поэтому здесь у блоков ТОЛЬКО вертикальные margin'ы. Не возвращай им
 * max-width / margin auto / padding var(--container-pad): рельс — один, и
 * он живёт в контейнере. Ограничения МЕРЫ — другое дело, см. ниже.
 *
 * Тот же урок, третий раз (#350). На странице статьи недоснятыми остались
 * ещё два дубля контракта: у `.xf-blog-goods` была своя мера 1100px, а у
 * `.xf-blog-article` — боковой padding 24px. Обе работали как второй рельс:
 * на 1440 товары шли по 194px от края, «Похожие статьи» — по 40px; на
 * телефоне проза и товары стояли по 40px (16 рельс + 24 padding) против
 * 16px у related. Три меры и два рельса на одном экране. Снято: товары —
 * на общий рельс, у прозы гуттер свёрнут в саму меру.
 *
 * МЕРЫ, которые тут есть и должны быть (всё остальное — рельс контейнера):
 *   .xf-blog-hero__lede   640px — лид на обложке списка;
 *   .xf-blog-article      712px — мера прозы, ОДНИМ числом, без бокового
 *                                 padding'а (см. комментарий у правила);
 *   .xf-blog-empty        720px — центрированная заглушка пустого списка.
 * Страница статьи после этого держит ровно две меры: колонка прозы 712 и
 * рельс контейнера — на нём и товары, и «Похожие статьи».
 * `.xf-blog-goods__grid` при этом остаётся с лестницей колонок 4/3/2: на
 * узком рельсе она по-прежнему работает (944 → 3 колонки, а не 4 узкие).
 * ============================================================ */

/* ---------- Hero ---------- */
.xf-blog-hero {
  margin: 28px 0 24px;
}
.xf-blog-hero__eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
  margin: 0 0 10px;
}
.xf-blog-hero__title {
  font-family: var(--font-sans);
  font-size: clamp(30px, 4.4vw, 52px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0 0 12px;
}
.xf-blog-hero__lede {
  font-family: var(--font-sans);
  font-size: 16.5px;
  line-height: 1.55;
  color: var(--text-2);
  margin: 0;
  max-width: 640px;
}

/* ---------- Section pill row ---------- */
.xf-blog-pills {
  margin: 20px 0 32px;
}
.xf-blog-pill__count {
  font-family: var(--font-mono);
  font-size: 11px;
  margin-left: 7px;
  opacity: 0.7;
}
.xf-pill-tab--active .xf-blog-pill__count {
  opacity: 0.85;
}

/* ---------- Featured card ---------- */
.xf-blog-featured {
  margin: 0 0 36px;
  display: block;
  text-decoration: none;
  color: inherit;
}
.xf-blog-featured__inner {
  display: grid;
  grid-template-columns: 1.15fr 1fr;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: calc(var(--radius) + 4px);
  overflow: hidden;
  transition: border-color .2s;
}
@media (max-width: 820px) {
  .xf-blog-featured__inner { grid-template-columns: 1fr; }
}
@media (hover: hover) {
  .xf-blog-featured:hover .xf-blog-featured__inner {
    border-color: var(--accent-line);
  }
  .xf-blog-featured:hover .xf-blog-featured__cta {
    color: var(--accent-2);
  }
}
.xf-blog-featured__image {
  aspect-ratio: 16 / 10;
  background-color: var(--surface-3);
  background-size: cover;
  background-position: center;
  position: relative;
}
.xf-blog-featured__image::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, transparent 50%, oklch(0 0 0 / 0.04) 100%);
  pointer-events: none;
}
.xf-blog-featured__image--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-3);
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.xf-blog-featured__body {
  padding: clamp(22px, 3.4vw, 38px);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 12px;
}
.xf-blog-featured__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
}
.xf-blog-featured__date { letter-spacing: 0.02em; }
.xf-blog-featured__tag {
  background: var(--surface-3);
  border-radius: 99px;
  padding: 3px 10px;
  color: var(--text-2);
  font-family: var(--font-sans);
  font-size: 11.5px;
}
.xf-blog-featured__title {
  font-family: var(--font-sans);
  font-size: clamp(22px, 2.8vw, 30px);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0;
}
.xf-blog-featured__lede {
  font-family: var(--font-sans);
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--text-2);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.xf-blog-featured__cta {
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  margin-top: 4px;
  transition: color .15s;
}

/* ---------- Card grid ---------- */
.xf-blog-grid {
  margin: 0 0 48px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
}
@media (max-width: 1024px) { .xf-blog-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px)  { .xf-blog-grid { grid-template-columns: 1fr; gap: 18px; } }

.xf-blog-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: border-color .2s;
}
@media (hover: hover) {
  .xf-blog-card:hover { border-color: var(--accent-line); }
  .xf-blog-card:hover .xf-blog-card__title { color: var(--accent-2); }
}
.xf-blog-card__image {
  aspect-ratio: 16 / 10;
  background-color: var(--surface-3);
  background-size: cover;
  background-position: center;
}
.xf-blog-card__image--empty {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-3);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.xf-blog-card__body {
  padding: 18px 20px 22px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
}
.xf-blog-card__meta {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-3);
  letter-spacing: 0.02em;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  align-items: center;
}
.xf-blog-card__tag {
  background: var(--surface-3);
  border-radius: 99px;
  padding: 2px 9px;
  color: var(--text-2);
  font-family: var(--font-sans);
  font-size: 11px;
}
.xf-blog-card__title {
  font-family: var(--font-sans);
  font-size: 17px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--text);
  margin: 0;
  transition: color .15s;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.xf-blog-card__lede {
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.5;
  color: var(--text-2);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- Pagination ---------- */
.xf-blog-pagination {
  margin: 0 0 72px;
  display: flex;
  justify-content: center;
  gap: 6px;
  flex-wrap: wrap;
}
.xf-blog-pagination a,
.xf-blog-pagination span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding: 0 12px;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-2);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: border-color .15s, color .15s, background .15s;
}
.xf-blog-pagination a:hover {
  border-color: var(--accent-line);
  color: var(--text);
}
.xf-blog-pagination__current {
  background: var(--accent);
  color: #fff;
  border-color: transparent;
}
.xf-blog-pagination__gap {
  border: 0 !important;
  background: transparent !important;
  color: var(--text-3) !important;
}

/* ---------- Empty state ---------- */
.xf-blog-empty {
  max-width: 720px;
  margin: 40px auto 80px;
  padding: 60px 32px;
  text-align: center;
  border: 1px dashed var(--line-soft);
  border-radius: var(--radius);
  color: var(--text-3);
  font-family: var(--font-sans);
  font-size: 15px;
}

/* ============================================================
 * Article (detail page)
 * ============================================================ */

/* [#350] Мера прозы — 712px, и это ровно та мера, что рендерилась всегда:
   было `max-width: 760px` + `padding: 0 24px` при border-box, то есть 760 − 48.
   Боковой padding убран, потому что он был вторым рельсом: пока бокс шире
   вьюпорта (планшет и телефон), проза уезжала на 24px внутрь от рельса
   контейнера, и «Похожие статьи» под ней шли по 16px, а проза — по 40px.
   Теперь на широком экране бокс центрируется и даёт те же 712px (проверено:
   на 1440 и 1024 рельс прозы и её ширина не изменились ни на пиксель), а как
   только контейнер становится уже 712 — проза садится ровно на его рельс.
   Медиа-запрос для этого не нужен: одна мера вместо «мера + гуттер» держит
   оба случая сама. 712px = ~72ch на 16px/1.7 — в пределах 65–75ch (DESIGN.md). */
.xf-blog-article {
  max-width: 712px;
  margin: 0 auto 40px;
  padding: 16px 0 0;
}
.xf-blog-article__crumbs {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-3);
  letter-spacing: 0.02em;
  margin: 0 0 22px;
}
.xf-blog-article__crumbs a {
  color: var(--text-2);
  text-decoration: none;
  transition: color .15s;
}
.xf-blog-article__crumbs a:hover { color: var(--accent-2); }
.xf-blog-article__crumbs-sep { margin: 0 8px; color: var(--text-3); }

.xf-blog-article__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin: 0 0 14px;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
}
.xf-blog-article__date { letter-spacing: 0.02em; }
.xf-blog-article__tag {
  background: var(--surface-3);
  border-radius: 99px;
  padding: 3px 10px;
  color: var(--text-2);
  font-family: var(--font-sans);
  font-size: 11.5px;
}
.xf-blog-article__title {
  font-family: var(--font-sans);
  font-size: clamp(28px, 4vw, 42px);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0 0 26px;
}
.xf-blog-article__hero {
  margin: 0 0 28px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-3);
  aspect-ratio: 16 / 9;
}
.xf-blog-article__hero img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.xf-blog-article__body {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.7;
  color: var(--text-2);
}
.xf-blog-article__body p,
.xf-blog-article__body > div { margin: 0 0 14px; }
.xf-blog-article__body p:empty,
.xf-blog-article__body div:empty,
.xf-blog-article__body p > br:only-child { margin: 0; }
.xf-blog-article__body ul,
.xf-blog-article__body ol {
  margin: 0 0 18px;
  padding-left: 24px;
}
.xf-blog-article__body li { margin: 0 0 6px; padding-left: 2px; }
.xf-blog-article__body li::marker { color: var(--accent-2); }
.xf-blog-article__body h2 {
  font-family: var(--font-sans);
  font-size: 24px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 36px 0 14px;
}
.xf-blog-article__body h3 {
  font-family: var(--font-sans);
  font-size: 19px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--text);
  margin: 28px 0 10px;
}
.xf-blog-article__body img {
  max-width: 100%;
  height: auto;
  border-radius: 10px;
  margin: 18px 0;
  display: block;
}
.xf-blog-article__body a {
  color: var(--accent-2);
  text-decoration: underline;
  text-decoration-color: var(--link-line);
  text-underline-offset: 3px;
  transition: color .15s, text-decoration-color .15s;
}
.xf-blog-article__body a:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}
/* Тёмная (#858): --accent текстом на фоне статьи — 3.9:1. Наведение как у
   ссылок в тексте справки и лендинга: текст --text, подчёркивание из линии
   акцента становится --accent-2. */
html[data-theme="dark"] .xf-blog-article__body a:hover {
  color: var(--text);
  text-decoration-color: var(--accent-2);
}
.xf-blog-article__body b,
.xf-blog-article__body strong { color: var(--text); font-weight: 600; }
.xf-blog-article__body blockquote {
  border-left: 3px solid var(--accent-line);
  margin: 18px 0;
  padding: 6px 0 6px 18px;
  color: var(--text-2);
  font-style: italic;
}

/* WYSIWYG defang: legacy admin-authored DETAIL_TEXT часто содержит layout-tables
   и broken/renamed image refs (cyrillic-filename → 404). Template не может
   починить контент, но обязан не дать ему сломать вёрстку. */
.xf-blog-article__body table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
  margin: 18px 0;
  border-collapse: collapse;
}
.xf-blog-article__body table td,
.xf-blog-article__body table th {
  padding: 6px 10px;
  border: 1px solid var(--line-soft);
  vertical-align: top;
  text-align: left;
  font-family: var(--font-sans);
  font-size: 14px;
}
.xf-blog-article__body table th { font-weight: 600; color: var(--text); }
/* Broken image — Bitrix WYSIWYG отдаёт alt = filename (часто кириллица типа
   «ьу2г.jpg»). Когда image грузится, alt не виден. Когда broken — alt будет
   показан стандартно браузером. Подменяем на нейтральный mono-placeholder,
   чтобы не торчал мусорный filename. */
.xf-blog-article__body img {
  background: var(--surface-3);
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-3);
  text-align: center;
  min-height: 24px;
}

/* ---------- Article: LINK_GOODS panel ---------- */
/* [#350] Панель товаров едет по общему рельсу контейнера — как «Похожие статьи»
   под ней. Своя мера 1100px + padding 24px были ничем не мотивированы: на 1440
   панель шла по 194px от края, related — по 40px, а на телефоне проза и товары
   стояли по 40px (16 рельс + 24 padding) против 16px у related. Три меры и два
   рельса на одном экране; ни одна из них ничего не ограничивала — это просто
   недоснятый дубль контракта контейнера (тот же случай, что #341 в шапке файла).
   Мера текста тут не нужна: карточки товаров — не проза. */
.xf-blog-goods {
  margin: 56px 0 60px;
}
.xf-blog-goods__title {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0 0 18px;
}
.xf-blog-goods__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
@media (max-width: 1024px) { .xf-blog-goods__grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 600px)  { .xf-blog-goods__grid { grid-template-columns: repeat(2, 1fr); } }

.xf-blog-goods__card {
  display: flex;
  flex-direction: column;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  padding: 14px;
  text-decoration: none;
  color: inherit;
  transition: border-color .15s;
}
.xf-blog-goods__card:hover { border-color: var(--accent-line); }
.xf-blog-goods__card-image {
  aspect-ratio: 1;
  background-color: var(--surface);
  border-radius: 8px;
  margin: 0 0 10px;
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
}
.xf-blog-goods__card-name {
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.35;
  color: var(--text);
  margin: 0 0 10px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(13px * 1.35 * 2);
}
.xf-blog-goods__card-price {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  margin-top: auto;
}
.xf-blog-goods__card-price--empty {
  color: var(--text-3);
  font-weight: 500;
}

/* ---------- Article: Related posts ---------- */
.xf-blog-related {
  margin: 0 0 72px;
}
.xf-blog-related__title {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0 0 20px;
}
.xf-blog-related__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
}
@media (max-width: 820px) { .xf-blog-related__grid { grid-template-columns: 1fr; } }

/* .xf-blog-comments (Comments wrapper) удалён в #350: 0 использований в
   разметке (grep по local/ — только этот файл), и он был третьим дублем
   рельс-контракта. Появятся комментарии — верстать от текущего рельса. */

/* #307 снимал это задвоение телефонным override'ом `padding-inline: 0` в конце
   файла. С #341 гасить нечего — дублирующий контракт убран из самих блоков
   (см. «РЕЛЬС» в шапке файла), и телефонная полоса получается тем же путём,
   что десктопная. Override удалён, чтобы не осталось правила, отменяющего то,
   чего больше нет. */

/* End */


/* Start:/local/templates/xcore_front/css/news.css?17926452352079*/
/* OWNS: .xf-news-* */
/* ============================================================
 * news.css — M18.2 news templates (xcore_front)
 * Base styles inherited from blog.css. Этот файл — только news-specific
 * additions (partner-badge, period-pill).
 * ============================================================ */

/* ---------- Partner badge ---------- */
/* Карточка с REDIRECT-prop ведёт на внешний сайт партнёра — показываем
   мини-маркер в meta-строке, чтобы пользователь видел что клик уйдёт наружу. */
.xf-news-partner-badge {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent-2);
  background: oklch(0.68 0.22 var(--accent-h) / 0.08);
  border: 1px solid oklch(0.68 0.22 var(--accent-h) / 0.18);
  border-radius: 99px;
  padding: 2px 9px;
  white-space: nowrap;
}
/* Тёмная (#857): литералы выше — индиго 268 светлой; пара — подложка и линия
   акцента темы, как у акцентной пилюли. */
html[data-theme="dark"] .xf-news-partner-badge {
  background: var(--accent-dim);
  border-color: var(--accent-line);
}

/* ---------- Period pill (на detail page, под h1) ---------- */
/* Срок действия акции из PROPERTY_PERIOD. */
.xf-news-period-pill {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  margin: 0 0 26px;
  padding: 8px 16px;
  border: 1px solid var(--line-soft);
  border-radius: 99px;
  background: var(--surface-2);
}
.xf-news-period-pill__label {
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-3);
}
.xf-news-period-pill__value {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text);
}

/* End */


/* Start:/local/templates/xcore_front/css/brands.css?179215482712037*/
/* OWNS: .xf-brand-page-*, .xf-brand-detail-*, .xf-brand-products-*
   NOT-OWNS: .xf-brand-* (→ home.css — brand marquee strip), .xf-brand-chip(s) (→ catalog.css — filter chip).
   Намеренный namespace split: «brand» как ВИДЖЕТ (logo, chip, marquee) живёт у surface,
   «brand-page»/«brand-detail» как СТРАНИЦА — здесь. */
/* ============================================================
 * brands.css — M21 brand listing + detail (/info/brands/ + /info/brands/<code>/)
 * Iblock 12 «Партнеры» через bitrix:news template "partners".
 * PHP: local/templates/xcore_front/components/bitrix/news/partners/{news,detail}.php
 * Listing: hero + grid карточек (logo + name + lede) + pagination.
 * Detail: crumbs + brand-hero (logo + site/phone) + body + linked products grid.
 * One-Voice-Indigo / Single-Gradient / Card-On-Cream / Mono-as-Data соблюдаем.
 * ============================================================ */

/* ---------- Listing hero ---------- */
.xf-brand-page-hero {
  max-width: 720px;
  margin: 0 auto;
  padding: 64px 24px 32px;
  text-align: center;
}
.xf-brand-page-hero__eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--accent-2);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 14px;
}
.xf-brand-page-hero__title {
  font-family: var(--font-sans);
  font-size: clamp(32px, 5vw, 44px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.05; /* Tight-Display */
  color: var(--text);
  margin: 0 0 16px;
}
.xf-brand-page-hero__lede {
  font-size: 16px;
  line-height: 1.55;
  color: var(--text-2);
  margin: 0;
}

/* ---------- Empty state (нет брендов) ---------- */
.xf-brand-page-empty {
  max-width: 720px;
  margin: 0 auto 80px;
  padding: 48px 24px;
  text-align: center;
  font-size: 15px;
  color: var(--text-3);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
}

/* ---------- Listing grid ---------- */
/* 4-col на десктопе — brand logos естественно «дышат» в широкой сетке. На планшете 3,
   на мобиле 2 (логотипы маленькие, в 1-col выглядят как list view, не как «бренды»). */
.xf-brand-page-grid {
  max-width: 1080px;
  margin: 0 auto 48px;
  padding: 0 24px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
}
@media (max-width: 960px) {
  .xf-brand-page-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 600px) {
  .xf-brand-page-grid { grid-template-columns: repeat(2, 1fr); gap: 14px; }
}

.xf-brand-page-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  overflow: hidden;
  transition: border-color .2s, transform .2s;
}
@media (hover: hover) {
  .xf-brand-page-card:hover {
    border-color: var(--accent-line);
    transform: translateY(calc(-2px * var(--xf-motion, 1)));
  }
  .xf-brand-page-card:hover .xf-brand-page-card__name { color: var(--accent-2); }
}
/* contain — у бренд-логотипов разный aspect ratio (квадрат, широкий, вертикаль).
   Заливаем equal-height container на surface-3 (neutral), чтобы лого «лежали»
   на полке независимо от своих пропорций. Padding внутри для воздуха. */
.xf-brand-page-card__logo {
  width: 100%;
  height: 120px;
  background-color: var(--surface-3);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  padding: 16px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Empty-state — первая буква бренда на accent-dim тонировке. */
.xf-brand-page-card__logo--empty {
  background-color: var(--accent-dim);
  color: var(--accent-2);
  font-family: var(--font-mono);
  font-size: 36px;
  font-weight: 600;
}
.xf-brand-page-card__body {
  padding: 14px 16px 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1;
}
.xf-brand-page-card__name {
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0;
  transition: color .15s;
}
.xf-brand-page-card__lede {
  font-size: 12.5px;
  line-height: 1.45;
  color: var(--text-3);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- Listing pagination ---------- */
.xf-brand-page-pagination {
  max-width: 1080px;
  margin: 0 auto 80px;
  padding: 0 24px;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 13px;
}
.xf-brand-page-pagination a,
.xf-brand-page-pagination__current {
  min-width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid transparent;
  color: var(--text-2);
  text-decoration: none;
  transition: border-color .15s, color .15s;
}
@media (hover: hover) {
  .xf-brand-page-pagination a:hover {
    border-color: var(--accent-line);
    color: var(--accent-2);
  }
}
.xf-brand-page-pagination__current {
  background: var(--accent-dim);
  color: var(--accent-2);
  font-weight: 600;
}
.xf-brand-page-pagination__gap {
  color: var(--text-3);
  padding: 0 4px;
}

/* ---------- Brand detail page ---------- */
.xf-brand-detail {
  max-width: 960px;
  margin: 0 auto 40px;
  padding: 32px 24px 0;
}
.xf-brand-detail__crumbs {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
  margin-bottom: 28px;
}
.xf-brand-detail__crumbs a {
  color: var(--text-3);
  text-decoration: none;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-brand-detail__crumbs a:hover { color: var(--accent-2); }
}
.xf-brand-detail__crumbs-sep {
  margin: 0 6px;
  opacity: 0.6;
}

/* Brand hero: logo слева, title+meta справа. Stack на mobile. */
.xf-brand-detail__head {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 32px;
  align-items: center;
  margin-bottom: 36px;
  padding-bottom: 32px;
  border-bottom: 1px solid var(--line-soft);
}
@media (max-width: 600px) {
  .xf-brand-detail__head {
    grid-template-columns: 1fr;
    gap: 20px;
    text-align: center;
  }
}
.xf-brand-detail__logo {
  width: 200px;
  height: 120px;
  background-color: var(--surface-2);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  padding: 16px;
  box-sizing: border-box;
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
}
@media (max-width: 600px) {
  .xf-brand-detail__logo { margin: 0 auto; }
}
.xf-brand-detail__logo--empty {
  background-color: var(--accent-dim);
  color: var(--accent-2);
  font-family: var(--font-mono);
  font-size: 56px;
  font-weight: 600;
}
.xf-brand-detail__heading {
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.xf-brand-detail__title {
  font-family: var(--font-sans);
  font-size: clamp(26px, 3.4vw, 36px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.1;
  color: var(--text);
  margin: 0;
}
.xf-brand-detail__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  font-family: var(--font-mono);
  font-size: 13px;
}
@media (max-width: 600px) {
  .xf-brand-detail__meta { justify-content: center; }
}
.xf-brand-detail__site {
  color: var(--accent-2);
  text-decoration: none;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-brand-detail__site:hover { color: var(--text); text-decoration: underline; }
}
/* Телефон бренда/партнёра — контакт, не data → Onest (перебивает mono,
   унаследованный от .xf-brand-detail__meta). Точечный ноль JBMono = 8. #91 */
.xf-brand-detail__phone {
  font-family: var(--font-sans);
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
  text-decoration: none;
}
@media (hover: hover) {
  .xf-brand-detail__phone:hover { color: var(--accent-2); }
}

/* Body — typographic для DETAIL_TEXT админовского HTML (та же логика что blog-article). */
.xf-brand-detail__body {
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--text-2);
  padding-bottom: 48px;
}
.xf-brand-detail__body h2 {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 32px 0 14px;
}
.xf-brand-detail__body h3 {
  font-family: var(--font-sans);
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
  margin: 24px 0 10px;
}
.xf-brand-detail__body p { margin: 0 0 14px; }
.xf-brand-detail__body p:last-child { margin-bottom: 0; }
.xf-brand-detail__body a {
  color: var(--accent-2);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.xf-brand-detail__body img {
  max-width: 100%;
  height: auto;
  border-radius: 10px;
  margin: 20px 0;
}
.xf-brand-detail__body ul,
.xf-brand-detail__body ol { margin: 0 0 14px; padding-left: 22px; }
.xf-brand-detail__body li { margin: 6px 0; }

/* ---------- Linked products (товары бренда) ---------- */
.xf-brand-products {
  max-width: 1080px;
  margin: 0 auto 80px;
  padding: 0 24px;
}
.xf-brand-products__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 22px;
}
.xf-brand-products__title {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0;
}
.xf-brand-products__all {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--accent-2);
  text-decoration: none;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-brand-products__all:hover { color: var(--text); text-decoration: underline; }
}
.xf-brand-products__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
@media (max-width: 960px) { .xf-brand-products__grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 600px) { .xf-brand-products__grid { grid-template-columns: repeat(2, 1fr); } }

.xf-brand-products__card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  text-decoration: none;
  color: inherit;
  transition: border-color .15s, transform .15s;
}
@media (hover: hover) {
  .xf-brand-products__card:hover {
    border-color: var(--accent-line);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
}
.xf-brand-products__card-image {
  width: 100%;
  aspect-ratio: 1;
  background-color: var(--surface-3);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 8px;
}
.xf-brand-products__card-name {
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.4;
  font-weight: 500;
  color: var(--text);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.xf-brand-products__card-price {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  margin-top: auto;
}
.xf-brand-products__card-price--empty {
  font-weight: 400;
  color: var(--text-3);
}

/* Empty-state когда у бренда нет привязанных товаров. */
.xf-brand-products--empty {
  text-align: center;
  padding: 40px 24px 80px;
}
.xf-brand-products__fallback {
  font-size: 14.5px;
  color: var(--text-3);
  margin: 0;
}
.xf-brand-products__fallback a {
  color: var(--accent-2);
  text-decoration: none;
  margin-left: 6px;
}
@media (hover: hover) {
  .xf-brand-products__fallback a:hover { color: var(--text); text-decoration: underline; }
}

/* End */


/* Start:/local/templates/xcore_front/css/projects.css?179215482710910*/
/* OWNS: .xf-project-* (портфолио проектов /projects/ — listing, section, detail)
   NOT-OWNS: .xf-back-link (→ content.css, shared). */
/* ============================================================
 * projects.css — #18 port портфолио проектов под xcore_front.
 * Iblock 16 «Проекты» через bitrix:news template "projects".
 * PHP: local/templates/xcore_front/components/bitrix/news/projects/{news,section,detail}.php
 * Listing: hero + грид фото-карточек + pagination.
 * Section: крошки + H1 категории + грид кейсов.
 * Detail: крошки + hero-фото + текст + галерея + «из чего собрано» (linked goods).
 * One-Voice-Indigo / Single-Gradient / Card-On-Cream / Mono-as-Data / Flat-By-Default.
 * Фото-ведомые карточки (cover-crop), в отличие от бренд-логотипов (contain).
 * ============================================================ */

/* ---------- Listing hero ---------- */
.xf-project-hero {
  max-width: 720px;
  margin: 0 auto;
  padding: 64px 24px 32px;
  text-align: center;
}
.xf-project-hero__eyebrow {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--accent-2);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-bottom: 14px;
}
.xf-project-hero__title {
  font-family: var(--font-sans);
  font-size: clamp(32px, 5vw, 44px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.05; /* Tight-Display */
  color: var(--text);
  margin: 0 0 16px;
}
.xf-project-hero__lede {
  font-size: 16px;
  line-height: 1.55;
  color: var(--text-2);
  margin: 0;
}

/* ---------- Empty state ---------- */
.xf-project-empty {
  max-width: 720px;
  margin: 0 auto 80px;
  padding: 48px 24px;
  text-align: center;
  font-size: 15px;
  color: var(--text-3);
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
}

/* ---------- Listing / section grid ---------- */
/* 3-col — проектные карточки богаче бренд-плиток (фото + заголовок + лид). */
.xf-project-grid {
  max-width: 1080px;
  margin: 0 auto 48px;
  padding: 0 24px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 22px;
}
@media (max-width: 900px) {
  .xf-project-grid { grid-template-columns: repeat(2, 1fr); gap: 18px; }
}
@media (max-width: 560px) {
  .xf-project-grid { grid-template-columns: 1fr; }
}

.xf-project-card {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  overflow: hidden;
  transition: border-color .2s, transform .2s;
}
@media (hover: hover) {
  .xf-project-card:hover {
    border-color: var(--accent-line);
    transform: translateY(calc(-2px * var(--xf-motion, 1)));
  }
  .xf-project-card:hover .xf-project-card__name { color: var(--accent-2); }
}
/* cover — фото сборки заполняет карточку (3:2). Neutral surface-3 под пустой. */
.xf-project-card__cover {
  width: 100%;
  aspect-ratio: 3 / 2;
  background-color: var(--surface-3);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  display: flex;
  align-items: center;
  justify-content: center;
}
.xf-project-card__cover--empty {
  background-color: var(--accent-dim);
  color: var(--accent-2);
  font-family: var(--font-mono);
  font-size: 44px;
  font-weight: 600;
}
.xf-project-card__body {
  padding: 16px 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
}
.xf-project-card__name {
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.3;
  color: var(--text);
  margin: 0;
  transition: color .15s;
}
.xf-project-card__lede {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-3);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---------- Listing pagination ---------- */
.xf-project-pagination {
  max-width: 1080px;
  margin: 0 auto 80px;
  padding: 0 24px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 13px;
}
.xf-project-pagination a,
.xf-project-pagination__current {
  min-width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  border-radius: 8px;
  border: 1px solid transparent;
  color: var(--text-2);
  text-decoration: none;
  transition: border-color .15s, color .15s;
}
@media (hover: hover) {
  .xf-project-pagination a:hover {
    border-color: var(--accent-line);
    color: var(--accent-2);
  }
}
.xf-project-pagination__current {
  background: var(--accent-dim);
  color: var(--accent-2);
  font-weight: 600;
}
.xf-project-pagination__gap {
  color: var(--text-3);
  padding: 0 4px;
}

/* ---------- Section head ---------- */
.xf-project-section-head {
  max-width: 1080px;
  margin: 0 auto;
  padding: 40px 24px 24px;
}
.xf-project-section-head__crumbs {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
  margin-bottom: 20px;
}
.xf-project-section-head__crumbs a {
  color: var(--text-3);
  text-decoration: none;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-project-section-head__crumbs a:hover { color: var(--accent-2); }
}
.xf-project-section-head__crumbs-sep {
  margin: 0 6px;
  opacity: 0.6;
}
.xf-project-section-head__title {
  font-family: var(--font-sans);
  font-size: clamp(28px, 4vw, 38px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.08;
  color: var(--text);
  margin: 0 0 12px;
}
.xf-project-section-head__lede {
  max-width: 680px;
  font-size: 15.5px;
  line-height: 1.55;
  color: var(--text-2);
  margin: 0;
}

/* ---------- Detail ---------- */
.xf-project-detail {
  max-width: 900px;
  margin: 0 auto;
  padding: 32px 24px 0;
}
.xf-project-detail__crumbs {
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-3);
  margin-bottom: 24px;
}
.xf-project-detail__crumbs a {
  color: var(--text-3);
  text-decoration: none;
  transition: color .15s;
}
@media (hover: hover) {
  .xf-project-detail__crumbs a:hover { color: var(--accent-2); }
}
.xf-project-detail__crumbs-sep {
  margin: 0 6px;
  opacity: 0.6;
}
.xf-project-detail__head {
  margin-bottom: 24px;
}
.xf-project-detail__title {
  font-family: var(--font-sans);
  font-size: clamp(28px, 3.6vw, 40px);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 1.08;
  color: var(--text);
  margin: 0;
}
.xf-project-detail__hero {
  margin: 0 0 32px;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid var(--line-soft);
}
.xf-project-detail__hero img {
  display: block;
  width: 100%;
  height: auto;
}

/* Body — typographic для DETAIL_TEXT (та же логика что brand-detail body). */
.xf-project-detail__body {
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--text-2);
  padding-bottom: 40px;
}
.xf-project-detail__body h2 {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 32px 0 14px;
}
.xf-project-detail__body h3 {
  font-family: var(--font-sans);
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
  margin: 24px 0 10px;
}
.xf-project-detail__body p { margin: 0 0 14px; }
.xf-project-detail__body p:last-child { margin-bottom: 0; }
.xf-project-detail__body a {
  color: var(--accent-2);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.xf-project-detail__body img {
  max-width: 100%;
  height: auto;
  border-radius: 10px;
  margin: 20px 0;
}
.xf-project-detail__body ul,
.xf-project-detail__body ol { margin: 0 0 14px; padding-left: 22px; }
.xf-project-detail__body li { margin: 6px 0; }

/* ---------- Gallery (PROPERTY_PHOTOS) ---------- */
.xf-project-gallery {
  max-width: 1080px;
  margin: 0 auto 48px;
  padding: 0 24px;
}
.xf-project-gallery__title {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0 0 20px;
}
.xf-project-gallery__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 14px;
}
@media (max-width: 720px) { .xf-project-gallery__grid { grid-template-columns: repeat(2, 1fr); } }
.xf-project-gallery__item {
  display: block;
  aspect-ratio: 4 / 3;
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--line-soft);
  transition: border-color .15s, transform .15s;
}
@media (hover: hover) {
  .xf-project-gallery__item:hover { border-color: var(--accent-line); transform: translateY(calc(-1px * var(--xf-motion, 1))); }
}
.xf-project-gallery__item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ---------- Linked goods (PROPERTY_LINK_GOODS) ---------- */
.xf-project-goods {
  max-width: 1080px;
  margin: 0 auto 80px;
  padding: 0 24px;
}
.xf-project-goods__head {
  margin-bottom: 22px;
}
.xf-project-goods__title {
  font-family: var(--font-sans);
  font-size: 22px;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0;
}
.xf-project-goods__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}
@media (max-width: 960px) { .xf-project-goods__grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 600px) { .xf-project-goods__grid { grid-template-columns: repeat(2, 1fr); } }

.xf-project-goods__card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  text-decoration: none;
  color: inherit;
  transition: border-color .15s, transform .15s;
}
@media (hover: hover) {
  .xf-project-goods__card:hover {
    border-color: var(--accent-line);
    transform: translateY(calc(-1px * var(--xf-motion, 1)));
  }
}
.xf-project-goods__card-image {
  width: 100%;
  aspect-ratio: 1;
  background-color: var(--surface-3);
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  border-radius: 8px;
}
.xf-project-goods__card-name {
  font-family: var(--font-sans);
  font-size: 12.5px;
  line-height: 1.4;
  font-weight: 500;
  color: var(--text);
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.xf-project-goods__card-price {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  margin-top: auto;
}
/* «Цена по запросу» — проза, не data → Onest (перебивает mono базового price). Mono-as-Data. */
.xf-project-goods__card-price--empty {
  font-family: var(--font-sans);
  font-weight: 400;
  color: var(--text-3);
}

/* End */


/* Start:/local/templates/xcore_front/css/chat.css?179264121536325*/
/* OWNS: .xf-chat(__*) (плавающий лаунчер живого чата + мини-меню каналов;
   с #702 — прямой вход к ИИ-помощнику и приглашение .xf-chat__teaser над кружком),
   переменная --xf-chat-guard и класс body.xf-chat-yield (подъём дока над
   размеченными [data-xf-chat-guard] кнопками и суммами и уступка места, когда
   подняться некуда, — #643; величину и класс ставит общий страж js/app.js
   initChatGuard)
   + чужие классы Битрикс24: .b24-widget-button-* (подавление родного кружка) и
   .bx-livechat-wrapper (позиция/видимость окна — десктоп-вправо, подъём над
   consent-баром). grep-before-add ищи здесь.
   Переиспользует body-хуки позиционирования из ui.css-контракта FAB:
   body.xf-cookie-up (подъём над consent-баром), body.xf-bottombar-up (скрытие
   под мобильной money-плашкой ≤640 и подъём над ней шире — #527, #553; класс
   owned by base.css), его спутники с #716 — body.xf-bottombar-stacked (полоса
   карточки, с #786 и корзины, стоит над баннером cookie) и
   body.xf-bottombar-ahead (полоса поднята, а основная кнопка ещё ниже экрана:
   кружок не прячем);
   @keyframes xf-chat-rehome (появление кружка над полосой карточки, #720),
   body.page-configurator
   (подъём FAB над нижней sticky-панелью конфигуратора, #201 — раньше док был
   скрыт целиком, M16-гейт снят). Родной кружок Битрикс24 (.b24-widget-button-*)
   на xcore_front всегда подавляем — B24 грузим ТОЛЬКО мы (по клику), внизу один
   наш брендовый кружок. Окно чата (.bx-livechat-wrapper) — обычный DIV в нашем
   документе (не iframe): на ДЕСКТОПЕ уводим вправо под кружок + поднимаем над
   consent-баром (зеркало FAB); на конфигураторе больше НЕ прячем (#201).
   Мобильную раскладку и внутренности окна B24 не трогаем. */

/* ─── Док-обёртка (фиксированная точка внизу справа) ──────────── */
.xf-chat {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 12px;
  /* bottom анимируется для подъёма над consent-баром (ниже), как у FAB. */
  transition: bottom .3s ease;
}

/* ─── Брендовый кружок (тоггл меню) ───────────────────────────── */
.xf-chat__fab {
  position: relative;
  width: 56px;
  height: 56px;
  flex: 0 0 auto;
  border: none;
  border-radius: 50%;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 8px 24px -8px var(--accent), 0 0 0 1px oklch(1 0 0 / 0.05) inset;
  transition: transform .2s, box-shadow .2s;
  animation: xf-chat-in .45s ease;
  -webkit-tap-highlight-color: transparent;
}
@media (hover: hover) {
  .xf-chat__fab:hover {
    transform: translateY(calc(-2px * var(--xf-motion, 1)));
    box-shadow: 0 12px 28px -8px var(--accent), 0 0 0 1px oklch(1 0 0 / 0.05) inset;
  }
}
.xf-chat__fab:active { transform: translateY(0) scale(calc(1 - 0.04 * var(--xf-motion, 1))); }
/* Тёмная тема: цветная тень под кружком гаснет и на наведении (0-2-1 выше
   :hover) — нейтральная тень держит его над страницей, светится только
   вывеска главной. */
html[data-theme="dark"] .xf-chat__fab { box-shadow: 0 10px 28px -12px oklch(0 0 0 / 0.8); }

/* Глифы в кружке: «чат» (закрыто, нет живого разговора) ↔ sparkle (закрыто, есть
   живой разговор — #187) ↔ «крестик» (меню открыто). Кросс-фейд + поворот. */
.xf-chat__fab-icon {
  position: absolute;
  display: inline-flex;
  transition: opacity .2s ease, transform .2s ease;
}
.xf-chat__fab-icon--close { opacity: 0; transform: rotate(-90deg) scale(.6); }
.xf-chat__fab-icon--live  { opacity: 0; transform: rotate(90deg) scale(.6); }
.xf-chat__fab-icon--open  { opacity: 1; transform: rotate(0) scale(1); }
/* Есть живой разговор, меню закрыто: чат → sparkle (память кружка, #187). */
.xf-chat.has-live:not(.is-open) .xf-chat__fab-icon--open { opacity: 0; transform: rotate(-90deg) scale(.6); }
.xf-chat.has-live:not(.is-open) .xf-chat__fab-icon--live { opacity: 1; transform: rotate(0) scale(1); }
/* Меню открыто — всегда крестик (перебивает и sparkle, и чат). */
.xf-chat.is-open .xf-chat__fab-icon--open  { opacity: 0; transform: rotate(90deg) scale(.6); }
.xf-chat.is-open .xf-chat__fab-icon--live  { opacity: 0; transform: rotate(90deg) scale(.6); }
.xf-chat.is-open .xf-chat__fab-icon--close { opacity: 1; transform: rotate(0) scale(1); }

/* Точка-индикатор «есть открытый разговор» (#187): маленькая, тихая — белая
   точка в верхнем-правом кружка, посаженная кольцом того же accent (чистый край,
   не бейдж-крикун). ARIA несёт то же в aria-label кружка (app.js). Гаснет, когда
   меню открыто: на крестике «непрочитанное» не к месту. */
.xf-chat__fab::after {
  content: "";
  position: absolute;
  top: 8px;
  right: 8px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 2px var(--accent);
  opacity: 0;
  transform: scale(.4);
  transition: opacity .2s ease, transform .2s ease;
  pointer-events: none;
}
.xf-chat.has-live .xf-chat__fab::after { opacity: 1; transform: scale(1); }
.xf-chat.has-live.is-open .xf-chat__fab::after { opacity: 0; transform: scale(.4); }

/* ─── Мини-меню (поповер над кружком) ─────────────────────────── */
.xf-chat__menu {
  width: 264px;
  max-width: calc(100vw - 32px);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  transform-origin: bottom right;
  transition: opacity .18s ease, transform .18s ease, visibility .18s;
}
.xf-chat__menu[hidden] { display: none; }
/* Анимация раскрытия (видимость гейтит JS через [hidden]; класс — для transition). */
.xf-chat:not(.is-open) .xf-chat__menu {
  opacity: 0;
  visibility: hidden;
  transform: translateY(8px) scale(.96);
  pointer-events: none;
}
.xf-chat.is-open .xf-chat__menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
}

/* ─── Приглашение ИИ-помощника (#702) ─────────────────────────────
   Вид — первая реплика помощника, как в мессенджере (выбор Ильи 14.10 из
   четырёх раскладок): подпись «кто говорит», пузырь с текстом, под ним
   кнопки-ответы. Хвоста нет: пузырь «говорит» острым нижним правым углом в
   сторону кружка, как реплика в окне чата. Кнопки-ответы повторяют чипсы
   окна (.xf-aichat__chip в aichat.css: радиус кнопки, accent-текст) — открытое
   окно читается продолжением. Индиго — аватар и текст кнопок (One-Voice),
   поверхность нейтральная; кнопки — не pill (Pill-Is-Not-Button).
   Показывает и прячет app.js (initChatTeaser): [hidden] снимается, кадром
   позже на док встаёт .has-teaser — тогда реплика проявляется и подъезжает. */
.xf-chat__teaser {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  max-width: min(300px, calc(100vw - 32px));
  transform-origin: bottom right;
  /* До .has-teaser стоит на месте невидимой и неосязаемой: app.js меряет её,
     прежде чем показать (не накроет ли главное на странице). */
  visibility: hidden;
  opacity: 0;
  transform: translateY(calc(8px * var(--xf-motion, 1))) scale(calc(1 - 0.03 * var(--xf-motion, 1)));
  transition: opacity .22s ease, transform .26s cubic-bezier(0.22, 1, 0.36, 1), visibility .26s;
}
.xf-chat__teaser[hidden] { display: none; }
.xf-chat.has-teaser .xf-chat__teaser { visibility: visible; opacity: 1; transform: none; }
.xf-chat__teaser-who {
  display: flex;
  align-items: center;
  gap: 7px;
  /* Метка (pill — информационный ярлык, не кнопка): подпись лежит прямо на
     странице, без подложки она терялась на плитках и картинках. */
  padding: 3px 10px 3px 3px;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: var(--surface);
  font-size: 12px;
  color: var(--text-2);
}
.xf-chat__teaser-avatar {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 7px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent-dim);
  color: var(--accent-2);
}
.xf-chat__teaser-bubble {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px 16px 4px 16px;
  box-shadow: var(--shadow-card);
}
.xf-chat__teaser-open {
  display: block;
  width: 100%;
  padding: 11px 36px 11px 14px; /* справа — место под «×» */
  border: none;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 14px;
  line-height: 1.45;
  text-align: left;
  cursor: pointer;
}
.xf-chat__teaser-close {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--text-3);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
@media (hover: hover) {
  .xf-chat__teaser-close:hover { background: var(--surface-2); color: var(--text); }
}
.xf-chat__teaser-asks {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 6px;
}
.xf-chat__teaser-ask {
  display: inline-flex;
  align-items: center;
  padding: 8px 13px;
  border-radius: 10px;
  border: 1px solid var(--accent-line);
  background: var(--surface);
  color: var(--accent-2);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.2;
  white-space: nowrap;
  cursor: pointer;
  /* лёгкая тень: кнопки лежат прямо на странице, без общей подложки */
  box-shadow: 0 6px 16px -10px oklch(0.3 0.05 285 / 0.35);
  transition: background-color .15s ease, border-color .15s ease;
}
@media (hover: hover) {
  .xf-chat__teaser-ask:hover { background: var(--accent-dim); border-color: var(--accent); }
}
/* Ступени, которыми реплика уступает место главному на странице (app.js, fit):
   узкая и без подписи над пузырём. Двойной класс — чтобы перебить мобильную
   ширину ниже. Без подписи пузырь всё равно «говорит» уголком в кружок. */
.xf-chat__teaser.xf-chat__teaser--narrow { max-width: 240px; }
.xf-chat__teaser--bare .xf-chat__teaser-who { display: none; }

/* ─── Строка-канал ────────────────────────────────────────────── */
.xf-chat__item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--text);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  width: 100%;
  font: inherit;
  transition: background-color .15s ease;
}
@media (hover: hover) {
  .xf-chat__item:hover { background: var(--surface-2); }
}
.xf-chat__item:active { background: var(--surface-3); }

.xf-chat__item-icon {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
/* ИИ-помощник — ЕДИНСТВЕННЫЙ акцентный чип в меню (One-Voice: индиго =
   «сюда», на приоритетный канал; после #68 бот-репо первая линия — бот). */
.xf-chat__item-icon--bot  { background: var(--accent); color: #fff; }
/* Менеджер — нейтральный чип (эскалация). Серый vs индиго держит мгновенный
   контраст «робот / живой человек»; акцент уехал к приоритетному каналу. */
.xf-chat__item-icon--live { background: var(--surface-3); color: var(--text-2); }

.xf-chat__item-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.xf-chat__item-title { font-size: 14px; font-weight: 600; }
/* --text-2 (не --text-3): на 12px AA-контраст держится в ОБЕИХ темах; иерархию
   несут кегль (14→12) и вес (600→400), не приглушённость до нечитаемости. */
.xf-chat__item-sub   { font-size: 12px; color: var(--text-2); margin-top: 2px; }

/* Спиннер загрузки на строке «Чат с менеджером», пока тянется загрузчик B24. */
.xf-chat__item.is-loading { pointer-events: none; }
.xf-chat__item.is-loading .xf-chat__item-icon--live {
  position: relative;
  color: transparent;
}
.xf-chat__item.is-loading .xf-chat__item-icon--live::after {
  content: "";
  position: absolute;
  width: 16px;
  height: 16px;
  /* Чип менеджера теперь нейтральный (акцент уехал к помощнику) — белая дуга
     на сером фоне не видна, спиннер строится из токенов текста/линий. */
  border: 2px solid var(--line);
  border-top-color: var(--text-2);
  border-radius: 50%;
  animation: xf-chat-spin .6s linear infinite;
}

/* ─── Подъём над consent-баром (зеркалит FAB-контракт, ui.css) ── */
body.xf-cookie-up .xf-chat { bottom: calc(var(--xf-cookie-h, 72px) + 24px); }

/* ─── Конфигуратор: чат-док виден (#201, M16-гейт снят) ───────────
   С M16 док был скрыт (бот был «про гарантию/заказы, не про совместимость»
   и конкурировал со step-counter accent). Бот стал сборко-компетентным — гейт
   снят согласованно: .xf-aichat (aichat.css) и .bx-livechat-wrapper (ниже) тоже
   разблокированы. Легаси .xf-floating-bot (ui.css) остаётся скрыт — он рендерится
   ТОЛЬКО в rollback-ветке footer.php ($xf_chat_live=false) и взаимоисключим с
   .xf-chat (никогда не два кружка), плюс телеграм-only и irrelevant к живому
   чат-доку. */

/* ─── Подъём FAB над sticky-CTA конфигуратора (#201) ──────────────
   В одноколоночном режиме (≤1100px) нижняя панель конфигуратора «Очистить
   сборку / Перейти в корзину» (bottom.php, .list-group-item.sticky-bottom)
   полноширинна и пиннится к низу вьюпорта — её CTA попадает в зону FAB.
   Поднимаем FAB НАД баром, а не прячем (как #61 на PDP): бот здесь сборко-
   компетентен, гасить его в момент готовой сборки — против смысла #201.
   Двухколоночный десктоп (>1100px) коллизии не даёт (бар в левой колонке, FAB
   справа над aside) — не трогаем. [#524] Уже даёт: «Перейти к оформлению»
   прилеплена к низу сайдбара v2 и при ~1100–1500px встаёт под FAB — это снимает
   JS-страж (app.js initChatGuard, --xf-chat-guard), не эти офсеты. Офсеты грубые, С ЗАПАСОМ вверх (перекрыть CTA
   нельзя; «висит чуть высоко» — не баг), точные значения — после визуал-смоука.
   Комбо-правила (cookie-up + configurator) суммируют оба лифта. */
@media (min-width: 641px) and (max-width: 1100px) {
  /* бар присутствует всегда (даже без picks — disabled-контролы) → лифт безусловный */
  body.page-configurator .xf-chat { bottom: 100px; }
  body.xf-cookie-up.page-configurator .xf-chat { bottom: calc(var(--xf-cookie-h, 72px) + 100px); }
}
@media (max-width: 640px) {
  /* ≤640 бар скрыт без picks (configurator.css M12/M15) → лифт только с picks */
  body.page-configurator.xf-has-picks .xf-chat { bottom: 152px; }
  body.xf-cookie-up.page-configurator.xf-has-picks .xf-chat { bottom: calc(var(--xf-cookie-h, 120px) + 152px); }
}

/* ─── Подъём над поднятой money-полосой на планшете (#527, #553) ────
   Нижние липкие полосы — «В корзину» карточки товара (.xf-buy-sticky,
   ≤767), «Оформить» корзины (.xf-cart-sticky, ≤767) и «Открыть в
   конфигураторе» страницы сборки (.xfpd-sticky, ≤991.98) — пока подняты,
   ставят body.xf-bottombar-up и меряют свою высоту в --xf-bottombar-h.
   ≤640 док прячется общим правилом мобайла ниже (#61). Шире 640 оно не
   действует, и кружок садился на кнопки полосы: замер на проде 700×900 —
   elementFromPoint по правому краю «В корзину» и по «В избранное» карточки,
   по «Оформить» корзины и по кнопке страницы сборки отдавал .xf-chat__fab;
   тап открывал чат, товар не добавлялся. Здесь док не прячем, а поднимаем
   над полосой — тот же выбор, что для панели конфигуратора (#201): на
   планшете место есть, а чат — рабочий канал продаж.
   Верхней границы у правила нет: класс ставится только там, где полоса
   существует — скрипты полос гейтят состояние своей шириной (initMoneyBar в
   js/app.js, BAR_MQ в js/configurator-preset-page.js). Раньше (#527) правило
   держалось за :has(> [data-xfpd-sticky].is-visible) и знало только страницу
   сборки: у карточки и корзины класс тогда врал на десктопе.
   Consent-бар поднят — правило молчит: баннер и так накрывает полосу, а док
   уже поднят над ним правилом body.xf-cookie-up. */
@media (min-width: 641px) {
  body.xf-bottombar-up:not(.xf-cookie-up) .xf-chat {
    bottom: calc(var(--xf-bottombar-h, 84px) + 16px);
  }
  /* [#716] Полоса карточки товара стоит НАД баннером cookie (класс
     xf-bottombar-stacked, ставит app.js для неё и для корзины, #786): «баннер и так
     накрывает полосу» к ней больше не относится, и кружок, поднятый лишь над
     баннером, сел бы на её кнопки. Чистим сумму высот. */
  body.xf-cookie-up.xf-bottombar-stacked .xf-chat {
    bottom: calc(var(--xf-cookie-h, 92px) + var(--xf-bottombar-h, 84px) + 16px);
  }
  /* [#720] Над полосой карточки кружок встаёт СРАЗУ, без перехода bottom:
     полоса въезжает снизу на своё конечное место, и кружок, едущий туда же
     .3s с прежнего места, пересекал её кнопки (см. блок #720 в мобильной
     части ниже). Обратно, когда полоса ушла, кружок съезжает как раньше —
     базовым transition. */
  body.xf-bottombar-stacked .xf-chat { transition: none; }
}

/* ─── Подавление родного кружка Битрикс24 ─────────────────────── */
.b24-widget-button-wrapper,
.b24-widget-button-position-bottom-right,
.b24-widget-button-position-bottom-left {
  display: none !important;
}

/* ─── Окно «Открытых линий» B24 — вправо + над баром (десктоп) ─────
   Виджет в админке портала настроен bottom-left → окно открывается слева,
   далеко от нашего кружка справа. Окно — DIV в нашем документе (не iframe),
   left/bottom приходят из стайлшита B24 (не inline), поэтому переопределяемы.
   Уводим к правому краю, под кружок (right:24px = .xf-chat). Селектор не привязан
   к классу позиции (-bottom-left/-right) — если владелец сменит настройку, окно
   всё равно останется справа. Мобайл НЕ трогаем: там B24 раскрывает окно почти
   на всю ширину — корректно. */
@media (min-width: 641px) {
  .bx-livechat-wrapper {
    left: auto !important;
    right: 24px !important;
  }
  /* Подъём над consent-баром — ЗЕРКАЛИТ FAB-док (body.xf-cookie-up .xf-chat).
     Окно B24 само висит на статичном bottom:130px и под бар не уходит, а бар на
     первом визите бывает выше: на узком десктопе текст переносится (~144px), а
     раскрытая панель «Настроить» — 350–450px. --xf-cookie-h меряется в рантайме
     (app.js) и отслеживает оба случая (есть лаг до пересчёта observer'ом), так
     что окно следует ровно за тем же лифтом, что и кружок → поле ввода не уходит
     под бар. Замерено: 760px свёрнут бар 144→окно 168; раскрыт 416→окно 440. */
  body.xf-cookie-up .bx-livechat-wrapper {
    bottom: calc(var(--xf-cookie-h, 92px) + 24px) !important;
  }
}

/* Окно «Чат с менеджером» (B24) на конфигураторе больше НЕ прячем (#201):
   меню FAB предлагает оба канала (ИИ-помощник + менеджер), менеджерский тоже
   должен открываться на конфигураторе. Окно висит на статичном bottom:130px
   (десктоп, ниже) — выше sticky-панели конфигуратора, отдельный лифт не нужен;
   мобильную почти-полноэкранную раскладку B24 не трогаем. */

/* ─── Анимации ────────────────────────────────────────────────── */
@keyframes xf-chat-in {
  from { opacity: 0; transform: translateY(10px) scale(.96); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes xf-chat-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .xf-chat, .xf-chat__fab, .xf-chat__fab-icon, .xf-chat__menu { transition: none; animation: none; }
  .xf-chat__fab::after { transition: none; } /* индикатор живого разговора (#187) — без анимации */
  .xf-chat__fab:hover { transform: none; }
  /* спиннер тоже гасим — статичная дуга остаётся как индикатор загрузки */
  .xf-chat__item.is-loading .xf-chat__item-icon--live::after { animation: none; }
}

/* ─── Мобайл ──────────────────────────────────────────────────── */
@media (max-width: 640px) {
  .xf-chat { right: 16px; bottom: 16px; }
  .xf-chat__fab { width: 52px; height: 52px; }
  /* Пузырь чуть шире: при «×» под палец текст иначе оставлял слово на строке. */
  .xf-chat__teaser { max-width: min(320px, calc(100vw - 32px)); }
  /* Под палец, не под курсор: «×» и кнопки-ответы не меньше 40px. */
  .xf-chat__teaser-close { width: 40px; height: 40px; top: 2px; right: 2px; }
  .xf-chat__teaser-open { padding-right: 44px; }
  .xf-chat__teaser-ask { min-height: 40px; }
  /* мобильный consent-бар выше (текст переносится) — больший fallback */
  body.xf-cookie-up .xf-chat { bottom: calc(var(--xf-cookie-h, 120px) + 16px); }
  /* [#61] Нижняя sticky money-плашка (PDP «В корзину» / оформление корзины)
     поднята → прячем весь чат-док: бар владеет thumb-зоной, второй accent-кружок
     ломал бы One-Voice и ловил мис-тапы. Зеркалит FAB-контракт (ui.css). Док
     возвращается на скролл вверх. Сигнал body.xf-bottombar-up ставит app.js. */
  body.xf-bottombar-up .xf-chat {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(8px) scale(0.94);
    transition: opacity .2s ease, visibility .2s ease, transform .2s ease;
  }
  /* [#716, #720] Карточка товара (с #786 — и корзина). Пока её полоса поднята (класс
     xf-bottombar-stacked ставит app.js для неё и для корзины, #786), место кружка — НАД
     полосой: и когда он виден, и когда спрятан правилом #61 выше.
     Виден он, когда полоса поднята потому, что основная кнопка ещё ВПЕРЕДИ —
     ниже экрана или под баннером cookie (класс xf-bottombar-ahead, app.js
     initMoneyBar). На экране настоящего телефона так выглядит первый кадр
     карточки у всех, кто не ответил на баннер; спрятать кружок здесь значило
     бы убрать чат с первого кадра — против решения #381 («на карточке живой
     чат — рабочий канал продаж»). #61 при этом цел: когда кнопку уже
     пролистали и полоса её подменяет, класса ahead нет — кружок спрятан.

     [#720] Кружок не должен ложиться на кнопки полосы НИ В ОДНОМ её
     состоянии — и в покое, и по пути. Что было: полоса и кружок приходили на
     места каждый своим переходом (полоса — transform .22s и bottom .3s,
     кружок — bottom .3s) из разных стартовых точек, а спрятанный кружок (#61)
     оставался лежать на кнопках поднятой полосы. Замер на проде, 390×664:
     при прокрутке мимо кнопки ~0,3 с гаснущий кружок был на въезжающей полосе;
     на 320×568 сразу после загрузки ~0,4 с ехал поверх «В избранное». А на
     странице, которая не рисуется (вкладка открыта в фоне — так открывают
     рекламу), таймер баннера срабатывал без кадров, и первый же кадр показывал
     кружок поверх полосы.
     Как теперь: на своё место над полосой кружок встаёт без перехода, в том же
     пересчёте стилей, что и классы полосы; полоса въезжает снизу и до него не
     достаёт. Прячется (#61) тоже сразу: гаснуть 0,2 с там, куда въезжает
     полоса, — то же наложение. Появление над полосой смягчает короткая
     анимация прозрачности — она не двигает кружок. Обратно, когда полоса ушла,
     кружок съезжает домой базовым transition.
     transform — страж кнопки и названия (правило в конце файла); в transition
     его нет намеренно, по той же причине, что и там.
     Порядок правил важен: блок стоит ПОСЛЕ #61 и перекрывает его transition
     при равном весе селектора — не переносить выше.
     Обкатано на /dev/ под скоупом d1, скоуп снят при выкатке 2026-10-12. */
  body.xf-bottombar-stacked .xf-chat {
    bottom: calc(var(--xf-bottombar-h, 84px) + 16px);
    transition: none;
  }
  body.xf-cookie-up.xf-bottombar-stacked .xf-chat {
    bottom: calc(var(--xf-cookie-h, 120px) + var(--xf-bottombar-h, 84px) + 16px);
  }
  body.xf-bottombar-ahead .xf-chat {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateY(calc(-1 * var(--xf-chat-guard, 0px)));
    animation: xf-chat-rehome .2s ease both;
  }
  @media (prefers-reduced-motion: reduce) {
    body.xf-bottombar-ahead .xf-chat { animation: none; }
  }
}
@keyframes xf-chat-rehome { from { opacity: 0; } to { opacity: 1; } }

/* ─── Страж кнопки покупки (#381) ──────────────────────────────────
   Правило выше закрывает случай «липкая money-плашка поднята». Но у карточки
   товара кнопка «В корзину» стоит ещё и В ПОТОКЕ страницы, и пока она видна,
   плашка как раз НЕ поднята — то есть ровно в этом окне док остаётся дома и
   ловит те самые мис-тапы, ради которых написано правило выше. Замер на живой
   карточке (монитор, 390×844, повторный визит): кружок 322-374 × 776-828,
   кнопка 16-374 × 821-873, пересечение 52×7 px, и elementFromPoint в центре
   пересечения отдавал .xf-chat__fab — палец открывал чат вместо добавления в
   корзину. Товар при этом молча не добавлялся.

   Здесь док НЕ прячем (он нужен покупателю именно на карточке — «много заказов
   идёт звонком или в чат»), а приподнимаем ровно настолько, чтобы сесть над
   кнопкой. Величину считает общий страж (js/app.js initChatGuard, #643; цели
   размечены data-xf-chat-guard) из живых прямоугольников и кладёт
   в --xf-chat-guard: фиксированным сдвигом тут не обойтись — кнопка проплывает
   сквозь полосу кружка при скролле, и любой заранее выбранный подъём она рано
   или поздно догоняет.

   :not(.xf-bottombar-up) — чтобы не перебить transform прятанья выше (там свой
   translateY+scale, и специфичность у нас была бы равной).

   Множителем --xf-motion сдвиг НЕ гейтится сознательно, в отличие от
   декоративных подъёмов: это не движение ради движения, а обход столкновения.
   Обнулив его под prefers-reduced-motion, мы вернули бы мис-тап тем самым
   пользователям, которые просили меньше движения. Transition тоже не вводим —
   кружок едет вместе со скроллом, отдельная анимация ему не нужна, и базовый
   `transition: bottom` остаётся нетронутым. */
body:not(.xf-bottombar-up) .xf-chat {
  transform: translateY(calc(-1 * var(--xf-chat-guard, 0px)));
}

/* ─── Уступка места (#643) ───────────────────────────────────────
   Подняться над размеченным некуда — свободного места в пределах подъёма нет,
   или место размечено data-xf-chat-guard="yield" (форма оформления на
   телефоне: сплошь поля и выборы во всю ширину): кружок прячется, пока под ним
   такое место, и возвращается, когда полоса свободна. Решает
   страж (js/app.js initChatGuard), открытый чат он не прячет никогда.
   Только прозрачность, без сдвига: кружок не уезжает и не возвращается
   движением, поэтому множитель --xf-motion здесь не нужен. */
body.xf-chat-yield .xf-chat {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .2s ease, visibility .2s ease;
}

/* End */


/* Start:/local/templates/xcore_front/css/aichat.css?179265386261719*/
/* OWNS: .xf-aichat(__*) — панель веб-чата «ИИ-помощник X-Core» (issue #9).
   DOM строит js/aichat.js (footer.php не трогаем), сюда только стили. Токены —
   из tokens.css (тема наследуется автоматом: light по умолчанию, [data-theme=dark]).
   Бот — первая линия (#68), но поверхность панели держим нейтральной (Restrained):
   индиго только там, куда должен смотреть глаз — пузырь клиента, primary CTA,
   фокус (One-Voice ≤10%). Рифмуется с .xf-chat: тот же угол, те же радиусы/тени,
   тот же дух анимации входа. */

/* ─── Корневой док (fixed, правый нижний угол, поверх кружка) ──────────── */
.xf-aichat {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 1200; /* выше FAB (100) и окна B24; ниже toast/consent-бара (9999) */
  width: 384px;
  max-width: calc(100vw - 32px);
  /* высота = min(желаемая, доступная над кружком/баром) — панель не вылезает
     за верх вьюпорта на низких экранах, внутренняя лента скроллится сама. */
  height: 560px;
  max-height: calc(100vh - 48px);
  display: none;
  /* bottom анимируется для подъёма над consent-баром (зеркалит .xf-chat). */
  transition: bottom .3s ease;
}
/* is-closing тоже держит панель в потоке: снятие is-open иначе мгновенно ставит
   display:none, и схлопывание (xf-aichat-out) не успевает проиграться (#187).
   JS снимает is-closing по концу анимации (~180мс). Ввод во время схлопывания
   гасим — панель уходит. */
.xf-aichat.is-open,
.xf-aichat.is-closing { display: block; }
.xf-aichat.is-closing { pointer-events: none; }

/* Подъём над consent-баром — переиспользуем runtime-переменную из app.js
   (initBotCookieClearance), тот же контракт, что у FAB и toast'ов. */
body.xf-cookie-up .xf-aichat { bottom: calc(var(--xf-cookie-h, 72px) + 24px); }

/* [#702] Прямой вход: панель над кружком (24 отступ + 56 кружок + 12 зазор),
   а не на его месте. Кружок остаётся виден и закрывает панель («×», app.js) —
   как закрывал меню каналов; второй клик по нему больше не попадает в кнопку
   панели. Подъём кружка стражами кнопок (--xf-chat-guard) панель повторяет.
   Только десктоп: на телефоне панель во весь экран (ниже), там от двойного
   тапа бережёт OPEN_ARM_MS в aichat.js. */
@media (min-width: 641px) {
  .xf-aichat--above-fab {
    bottom: calc(92px + var(--xf-chat-guard, 0px));
    max-height: calc(100vh - 116px);
  }
  body.xf-cookie-up .xf-aichat--above-fab {
    bottom: calc(var(--xf-cookie-h, 72px) + 92px + var(--xf-chat-guard, 0px));
    max-height: calc(100vh - var(--xf-cookie-h, 72px) - 116px);
  }
}

/* Конфигуратор: панель ИИ больше НЕ прячем (#201, M16-гейт снят вместе с FAB
   .xf-chat в chat.css — бот стал сборко-компетентным). На десктопе/планшете
   панель fixed bottom:24 right:24 (перетаскиваемая — inline-позиция из aichat.js
   перебивает bottom); на ≤640 раскрывается на весь экран. Открытая панель сама
   накрывает sticky-панель конфигуратора — отдельный подъём (как у FAB в chat.css)
   не нужен. */

/* ─── Карточка панели ─────────────────────────────────────────────────── */
.xf-aichat__panel {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 18px;
  box-shadow: var(--shadow-card), 0 2px 8px -4px oklch(0.2 0.03 285 / 0.25);
  transform-origin: bottom right;
}

/* Появление — тем же духом, что xf-chat-in: fade + подъём + лёгкий scale. */
.xf-aichat.is-open .xf-aichat__panel { animation: xf-aichat-in .24s cubic-bezier(0.22, 1, 0.36, 1); }
/* Закрытие = «схлопывание» окна в кружок (#187): панель ужимается к своему
   правому нижнему углу (transform-origin ниже) и скругляется — при дефолтной
   позиции этот угол лежит ровно над FAB, и окно уходит буквально в кружок; после
   драга (#183) окно стоит где угодно — схлопываем в его СОБСТВЕННЫЙ нижний-правый
   (не гоним через пол-экрана к реальному FAB: длинный полёт смотрелся бы дёшево,
   а «сложилось само в свой нижний-правый» цельно везде и рифмуется с углом кружка).
   ease-in: медленный старт → ускорение в угол. */
.xf-aichat.is-closing .xf-aichat__panel { animation: xf-aichat-out .2s cubic-bezier(0.4, 0, 1, 1) forwards; }

@keyframes xf-aichat-in {
  from { opacity: 0; transform: translateY(14px) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes xf-aichat-out {
  from { opacity: 1; transform: scale(1);   border-radius: 18px; }
  to   { opacity: 0; transform: scale(.16); border-radius: 999px; }
}
/* Мобайл — панель фуллскрин: схлопывать её в угол дёшево (весь экран в точку),
   поэтому там мягкий уход вниз + fade (жест «смахнуть лист»), не scale-в-угол. */
@keyframes xf-aichat-out-m {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(24px); }
}

/* ─── Шапка ───────────────────────────────────────────────────────────── */
.xf-aichat__header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 12px 12px 16px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--surface);
}
.xf-aichat__avatar {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* мягкая accent-подложка, НЕ solid: бот остаётся нейтрально-статусным,
     но аватар даёт узнаваемый «искристый» акцент (sparkle из icons.php). */
  background: var(--accent-dim);
  color: var(--accent-2);
}
.xf-aichat__ident { display: flex; flex-direction: column; min-width: 0; margin-right: auto; line-height: 1.25; }
/* nowrap: имя — первое, что видит клиент; перенос «X-Core» на вторую строку
   ломал шапку (P1 критики). Telegram-мост ужат до иконки, места хватает. */
.xf-aichat__name {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.xf-aichat__status {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--text-2);
  margin-top: 1px;
}
.xf-aichat__status::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--success);
  box-shadow: 0 0 0 3px oklch(0.65 0.17 155 / 0.16);
}
/* Сбой API: шапка не спорит с карточкой «недоступен» (P1) — точка гаснет. Так же
   — после повторных несостоявшихся ходов («не отвечает», «занят», «нет связи»,
   #701): зелёное «на связи» над пятью недоставленными сообщениями было неправдой. */
.xf-aichat__status.is-off::before {
  background: var(--text-3);
  box-shadow: none;
}

.xf-aichat__act {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: none;
  border-radius: 10px;
  background: none;
  color: var(--text-2);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
@media (hover: hover) {
  .xf-aichat__act:hover { background: var(--surface-2); color: var(--text); }
}
.xf-aichat__act:active { background: var(--surface-3); }
.xf-aichat__act:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Telegram-мост в шапке — иконка с aria-label/title на всех ширинах: текстовая
   подпись отжимала место у имени бота (P1). Не accent: alt-канал, не CTA. */
.xf-aichat__tg { color: var(--text-2); }

/* ─── Перетаскивание дока за шапку (desktop, issue #183) ──────────────────────
   Шапка — ручка: grab / grabbing. Только десктоп — на мобайле панель фуллскрин,
   таскать нечего (JS не стартует драг по isMobile). Позицию после отпускания JS
   запекает в inline left/top (right/bottom:auto) — CSS-дефолт правого нижнего
   угла и cookie-сдвиг с этого момента уступают клиенту (#183 п.6). Курсор — не
   :focus-стиль, инвариант A11y не нарушаем. */
@media (min-width: 641px) {
  .xf-aichat__header { cursor: grab; touch-action: none; }
  .xf-aichat.is-dragging .xf-aichat__header { cursor: grabbing; }
  /* интерактив шапки (Telegram-мост, крестик) сохраняет указатель — драг с них
     не стартует, курсор не должен обещать перетаскивание. */
  .xf-aichat__header .xf-aichat__act { cursor: pointer; }
}
/* Пока тащим — гасим выделение текста под курсором на всей странице. */
body.xf-aichat-drag { -webkit-user-select: none; user-select: none; }

/* ─── Тело / лента ────────────────────────────────────────────────────── */
.xf-aichat__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Низ 14px: последний элемент ленты (кнопки заявки) не стоит вплотную к
     строке «Позвать менеджера» под лентой (#705). */
  padding: 16px 16px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  scroll-behavior: smooth;
  /* Плоская поверхность: radial-градиент в фоне ленты нарушал бы
     Flat-Surface Rule (декоративные градиенты-заливки сняты целиком, #280). */
  background: var(--bg);
}
@media (prefers-reduced-motion: reduce) { .xf-aichat__body { scroll-behavior: auto; } }

/* Тонкая приписка при первом открытии. */
.xf-aichat__note {
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-3);
  text-align: center;
  padding: 2px 10px 6px;
  margin: 0 auto;
  max-width: 32ch;
}

/* Строка сообщения — задаёт сторону и максимум ширины пузыря. */
/* min-width:0 обязателен (issue #167): дефолтный min-width:auto флекс-элемента
   не даёт ряду ужаться ниже неразрывной строки (nowrap-ссылка) — пузырь
   распирало шире панели. */
.xf-aichat__row { display: flex; flex-direction: column; max-width: 86%; min-width: 0; }
.xf-aichat__row--bot  { align-self: flex-start; align-items: flex-start; }
.xf-aichat__row--user { align-self: flex-end;   align-items: flex-end; }

.xf-aichat__bubble {
  padding: 9px 13px;
  border-radius: 14px;
  font-size: 14px;
  line-height: 1.5;
  /* reply — plain text с \n: сохраняем переносы/структуру каталога как есть. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  word-break: break-word;
  /* Пара к min-width:0 у ряда: пузырь не шире ряда даже с nowrap-контентом. */
  max-width: 100%;
  min-width: 0;
}
/* Ссылка в ответе бота (issue #167): длинные URL каталога не ломаем по буквам —
   одна строка с многоточием; ellipsis требует block-контейнера, поэтому
   inline-block + max-width. Каталожные ссылки на десктопе идут в этой же
   вкладке, панель переоткрывается после навигации (#174); мобайл и чужие
   домены — в новую вкладку. */
.xf-aichat__link {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.xf-aichat__link:hover { text-decoration: none; }
/* Тёмная (#858): --accent там — краска заливки, текстом на пузыре 3.7:1;
   фиолет текста — --accent-2 (9.9:1). Отклик на наведение прежний —
   подчёркивание гаснет. */
html[data-theme="dark"] .xf-aichat__link { color: var(--accent-2); }
/* Бот — «бумага на верстаке»: карточка светлее фона ленты (Card-On-Cream). */
.xf-aichat__row--bot .xf-aichat__bubble {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--line-soft);
  border-bottom-left-radius: 5px;
}
/* Клиент — единственный accent-элемент ленты (свои реплики, как в мессенджерах).
   Без тени: flat-by-default, тень в панели только у самой панели и toast'а. */
.xf-aichat__row--user .xf-aichat__bubble {
  background: var(--accent);
  color: #fff;
  border-bottom-right-radius: 5px;
}
/* В dark --accent осветлён под роль ПЕРЕДНЕГО плана (текст/фокус) — как
   заливка под белым текстом он давал 3.2:1 (P2 критики). Берём глубокий тон.
   [#708] 3.2:1 — прежней тёмной темы. В «Ночной вывеске» (#739) белая
   подпись на --accent — 5.15:1, на --accent-3 — 6.4:1; правило живое и
   держит прежний вид (реплика клиента на тон глубже кнопок). */
html[data-theme="dark"] .xf-aichat__row--user .xf-aichat__bubble {
  background: var(--accent-3);
}
.xf-aichat__row + .xf-aichat__row { margin-top: 0; }
/* Плотнее группируем подряд идущие реплики одного автора. */
.xf-aichat__row--bot + .xf-aichat__row--bot,
.xf-aichat__row--user + .xf-aichat__row--user { margin-top: -4px; }

/* Индикатор набора — три точки + подпись с эскалацией по времени ожидания
   (issue #206). Подпись честно отражает ТОЛЬКО течение времени (у фронта нет
   сигнала о реальном этапе ответа) — сами фразы и их тайминг ставит js/aichat.js. */
.xf-aichat__typing {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 90%;
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line-soft);
  border-radius: 14px;
  border-bottom-left-radius: 5px;
}
.xf-aichat__typing-dots {
  display: inline-flex;
  flex: 0 0 auto;
  gap: 4px;
}
.xf-aichat__typing-dots span {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-3);
  animation: xf-aichat-blink 1.2s infinite ease-in-out;
}
.xf-aichat__typing-dots span:nth-child(2) { animation-delay: .18s; }
.xf-aichat__typing-dots span:nth-child(3) { animation-delay: .36s; }
@keyframes xf-aichat-blink {
  0%, 60%, 100% { opacity: .3; transform: translateY(0); }
  30%           { opacity: 1;  transform: translateY(-3px); }
}
/* Подпись-статус: Onest (нарратив, не данные — mono тут не по правилу), приглушённый
   тон, мягкое появление/смена через opacity. Пустая (0–4с) — :empty убирает её из
   флекса совсем, пузырь показывает только точки без «пустого» отступа справа. */
.xf-aichat__typing-label {
  flex: 0 1 auto;
  min-width: 0;
  font-size: 12.5px;
  line-height: 1.35;
  color: var(--text-2);
  opacity: 0;
  transition: opacity .2s ease;
}
.xf-aichat__typing-label:empty { display: none; }
.xf-aichat__typing-label.is-shown { opacity: 1; }
.xf-aichat__typing-label.is-fading { opacity: 0; }

/* ─── Быстрые ответы (чипсы) ──────────────────────────────────────────── */
.xf-aichat__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-self: flex-start;
  max-width: 100%;
  margin-top: 2px;
}
/* Чипсы — ДЕЙСТВИЯ, поэтому не pill (Pill-Is-Not-Button): радиус кнопочный,
   как у .xf-aichat__btn, accent-текст говорит «это продолжение диалога». */
.xf-aichat__chip {
  display: inline-flex;
  align-items: center;
  max-width: 100%;
  padding: 8px 13px;
  border-radius: 10px;
  border: 1px solid var(--accent-line);
  background: var(--surface);
  color: var(--accent-2);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 500;
  line-height: 1.2;
  text-align: left;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, transform .1s ease;
}
@media (hover: hover) {
  .xf-aichat__chip:hover { background: var(--accent-dim); border-color: var(--accent); }
}
.xf-aichat__chip:active { transform: scale(calc(1 - 0.03 * var(--xf-motion, 1))); }
.xf-aichat__chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.xf-aichat__chip[disabled] { opacity: .5; cursor: default; }

/* ─── Заявка: карточка и её состояния (#703, #705) ────────────────────────
   Бот называет заявку «карточкой» и говорит клиенту «кнопка под карточкой» —
   так она и выглядит: своя рамка на всю ширину ленты, текст блоками, под ним
   полоса состояния. Что с заявкой, говорит строка состояния (иконка + слова,
   не один цвет): ждёт кнопку, отправляется, отправлена менеджеру, не получилось
   отправить, заменена, разговор истёк. Ждущую выделяет рамка --accent-line
   (Flat-Surface: рамка и тональная ступень, без подсветки и тени). */
.xf-aichat__card {
  /* Не сжимается: лента — flex-колонка, а у блока с overflow:hidden минимальная
     высота нулевая — без этого карточку сплющивало до двух строк. */
  flex: 0 0 auto;
  align-self: stretch;
  display: flex;
  flex-direction: column;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--surface);
  overflow: hidden;
}
.xf-aichat__card[data-state="wait"],
.xf-aichat__card[data-state="fail"],
.xf-aichat__card[data-state="hold"],
.xf-aichat__card[data-state="busy"],
.xf-aichat__card[data-state="bad"] { border-color: var(--accent-line); }
.xf-aichat__card-text {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 12px 14px;
  font-size: 14px;
  line-height: 1.5;
  overflow-wrap: anywhere;
  word-break: break-word;
}
.xf-aichat__card-title {
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: -0.1px;
  text-wrap: balance;
}
/* Блоки текста карточки (описание, «Товары», «Телефон») — абзацами: длинная
   заявка читается, а не стоит стеной. Переносы внутри блока сохраняем. */
.xf-aichat__card-part { white-space: pre-wrap; }
/* Блок-перечень («Товары»): позиции списком с отбивкой — название со ссылкой
   занимает две-три строки, и границу между товарами должно быть видно. Точка
   нарисована, не символ. */
.xf-aichat__card-head { white-space: normal; }
.xf-aichat__card-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 3px 0 0;
  padding: 0;
  list-style: none;
  white-space: normal;
}
.xf-aichat__card-list > li {
  position: relative;
  padding-left: 14px;
}
.xf-aichat__card-list > li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: .68em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--text-3);
}
/* Заменённая заявка отступает: актуальная стоит ниже. */
.xf-aichat__card[data-state="mute"] .xf-aichat__card-text { color: var(--text-2); }
.xf-aichat__card-foot {
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 10px 12px 12px;
  border-top: 1px solid var(--line-soft);
  background: var(--surface-2);
}
.xf-aichat__card-foot[hidden],
.xf-aichat__confirm[hidden],
.xf-aichat__redo[hidden] { display: none; }
/* «Восстановить заявку» под заявкой, которую кнопкой уже не отправить: одна
   кнопка во всю ширину полосы состояния. В состоянии «неизвестно, ушла ли
   заявка» она второстепенная (--ghost) и занимает свою ширину: повторная
   отправка там — выбор клиента, не шаг по умолчанию. */
.xf-aichat__redo { align-self: stretch; display: flex; }
.xf-aichat__card-state {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  font-size: 13px;
  font-weight: 500;
  line-height: 1.4;
  color: var(--text);
}
.xf-aichat__card-icon {
  flex: 0 0 auto;
  display: inline-flex;
  width: 15px;
  height: 15px;
  margin-top: 1.5px;
  color: var(--text-3);
}
.xf-aichat__card[data-state="wait"] .xf-aichat__card-icon,
.xf-aichat__card[data-state="hold"] .xf-aichat__card-icon { color: var(--warning); }
.xf-aichat__card[data-state="ok"] .xf-aichat__card-icon { color: var(--success); }
.xf-aichat__card[data-state="bad"] .xf-aichat__card-icon { color: var(--danger); }
/* Последняя попытка отправить не удалась: слова те же, «Не отправлена», а
   значок и цвет — ошибки, как у «Не доставлено» под сообщением
   (.xf-aichat__undelivered-mark). Причину говорит строка под лентой.
   Полоса состояния при этом — на --surface, не на --surface-2: --danger как
   цвет текста держит AA только на светлой подложке (4.79:1 на --surface,
   4.33 на --surface-2; «Не доставлено» стоит на --bg, 4.59). */
.xf-aichat__card[data-state="fail"] .xf-aichat__card-state,
.xf-aichat__card[data-state="fail"] .xf-aichat__card-icon { color: var(--danger); }
.xf-aichat__card[data-state="fail"] .xf-aichat__card-foot { background: var(--surface); }
.xf-aichat__card[data-state="mute"] .xf-aichat__card-state { color: var(--text-3); font-weight: 400; }
/* «Отправляем заявку…» — тот же спиннер, что у кнопки отправки сообщения. */
.xf-aichat__card-icon.is-spin,
.xf-aichat__bar[data-state="busy"] .xf-aichat__bar-label::before {
  box-sizing: border-box;
  width: 14px;
  height: 14px;
  border: 2px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: xf-aichat-spin .6s linear infinite;
}

/* ─── Заявка: плашка над полем ввода (#705) ───────────────────────────────
   Стоит, пока кнопок ждущей заявки не видно в ленте: «Заявка не отправлена»,
   та же кнопка и путь к карточке. Кнопка слева, как под карточкой (блок
   оценки держит свои кнопки справа). Одна строка: при открытой клавиатуре
   телефона лента и так короткая. Тон — ступень --surface-2 и рамка
   --accent-line, как у прочих выделенных блоков шаблона. */
.xf-aichat__bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-top: 1px solid var(--accent-line);
  background: var(--surface-2);
}
.xf-aichat__bar[hidden],
.xf-aichat__bar-send[hidden],
.xf-aichat__bar-show[hidden],
.xf-aichat.is-gated .xf-aichat__bar { display: none; }
.xf-aichat__bar .xf-aichat__bar-send { flex: 0 0 auto; }
.xf-aichat__bar-info {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
}
.xf-aichat__bar-label {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--text);
}
.xf-aichat__bar[data-state="hold"] .xf-aichat__bar-label,
.xf-aichat__bar[data-state="bad"] .xf-aichat__bar-label { font-weight: 500; }
/* «Заявка отправлена менеджеру»: та же галочка, что в отметке на карточке.
   «Заявка не отправлена» после неудавшейся попытки — тот же значок и цвет
   ошибки, что на карточке. */
.xf-aichat__bar[data-state="ok"] .xf-aichat__bar-label,
.xf-aichat__bar[data-state="fail"] .xf-aichat__bar-label { display: flex; align-items: center; gap: 7px; }
.xf-aichat__bar-icon { flex: 0 0 auto; display: inline-flex; color: var(--success); }
.xf-aichat__bar[data-state="fail"] .xf-aichat__bar-label,
.xf-aichat__bar[data-state="fail"] .xf-aichat__bar-icon { color: var(--danger); }
/* Подложка — как у полосы состояния карточки в этом же тоне (контраст). */
.xf-aichat__bar[data-state="fail"] { background: var(--surface); }
.xf-aichat__bar[data-state="busy"] .xf-aichat__bar-label::before {
  content: "";
  display: inline-block;
  margin-right: 7px;
  vertical-align: -2px;
}
/* «Показать заявку»: строка невысокая, а область нажатия — 40px. Запас берём
   отрицательными полями: он ложится на подпись над ссылкой и на поле плашки под
   ней (там нажимать нечего), а высота плашки не растёт. */
.xf-aichat__bar-show {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-height: 40px;
  margin: -9px 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--accent-2);
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.3;
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
@media (hover: hover) { .xf-aichat__bar-show:hover { text-decoration: none; } }
/* Открытая клавиатура телефона: окно низкое — плашка ужимается, лента важнее.
   Под лентой при этом стояло бы четыре полосы: «Нужен живой человек?», плашка,
   поле ввода, строка о данных. Пока стоит плашка заявки, строка «Нужен живой
   человек?» уступает место: заявка сейчас важнее, а менеджера клиент позовёт,
   когда плашка уйдёт (.has-request-bar ставит paintBar). */
@media (max-height: 520px) {
  .xf-aichat__bar { padding: 6px 12px; }
  .xf-aichat__bar .xf-aichat__bar-send { min-height: 36px; padding: 8px 12px; }
  .xf-aichat.has-request-bar .xf-aichat__escalate { display: none; }
}

/* ─── Строка окна, которую нельзя пропустить (#705) ───────────────────────
   «Разговор истёк, заявка не отправлена…»: не серая подпись в 12px, а блок с
   иконкой — клиент должен узнать, что заявка не ушла. */
.xf-aichat__alert {
  flex: 0 0 auto;
  align-self: stretch;
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  font-size: 13px;
  line-height: 1.45;
  color: var(--text);
}
.xf-aichat__alert-icon { flex: 0 0 auto; display: inline-flex; margin-top: 1px; color: var(--danger); }

/* ─── Кнопки заявки и общая кнопка панели ─────────────────────────────── */
.xf-aichat__confirm {
  align-self: stretch;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.xf-aichat__btn {
  flex: 1 1 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 40px;
  padding: 10px 14px;
  border-radius: 11px;
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 600;
  cursor: pointer;
  /* Кнопкой бывает и <a> (мост в Telegram из карточки сбоя) — без подчёркивания. */
  text-decoration: none;
  transition: transform .12s ease, box-shadow .15s ease, background-color .15s ease, border-color .15s ease;
}
.xf-aichat__btn--primary {
  background: var(--accent);
  color: #fff;
  border: 1px solid transparent;
  box-shadow: 0 6px 18px -8px var(--accent);
}
/* Тёмная (#857): цветной тени у кнопок нет — блик по верхней кромке, как у
   .xf-btn (DESIGN.md, The Night-Sign Rule). */
html[data-theme="dark"] .xf-aichat__btn--primary { box-shadow: var(--nx-key); }
@media (hover: hover) { .xf-aichat__btn--primary:hover { transform: translateY(calc(-1px * var(--xf-motion, 1))); } }
.xf-aichat__btn--ghost {
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--line);
  flex: 0 1 auto;
}
@media (hover: hover) { .xf-aichat__btn--ghost:hover { background: var(--surface-2); } }
.xf-aichat__btn:active { transform: scale(calc(1 - 0.02 * var(--xf-motion, 1))); }
.xf-aichat__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.xf-aichat__btn[disabled] { opacity: .55; cursor: default; transform: none; }

/* ─── Системная строка (диалог истёк, спасибо за оценку) ───────────────── */
.xf-aichat__system {
  align-self: center;
  max-width: 90%;
  text-align: center;
  font-size: 12px;
  color: var(--text-3);
  padding: 4px 12px;
}

/* ─── Несостоявшееся сообщение: пометка, «Повторить», причина (issue #701) ──
   Сообщение, на которое помощник не ответил, остаётся в ленте; под пузырём, по
   его же правому краю, — пометка и кнопка. Пузырь не трогаем: это всё то же
   сообщение клиента, о сбое говорит строка под ним.
   Пометка — Signal Danger мелким текстом с иконкой (цвет не единственный
   носитель: есть слова). Кнопка нейтральная, не accent: на экране их бывает
   несколько подряд, а индиго держим для пузырей клиента и отправки (One-Voice,
   Single-Primary — primary здесь по-прежнему «отправить»). Плоская, без тени
   (Flat-By-Default); радиус кнопочный, не pill (Pill-Is-Not-Button). Фокус —
   глобальный :focus-visible (ui.css), своих :focus-правил нет. */
.xf-aichat__undelivered {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 4px 10px;
  max-width: 100%;
  margin-top: 5px;
}
.xf-aichat__undelivered-mark {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.3;
  color: var(--danger);
}
.xf-aichat__undelivered-icon { display: inline-flex; flex: 0 0 auto; }
.xf-aichat__retry {
  flex: 0 0 auto;
  min-height: 30px;
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, transform .1s ease;
}
@media (hover: hover) {
  .xf-aichat__retry:hover { background: var(--surface-2); }
}
.xf-aichat__retry:active { transform: scale(calc(1 - 0.03 * var(--xf-motion, 1))); }
.xf-aichat__retry[disabled] { opacity: .5; cursor: default; transform: none; }
/* Подряд идущие реплики одного автора стянуты (-4px, см. выше). Под пометкой
   это прижало бы следующий пузырь к чужой пометке — непонятно, к какому
   сообщению она относится. Отодвигаем: пометка держится за свой пузырь. */
.xf-aichat__row.is-undelivered + .xf-aichat__row { margin-top: 4px; }
/* Строка о сбое — последней в ленте, в духе системной строки: тот же размер и
   центр, но тон text-2 — её читают, чтобы решить, что делать. В ней причина
   последнего сбоя и, после повторных, другой способ связаться: телефон и
   Telegram магазина. Телефон — действие, а не данные: Onest, не mono
   (Mono-as-Data); цифры ровные. Каждый способ — неразрывной группой: строка
   ломается между «позвоните …» и «или напишите …», а не посреди номера. */
.xf-aichat__failnote {
  align-self: center;
  max-width: 92%;
  padding: 2px 12px 4px;
  text-align: center;
  /* Две короткие строки вместо длинной и «раз.» под ней; где свойства нет —
     обычный перенос. */
  text-wrap: balance;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-2);
}
/* Причина: предложение — группой, строка ломается между предложениями, а не
   посреди первого; внутри группы перенос остаётся, если она шире ленты. */
.xf-aichat__failnote-part { display: inline-block; }
/* Шаг строк блока задан явно и равен высоте ссылки вместе с её полями: строки
   идут ровно, а зоны нажатия соседних ссылок не налезают друг на друга. Ссылка
   поэтому строчная — поля inline-block раздвигали только «свои» строки, и
   между второй и третьей шаг был больше, чем между первой и второй. */
.xf-aichat__failnote-alt { margin-top: 2px; line-height: 24px; }
.xf-aichat__failnote-way { display: inline-block; white-space: nowrap; }
.xf-aichat__failnote-link {
  padding: 3px 0;
  color: var(--text);
  font-family: var(--font-sans);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-decoration: underline;
  text-underline-offset: 2px;
}
@media (hover: hover) { .xf-aichat__failnote-link:hover { text-decoration: none; } }

/* ─── Оценка помощника после заявки (issue #81 бот-репо) ──────────────────
   В духе чипсов/кнопок: нейтральная поверхность (оценка — не CTA), accent
   приходит только на hover/focus. Тёмная тема — через те же токены. */
.xf-aichat__rate {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 2px;
  padding: 12px;
  border: 1px solid var(--line-soft);
  border-radius: 12px;
  background: var(--surface);
}
.xf-aichat__rate-prompt {
  font-size: 13px;
  color: var(--text-2);
  text-align: center;
}
/* Вопрос об оценке — одной строкой: слова слева, кнопки справа. Блок встаёт в
   конец ленты сразу после отправки заявки, а кнопка отправки стоит слева —
   под палец, который её только что нажал, попадают слова, а не оценка (#705). */
.xf-aichat__rate.is-ask {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}
.xf-aichat__rate.is-ask .xf-aichat__rate-prompt { text-align: left; }
.xf-aichat__rate.is-ask .xf-aichat__rate-actions { flex: 0 0 auto; }
.xf-aichat__rate.is-ask .xf-aichat__rate-btn { min-width: 46px; padding: 8px 10px; }
/* Первую секунду после появления блок не берёт нажатий (случайный тап пальцем,
   который только что нажал «Отправить заявку») — и это видно: кнопки тусклые. */
.xf-aichat__rate-btn { transition: opacity .18s ease; }
.xf-aichat__rate.is-arming .xf-aichat__rate-btn { opacity: .4; cursor: default; }
.xf-aichat__rate-actions {
  display: flex;
  gap: 8px;
  justify-content: center;
}
/* 👍/👎 — крупная тап-цель, нейтральная рамка; accent только на hover/focus. */
.xf-aichat__rate-btn {
  flex: 0 0 auto;
  min-width: 56px;
  min-height: 40px;
  padding: 8px 16px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background-color .15s ease, border-color .15s ease, transform .1s ease;
}
@media (hover: hover) {
  .xf-aichat__rate-btn:hover { background: var(--accent-dim); border-color: var(--accent); }
}
.xf-aichat__rate-btn:active { transform: scale(calc(1 - 0.06 * var(--xf-motion, 1))); }
.xf-aichat__rate-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Поле причины (👎-путь) — как композер, но компактнее и внутри блока. */
.xf-aichat__rate-input {
  width: 100%;
  resize: none;
  padding: 9px 12px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.45;
  transition: border-color .15s ease, box-shadow .15s ease;
}
.xf-aichat__rate-input::placeholder { color: var(--text-3); }
/* [#406] Свечение --accent-dim (10% альфы) здесь и у .xf-aichat__field ниже —
   не индикатор фокуса; индикатор это глобальное кольцо ui.css. Не гасить. */
.xf-aichat__rate-input:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}

/* ─── Карточка ошибки (unavailable на старте) ─────────────────────────── */
.xf-aichat__fault {
  margin: auto 4px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 12px;
  padding: 24px 20px;
}
.xf-aichat__fault-icon {
  width: 46px;
  height: 46px;
  border-radius: 13px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  color: var(--text-3);
}
.xf-aichat__fault-title { font-size: 15px; font-weight: 600; }
.xf-aichat__fault-text { font-size: 13px; line-height: 1.5; color: var(--text-2); max-width: 30ch; }
.xf-aichat__fault .xf-aichat__btn { flex: 0 0 auto; padding: 10px 18px; }
/* Телефон и Telegram магазина под кнопками карточки (#701): та же строка, что
   под лентой после повторных сбоев (.xf-aichat__failnote-alt). */
.xf-aichat__fault-ways { font-size: 12px; color: var(--text-2); text-wrap: balance; }
.xf-aichat__fault-ways .xf-aichat__failnote-alt { margin-top: 0; }

/* ─── Эскалация к живому менеджеру (issue #187) ───────────────────────────
   Тихая строка над композером: при живом диалоге кружок открывает помощника
   минуя меню каналов, поэтому живого менеджера даём отсюда. НЕЙТРАЛЬНАЯ (не
   accent): индиго держим для приоритетного бота — так же, как менеджерский чип
   в меню (chat.css) держит серый против индиго бота (робот/человек — мгновенный
   контраст). Клик закрывает помощника и дёргает тот же ленивый B24, что пункт
   меню. Фокус — глобальный :focus-visible (ui.css), своих :focus-правил нет. */
.xf-aichat__escalate {
  flex: 0 0 auto;
  padding: 6px 10px;
  border-top: 1px solid var(--line-soft);
  background: var(--surface);
}
.xf-aichat__escalate-btn {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 7px 10px;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--text-2);
  font: inherit;
  font-size: 12.5px;
  line-height: 1.2;
  text-align: left;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
@media (hover: hover) {
  .xf-aichat__escalate-btn:hover { background: var(--surface-2); color: var(--text); }
}
.xf-aichat__escalate-btn:active { background: var(--surface-3); }
.xf-aichat__escalate-icon { flex: 0 0 auto; display: inline-flex; color: var(--text-2); }
.xf-aichat__escalate-cta { margin-left: auto; font-weight: 600; color: var(--text); }

/* Загрузка B24 после клика (#187, ревью): строка сама показывает «Зовём
   менеджера…» + спиннер вместо гарнитуры; клик заблокирован (повтор — no-op,
   JS-гард + pointer-events), панель закрывается только по подтверждённому
   открытию окна. aria-disabled, не disabled — фокус с кнопки не сбрасывается.
   Спиннер — из нейтральных токенов, как у пункта меню (chat.css). */
.xf-aichat__escalate-btn.is-loading { pointer-events: none; }
.xf-aichat__escalate-btn.is-loading .xf-aichat__escalate-icon svg { display: none; }
.xf-aichat__escalate-btn.is-loading .xf-aichat__escalate-icon::after {
  content: "";
  width: 13px;
  height: 13px;
  border: 2px solid var(--line);
  border-top-color: var(--text-2);
  border-radius: 50%;
  animation: xf-aichat-spin .6s linear infinite;
}

/* ─── Композер (ввод) ─────────────────────────────────────────────────── */
.xf-aichat__composer {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  /* Нижний safe-area несёт .xf-aichat__foot — он всегда под композером, когда тот
     виден (оба прячет только is-gated). Иначе инсет считался бы дважды. */
  padding: 10px 12px;
  border-top: 1px solid var(--line-soft);
  background: var(--surface);
}
.xf-aichat__field {
  flex: 1 1 auto;
  min-width: 0;
  resize: none;
  max-height: 112px; /* ~4 строки; авто-рост считает js/aichat.js */
  padding: 10px 14px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.45;
  transition: border-color .15s ease, box-shadow .15s ease, background-color .15s ease;
}
.xf-aichat__field::placeholder { color: var(--text-3); }
.xf-aichat__field:focus {
  border-color: var(--accent);
  background: var(--surface);
  box-shadow: 0 0 0 3px var(--accent-dim);
}
.xf-aichat__field:disabled { opacity: .6; cursor: not-allowed; }

.xf-aichat__send {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  border-radius: 12px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 6px 18px -10px var(--accent);
  transition: transform .12s ease, background-color .15s ease, opacity .15s ease;
}
@media (hover: hover) { .xf-aichat__send:hover { transform: translateY(calc(-1px * var(--xf-motion, 1))); } }
.xf-aichat__send:active { transform: scale(calc(1 - 0.06 * var(--xf-motion, 1))); }
.xf-aichat__send:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.xf-aichat__send:disabled {
  background: var(--surface-3);
  color: var(--text-3);
  box-shadow: none;
  cursor: default;
  transform: none;
}
/* Тёмная (#857): блик по кромке вместо цветной тени, как у .xf-btn. Выключенная
   стрелка остаётся без тени: без :not(:disabled) правило (0-2-1) перебило бы
   :disabled выше (0-2-0). */
html[data-theme="dark"] .xf-aichat__send:not(:disabled) { box-shadow: var(--nx-key); }
/* Иконка «отправить» → спиннер, пока ждём ответ. */
/* flex, не инлайн: SVG в плоском span стоит на текстовой базовой линии
   с зазором под descender — стрелка съезжала выше центра кнопки (#174). */
.xf-aichat__send-arrow { display: inline-flex; }
.xf-aichat__send.is-busy .xf-aichat__send-arrow { display: none; }
.xf-aichat__send:not(.is-busy) .xf-aichat__send-spin { display: none; }
.xf-aichat__send-spin {
  width: 17px;
  height: 17px;
  border: 2px solid oklch(1 0 0 / 0.35);
  border-top-color: #fff;
  border-radius: 50%;
  animation: xf-aichat-spin .6s linear infinite;
}

/* ─── Экран согласия на обработку ПДн (закон РБ №99-З, issue #193) ─────────
   Первый экран панели: активное согласие ДО обработки. is-gated отдаёт всё место
   под шапкой этому экрану (лента/эскалация/композер/футер-ссылка спрятаны).
   Компактная форма (решение владельца 2026-07-13): одна фраза со ссылкой на
   политику, прижатая вниз к CTA. Поверхность — var(--bg), как у ленты
   (Card-On-Cream: primary-кнопка снизу на surface).
   ⚠️ `is-gated` — не только косметика: по нему счётчик согласия в js/aichat.js
   (#358, closePanel) отличает «закрыли экран согласия» от «закрыли диалог».
   Переименуешь класс — метрика отказов молча занулится. */
.xf-aichat__gate {
  display: none;
  flex-direction: column;
  min-height: 0;
  background: var(--bg);
}
.xf-aichat.is-gated .xf-aichat__gate { display: flex; flex: 1 1 auto; }
.xf-aichat.is-gated .xf-aichat__body,
.xf-aichat.is-gated .xf-aichat__escalate,
.xf-aichat.is-gated .xf-aichat__composer,
.xf-aichat.is-gated .xf-aichat__foot { display: none; }
.xf-aichat__gate-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 18px 18px 14px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end; /* фраза у CTA, воздух сверху (компактная форма) */
  gap: 11px;
}
/* [#702] «Чем помогу» — над юридической фразой: с прямым входом экран согласия
   первое, что видит нажавший кружок. Блок стоит вместе с фразой у кнопки, воздух
   сверху — компактная форма #193; разнести их по краям значило оставить на
   телефоне дыру в 340 px посреди экрана (критика #702).
   Иконки нейтральные: индиго на экране один — кнопка согласия (One-Voice). */
.xf-aichat__gate-intro { margin-bottom: 8px; }
.xf-aichat__gate-title {
  margin: 0 0 12px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.2px;
  color: var(--text);
}
.xf-aichat__gate-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.xf-aichat__gate-item {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 14px;
  line-height: 1.4;
  color: var(--text);
}
.xf-aichat__gate-icon {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  color: var(--text-2);
}
.xf-aichat__gate-ask {
  margin: 0;
  padding: 9px 12px;
  border-radius: 10px;
  background: var(--accent-dim);
  color: var(--text);
  font-size: 13.5px;
  line-height: 1.45;
}
.xf-aichat__gate-ask[hidden] { display: none; }
.xf-aichat__gate-text {
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--text-2);
  overflow-wrap: anywhere; /* длинная t.me-ссылка не распирает узкую панель */
}
.xf-aichat__gate-link {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.xf-aichat__gate-link:hover { text-decoration: none; }
/* Тёмная (#858): как у ссылки в ответе — фиолет текста (на --bg 10.4:1
   вместо 3.9:1 у --accent). */
html[data-theme="dark"] .xf-aichat__gate-link { color: var(--accent-2); }
/* Подвал экрана: primary-CTA на surface, отделён линией от скролл-текста. */
.xf-aichat__gate-foot {
  flex: 0 0 auto;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--line-soft);
  background: var(--surface);
}
.xf-aichat__gate-accept { width: 100%; }
/* [#702] «Нужен живой человек? Позвать менеджера» — и до согласия: тихой строкой
   под кнопкой, без своей линии и подложки (подвал экрана уже на surface).
   Правило общего is-gated выше прячет строку над композером; эту — показываем. */
.xf-aichat.is-gated .xf-aichat__escalate--gate {
  display: block;
  margin: 10px -10px -4px;
  padding: 0;
  border-top: none;
  background: transparent;
}
/* На телефоне — под палец: строка не тоньше 40px и не вплотную к кнопке
   согласия (промах по ней — другое действие с другими последствиями). */
@media (max-width: 640px) {
  .xf-aichat.is-gated .xf-aichat__escalate--gate { margin-top: 12px; }
  .xf-aichat__escalate--gate .xf-aichat__escalate-btn { min-height: 40px; }
}

/* ─── Футер панели: тихая ссылка на политику (доступна и после согласия, #193) ─
   Постоянная мелкая ссылка под композером. Несёт нижний safe-area (композер его
   отдал — см. .xf-aichat__composer). Фокус — глобальный :focus-visible. */
.xf-aichat__foot {
  flex: 0 0 auto;
  padding: 7px 12px calc(9px + env(safe-area-inset-bottom, 0px));
  text-align: center;
  border-top: 1px solid var(--line-soft);
  background: var(--surface);
}
.xf-aichat__policy {
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-3);
  text-decoration: underline;
  text-underline-offset: 2px;
}
@media (hover: hover) { .xf-aichat__policy:hover { color: var(--text-2); } }

/* ─── Локальный toast (rate_limited) — свой, showToast из app.js в IIFE ── */
.xf-aichat__toast {
  position: absolute;
  left: 16px;
  right: 16px;
  /* Над стеком «композер + футер-ссылка» (~101px) с зазором: футер приподнял
     композер, иначе тост частично уходил под поле ввода (#193). */
  bottom: 120px;
  z-index: 2;
  padding: 10px 14px;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-card);
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  text-align: center;
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity .2s ease, transform .2s ease;
}
.xf-aichat__toast.is-shown { opacity: 1; transform: translateY(0); }

@keyframes xf-aichat-spin { to { transform: rotate(360deg); } }

/* ─── Reduced motion ──────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .xf-aichat,
  .xf-aichat__panel,
  .xf-aichat__typing-dots span,
  .xf-aichat__typing-label,
  .xf-aichat__send-spin,
  .xf-aichat__escalate-icon::after,
  .xf-aichat__card-icon.is-spin,
  .xf-aichat__bar[data-state="busy"] .xf-aichat__bar-label::before,
  .xf-aichat__toast { animation: none !important; transition: none !important; }
  .xf-aichat.is-open .xf-aichat__panel,
  .xf-aichat.is-closing .xf-aichat__panel { animation: none; }
}

/* ─── Мобайл: на весь экран с безопасными отступами ───────────────────── */
@media (max-width: 640px) {
  .xf-aichat {
    right: 0;
    bottom: 0;
    left: 0;
    top: 0;
    width: 100%;
    max-width: none;
    height: 100%;
    /* dvh (где поддержан) сжимается вместе с экранной клавиатурой — композер
       остаётся видимым НАД ней, а не прятался под (скрины владельца, iOS).
       top:0 + height задают геометрию, bottom игнорируется — так и задумано. */
    height: 100dvh;
    max-height: none;
  }
  /* Consent-бар поднят (z 9999, во всю ширину низа): окно кончается у его
     верхнего края, а не под ним. Раньше окно оставалось на весь экран, и бар
     лежал поверх его низа — под ним оказывались поле ввода, строка о данных и
     плашка заявки, вся целиком (замер на 375 и 390 px, бар 162 px). С тех пор
     как заявку отправляет только кнопка (#703), спрятанная плашка — потерянная
     заявка у каждого, кто бар не закрыл (#705).
     Высоту бара ведёт сайт: --xf-cookie-h и класс ставит app.js
     (initBotCookieClearance) и пересчитывает при переносе текста и раскрытии
     «Настроить»; своих чисел тут нет. Режем max-height, а не height: высотой и
     верхом окна на мобайле владеет js/aichat.js (syncMobileViewport — инлайном,
     от visualViewport, чтобы композер стоял над клавиатурой), потолок из CSS
     действует поверх него. Клавиатура открыта — окно и так ниже потолка, а бар
     остался под клавиатурой: ничего не меняется. Запасное 0px: переменной нет —
     окно как раньше, а не с дырой под собой. vh — для браузеров без dvh.
     Десктопный подъём (bottom) на мобайле не нужен: геометрию задают top и
     высота. */
  body.xf-cookie-up .xf-aichat {
    bottom: 0;
    max-height: calc(100vh - var(--xf-cookie-h, 0px));
    max-height: calc(100dvh - var(--xf-cookie-h, 0px));
  }
  .xf-aichat__panel {
    border-radius: 0;
    border: 0;
    /* верхний safe-area gutter (чёлка) — паддингом шапки. */
  }
  /* Фуллскрин закрываем мягким уходом вниз (не scale-в-угол, см. #187). */
  .xf-aichat.is-closing .xf-aichat__panel { animation: xf-aichat-out-m .18s ease forwards; }
  .xf-aichat__header { padding-top: calc(12px + env(safe-area-inset-top, 0px)); }
  .xf-aichat__row { max-width: 90%; }
  /* 16px, не 14: iOS Safari принудительно ЗУМИТ страницу при фокусе поля со
     шрифтом мельче 16px — владелец ловил «приблизило, надо отзумивать». */
  .xf-aichat__field,
  .xf-aichat__rate-input { font-size: 16px; }
  /* «Повторить» и ссылки под причиной сбоя (#701) — под палец, а не под курсор. */
  .xf-aichat__retry { min-height: 36px; padding: 7px 13px; font-size: 13px; }
  .xf-aichat__failnote,
  .xf-aichat__fault-ways { font-size: 12.5px; }
  .xf-aichat__failnote-alt { line-height: 30px; }
  .xf-aichat__failnote-link { padding: 6px 0; }
  /* Подложка под панелью: при анимации клавиатуры iOS панель на кадры
     разъезжается с видимой областью — фон гасит мелькание каталога в щели. */
  .xf-aichat { background: var(--bg); }
}

/* Замок скролла страницы под открытой панелью (ставит/снимает js/aichat.js,
   только мобайл): iOS иначе скроллит САЙТ под клавиатуру, и в зазоре между
   панелью и клавиатурой мелькал каталог (скрин владельца). top задаётся
   инлайном (-scrollY), позиция восстанавливается при снятии. Медиа-обёртка —
   второй рубеж к JS-реконсиляции: даже залипший класс не заморозит десктоп. */
@media (max-width: 640px) {
  body.xf-aichat-lock {
    position: fixed;
    left: 0;
    right: 0;
    width: 100%;
    overflow: hidden;
  }
}

/* End */


/* Start:/local/templates/xcore_front/css/catalog-hub.css?179264122822319*/
/* OWNS: .xf-dept*, .xf-l1hub* (хаб каталога, issue #362), включая
   .xf-l1hub-tile--empty (плитка подраздела без товаров); внутри
   .xf-l1hub-grid — документированные override'ы .xf-catgrid,
   .xf-cat-tile__name и .xf-cat-tile__count (только тёмная тема); внутри
   .xf-l1hub-chiprow — верхнее поле полос .xf-brand-chips/.xf-facet-chips
   (сами полосы и их телефонная прокрутка — catalog.css, #646) */

/* Разметку под эти классы рисуют include/catalog/landing.php (корень каталога)
   и include/catalog/section.php (L1-раздел). Файл подключается последним в
   списке header.php — переопределения .xf-catgrid внутри .xf-l1hub-grid
   рассчитывают на то, что catalog.css и catsection.css уже прочитаны.

   Зачем: хаб каталога — не витрина, а развилка. Посетитель приходит уже зная,
   что ему нужно, и думает словами второго уровня («видеокарта», «SSD»,
   «роутер»), а не первого («Комплектующие для компьютеров»). Плитка-департамент
   поднимает эти слова на хаб: один экран — 17 разделов и ~70 их подразделов
   прямыми ссылками, без промежуточного клика-перевода.                       */

/* ─── Сетка департаментов (корень /catalog/) ────────────────────────────── */
.xf-dept-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px;
}

.xf-dept {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
  overflow: hidden;
  transition: border-color .2s, transform .2s, box-shadow .2s;
}
/* Крупнейшие разделы занимают две колонки: вес плитки следует за реальным
   объёмом раздела, а не за одинаковой сеткой. Побочно закрывает рваный
   последний ряд — 3 двойные + 14 одиночных = ровно 5 рядов по 4. */
.xf-dept--wide { grid-column: span 2; }

@media (hover: hover) {
  .xf-dept:hover {
    border-color: var(--accent-line);
    transform: translateY(calc(-2px * var(--xf-motion, 1)));
    box-shadow: var(--shadow-card);
  }
  /* [#323-урок] В тёмной теме длинная тень долетает до соседнего ряда и
     мерцает — здесь она короче и мягче, как у .xf-cat-tile. */
  html[data-theme="dark"] .xf-dept:hover {
    box-shadow: 0 8px 20px -12px oklch(0.05 0.03 280 / 0.35);
  }
}

/* Плитка обещает кликабельность всем телом: на hover она целиком
   приподнимается, светится рамкой и стрелкой. Замер живого /dev/ 2026-08-06:
   ссылкой была только шапка — 76 px из 189, — а на остальной плитке стоял
   `cursor: auto`, и попадание мимо шапки не делало ничего. Растягиваем ссылку
   шапки на всю плитку; словарь подразделов поднимаем над этим слоем, чтобы
   его собственные ссылки продолжали работать. */
.xf-dept { position: relative; }
.xf-dept__head::after { content: ''; position: absolute; inset: 0; }
/* Поднимаем над накладкой САМИ ссылки, а не список: подняв `ul`, мы вместе с
   именами подразделов подняли бы и пустоту справа от них — проверено, клик по
   середине плитки снова уходил в никуда. */
.xf-dept__link { position: relative; z-index: 1; }

/* ─── Шапка плитки: сам раздел ──────────────────────────────────────────── */
.xf-dept__head {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 18px;
  color: var(--text);
  text-decoration: none;
  min-height: 0;
}
/* Иконка раздела: нейтральная плашка (Neutral-Placeholder Rule — различие
   плиток несёт форма иконки, не цвет). */
.xf-dept__icon {
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--surface-3);
  border: 1px solid var(--line-soft);
  color: var(--text-2);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background .25s, border-color .25s, color .25s;
}
.xf-dept__icon svg { width: 24px; height: 24px; }
.xf-dept__icon img {
  max-width: 70%;
  max-height: 70%;
  object-fit: contain;
}
@media (hover: hover) {
  .xf-dept:hover .xf-dept__icon {
    background: var(--accent);
    border-color: transparent;
    color: #fff;
  }
}

/* Раздел без подразделов (плоский L1 — «Умный дом», «Электросамокаты»):
   шапка занимает плитку целиком и центрируется, иначе под ней зияет пустота
   в ряду, высоту которого задал сосед со списком. */
.xf-dept:not(:has(.xf-dept__links)) .xf-dept__head { flex: 1 1 auto; }

.xf-dept__label {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* Имя раздела — первое и самое весомое в плитке (и в DOM: скринридер читает
   «Комплектующие для компьютеров, 7679 товаров», а не наоборот). */
.xf-dept__name {
  margin: 0;              /* это h3 — сбрасываем браузерные поля заголовка */
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.3px;
  line-height: 1.25;
}
.xf-dept--wide .xf-dept__name { font-size: 18px; }
.xf-dept__count {
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--text-3);
}
.xf-dept__arrow {
  flex: 0 0 auto;
  color: var(--text-3);
  display: flex;
  transition: color .2s, transform .2s;
}
@media (hover: hover) {
  .xf-dept:hover .xf-dept__arrow {
    color: var(--accent);
    transform: translate(calc(1px * var(--xf-motion, 1)), calc(-1px * var(--xf-motion, 1)));
  }
}

/* ─── Подразделы: словарь посетителя прямыми ссылками ───────────────────── */
.xf-dept__links {
  display: flex;
  flex-wrap: wrap;
  align-content: flex-start;
  /* Разделяет расстояние, а не пунктуация. Точка-разделитель здесь пробовалась
     и проиграла: в узкой плитке каждое имя занимает свою строку, и точка
     повисает хвостом на конце каждой (а ведущая — маркером в начале). Зазор в
     16px против словесного пробела в 3-4px читается однозначно и не шумит. */
  gap: 3px 16px;
  margin: 0;
  padding: 0 18px 16px;
  list-style: none;
}
.xf-dept__link {
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-2);
  text-decoration: none;
  transition: color .15s;
}
.xf-dept__link:hover { color: var(--accent-2); text-decoration: underline; }
/* «ещё N» отличается ФОРМОЙ, а не оттенком. Оттенка не хватило: в тёмной теме
   ниже мы поднимаем и счётчик, и «ещё» до --text-2 ради контраста — то есть
   ровно до цвета соседних ссылок, и в списке «Аккумуляторы для ИБП  ещё 11»
   последнее слово читалось как ещё одна категория. Подчёркивание видно в
   обеих темах и говорит «эта ссылка про другое».
   Подчёркивание СПЛОШНОЕ, а не пунктирное: в рунете пунктир — устоявшаяся
   метка «раскроется на месте / подсказка», а «ещё N» уводит на страницу
   раздела. Отличие от соседей всё равно держится — у них подчёркивания нет
   вовсе, оно появляется только на наведении. */
.xf-dept__more {
  color: var(--text-3);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* Список гейтился одной шириной экрана, а планшет в портрете — это 768px:
   пальцу доставались цели высотой 17px с зазором 7.5px. Ширина не говорит
   о том, чем по экрану попадают; спрашиваем про палец напрямую и добираем
   строке высоту до 25px — это уже выше порога 24×24 (WCAG 2.5.8). */
@media (pointer: coarse) {
  .xf-dept__links { gap: 6px 16px; }
  .xf-dept__link { padding: 4px 0; }
}

/* Замерено на живой странице: --text-3 на карточке даёт 4.85:1 в светлой теме
   (AA проходит) и 4.28:1 в тёмной (не проходит для мелкого текста). Поэтому
   тише делаем только там, где это безопасно, а в тёмной поднимаем на ступень.
   Правится точечно здесь, а не в токенах: --text-3 живёт по всему шаблону,
   его сдвиг — отдельное решение, не побочный эффект правки хаба.
   [#708] 4.28:1 — замер прежней тёмной темы. В «Ночной вывеске» (#739)
   --text-3 на карточке даёт 6.6:1, AA проходит и без подъёма; правило живое
   и держит прежний вид (--text-2, 10.2:1) — снять его значит сменить вид. */
html[data-theme="dark"] .xf-dept__count,
html[data-theme="dark"] .xf-dept__more { color: var(--text-2); }

/* Планшет: колонок меньше, словарь остаётся — места под него хватает.
   Двойную плитку на трёх колонках снимаем. Приём вводился, чтобы закрыть
   рваный последний ряд на четырёх колонках, а на трёх он делает ровно
   обратное: плитка шириной в две ячейки не пускает к себе в ряд вторую
   такую же, та переносится, и в первых двух рядах остаются зияющие
   третьи ячейки — плюс сирота в конце. На двух колонках (768–899px)
   span 2 — это целый ряд, дырки не возникает, поэтому там оставляем. */
@media (min-width: 900px) and (max-width: 1199px) {
  .xf-dept-grid { grid-template-columns: repeat(3, 1fr); }
  .xf-dept--wide { grid-column: auto; }
  .xf-dept--wide .xf-dept__name { font-size: 16px; }
}
@media (min-width: 768px) and (max-width: 899px) {
  .xf-dept-grid { grid-template-columns: repeat(2, 1fr); }
}

/* ─── Телефон: словарь остаётся там, где он помещается ──────────────────
   Телефон — главная сцена покупки (PRODUCT.md), поэтому прятать на нём весь
   словарь второго уровня — значит отдать главному сценарию ровно ничего. Но и класть
   его во все 17 плиток нельзя: узкая плитка на 390 px — это 171 px, каждое
   имя заняло бы там свою строку.
   Компромисс по ширине: три крупнейших раздела и так идут во весь экран
   (356 px) — им словарь достаётся; остальные остаются компактными дверьми.
   Плата — около 120 px на страницу, и именно за товаром этих трёх разделов
   сюда чаще всего и приходят с телефона.
   Брейкпоинт 767px — тот же, на котором переключается вся сетка каталога. */
@media (max-width: 767px) {
  .xf-dept-grid { grid-template-columns: repeat(2, 1fr); gap: 12px; }
  .xf-dept--wide { grid-column: span 2; }
  .xf-dept:not(.xf-dept--wide) .xf-dept__links { display: none; }
  .xf-dept--wide .xf-dept__links { padding: 0 14px 14px; gap: 2px 14px; }
  .xf-dept__arrow { display: none; }
  .xf-dept__head { flex: 1 1 auto; padding: 14px; gap: 10px; }
  .xf-dept__icon { width: 36px; height: 36px; border-radius: 10px; }
  .xf-dept__icon svg { width: 20px; height: 20px; }
  .xf-dept__name { font-size: 14px; }
  /* В узкой плитке иконка и имя в одну строку не помещаются: «Видеонаблюдение»
     — одно длинное слово, оно не переносится и обрезается многоточием. Ставим
     иконку отдельной строкой — имя получает всю ширину карточки. Широкая
     плитка занимает весь ряд, там строки хватает и колонка не нужна. */
  .xf-dept:not(.xf-dept--wide) .xf-dept__head {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
  .xf-dept--wide .xf-dept__name { font-size: 15px; }
  .xf-dept__name { overflow-wrap: break-word; hyphens: auto; }
}

/* ─── L1 sub-hub (section.php): сетка подразделов без сироты ────────────
   Базовая .xf-catgrid жёстко даёт 4 колонки, поэтому пять подразделов
   раскладываются как 4 + 1 — последний ряд читается как обрыв. Число колонок
   здесь считает PHP по числу детей (--xf-l1cols) и оставляет ровные ряды.
   Это осознанный override колонок .xf-catgrid, а не случайный дубль каскада:
   класс ставит только сетка подразделов в section.php.                      */
.xf-l1hub-grid { grid-template-columns: repeat(var(--xf-l1cols, 4), 1fr); }
/* Если сирота всё же остаётся (n % колонок == 1), последняя плитка занимает
   две ячейки — ряд закрывается, а не обрывается. */
.xf-l1hub-grid--stretch-last > :last-child { grid-column: span 2; }
/* Ниже десктопа число колонок диктует ширина экрана, а не число подразделов:
   плитка с фото товара уже узкой 240px не читается. Растяжку сироты тоже
   снимаем — на 2-3 колонках она бы вернула рваный ряд. */
@media (min-width: 768px) and (max-width: 1099px) {
  .xf-l1hub-grid { grid-template-columns: repeat(3, 1fr); }
  .xf-l1hub-grid--stretch-last > :last-child { grid-column: auto; }
}
/* Тот же порог в 240px нарушала и сама пятиколоночная сетка: PHP считает
   колонки по числу подразделов и о ширине экрана не знает, поэтому на
   1100–1335px плитка падала до 193px — УЖЕ, чем она же на экране поменьше,
   где работает трёхколоночное правило выше. Пятая колонка окупается только
   с 1336px ((1336−80−4×14)/5 = 240); ниже складываемся в те же три колонки,
   что и до 1100px, и порог держится на всей ширине. */
@media (min-width: 1100px) and (max-width: 1335px) {
  .xf-l1hub-grid--five { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 767px) {
  .xf-l1hub-grid { grid-template-columns: repeat(2, 1fr); }
  .xf-l1hub-grid--stretch-last > :last-child { grid-column: auto; }
}

/* Имя подраздела резервирует две строки и на кураторской обложке (--static).
   home.css делает такой резерв только для фото-обложки (--cover), поэтому в
   ряду, где у одной плитки имя в две строки, счётчики соседей съезжали по
   вертикали вразнобой. Осознанный override внутри сетки хаба, не дубль.
   Только с 768px: ниже этой ширины `.xf-cat-tile__name` уже настроен в
   catalog.css (свой line-clamp и line-height для телефона), и правило без
   медиазапроса перебивало его по специфичности — одна и та же плитка
   получала бы разную типографику в зависимости от того, в какой она сетке. */
@media (min-width: 768px) {
  .xf-l1hub-grid .xf-cat-tile:has(.xf-cat-tile__art--static) .xf-cat-tile__name {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.35;
    min-height: calc(1.35em * 2);
  }
}

/* Имя подраздела — это h3 (section.php), иначе 12 плиток сетки не видны
   навигации по заголовкам. Заголовку сбрасываем браузерные поля: базовый
   .xf-cat-tile__name гасит только margin-top, и снизу у h3 осталось бы 1em —
   плитка стала бы выше соседних. Тот же приём, что у .xf-dept__name на хабе. */
.xf-l1hub-grid h3.xf-cat-tile__name { margin: 0; }

/* Подраздел без товаров (section.php снимает с него ссылку): плитка остаётся
   на месте — пустая полка это факт, прятать его значит рисовать ассортимент
   лучше, чем он есть, — но перестаёт зазывать. Гасим обложку, а не текст:
   приглушённый текст ушёл бы ниже AA, а картинка контраста не несёт.
   Наведение тоже молчит — двигаться некуда. */
.xf-l1hub-tile--empty { cursor: default; background: var(--surface-2); }
.xf-l1hub-tile--empty .xf-cat-tile__art { opacity: .45; }
.xf-l1hub-tile--empty .xf-cat-tile__count { color: var(--text-3); }
@media (hover: hover) {
  .xf-l1hub-tile--empty:hover {
    transform: none;
    box-shadow: none;
    border-color: var(--line-soft);
  }
}

/* Счётчик на плитке подраздела: тот же случай, что у .xf-dept__count выше —
   --text-3 в тёмной теме даёт 4.27:1, для 11.5px это ниже AA. Первый заход
   поднял только счётчики хаба и пропустил счётчик самой сетки подразделов,
   хотя чинил именно её страницу. Правка живёт внутри сетки хаба, на плитки
   других страниц не распространяется. [#708] Число — прежней тёмной; в
   «Ночной вывеске» --text-3 даёт 6.6:1, правило держит прежний вид. */
html[data-theme="dark"] .xf-l1hub-grid .xf-cat-tile__count { color: var(--text-2); }

/* Подпись перед полосой брендов: без неё чипы читаются как инертные теги.
   Слово рядом объясняет, что это фильтр по бренду, и не добавляет акцента
   (One-Voice Indigo). */
.xf-l1hub-chiprow {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px 10px;
  /* Отступ сверху раньше нёс сам .xf-brand-chips; обёртка забирает его себе,
     иначе подпись прилипает к лиду раздела. */
  margin-top: 12px;
}
.xf-l1hub-chiprow__label {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--text-3);
  white-space: nowrap;
}
/* Только верхнее поле: боковые у полосы свои — на телефоне она выходит под
   край экрана (catalog.css, #646), и `margin: 0` здесь это перебивал. */
.xf-l1hub-chiprow .xf-brand-chips,
.xf-l1hub-chiprow .xf-facet-chips { margin-top: 0; }
/* [#646] На телефоне чипы — полоса с прокруткой вбок (catalog.css): подпись
   встаёт строкой над ней, полоса — во всю ширину. */
@media (max-width: 767px) {
  .xf-l1hub-chiprow { display: block; }
  .xf-l1hub-chiprow__label { display: block; margin-bottom: 2px; }
}

/* End */


/* Start:/local/templates/xcore_front/components/yam.user.cookies/.default/style.css?179264518324622*/
/* OWNS: .xf-cookie, .xf-cookie__* (consent banner + панель «Настроить»), .xf-cookie--expanded.
   Все селекторы строго под .xf-cookie__ — не пересекаются с form.css (.licence_block)
   и css/*. Перед добавлением нового класса: grep по css/ и этому файлу.
   Уважает токены xcore_front (Onest + OKLCH accent 268°), dark/light.
   JS-контракт: container#cookie_notice__wrap, btnClose#cookies_btn__close,
   hide-class .cookie_hidden, panel#xf-cookie-prefs, чекбоксы #xf-pref-*. */

.xf-cookie {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 9999;
    background: var(--surface);
    color: var(--text);
    border-top: 1px solid var(--accent-line);
    box-shadow: 0 -8px 32px -8px oklch(0.3 0.05 285 / 0.18);
    font-family: var(--font-sans);
    transition: transform .4s cubic-bezier(0.16, 1, 0.3, 1), opacity .3s ease;
    transform: translateY(0);
    opacity: 1;
}

.xf-cookie.cookie_hidden {
    transform: translateY(120%);
    opacity: 0;
    pointer-events: none;
}

.xf-cookie__inner {
    max-width: 1200px;
    margin: 0 auto;
    padding: 14px 20px;
    display: flex;
    align-items: flex-start;
    gap: 16px;
}

.xf-cookie__main {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
}

.xf-cookie__text {
    flex: 1 1 320px;
    margin: 0;
    font-size: 14px;
    line-height: 1.5;
    color: var(--text-2);
}

.xf-cookie__text strong { color: var(--text); font-weight: 600; }

.xf-cookie__link {
    color: var(--text);
    text-decoration: underline;
    text-decoration-color: var(--accent-line);
    text-underline-offset: 3px;
    transition: text-decoration-color .15s ease;
}

.xf-cookie__link:hover { text-decoration-color: var(--accent); }

.xf-cookie__actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

/* Действия согласия */
.xf-cookie__accept {
    appearance: none;
    border: 0;
    background: var(--accent);
    color: oklch(1 0 0);
    font: 500 14px/1 var(--font-sans);
    padding: 11px 20px;
    border-radius: 10px;
    cursor: pointer;
    transition: background .15s ease, transform .08s ease;
}
.xf-cookie__accept:hover  { background: var(--accent-2); }
.xf-cookie__accept:active { transform: scale(calc(1 - 0.02 * var(--xf-motion, 1))); }
/* Тёмная (#857): там --accent-2 — фиолет для текста, белая подпись на нём
   1.9:1. Наведение залитой кнопки — --accent-3, как у .xf-btn: 6.4:1. */
html[data-theme="dark"] .xf-cookie__accept:hover { background: var(--accent-3); }

.xf-cookie__reject {
    appearance: none;
    background: transparent;
    color: var(--text-2);
    border: 1px solid var(--accent-line);
    font: 500 14px/1 var(--font-sans);
    padding: 10px 18px;
    border-radius: 10px;
    cursor: pointer;
    transition: color .15s ease, border-color .15s ease, background .15s ease, transform .08s ease;
}
.xf-cookie__reject:hover  { color: var(--text); border-color: var(--text-3); background: var(--surface-2); }
.xf-cookie__reject:active { transform: scale(calc(1 - 0.02 * var(--xf-motion, 1))); }

/* «Настроить» — третичное действие (открыть панель), без рамки */
.xf-cookie__settings {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--text-2);
    font: 500 14px/1 var(--font-sans);
    padding: 10px 12px;
    border-radius: 10px;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: var(--accent-line);
    text-underline-offset: 3px;
    transition: color .15s ease, background .15s ease;
}
.xf-cookie__settings:hover { color: var(--text); background: var(--surface-2); }

.xf-cookie__close {
    appearance: none;
    border: 0;
    background: transparent;
    color: var(--text-3);
    width: 36px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
    cursor: pointer;
    flex-shrink: 0;
    transition: color .15s ease, background .15s ease;
}
.xf-cookie__close:hover { color: var(--text); background: var(--surface-2); }

/* ───────── Панель «Настроить» ───────── */
.xf-cookie__panel {
    border-top: 1px solid var(--surface-3);
    background: var(--surface-2);
}
.xf-cookie__panel[hidden] { display: none; }

.xf-cookie__panel-inner {
    max-width: 1200px;
    margin: 0 auto;
    padding: 16px 20px;
    display: flex;
    flex-direction: column;
    gap: 14px;
    max-height: 48vh;
    overflow-y: auto; /* запасной путь: первым сжимается список ниже, а панель
                         прокрутится, только если не влезут даже кнопки */
}

/* Прокручивается только список категорий, кнопки панели всегда под ним. Описания
   категорий длинные (это раскрытие по политике), и при общей прокрутке панели
   «Сохранить выбор» и «Отклонить все» на низких телефонах (360×640, 375×667)
   уходили за её край. Список — flex-элемент со своим overflow: он сжимается до
   высоты, которая остаётся от max-height панели после кнопок, без заданных
   вручную высот. margin/padding 4px — место под кольцо фокуса тумблера
   (outline 2px + offset 2px), иначе край прокрутки его обрежет.
   position: relative — скрытые чекбоксы (position: absolute) иначе отсчитываются
   от fixed-баннера, лежат вне прокрутки, и Tab на «Маркетинг» не прокручивает
   список к тумблеру. scroll-padding-block 16px — прокрутка к фокусу оставляет
   видимыми весь тумблер (24px вокруг чекбокса по центру ряда) и его кольцо. */
.xf-cookie__pref-list {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-height: 0;
    overflow-y: auto;
    scroll-padding-block: 16px;
    margin: -4px;
    padding: 4px;
}

.xf-cookie__pref { display: flex; flex-direction: column; gap: 4px; }

.xf-cookie__pref-row {
    display: flex;
    align-items: center;
    gap: 12px;
    cursor: pointer;
}
.xf-cookie__pref > .xf-cookie__pref-row { cursor: default; } /* locked-ряд «Необходимые» */
label.xf-cookie__pref-row { cursor: pointer; }

.xf-cookie__pref-title {
    flex: 1;
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
}

.xf-cookie__pref-state {
    font: 500 12px/1 var(--font-mono);
    color: var(--text-2); /* AA: consent-маркер должен проходить 4.5:1, --text-3 не дотягивает */
    letter-spacing: .02em;
}

.xf-cookie__pref-desc {
    margin: 0;
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--text-2); /* AA: юридически значимое раскрытие — не --text-3 (4.38:1) */
    padding-right: 52px; /* запас под тумблер справа */
}

/* Доступный switch: визуально скрытый checkbox + дорожка с кноб'ом */
.xf-cookie__check {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    margin: 0;
    pointer-events: none;
}
.xf-cookie__switch {
    position: relative;
    flex-shrink: 0;
    width: 40px;
    height: 24px;
    border-radius: 999px;
    background: var(--surface-3);
    border: 1px solid var(--accent-line);
    transition: background .15s ease, border-color .15s ease, opacity .15s ease;
}
.xf-cookie__knob {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--surface);
    box-shadow: 0 1px 2px oklch(0 0 0 / 0.25);
    transition: transform .15s ease;
}
.xf-cookie__check:checked + .xf-cookie__switch {
    background: var(--accent);
    border-color: var(--accent);
}
.xf-cookie__check:checked + .xf-cookie__switch .xf-cookie__knob {
    transform: translateX(16px);
}
.xf-cookie__check:focus-visible + .xf-cookie__switch {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Locked-тумблер «Необходимые» — всегда вкл., не интерактивен */
.xf-cookie__switch--locked {
    background: var(--accent);
    border-color: var(--accent);
    opacity: .65;
}
.xf-cookie__switch--locked .xf-cookie__knob { transform: translateX(16px); }
/* Тёмная тема: выключенный тумблер с контуром accent-line и тёмным кноб'ом на
   тёмной панели не читался как «это можно включить» — контур 45% белого
   (4.5:1 к --surface) и светлый кноб. Включённый и «Необходимые» — в акценте. */
html[data-theme="dark"] .xf-cookie__check:not(:checked) + .xf-cookie__switch:not(.xf-cookie__switch--locked) {
    border-color: oklch(1 0 0 / 0.45);
}
html[data-theme="dark"] .xf-cookie__check:not(:checked) + .xf-cookie__switch:not(.xf-cookie__switch--locked) .xf-cookie__knob {
    background: oklch(0.86 0.006 292);
}

.xf-cookie__panel-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    padding-top: 4px;
    flex-shrink: 0; /* сжимается список категорий, не кнопки */
}

@media (max-width: 720px) {
    .xf-cookie__inner { padding: 10px 16px; gap: 8px; }
    .xf-cookie__main { gap: 10px; }
    .xf-cookie__text { flex-basis: 100%; padding-right: 36px; font-size: 13px; line-height: 1.4; }
    .xf-cookie__actions { width: 100%; gap: 6px; }
    .xf-cookie__settings { flex: 1 1 100%; text-align: center; }
    .xf-cookie__reject,
    .xf-cookie__accept { flex: 1; padding: 10px 14px; font-size: 13px; }
    .xf-cookie__close { position: absolute; top: 6px; right: 6px; width: 30px; height: 30px; }
    .xf-cookie__panel-inner { padding: 14px 16px; max-height: 62vh; } /* #722: было 56vh */
    .xf-cookie__pref-desc { padding-right: 0; }
    .xf-cookie__panel-actions .xf-cookie__reject,
    .xf-cookie__panel-actions .xf-cookie__accept { flex: 1 1 0; min-width: 0; text-align: center; }
}

/* ───────── Короткий баннер (#715) ─────────
   Решение Ильи 2026-10-12: «сильно короче, текста явно меньше, чуть аккуратнее».
   Текст первого слоя — строка-подпись и ссылка (template.php), здесь —
   раскладка; вид самой подписи — в блоке «Первый слой в одну строку» ниже.
   Блок стоит последним и перекрывает правила выше порядком в файле (мобильный
   блок ≤720) — не переносить выше. Блок #704 (сжатие на 340–480 px) им
   заменён и удалён: всё, что он задавал, задано здесь.
   Кнопки первого слоя правим ТОЛЬКО под .xf-cookie__actions: те же классы
   носят «Сохранить выбор» и «Отклонить все» в панели «Настроить», а у них своя
   раскладка (до #722 — столбиком, где с нулевыми вертикальными полями и
   flex: 1 1 0 они сжимались до высоты текста, 14 px; с #722 — в ряд).
   Обкатано на /dev/ под скоупом d1, скоуп снят при выкатке 2026-10-12. */

/* Десктоп и планшет: текст в одну строку — ряд центруем по высоте,
   крестик встаёт вровень с кнопками. */
.xf-cookie__inner { align-items: center; padding: 12px 20px; }
.xf-cookie__text { line-height: 1.45; }
/* Три действия одного роста: высоту задаём числом, а не полями — у трёх кнопок
   были три разные высоты (33, 34 и 35 px). */
.xf-cookie__actions .xf-cookie__accept,
.xf-cookie__actions .xf-cookie__reject,
.xf-cookie__actions .xf-cookie__settings {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 38px;
    padding-top: 0;
    padding-bottom: 0;
    white-space: nowrap;
}
/* «Отклонить» не бледнее «Согласиться»: текст основным цветом, рамка — не
   акцентная на 30%. Отказ не должен быть заметен хуже согласия. То же — у
   «Отклонить все» в панели. Цвет рамки здесь (--line) перекрыт блоком #718 в
   конце файла: --line почти не читалась. */
.xf-cookie__reject { color: var(--text); border-color: var(--line); }
/* Ссылка на политику: мишень выше 17 px текста — поля строчного элемента
   строку не раздвигают. Вниз — только 2 px: под текстом ряд кнопок, и палец,
   попавший чуть выше «Отклонить», не должен уводить на страницу политики.
   Две подписи: полная («Подробнее в политике») и короткая для телефона
   («Политика») — см. блок «Первый слой в одну строку» ниже. */
.xf-cookie__link { padding: 8px 0 2px; white-space: nowrap; }
.xf-cookie__link-narrow { display: none; }

@media (max-width: 720px) {
    .xf-cookie__inner { align-items: flex-start; padding: 10px 16px; gap: 0; }
    /* min-width: 0 — ряд кнопок шире экрана больше не растягивает весь блок
       (на 320 px он тянул за край и текст, #713). */
    .xf-cookie__main { gap: 10px; min-width: 0; }
    .xf-cookie__text { flex-basis: 100%; padding-right: 0; font-size: 12.5px; line-height: 1.4; }
    /* Место под крестик держит плавающая распорка — только у первой строки:
       крестик стоит у самой кромки (значок 14 px — на 11–25 px от верха),
       вторая строка (до #722 она была на 320 px) идёт уже на всю ширину. Выше
       не поднимаем: над баннером на карточке товара стоит липкая полоса, и её
       «В избранное» — ровно над крестиком. right: 8px (#722) — правый край
       значка (кнопка 30 px, значок 14 по центру) на кромке ряда, вровень с
       «Согласиться»; при 4 px он торчал на 4 px за неё. */
    .xf-cookie__text::before { content: ""; float: right; width: 24px; height: 16px; }
    .xf-cookie__close { position: absolute; top: 3px; right: 8px; width: 30px; height: 30px; }
    /* Ряд действий: «Отклонить» и «Согласиться» — равной ширины, «Настроить» —
       по тексту. Раньше три кнопки были трёх разных ширин и высот.
       [#722] Порядок — как в разметке и на десктопе: «Настроить», «Отклонить»,
       «Согласиться» (раньше order: 3 уводил «Настроить» в конец — фокус с
       клавиатуры шёл не в том порядке, что на экране). Текст «Настроить» —
       вровень с подписью над ним: кнопка сдвинута влево на своё же поле
       (margin = −padding-left, здесь и на ≤359 ниже — менять парой). */
    .xf-cookie__actions { width: 100%; gap: 8px; }
    .xf-cookie__actions .xf-cookie__reject,
    .xf-cookie__actions .xf-cookie__accept { flex: 1 1 0; min-width: 0; padding-left: 10px; padding-right: 10px; font-size: 13.5px; }
    .xf-cookie__actions .xf-cookie__settings { flex: 0 0 auto; padding-left: 8px; padding-right: 8px; margin-left: -8px; font-size: 13.5px; text-align: center; }
    /* Кнопки панели «Настроить» были 33 и 35 px — того же роста, что действия
       первого слоя: min-height 38. [#722] Стоят рядом, равной ширины, как
       «Отклонить» и «Согласиться» первого слоя (раньше — столбиком): списку
       категорий это 46 px высоты. */
    .xf-cookie__panel-actions .xf-cookie__reject,
    .xf-cookie__panel-actions .xf-cookie__accept { min-height: 38px; padding-left: 10px; padding-right: 10px; font-size: 13.5px; }
}

/* Самые узкие экраны (#713): на 320 px ряду нужно 312 px при 288 доступных —
   «Настроить» выходила за правый край (233–329 px). Поля и кегль кнопок ужаты,
   ряд помещается целиком. */
@media (max-width: 359px) {
    .xf-cookie__actions { gap: 6px; }
    .xf-cookie__actions .xf-cookie__reject,
    .xf-cookie__actions .xf-cookie__accept { padding-left: 6px; padding-right: 6px; font-size: 13px; }
    .xf-cookie__actions .xf-cookie__settings { padding-left: 6px; padding-right: 4px; margin-left: -6px; font-size: 13px; }
}

/* ───────── Первый слой в одну строку (#715, второй заход) ─────────
   Илья, 2026-10-12: «Про cookie можно просто Cookie аналитики и маркетинга».
   Строка-подпись вместо абзаца: основным цветом и полужирнее, ссылка на
   политику рядом — тише. Фраза о согласии и о Google без cookie переехала в
   панель «Настроить» (.xf-cookie__panel-note) слово в слово.
   На телефоне подпись ссылки короткая («Политика») на любой ширине: с полной
   строка не помещалась в одну на 390–412 px. Адрес ссылки тот же, и слово
   по-прежнему называет, куда она ведёт.
   Высота баннера: 87 px на 340–720 px ширины, 105 на 320 (подпись в две
   строки; с #722 — 86, одна строка), 63 на десктопе. Было 122 / 139 / 66, до
   #715 — 162 / 212 / 92.
   Обкатано на /dev/ под скоупом d1, скоуп снят при выкатке 2026-10-12. */
.xf-cookie__text--label { color: var(--text); font-weight: 500; }
.xf-cookie__text--label .xf-cookie__link { margin-left: 2px; font-weight: 400; color: var(--text-2); }
.xf-cookie__panel-note { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--text-2); }
@media (max-width: 720px) {
    .xf-cookie__text--label { font-size: 13px; }
    .xf-cookie__link-wide { display: none; }
    .xf-cookie__link-narrow { display: inline; }
}

/* ───────── «Отклонить» наравне с «Согласиться» (#718) ─────────
   Взгляд со стороны после #715: рамка «Отклонить» цветом --line почти не
   читалась (контраст с фоном баннера ~1,5:1; в тёмной теме кнопка выглядела
   просто текстом рядом с залитой «Согласиться»), а на планшете и десктопе
   кнопка была уже «Согласиться» (110 против 126 px). Рамка — --text-3 (этим
   цветом она раньше подсвечивалась при наведении; с фоном больше 3:1 в обеих
   темах), при наведении — --text-2; то же у «Отклонить все» в панели. От
   721 px у двух кнопок общая минимальная ширина. Заливку не трогаем: делать
   ли кнопки одного веса — решение владельца.
   Обкатано на /dev/ под скоупом d1, скоуп снят при выкатке 2026-10-12. */
.xf-cookie__reject { border-color: var(--text-3); }
.xf-cookie__reject:hover { border-color: var(--text-2); }
@media (min-width: 721px) {
    .xf-cookie__actions .xf-cookie__reject,
    .xf-cookie__actions .xf-cookie__accept { min-width: 128px; }
}

/* ───────── Косметика после #715 (#722) ─────────
   Замеры ДО — живой s1, 2026-10-21, карточка товара, баннер открыт. Часть
   правок сделана на месте, в блоках выше (с пометкой #722): порядок действий
   на телефоне и «Настроить» вровень с подписью, крестик на кромке ряда, кнопки
   окна «Настроить» в ряд, окно до 62vh. Здесь — то, чего выше не было:
   1. Планшет (721 px и шире): подпись и три кнопки — одним рядом. У подписи
      была база 320 px, и рядом с кнопками (368 px) она не помещалась уже на
      768: кнопки уходили вторым рядом, правая половина пустела, крестик вставал
      между рядами (баннер 99 px). Теперь подпись сама уступает ширину и
      переносится — «Подробнее в политике» целиком второй строкой, — ряд один,
      66 px. На десктопе подпись по-прежнему в строку: ширины ей хватает.
   2. 320 px: «Политика» не падает на вторую строку. Подписи не хватало 1 px
      (265 при 264 доступных, баннер 105 px вместо 87) — кегль 12.5 на ≤359 px
      (с #821 — 12: в Safari-движке не хватало ещё 10 px, см. ниже).
   3. Окно «Настроить» на ≤359 px: категория «Необходимые» (её переключатель не
      нажимается) — после двух переключаемых, иначе «Маркетинг» уходил под край
      списка. Фокус порядок не меняет: у «Необходимых» нет поля ввода. Абзац о
      согласии и о Google без cookie — последним в списке на любой ширине
      (template.php): первым он уводил переключатели под край окна
      (390×664 — «Маркетинг» за краем, 320×568 — «Аналитика»). */
@media (min-width: 721px) {
    .xf-cookie__text { flex: 1 1 200px; min-width: 0; }
}
/* [#821] Кегль подписи на ≤359 — 12 px, не 12.5: в Safari-движке строка на
   10 px уже, чем в Chrome (замер: 278 против 288 px на 320), и «Политика»
   снова падала второй строкой — баннер 103 px вместо 86. На карточке товара
   эти 17 px поднимали липкую полосу на последнюю строку названия. */
@media (max-width: 359px) {
    .xf-cookie__text--label { font-size: 12px; }
    .xf-cookie__pref--locked { order: 1; }
    .xf-cookie__panel-note { order: 2; }
}

/* End */


/* Start:/local/templates/xcore_front/styles.css?1792154828280*/
/* Deprecated aggregator. css/*.css подключаются по одному через header.php
   с ?v=<filemtime> для корректного cache-bust. Оставлен как no-op, чтобы
   не сломать возможные внешние ссылки. */

/* End */
/* /local/templates/xcore_front/css/tokens.css?179264524827140 */
/* /local/templates/xcore_front/css/base.css?179264519117225 */
/* /local/templates/xcore_front/css/layout.css?1792154827756 */
/* /local/templates/xcore_front/css/ui.css?179264525219093 */
/* /local/templates/xcore_front/css/header.css?179265388452926 */
/* /local/templates/xcore_front/css/footer.css?17926538796313 */
/* /local/templates/xcore_front/css/home.css?1792645227182604 */
/* /local/templates/xcore_front/css/catalog.css?179265387015933 */
/* /local/templates/xcore_front/css/catsection.css?179265387551080 */
/* /local/templates/xcore_front/css/product.css?1792653888108983 */
/* /local/templates/xcore_front/css/installment.css?179264523113382 */
/* /local/templates/xcore_front/css/cart.css?179264120217644 */
/* /local/templates/xcore_front/css/auth.css?179264121913235 */
/* /local/templates/xcore_front/css/order.css?179264121014026 */
/* /local/templates/xcore_front/css/account.css?179215482727187 */
/* /local/templates/xcore_front/css/search.css?179264524418350 */
/* /local/templates/xcore_front/css/modal.css?179264123385188 */
/* /local/templates/xcore_front/css/content.css?179264521449289 */
/* /local/templates/xcore_front/css/form.css?179264521811903 */
/* /local/templates/xcore_front/css/blog.css?179265386619943 */
/* /local/templates/xcore_front/css/news.css?17926452352079 */
/* /local/templates/xcore_front/css/brands.css?179215482712037 */
/* /local/templates/xcore_front/css/projects.css?179215482710910 */
/* /local/templates/xcore_front/css/chat.css?179264121536325 */
/* /local/templates/xcore_front/css/aichat.css?179265386261719 */
/* /local/templates/xcore_front/css/catalog-hub.css?179264122822319 */
/* /local/templates/xcore_front/components/yam.user.cookies/.default/style.css?179264518324622 */
/* /local/templates/xcore_front/styles.css?1792154828280 */
