/* ==========================================================================
   Alice's Looks — вітрина. Тепло-мінімальна тема.
   Порядок: токени → база → міст до Bootstrap → компоненти → адаптив.
   Кольори брати лише зі змінних нижче, hex у компонентах не хардкодити.
   ========================================================================== */

:root {
    /* Палітра. Основа — логотип: темно-синій бант на теплому жовтому. */
    --shop-primary: #2e4666;
    --shop-primary-deep: #223550;
    --shop-secondary: #fccf71;
    --shop-secondary-soft: #fdecc9;
    /* Акцент — тільки промо: бейдж знижки і ціна зі знижкою. */
    --shop-accent: #b4553f;
    /* Помилки мають власний тон: спільний колір зі знижкою робив із неї сигнал проблеми.
       Червоніший і насиченіший за акцент, лишається теплим і дає 5.5:1 на --shop-page. */
    --shop-danger: #c02a1e;

    --shop-ink: #231f1b;
    --shop-ink-soft: #6f665d;
    /* Тон тримає теплу гаму, але лишається читабельним: 4.5:1 навіть на найтемнішому
       тлі вітрини --shop-surface, де стоїть .shop-footer-legal. */
    --shop-ink-faint: #736a60;

    --shop-page: #fbf8f3;
    --shop-card: #ffffff;
    --shop-surface: #f4eee5;
    --shop-media: #f1eae0;
    --shop-line: #e8dfd2;
    --shop-line-soft: #f1eadf;
    /* Межа лише для інтерактивних контролів (issue #80): --shop-line на них дає 1.25–1.32:1,
       WCAG 1.4.11 вимагає ≥3:1. Виміряно 3.27:1 на --shop-card і 3.08:1 на --shop-page.
       Декоративні контейнери (.cart-line, .checkout-section, .cart-summary, .pdp-description)
       лишаються на --shop-line — їм контраст межі не регламентований, і робити її жорсткішою
       означало б забрати "повітряність" теми там, де це не потрібно. */
    --shop-line-strong: #9e8c6e;

    /* Шкала відступів */
    --sp-1: .25rem;
    --sp-2: .5rem;
    --sp-3: .75rem;
    --sp-4: 1rem;
    --sp-5: 1.5rem;
    --sp-6: 2rem;
    --sp-7: 3rem;
    --sp-8: 4.5rem;

    /* Висота sticky-шапки. Керує і самою шапкою, і scroll-офсетом якорів. */
    --shop-header-h: 82px;

    /* Розмірна шкала контролів (issue #80): анатомія пальця (44px ≈ 9мм, медіана ширини
       подушечки) і оптична щільність пігулки, а не крок --sp-*, який побудований для
       композиції відступів — звідси окрема шкала, а не --sp-3/--sp-4. До появи цих
       токенів висота контролю мала 7 різних значень (2.4–3.25rem), горизонтальний
       padding пігулки — 4 (.85–1.75rem). */
    --control-h-sm: 2.25rem;
    /* Мінімум зони натискання 44px (WCAG 2.5.5) — базовий розмір для більшості контролів
       вітрини: іконкові кнопки, степери кількості, поля вводу, рядки фільтрів. */
    --control-h-md: 2.75rem;
    --control-h-lg: 3rem;
    --control-pad-x: 1.5rem;

    /* Єдиний індикатор фокуса вітрини — токенізований, щоб не накопичувались варіанти
       (до цього — синій outline 2px, Bootstrap-тінь 4px і form-контроли з rgba-кільцем
       окремої непрозорості одночасно на тому самому елементі). */
    --focus-ring-width: 2px;
    --focus-ring-color: var(--shop-primary);
    --focus-ring: var(--focus-ring-width) solid var(--focus-ring-color);
    --focus-ring-offset: 2px;

    /* Радіуси */
    --r-sm: .5rem;
    --r-md: .75rem;
    --r-lg: 1.25rem;
    --r-pill: 999px;

    /* Тіні — теплі й дуже стримані */
    --shadow-sm: 0 1px 2px rgba(35, 31, 27, .05);
    --shadow-md: 0 14px 34px -18px rgba(35, 31, 27, .28);

    /* Типографіка */
    --font-sans: 'Manrope', system-ui, -apple-system, 'Segoe UI', sans-serif;
    --fs-eyebrow: .6875rem;
    --fs-xs: .75rem;
    --fs-sm: .8125rem;
    --fs-base: .9375rem;
    --fs-md: 1.0625rem;
    --fs-lg: 1.25rem;
    --fs-xl: 1.625rem;
    --fs-display: clamp(1.9rem, 1.2rem + 2.6vw, 3.1rem);
    /* Рівно 1rem, а не крок шкали: 16px — поріг, нижче якого iOS Safari сам масштабує
       сторінку при фокусі на полі. Значення технічне, не дизайнерське. */
    --fs-input: 1rem;
    --tracking-wide: .09em;

    --ease-out: cubic-bezier(.2, .6, .2, 1);
}

/* ---------- База ---------------------------------------------------------- */

/* Місце під смугу прокрутки резервуємо завжди. Інакше зміна кількості в кошику
   вкорочує сторінку, смуга зникає, і липка шапка разом з усім контейнером
   стрибає горизонтально на її ширину. */
html {
    scrollbar-gutter: stable;
}

body.shop-page {
    background: var(--shop-page);
    color: var(--shop-ink);
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    font-weight: 450;
    line-height: 1.6;
    letter-spacing: -.005em;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.shop-page h1,
.shop-page h2,
.shop-page h3,
.shop-page h4 {
    font-weight: 600;
    line-height: 1.2;
    letter-spacing: -.02em;
}

.shop-page ::selection {
    background: var(--shop-secondary);
    color: var(--shop-primary-deep);
}

.shop-page :focus-visible {
    outline: var(--focus-ring);
    outline-offset: var(--focus-ring-offset);
    border-radius: var(--r-sm);
}

/* tabindex="-1" тримають лише цілі програмного переходу — #catalog (CTA героя),
   #main-content (skip-link), офканваси й модалки quick-add. Клавіатурою в них не
   потрапляють: від'ємний tabindex прибирає елемент із порядку табуляції, тож кільце
   тут нікому не повідомляє, де фокус, — користувач сам щойно сюди перейшов.
   Safari, на відміну від Chrome, вважає програмний .focus() на такому елементі за
   :focus-visible, і після тапу «Дивитись каталог» малював 2px рамку навколо всієї
   секції каталогу — на всю ширину екрана, ще й із outline-offset назовні, впритул
   під межею смуги категорій. На справжніх контролах --focus-ring лишається
   обов'язковим (WCAG 2.4.7 стосується елементів, керованих з клавіатури). */
.shop-page [tabindex="-1"]:focus,
.shop-page [tabindex="-1"]:focus-visible {
    outline: none;
}

/* Дрібний капслок-надпис над заголовком секції */
.shop-eyebrow {
    margin-bottom: var(--sp-2);
    color: var(--shop-ink-faint);
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

.shop-section-title {
    margin: 0;
    font-size: var(--fs-xl);
}

/* ---------- Іконки (SVG-спрайт) ------------------------------------------- */

/* Символи з fragments/shop/icons.html — заміна bootstrap-icons (issue #48).
   Розмір масштабується від font-size контексту, як раніше в іконкового шрифту;
   fill: currentColor фарбується від color так само, як раніше .bi. */
.icon {
    width: 1em;
    height: 1em;
    fill: currentColor;
    flex-shrink: 0;
}

/* ---------- Міст до Bootstrap -------------------------------------------- */

.shop-page {
    --bs-body-font-family: var(--font-sans);
    --bs-body-color: var(--shop-ink);
    --bs-body-bg: var(--shop-page);
    --bs-emphasis-color: var(--shop-ink);
    --bs-secondary-color: var(--shop-ink-soft);
    --bs-border-color: var(--shop-line);
    --bs-border-radius: var(--r-sm);
    --bs-link-color: var(--shop-primary);
    --bs-link-hover-color: var(--shop-primary-deep);
    --bs-focus-ring-color: rgba(46, 70, 102, .2);

    /* RGB-трійки семантичних кольорів Bootstrap. `.text-bg-*` (і будь-який інший
       компонент, що читає ці кореневі змінні — `.bg-*`, `.text-*`, `.border-*`)
       інакше фарбували б у стандартну Bootstrap-палітру повз --shop-* (issue #76).
       У теплій палітрі вітрини немає окремого «успіху» — success веде на primary. */
    --bs-primary-rgb: 46, 70, 102;
    --bs-secondary-rgb: 111, 102, 93;
    --bs-success-rgb: 46, 70, 102;
    --bs-info-rgb: 252, 207, 113;
    --bs-warning-rgb: 252, 207, 113;
    --bs-danger-rgb: 192, 42, 30;
    --bs-light-rgb: 244, 238, 229;
    --bs-dark-rgb: 35, 31, 27;
}

.shop-page .btn {
    --bs-btn-font-family: var(--font-sans);
    --bs-btn-font-size: var(--fs-sm);
    --bs-btn-font-weight: 600;
    --bs-btn-border-radius: var(--r-pill);
    --bs-btn-padding-y: .875rem;
    --bs-btn-padding-x: var(--sp-5);
    /* Bootstrap додав би власне 4px rgba-кільце на :focus-visible поверх нашого токенізованого
       outline (shop.css:134) — на тому самому елементі виходило два різні індикатори фокуса
       одночасно (issue #80). outline:0 з Bootstrap програє нашому :focus-visible за порядком
       підключення файлів при однаковій специфічності, тож глушимо тільки тінь. */
    --bs-btn-focus-box-shadow: none;
}

/* Захисно: без цього кнопка «Очистити фільтри» (filter.html) та інші .btn-sm виросли б
   разом з рештою кнопок вітрини — місток вище зачіпає лише звичайні .btn (issue #75).
   Розмір і модалка більше не залежать від Bootstrap-кнопки (issue #77, .pdp-size нижче). */
.shop-page .btn-sm {
    --bs-btn-padding-y: .375rem;
    --bs-btn-padding-x: .5rem;
}

.shop-page .btn-primary,
.shop-page .btn-dark {
    --bs-btn-bg: var(--shop-primary);
    --bs-btn-border-color: var(--shop-primary);
    --bs-btn-hover-bg: var(--shop-primary-deep);
    --bs-btn-hover-border-color: var(--shop-primary-deep);
    --bs-btn-active-bg: var(--shop-primary-deep);
    --bs-btn-active-border-color: var(--shop-primary-deep);
}

/* Bootstrap-дефолт — box-sizing: content-box, width/height: 1em, тож зона натискання
   росла б лише за рахунок padding, а точний --control-h-md (issue #80) через padding
   не порахувати без прив'язки до font-size. box-sizing: border-box дає точний 44×44
   незалежно від padding; іконка-хрестик має фіксований background-size: 1em auto й
   центрується в межах padding box, тож лишається того самого розміру. flex-shrink: 0 —
   інакше довгий заголовок у flex-рядку .modal-header стискає кнопку разом із собою й
   з'їдає щойно додану зону натискання. */
.shop-page .btn-close {
    box-sizing: border-box;
    width: var(--control-h-md);
    height: var(--control-h-md);
    padding: 0;
    flex-shrink: 0;
    /* Vendor-дефолт малює власне 4px rgba(13,110,253,…) кільце — сирий Bootstrap-синій,
       що не проходить навіть через міст (issue #80). :focus, а не :focus-visible, тож
       спрацьовував і на клік мишею. Глушимо, ринг дає токенізований :focus-visible. */
    --bs-btn-close-focus-shadow: none;
}

.shop-page .form-check-input {
    /* --shop-line дає 1.25:1 до сусідньої поверхні — нижче WCAG 1.4.11 (issue #80). */
    border-color: var(--shop-line-strong);
}

.shop-page .form-check-input:checked {
    background-color: var(--shop-primary);
    border-color: var(--shop-primary);
}

/* rgba-кільце нижче дублювало токенізований :focus-visible outline (shop.css:134) власною
   непрозорістю — другий одночасний індикатор фокуса на тому самому контролі (issue #80).
   border-color лишається: видима зміна контролу і на звичайний :focus (клік мишею). */
.shop-page .form-check-input:focus {
    border-color: var(--shop-primary);
    box-shadow: none;
}

.shop-page .form-control:focus,
.shop-page .form-select:focus {
    border-color: var(--shop-primary);
    box-shadow: none;
}

/* Радіус за замовчуванням у Bootstrap іде через --bs-border-radius (--r-sm) — на вітрині
   бейджі мають бути пігулкою, як .product-sale-badge. Колір заливки бере --bs-*-rgb вище
   через .text-bg-*, тож тут його не дублюємо (issue #76). */
.shop-page .badge {
    --bs-badge-border-radius: var(--r-pill);
}

/* ---------- Шапка --------------------------------------------------------- */

/* Поза екраном, поки немає фокуса; клавіатурний фокус зсуває в видиму зону над
   шапкою (issue #61). z-index вище sticky-навбару (у якого немає власного z-index,
   тож стандартного stacking-порядку елементів досить, але fixed-позиція вимагає
   явного значення, щоб лишатися над навбаром при скролі). */
.shop-skip-link {
    display: inline-flex;
    align-items: center;
    /* Самостійна клавіатурна ціль, а не інлайнове посилання в тексті абзацу — потребує
       мінімальної зони натискання 44px (issue #80). */
    min-height: var(--control-h-md);
    position: fixed;
    top: var(--sp-2);
    left: var(--sp-2);
    z-index: 1050;
    padding: var(--sp-2) var(--sp-4);
    background: var(--shop-primary);
    color: #fff;
    border-radius: var(--r-sm);
    font-weight: 600;
    text-decoration: none;
    transform: translateY(-150%);
    transition: transform .18s var(--ease-out);
}

.shop-skip-link:focus {
    transform: translateY(0);
}

/* Загальний :focus-visible outline тим самим кольором, що й фон, злився б. */
.shop-skip-link:focus-visible {
    outline-color: #fff;
}

/* Позначка адмін-перегляду: вітрина закрита для покупців, але адмін бачить справжні
   сторінки, щоб перевірити їх перед відкриттям (issue #90). Всередині sticky-шапки, тому
   лишається на екрані під час скролу — адмін не має забути, що магазин закритий. Смуга
   додає висоти шапці, тож scroll-офсет якорів (--shop-header-h) у цьому режимі трохи
   завеликий; за межі адмін-перегляду це не виходить. */
.shop-admin-preview {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Уся смуга і є клавіатурною й тач-ціллю, тож мінімум 44px (issue #80). */
    min-height: var(--control-h-md);
    padding: var(--sp-2) var(--sp-4);
    background: var(--shop-primary);
    color: #fff;
    font-size: var(--fs-sm);
    font-weight: 600;
    text-align: center;
    text-decoration: none;
}

.shop-admin-preview:hover,
.shop-admin-preview:focus {
    color: #fff;
    text-decoration: underline;
}

/* Загальний :focus-visible outline тим самим кольором, що й фон, злився б, а зовнішній
   offset на смузі на всю ширину обрізало б з боків — тому кільце всередині. */
.shop-admin-preview:focus-visible {
    outline-color: #fff;
    outline-offset: -4px;
}

.shop-navbar {
    min-height: var(--shop-header-h);
    padding-block: var(--sp-3);
    background: var(--shop-page);
    border-bottom: 1px solid var(--shop-line);
}

.shop-logo {
    width: 150px;
    max-height: 46px;
    object-fit: contain;
}

.shop-header-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
}

.shop-icon-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--control-h-md);
    height: var(--control-h-md);
    padding: 0;
    color: var(--shop-primary);
    background: transparent;
    border: 0;
    border-radius: var(--r-pill);
    line-height: 1;
    transition: background-color .18s var(--ease-out), color .18s var(--ease-out);
}

.shop-icon-button .icon {
    font-size: 1.15rem;
}

.shop-icon-button:hover,
.shop-icon-button:focus {
    color: var(--shop-primary-deep);
    background: var(--shop-surface);
}

