/* =============================================
   КАТАЛОГ
   ============================================= */
.catalog-layout { display: grid; grid-template-columns: 240px 1fr; gap: 32px; align-items: start; }
/* Без min-width:0 grid-колонка не сжимается уже содержимого — вложенный
   .products-grid при большом масштабе текста раздувал всю страницу вширь
   (тот же класс бага, что и в .footer-grid, найден реальным тестом). */
.catalog-layout > * { min-width: 0; }
.catalog-sidebar { position: sticky; top: 90px; }

.filter-block {
    background: var(--white);
    border-radius: var(--radius);
    padding: 20px;
    border: 1px solid var(--border);
    margin-bottom: 16px;
}
.filter-block__title {
    font-size: 0.8125rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--text-light);
    margin-bottom: 14px;
}
.filter-cat-list { display: flex; flex-direction: column; gap: 4px; }
.filter-cat-list a {
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    font-size: 0.9375rem;
    color: var(--text);
    display: flex;
    align-items: center;
    justify-content: space-between;
    transition: all .2s;
}
.filter-cat-list a:hover { background: var(--primary-light); color: var(--primary-h); text-decoration: none; }
/* P7 -- была сплошной терракотовой заливкой (var(--primary)) + белым
   текстом, одна из 5 находок "терракот означает 5 разных вещей"
   одновременно (промо-плашка/поисковая кнопка/этот фильтр/язык RU/бейдж
   скидки -- живой аудит, computed-style). Светлый коралловый тинт вместо
   сплошной заливки -- тот же язык, что уже несёт .main-nav a.active
   (base.css) и активный язык в .lang-switch (после той же правки). Hover
   на уже-активном пункте намеренно не темнеет отдельно (тот же паттерн,
   что у .main-nav -- :hover и .active делят одно и то же значение). */
.filter-cat-list a.active { background: var(--nav-active); color: var(--nav-active-text); font-weight: 600; }
.filter-cat-list a.active:hover { background: var(--nav-active); color: var(--nav-active-text); }

.catalog-toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 24px;
    background: var(--white);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px 16px;
    flex-wrap: wrap;
}
.catalog-toolbar__count {
    font-size: 0.875rem;
    color: var(--text-light);
    flex: 1;
}
.catalog-toolbar__count strong { color: var(--text); }
/* P8 (аудит находка #16, Фаза 1) -- быстрые фильтры-чипы: полноширинная
   строка над count/search/sort (flex-basis 100% внутри уже flex-wrap
   .catalog-toolbar), сама скроллится горизонтально и не переносит чипы на
   новую строку -- требование и для десктопа, и для мобильного (там просто
   меньше видно за раз, механика скролла та же). order:-1 поднимает её над
   остальными детьми toolbar независимо от порядка в разметке. */
.catalog-quick-filters {
    display: flex;
    gap: 8px;
    flex-wrap: nowrap;
    overflow-x: auto;
    flex: 0 0 100%;
    order: -1;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}
