/* Тут жили «сироти» — тіла правил без селекторів (їх стерли 06.09.2026 разом
   зі старою панеллю фільтрів). Браузер приклеював таку сироту до наступного
   правила й відкидав обидва: мобільний блок нижче (дві колонки, розміри під
   палець, відступ під панель кошика) не працював зовсім. Прибрано 25.10.2026. */
/* Локальні імена лишились (їх тут сотня використань), але значення
           тепер беруться з дизайн-системи (filament/theme.blade.php). Саме з
           цих токенів вона колись і виросла — тепер зв'язок став прямим, і
           каталог не може розійтися з рештою CRM по кольору чи по куту.

           Власний font-family звідси прибрано навмисно. Він примусово ставив
           -apple-system, тобто на макбуку каталог набирався SF Pro, а на
           Windows і Android — чимось іншим, і єдина сторінка з фотографіями
           виглядала на кожній машині по-своєму. Тепер успадковується той
           самий Inter, що й скрізь. */
        /* .pv-backdrop — швидкий перегляд винесено в body (@teleport), тож
           змінні й успадковане від .cx він має отримувати сам. */
        .cx, .pv-backdrop {
            --surface: var(--ds-surface); --text: var(--ds-ink); --muted: var(--ds-muted);
            --border: var(--ds-border); --field: var(--ds-field); --accent: var(--primary-600);
            --shadow: none; --radius: .375rem; /* класичний стиль A, 03.11.2026: було 1rem і тінь */
            -webkit-font-smoothing: antialiased; color: var(--text);
            letter-spacing: -.011em;
        }
        .dark .cx, .dark .pv-backdrop { --accent: var(--primary-400); }

        /* Тулбар (без blur) */
        /* Липне ПІД верхньою панеллю Filament, а не поверх неї.
           
           top: .5rem означало «до самого верху вікна», і на телефоні панель
           пошуку заїжджала під бургер і аватар: кнопка кошика опинялась
           наполовину за кружком профілю. Верхня панель має висоту 4rem —
           саме на неї й відступаємо. */
        .cx-bar { position: sticky; top: calc(4rem + .5rem); z-index: 10; display: flex; align-items: center; gap: .75rem;
            padding: .55rem .65rem; margin-bottom: 1.1rem; border-radius: .375rem;
            background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow); }
        .cx-title { font-size: var(--ds-fs-md); font-weight: 650; padding-inline-start: .5rem; white-space: nowrap; }
        .cx-search { flex: 1; min-width: 0; display: flex; align-items: center; gap: .5rem; padding: .48rem .8rem;
            border-radius: 10px; background: var(--field); border: 1px solid transparent; }
        /* Підсвічуємо саму пілюлю, а не поле всередині неї.
           
           Загальне правило теми малює обведення на будь-якому :focus-visible —
           і всередині круглої панелі пошуку зʼявлявся синій ПРЯМОКУТНИК,
           обведений навколо input. Виглядало як помилка верстки, хоча це
           звичайна доступність. Тому обведення переносимо на контейнер: він і
           так має форму поля. */
        .cx-search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent); }
        .cx-search input:focus,
        .cx-search input:focus-visible { outline: none; box-shadow: none; }
        .cx-search input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--text); font-size: var(--ds-fs-sm); }
        /* Розмір дублюється в самій розмітці (width/height на <svg>) — саме
           він рятує від «стрибка лупи» на завантаженні: поки цей файл ще
           їде, браузер малює іконку натуральним розміром, тобто величезною.
           Тут лишається колір і те, що іконка не розтягується. */
        .cx-search svg { width: 16px; height: 16px; color: var(--muted); flex: none; }
        /* Скільки знайдено — прямо в полі пошуку: окремий рядок під це
           тримати не варто, а число потрібне поруч із запитом. */
        .cx-found { flex: none; font-size: var(--ds-fs-2xs); font-weight: 700; color: var(--muted);
            font-variant-numeric: tabular-nums; }
        .cx-cart { display: inline-flex; align-items: center; gap: .5rem; padding: .5rem .9rem; border-radius: 10px;
            background: var(--text); color: var(--surface); font-weight: 600; font-size: var(--ds-fs-sm); white-space: nowrap; text-decoration: none; }
        .cx-cart svg { width: 17px; height: 17px; }
        .cx-cart .badge { background: var(--accent); color: #fff; border-radius: 999px; min-width: 19px; height: 19px;
            display: inline-flex; align-items: center; justify-content: center; font-size: var(--ds-fs-2xs); padding: 0 6px; }

        /* ── Керування одним рядком ─────────────────────────────────────
           Три випадні списки, лічильник і ряд вивантажень займали до третини
           екрана телефона — на вкладці, де головне фотографії. Спробували
           сховати їх у шторку (зникає стан: людина бачить дві картки й не
           розуміє чому) і розкласти смужками чипів (24 категорії та 29
           розмірів — це не мінімалізм, а два ряди шуму).

           Лишилось найпростіше: РЯДОК ТЕКСТУ. Три слова, кожне натискається й
           відкриває свій список на місці. Обране заміняє підпис і темніє, тож
           стан видно завжди, а керування не має ані рамок, ані плашок. */
        .cx-line { display: flex; align-items: center; margin: 0 0 1rem; padding-inline: .15rem;
            font-size: var(--ds-fs-sm); color: var(--muted); }
        .cx-sep { opacity: .45; padding: 0 .45rem; }
        .cx-token { border: 0; background: transparent; padding: .1rem 0; font: inherit; cursor: pointer;
            color: var(--muted); border-bottom: 1px dashed var(--border); white-space: nowrap;
            max-width: 11rem; overflow: hidden; text-overflow: ellipsis; }
        .cx-token:hover { color: var(--text); }
        /* Обране — темне й важче: рядок читається як речення про поточну
           вибірку, а не як набір кнопок. */
        .cx-token.on { color: var(--text); font-weight: 650; border-bottom-color: var(--muted); }

        .cx-menu { position: relative; display: inline-flex; min-width: 0; }
        /* Вивантаження — праворуч, окремо від фільтрів: прайс, галерея й ZIP
           потрібні раз на день, а не під час гортання. */
        .cx-line .cx-menu.cx-more { margin-inline-start: auto; }
        .cx-icon { display: inline-flex; align-items: center; justify-content: center;
            width: 30px; height: 30px; border: 0; border-radius: 8px; background: transparent;
            color: var(--muted); cursor: pointer; }
        .cx-icon:hover, .cx-icon.on { background: var(--field); color: var(--text); }
        .cx-icon svg { width: 16px; height: 16px; }

        .cx-pop { position: absolute; top: calc(100% + .45rem); inset-inline-start: 0; z-index: 25;
            min-width: 13rem; max-height: 60vh; overflow-y: auto; display: flex; flex-direction: column; gap: 1px;
            padding: .35rem; border-radius: .375rem; background: var(--surface); border: 1px solid var(--border);
            box-shadow: var(--shadow-lg); }
        .cx-pop.to-end { inset-inline-start: auto; inset-inline-end: 0; }
        .cx-pop button { display: flex; align-items: center; justify-content: space-between; gap: .75rem;
            text-align: start; padding: .45rem .6rem; border: 0; border-radius: 8px; background: transparent;
            color: var(--text); font-size: var(--ds-fs-sm); cursor: pointer; white-space: nowrap; }
        .cx-pop button:hover { background: var(--field); }
        .cx-pop button.on { background: var(--field); font-weight: 700; }
        /* Вкладена категорія: відступ лишається, але тепер із гілкою — разом
           вони кажуть те саме двома способами, і зсунутий рядок більше не
           виглядає випадковим. Відступ зменшено рівно на ширину гілки, щоб
           назва стояла там само, де стояла. */
        .cx-pop button.sub { padding-inline-start: 1rem; color: var(--muted); }
        .cx-pop .cx-branch { margin-inline-end: .35rem; opacity: .8; }
        /* Трикутник біля батьківської.
           Перша версія була .62rem приглушеним сірим — тобто десять пікселів,
           які на екрані просто не помічаєш. Значок, чия робота «не сплутати»,
           мусить бути видно з першого погляду, лишаючись підказкою, а не
           другою назвою: той самий кегль, що в числа поруч, і колір тексту. */
        .cx-pop .cx-has-sub { margin-inline-start: .4rem; font-size: var(--ds-fs-2xs); }
        .cx-pop .n { font-size: var(--ds-fs-2xs); color: var(--muted); font-variant-numeric: tabular-nums; }

        /* Розкладка */
        @media (max-width: 1024px) { .cx-body { grid-template-columns: 1fr; gap: .9rem; } }


        /* Сітка карток */
        .cx-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(185px, 1fr)); gap: 1rem; }
        .card { position: relative; border-radius: var(--radius); overflow: hidden; background: var(--field);
            aspect-ratio: 4/5; box-shadow: var(--shadow);
            /* Волосяна межа поверх фото: світлини одягу зняті на білому тлі, і
               без неї верхній край картки просто зникав на світлій темі. */
            outline: 1px solid var(--border); outline-offset: -1px;
            transition: transform .22s var(--ds-ease), box-shadow .22s var(--ds-ease);
            /* Сітка буває на сотню карток. Поза екраном браузер має право їх
               не малювати — розмір при цьому вже відомий з aspect-ratio, тож
               смуга прокрутки не стрибає. */
            /* content-visibility прибрано навмисно.
               
               Воно дозволяло браузеру не малювати картки за екраном — на
               сотні карток це мало сенс. Але порція тепер 24, виграш зник, а
               ціна лишилась: на швидкій прокрутці картка встигала показатись
               ще НЕ намальованою, і це видно як білі спалахи. */
        }
        .card-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
            transition: transform .5s var(--ds-ease); }

        /* Наведення — лише на пристроях із курсором: на телефоні :hover
           «залипає» після дотику й картка лишається піднятою. Рухаються тільки
           transform і тінь, тобто робота йде повз розкладку. */
        @media (hover: hover) {
            .card:hover { transform: translateY(-2px); }
            .card:hover .card-img { transform: scale(1.045); }
        }

        @media (prefers-reduced-motion: reduce) {
            .card, .card-img { transition: none !important; }
            .card:hover { transform: none; }
            .card:hover .card-img { transform: none; }
        }
        .card-none { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: var(--ds-fs-xs); }
        .card-grad { position: absolute; inset: 0; background: linear-gradient(to top, rgba(0,0,0,.68) 0%, rgba(0,0,0,.1) 40%, rgba(0,0,0,0) 60%); }

        .card-price { position: absolute; top: .6rem; left: .6rem; display: flex; flex-direction: column; align-items: flex-start; line-height: 1.15;
            padding: .32rem .55rem; border-radius: 8px; color: #fff; background: rgba(0,0,0,.58); }
        .card-price b { font-size: var(--ds-fs-sm); font-weight: 700; }
        .card-price s { font-size: var(--ds-fs-2xs); opacity: .82; text-decoration: none; font-weight: 500; }

        .card-info { position: absolute; left: 0; right: 0; bottom: 0; padding: .7rem; display: flex; flex-direction: column; gap: .5rem; }
        .card-sku { color: #fff; font-weight: 650; font-size: var(--ds-fs-md); }
        /* Один рядок і без переносів: розміри — це ритм картки, а не абзац.
           Що не вмістилось, згортається в «+2» (відкриває картку, де видно всі). */
        .card-sizes { display: flex; flex-wrap: nowrap; gap: 4px; }
        /* Розмір + залишок.
           ОКО бачить 26px, ПАЛЕЦЬ влучає в 44px: сама пілюля мала, а зона
           натискання розтягнута невидимим ::after поверх неї. Раніше цей
           конфлікт вирішувався в лоб — чип робили 42px заввишки, і чотири
           розміри вже не вміщались у рядок на телефоні. */
        .size { position: relative; min-width: 26px; height: 26px; padding: 0 7px; display: inline-flex; align-items: center; justify-content: center;
            gap: 3px; font-size: var(--ds-fs-2xs); font-weight: 650; border-radius: 8px; cursor: pointer; border: 1px solid rgba(255,255,255,.28);
            background: rgba(255,255,255,.16); color: #fff; }
        .size::after { content: ''; position: absolute; inset: -9px -4px; }
        .size:hover { background: #fff; color: #0b0b0f; }
        .size:hover .n { background: rgba(0,0,0,.12); color: #0b0b0f; }
        /* Залишок на чипі: партнер бачить дефіцит до того, як покладе в кошик. */
        .size .n { font-size: var(--ds-fs-2xs); font-weight: 700; line-height: 1; padding: 2px 4px; border-radius: 5px;
            background: rgba(0,0,0,.28); color: rgba(255,255,255,.92); font-variant-numeric: tabular-nums; }
        /* Згорнутий залишок розмірів. Пунктир — щоб читалось як «є ще», а не
           як ще один розмір, який можна натиснути. */
        .size.more { background: transparent; border-style: dashed; cursor: pointer; }
        .size.low { border-color: rgba(255,159,10,.85); }
        .size.low .n { background: #ff9f0a; color: #1a1200; }

        /* Картка, якої немає в наявності: показуємо, але чесно. Сіре фото
           читається як «недоступно» ще до того, як людина дочитає напис. */
        .card.is-out .card-img { filter: grayscale(.9); opacity: .55; }
        .card-out { align-self: flex-start; padding: .3rem .55rem; border-radius: 8px; font-size: var(--ds-fs-2xs);
            font-weight: 650; color: #fff; background: rgba(0,0,0,.55); border: 1px solid rgba(255,255,255,.3); }

        .cx-empty { grid-column: 1/-1; border: 1px dashed var(--border); border-radius: var(--radius); padding: 4rem 1rem; text-align: center; color: var(--muted); }

        .cx-sub { display: flex; align-items: center; justify-content: space-between; gap: .75rem; margin-bottom: .9rem; flex-wrap: wrap; }
        .cx-sort { display: flex; align-items: center; gap: .5rem; font-size: var(--ds-fs-xs); color: var(--muted); }
        .cx-sort select { padding: .4rem .6rem; border: 1px solid var(--border); border-radius: 9px; background: var(--surface); color: var(--text); font-size: var(--ds-fs-sm); outline: none; }
        .cx-sort select:focus { border-color: var(--accent); }
        .cx-more-btn { padding: .7rem 1.4rem; border: 1px solid var(--border); border-radius: 11px; background: var(--surface); color: var(--text); font-weight: 600; font-size: var(--ds-fs-sm); cursor: pointer; box-shadow: var(--shadow); }
        .cx-more-btn:hover { background: var(--field); }
        .cx-more-btn[disabled] { opacity: .5; }

        .cx-filter { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; margin-bottom: 1rem; }
        .cx-filter .lbl { font-size: var(--ds-fs-2xs); color: var(--muted); font-weight: 600; }
        .szf { min-width: 34px; height: 30px; padding: 0 10px; display: inline-flex; align-items: center; justify-content: center;
            font-size: var(--ds-fs-xs); font-weight: 600; border-radius: 8px; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: pointer; }
        .szf:hover { background: var(--field); }
        .szf.on { background: var(--text); color: var(--surface); border-color: var(--text); }
        .cx-dl { margin-left: auto; display: inline-flex; align-items: center; gap: .4rem; padding: .5rem .9rem; border-radius: 9px;
            border: 0; background: var(--accent); color: #fff; font-weight: 600; font-size: var(--ds-fs-sm); cursor: pointer; }
        .cx-dl[disabled] { opacity: .6; cursor: default; }
        /* Галерея — для телефона. На компʼютері з мишею лишається архів:
           там «Поділитися» відкривало AirDrop замість завантаження. */
        @media (pointer: fine) { .cx-pop .cx-dl { display: none; } }

        /* ── Мобільні пристрої ── */
        @media (max-width: 640px) {
            .cx-bar { padding: .5rem; gap: .5rem; top: calc(4rem + .25rem); }
            .cx-grid { grid-template-columns: repeat(2, 1fr); gap: .6rem; }
            .cx-token { max-width: 7.5rem; }
            .cx-sep { padding: 0 .3rem; }
            .card-sku { font-size: var(--ds-fs-sm); }
            .card-price b { font-size: var(--ds-fs-sm); }
            /* Цільова зона пальця лишається великою — але не за рахунок вигляду.
               Пілюля 28px, невидима зона навколо неї доводить ціль до 46px,
               тож промахів не більше, а рядок нарешті вміщає всі розміри. */
            .size { min-width: 32px; height: 32px; font-size: var(--ds-fs-2xs); }
            .size::after { inset: -7px -2px; }
            .size .n { font-size: var(--ds-fs-2xs); }
            .card-sizes { gap: 4px; }
            /* Липка панель кошика перекриває низ сторінки — лишаємо їй місце.
               Відступ саме на .cx, а не на сітці: під сіткою є ще кнопка
               «Показати більше», і вона так само опинялась під панеллю.
               Тільки коли панель реально є — з порожнім кошиком порожнеча внизу
               ні до чого. */
            .cx.has-dock { padding-bottom: calc(4.75rem + env(safe-area-inset-bottom)); }
        }
        @media (max-width: 360px) {
            .cx-grid { grid-template-columns: 1fr; }
        }

        /* ── Кошик на телефоні — плаваюча кнопка ──
           Власник (30.10.2026): без панелі на всю ширину, лише іконка з
           кількістю позицій. Кругла кнопка в правому нижньому куті — під
           великим пальцем, нічого не затуляє. */
        .cx-dock { display: none; }
        @media (max-width: 640px) {
            /* z-index нижчий за бічне меню Filament (30) — навіть якщо Alpine
               ще не встиг сховати кнопку, вона не опиниться поверх меню. */
            .cx-dock {
                position: fixed; right: 16px; bottom: calc(16px + env(safe-area-inset-bottom)); z-index: 20;
                width: 56px; height: 56px; border-radius: 50%;
                display: grid; place-items: center;
                background: var(--text); color: var(--surface); text-decoration: none;
                box-shadow: 0 10px 24px -8px rgba(0,0,0,.45), 0 2px 6px rgba(0,0,0,.12);
                transition: transform .15s ease;
                -webkit-tap-highlight-color: transparent;
            }
            .cx-dock:active { transform: scale(.94); }
            .cx-dock svg { width: 24px; height: 24px; }
            .cx-dock-n {
                position: absolute; top: -2px; right: -2px; min-width: 22px; height: 22px; padding: 0 6px; box-sizing: border-box;
                border-radius: 11px; border: 2px solid var(--bg, #f4f4f4);
                background: var(--brand, #a8601e); color: #fff;
                font-size: var(--ds-fs-2xs); font-weight: 800; line-height: 18px; text-align: center; font-variant-numeric: tabular-nums;
            }
        }

        /* ── Швидкий перегляд товару ── */
        .card { cursor: pointer; }
        /* Сповіщення (Filament z-index 50) — над модалкою (60), щоб було видно «Перейти в кошик» */
        .fi-no { z-index: 70 !important; }
        .pv-backdrop { position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.5); display: flex; align-items: center; justify-content: center; padding: 1rem; }
        .pv { position: relative; width: 100%; max-width: 760px; max-height: 92vh; overflow: auto; background: var(--surface); border-radius: .5rem; box-shadow: 0 20px 60px rgba(0,0,0,.45); display: grid; grid-template-columns: 320px 1fr; align-items: stretch; }
        @media (max-width: 640px) { .pv { grid-template-columns: 1fr; } }

        /* ── Поява й зникання картки ──
           Тінь фону вмикалась ривком (x-show без переходу), а закриття не мало
           чого анімувати взагалі: усе гасло тим самим кадром, у якому клацнули.
           Тепер тінь проявляється, а картка підростає — і так само йде назад.

           Тільки opacity й transform: композитор малює їх на GPU, розкладка не
           перераховується. Нічого з height/max-height тут бути не може — це
           рівно ті ривки на слабкому телефоні, від яких ми пішли.

           Тінь гасне трохи довше за картку (.2s проти .18s) навмисно: картка
           лежить усередині неї, тож має встигнути поїхати ДО того, як зникне
           шар, — інакше останні кадри її руху просто зріже. */
        .pv-bd-in   { transition: opacity .18s ease-out; }
        .pv-bd-out  { transition: opacity .2s ease-in; }
        .pv-bd-from { opacity: 0; }
        .pv-bd-to   { opacity: 1; }

        .pv-in   { transition: transform .22s cubic-bezier(.22,.61,.36,1), opacity .22s ease-out;
                   will-change: transform, opacity; }
        .pv-out  { transition: transform .18s cubic-bezier(.4,0,1,1), opacity .18s ease-in;
                   will-change: transform, opacity; }
        .pv-from { opacity: 0; transform: translateY(10px) scale(.96); }
        .pv-to   { opacity: 1; transform: none; }

        @media (max-width: 640px) {
            /* Знизу вгору: рух збігається з тим, звідки прийшов палець, —
               той самий жест, що й у модалок Filament (motion.blade.php).
               Масштаб майже не чіпаємо, бо картка тут майже на весь екран. */
            .pv-from { transform: translateY(18px) scale(.99); }
        }

        @media (prefers-reduced-motion: reduce) {
            /* Рух прибираємо, коротке проявлення лишаємо: прозорість руху в
               кадрі не створює. Заразом це зберігає transitionend, на якому
               тримається прибирання даних після закриття. */
            .pv-from { transform: none; }
            .pv-in, .pv-out { transition: opacity .12s linear; }
        }

        .pv-close { position: absolute; top: .6rem; right: .6rem; z-index: 2; width: 34px; height: 34px; border: 0; border-radius: 999px; background: rgba(0,0,0,.5); color: #fff; font-size: var(--ds-fs-xl); line-height: 1; cursor: pointer; }
        /* Фото: різкий контейн-шар над розмитим фоном того ж фото —
           бічні «рамки» заповнюються красиво, без пласких сірих полів. */
        /* Гортання знімків у картці товару. Кнопки великі й прозорі: вони
           лежать поверх фотографії, і суцільна плашка з'їдала б саме те,
           заради чого картку відкривають. */
        .pv-nav { position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
            width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
            border: 0; border-radius: 999px; cursor: pointer; font-size: var(--ds-fs-2xl); line-height: 1;
            color: #fff; background: rgba(0,0,0,.42); }
        .pv-nav:hover { background: rgba(0,0,0,.62); }
        .pv-nav.prev { inset-inline-start: .6rem; }
        .pv-nav.next { inset-inline-end: .6rem; }
        .pv-dots { position: absolute; z-index: 3; inset-inline: 0; bottom: .7rem;
            display: flex; justify-content: center; gap: 6px; }
        .pv-dot { width: 7px; height: 7px; border-radius: 999px; cursor: pointer;
            background: rgba(255,255,255,.45); box-shadow: 0 0 0 1px rgba(0,0,0,.18); }
        .pv-dot.on { background: #fff; }
        @media (max-width: 640px) {
            /* На телефоні гортають пальцем, тож стрілки лише заважали б фото. */
            .pv-nav { display: none; }
        }

        .pv-media { position: relative; background: var(--field); overflow: hidden; min-height: 340px; }
        .pv-media-bg { position: absolute; inset: 0; z-index: 0; width: 100%; height: 100%; object-fit: cover; transform: scale(1.2); filter: blur(32px) saturate(1.15) brightness(.96); }
        .dark .pv-media-bg { filter: blur(32px) saturate(1.15) brightness(.55); }
        .pv-media-fg { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; object-fit: cover; display: block; }
        @media (max-width: 640px) {
            .pv-media { height: 40vh; min-height: 0; flex: 0 0 auto; }
            .pv-media-fg { position: relative; width: 100%; height: 100%; object-fit: contain; }
        }

        /* ── Утримання фото = зберегти в галерею ──
           На дотику вимикаємо системне меню: інакше iOS піднімає власний
           збільшений прев'ю-шар і сперечається з нашим жестом. На десктопі
           (є курсор) правий клік лишається як був.
           Уся анімація — тільки transform і opacity: композитор малює їх на GPU,
           без перерахунку розкладки. Розмір самого фото не чіпаємо. */
        @media (hover: none) {
            .pv-media-fg { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
        }
        .pv-hold {
            position: absolute; left: 0; right: 0; bottom: 0; z-index: 3; height: 3px;
            background: var(--accent); pointer-events: none;
            transform: scaleX(0); transform-origin: left center; opacity: 0;
        }
        .pv-hold.on { transform: scaleX(1); opacity: 1; transition: transform .48s linear, opacity .1s ease; }
        .pv-chip {
            position: absolute; top: .6rem; left: .6rem; z-index: 3; pointer-events: none;
            display: inline-flex; align-items: center; gap: .35rem;
            padding: .32rem .62rem; border-radius: 999px;
            background: rgba(0,0,0,.62); color: #fff;
            font-size: var(--ds-fs-2xs); font-weight: 650; white-space: nowrap;
        }
        .pv-chip svg { width: 13px; height: 13px; }
        @media (prefers-reduced-motion: reduce) {
            .pv-hold.on { transition: opacity .1s ease; transform: scaleX(1); }
        }
        .pv-none { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; color: var(--muted); font-size: var(--ds-fs-sm); }
        .pv-body { padding: 1.3rem; }
        .pv-cats { font-size: var(--ds-fs-2xs); color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
        .pv-title { font-size: var(--ds-fs-xl); font-weight: 750; margin: .25rem 0 .1rem; letter-spacing: -.02em; line-height: 1.15; }
        .pv-sku { font-size: var(--ds-fs-sm); color: var(--muted); }
        .pv-price { margin: .85rem 0; display: flex; align-items: baseline; gap: .6rem; }
        .pv-price b { font-size: var(--ds-fs-xl); font-weight: 780; letter-spacing: -.02em; }
        .pv-price s { color: var(--muted); text-decoration: none; font-size: var(--ds-fs-sm); }
        .pv-desc { font-size: var(--ds-fs-sm); color: var(--muted); line-height: 1.55; margin-bottom: 1rem; white-space: pre-line; }
        .pv-sizes-title { font-size: var(--ds-fs-2xs); font-weight: 700; text-transform: uppercase; color: var(--muted); margin-bottom: .5rem; }
        .pv-sizes { display: flex; flex-direction: column; gap: .5rem; }
        .pv-size { display: flex; align-items: center; gap: .6rem; padding: .6rem .8rem; border: 1px solid var(--border); border-radius: .375rem; background: var(--surface); cursor: pointer; width: 100%; }
        .pv-size:hover { border-color: var(--accent); background: var(--field); }
        .pv-size .s { font-weight: 700; min-width: 44px; }
        .pv-size .q { font-size: var(--ds-fs-xs); color: var(--muted); }
        .pv-size .add { margin-left: auto; font-size: var(--ds-fs-xs); font-weight: 650; color: var(--accent); }
        .pv-size.out { opacity: .55; cursor: not-allowed; }
        .pv-size.out .q { color: #e11d48; }
        @media (max-width: 640px) {
            /* Компактна центрована картка (як у старій версії): фото видно
               ПОВНІСТЮ (contain), картка за розміром вмісту — без білого поля. */
            .pv-backdrop { padding: calc(.75rem + env(safe-area-inset-top)) .75rem calc(.75rem + env(safe-area-inset-bottom)); align-items: center; }
            /* dvh, а не vh: на iPhone 92vh вищі за видиму частину екрана, і
               низ картки (розміри) ховався за панеллю Safari. */
            .pv { max-width: 420px; height: auto; max-height: 92vh; max-height: calc(100dvh - 1.5rem - env(safe-area-inset-top) - env(safe-area-inset-bottom));
                  border-radius: 18px; display: flex; flex-direction: column; overflow: hidden; overscroll-behavior: contain; }
            .pv-media { height: min(40vh, 100vw); height: min(40dvh, 100vw); }
            /* min-height: 0 — без нього flex-елемент не стискається нижче
               свого вмісту, прокрутки всередині не виникає, і свайп по
               розмірах ішов на сторінку каталогу позаду. */
            .pv-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; padding: 1rem; }
            .pv-title { font-size: var(--ds-fs-lg); }
            .pv-price { margin: .55rem 0; }
            .pv-price b { font-size: var(--ds-fs-xl); }
            /* Опис не має відтісняти розміри — згортаємо до 2 рядків. */
            .pv-desc { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                       overflow: hidden; margin-bottom: .7rem; }
            .pv-sizes { gap: .4rem; }
            .pv-size { padding: .55rem .7rem; }
        }

/* ── Відчуття швидкості ─────────────────────────────────────────────────
   Мережу зробити швидшою не можна, але ОЧІКУВАННЯ можна зробити непомітним.
   Три речі нижче не пришвидшують нічого — вони прибирають три сорти
   «завмирань», через які інтерфейс здається повільнішим, ніж він є. */

/* 1. Замість порожньої дірки — мерехтлива підкладка.
   Порожнє сіре місце читається як «зламалось»; рух читається як «вантажиться».
   Тому підкладка живе під кожним фото й ховається під ним, щойно воно є. */
/* Підкладка СТАТИЧНА, без мерехтіння.
   
   Спершу тут була біжуча смуга — вона мала показувати «вантажиться». На
   практиці вийшло інше: картки з ще не завантаженими фото пульсують просто
   під час прокрутки, і весь список здається тремтливим. Спокійна підкладка
   каже те саме («тут скоро буде фото»), не смикаючи око. */
.card { background: var(--field); }

/* Саме фото проявляється, а не зʼявляється ривком. */
/* Фото НЕ ховається до події завантаження.
   
   Раніше воно стояло на opacity: 0 й проявлялось за onload. У парі з
   пропуском малювання це давало найгірше: подія могла не дійти до картки,
   яку браузер саме «пропустив», і фото лишалось невидимим — виглядало як
   «товар без фото», хоча файл давно поруч.
   
   Тепер картинка видима завжди, а плавність дає коротка поява — вона не
   залежить від жодних подій і не може «застрягнути». */
.card-img { animation: cx-fade .28s var(--ds-ease) both; }

@keyframes cx-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* 2. Дотик має відгукнутись НЕГАЙНО.
   Livewire відповість через десятки мілісекунд, і весь цей час палець не має
   підстав вірити, що натискання зараховане. Легке втискання дає цю підставу
   миттєво — воно не чекає ні мережі, ні JS. */
/* Відгук на дотик — яскравістю, а не зсувом.
   
   Втискання виглядало добре на натисканні й погано на прокрутці: браузер
   вмикає :active з невеликою затримкою, тож картка встигала смикнутись під
   пальцем, що просто гортає список. Зміна прозорості дає той самий миттєвий
   відгук і нічого не рухає. */
@media (hover: none) {
    .card:active { opacity: .82; }
    .size:active, .cx-pill:active, .cx-token:active { opacity: .6; }
}
.card, .size, .cx-pill, .cx-token { transition: opacity .1s linear; }

/* 3. Поки сітка оновлюється — вона тьмяніє, а зверху йде смужка.
   Без цього перемикання фільтра виглядає так, ніби нічого не сталося: старі
   картки стоять на місці, і людина тисне вдруге. */
.cx-grid { transition: opacity .16s linear; }
.cx-grid.busy { opacity: .45; pointer-events: none; }

.cx-progress { position: fixed; inset-inline: 0; top: 0; height: 2px; z-index: 60; overflow: hidden; }
.cx-progress::after {
    content: ''; position: absolute; inset-block: 0; width: 40%;
    background: var(--accent);
    animation: cx-slide 1s var(--ds-ease) infinite;
}

@keyframes cx-slide {
    from { inset-inline-start: -40%; }
    to   { inset-inline-start: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .card-img, .cx-progress::after { animation: none !important; transition: none !important; }
}

/* Стара ціна поруч із ціною зі знижкою: дрібно, закреслено, приглушено —
   видно, що знижка є, але око першим бере нову ціну. */
.card-price .card-was { display: block; font-size: var(--ds-fs-2xs); font-weight: 500; opacity: .75; text-decoration: line-through; }

/* ── Заглушка, поки фото картки вантажиться ──
   Сіра пляма без руху читається як «тут порожньо»; світла смуга, що
   пробігає по ній, — як «зараз буде». Лише transform: смуга — псевдоелемент,
   що їде вбік, тож перемальовки немає. Зникає разом із .ready (фото
   завантажилось або його немає). */
@media (prefers-reduced-motion: no-preference) {
    .card:not(.ready)::before {
        content: '';
        position: absolute;
        inset: 0;
        background: linear-gradient(100deg, transparent 20%, color-mix(in srgb, #fff 55%, transparent) 50%, transparent 80%);
        transform: translateX(-100%);
        animation: ds-shimmer 1.2s ease-in-out infinite;
    }
    @keyframes ds-shimmer { to { transform: translateX(100%); } }
}

/* NEW і «Поділитись» поверх фото — на всіх екранах (власник, 02.11.2026: «не
   лише на мобільній версії має бути все нове»). На комп'ютері стрілка
   завантажує оригінали фото (Web Share із файлами там немає), див.
   photoShare у catalog.js. */
.card-share {
    position: absolute; z-index: 3; right: 8px; top: 8px; width: 32px; height: 32px; border-radius: 50%;
    display: grid; place-items: center; border: 0; color: #fff; cursor: pointer;
    background: rgb(12 12 12 / .42); -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    transition: background .15s ease, transform .15s ease;
}
.card-share::after { content: ""; position: absolute; inset: -7px; }
.card-new {
    position: absolute; z-index: 3; top: 12px; right: 46px; height: 19px; padding: 0 7px; border-radius: 6px;
    display: grid; place-items: center; background: rgb(255 255 255 / .94); color: #111;
    font-size: var(--ds-fs-2xs); font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
@media (hover: hover) {
    .card-share:hover { background: rgb(12 12 12 / .7); transform: scale(1.06); }
}

/* ── Картка товару на телефоні: як було до кіту (власник, 26.10.2026) ──
   Фото на всю картку, ціна скляною плашкою зліва вгорі, артикул і розміри —
   поверх фото знизу (стилі нижче, у @media 640px). Кіт робив білу картку з
   фото 1:1 і текстом під ним — власник попросив повернути попередній вигляд.
   Від кіту лишились лише «Поділитись» / NEW (тільки телефон) і 2 стовпчики. */
@media (max-width: 1023.98px) {
    .cx-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
    .card-nm { display: none !important; }
    .is-drop-first .card-price { flex-direction: column-reverse; }
    .is-drop-first .card-price b { font-size: var(--ds-fs-2xs); font-weight: 500; opacity: .78; }
    .is-drop-first .card-price s { font-size: var(--ds-fs-sm); font-weight: 700; opacity: 1; text-decoration: none; }
    .card:hover { transform: none; }
    /* Гість і покупець бачать одну ціну (без дропу) — для них цінник
       світла пігулка з чорною жирною ціною: темна плашка під два рядки з
       одним числом виглядала порожньою (власник, 29.10.2026). Знижка —
       стара ціна закреслена поруч, дрібно. */
    .card-price:not(:has(s)) {
        top: 10px; left: 10px; padding: 5px 11px; border-radius: 999px;
        background: rgb(255 255 255 / .95) !important; color: #111;
        box-shadow: 0 2px 10px rgb(0 0 0 / .18); flex-direction: row; align-items: baseline; gap: 6px;
    }
    .card-price:not(:has(s)) b { font-size: var(--ds-fs-md); font-weight: 800; letter-spacing: -.01em; }
    .card-price:not(:has(s)) .card-was { color: #6b6b6b; opacity: 1; }
    /* Пошук + кошик липнуть ВПРИТУЛ під верхньою панеллю (власник,
       26.10.2026). Відступ 4.5rem був під панель, коли прокручувалось вікно;
       на телефоні прокручується .fi-main-ctn, що й так починається під
       панеллю, — і в щілину над смужкою просвічували картки. Тло суцільне,
       на всю ширину, без тіні й рамки — смужка продовжує панель. */
    .cx-bar { top: 0 !important; margin: 0 -1rem .6rem !important; padding: .5rem 1rem !important;
        border: 0 !important; border-radius: 0 !important; box-shadow: 0 1px 0 var(--border) !important;
        background: var(--bg, var(--ds-bg)) !important; }

    /* Розміри у відкритій картці — та сама мова: тонкий чорний контур. */
    .pv-size { border: 0; box-shadow: inset 0 0 0 1px rgb(17 17 17 / .7); border-radius: .375rem; }
    .pv-size .add { color: var(--text); font-weight: 700; }
    .pv-size:active { background: #111; box-shadow: inset 0 0 0 1px #111; }
    .pv-size:active :is(.s, .q, .add) { color: #fff; }
    .pv-size.out { box-shadow: inset 0 0 0 1px rgb(17 17 17 / .18); }
    .dark .pv-size { box-shadow: inset 0 0 0 1px rgb(255 255 255 / .55); }

}