/* Каталог і пошук у шапці (issue #52): іконки-кнопки без стандартного трикутника
   Bootstrap dropdown-toggle — він поруч із самотньою іконкою виглядав би зайвою деталлю. */
.shop-icon-button.dropdown-toggle::after {
    display: none;
}

.shop-icon-button.show {
    color: var(--shop-primary-deep);
    background: var(--shop-surface);
}

.shop-nav-menu {
    min-width: 15rem;
    max-height: min(70vh, 28rem);
    overflow-y: auto;
}

.shop-nav-search-menu {
    width: 20rem;
    max-width: calc(100vw - 2 * var(--sp-4));
    padding: var(--sp-3);
}

.shop-nav-search-form {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}

.shop-nav-search-menu .shop-search-field {
    margin: 0;
}

.shop-nav-search-submit {
    width: 100%;
    /* 2.75rem = 44px — той самий мобільний орієнтир мінімуму зони натискання, що й
       .product-quick-add-button (issue #69: було лише 34px). */
    min-height: 2.75rem;
}

/* ---------- Герой --------------------------------------------------------- */

.shop-hero {
    position: relative;
    overflow: hidden;
    /* Верх лишається просторим, низ навмисно тісніший: герой і стрічка категорій
       мають читатись як послідовність блоків, а не як два окремих екрани. */
    padding-block: var(--sp-7) var(--sp-5);
    background: var(--shop-page);
}

.shop-hero-inner {
    display: grid;
    grid-template-columns: 1.05fr .95fr;
    /* end, а не center: маскот нижчий за текстову колонку, і центрування лишало б
       над стрічкою категорій порожню смугу там, де ілюстрація вже закінчилась. */
    align-items: end;
    gap: var(--sp-6);
}

.shop-hero-copy {
    position: relative;
    z-index: 1;
}

.shop-hero-title {
    max-width: 14ch;
    margin-bottom: var(--sp-4);
    font-size: var(--fs-display);
    font-weight: 700;
    letter-spacing: -.03em;
}

.shop-hero-text {
    max-width: 42ch;
    margin-bottom: var(--sp-5);
    color: var(--shop-ink-soft);
    font-size: var(--fs-md);
}

/* Жовте коло вже є в самому зображенні маскота — додатковий ореол у CSS не потрібен */
/* Один URL у базовому правилі, image-set із 2x — лише в desktop media query нижче.
   На мобільному коробка маскота ~7rem, тобто 480px джерела вистачає навіть на dpr 3,
   і 2x-запис лише витягнув би вдвічі важчий girl-720 без жодної видимої різниці. */
.shop-hero-figure {
    width: 340px;
    max-width: 100%;
    aspect-ratio: 2 / 3;
    justify-self: center;
    background-image: url("/images/logo/girl-480.webp");
    background-position: center;
    background-repeat: no-repeat;
    background-size: contain;
}

.shop-hero-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    min-height: var(--control-h-lg);
    padding: 0 var(--control-pad-x);
    color: #fff;
    background: var(--shop-primary);
    border-radius: var(--r-pill);
    font-size: var(--fs-sm);
    font-weight: 600;
    letter-spacing: .01em;
    text-decoration: none;
    transition: background-color .18s var(--ease-out), transform .18s var(--ease-out);
}

.shop-hero-cta:hover,
.shop-hero-cta:focus {
    color: #fff;
    background: var(--shop-primary-deep);
    transform: translateY(-1px);
}

/* Пігулку лишаємо там, де CTA — єдина дія сторінки (error, 404, coming-soon).
   У героя головної вона була найконтрастнішим елементом екрана (9.61:1 білим по
   --shop-primary, 48px) і при цьому виконувала скрол, який користувач отримує
   свайпом — а стрічка категорій, справжній вхід у товар, лишалась легшою за неї.
   Знижуємо до тексту: --shop-primary дає ті самі 9.08:1, зона натискання лишається
   48px через успадкований min-height, вага переходить на кружки категорій. */
.shop-hero .shop-hero-cta {
    padding: 0;
    color: var(--shop-primary);
    background: transparent;
}

.shop-hero .shop-hero-cta:hover,
.shop-hero .shop-hero-cta:focus {
    color: var(--shop-primary-deep);
    background: transparent;
    transform: none;
}

/* Підкреслення на окремому span, а не text-decoration на самому посиланні:
   стрілка — flex-item, і лінія на анкорі протягнулась би і під нею. */
.shop-hero .shop-hero-cta-label {
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: .3em;
}

/* Відфільтрована видача — ?category=…, ?q=… або будь-який фасет, той самий hasActiveNarrowing,
   що вже пояснює порожню видачу. Користувач уже обрав, що дивиться, а над каталогом усе ще
   стоїть обіцянка бренду й CTA «Дивитись каталог», який веде в каталог, у якому він уже є.
   На 390×844 це 148px — рівно та висота, через яку ціна першої картки (828…872px) ріжеться
   згином. Прибираємо все, крім H1: він на сторінці єдиний, і без нього структура заголовків
   починалася б з H2 каталогу, тож у розмітці він переходить у .visually-hidden. Секція лишається
   в потоці — вона ж тримає паузу між липкою шапкою і стрічкою категорій, а без неї стрічка
   починалась би за 8px від шапки. Складений селектор, а не один клас: мобільне правило
   .shop-hero нижче має ту саму специфічність і йде пізніше в файлі.
   Стосується лише повного рендера сторінки: htmx свопить сам #productResults, тому вибір
   категорії з уже відкритої вітрини нічого не зсуває — так само, як і в самій стрічці. */
.shop-hero.shop-hero-quiet {
    padding-block: var(--sp-5) 0;
}

/* ---------- Смуга категорій ---------------------------------------------- */

.category-strip {
    /* Розмір кружка живе тут, бо по ньому ж вирівнюються кнопки прокрутки. */
    --category-image-size: 7.25rem;
    background: transparent;
    border-bottom: 1px solid var(--shop-line-soft);
}

.category-strip-scroller {
    position: relative;
}

/* proximity, а не mandatory: стрічка ширша за екран у 3+ рази, і при mandatory кожен
   інерційний змах доводилось би догортувати — браузер підтягував би до найближчого
   кружка навіть посеред швидкого руху. proximity лишає вільне гортання, але не дає
   зупинитись на півкружку, як було досі (scroll-snap-type: none). */
.category-stories {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-4);
    overflow-x: auto;
    padding-block: var(--sp-3) var(--sp-4);
    scroll-snap-type: x proximity;
    scrollbar-width: none;
}

.category-stories::-webkit-scrollbar {
    display: none;
}

.category-story {
    position: relative;
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    width: 7.5rem;
    min-width: 7.5rem;
    padding-bottom: var(--sp-2);
    color: var(--shop-ink);
    text-align: center;
    outline: 0;
}

.category-image {
    position: relative;
    display: block;
    width: var(--category-image-size);
    height: var(--category-image-size);
    overflow: hidden;
    border-radius: 50%;
    background: var(--shop-media);
    box-shadow: 0 0 0 1px var(--shop-line-soft);
    transition: transform .25s var(--ease-out), box-shadow .25s var(--ease-out);
}

.category-image img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
}

.category-image-noscript {
    position: absolute;
    inset: 0;
}

.category-image-all {
    display: grid;
    place-items: center;
    background: var(--shop-surface);
    color: var(--shop-primary);
}

.category-all-mark {
    display: grid;
    place-items: center;
    color: inherit;
    font-size: var(--fs-xl);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

.category-story:hover .category-image,
.category-story:focus .category-image {
    transform: translateY(-2px);
    box-shadow: 0 0 0 1px var(--shop-line), var(--shadow-sm);
}

.category-title {
    display: flex;
    align-items: flex-start;
    justify-content: center;
    min-height: 2.7em;
    color: var(--shop-primary-deep);
    font-size: var(--fs-base);
    font-weight: 600;
    line-height: 1.35;
    transition: color .18s var(--ease-out);
}

.category-story::after {
    width: var(--sp-7);
    height: var(--sp-1);
    background: transparent;
    border-radius: var(--r-pill);
    content: "";
    transition: background-color .18s var(--ease-out);
}

.category-story:hover,
.category-story:focus {
    color: var(--shop-ink);
}

.category-story:hover .category-title {
    color: var(--shop-primary);
}

.category-story.active::after {
    background: var(--shop-secondary);
}

/* Кільце — головний носій активного стану, жовта смужка нижче лишається декором.
   До нього стан для категорії з фото не читався взагалі: смужка --shop-secondary
   дає 1.39:1 до --shop-page (WCAG 1.4.11 вимагає 3:1 для індикатора), а зсув
   підпису --shop-primary-deep → --shop-primary — 1.29:1, тобто нечутний. Інверсія
   кружка нижче працює лише на «Усі», бо чіпляється за .category-image-all, і всі
   22 справжні категорії лишались без підтвердження вибору. --shop-primary дає 9.08:1.
   Правило стоїть після :hover навмисно: специфічність однакова (0,3,0), тож
   наведення на активну категорію лишає кільце, а не підміняє його межею hover. */
.category-story.active .category-image {
    box-shadow: 0 0 0 3px var(--shop-primary);
}

.category-story.active .category-image-all {
    color: var(--shop-secondary);
    background: var(--shop-primary);
    box-shadow: none;
}

.category-story.active .category-title {
    color: var(--shop-primary);
    font-weight: 700;
}

/* Кнопки прокрутки стрічки. Смуга прокрутки прихована, а вертикальне колесо миші
   гортає сторінку, тож без них частина категорій недосяжна звичайною мишею.
   Вирівняні по центру кружка, а не всієї картки: підпис має різну висоту.
   Свідомий виняток зі шкали контролів (issue #80): кнопка вже накриває кружок першої
   категорії на 40px (виміряно на 1440: кнопка x=72…112, картка x=36…156). Ріст до
   var(--control-h-md) (44px) без зміни позиціювання зробив би перекриття більшим —
   а перепозиціювання за межі скролера (left/right: calc(var(--sp-6) * -1)) на вузьких
   екранах виносить кнопку за padding .container, тож лишається на 2.5rem/40px до
   окремої задачі на репозиціювання. */
.category-scroll-button {
    position: absolute;
    top: calc(var(--sp-3) + var(--category-image-size) / 2);
    z-index: 2;
    display: grid;
    place-items: center;
    width: 2.5rem;
    height: 2.5rem;
    padding: 0;
    border: 1px solid var(--shop-line);
    border-radius: 50%;
    background: var(--shop-card);
    color: var(--shop-primary);
    box-shadow: var(--shadow-md);
    transform: translateY(-50%);
    transition: background-color .18s var(--ease-out), color .18s var(--ease-out), opacity .18s var(--ease-out);
}

.category-scroll-button[hidden] {
    display: none;
}

.category-scroll-button:hover:not(:disabled) {
    background: var(--shop-primary);
    color: var(--shop-secondary);
}

.category-scroll-button:disabled {
    opacity: .35;
}

.category-scroll-prev {
    left: 0;
}

.category-scroll-next {
    right: 0;
}

/* ---------- Секція товарів ------------------------------------------------ */

.products-section {
    background: transparent;
    /* Якір із hero: sticky-шапка не повинна накривати заголовок каталогу. */
    scroll-margin-top: var(--shop-header-h);
}

/* Панель керування: фільтри, вигляд, сортування */
.shop-control,
.shop-page .shop-control {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    min-height: var(--control-h-md);
    padding: 0 var(--control-pad-x);
    color: var(--shop-ink);
    background: var(--shop-card);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-pill);
    font-size: var(--fs-sm);
    font-weight: 600;
    line-height: 1;
    transition: border-color .18s var(--ease-out), background-color .18s var(--ease-out), color .18s var(--ease-out);
}

.shop-page .shop-control:hover,
.shop-page .shop-control:focus {
    border-color: var(--shop-primary);
    color: var(--shop-primary);
    background: var(--shop-card);
}

.shop-page .shop-control.active,
.shop-page .shop-control:active {
    color: #fff;
    background: var(--shop-primary);
    border-color: var(--shop-primary);
}

.shop-control-group {
    display: inline-flex;
    padding: .2rem;
    background: var(--shop-surface);
    border-radius: var(--r-pill);
}

/* Фільтри/вигляд/сортування лишаються в один рядок на будь-якій ширині; довгий текст
   сортування колапсується в "…" замість переносу кнопки на 2-й рядок. Еліпсис тут —
   страхувальна сітка, а не робочий стан: до 768px перемикач вигляду прихований
   (d-none d-md-inline-flex у розмітці), бо разом з ним на 360px підпису сортування
   лишалось 29px і кнопка показувала "Но…". */
.shop-controls-row #openFilterButton,
.shop-controls-row .shop-control-group {
    flex-shrink: 0;
}

.shop-controls-row .dropdown {
    min-width: 0;
    flex-shrink: 1;
}

.shop-controls-row .dropdown-toggle {
    min-width: 0;
    width: 100%;
}

.shop-controls-row .text-sort-value {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.shop-page .shop-control-group .shop-control {
    /* Компактний варіант навмисно лишається на власному, вужчому padding, а не на
       --control-pad-x: це сегмент перемикача вигляду, а не самостійна пігулка (issue #80).
       min-width синхронізовано з новим --control-h-md, щоб іконкові кнопки лишались
       квадратними, як і на старому 2.6rem. */
    min-width: var(--control-h-md);
    padding: 0 .85rem;
    background: transparent;
    border-color: transparent;
}

.shop-page .shop-control-group .shop-control.active {
    background: var(--shop-card);
    border-color: transparent;
    color: var(--shop-primary);
    box-shadow: var(--shadow-sm);
}

.shop-page #openFilterButton.has-active-filters {
    color: #fff;
    background: var(--shop-primary);
    border-color: var(--shop-primary);
}

.shop-filter-count {
    display: inline-grid;
    place-items: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 .35rem;
    background: var(--shop-secondary);
    border-radius: var(--r-pill);
    color: var(--shop-primary-deep);
    font-size: var(--fs-eyebrow);
    font-weight: 700;
}

.shop-page .dropdown-menu {
    padding: var(--sp-2);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-md);
    font-size: var(--fs-sm);
}

.shop-page .dropdown-item {
    border-radius: var(--r-sm);
    font-weight: 500;
}

.shop-page .dropdown-item:hover,
.shop-page .dropdown-item:focus {
    background: var(--shop-surface);
}

.shop-page .dropdown-item.active,
.shop-page .dropdown-item:active {
    background: var(--shop-primary);
    color: #fff;
}

/* Пошук у каталозі: окремий рядок на всю ширину, щоб на вузьких екранах не тіснити
   ряд «Фільтри / вигляд / сортування», який лишається однорядковим. */
.shop-search-field {
    position: relative;
    display: flex;
    align-items: center;
}

.shop-search-icon {
    position: absolute;
    left: 1.1rem;
    color: var(--shop-ink-faint);
    font-size: var(--fs-base);
    line-height: 1;
    pointer-events: none;
}

.shop-page .shop-search-input {
    width: 100%;
    min-height: var(--control-h-md);
    padding-left: 3.1rem;
    /* Ширше за ліве поле: справа росте .shop-search-clear (var(--control-h-md), issue #80)
       і без запасу текст ліз би під кнопку раніше плейсхолдера. */
    padding-right: 3.45rem;
    color: var(--shop-ink);
    background: var(--shop-card);
    /* --shop-line дає 1.25:1 до --shop-page — нижче WCAG 1.4.11 (issue #80). */
    border: 1px solid var(--shop-line-strong);
    border-radius: var(--r-pill);
    font-family: inherit;
    /* ≥16px: менший font-size змушує Safari на iOS зумити сторінку при фокусі й не
       повертає масштаб назад. --fs-base (15px) тут не підходить, --fs-md — найближчий
       наявний токен ≥16px. */
    font-size: var(--fs-md);
    transition: border-color .18s var(--ease-out), box-shadow .18s var(--ease-out);
}

.shop-page .shop-search-input::placeholder {
    color: var(--shop-ink-faint);
}