.catalog-quick-filters::-webkit-scrollbar { display: none; }
.catalog-quick-filter {
    flex: 0 0 auto;
    /* Тач-таргет (WCAG 2.5.8). БЫЛО: font-size 0.8125rem (13px) * унаследо-
       ванный от body line-height 1.7 = 22.1px + padding 8+8 + border
       1.5+1.5 = 41.1px, мобильного оверрайда не было. Это горизонтально
       прокручиваемая строка категорий над сеткой товаров -- основной способ
       сузить выбор с телефона; соседние размерные фильтры (.filter-size-btn
       ниже по файлу) уже 44x44.
       min-height + inline-flex + центрирование: горизонтальные метрики чипа
       (padding/border/шрифт) не меняются, растёт только высота. */
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 8px 16px;
    border: 1.5px solid var(--border);
    border-radius: 999px;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    text-decoration: none;
    transition: all .18s;
    background: var(--white);
}
.catalog-quick-filter:hover  { border-color: var(--primary); color: var(--primary); text-decoration: none; }
.catalog-quick-filter.active { border-color: var(--primary); background: var(--primary); color: #fff; }
.catalog-search {
    display: flex;
    gap: 8px;
    align-items: center;
}
/* Enterprise-аудит 2026-08-06, Medium #72 -- оба селектора ниже (поиск +
   сортировка каталога) были 0.875rem (14px): iOS Safari зумит страницу при
   фокусе на ЛЮБОЕ поле ввода/select с вычисленным font-size < 16px. 1rem --
   тот же порог и то же обоснование, что у .form-group input (cart-checkout.css). */
.catalog-search input {
    /* 11px по вертикали вместо 8px -- высота поля выходит ровно на 44px.
       font-size здесь трогать нельзя: 1rem стоит против автозума iOS Safari
       при фокусе, а зум ломает раскладку каталога целиком. */
    padding: 11px 14px;
    border: 1.5px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: 1rem;
    width: 200px;
    font-family: var(--font-body);
    transition: border-color .2s;
}
.catalog-search input:focus { border-color: var(--primary); outline: none; }
.catalog-sort select {
    /* 11px по вертикали вместо 8px -- высота поля выходит ровно на 44px.
       font-size здесь трогать нельзя: 1rem стоит против автозума iOS Safari
       при фокусе, а зум ломает раскладку каталога целиком. */
    padding: 11px 14px;
    border: 1.5px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: 1rem;
    font-family: var(--font-body);
    background: var(--white);
    cursor: pointer;
}
.catalog-count { font-size: 0.8125rem; color: var(--text-light); margin-top: 12px; }
/* =============================================
   БАННЕР КАТЕГОРИИ
   ============================================= */
.category-banner {
    padding: 36px 0;
    overflow: hidden;
    position: relative;
}
.category-banner__inner {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
}
.category-banner__content { flex: 1; }
.category-banner__title {
    font-family: var(--font-display);
    font-size: 2.5rem;
    font-weight: 700;
    color: #fff;
    line-height: 1.1;
    margin: 10px 0 10px;
}
.category-banner__sub { font-size: 0.875rem; color: rgba(255,255,255,.7); }
/* Владелец, дизайн-ревью 2026-08-19: "СВГ картинки на странице категорий
   выглядят ужасно, особенно за счёт полупрозрачных линий... когда
   накладываются друг на друга — просто ужасно". Root cause -- ДВА
   независимых набора тонких полупрозрачных линий на одном фоне: диагональный
   repeating-linear-gradient паттерн (было на .category-banner::before, ВЕСЬ
   баннер) поверх stroke-иконки (icon(), assets/icons.php -- ВСЕ иконки
   этой системы line-art, fill:none/stroke:currentColor) на rgba(255,255,255,.25)
   -- ровно там, где штрихи иконки пересекали диагональные линии фона,
   получалась визуально мутная "каша" из непонятных полос, а не читаемая
   картинка. Убрали паттерн ЦЕЛИКОМ (::before выше больше не существует) --
   вместо конкурирующей текстуры иконка получает СОБСТВЕННЫЙ мягкий круглый
   "бейдж" (см. .category-banner__icon ниже) -- уже устоявшийся приём
   (иконка в мягком ореоле), не одна прозрачная линия поверх другой.

   Живой повторный репорт владельца (тот же день, после деплоя): "улучшил,
   но не решил проблему" -- фон действительно перестал конфликтовать, но
   САМА иконка (color: rgba(255,255,255,.65) ниже) осталась полупрозрачной
   -- ровно тот же корневой дефект из исходной жалобы, просто без второго
   слоя поверх него. Тонкий stroke (2px, см. icon() в assets/icons.php) на
   65% непрозрачности на разноцветных фонах баннера читается блёкло,
   "смытым" -- на скриншоте повторного репорта видно именно это, не
   визуальный мусор, а слабый, невыразительный контур. Непрозрачный #fff +
   drop-shadow (не box-shadow -- повторяет форму самого stroke-пути, не
   прямоугольник) дают иконке собственную глубину поверх круглого бейджа,
   вместо ещё одного полупрозрачного слоя. */
.category-banner__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 132px;
    height: 132px;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 30%, rgba(255,255,255,.20), rgba(255,255,255,.06) 70%);
    color: #fff;
    filter: drop-shadow(0 2px 8px rgba(0,0,0,.22));
    flex-shrink: 0;
}