/* Права половина padding-у зарезервована під .shop-search-clear, але та кнопка існує
   лише коли в полі є текст. На порожньому полі те саме місце віддаємо плейсхолдеру —
   інакше «Пошук за назвою або артикулом» обрізається на 320–375px (issue #69). */
.shop-page .shop-search-input:placeholder-shown {
    padding-right: var(--sp-4);
}

.shop-page .shop-search-input:focus {
    border-color: var(--shop-primary);
}

/* Нативний хрестик type="search" є лише в частині браузерів і не має видимого фокусу,
   тому очищення робить власна кнопка — однакова скрізь і доступна з клавіатури. */
.shop-search-input::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}

.shop-page .shop-search-clear {
    position: absolute;
    right: .5rem;
    display: inline-grid;
    place-items: center;
    width: var(--control-h-md);
    height: var(--control-h-md);
    padding: 0;
    color: var(--shop-ink-soft);
    background: transparent;
    border: 0;
    border-radius: var(--r-pill);
    font-size: var(--fs-xs);
    transition: color .18s var(--ease-out), background-color .18s var(--ease-out);
}

.shop-page .shop-search-clear:hover,
.shop-page .shop-search-clear:focus-visible {
    color: var(--shop-primary);
    background: var(--shop-surface);
}

.shop-result-count {
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

.shop-reset-link {
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
    font-weight: 600;
    text-decoration: none;
    border-bottom: 1px solid var(--shop-line);
}

.shop-reset-link:hover {
    color: var(--shop-primary);
    border-bottom-color: var(--shop-primary);
}

/* ---------- Сітка і картка товару ---------------------------------------- */

.product-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--sp-6) var(--sp-5);
}

.product-grid-list {
    grid-template-columns: 1fr;
    gap: var(--sp-5);
}

/* Картка без рамки: межі створює повітря, а не лінія */
.product-card {
    /* 1.5rem (24px) — мінімум WCAG 2.5.8 уже самим кружком, без опори на невидиму
       зону; вона нижче доводить ціль до 44px за правилом проєкту. */
    --product-swatch-size: 1.5rem;

    position: relative;
    display: grid;
    grid-template-rows: auto 1fr;
    background: transparent;
    border: 0;
}

.product-grid-list .product-card {
    grid-template-columns: minmax(180px, 260px) 1fr;
    grid-template-rows: auto;
    gap: var(--sp-5);
    padding-bottom: var(--sp-5);
    border-bottom: 1px solid var(--shop-line-soft);
}

.product-media {
    position: relative;
    overflow: hidden;
    aspect-ratio: 4 / 5;
    background: var(--shop-media);
    border-radius: var(--r-md);
}

.product-grid-list .product-media {
    height: 100%;
    min-height: 240px;
}

.product-image-link {
    display: block;
    height: 100%;
}

.product-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    transition: transform .6s var(--ease-out);
}

.product-card:hover .product-image {
    transform: scale(1.04);
}

.product-body {
    display: flex;
    flex-direction: column;
    padding: var(--sp-3) var(--sp-1) 0;
}

.product-grid-list .product-body {
    padding: var(--sp-2) 0 0;
}

/* У сітці назва отримує всю ширину картки, наявність іде окремим рядком під нею.
   У режимі списку місця вистачає, тому там це один рядок. */
.product-head {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: .1rem;
    min-width: 0;
}

.product-grid-list .product-head {
    flex-direction: row;
    justify-content: space-between;
    gap: var(--sp-3);
}

.product-info {
    width: 100%;
    min-width: 0;
}

/* Слот свотчів тримає висоту й без кольорів, щоб назва та ціна стояли на однакових
   рівнях у всіх картках ряду. Висота дорівнює самому свотчу. */
.product-card-swatches {
    /* Зазор тримає CSS, а не утиліта gap-*: він мусить бути в парі з --control-h-md,
       щоб 44px зони натискання сусідніх свотчів не налазили одна на одну. */
    gap: var(--sp-5);
    min-height: var(--product-swatch-size);
}

.shop-page .product-title {
    margin-bottom: var(--sp-1);
    font-size: var(--fs-base);
    font-weight: 500;
    line-height: 1.4;
    letter-spacing: -.01em;
}

.product-title a {
    display: block;
    min-height: 2.8em;
    color: var(--shop-ink);
    text-decoration: none;
    /* Довге слово без пробілів не має розсувати картку вшир і відсувати ціну та кнопку
       «Додати в кошик» за межі видимого. */
    overflow-wrap: anywhere;
}

/* Обрізання стосується лише сітки: у list layout довга назва має лишатися повною. */
.product-grid:not(.product-grid-list) .product-title a {
    display: -webkit-box;
    max-height: 2.8em;
    overflow: hidden;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.product-title a:hover {
    color: var(--shop-primary);
}

.product-description {
    display: -webkit-box;
    min-height: 3rem;
    overflow: hidden;
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

/* ---------- Галерея quick view ------------------------------------------- */

.quick-add-gallery {
    display: grid;
    gap: var(--sp-3);
}

/* Пропорція та тло ті самі, що в .pdp-stage: швидкий перегляд має читатись як та
   сама сцена, що й сторінка товару, лише меншою. Вписаний кадр лишає з боків поля —
   вони на теплому --shop-media, тому виглядають як паспарту, а не як діра в макеті. */
.quick-add-stage {
    position: relative;
    overflow: hidden;
    aspect-ratio: 4 / 5;
    background: var(--shop-media);
    border-radius: var(--r-lg);
}

/* Гортання тримає нативний scroll-snap, а не JS-карусель: свайп, інерція й
   зупинка на кадрі — це поведінка браузера, JS лишається на індикаторах. */
.quick-add-track {
    display: flex;
    height: 100%;
    overflow-x: auto;
    /* Свайп на краю треку не має ні гортати сторінку під модалкою, ні тягти
       браузерний жест «назад». */
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
}

.quick-add-track::-webkit-scrollbar {
    display: none;
}

/* scroll-behavior не глушиться спільним блоком prefers-reduced-motion нижче — він
   знімає transition і animation, а не плавний скрол, тому вмикаємо адресно. */
@media (prefers-reduced-motion: no-preference) {
    .quick-add-track {
        scroll-behavior: smooth;
    }
}

.quick-add-slide {
    flex: 0 0 100%;
    height: 100%;
    scroll-snap-align: center;
    /* Інакше швидкий свайп перестрибує через кадр і зупиняється через один. */
    scroll-snap-stop: always;
}

/* contain, а не cover: у портретному кадрі cover обрізав саме те, заради чого
   відкривають швидкий перегляд — посадку речі на моделі. Центрує кадр сам
   object-fit, тому слайд лишається звичайним блоком: у grid-слайді з
   place-items: center елемент не розтягується, height: 100% лишається
   нерозв'язаним, і фото поверталось до власної висоти, переростаючи сцену. */
.quick-add-image {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
}

/* Той самий кружок, що й у стрічці категорій, але на --control-h-md: тут він лежить
   поверх фото, де 40px промахнутись легше, ніж у стрічці під пальцем. */
.quick-add-nav {
    position: absolute;
    top: 50%;
    z-index: 2;
    display: grid;
    place-items: center;
    width: var(--control-h-md);
    height: var(--control-h-md);
    padding: 0;
    background: var(--shop-card);
    border: 1px solid var(--shop-line);
    border-radius: 50%;
    box-shadow: var(--shadow-md);
    color: var(--shop-primary);
    transform: translateY(-50%);
    transition: background-color .18s var(--ease-out), color .18s var(--ease-out), opacity .18s var(--ease-out);
}

/* Кегль іконки — як у .shop-icon-button: у 44px кружку успадкований 15px
   виглядав би загубленим. */
.quick-add-nav .icon {
    font-size: 1.05rem;
}

.quick-add-nav:hover:not(:disabled) {
    background: var(--shop-primary);
    color: var(--shop-secondary);
}

/* Гасимо, а не ховаємо: зникла стрілка зсувала б другу по сцені щоразу на
   першому й останньому кадрі. */
.quick-add-nav:disabled {
    opacity: .3;
}

.quick-add-nav-prev {
    left: var(--sp-2);
}

.quick-add-nav-next {
    right: var(--sp-2);
}

.quick-add-dots {
    display: flex;
    justify-content: center;
    gap: var(--sp-2);
}

/* Активна крапка витягується в пігулку, а не просто темнішає: на 6px різниця
   кольору читається гірше за різницю форми. */
.quick-add-dot {
    width: .375rem;
    height: .375rem;
    background: var(--shop-line-strong);
    border-radius: var(--r-pill);
    opacity: .5;
    transition: width .22s var(--ease-out), background-color .22s var(--ease-out), opacity .22s var(--ease-out);
}

.quick-add-dot.active {
    width: 1.125rem;
    background: var(--shop-primary);
    opacity: 1;
}

/* Опис у quick view — довідковий текст, не головна дія: обрізаємо, щоб ціна, варіанти
   й кнопка «Додати в кошик» лишались близько до верху модалки. */
.quick-add-description {
    display: -webkit-box;
    overflow: hidden;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
}

/* Bootstrap-клас .h5 (вага 500) знімали разом із розміром — ціна лишається жирною,
   як і скрізь на сайті: PDP 28/700, липка панель і картка теж 700 (issue #75). */
.quick-add-price {
    font-size: var(--fs-lg);
    font-weight: 700;
}

/* Закріплений футер quick view. Bootstrap розставляє дітям .modal-footer власні
   margin'и замість gap — обнуляємо їх, інакше рядок з flex-basis: 100% виходив би
   за ширину модалки. Нотатка про недоступність стоїть першою в DOM і займає весь
   рядок, тому лишається над ціною та кнопкою. */
.quick-add-footer {
    justify-content: space-between;
    gap: var(--sp-3);
}

.quick-add-footer > * {
    margin: 0;
}

.quick-add-footer .quick-add-unavailable,
.quick-add-footer .quick-add-submit {
    flex: 1 1 100%;
}

/* З 768px ціна й кнопка стають одним рядком: місця вистачає, і кнопка на всю ширину
   виглядала б важчою за саму модалку. */
@media (min-width: 768px) {
    .quick-add-footer .quick-add-submit {
        flex: 0 0 auto;
    }
}

/* flex-start, а не center: на картці зі знижкою група ціни має два рядки, і центрування
   піднімало саму ціну вище, ніж у сусідній картці того ж ряду — по горизонталі ціни не
   читались одним рухом ока. */
.product-price-row {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--sp-3);
    margin-top: var(--sp-1);
}

/* Розміри притиснуті до низу картки. Картки ряду однакові по висоті (grid stretch), тож
   рядок чипів опиняється на одному рівні в обох колонках незалежно від того, чи має
   сусід зайвий рядок старої ціни. */
.product-sizes {
    margin-top: auto;
}

.product-price {
    font-size: var(--fs-md);
    font-weight: 700;
    letter-spacing: -.02em;
}

.product-price-sale {
    color: var(--shop-accent);
}

.product-price-old {
    color: var(--shop-ink-faint);
    font-size: var(--fs-xs);
    white-space: nowrap;
    text-decoration: line-through;
}

.product-quick-add-button {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: var(--control-h-md);
    height: var(--control-h-md);
    padding: 0;
    color: var(--shop-primary);
    background: var(--shop-card);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-pill);
    line-height: 1;
    transition: color .18s var(--ease-out), background-color .18s var(--ease-out),
    border-color .18s var(--ease-out), transform .18s var(--ease-out);
}

.product-quick-add-button .icon {
    font-size: 1rem;
}

.product-quick-add-button:hover,
.product-quick-add-button:focus {
    color: #fff;
    background: var(--shop-primary);
    border-color: var(--shop-primary);
    transform: translateY(-1px);
}

/* ---------- Мітки на картці ---------------------------------------------- */

/* Позиціювання винесене в батьківські селектори нижче, а не в сам клас: у картці й на
   PDP бейдж лежить абсолютно поверх фото, але в модалці швидкого перегляду той самий
   клас має стояти в потоці поруч із ціною (issue #75). */
.product-sale-badge {
    z-index: 1;
    padding: .3rem .65rem;
    background: var(--shop-accent);
    border-radius: var(--r-pill);
    color: #fff;
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

.product-media .product-sale-badge,
.pdp-stage .product-sale-badge {
    position: absolute;
    top: var(--sp-3);
    left: var(--sp-3);
}

/* Наявність — службова інформація, а не акцент: тихий мікротекст без плашки */
/* Той самий бейдж, що й знижка, але приглушений: недоступність — це стан товару, а не
   привід святкувати, тому нейтральна темна поверхня замість акцентної. Стоїть у тому ж
   кутку й у тій самій формі — два різні бейджі на фото читались би як два механізми. */
.product-stock-badge {
    position: absolute;
    top: var(--sp-3);
    left: var(--sp-3);
    z-index: 1;
    white-space: nowrap;
    padding: .3rem .65rem;
    background: var(--shop-ink);
    border-radius: var(--r-pill);
    color: #fff;
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

/* Рамка з радіусом робила з переліку розмірів копію кнопок вибору в модалці, хоча тут
   це довідковий напис і клік нічого не робить. Розміри лишаються списком тексту через
   крапку — вибір розміру живе в модалці й на сторінці товару. */
.product-size-chip {
    color: var(--shop-ink-soft);
    font-size: var(--fs-xs);
    font-weight: 600;
}

.product-size-chip + .product-size-chip::before {
    content: "·";
    margin-right: var(--sp-2);
}

/* +N — не реальний розмір, а лічильник прихованих; трохи тьмяніший за самі чипи. */
.product-size-more {
    font-weight: 500;
    color: var(--shop-ink-soft);
}

.product-swatch,
.color-dot {
    display: inline-block;
    width: 1.1rem;
    height: 1.1rem;
    border: 1px solid rgba(35, 31, 27, .14);
    border-radius: 50%;
}

.product-swatch {
    width: var(--product-swatch-size);
    height: var(--product-swatch-size);
    padding: 0;
    appearance: none;
    cursor: pointer;
    transition: transform .15s var(--ease-out);
}

.product-swatch:hover {
    transform: scale(1.12);
}

.product-swatch:focus {
    outline: none;
}

.product-swatch.active,
.product-swatch.active:focus,
.product-swatch.active:focus-visible {
    outline: 1.5px solid var(--shop-primary);
    outline-offset: 2px;
}

/* Видимий кружок лишається кружком — до 44px ціль доводить невидимий ::after поза
   потоком. WCAG 2.5.8 діє незалежно від viewport, тож зона працює завжди, а не лише
   на мобільному. +2px (border-box swatch, .1rem border з кожного боку) компенсує те,
   що absolute ::after позиціюється від padding-box контейнера, а не border-box — без
   цього зона виходила на ~2px меншою за задану. Крок ряду (24px кружок + var(--sp-5)
   зазор = 48px) навмисно більший за --control-h-md, щоб зони сусідніх свотчів не
   перекривались. Сторінка товару й модалка цієї добавки не потребують — їхній спільний
   .pdp-swatch нижче вже 36px. */
.product-swatch {
    position: relative;
}

.product-swatch::after {
    content: "";
    position: absolute;
    inset: calc(-1 * (var(--control-h-md) + 2px - var(--product-swatch-size)) / 2);
}

/* ---------- Порожній стан ------------------------------------------------- */

.shop-empty {
    padding: var(--sp-7) var(--sp-5);
    background: var(--shop-surface);
    border-radius: var(--r-lg);
    text-align: center;
}

.shop-empty-mascot {
    width: 150px;
    height: auto;
    margin-bottom: var(--sp-3);
}

/* Той самий кегль і та сама насиченість, що й .cart-empty-title (issue #79): порожній
   каталог і порожній кошик — один візуальний примітив, а не два різні екрани. Селектор
   із префіксом .shop-page — інакше програє базовому ".shop-page h3" (font-weight: 600). */
.shop-page .shop-empty-title {
    margin-bottom: var(--sp-2);
    font-size: var(--fs-md);
    font-weight: 700;
}

.shop-empty-text {
    margin: 0;
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

/* Головна дія порожнього стану єдина й безальтернативна, тому вона — найпомітніший
   елемент блока, пігулка як .cart-primary-action, а не підкреслений текст (issue #79). */
.shop-page .shop-empty-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 3rem;
    margin-top: var(--sp-2);
    padding: 0 var(--sp-5);
    background: var(--shop-primary);
    border: 1px solid var(--shop-primary);
    border-radius: var(--r-pill);
    color: var(--shop-card);
    font-size: var(--fs-sm);
    font-weight: 600;
}

.shop-page .shop-empty-action:hover {
    background: var(--shop-primary-deep);
    color: var(--shop-card);
}

/* ---------- Довантаження порції ------------------------------------------- */

/* Блок живе всередині сітки, тому розтягуємо його на всі колонки: окремою коміркою він
   забирав би місце картки, а саме неповний ряд ця задача й прибирає. */
.product-more {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-3);
    margin-top: var(--sp-4);
}

.shop-page .product-more-link {
    text-decoration: none;
}

/* Стан завантаження підміняє посилання на місці, тому висота блоку не стрибає. */
.shop-page .product-more-loading {
    display: none;
    color: var(--shop-ink-soft);
}

.shop-page .product-more.htmx-request .product-more-link {
    display: none;
}

.shop-page .product-more.htmx-request .product-more-loading {
    display: inline-flex;
}

.product-more-status {
    margin: 0;
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

/* ---------- Фільтри (offcanvas) ------------------------------------------ */

.shop-page .offcanvas,
.shop-page .modal-content {
    background: var(--shop-page);
    border: 0;
}

.shop-page .offcanvas-header,
.shop-page .modal-header {
    padding: var(--sp-5);
    border-bottom: 1px solid var(--shop-line-soft);
}

.shop-page .offcanvas-title,
.shop-page .modal-title {
    font-size: var(--fs-md);
    font-weight: 700;
}

.filter-accordion .accordion-item {
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--shop-line-soft);
}

/* --fs-base, а не --fs-sm: заголовок групи був рівно того ж кегля, що й її власні
   пункти, і тримався на самих капсі й жирності — для секції з 13 рядків цього замало. */
.filter-accordion .accordion-button {
    padding-inline: 0;
    background: transparent;
    box-shadow: none;
    color: var(--shop-ink);
    font-size: var(--fs-base);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

.filter-accordion .accordion-button:not(.collapsed) {
    color: var(--shop-primary);
}

.filter-accordion .accordion-button:focus {
    box-shadow: none;
}

.filter-accordion .accordion-body {
    padding-inline: 0;
}

/* Той самий мінімум зони натискання, що й у сусідньому офканвасі каталогу (issue #68,
   .shop-catalog-offcanvas-list .dropdown-item нижче) — до фільтрів фікс не дійшов. */
.filter-accordion .form-check {
    min-height: 2.75rem;
}

.filter-accordion .form-check-label {
    width: 100%;
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

.filter-accordion .form-check-input:checked ~ .form-check-label {
    color: var(--shop-ink);
    font-weight: 600;
}

/* Липка смуга внизу офканваса фільтрів (issue #68): офканвас Bootstrap — вже flex-column
   (шапка/тіло/ця смуга як три елементи), тому третій елемент без flex-grow сам лишається
   притиснутим до низу, а прокручується лише .offcanvas-body над ним. Лише мобільний —
   на ≥768px десктопна поведінка офканваса лишається незмінною (акцептанс-критерій #5). */
.shop-page .offcanvas-footer {
    flex-shrink: 0;
    padding: var(--sp-4) var(--sp-5);
    background: var(--shop-page);
    border-top: 1px solid var(--shop-line-soft);
}

/* Смуга тримає дві дії в рядку: головна росте на весь вільний простір, «Очистити»
   лишається текстовою дією поруч, а не другою пігулкою рівної ваги. */
.shop-filter-footer {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
}

.shop-filter-footer .shop-filter-clear {
    flex: 0 0 auto;
    min-height: var(--control-h-md);
    padding-inline: var(--sp-3);
}

.shop-filter-apply-button {
    flex: 1 1 auto;
}

/* ---------- Офканвас каталогу (мобільний, issue #68) ---------------------- */

/* Рядок дропдауна на десктопі лишається 28–30px (не порушення, лишаємо як є), але на
   мобільному офканвасі та сама розмітка отримує зону натискання ≥44px — мінімум
   орієнтир проєкту (--shop-icon-button, --product-quick-add-button теж 2.75rem). */
.shop-catalog-offcanvas-list .dropdown-item {
    display: flex;
    align-items: center;
    min-height: 2.75rem;
    padding-block: var(--sp-3);
}

/* ---------- Підвал -------------------------------------------------------- */

.shop-footer {
    margin-top: var(--sp-8);
    padding-block: var(--sp-7);
    background: var(--shop-surface);
}

.footer-logo {
    width: 172px;
    max-height: 60px;
    object-fit: contain;
}

.shop-footer-tagline {
    margin: var(--sp-3) 0 0;
    max-width: 30ch;
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

.shop-footer-contacts {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    margin: var(--sp-4) 0 0;
    padding: 0;
    list-style: none;
}

.shop-footer-contact-link {
    display: inline-flex;
    align-items: center;
    /* Самостійний пункт навігації підвалу, а не інлайнове посилання в тексті — потребує
       зони натискання ≥44px (issue #80), як і .shop-footer-links a нижче. */
    min-height: var(--control-h-md);
    gap: var(--sp-2);
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
    font-weight: 600;
    text-decoration: none;
}

.shop-footer-contact-link:hover {
    color: var(--shop-primary);
}

.shop-footer-heading {
    margin: 0 0 var(--sp-3);
    color: var(--shop-ink);
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

.shop-footer-links {
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
    margin: 0;
    padding: 0;
    list-style: none;
}

.shop-footer-links a,
.shop-footer-cookie {
    display: inline-flex;
    align-items: center;
    /* Було 18px на всіх ширинах (issue #80) — сама висота рядка тексту, без запасу
       на зону натискання. */
    min-height: var(--control-h-md);
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
    font-weight: 600;
    text-decoration: none;
}

.shop-footer-cookie {
    padding: 0;
    border: 0;
    background: transparent;
}

.shop-footer-links a:hover,
.shop-footer-cookie:hover {
    color: var(--shop-primary);
}

/* ---------- Згода на аналітику ------------------------------------------ */

.analytics-consent {
    position: fixed;
    z-index: 1090;
    right: var(--sp-3);
    bottom: var(--sp-3);
    left: var(--sp-3);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-4);
    max-width: 68rem;
    margin-inline: auto;
    padding: var(--sp-3) var(--sp-4);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-lg);
    background: var(--shop-card);
    box-shadow: var(--shadow-md);
}

.analytics-consent[hidden] {
    display: none;
}

.analytics-consent-copy p {
    margin: 0;
}

.analytics-consent-copy p {
    max-width: 68ch;
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

.analytics-consent-copy a {
    color: var(--shop-primary);
    font-weight: 700;
}

.analytics-consent-actions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    flex: 0 0 auto;
    min-width: 20rem;
    gap: var(--sp-2);
}

.analytics-consent-primary,
.analytics-consent-secondary {
    min-height: var(--control-h-md);
    padding-inline: var(--control-pad-x);
    border: 1px solid var(--shop-line-strong);
    border-radius: var(--r-pill);
    font: inherit;
    font-size: var(--fs-sm);
    font-weight: 700;
}

.analytics-consent-primary {
    border-color: var(--shop-primary);
    background: var(--shop-primary);
    color: var(--shop-card);
}

.analytics-consent-secondary {
    background: var(--shop-card);
    color: var(--shop-primary);
}

@media (max-width: 767.98px) {
    .analytics-consent {
        align-items: stretch;
        flex-direction: column;
        gap: var(--sp-3);
        padding: var(--sp-3);
    }

    .analytics-consent-actions {
        min-width: 0;
    }
}

.shop-footer-legal {
    color: var(--shop-ink-faint);
    font-size: var(--fs-xs);
    letter-spacing: .01em;
}

/* ---------- Інформаційні сторінки (доставка, повернення, правові) --------- */

/* Спільна вузька колонка тексту для /delivery, /returns, /privacy, /terms:
   довгий суцільний текст на всю ширину контейнера читати важче за колонку ~68ch. */

.info-page {
    max-width: 68ch;
    margin-inline: auto;
    padding-block: var(--sp-7);
}

.info-page-eyebrow {
    margin: 0 0 var(--sp-2);
    color: var(--shop-ink-faint);
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

.info-page-title {
    margin: 0 0 var(--sp-5);
}

.info-page section + section {
    margin-top: var(--sp-6);
}

.info-page h2 {
    margin: 0 0 var(--sp-3);
    font-size: var(--fs-lg);
}

.info-page p,
.info-page li {
    color: var(--shop-ink-soft);
}

.info-page p + p {
    margin-top: var(--sp-3);
}

.info-page ul {
    margin: var(--sp-2) 0 0;
    padding-inline-start: 1.2em;
}

.info-page li + li {
    margin-top: var(--sp-2);
}

.info-page a {
    color: var(--shop-primary);
}

/* ---------- Сторінка товару ----------------------------------------------- */

/* Компактна компоновка: фото не ширше 440px, мініатюри під ним, права колонка
   ширша за ліву. Так ціна й вибір розміру лишаються на першому екрані навіть
   тоді, коли в товару одне фото — а це більшість каталогу. */

.pdp-crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2);
    padding-block: var(--sp-5) var(--sp-4);
    color: var(--shop-ink-faint);
    font-size: var(--fs-sm);
}

.pdp-crumbs a {
    color: var(--shop-ink-soft);
    text-decoration: none;
}

.pdp-crumbs a:hover {
    color: var(--shop-primary);
}

.pdp-crumbs [aria-current] {
    color: var(--shop-ink);
    font-weight: 600;
}

.pdp-crumbs-sep {
    color: var(--shop-line);
}

.pdp {
    display: grid;
    grid-template-columns: minmax(0, .85fr) minmax(0, 1fr);
    gap: var(--sp-8);
    align-items: start;
}

/* ---------- Галерея ------------------------------------------------------- */

.pdp-gallery {
    display: grid;
    gap: var(--sp-3);
    max-width: 440px;
}

.pdp-stage {
    position: relative;
    overflow: hidden;
    aspect-ratio: 4 / 5;
    background: var(--shop-media);
    border-radius: var(--r-lg);
}

.pdp-stage-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.pdp-badge {
    position: absolute;
    top: var(--sp-4);
    left: var(--sp-4);
    z-index: 1;
    padding: .3rem .65rem;
    background: var(--shop-accent);
    border-radius: var(--r-pill);
    color: #fff;
    font-size: var(--fs-eyebrow);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

.pdp-thumbs {
    display: flex;
    gap: var(--sp-3);
    overflow-x: auto;
    scrollbar-width: none;
}

.pdp-thumbs::-webkit-scrollbar {
    display: none;
}

.pdp-thumb {
    flex: 0 0 auto;
    width: 4rem;
    aspect-ratio: 4 / 5;
    padding: 0;
    overflow: hidden;
    background: var(--shop-media);
    border: 2px solid var(--shop-line-soft);
    border-radius: var(--r-md);
    transition: border-color .18s var(--ease-out);
}

.pdp-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.pdp-thumb:hover {
    border-color: var(--shop-primary);
}

.pdp-thumb.active {
    border-color: var(--shop-primary);
}

/* ---------- Права колонка ------------------------------------------------- */

.pdp-panel {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
}

.pdp-eyebrow {
    margin: 0;
    color: var(--shop-ink-faint);
    font-size: var(--fs-eyebrow);
    font-weight: 700;
    letter-spacing: var(--tracking-wide);
    text-transform: uppercase;
}

.pdp-eyebrow a {
    display: inline-flex;
    align-items: center;
    /* Посилання на категорію над назвою товару — не хлібна крихта і не інлайнове
       посилання в суцільному тексті абзацу, тож потребує зони натискання 44px
       (issue #80). min-width поруч із min-height: для коротких назв категорій
       (наприклад «Худі» на /product/40) ширина боксу інакше дорівнювала б
       природній ширині тексту, лишаючись <44px по горизонталі. Саме min-width,
       а не горизонтальний padding, — box лишається невидимим і не зсуває
       текст відносно .pdp-title під ним (issue #81). */
    min-height: var(--control-h-md);
    min-width: var(--control-h-md);
    color: inherit;
    text-decoration: none;
}

.pdp-eyebrow a:hover {
    color: var(--shop-primary);
}

.pdp-title {
    margin: var(--sp-2) 0 0;
    font-size: var(--fs-xl);
    font-weight: 700;
}

.pdp-price-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--sp-3);
}

.pdp-price {
    color: var(--shop-primary-deep);
    font-size: 1.75rem;
    font-weight: 700;
    letter-spacing: -.02em;
}

.pdp-price-old {
    color: var(--shop-ink-faint);
    font-size: var(--fs-md);
    text-decoration: line-through;
}

/* Залишок — аргумент, а не службовий напис, тому на сторінці товару це плашка, а не
   бейдж-виняток, як .product-stock-badge на картці каталогу. */
.pdp-stock {
    padding: .3rem .75rem;
    background: var(--shop-secondary-soft);
    border-radius: var(--r-pill);
    color: var(--shop-primary-deep);
    font-size: var(--fs-xs);
    font-weight: 600;
}

.pdp-stock-out {
    background: var(--shop-surface);
    color: var(--shop-ink-soft);
}

.pdp-block {
    display: flex;
    flex-direction: column;
    gap: var(--sp-3);
}

.pdp-block-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
}

.pdp-label {
    font-size: var(--fs-sm);
    font-weight: 700;
}

.pdp-label span {
    color: var(--shop-ink-soft);
    font-weight: 500;
}

.pdp-sizes {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
}

/* Один компонент розміру на дві поверхні — сторінка товару (кнопка) і модалка швидкого
   додавання (.btn-check + label): вигляд не має розходитись при правці однієї з них
   (issue #77, той самий принцип, що й у степера кількості нижче, shop.css:1880). PDP керує
   станом через клас (:disabled/.active навісний JS-ом), модалка — нативним radio, тому
   .btn-check-стани додано поруч, а не замість. */
.pdp-size {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 3.25rem;
    min-height: var(--control-h-md);
    padding: 0 var(--control-pad-x);
    background: var(--shop-card);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-pill);
    color: var(--shop-ink);
    font-size: var(--fs-sm);
    font-weight: 600;
    line-height: 1;
    /* Пропорційні цифри Manrope дають розкид ширини пігулок до 2.6px («110» проти «140»).
       На PDP це один ряд і майже не видно, у фільтрі — три ряди, і колонки помітно
       розсипаються. Табличні цифри вирівнюють усі розміри в однакову ширину. */
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: border-color .18s var(--ease-out), background-color .18s var(--ease-out), color .18s var(--ease-out);
}

.pdp-size:hover:not(:disabled) {
    border-color: var(--shop-primary);
    color: var(--shop-primary);
}

.pdp-size.active:not(:disabled),
.btn-check:checked + .pdp-size {
    background: var(--shop-primary);
    border-color: var(--shop-primary);
    /* Hover може лишатися активним після тапу, а його selector специфічніший. */
    color: var(--shop-card);
}

/* .btn-check у модалці ховає input Bootstrap-способом (position: absolute; clip), тож
   :focus-visible на самому input невидимий — ринг малюємо на сусідньому лейблі. На PDP
   кнопка нативна, тому .shop-page :focus-visible (shop.css:134) уже дає ринг сама. */
.btn-check:focus-visible + .pdp-size {
    outline: var(--focus-ring);
    outline-offset: var(--focus-ring-offset);
}

/* .shop-page :focus-visible (shop.css:134) специфічніша за .pdp-size (0,0,2,0 проти 0,0,1,0)
   і перебиває пігулку на var(--r-sm) при фокусі кнопки на PDP. Правило нижче має ту саму
   специфічність, тож виграє за порядком у файлі — той самий прийом, що й для .pdp-swatch
   нижче (issue #77, рев'ю MR !74). */