@media (max-width: 600px) {
    .category-banner__icon { display: none; }
    .category-banner__title { font-size: 1.75rem; }
}
/* =============================================
   БЛОК 3.4 — ФИЛЬТР ПО РАЗМЕРУ
   ============================================= */
.filter-size-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 10px;
}
.filter-size-btn {
    /* 44x44 -- практический минимум тач-цели (WCAG 2.5.8 требует 24, но на
       телефоне палец мажет). Чипы размеров стоят плотной сеткой, и промах по
       соседнему размеру здесь дороже обычного: покупательница фильтрует не
       глядя и получает не тот ряд. */
    min-width: 44px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1.5px solid var(--border);
    border-radius: 8px;
    font-size: var(--fs-xs);
    font-weight: 600;
    color: var(--text);
    text-decoration: none;
    transition: all .18s;
    background: var(--white);
}
.filter-size-btn:hover  { border-color: var(--primary); color: var(--primary); text-decoration: none; }
.filter-size-btn.active { border-color: var(--primary); background: var(--primary); color: #fff; }
/* =============================================
   БЛОК 3.6 — МОБИЛЬНЫЙ ФИЛЬТР КАТАЛОГА
   ============================================= */
.mobile-filter-toggle {
    display: none;
    align-items: center;
    gap: 8px;
    padding: 10px 20px;
    background: var(--white);
    border: 1.5px solid var(--border);
    border-radius: var(--radius);
    font-family: var(--font-body);
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text);
    cursor: pointer;
    margin-bottom: 16px;
    width: 100%;
    justify-content: center;
    transition: all .2s;
}
.mobile-filter-toggle:hover { border-color: var(--primary); color: var(--primary); }
.filter-badge {
    background: var(--primary);
    color: #fff;
    width: 18px; height: 18px;
    border-radius: 50%;
    font-size: 0.75rem;
    display: flex; align-items: center; justify-content: center;
}
.catalog-sidebar__close { display: none; }
/* Затемнение-скрим позади мобильной drawer-панели фильтров -- тот же приём,
   что и .search-overlay__backdrop (base.css) и .mobile-menu-overlay
   (бургер-меню, main.js): отдельный fixed-элемент, скрытый по умолчанию,
   показывается через селектор соседа ("~") от .catalog-sidebar.open --
   чисто CSS, без доп. JS (существующий document-click-outside в main.js
   уже закрывает панель по клику на любой элемент вне .catalog-sidebar,
   значит и по клику на этот скрим тоже). */