.pdp-size:focus-visible {
    border-radius: var(--r-pill);
}

/* Недоступний розмір лишається видимим і перекресленим: так видно, що річ буває більшою. */
.pdp-size:disabled,
.btn-check:disabled + .pdp-size {
    color: var(--shop-ink-faint);
    cursor: not-allowed;
    background: linear-gradient(to top left,
    transparent calc(50% - .5px), var(--shop-line) 50%, transparent calc(50% + .5px));
}

/* pointer-events: none лише для модалки: :disabled стоїть на сусідньому input, а не на
   самому .pdp-size, тож без цього лейбл і далі приймав би hover/click. На PDP кнопка сама
   disabled — браузер і так блокує клік, а зайвий pointer-events:none тут погасив би hover
   (cursor: not-allowed) і нативний tooltip. */
.btn-check:disabled + .pdp-size {
    pointer-events: none;
}

.pdp-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
}

/* Той самий принцип для свотча кольору: сторінка товару (кнопка) і модалка (.btn-check +
   label) ділять один візуальний компонент. */
.pdp-swatch {
    display: inline-block;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    appearance: none;
    cursor: pointer;
    border: 0;
    border-radius: 50%;
    box-shadow: 0 0 0 1px var(--shop-line);
    transition: box-shadow .18s var(--ease-out);
}

.pdp-swatch.active,
.btn-check:checked + .pdp-swatch {
    box-shadow: 0 0 0 2px var(--shop-primary), inset 0 0 0 2px var(--shop-card);
}

/* Той самий принцип фокуса, що й у .pdp-size вище: у модалці ринг малюємо на лейблі,
   бо сам .btn-check-input візуально прихований. border-radius тут потрібен з тієї самої
   причини, що й у .pdp-size:focus-visible нижче — інакше .shop-page :focus-visible
   (shop.css:134) перебила б коло на var(--r-sm). */
.btn-check:focus-visible + .pdp-swatch {
    outline: var(--focus-ring);
    outline-offset: var(--focus-ring-offset);
    border-radius: 50%;
}

/* PDP-варіант того самого фокуса: кнопка нативна, тож .shop-page :focus-visible
   (shop.css:134, специфічність 0,0,2,0) перебиває коло .pdp-swatch (0,0,1,0) на
   var(--r-sm). Однакова специфічність тут — виграш за порядком у файлі (issue #77,
   рев'ю MR !74). */
.pdp-swatch:focus-visible {
    border-radius: 50%;
}

/* pointer-events: none лише для модалки — на PDP свотч сам disabled, клік і так блокований,
   а зайвий pointer-events:none погасив би hover-tooltip (title). */
.pdp-swatch:disabled,
.btn-check:disabled + .pdp-swatch {
    cursor: not-allowed;
    opacity: .4;
}

.btn-check:disabled + .pdp-swatch {
    pointer-events: none;
}

.pdp-buy {
    display: flex;
    align-items: stretch;
    gap: var(--sp-3);
}

/* Один контрол кількості на дві сусідні поверхні: сторінка товару і модалка швидкого
   додавання. Селектори згруповані, щоб вигляд не розходився при правці однієї з них. */
.pdp-qty,
.quick-add-qty {
    display: inline-flex;
    align-items: center;
    background: var(--shop-card);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-pill);
}

.pdp-qty-step,
.quick-add-qty-step {
    width: var(--control-h-md);
    /* Висота, а не var(--control-h-md): .pdp-qty стоїть поруч із .pdp-cta в .pdp-buy
       (align-items: stretch, issue #80) — крок степера синхронізований з висотою кнопки,
       а не з окремою квадратною ціллю. Ширина лишається на --control-h-md, тож зона
       натискання все одно ≥44px в обох вимірах. */
    height: var(--control-h-lg);
    background: transparent;
    border: 0;
    border-radius: var(--r-pill);
    color: var(--shop-primary);
}

.pdp-qty-step:hover,
.quick-add-qty-step:hover {
    background: var(--shop-surface);
}

.pdp-qty-value,
.quick-add-qty-value {
    min-width: 2rem;
    font-size: var(--fs-md);
    font-weight: 700;
    text-align: center;
}

.pdp-cta {
    flex: 1;
    min-height: var(--control-h-lg);
    padding: 0 var(--control-pad-x);
    background: var(--shop-primary);
    border: 1px solid var(--shop-primary);
    border-radius: var(--r-pill);
    color: #fff;
    font-size: var(--fs-sm);
    font-weight: 600;
    letter-spacing: .01em;
    transition: background-color .18s var(--ease-out), transform .18s var(--ease-out);
}

.pdp-cta:hover:not(:disabled) {
    background: var(--shop-primary-deep);
    transform: translateY(-1px);
}

.pdp-cta:disabled {
    background: var(--shop-line);
    border-color: var(--shop-line);
    color: var(--shop-ink-faint);
}

.pdp-unavailable {
    margin: calc(var(--sp-4) * -1) 0 0;
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

.pdp-assurance {
    display: grid;
    gap: var(--sp-3);
    margin: 0;
    padding: var(--sp-4) 0 0;
    border-top: 1px solid var(--shop-line-soft);
    list-style: none;
}

.pdp-assurance li {
    display: flex;
    align-items: flex-start;
    gap: var(--sp-3);
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

.pdp-assurance .icon {
    color: var(--shop-primary);
}

.pdp-assurance b {
    color: var(--shop-ink);
    font-weight: 600;
}

.pdp-assurance-link {
    color: var(--shop-primary);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: .2em;
}

.pdp-assurance-link:hover,
.pdp-assurance-link:focus-visible {
    color: var(--shop-primary-deep);
}

/* ---------- Опис ---------------------------------------------------------- */

.pdp-details {
    grid-column: 1 / -1;
    margin-top: var(--sp-7);
}

/*
 * Коли є ще й заміри, «Опис» і «Заміри» стають двома колонками на всю ширину секції — саме тому
 * тулилися в ліву половину раніше. Без замірів колонки нема кому займати другу половину, тож
 * опис лишається одноколонковим і центрується власною шириною рядка (нижче), а не розтягується.
 *
 * Заголовок і текст опису — один грід-елемент (.pdp-description-block), а не два в окремих
 * рядках: інакше висоту рядка заголовка задає висота сусідньої таблиці замірів, і між
 * заголовком та текстом з'являється порожнеча. align-items: start прибирає розтягування
 * коротшої колонки на всю висоту рядка.
 */
@media (min-width: 992px) {
    .pdp-details:has(.pdp-measurements) {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        column-gap: var(--sp-8);
        align-items: start;
    }

    .pdp-details:has(.pdp-measurements) > .pdp-description-block {
        grid-column: 1;
    }

    .pdp-details:has(.pdp-measurements) > .pdp-description-block > .pdp-details-title,
    .pdp-details:has(.pdp-measurements) > .pdp-description-block > .pdp-description,
    .pdp-details:has(.pdp-measurements) > .pdp-description-block .pdp-description-section-title,
    .pdp-details:has(.pdp-measurements) > .pdp-description-block .pdp-description-section > .pdp-description {
        margin-inline: 0;
    }

    .pdp-details:has(.pdp-measurements) > .pdp-measurements {
        grid-column: 2;
        margin-top: 0;
    }
}

/* Без пари колонка порожня не буває: ширина рядка обмежена свідомо, а не якимось лімітом верстки,
   тож блок центрується в секції замість того, щоб тулитися ліворуч із порожнім правим краєм. */
.pdp-details-title {
    max-width: 70ch;
    margin: 0 auto var(--sp-4);
    font-size: var(--fs-lg);
}

/*
 * Опис приходить з адмінки одним текстовим полем, тому pre-line зберігає його переноси.
 * Цей же клас показує заміри, які не склалися в таблицю (напр. один вимір без сітки розмірів).
 */
.pdp-description {
    max-width: 70ch;
    margin: 0 auto;
    padding: var(--sp-5);
    background: var(--shop-card);
    border: 1px solid var(--shop-line-soft);
    border-radius: var(--r-lg);
    color: var(--shop-ink-soft);
    line-height: 1.75;
    white-space: pre-line;
    overflow-wrap: anywhere;
    font-variant-numeric: tabular-nums;
}

/*
 * Підписаний розділ опису («Матеріал», «Особливості», «Догляд») — той самий блок .pdp-description,
 * лише з власним заголовком і відступом зверху, щоб розділи не зливались один в одного.
 */
.pdp-description-section {
    margin-top: var(--sp-4);
}

.pdp-description-section-title {
    max-width: 70ch;
    margin: 0 auto var(--sp-2);
    font-size: var(--fs-md);
}

/* ---------- Заміри -------------------------------------------------------- */

.pdp-measurements {
    margin-top: var(--sp-5);
    /* Перехід за посиланням «Дивіться заміри нижче» не має ховатись під sticky-шапкою. */
    scroll-margin-top: var(--shop-header-h);
}

.pdp-measurements-title {
    margin-bottom: var(--sp-3);
    font-size: var(--fs-md);
}

/*
 * Ширина по вмісту, але не ширше секції: таблиця на три колонки не розтягується на всю
 * сторінку порожніми клітинками, а комплект на сім колонок скролиться сам, не тягнучи
 * за собою горизонтальний скрол сторінки. 70ch опису тут не діє — рядок таблиці короткий.
 */
.pdp-measurements-scroll {
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    background: var(--shop-card);
    border: 1px solid var(--shop-line-soft);
    border-radius: var(--r-lg);
}

.pdp-measurements-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
}

.pdp-measurements-table th,
.pdp-measurements-table td {
    padding: var(--sp-3) var(--sp-4);
    text-align: left;
    white-space: nowrap;
    border-bottom: 1px solid var(--shop-line-soft);
}

.pdp-measurements-table thead th {
    color: var(--shop-ink);
    font-weight: 600;
    background: var(--shop-surface);
    border-bottom-color: var(--shop-line);
}

.pdp-measurements-table tbody th {
    color: var(--shop-ink);
    font-weight: 600;
}

.pdp-measurements-table tbody td {
    color: var(--shop-ink-soft);
}

.pdp-measurements-table tbody tr:last-child th,
.pdp-measurements-table tbody tr:last-child td {
    border-bottom: 0;
}

/* Рядок обраного розміру підсвічує product.js — заради цього таблиця і стоїть на сторінці. */
.pdp-measurements-row.active th,
.pdp-measurements-row.active td {
    background: var(--shop-secondary-soft);
    color: var(--shop-ink);
}

.pdp-measurements-note {
    margin: var(--sp-2) 0 0;
    color: var(--shop-ink-faint);
    font-size: var(--fs-xs);
}

/* ---------- Схожі товари -------------------------------------------------- */

.pdp-related {
    padding-block: var(--sp-7);
}

.pdp-related .shop-section-title {
    margin-bottom: var(--sp-4);
}

.pdp-related-empty-text {
    max-width: 60ch;
    margin: 0 0 var(--sp-4);
    color: var(--shop-ink-soft);
}

.pdp-related-empty-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-3);
}

/* ---------- Липка панель покупки ------------------------------------------ */

/*
 * Панель стоїть у кінці <main>: поки сторінку гортають, вона прилипла до низу екрана,
 * а в самому низу «сідає» на своє місце в потоці, перед футером. Щоб приземлення не
 * читалось як зламана смуга на всю ширину, панель і в прилиплому стані намальована
 * островом — з полями, скругленням і тінню. Між станами змінюється тільки тінь,
 * геометрія однакова, тому момент переходу не смикає верстку.
 */
.pdp-sticky-buy {
    display: none;
    position: sticky;
    bottom: 0;
    z-index: 10;
    /* По боках поля дає .container всередині — острів стоїть рівно по краях контенту
       на будь-якій ширині. Знизу — запас під домашню смужку iPhone, зверху — під затінення. */
    padding: 0;
    /* Контент, що проїжджає під островом, гасне в колір сторінки; непрозорість набирається
       рівно до верхнього краю острова. Коли панель приземлилась, градієнт зливається
       з фоном сторінки і його не видно. */

    /* Смуга ширша за острів, тож кліки крізь прозоре затінення має ловити контент під ним. */
    pointer-events: none;
    /* Chromium вміє спитати, чи панель зараз прилипла — див. @container нижче. */
    container-type: scroll-state;

    .container {
        padding: 0;
    }
}

.pdp-sticky-bar {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    padding: var(--sp-2) var(--sp-2) var(--sp-2) var(--sp-4);
    background: var(--shop-card);
    border: 1px solid var(--shop-line-soft);
    border-radius: 0;
    box-shadow: 0 16px 30px -18px rgba(35, 31, 27, .5), var(--shadow-sm);
    pointer-events: auto;
    transition: box-shadow .32s var(--ease-out), transform .32s var(--ease-out);
}

.pdp-sticky-price {
    font-size: var(--fs-lg);
}

/*
 * Панель доїхала до свого місця в потоці — тінь м'якшає і острів «сідає» на сторінку.
 * Там, де scroll-state ще не підтримується, лишається базовий вигляд «висить»: він теж
 * коректний сам по собі, бо геометрія в обох станах однакова.
 */
@container scroll-state(stuck: none) {
    .pdp-sticky-buy {
        .container {
            padding: 0 12px;
        }
    }
    .pdp-sticky-bar {
        transform: translateY(2px);
        box-shadow: var(--shadow-sm);
        border-radius: var(--r-pill);
    }
}

/*
 * Адаптив сторінки товару тримаємо тут, а не в спільній секції нижче:
 * це один брейкпоінт, який стосується лише цих класів, і читати його поруч
 * з компонентами простіше, ніж шукати через пів файла.
 */
@media (max-width: 991.98px) {
    .pdp {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--sp-5);
    }

    .pdp-gallery {
        max-width: none;
    }

    .pdp-details {
        margin-top: var(--sp-6);
    }

    /* На мобільному кнопка з правої колонки їде за межі екрана — її замінює липка панель */
    .pdp-buy .pdp-cta {
        display: none;
    }

    .pdp-sticky-buy {
        display: block;
    }
}

/* ---------- Кошик --------------------------------------------------------- */

.shop-cart-button {
    position: relative;
    text-decoration: none;
}

/* Лічильник сидить на іконці сумки, тому кнопка шапки лишається того самого розміру. */
.shop-cart-count {
    position: absolute;
    top: .35rem;
    right: .3rem;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.15rem;
    height: 1.15rem;
    padding: 0 .3rem;
    background: var(--shop-primary);
    border-radius: var(--r-pill);
    color: var(--shop-card);
    font-size: var(--fs-eyebrow);
    font-weight: 700;
    line-height: 1;
}

/*
 * Підтвердження показуємо зверху по центру: знизу на мобільному стоїть липка панель покупки,
 * і повідомлення накрило б саме ту кнопку, яку користувач щойно натиснув.
 * z-index вище за модалку Bootstrap (1055) — «Швидкий перегляд» додає товар з-під неї.
 */
.cart-toast-region {
    position: fixed;
    top: calc(var(--shop-header-h) + var(--sp-3));
    left: 50%;
    z-index: 1080;
    display: flex;
    justify-content: center;
    width: min(30rem, calc(100vw - var(--sp-5)));
    transform: translateX(-50%);
    pointer-events: none;
}

.cart-toast {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--sp-2) var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    background: var(--shop-card);
    border: 1px solid var(--shop-line);
    border-left: 3px solid var(--shop-primary);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-md);
    color: var(--shop-ink);
    font-weight: 600;
    pointer-events: auto;
}

.cart-toast .icon {
    color: var(--shop-primary);
}

.cart-toast-error {
    border-left-color: var(--shop-danger);
}

.cart-toast-error .icon {
    color: var(--shop-danger);
}

.cart-toast-link {
    margin-left: auto;
    color: var(--shop-primary);
    font-size: var(--fs-sm);
    text-decoration: underline;
}

.cart-page {
    padding-bottom: var(--sp-7);
}

.cart-title {
    margin-bottom: var(--sp-5);
    font-size: var(--fs-xl);
}

.cart-alert {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin-bottom: var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    background: var(--shop-surface);
    border-left: 3px solid var(--shop-danger);
    border-radius: var(--r-sm);
    color: var(--shop-ink);
    font-size: var(--fs-sm);
}

.cart-alert .icon {
    color: var(--shop-danger);
}

/* Той самий фон і та сама відсутність рамки, що й .shop-empty (issue #79): порожній
   стан — це пауза в потоці, а не картка з даними, яка не завантажилась. */
.cart-empty {
    display: grid;
    justify-items: center;
    gap: var(--sp-2);
    padding: var(--sp-7) var(--sp-5);
    background: var(--shop-surface);
    border-radius: var(--r-lg);
    text-align: center;
}

.cart-empty .icon {
    color: var(--shop-ink-faint);
    font-size: 2rem;
}

.cart-empty-title {
    margin: 0;
    font-size: var(--fs-md);
    font-weight: 700;
}

.cart-empty-note {
    max-width: 26rem;
    margin: 0 0 var(--sp-3);
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

.cart-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 20rem);
    gap: var(--sp-5);
    align-items: start;
}

.cart-lines {
    display: grid;
    gap: var(--sp-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.cart-line {
    display: grid;
    grid-template-columns: 5rem minmax(0, 1fr) auto;
    gap: var(--sp-4);
    align-items: center;
    padding: var(--sp-3);
    background: var(--shop-card);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-md);
}

.cart-line-media {
    display: block;
    overflow: hidden;
    background: var(--shop-media);
    border-radius: var(--r-sm);
}

.cart-line-media img {
    display: block;
    width: 100%;
    height: auto;
    object-fit: cover;
}

.cart-line-body {
    display: grid;
    gap: var(--sp-1);
}

.cart-line-title {
    color: var(--shop-ink);
    font-weight: 600;
    text-decoration: none;
}

.cart-line-title:hover {
    color: var(--shop-primary);
}

.cart-line-options {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-3);
    margin: 0;
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

.cart-line-unit {
    margin: 0;
    color: var(--shop-ink-faint);
    font-size: var(--fs-xs);
}

/* Пояснення вимкненого «+». Окремий рядок у сітці .cart-line-body, тож ряд дій лишається як був. */
.cart-line-limit {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    margin: 0;
    color: var(--shop-ink-soft);
    font-size: var(--fs-xs);
}

.cart-line-actions {
    display: flex;
    align-items: center;
    gap: var(--sp-4);
}

.cart-qty {
    display: inline-flex;
    align-items: center;
    background: var(--shop-page);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-pill);
}

/* 2.75rem = 44px — той самий мінімум зони натискання, що й .pdp-qty-step на сторінці
   товару (issue #71): 2.25rem (36px) проходив поріг WCAG 2.5.8, але помітно поступався
   і орієнтиру 44px, і власному стандарту проєкту. */
.cart-qty-step {
    width: var(--control-h-md);
    height: var(--control-h-md);
    background: transparent;
    border: 0;
    border-radius: var(--r-pill);
    color: var(--shop-primary);
}

.cart-qty-step:hover:not(:disabled) {
    background: var(--shop-surface);
}

.cart-qty-step:disabled {
    color: var(--shop-ink-faint);
}

.cart-qty-value {
    min-width: 1.75rem;
    font-weight: 700;
    text-align: center;
}

.cart-line-price {
    min-width: 5.5rem;
    font-weight: 700;
    text-align: right;
}

/* Той самий мінімум 44px, що й .cart-qty-step вище: видалення позиції спрацьовує
   одразу, без підтвердження, тож саме цій кнопці мала зона шкодить найбільше. */
.cart-remove {
    width: var(--control-h-md);
    height: var(--control-h-md);
    background: transparent;
    border: 0;
    border-radius: var(--r-pill);
    color: var(--shop-ink-faint);
}

.cart-remove:hover {
    background: var(--shop-surface);
    color: var(--shop-danger);
}

.cart-summary {
    display: grid;
    gap: var(--sp-3);
    padding: var(--sp-5);
    background: var(--shop-card);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-lg);
}

.cart-summary-title {
    margin: 0;
    font-size: var(--fs-md);
}

.cart-summary-row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--sp-3);
    margin: 0;
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

.cart-summary-total {
    color: var(--shop-ink);
    font-size: var(--fs-lg);
    font-weight: 700;
}

.cart-summary-note {
    margin: 0;
    color: var(--shop-ink-faint);
    font-size: var(--fs-xs);
}

/* --fs-base, а не --fs-sm: це головна дія сторінки — «Додати в кошик», «Оформити замовлення»,
   «Перейти до оплати». На 13px пігулка оптично легша за назву товару поруч, хоча важить у
   рішенні більше за будь-який текст на екрані. */
.cart-primary-action,
.cart-secondary-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: var(--control-h-lg);
    padding: 0 var(--control-pad-x);
    border-radius: var(--r-pill);
    font-size: var(--fs-base);
    font-weight: 600;
    text-decoration: none;
}

.cart-primary-action {
    background: var(--shop-primary);
    border: 1px solid var(--shop-primary);
    color: var(--shop-card);
}

.cart-primary-action:hover {
    background: var(--shop-primary-deep);
    color: var(--shop-card);
}

.cart-primary-action:disabled {
    cursor: not-allowed;
    opacity: .6;
}

.cart-secondary-action {
    background: transparent;
    /* --shop-line дає 1.32:1 до --shop-card — нижче WCAG 1.4.11 (issue #80). */
    border: 1px solid var(--shop-line-strong);
    color: var(--shop-ink);
}

.cart-secondary-action:hover {
    background: var(--shop-surface);
    color: var(--shop-primary);
}

/* ---------- Оформлення замовлення ---------------------------------------- */

/*
 * Форма обмежена 34rem, зведення — 20rem, а вільну ширину з'їдав 1fr першої колонки: на 1440
 * між ними лишалося близько 500px порожнечі, через яку око стрибало від останнього поля до суми.
 *
 * Обмеження стоїть на контейнері, а не на .checkout-layout: інакше заголовок і хлібні крихти
 * лишились би на своїй вертикалі, а колонки поїхали б від них окремо. 57rem — це 34 + 20 колонок,
 * --sp-5 проміжку між ними й padding самого контейнера.
 */
.checkout-page {
    max-width: 57rem;
}

.checkout-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 20rem);
    gap: var(--sp-5);
    align-items: start;
}

/* Зведення стоїть у розмітці перед формою — суму треба прочитати до кнопки. На десктопі
   обидва блоки розміщені в гріді явно: інакше зведення зайняло б першу комірку, а форма
   з'їхала б у праву колонку. */
/*
 * Індикатор кроку. Онлайн-оплата робить оформлення двоекранним, і сама кнопка «Перейти до
 * оплати» не каже, скільки ще лишилось. Крок 2 живе на окремій сторінці, тож тут він завжди
 * майбутній — підсвічувати його нема коли.
 */
.checkout-steps {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-3);
    align-items: center;
    margin: 0 0 var(--sp-5);
    padding: 0;
    list-style: none;
    font-size: var(--fs-sm);
}

.checkout-step {
    display: flex;
    gap: var(--sp-2);
    align-items: center;
    color: var(--shop-ink-faint);
}

.checkout-step[aria-current] {
    color: var(--shop-ink);
    font-weight: 600;
}

/* Риску між кроками малює сам другий крок: окремий <li> лише під межу потрапив би в список
   для screen reader порожнім пунктом. */
.checkout-step + .checkout-step::before {
    content: "";
    width: var(--sp-4);
    height: 1px;
    background: var(--shop-line-strong);
}

/* Кружечок декоративний, не контрол, тож розмір іде зі шкали відступів, а не з --control-h-*:
   зона натискання тут ні до чого. */
.checkout-step-index {
    display: grid;
    place-items: center;
    width: var(--sp-5);
    height: var(--sp-5);
    border: 1px solid var(--shop-line-strong);
    border-radius: var(--r-pill);
    font-size: var(--fs-xs);
    font-weight: 600;
}

.checkout-step[aria-current] .checkout-step-index {
    color: var(--shop-card);
    background: var(--shop-primary);
    border-color: var(--shop-primary);
}

/* Заголовок стоїть щільніше до кроків, ніж до форми: --sp-5 між ними розірвав би пару
   «що я роблю» + «де я в процесі» на два незалежні блоки. */
.cart-title:has(+ .checkout-steps) {
    margin-bottom: var(--sp-3);
}

.checkout-form {
    display: grid;
    grid-column: 1;
    grid-row: 1;
    max-width: 34rem;
    gap: var(--sp-4);
}

.checkout-section {
    display: grid;
    gap: var(--sp-4);
    padding: var(--sp-5);
    background: var(--shop-card);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-lg);
}

.checkout-section-title {
    margin: 0;
    font-size: var(--fs-md);
}

/* Заголовок секції зліва, знак перевізника справа — рядок лишається один навіть на 320px,
   бо "Доставка" коротка, а логотип має фіксовану висоту й не тягнеться. */
.checkout-section-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-3);
}

/*
 * Задається висота, а не ширина: логотип мусить сидіти на одному оптичному рядку із заголовком,
 * а не підлаштовуватися під ширину картки. width/height в атрибутах — інтринсик 861x124, він
 * лише резервує пропорцію проти стрибка верстки; реальний розмір рахується звідси.
 */
.checkout-carrier-logo {
    flex-shrink: 0;
    width: auto;
    height: 1.375rem;
}

.checkout-field {
    display: grid;
    gap: var(--sp-1);
    /*
     * Фокус у полі доставки догортує його догори (скрипт у checkout.html), але шапка sticky —
     * і без цього запасу поле разом із власним лейблом лягало точно під неї. Той самий прийом,
     * що в .products-section і .pdp-measurements; --sp-3 зверху, щоб поле не притискалося до
     * межі навбара впритул.
     */
    scroll-margin-top: calc(var(--shop-header-h) + var(--sp-3));
}

/*
 * Ціль якоря зі зведення помилок: пункт веде на сам input, щоб браузер переставив туди ще й
 * фокус. Тому запас мусить покрити не лише sticky-шапку, а й лейбл, що стоїть НАД полем —
 * інакше користувач приїжджає до поля без підпису. --sp-7 узято під найгірший виміряний
 * випадок: на 320px "Відділення, поштомат або пункт видачі" переноситься у два рядки (46px).
 * Для однорядкових підписів це просто садить поле трохи нижче, що для помилки навіть краще —
 * зверху лишається контекст.
 */
.checkout-input {
    scroll-margin-top: calc(var(--shop-header-h) + var(--sp-3) + var(--sp-7));
}

/*
 * Без обмеження ці поля розтягувалися на всю колонку форми (~800px): ширина поля — підказка
 * про очікуваний обсяг вводу, а не просто залишок ряду. Пошук населеного пункту, відділення
 * й коментар лишаються на всю ширину — там очікується довший текст (issue #84: населений
 * пункт раніше теж потрапляв сюди, хоча сусіднє поле відділення лишалось нічим не обмеженим —
 * звідси помітна сходинка між двома полями в одній картці).
 */
.checkout-field-name,
.checkout-field-email,
.checkout-field-phone {
    max-width: 26rem;
}

/* Коротше за колонку тільки саме поле: номер має фіксовану довжину, і поле на всю ширину
   читалося б як запрошення написати щось довше. Обмеження на input, а не на колонку — інакше
   підказка під ним ламається надвоє, бо ділить із полем ту саму ширину. */
.checkout-field-phone .checkout-input {
    max-width: 18rem;
}

.checkout-label {
    color: var(--shop-ink);
    font-size: var(--fs-sm);
    font-weight: 600;
}

.checkout-required {
    color: var(--shop-danger);
}

.checkout-input {
    width: 100%;
    min-height: var(--control-h-lg);
    padding: var(--sp-2) var(--sp-3);
    background: var(--shop-page);
    /* --shop-line дає 1.32:1 до --shop-card (поле, як і картка, ще й на --shop-page —
       різниця фону 1.06:1, тож саме межа лишається єдиним видимим орієнтиром: issue #80).
       Успадковує й .checkout-textarea — окремого border там немає. */
    border: 1px solid var(--shop-line-strong);
    border-radius: var(--r-sm);
    color: var(--shop-ink);
    font-size: var(--fs-input);
}

.checkout-input:focus {
    border-color: var(--shop-primary);
    outline: var(--focus-ring);
    outline-offset: var(--focus-ring-offset);
}

.checkout-input:disabled {
    cursor: not-allowed;
    opacity: .65;
}

/*
 * Обгортка лише навколо самого поля (input + hidden + spinner + панель), без label/hint/error
 * (issue #84, баг 4): контекст позиціонування прив'язаний до інпута, а не до всього
 * .checkout-field, інакше "top: 100%" рахувався б від нижньої межі error/hint, а не інпута.
 */
.checkout-autocomplete {
    position: relative;
}

/* Запас під спінер (issue #84, баг 9), щоб довгий текст під час набору не заходив під іконку.
   Тепер у правому краї їх двоє: кнопка очищення притиснута до межі, спінер стоїть ліворуч від
   неї, тож запас накриває обидва — інакше вони блимали б один поверх одного під час пошуку. */
.checkout-autocomplete .checkout-input {
    padding-right: calc(var(--control-h-md) + var(--sp-5));
}

.checkout-autocomplete-spinner {
    position: absolute;
    top: 50%;
    right: calc(var(--sp-1) + var(--control-h-md));
    color: var(--shop-ink-soft);
    transform: translateY(-50%);
}

/* Повні 44px тач-таргета всередині поля — як у .shop-search-clear каталогу (issue #80). */
.checkout-autocomplete-clear {
    position: absolute;
    top: 50%;
    right: var(--sp-1);
    display: inline-grid;
    place-items: center;
    width: var(--control-h-md);
    height: var(--control-h-md);
    padding: 0;
    color: var(--shop-ink-soft);
    background: transparent;
    border: 0;
    border-radius: var(--r-pill);
    font-size: var(--fs-xs);
    transform: translateY(-50%);
    transition: color .18s var(--ease-out), background-color .18s var(--ease-out);
}

.checkout-autocomplete-clear:hover,
.checkout-autocomplete-clear:focus-visible {
    color: var(--shop-primary);
    background: var(--shop-surface);
}

/*
 * Overlay поверх решти форми (issue #84, баг 3): раніше панель була рядком грида в потоці
 * документа й розсовувала все нижче — Оплату, Коментар, кнопку підтвердження — щойно
 * відкривався список.
 */
.checkout-autocomplete-panel {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    z-index: 10;
    margin-top: var(--sp-1);
    display: grid;
    gap: var(--sp-1);
    padding: var(--sp-1) 0;
    /* 45vh — щоб на мобільному список не впирався в нижній згин екрана під клавіатурою
       (issue #84, баг 15); фіксовані 18rem лишаються межею на десктопі. */
    max-height: min(18rem, 45vh);
    overflow-y: auto;
    background: var(--shop-card);
    /* --shop-line дає 1.32:1 до --shop-card — нижче WCAG 1.4.11 (issue #84, баг 11). */
    border: 1px solid var(--shop-line-strong);
    border-radius: var(--r-sm);
    box-shadow: var(--shadow-md);
}

/*
 * Підказка про мінімальну довжину запиту, кількість знайденого й обрізання списку — озвучується
 * тут через aria-live, а не всередині role="listbox" нижче, де listbox може містити лише
 * option/group і допоміжні технології мають право ігнорувати сторонні ролі (issue #84, баг 13).
 */
.checkout-autocomplete-status {
    margin: 0;
    padding: 0 var(--sp-3);
    color: var(--shop-ink-soft);
    font-size: var(--fs-xs);
}

.checkout-autocomplete-status:empty {
    display: none;
}

.checkout-autocomplete-results {
    display: grid;
}