.catalog-sidebar-backdrop { display: none; }
@media (max-width: 900px) {
    .mobile-filter-toggle { display: flex; }
    .catalog-sidebar {
        position: fixed;
        top: 0; left: 0;
        width: 280px;
        height: 100dvh;
        background: var(--white);
        z-index: 300;
        padding: 24px 20px;
        overflow-y: auto;
        box-shadow: 4px 0 32px rgba(0,0,0,0.15);
        transform: translateX(-100%);
        transition: transform .3s cubic-bezier(.4,0,.2,1);
        pointer-events: none;
    }
    .catalog-sidebar.open {
        transform: translateX(0);
        pointer-events: auto;
    }
    .catalog-sidebar__close {
        display: flex;
        align-items: center;
        justify-content: center;
        /* Кнопка закрытия шторки фильтров -- одиночная цель у края экрана,
           мимо неё мажут чаще всего. */
        width: 44px; height: 44px;
        background: var(--bg);
        border: none;
        border-radius: 50%;
        color: var(--text-light);
        cursor: pointer;
        margin: 0 0 16px auto;
    }
    .catalog-sidebar.open ~ .catalog-sidebar-backdrop {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 299;
        background: rgba(10,10,20,.55);
        backdrop-filter: blur(2px);
    }
    /* Блокировка скролла страницы, пока drawer открыт -- тот же приём, что
       уже используется в base.css (body:has(.prd-layout) ...), просто на
       другом условии. */
    body:has(.catalog-sidebar.open) { overflow: hidden; }
    /* Round-4 независимый аудит, P1 #17 catalog-filter-close-button-
       unclickable -- живой Playwright-клик (не JS-эмуляция) по видимой
       #catalogSidebarClose гарантированно не срабатывал ни на одном
       мобильном брейкпоинте: document.elementFromPoint в центре кнопки
       возвращал div.header-inner, а не саму кнопку -- Playwright honestly
       репортил "<div class="header-inner">... from <header class="site-
       header">... subtree intercepts pointer events".
       Это НЕ проблема самих z-index-значений -- .catalog-sidebar уже
       z-index:300 против .site-header z-index:200 (300 > 200), числа
       давно верные. Корень найден живой Playwright-диагностикой
       (page.evaluate + getComputedStyle), не догадкой: loader.css вешает
       на main#main декоративную анимацию появления страницы
       (`animation: main-fade-in .32s ease-out both;`, см. её собственный
       докблок) -- по спеке CSS-анимация/transition, чьи keyframes трогают
       opacity/transform/filter, ПРИНУДИТЕЛЬНО создаёт свой stacking
       context на элементе, даже когда сама анимация уже давно
       доиграла (animation-fill-mode:both держит ассоциацию вечно). main
       при этом остаётся невспозиционированным (position:static) -- его
       контекст стекуется в КОРНЕВОМ контексте на "auto/0"-уровне,
       СТРОГО НИЖЕ явного z-index:200 у .site-header (родного сиблинга
       main по DOM, идёт раньше него), НЕЗАВИСИМО от того, какой z-index
       выставлен descendant'ам ВНУТРИ main -- .catalog-sidebar физически
       не может "дотянуться" выше .site-header, пока сидит внутри чужого
       stacking context, сколько бы ни повышали её собственный z-index
       (сама .catalog-sidebar тоже не может "сбежать" из контекста
       предка -- ни z-index, ни position:fixed, ни isolation:isolate НА
       НЕЙ САМОЙ этого не меняют, contexts всегда рисуются внутри
       контекста предка). Похожая ловушка уже задокументирована в
       base.css (.site-header комментарий про .product-sticky-bar) --
       та же механика, другой descendant main.
       Фикс -- не трогая loader.css (внешний относительно этой находки
       файл, single-file scope round-4), гасим саму анимацию (а с ней и
       её побочный stacking context) на main ТОЛЬКО пока сайдбар
       реально открыт: `body:has(.catalog-sidebar.open) main#main` имеет
       специфичность (1 id, 1 псевдокласс, 2 элемента), которая бьёт
       loader.css-правило `main#main` (1 id, 0 классов, 1 элемент)
       независимо от порядка подключения файлов (у catalog.css итак нет
       приоритета по source order -- loader.css в layout.php подключается
       ПОСЛЕ). main#main вне этого состояния (сайдбар закрыт) анимацию не
       теряет -- обычное появление страницы при заходе не регрессирует
       (проверено тем же Playwright-стендом: animationName остаётся
       'main-fade-in', когда .open отсутствует). Фикс подтверждён
       живым Playwright-кликом после правки: elementFromPoint попадает в
       саму кнопку, click() реально закрывает панель -- не просто "класс
       .open исчез" по прямому вызову, а honest end-to-end клик. Регресс-
       тест: tests/test_catalog_filter_close_button_stacking_2026_08_10.php. */
    body:has(.catalog-sidebar.open) main#main { animation: none; }
}
/* =============================================
   ПАГИНАЦИЯ КАТАЛОГА
   ============================================= */
.catalog-pagination {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 6px;
    margin-top: 40px;
    flex-wrap: wrap;
}
.page-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Пагинация: 40px не хватает четырёх пикселей до тач-минимума, а
       промах здесь уводит на соседнюю страницу каталога. */
    width: 44px;
    height: 44px;
    border: 1.5px solid var(--border);
    border-radius: 8px;
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--text);
    background: var(--white);
    text-decoration: none;
    transition: all .18s;
    cursor: pointer;
}
.page-btn:hover { border-color: var(--primary); color: var(--primary); text-decoration: none; }
.page-btn.active { border-color: var(--primary); background: var(--primary); color: #fff; }
.page-btn--wide { width: auto; padding: 0 14px; gap: 6px; }
/* 0.4 давал 2.34:1 (нужно 4.5:1) — появилось только сейчас, когда каталог
   вырос до 55 товаров и на нём впервые появилась пагинация. 0.7 даёт
   ~5.2:1 с запасом. */
.page-btn:disabled,
.page-btn--disabled { opacity: 0.7; pointer-events: none; }
.page-dots { color: var(--text-light); padding: 0 4px; line-height: 2.5rem; }
/* =============================================
   AJAX-КАТАЛОГ: СОСТОЯНИЯ ЗАГРУЗКИ
   ============================================= */
.catalog-main { position: relative; }

.catalog-grid--loading {
    opacity: 0.4;
    pointer-events: none;
    transition: opacity .2s;
}

.catalog-loader {
    position: absolute;
    top: 80px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    display: none;
}
.catalog-loader.visible { display: flex; }
.catalog-loader__spinner {
    width: 36px; height: 36px;
    border: 3px solid var(--border);
    border-top-color: var(--primary);
    border-radius: 50%;
    animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* =============================================
   КАТАЛОГ — EMPTY STATE С КНОПКОЙ СБРОСА
   ============================================= */
.catalog-empty-state { padding: 64px 24px; }
.catalog-empty-state__actions {
    display: flex;
    gap: 12px;
    justify-content: center;
    flex-wrap: wrap;
    margin-top: 20px;
}
.catalog-empty-state__hint {
    margin-top: 20px;
    font-size: 0.875rem;
    color: var(--text-light);
}

/* =============================================
   СЕТКА ТОВАРОВ — РАЗРЕЖЁННЫЕ КАТЕГОРИИ
   ============================================= */
/* P1 #3 (cat-kurtki-single-product-void, round-5 аудит) -- базовая
   .products-grid (components.css) -- repeat(4, 1fr) -- всегда резервирует
   4 равные колонки независимо от реального числа карточек; категория с
   малым остатком товаров (живой repro: "Куртки" -- ровно 1 товар) рендерила
   единственную карточку прижатой в левый верхний угол с 3 явно пустыми
   колонками рядом. Компаунд-селектор (specificity 0,0,2,0) нужен, чтобы
   гарантированно победить и саму components.css'ную .products-grid, и её
   media-query-варианты на всех брейкпоинтах (1100/900/640px -- тоже
   0,0,1,0 каждый) -- НЕЗАВИСИМО от порядка подключения файлов
   ($page_styles = ['catalog', 'components'] в catalog.php, components.css
   грузится ПОСЛЕ этого файла, так что при равной специфичности выиграл бы
   именно он). repeat(auto-fit, minmax(...)) -- стандартное поведение
   auto-fit -- схлопывает НЕиспользуемые (без единой карточки) треки до 0
   сами по себе: сетка сужается ровно под реальное число отрендеренных
   карточек без ручного вычисления ширины по count() в PHP и без отдельных
   правил под 1/2/3 карточки. justify-content:start прижимает получившийся
   ряд к левому краю (та же сторона, что и раньше -- визуальный порядок карточек
   не меняется). minmax(200px, 260px) -- тот же порядок ширины, что карточка
   получает и в обычной 4-колоночной раскладке на этом контейнере (не раздувает
   единственную карточку на всю ширину контента, не сжимает её уже привычного
   размера). Регресс-тест: tests/test_catalog_round5_size_vocab_and_sparse_grid_2026_08_10.php. */
.products-grid.products-grid--sparse {
    grid-template-columns: repeat(auto-fit, minmax(200px, 260px));
    justify-content: start;
}

/* =============================================
   WIDESCREEN (каталог) -- Фаза 2 аудит, находка №9
   ============================================= */
/* .container уже расширяется на 1440px+ (см. base.css) -- без этой
   надстройки .products-grid (components.css, repeat(4,1fr)) просто
   растягивал бы те же 4 карточки на всю новую ширину (шире поля, не
   больше товаров в ряду), находка №9 явно требует обратного ("не сжимать
   сетку до 3 карточек" -- т.е. добавлять колонки, а не просто расширять
   существующие). :not(.products-grid--sparse) -- разрежённые категории
   (см. её докблок ниже по файлу) уже сами по себе саморасширяются под
   любую ширину контейнера через repeat(auto-fit, minmax(...)) -- отдельная
   надстройка под них не нужна и конфликтовала бы с auto-fit. Специфичность
   компаунд-селектора (.catalog-main + .products-grid + :not(...)) --
   0,0,3,0, гарантированно бьёт голую components.css'ную .products-grid
   (0,0,1,0) независимо от порядка подключения файлов -- тот же приём, что
   уже применяет .products-grid--sparse выше. Область намеренно ограничена
   .catalog-main (не глобальный .products-grid) -- та же карточная сетка
   используется на home.php/product.php/order_success.php ("похожие
   товары"/"хиты"), которые НЕ входят в периметр этого юнита (только
   catalog.css/base.css, см. ТЗ) и не должны получить лишнюю колонку без
   отдельного решения по каждой странице.
   5 колонок при 1600px-контейнере (см. base.css) даёт карточку шириной
   ~237px -- тот же порядок ширины, что и дефолтные 4 колонки при 1220px
   (~207px), плотность не проседает, просто помещается на одну карточку
   больше в ряд. */
@media (min-width: 1440px) {
    .catalog-main .products-grid:not(.products-grid--sparse) {
        grid-template-columns: repeat(5, 1fr);
    }
}

/* =============================================
   АДАПТИВ (каталог)
   ============================================= */
@media (max-width: 1100px) {
    .catalog-layout   { grid-template-columns: 200px 1fr; }
}
@media (max-width: 900px) {
    .catalog-layout   { grid-template-columns: 1fr; }
    /* УДАЛЕНО: .catalog-sidebar { position: static; } -- эта строка была
       найдена в сегодняшнем аудите как критический баг: тот же брейкпоинт
       (max-width:900px), та же специфичность (0,0,1,0), но объявлен ФИЗИЧЕСКИ
       позже блока 3.6 (см. выше, ~line 188) -- по каскаду побеждал этот,
       превращая .catalog-sidebar обратно в position:static и ломая
       drawer-оверлей: панель фильтров раздвигала страницу вместо того,
       чтобы появляться поверх неё. Сама grid-колонка 1fr остаётся нужной --
       на мобильном сайдбар выходит из грид-потока (position:fixed), контенту
       не нужна вторая колонка. */
}

/* =============================================
   МОБИЛЬНЫЕ СОСТОЯНИЯ КНОПОК (каталог)
   ============================================= */
.filter-size-btn:active {
    filter: brightness(0.9);
    transform: scale(0.96);
}

/* ЗАДАЧА 2 — touch-таргеты (каталог) */
@media (max-width: 640px) {
    .filter-size-btn { width: 44px; height: 44px; font-size: 0.8125rem; }
}
@media (max-width: 768px) {
    .filter-cat-list a { padding: 12px 14px; min-height: 48px; }
}