.checkout-autocomplete-option {
    display: grid;
    gap: var(--sp-1);
    width: 100%;
    min-height: var(--control-h-md);
    padding: var(--sp-2) var(--sp-3);
    color: var(--shop-ink);
    text-align: left;
    background: var(--shop-card);
    border: 0;
    border-bottom: 1px solid var(--shop-line-strong);
}

.checkout-autocomplete-option:last-of-type {
    border-bottom: 0;
}

/*
 * --shop-surface дає лише 1.15:1 до --shop-card (issue #84, баг 12) — рядок під курсором майже
 * не відрізняється від сусідніх. Додатковий inset-акцент кольором primary не залежить від
 * тонкої різниці яскравості й помітний завжди, а не лише в стані :focus із власним outline.
 */
.checkout-autocomplete-option:hover,
.checkout-autocomplete-option[aria-selected="true"] {
    background: var(--shop-surface);
    box-shadow: inset 3px 0 0 var(--shop-primary);
}

/*
 * Клавіатурний курсор списку. Фокус лишається в полі (aria-activedescendant), тож :focus тут
 * більше не спрацьовує — стан позначає aria-selected. Кільце понад заливкою потрібне саме
 * тому, що заливка збігається з :hover: коли миша лежить на одному рядку, а стрілки стоять
 * на іншому, без кільця обидва виглядали б однаково активними.
 *
 * Offset від'ємний: опції притиснуті до країв панелі, і назовні кільце обрізалося б її
 * межею та overflow-ом.
 */
.checkout-autocomplete-option[aria-selected="true"] {
    outline: var(--focus-ring);
    outline-offset: calc(var(--focus-ring-width) * -1);
}

.checkout-autocomplete-title {
    font-size: var(--fs-sm);
    font-weight: 600;
}

.checkout-autocomplete-meta {
    color: var(--shop-ink-soft);
    font-size: var(--fs-xs);
}

/* Контекст обраного значення (область/район або адреса) окремим рядком під полем: повний
   "назва — область, район" не вміщався в саме поле (issue #84, баг 6). */
.checkout-selection-context {
    margin: 0;
    color: var(--shop-ink-soft);
    font-size: var(--fs-xs);
}

.checkout-selection-context:empty {
    display: none;
}

.checkout-directory-message {
    align-items: flex-start;
    margin: 0;
}

.checkout-textarea {
    min-height: 5.5rem;
    resize: vertical;
}

.checkout-input-invalid {
    border-color: var(--shop-danger);
}

.checkout-input-invalid:focus {
    border-color: var(--shop-danger);
}

/* Підказки, помилки полів і пояснення способів оплати — --fs-sm, а не --fs-xs. Це не дрібний
   службовий текст: без них поле заповнюється навмання, а помилку треба ще й прочитати з
   першого разу. 12px під кожним полем форми на це не розраховані. */
.checkout-hint {
    margin: 0;
    color: var(--shop-ink-faint);
    font-size: var(--fs-sm);
}

.checkout-error {
    margin: 0;
    color: var(--shop-danger);
    font-size: var(--fs-sm);
    font-weight: 600;
}

.checkout-errors {
    align-items: flex-start;
}

.checkout-errors-title {
    margin: 0 0 var(--sp-1);
    font-weight: 600;
}

.checkout-errors-list {
    margin: 0;
    padding-left: var(--sp-4);
}

/* Пункт зведення — посилання на поле, тож підкреслення лишається: без нього клікабельність
   видно лише за курсором, а колір тексту тут той самий, що й у решті блоку. */
.checkout-errors-link {
    color: inherit;
    text-decoration: underline;
}

.checkout-errors-link:hover {
    color: var(--shop-primary);
}

/* Фокус на зведенні ставиться програмно (autofocus) і на групу оплати — переходом за якорем,
   тому потрібен :focus, а не :focus-visible. */
.checkout-errors:focus,
.checkout-payment:focus {
    outline: var(--focus-ring);
    outline-offset: var(--focus-ring-offset);
}

.checkout-payment {
    display: grid;
    /* 8px між картками читалося як одна суцільна пляма: у них власний радіус --r-md і
       внутрішній padding, тож проміжок має бути не меншим за цей радіус. */
    gap: var(--sp-3);
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;
    /* Зайвий запас під підпис тут не потрібен: <legend> лежить усередині fieldset, а не над ним,
       тож досить розминутися зі sticky-шапкою. */
    scroll-margin-top: calc(var(--shop-header-h) + var(--sp-3));
}

/* Уся картка способу оплати клікабельна: на дотику влучити в саме коло радіокнопки складно.
   Радіокнопка стоїть окремою колонкою й центрується проти всього блоку підпис + підказка. */
.checkout-option {
    display: flex;
    gap: var(--sp-3);
    align-items: center;
    min-height: var(--control-h-md);
    padding: var(--sp-4);
    background: var(--shop-page);
    border: 1px solid var(--shop-line);
    border-radius: var(--r-md);
    cursor: pointer;
}

.checkout-option:has(input:checked) {
    background: var(--shop-surface);
    border-color: var(--shop-primary);
}

/* Кольори радіокнопки вже дає міст до Bootstrap (--shop-line-strong / --shop-primary),
   лишається розмір і вага: 1em від --fs-base — це 15px, а межа 1px поруч із підписом
   виглядає нерішучою. 2px і 1.25rem дають контролу присутність, з якою його видно як
   контрол, а не як декоративну крапку. Власний margin Bootstrap скидаємо — вертикаль
   тримає align-items картки. */
.checkout-option .form-check-input {
    width: 1.25rem;
    height: 1.25rem;
    margin: 0;
    border-width: 2px;
}

.checkout-option-body {
    display: grid;
    /* Підказка йде окремим рядком одразу під підписом, тому проміжок — найменший крок шкали:
       більший розірвав би пару «назва + пояснення» на два незалежні рядки. */
    gap: var(--sp-1);
    flex: 1 1 auto;
    min-width: 0;
}

/* На широкому екрані назва ліворуч, марки гаманців праворуч.
   На мобільному окреме правило розташовує їх колонкою зі спільним лівим краєм. */
.checkout-option-head {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-4);
    align-items: center;
    min-width: 0;
}

.checkout-option-label {
    font-size: var(--fs-base);
    font-weight: 600;
}

.checkout-option-hint {
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

/* Правий край для широкого екрана; на мобільному відступ скидається. */
.checkout-wallets {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    margin-left: auto;
}

/* Та сама оптична висота, що й у радіокнопки. На 28px марки перебивали підпис і читалися
   як реклама платіжних систем, хоча їхня роль тут — тиха підказка про спосіб оплати. */
.checkout-wallets img {
    display: block;
    height: 1.25rem;
    width: auto;
}

.checkout-submit {
    width: 100%;
    gap: var(--sp-2);
    border: 0;
}

/* Крапка між дією і сумою: без неї два написи в одній пігулці читаються як два різні
   підписи кнопки. aria-hidden у розмітці — голосу пауза не потрібна. */
.checkout-submit-sep {
    opacity: .55;
}

.checkout-submit-total {
    font-weight: 700;
}

.checkout-summary {
    position: sticky;
    grid-column: 2;
    grid-row: 1;
    top: calc(var(--shop-header-h) + var(--sp-4));
    /* Padding переїжджає всередину: смуга-заголовок мусить бути клікабельною на всю ширину
       картки, а не лише на висоту тексту. Власний gap картки при цьому зайвий — вертикаль
       тримають padding смуги й gap тіла. */
    padding: 0;
    gap: 0;
}

/* Уся смуга — зона натискання: у згорнутому стані це єдиний шлях до складу замовлення,
   і цілитись пальцем у сам заголовок незручно. */
.checkout-summary-toggle {
    /* Grid, а не flex: заголовок і кількість стоять стовпчиком, але обидва мусять лишатись
       прямими нащадками <summary> (обгортка навколо <h2> була б невалідною), тож стовпчик
       складають areas, а не додатковий елемент. Сума й шеврон тримають обидва рядки. */
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto auto;
    grid-template-areas:
        "title total chevron"
        "count total chevron";
    align-items: center;
    gap: 0 var(--sp-3);
    min-height: var(--control-h-lg);
    padding: var(--sp-4) var(--sp-5);
    cursor: pointer;
    /* Обидва рядки прибирають типовий трикутник: list-style — у Chrome і Firefox,
       ::-webkit-details-marker — у Safari, який ще читає лише його. */
    list-style: none;
}

.checkout-summary-toggle .cart-summary-title {
    grid-area: title;
}

.checkout-summary-toggle::-webkit-details-marker {
    display: none;
}

/* Кількість — під заголовком: на 375px «Ваше замовлення», «Товарів: N» і сума в один
   рядок не вміщаються, і заголовок ламався навпіл посеред смуги. */
.checkout-summary-count {
    grid-area: count;
    color: var(--shop-ink-soft);
    font-size: var(--fs-xs);
}

/* Сума тримає праву межу смуги й ніколи не переноситься: «760» окремо від «грн» —
   не число, а два уламки. */
.checkout-summary-glance {
    grid-area: total;
    white-space: nowrap;
}

/* У розгорнутому стані ті самі товари й сума стоять рядком під списком: два однакові
   тотали за 200px один від одного читались би як дві різні суми. */
.checkout-summary[open] .checkout-summary-glance,
.checkout-summary[open] .checkout-summary-count {
    display: none;
}

.checkout-summary-chevron {
    grid-area: chevron;
    color: var(--shop-ink-soft);
    font-size: var(--fs-base);
    transition: transform .2s var(--ease-out);
}

.checkout-summary[open] .checkout-summary-chevron {
    transform: rotate(180deg);
}

.checkout-summary-body {
    display: grid;
    gap: var(--sp-3);
    padding: 0 var(--sp-5) var(--sp-5);
}

.checkout-items {
    display: grid;
    gap: var(--sp-3);
    margin: 0;
    padding: 0;
    list-style: none;
}

.checkout-item {
    display: grid;
    grid-template-columns: 3.5rem minmax(0, 1fr) auto;
    gap: var(--sp-3);
    align-items: start;
}

.checkout-item-image {
    width: 100%;
    height: auto;
    background: var(--shop-media);
    border-radius: var(--r-sm);
    object-fit: cover;
}

.checkout-item-body {
    display: grid;
    gap: var(--sp-1);
    min-width: 0;
}

.checkout-item-title {
    font-size: var(--fs-sm);
    font-weight: 600;
}

.checkout-item-options {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-1) var(--sp-3);
    color: var(--shop-ink-soft);
    font-size: var(--fs-xs);
}

.checkout-item-price {
    font-size: var(--fs-sm);
    font-weight: 600;
    white-space: nowrap;
}

/* Окрема картка (issue #79), а не .cart-empty: сторінка успіху раніше позичала контейнер
   порожнього кошика, хоча в неї власний контент і власна ширина. Фон, радіус і text-align —
   ті самі, що в .shop-empty/.cart-empty: три екрани лишаються одним примітивом «пауза
   в потоці». Власний max-width не дає картці розтягнутись на всю ширину .container на 1440 —
   26rem (ширина блоків нижче) + padding по sp-5 з обох боків. */
.checkout-done-card {
    display: grid;
    justify-items: center;
    gap: var(--sp-2);
    max-width: 29rem;
    margin-inline: auto;
    padding: var(--sp-7) var(--sp-5);
    background: var(--shop-surface);
    border-radius: var(--r-lg);
    text-align: center;
}

.checkout-done-card .icon {
    color: var(--shop-ink-faint);
    font-size: 2rem;
}

/* h1 сторінки, типографіка помітно більша за заголовки секцій нижче (17px/700), бо тут це
   головний заголовок останнього кроку воронки, а не заглушка. */
.shop-page .checkout-done-title {
    margin: 0;
    font-size: var(--fs-xl);
    font-weight: 700;
}

/* Текст поруч прямо просить "Збережіть номер замовлення" — акцентний жовтий, а не звичайний
   абзац: цей номер єдиний, що знадобиться гостю після цієї сторінки. */
.checkout-done-number {
    margin: 0;
    padding: var(--sp-2) var(--sp-4);
    background: var(--shop-secondary-soft);
    border-radius: var(--r-pill);
    font-size: var(--fs-md);
    font-weight: 700;
}

/* Склад замовлення читається зліва направо, як і наступні кроки нижче. */
.checkout-done-order,
.checkout-done-summary,
.checkout-done-next {
    width: 100%;
    max-width: 26rem;
    margin-inline: auto;
}

.checkout-done-order {
    margin-block-start: var(--sp-2);
    text-align: left;
}

.checkout-done-order-title {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-md);
    font-weight: 600;
}

/* Без фото товару, на відміну від .checkout-item у зведенні оформлення: замовлення вже
   підтверджене, картинка тут не потрібна для звірки складу. */
.checkout-item-plain {
    grid-template-columns: minmax(0, 1fr) auto;
}

.checkout-done-summary {
    display: grid;
    grid-template-columns: auto auto;
    gap: var(--sp-1) var(--sp-4);
    margin-block: var(--sp-2) var(--sp-3);
    font-size: var(--fs-sm);
}

.checkout-done-summary dt {
    color: var(--shop-ink-soft);
    font-weight: 400;
    text-align: right;
}

.checkout-done-summary dd {
    margin: 0;
    font-weight: 600;
    text-align: left;
}

/* Наступні кроки читаються зліва направо: сам текст лишається без центрування підтвердження,
   хоча зовнішня межа блока вирівняна зі складом замовлення й зведенням вище (issue #75). */
.checkout-done-next {
    margin-block-end: var(--sp-4);
    text-align: left;
}

.checkout-done-next-title {
    margin: 0 0 var(--sp-2);
    font-size: var(--fs-md);
    font-weight: 600;
}

.checkout-done-steps {
    display: grid;
    gap: var(--sp-2);
    margin: 0;
    padding-left: var(--sp-4);
    color: var(--shop-ink-soft);
    font-size: var(--fs-sm);
}

/* ---------- Адаптив ------------------------------------------------------- */

/* Планшетний діапазон пропускав власну колонковість і успадковував мобільні 2 колонки —
   картка роздувалась замість трьох компактних у ряд. */
@media (min-width: 768px) and (max-width: 991.98px) {
    .product-grid:not(.product-grid-list) {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: var(--sp-6) var(--sp-5);
    }
}

/* Перевизначений background-image не завантажується, тому на ≥768px браузер бере лише
   те, що виграло каскад: girl-720 на dpr 2 і girl-480 на dpr 1. */
@media (min-width: 768px) {
    .shop-hero-figure {
        background-image: image-set(
            url("/images/logo/girl-480.webp") 1x,
            url("/images/logo/girl-720.webp") 2x
        );
    }
}

@media (min-width: 992px) {
    .product-grid:not(.product-grid-list) {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

@media (min-width: 1400px) {
    .product-grid:not(.product-grid-list) {
        grid-template-columns: repeat(5, minmax(0, 1fr));
    }
}

/* До `lg` бічна колонка не вміщається поруч із формою, тому блоки складаються в один стовпчик
   у порядку DOM: спершу зведення, під ним форма. Візуальний порядок навмисно не змінюється
   через `order` — інакше фокус ішов би не туди, куди дивиться око. Тут зведення згорнуте
   (скрипт у checkout.html), тож воно займає одну смугу й не витісняє перше поле форми.
   Липке зведення на вузькому екрані з'їдало б половину висоти, тому воно прокручується
   разом зі сторінкою. */
@media (max-width: 991.98px) {
    .checkout-layout {
        grid-template-columns: minmax(0, 1fr);
    }

    /* Явне розміщення з десктопа тут створило б неіснуючу другу колонку — повертаємо
       блоки в потік у порядку розмітки. */
    .checkout-form,
    .checkout-summary {
        grid-column: auto;
        grid-row: auto;
    }

    .checkout-summary {
        position: static;
    }

    /* Той самий двоколонковий макет, що й .checkout-layout, зі своєю копією
       grid-template-columns: бічна колонка підсумку не вміщається поруч зі списком
       товарів уже в планшетному діапазоні, тож згортаємо в один стовпчик на тому
       самому брейкпоінті, а не пізніше. */
    .cart-layout {
        grid-template-columns: minmax(0, 1fr);
    }
}

@media (max-width: 767.98px) {
    :root {
        --shop-header-h: 72px;
    }

    .checkout-option {
        align-items: flex-start;
    }

    .checkout-option .form-check-input {
        margin-top: var(--sp-1);
    }

    .checkout-option-head {
        flex-direction: column;
        align-items: flex-start;
    }

    .checkout-wallets {
        margin-left: 0;
    }

    /* .container сам перевизначає --bs-gutter-x (bootstrap.min.css), тож :root
       override його не дістає — потрібне саме це селектор. Поле лишається
       вираженим через --sp-4 * 2 (padding = --bs-gutter-x / 2), а не хардкодженим
       px, щоб шапка й основний контент (issue #82) трималися одного токена. */
    .container {
        --bs-gutter-x: calc(var(--sp-4) * 2);
    }

    .shop-navbar {
        padding-block: var(--sp-2);
    }

    .shop-logo {
        width: 126px;
    }

    /* Було 2.4rem (38.4px): на мобільному пальцем, а не курсором, тож наближаємо
       до орієнтиру 44×44, а не лишаємо звужений під десктопну шапку розмір. */
    .shop-icon-button {
        width: 2.75rem;
        height: 2.75rem;
    }

    /* dropdown-menu-end прив'язує правий край панелі до кнопки-іконки, а не до
       viewport. Кнопка пошуку не крайня праворуч (за нею ще кошик), тож на вузьких
       телефонах прив'язана до кнопки панель шириною 20rem виїжджала за лівий край
       екрана (issue #69). Фіксоване позиціювання з відступами від країв екрана —
       той самий прийом, що й у .cart-toast-region.
       Подвійний селектор навмисний: Bootstrap позиціює керовану Popper-ом панель
       через `.dropdown-menu[data-bs-popper]` / `.dropdown-menu-end[data-bs-popper]`
       (клас + атрибут, 2 прості селектори) — з рівнем в один клас цей override
       програвав би top/right/left, попри пізніше підключення shop.css. */
    .shop-page .shop-nav-search-menu {
        position: fixed;
        top: calc(var(--shop-header-h) + var(--sp-2));
        right: var(--sp-4);
        left: var(--sp-4);
        width: auto;
        max-width: none;
    }

    /* На мобільному підтвердження «Товар додано в кошик» показуємо знизу, а не під
       шапкою: великий палець і так лежить унизу екрана, а на сторінці товару там-таки
       живе липка панель покупки — саме кнопка, яку щойно натиснули (issue #71). На
       інших сторінках знизу вільно, конфлікту немає. */
    .cart-toast-region {
        top: auto;
        bottom: calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
    }

    /* На сторінці товару тост має піднятись над липкою панеллю покупки, а не лягти
       під неї. Висота панелі — .pdp-cta (var(--control-h-lg), issue #80) плюс вертикальні
       відступи .pdp-sticky-bar (var(--sp-2) з обох боків) і її рамка (1px × 2). Токен, а не
       літерал: висота .pdp-cta й цей розрахунок мають лишатись синхронними самі. */
    .pdp-page .cart-toast-region {
        bottom: calc(var(--control-h-lg) + var(--sp-2) * 2 + 2px + var(--sp-3) + env(safe-area-inset-bottom, 0px));
    }

    /* Найдорожчий екран сайту — саме цей: до першого товару має лишатись менше
       ніж один прогорнутий екран, а не півтора. Місце беремо з самого героя, а не
       з функціональних елементів каталогу нижче: пошук, фільтри й сортування лишаються
       на місці. Верхній відступ — --sp-4, а не --sp-2: на 8px заголовок
       героя стояв упритул до липкої шапки і сторінка починалась без жодної паузи. */
    .shop-hero {
        padding-block: var(--sp-4) 0;
    }

    /* Підзаголовок ховаємо саме на мобільному: чотири рядки у вузькій колонці коштували
       125px разом з відступом — більше, ніж будь-який інший блок героя, — а обіцянку
       бренду вже несе H1, і для пошуку той самий текст лишається в meta description.
       Без нього герой складається з двох речей замість трьох, і колонки стають
       співмірними: копія 122px проти ілюстрації 132px. */
    .shop-hero-text {
        display: none;
    }

    /* Маскот повернувся на мобільний, але не окремим блоком над копією: 2/3 картинка
       на всю ширину коштувала б ~250px висоти найдорожчого екрана. Замість цього та сама
       двоколонкова ідея, що й на десктопі, у меншому масштабі — копія ліворуч, ілюстрація
       праворуч. align-items: center, а не успадкований end: без підзаголовка ілюстрація
       стала вищою за копію, і при вирівнюванні по низу заголовок з кнопкою висіли б
       у нижньому куті з порожнечею над ними. */
    .shop-hero-inner {
        grid-template-columns: 1fr auto;
        gap: var(--sp-4);
        align-items: center;
    }

    .shop-hero-title,
    .shop-hero-text {
        max-width: none;
    }

    /* Issue #82: на мобільному H1 → опис і опис → CTA стояли впритул (4px і 8px) —
       блоки героя читались одним суцільним абзацом. Крок токенів угору (sp-1 → sp-3,
       sp-2 → sp-4) розділяє смисли, не займаючи текст і розмір кнопки.
       --fs-xl замість --fs-display (30.4px на цій ширині): двох рядків заголовок
       тримається в обох кеглях, але менший кегль лишає копії 122px замість 133 —
       рівно стільки, щоб перший ряд фото вміщався над згином цілком. Той самий розмір,
       що й у .shop-section-title, ієрархію не плутає: H1 і H2 на мобільному розділяє
       вся стрічка категорій, поруч вони не стоять. */
    .shop-hero-title {
        margin-bottom: var(--sp-3);
        font-size: var(--fs-xl);
    }

    /* 5.5rem, а не колишні 7rem: без підзаголовка копія героя схудла до 122px, і
       168px ілюстрації поруч читались би вже не парою, а картинкою з підписом.
       Менше за 5rem обличчя маскота перестає читатись. justify-self скидаємо з center,
       бо колонка тепер рівно по ширині ілюстрації. */
    .shop-hero-figure {
        width: 5.5rem;
        justify-self: end;
    }

    /* Після #51 стрічка лишилась найбільшим блоком над згином (198px, 24% екрана).
       Місце для ритму каталогу беремо саме тут: менший кружок і рівний підпис нижче. */
    .category-strip {
        --category-image-size: 4rem;
    }

    .category-stories {
        gap: var(--sp-3);
        margin-right: calc(var(--bs-gutter-x) * -0.5);
        /* Issue #82 підняв верхній відступ до sp-3 (разом з py-2 смуги 20px), бо CTA
           героя й початок стрічки стояли за 16px. Тепер герой нижчий і без підзаголовка,
           тож 16px разом з py-2 знову читаються як пауза між блоками, а не як злипання. */
        padding-block: var(--sp-2);
        padding-right: calc(var(--bs-gutter-x) * 0.5);
    }

    /* На тачі стрічка гортається свайпом — кнопки лише з'їдали б крайні кружки. */
    .category-scroll-button {
        display: none;
    }

    /* Підказка «категорії не закінчились». Досі її роль грав наступний кружок, що
       визирає за край, але це залежить від того, як ширина екрана ділиться на крок
       стрічки: виміряно, що на 375 і 390 — найпоширеніших iPhone — не визирало
       нічого ні до розширення підпису, ні після (0px кружка в обох випадках), і
       стрічка з 11 категорій читалась як три. Градієнт від арифметики не залежить.
       Правий край тягнемо на від'ємний gutter, щоб збігтися з bleed самої стрічки;
       гасне сам, коли догортали до кінця — клас чіпляє updateStripScrollButtons. */
    .category-strip-scroller::after {
        position: absolute;
        top: 0;
        right: calc(var(--bs-gutter-x) * -0.5);
        bottom: 0;
        z-index: 1;
        width: var(--sp-6);
        background: linear-gradient(to right, transparent, var(--shop-page));
        content: "";
        opacity: 0;
        pointer-events: none;
        transition: opacity .18s var(--ease-out);
    }

    .category-strip-scroller.has-scroll-end::after {
        opacity: 1;
    }

    /* Дзеркало правого градієнта. Закріплювати тут «Усі» sticky-піном було спокусливо, але
       на 390px він коштував 108px рейки з 374 — після прокрутки лишалось два повні кружки з
       трьох з половиною, і ще один назавжди стояв під самим піном. Ціна зависока для контрола,
       потрібного лише після вибору категорії, тим паче що вихід із фільтра дає ще й повторний
       клік по активному кружку. Тому ліворуч лишається сама підказка «там ще є».
       Лівий край без bleed, на відміну від правого: від'ємний margin має тільки він. */
    .category-strip-scroller::before {
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 1;
        width: var(--sp-6);
        background: linear-gradient(to left, transparent, var(--shop-page));
        content: "";
        opacity: 0;
        pointer-events: none;
        transition: opacity .18s var(--ease-out);
    }

    .category-strip-scroller.has-scroll-start::before {
        opacity: 1;
    }

    /* Відступ під підкресленням тримає .category-stories: власний padding кожної
       картки лише додавав висоти стрічці, бо підкреслення й так останнє в колонці. */
    /* 6rem, а не колишні 5.25rem: 9 із 22 назв закінчуються на «для дівчаток»,
       «для хлопчиків» або «для дівчат», і на 84px два рядки різались посеред цього
       хвоста — «Брюки та штани для…». Перейменувати категорії не можна: назва є
       ключем у ?category=, тож це зламало б наявні посилання й SEO. Розширюємо
       горизонтально, а не третім рядком, бо бюджет першого екрана рахований по
       вертикалі — тут він не змінюється зовсім. Саме 6rem, а не більше: виміряно на
       реальних назвах, що 96, 100 і 104px клампають однакові дві найдовші
       («Костюми весна/осінь…», «Утеплені костюми…»), тож ширше за 6rem не купує
       жодного підпису, лише з'їдає кружки на екрані. */
    .category-story {
        gap: var(--sp-2);
        width: 6rem;
        min-width: 6rem;
        padding-bottom: 0;
    }

    .category-all-mark {
        font-size: var(--fs-base);
    }

    /* Рівно два рядки фіксованою висотою, а не min-height: у трьох із дванадцяти
       категорій підпис лягав у три рядки, ці .category-story ставали вищими за решту,
       і підкреслення активної категорії висіло над лінією сусідів. Повна назва лишається
       в DOM і в aria-label посилання, тож screen reader читає її незрізаною. */
    .category-title {
        display: -webkit-box;
        height: 2.7em;
        overflow: hidden;
        font-size: var(--fs-xs);
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
    }

    .category-story::after {
        width: var(--sp-6);
    }

    /* П'ять смислових рядів каталогу — «Каталог / Товари», контроли, пошук, лічильник
       і перша картка — стояли на 4–8px і читались як один злиплий блок керування,
       щільніший за контент, яким він керує. Крок ритму — --sp-3, а під шапкою секції
       лишається більший --sp-5, щоб «Каталог / Товари» не входили в той самий блок.
       На ≥768px відступи задають md-утиліти в product-section.html. */
    .shop-catalog-head {
        gap: var(--sp-5);
    }

    .shop-catalog-head,
    .shop-search,
    .shop-results-head {
        margin-bottom: var(--sp-3);
    }

    /* «Каталог / Товари» на головній нічого не називають: H1 героя вже сказав, що тут
       продають одяг, а під смугою категорій іде очевидна сітка. Видимими вони коштували
       82px найдорожчого екрана (19px надпис + 8px відступ + 31px H2 + 24px gap самої
       шапки). Ховаємо кліпом, а не display: none — H2 лишається в дереві доступності,
       тож структура H1 → H2 → H3-картки не рветься, а якір #catalog і далі має куди
       прокрутитись. Правила ті самі, що в Bootstrap .visually-hidden: responsive-версії
       цієї утиліти Bootstrap не дає, тому вона тут, а не класом у розмітці.
       Позиціювання absolute виводить блок із потоку, тож gap шапки вище лишається без
       діла — у потоці єдиний .shop-controls-row. */
    .shop-catalog-heading {
        position: absolute !important;
        width: 1px !important;
        height: 1px !important;
        padding: 0 !important;
        margin: -1px !important;
        overflow: hidden !important;
        clip-path: inset(50%) !important;
        white-space: nowrap !important;
        border: 0 !important;
    }

    .product-grid {
        gap: var(--sp-5) var(--sp-3);
    }

    /* Дві колонки на 375px — ціна, стара ціна і кнопка мають вміститись в один рядок */
    .product-price-row {
        gap: var(--sp-2);
    }

    .product-price {
        font-size: var(--fs-base);
    }

    .product-quick-add-button .icon {
        font-size: .9rem;
    }

    /* Quick view на вузькому екрані: повна пропорція 4/5 забрала б майже весь перший
       екран модалки й відсунула б колір, розмір і кількість під згин. Висота від
       viewport лишає кадр цілим і тримає варіанти видимими; на низькому екрані
       (SE, ландшафт) вона зменшується сама, тому clamp, а не одне число. */
    .quick-add-stage {
        aspect-ratio: auto;
        height: clamp(240px, 40vh, 320px);
    }

    .product-grid-list .product-card {
        grid-template-columns: 1fr;
        gap: var(--sp-3);
    }

    .product-grid-list .product-media {
        min-height: auto;
    }

    .product-title a {
        min-height: 2.8em;
    }

    .shop-cart-count {
        top: .2rem;
        right: .1rem;
    }

    /* Одноколонковий .cart-layout вже діє з 991.98px; тут рядок кошика додатково
       ламається у два ряди: ціна й кроки кількості поруч із фото не вміщаються
       вже на 375px. */
    .cart-line {
        grid-template-columns: 4.5rem minmax(0, 1fr);
        gap: var(--sp-3);
    }

    .cart-line-actions {
        grid-column: 1 / -1;
        justify-content: space-between;
        gap: var(--sp-3);
    }

    .cart-line-price {
        min-width: 0;
    }

    .checkout-section {
        padding: var(--sp-4);
    }

    /* На вузькому екрані колонка форми й так вужча за обмеження цього поля. */
    .checkout-field-phone {
        max-width: none;
    }

    /* .pdp-badge лишається на --fs-eyebrow у базовому правилі, тож мобільний підйом до
       --fs-xs для нього досі потрібен. Решта селекторів цього блоку після issue #78 мають
       12px на всіх ширинах, тому окремого мобільного винятку більше не потребують. */
    .pdp-badge {
        font-size: var(--fs-xs);
    }
}

/* На 320px старій ціні вже не вистачає ширини поруч із новою, і вона переносить рядок
   ціни в картці зі знижкою на 7px нижче за картку без знижки — рядок розмірів під ними
   розсинхронізується між сусідніми картками (issue #71). На 375px+ обидві ціни й так
   стоять в один рядок, тому нижче не чіпаємо. Резервуємо висоту двох рядків завжди,
   щоб обидва варіанти картки лишались однакової висоти незалежно від знижки. */
@media (max-width: 359.98px) {
    .product-price-row {
        min-height: 3.1875rem; /* 51px — виміряна висота рядка з перенесеною старою ціною */
    }
}

@media (prefers-reduced-motion: reduce) {
    .shop-page *,
    .shop-page *::before,
    .shop-page *::after {
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
    }

    .product-card:hover .product-image,
    .category-story:hover .category-image {
        transform: none;
    }
}

.checkout-payment-card {
    gap: var(--sp-4);
}

.checkout-payment-message,
.checkout-payment-fiscal,
.checkout-payment-notice {
    margin: 0;
}

.checkout-payment-notice {
    border: var(--shop-line) solid var(--shop-line-strong);
    border-radius: var(--r-md);
    padding: var(--sp-3);
}

.checkout-payment-secondary {
    align-items: center;
    display: inline-flex;
    justify-content: center;
    min-height: var(--control-h-md);
    padding-inline: var(--control-pad-x);
}
