/* ==========================================================================
   responsive.css — the storefront's responsive layer.
   Loaded LAST (after style.css) from includes/v2/head.php.
   ==========================================================================

   Why this file exists
   --------------------
   style.css grew a different breakpoint for almost every component: 1400,
   1300, 1200, 1100, 1000, 992, 940, 900, 820, 768, 720, 700, 650, 640, 620,
   600, 576, 560, 520, 480, 460, 450, 440, 420, 400, 380. Nothing lined up, so
   a tablet could get the phone layout in one component and the desktop layout
   in the next. This file defines ONE scale and re-states the layout rules on
   it, so every page steps between form factors at the same widths.

   The scale
   --------------------
     ≤ 479      phone, small        1 - 2 up
     480 – 767  phone               2 up
     768 – 1023 tablet portrait     3 up   <- was getting the 2-up phone layout
     1024 –1279 tablet landscape    3 up
     ≥ 1280     desktop             page's own choice (usually 4 up)

   Everything below is expressed mobile-first with min-width queries, so a rule
   set at one size holds until a wider one replaces it. Breakpoints are
   min-width: 480 / 768 / 1024 / 1280 to avoid the off-by-one gaps that
   max-width chains produce.

   Tokens are on :root and re-declared inside the media queries, so a component
   only has to use var(--space-section) once to be responsive everywhere.
*/

/* --------------------------------------------------------------- tokens -- */
:root {
    /* Page gutter — the breathing room between content and the screen edge. */
    --gutter: 16px;

    /* Vertical rhythm. --space-section is the gap between major page bands,
       --space-block the gap between elements inside one. */
    --space-section: 40px;
    --space-block: 20px;
    --space-card: 14px;      /* padding inside a card */

    /* Type scale. clamp() so sizes glide between breakpoints instead of
       snapping — a 34px heading on a 768px tablet and a 34px heading on a
       1400px desktop are not the same heading. */
    --fs-h1: clamp(22px, 4.4vw, 40px);
    --fs-h2: clamp(20px, 3.4vw, 32px);
    --fs-h3: clamp(17px, 2.4vw, 22px);
    --fs-body: 15px;
    --fs-small: 13px;

    /* Grid columns for product/blog card listings. */
    --cards: 2;
    --cards-gap: 12px;

    /* Height cap for a product image inside a card. */
    --tile-img-h: 130px;
}

@media (min-width: 480px) {
    :root {
        --gutter: 20px;
        --space-section: 48px;
        --cards-gap: 16px;
        --tile-img-h: 150px;
    }
}

@media (min-width: 768px) {
    :root {
        --gutter: 24px;
        --space-section: 60px;
        --space-block: 24px;
        --space-card: 18px;
        --fs-body: 16px;
        --cards: 3;
        --cards-gap: 18px;
        --tile-img-h: 165px;
    }
}

@media (min-width: 1024px) {
    :root {
        --gutter: 28px;
        --space-section: 72px;
        --cards: 3;
        --cards-gap: 20px;
        --tile-img-h: 180px;
    }
}

@media (min-width: 1280px) {
    :root {
        --gutter: 32px;
        --space-section: 88px;
        --space-block: 28px;
        --space-card: 20px;
        --cards-gap: 24px;
        --tile-img-h: 200px;
    }
}

/* ------------------------------------------------- no horizontal scroll -- */
/* `clip` rather than `hidden`: hidden turns the element into a scroll
   container, which silently kills every `position: sticky` inside it (the
   sticky header, the checkout summary, the product buy-box). clip does the
   same visual job with no scroll container. This is the safety net — the
   individual causes are fixed at source below, not papered over here. */
html {
    overflow-x: clip;
}

body {
    overflow-x: clip;
    max-width: 100%;
}

/* Replaced media never widens its column. */
img, video, canvas, svg, iframe, embed, object {
    max-width: 100%;
}

/* An unbreakable string — a part number, a pasted URL, a long email — is the
   single most common cause of a page that scrolls sideways on a phone. */
h1, h2, h3, h4, h5, h6, p, li, dd, dt, td, th, figcaption, label, blockquote {
    overflow-wrap: break-word;
}

/* CMS/admin-authored HTML: descriptions and page bodies contain arbitrary
   markup that was never written with a 360px screen in mind. */
.description-content, .single-blog-content, .cp-prose, .blog-content {
    overflow-wrap: break-word;
}

.description-content pre,
.single-blog-content pre,
.cp-prose pre {
    overflow-x: auto;
    max-width: 100%;
}

/* --------------------------------------------------------- page gutters -- */
/* Every page shell gets the same fluid gutter, so content lines up with the
   header and footer no matter which page you are on. */
.container,
.product-page-wrapper,
.category-page-container,
.cart-page,
.co__main,
.blog-page-wrapper,
.single-blog-wrapper,
.about-page-wrapper,
.contact-page-wrapper,
.acct,
.calc-page {
    padding-left: var(--gutter);
    padding-right: var(--gutter);
    width: 100%;
    margin-left: auto;
    margin-right: auto;
}

/* --------------------------------------------------------- type scale --- */
/* Section headings across pages. Page-level h1/h2 that already carry a
   bespoke size keep it — these only catch the shared title classes. */
.section-title,
.timeline-main-title,
.about-page-wrapper h1,
.contact-page-wrapper h1,
.single-blog-wrapper h1,
.category-header h1 {
    font-size: var(--fs-h2);
    line-height: 1.22;
}

.subsection-title,
.about-page-wrapper h2,
.single-blog-wrapper h2,
.cp-prose h2 {
    font-size: var(--fs-h3);
    line-height: 1.3;
}

/* ------------------------------------------------------ card listings --- */
/* One rule, one variable. index.php and the favourites grid still set
   --per-row inline for their desktop column count; below 1280 the token wins
   so a phone never inherits a 4-column desktop choice. */
.main-product-grid {
    grid-template-columns: repeat(var(--per-row, 3), minmax(0, 1fr));
    gap: var(--cards-gap);
    margin-bottom: var(--space-section);
}

@media (max-width: 1279.98px) {
    .main-product-grid {
        --per-row: var(--cards) !important;
    }
}

/* Card internals shrink with the card. At 2-up on a 360px phone the old 20px
   padding left ~120px for the title, which is why product names were being
   truncated after three words. */
.product-card {
    padding: var(--space-card);
    min-width: 0;
}

.product-card .product-image-wrapper {
    height: var(--tile-img-h);
    /* The hover cross-fade slides the alternate image in from translateX(40px).
       The wrapper was overflow:unset, so on the last card of a row that hidden
       image poked past the viewport and gave every listing page 7px of
       horizontal scroll. Badges live outside this wrapper, so clipping it
       costs nothing. */
    overflow: hidden;
}

/* The image block reserves room for the badge row underneath; that reserve was
   a flat 75px, which is most of a phone card. */
.product-card .product-image-wrapper {
    margin-bottom: 8px;
}

/* One line, always. Reserving the row keeps a card with no badge the same
   height as one with a badge; nowrap keeps a card with two badges from
   growing a second row and pushing its title below its neighbours'. The
   badges themselves must not wrap either — a two-line "BEST-SELLER" makes the
   pill taller and reintroduces the same ragged row it was meant to prevent. */
.product-card .product-badges {
    min-height: 22px;
    flex-wrap: nowrap;
    overflow: hidden;
}

.product-card .product-badges .badge {
    white-space: nowrap;
    flex: 0 0 auto;
}

/* Two badges have to share a ~190px card on a tablet and ~150px on a phone,
   so they shrink before the card does. */
@media (max-width: 1023.98px) {
    .product-card .product-badges .badge {
        font-size: 9px;
        padding: 3px 6px;
        letter-spacing: 0;
    }
}

@media (max-width: 767.98px) {
    .product-card .product-title {
        font-size: 13px;
        line-height: 1.35;
    }
    .product-card .price-main {
        font-size: 16px;
    }
    .product-card .price-net,
    .product-card .tax {
        font-size: 11px;
    }
    .product-card .btn-product {
        padding: 10px 8px;
        font-size: 13px;
    }
    .product-card .badge {
        font-size: 9px;
        padding: 3px 5px;
    }
}

/* Cards in a row end up the same height, and the buy button always sits on the
   baseline instead of floating wherever the title happened to end. */
.main-product-grid > .product-card,
.product-slider .splide__slide > .product-card {
    display: flex;
    flex-direction: column;
    height: 100%;
}

.product-card .product-info {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
}

.product-card .product-info .product-price {
    margin-top: auto;
}

.product-slider .splide__list {
    align-items: stretch;
}

/* ------------------------------------------------------------ sliders --- */
/* Splide's own perPage breakpoints are set in includes/render_tile.php; these
   are the visual bits Splide does not control. */
.product-slider .splide__slide {
    height: auto;
}

/* Phone carousels: one full-bleed card with a slice of its neighbours either
   side (the perPage/focus/padding half lives in includes/render_tile.php), and
   the arrows moved out from over the card to a row under the track, aligned
   right. Splide renders .splide__arrows before the track and .splide__pagination
   after it, so the root becomes a flex column and `order` puts them in the
   reading order the design asks for. */
@media (max-width: 767.98px) {
    .product-slider,
    #recommended-slider {
        display: flex;
        flex-direction: column;
        /* Cancels the section gutter below so the track runs edge to edge. */
        margin-left: -16px;
        margin-right: -16px;
    }

    .product-slider .splide__track,
    #recommended-slider .splide__track { order: 1; }

    .product-slider .splide__arrows,
    #recommended-slider .splide__arrows {
        order: 2;
        display: flex;
        justify-content: flex-end;
        gap: 12px;
        padding: 14px 16px 0;
    }

    .product-slider .splide__pagination,
    #recommended-slider .splide__pagination {
        order: 3;
        position: static;
        padding: 12px 16px 0;
    }

    /* Static, not absolutely positioned over the slide. */
    .product-slider .splide__arrow,
    .blog-slider .splide__arrow,
    #recommended-slider .splide__arrow {
        position: static;
        transform: none;
        width: 40px;
        height: 40px;
        border-radius: 50%;
        background: #fff;
        border: 1px solid #e0e0e0;
        opacity: 1;
        box-shadow: none;
    }
    .product-slider .splide__arrow svg,
    #recommended-slider .splide__arrow svg {
        width: 14px;
        height: 14px;
        fill: var(--text-main, #1a1a1a);
    }
    .product-slider .splide__arrow:disabled { opacity: 0.35; }

    /* A blog card in a carousel is a full-width card, not one of the two
       half-width tiles the blog listing shows — style.css sizes both from the
       same ≤992 block, which is why the slider version came out at 12px type
       on a 100px image. Restated here at the size the design asks for. */
    /* The listing grid gets exactly the carousel card's proportions: one card
       per row on a phone, so a post has the same room to read whether it is met
       on the home page or on the blog index. style.css sizes the grid card for a
       two-up layout that no longer exists here. */
    .blog-slider .blog-card,
    .blog-grid .blog-card { border-radius: 20px; }
    .blog-slider .blog-card-image,
    .blog-grid .blog-card-image { height: 190px; }
    .blog-slider .blog-card-content,
    .blog-grid .blog-card-content { padding: 20px; }
    .blog-slider .blog-date,
    .blog-grid .blog-date { font-size: 13px; margin-bottom: 18px; }
    .blog-slider .blog-title,
    .blog-grid .blog-title {
        font-size: 17px;
        line-height: 1.4;
        margin-bottom: 14px;
        min-height: 2.8em;
        max-height: 2.8em;
    }
    .blog-slider .blog-excerpt,
    .blog-grid .blog-excerpt {
        font-size: 14px;
        line-height: 1.65;
        margin-bottom: 18px;
        -webkit-line-clamp: 3;
    }
    .blog-slider .blog-author,
    .blog-grid .blog-author { font-size: 14px; }

    /* One per row. Two half-width cards cannot hold a headline and an excerpt,
       which is what made the listing cards look clipped next to the home page's. */
    .blog-grid { grid-template-columns: 1fr !important; gap: 16px; }

    /* The round "read it" arrow, matching the product tile's. Listing only — in
       a carousel the whole card is already the target. */
    .blog-grid .blog-card__go { display: inline-flex; }
}

/* ------------------------------------------------------------- tables --- */
/* assets/js/responsive.js wraps every <table> that is not already inside a
   scroll container in <div class="table-scroll">. That covers admin-authored
   HTML in product descriptions and CMS pages, which is where the wide tables
   actually come from. */
.table-scroll {
    position: relative;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    max-width: 100%;
    /* Right-hand fade that appears only while there is more table to the
       right, so the user can tell it scrolls. Driven by background-attachment:
       local — no JS, no scroll listener. */
    background:
        linear-gradient(to right, #fff 30%, rgba(255, 255, 255, 0)) left center / 24px 100% no-repeat local,
        linear-gradient(to left, #fff 30%, rgba(255, 255, 255, 0)) right center / 24px 100% no-repeat local,
        radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .12), transparent) left center / 12px 100% no-repeat scroll,
        radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .12), transparent) right center / 12px 100% no-repeat scroll;
}

.table-scroll > table {
    margin: 0;
    min-width: 100%;
}

/* A table with enough rows to outrun the screen gets a capped height and a
   header that stays put while you scroll it. responsive.js adds .is-tall. */
@media (max-width: 1023.98px) {
    .table-scroll.is-tall {
        max-height: 70vh;
        overflow-y: auto;
    }
    .table-scroll.is-tall > table thead th {
        position: sticky;
        top: 0;
        z-index: 3;
        background: #f5f5f5;
        box-shadow: inset 0 -1px 0 #e2e2e2;
    }
    /* Wide table on a phone: keep the row's label column in view while the
       numbers scroll under it, otherwise a scrolled row is unreadable. */
    .table-scroll.is-wide > table th:first-child,
    .table-scroll.is-wide > table td:first-child {
        position: sticky;
        left: 0;
        z-index: 2;
        background: #fff;
        box-shadow: inset -1px 0 0 #ececec;
    }
    .table-scroll.is-wide > table thead th:first-child {
        z-index: 4;
        background: #f5f5f5;
    }
}

@media (max-width: 767.98px) {
    .table-scroll > table {
        font-size: 13px;
    }
    .table-scroll > table th,
    .table-scroll > table td {
        padding: 8px 10px;
        white-space: nowrap;
    }
    /* The label column is the one column that should wrap. */
    .table-scroll > table th:first-child,
    .table-scroll > table td:first-child {
        white-space: normal;
        min-width: 120px;
    }
}

/* --------------------------------------------------- horizontal scrollers */
/* Shared behaviour for any strip of pills/tabs that is too wide to fit:
   scroll it, snap it, and hide the scrollbar. */
.scroll-x {
    display: flex;
    overflow-x: auto;
    flex-wrap: nowrap;
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.scroll-x::-webkit-scrollbar { display: none; }
.scroll-x > * { scroll-snap-align: start; flex: 0 0 auto; }

/* ---------------------------------------------------------- touch size --- */
/* Anything tappable clears the 44px target on a touch screen. */
@media (hover: none) and (pointer: coarse) {
    button,
    .btn-product,
    .btn-primary,
    a.btn,
    .wishlist-btn,
    input[type="submit"],
    select {
        min-height: 44px;
    }
    /* iOS zooms the page when a focused input's text is under 16px. */
    input[type="text"], input[type="email"], input[type="tel"],
    input[type="number"], input[type="password"], input[type="search"],
    select, textarea {
        font-size: 16px;
    }
    /* Quantity steppers and other genuinely small controls opt out. */
    .qty-selector button,
    .qty-btn,
    .cart-row__qty button {
        min-height: 36px;
    }
}

/* ------------------------------------------------------- icon wrappers --- */
/* Every icon-only control on a phone is a square box with the glyph centred by
 * flexbox and no padding of its own.
 *
 * These controls were written at different times by different hands, and it
 * shows: some centred with line-height, some with padding, some with `text-align`
 * plus a nudge on the glyph. The result is a row of "round" buttons that are not
 * round, tap targets that are 34×41, and a chevron sitting two pixels above the
 * middle of its circle. On a desktop that is invisible; on a phone the icons are
 * side by side at the top of the screen and the mismatch is the first thing you
 * see.
 *
 * `aspect-ratio: 1` rather than a hard 40px: each control keeps whatever size
 * its own design gave it, and only the *other* axis is forced to match. Padding
 * goes to zero because a centred flex item does not need it — and because
 * padding was what made these boxes non-square in the first place.
 *
 * Deliberately excluded: .footer-social__link, whose width, height and both
 * paddings are set by the shop in admin ▸ Appearance ▸ Social icons. Overriding
 * them here would silently disable four settings. It gets the centring only.
 */
/* Applied at every width, not just on a phone. The tablet range (768–992) is
   where the store's filter panel and its close button live, and the desktop
   range has the same arrows and toggles — a rule that only ran below 768 left
   exactly the controls being complained about untouched. */
/* header */
.mnav__icon-wrap,
.cart-wrapper,
.msheet__close,
.mmenu__close,
.sbm__close,
.select__close-mobile-btn,
/* main content */
.sidebar__close,
.btn-page-nav--icon,
.logo-slider-toggle,
.close-region,
.copy-btn,
.wishlist-btn,
.btn-float-fav,
.splide__arrow,
.qty-selector button,
.co-stepper__btn,
.btn-icon,
.glb-close,
.glb-zoom-in,
.glb-zoom-out,
.glb-zoom-reset,
.hiw-close,
.cam__close,
.pw-field__toggle,
/* footer */
.scroll-top,
.back-to-top {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1 / 1;
    padding: 0;
    line-height: 1;
    flex: 0 0 auto;
}

/* The contact-bar arrows exist only below 992px — above it the phone numbers all
   fit and the row does not scroll. They are excluded from the list above because
   that block sets `display`, which turned `display: none` into a visible pair of
   arrows on every desktop, with the gap either side of the row to match. */
@media (max-width: 992px) {
    .top-bar-arrow {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        aspect-ratio: 1 / 1;
        padding: 0;
        line-height: 1;
        flex: 0 0 auto;
        min-height: 0;
    }
}

/* The promo bar's arrows are the exception: they are full-height strips down
   the left and right edges of the black band, not buttons with an icon in
   them. Squaring them would shrink a 36×95 target to 36×36 on the one
   control at the very top of every page. */
.promo-splide .splide__arrow { aspect-ratio: auto; }

/* The glyph itself must not re-introduce an offset. */
.sidebar__close > .icon,
.btn-page-nav--icon > i,
.logo-slider-toggle > i,
.close-region > i,
.copy-btn > svg,
.top-bar-arrow > .icon,
.mnav__icon-wrap > .icon,
.cart-wrapper > .icon,
.wishlist-btn > .icon,
.btn-float-fav > i,
.qty-selector button > i,
.hiw-close > i,
.cam__close > i,
.glb-close > i,
.glb-zoom-reset > i {
    display: block;
    margin: 0;
}

/* Admin-sized, so only the centring — see the note above. */
.footer-social__link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.footer-social__link .icon { display: block; margin: 0; }

/* min-height: 44px from the touch-target block would win over the square
   and stretch a 28px wishlist button into a 28×44 pill. Icon controls are
   exempt: they are sized by their own design, and the ones that matter
   (nav, arrows, close) are already at or above 40px. */
.wishlist-btn,
.mnav__icon-wrap,
.splide__arrow,
.btn-float-fav,
.glb-close,
.hiw-close,
.cam__close,
.pw-field__toggle { min-height: 0; }

/* ------------------------------------------------------------- header --- */
/* A phone was spending ~350px — most of the first screen — on chrome before
   any page content: promo bar, contact bar, search, logo. Everything below
   trims that to ~230px without removing anything. */
@media (max-width: 767.98px) {
    /* The promo slide wraps its text and then drops the button onto a third
       line. Keeping the chip inline and tightening the gaps costs no
       information and saves ~45px. */
    .promo-slide {
        gap: 8px;
        min-height: 40px;
        padding: 5px 4px;
        font-size: 12px;
        line-height: 1.3;
    }
    .promo-slide .promo-btn {
        padding: 4px 10px;
        font-size: 10px;
        letter-spacing: 0.06em;
    }
    .promo-splide { padding: 0 38px; }

    .middle-bar {
        padding-top: 8px;
        padding-bottom: 8px;
        gap: 8px;
    }
    .logo-container {
        padding: 4px 0;
    }
    .logo-container img {
        max-height: 42px;
        width: auto;
    }
    .search-container { margin: 0; }
}

/* ------------------------------------------------------------- footer --- */
/* The footer was 2,120px on a 390px phone — a third of the whole product page
   — because every trust badge and every link column dropped to one per row
   below 620px. These blocks are short enough to pair up; two columns halves
   the height and still leaves ~170px per cell. */
@media (max-width: 767.98px) {
    .footer-features {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 18px 14px;
    }
    .footer-features > * { min-width: 0; }
    .footer-features h4,
    .footer-features strong {
        font-size: 11px;
        line-height: 1.25;
    }
    .footer-features p {
        font-size: 12px;
        line-height: 1.35;
    }

    /* The address/company block needs the full width; the link lists pair. */
    .footer-main {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 24px 16px;
    }
    .footer-main .footer-col.company-col {
        grid-column: 1 / -1;
    }
    .footer-main .footer-col { min-width: 0; }
}

@media (max-width: 419.98px) {
    /* Below ~420 two link columns start truncating the longer menu labels. */
    .footer-main { grid-template-columns: minmax(0, 1fr); }
}

/* ------------------------------------------------------------ section --- */
/* Shared vertical rhythm for the home page bands and the generic content
   sections, replacing the fixed 60–80px margins that made a phone scroll
   through empty space. */
.product-slider-section,
.timeline-container,
.newsletter-container {
    margin-bottom: var(--space-section);
}

@media (max-width: 767.98px) {
    /* 16px gutter on the band itself. !important because index.php sets
       `padding: 80px 30px` inline on the categories band, which no amount of
       specificity in a stylesheet can beat. Carousels inside cancel it again
       with -16px margins so the track still runs edge to edge. */
    .product-slider-section,
    .timeline-container,
    .newsletter-container {
        margin-bottom: var(--space-section);
        padding-left: 16px !important;
        padding-right: 16px !important;
    }

    /* The promotional-products band is a grid, not a carousel, so its cards
       keep their own gutter inside the band's. */
    .product-slider-section--grid .main-product-grid {
        padding-left: 15px;
        padding-right: 15px;
    }
}

/* ------------------------------------------------- Google sign-in button --- */
/* Sits above the email/password form in the login panel. Google's brand
   guidelines ask for the wordmark colours on a white or dark button with the
   logo at its natural size, which is why the SVG is inline rather than a
   recoloured icon-font glyph. */
.btn-google{
    display:flex;
    align-items:center;
    justify-content:center;
    gap:10px;
    width:100%;
    padding:12px 16px;
    margin-bottom:16px;
    border:1px solid #dadce0;
    border-radius:8px;
    background:#fff;
    color:#3c4043;
    font-size:15px;
    font-weight:600;
    text-decoration:none;
    transition:background .15s,box-shadow .15s,border-color .15s;
}
.btn-google:hover{background:#f8f9fa;border-color:#d2d5d9;box-shadow:0 1px 3px rgba(60,64,67,.16)}
.btn-google svg{flex:0 0 auto}

.auth-divider{
    display:flex;
    align-items:center;
    gap:12px;
    margin:0 0 16px;
    color:var(--text-muted,#666);
    font-size:13px;
}
.auth-divider::before,
.auth-divider::after{content:"";flex:1 1 auto;height:1px;background:var(--border-light,#eaeaea)}

/* --------------------------------------------------- password show/hide --- */
/* The wrapper is added by assets/js/password-toggle.js around every
   input[type=password]; it must not change the field's own layout, so it is a
   block that fills the space the input used to occupy. */
.pw-field{
    position:relative;
    display:block;
    width:100%;
}
.pw-field > input{
    width:100%;
    /* Room for the button so a long password never runs underneath it. */
    padding-right:44px !important;
}
.pw-field__toggle{
    position:absolute;
    top:50%;
    right:6px;
    transform:translateY(-50%);
    display:flex;
    align-items:center;
    justify-content:center;
    width:34px;
    height:34px;
    padding:0;
    border:0;
    border-radius:6px;
    background:transparent;
    color:var(--text-muted,#666);
    cursor:pointer;
    transition:color .15s,background .15s;
}
.pw-field__toggle:hover{color:var(--text-main,#1a1a1a);background:rgba(0,0,0,.05)}
.pw-field__toggle svg{width:19px;height:19px;display:block}

/* On a touch screen the 44px minimum applies here too. */
@media (hover:none) and (pointer:coarse){
    .pw-field__toggle{width:40px;height:40px}
    .pw-field > input{padding-right:50px !important}
}

/* --------------------------------------------- standalone contact form --- */
/* /contact-form is the same form as the one at the bottom of the Contact page,
   but it is the only thing on the page — so it gets a readable measure and
   sits centred instead of stretching to the full 1200px wrapper. */
.contact-form-section--solo{
    max-width: 640px;
    margin: 0 auto;
    padding: 8px 0 var(--space-section);
}
.contact-form-section--solo h1{
    font-size: var(--fs-h2);
    line-height: 1.2;
    margin-bottom: 12px;
    text-align: center;
}
.contact-form-section--solo .form-intro{
    text-align: center;
    margin-bottom: 24px;
    color: var(--text-muted, #666);
}
.contact-form-alt{
    margin-top: 22px;
    font-size: 14px;
    text-align: center;
    color: var(--text-muted, #666);
}
.contact-form-alt a{ color: var(--text-main, #1a1a1a); font-weight: 600; }

/* ------------------------------------------------------ slider cursors --- */
/* Splide tags a carousel it can drag with `splide--draggable`, and adds
   `is-dragging` to the root for the duration of a drag. Keying off those two
   means every slider on the site gets the affordance from one rule — the
   banners, the product rows, the categories, the blog row, the product gallery
   and the mega-menu strip — and a slider with drag turned off correctly does
   NOT show a grab cursor.
   The per-slider copies of this that lived in single-product.php are removed. */
.splide--draggable .splide__track {
    cursor: grab;
}

/* While dragging, everything under the pointer shows the closed hand.
   `is-grabbing` is ours, set from assets/js/responsive.js on pointerdown —
   Splide 4.1.4 does not expose a dragging state of its own. (The rules that
   used to live in single-product.php keyed off `is-dragging`, a class this
   version never adds, so they had never once matched.)

   The descendant selector and !important are what beat the `cursor: pointer` on
   the product links inside the track — without them the cursor flickers between
   grabbing and pointer as slides move under a stationary mouse. */
.splide.is-grabbing .splide__track,
.splide.is-grabbing .splide__track * {
    cursor: grabbing !important;
}

/* A link inside a draggable track keeps its pointer at rest: the card is still
   a link, and the grab cursor belongs to the gaps around it. */
.splide--draggable .splide__track a,
.splide--draggable .splide__track button {
    cursor: pointer;
}

/* Dragging a carousel should not paint a text selection across the slides. */
.splide--draggable .splide__track {
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
}

/* Dragging an image is the browser's default; it fights the carousel drag. */
.splide--draggable .splide__track img {
    -webkit-user-drag: none;
    user-drag: none;
}

/* ==========================================================================
   Mobile product card — matched to the reference design (sklepsoltech.pl at
   373px / 2× DPR).
   ==========================================================================
   The reference puts ONE card per row on a phone, with a large product image,
   the price block on the left and a round arrow button on the right, instead of
   the two-up grid with a full-width "Add to cart" bar this build had.

   Two-up was a deliberate earlier choice — the comment in style.css argues a
   full-width card "wastes most of the screen" — so this is a reversal, not an
   oversight being corrected. It is what the reference does: at 373px a two-up
   card leaves ~150px for the title, which is why product names were being
   clipped after three words.
   ========================================================================== */
@media (max-width: 767.98px) {

    .main-product-grid {
        --per-row: 1 !important;
        gap: 16px;
    }

    .product-card {
        padding: 16px;
        border-radius: 16px;
    }

    /* The image gets the room a full-width card frees up. */
    .product-card .product-image-wrapper {
        height: 250px;
        margin-bottom: 12px;
    }

    .product-card .wishlist-btn {
        width: 28px;
        height: 28px;
    }

    /* The ≤1023 rule shrinks badges to 9px so two fit a narrow tablet column;
       a full-width card has room for the reference's 11px. */
    .product-card .product-badges .badge {
        font-size: 11px;
        padding: 4px 8px;
    }
    .product-card .product-badges { min-height: 26px; }

    /* Title, price and button on a two-column grid: the title spans both, the
       price sits bottom-left and the round button bottom-right, baselines
       aligned. */
    .product-card .product-info {
        display: grid;
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: end;
        column-gap: 12px;
    }

    .product-card .product-title {
        grid-column: 1 / -1;
        font-size: 17px;
        line-height: 1.3;
        font-weight: 700;
        margin-bottom: 12px;
    }

    .product-card .product-price {
        grid-column: 1;
        grid-row: 2;
        margin-top: 0;
    }

    .product-card .price-main   { font-size: 20px; font-weight: 700; }
    .product-card .price-main .tax,
    .product-card .price-net    { font-size: 15px; }
    /* "gross" carries the price's colour and "net" stays muted, so the two
       lines read as one figure each rather than four separate values. */
    .product-card .price-main .tax { color: inherit; font-weight: 500; }
    .product-card .price-old    { font-size: 13px; }
    .product-card .price-net    { color: var(--text-main, #1a1a1a); }
    .product-card .price-net .tax,
    .product-card .price-old del { color: var(--text-muted, #666); }

    /* "Add to cart" becomes the round plus. The label is kept in the DOM for
       screen readers and for the JS that reads it — it is hidden visually with
       font-size:0 rather than display:none, which would take it out of the
       accessibility tree along with the button's name. */
    .product-card .btn-product {
        grid-column: 2;
        grid-row: 2;
        width: 48px;
        height: 48px;
        min-height: 48px;
        padding: 0;
        border: 0;
        border-radius: 50%;
        font-size: 0;
        background-color: var(--primary, #f2ad21);
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%231a1a1a' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='12' y1='5' x2='12' y2='19'/%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3C/svg%3E");
        background-repeat: no-repeat;
        background-position: center;
        align-self: end;
        justify-self: end;
        flex: 0 0 auto;
    }

    /* While the add request is in flight the plus gives way to the spinner in
       style.css — otherwise the glyph sits under it, since that rule only
       clears the button's text colour. */
    .product-card .btn-product.is-loading {
        background-image: none;
    }

    /* Out of stock: the same round button carrying an arrow, which opens the
       product page (assets/js/script.js). "Notify us" needs the stock/alert
       controls that only the product page has, so on a phone the tile sends
       you there instead of putting a second modal over a 48px target. */
    .product-card .btn-product.js-notify {
        grid-column: 2;
        grid-row: 2;
        width: 48px;
        height: 48px;
        min-height: 48px;
        margin-top: 0;
        border-radius: 50%;
        font-size: 0;
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 24 24' fill='none' stroke='%231a1a1a' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='4' y1='12' x2='19' y2='12'/%3E%3Cpolyline points='13 6 19 12 13 18'/%3E%3C/svg%3E");
    }

    /* Section headers: centred title with the "see more" link under it on the
       right, as in the reference. */
    .slider-section-header {
        margin-top: 32px;
        margin-bottom: 20px;
        gap: 10px;
    }
    .slider-section-header .section-title { text-align: center; }
    .slider-section-header .see-more-link {
        align-self: flex-end;
        font-size: 15px;
        font-weight: 600;
    }
}

/* Below ~360px the round button plus a price line is tight; the price wraps
   rather than the button shrinking below its touch target. */
@media (max-width: 359.98px) {
    .product-card .price-main { font-size: 18px; }
    .product-card .product-image-wrapper { height: 210px; }
}

/* -------------------------------------------------- cart confirmations --- */
/* "Remove product" / "Remove all products" sit side by side in a dialog that
   is only as wide as a phone; at 14px/600 the longer label wrapped. */
@media (max-width: 767.98px) {
    .cart-confirm-modal .btn-confirm-cancel,
    .cart-confirm-modal .btn-confirm-ok {
        font-size: 13px;
        font-weight: 400;
    }
}

/* ============================================================ store page ===
   Category heading, the pager, and the filter sidebar-as-modal. The desktop
   rules for all three live in style.css; these only restate what a phone needs.
   ========================================================================== */
@media (max-width: 992px) {

    /* The heading is 28px Poppins on desktop, which on a 390px screen puts a
       two-word category on three lines. The blog index gets the same treatment
       and the same size — its title was breaking mid-word
       ("Installationsanleitun / g") — so the two listings read as one site. */
    .category-top-header h1,
    .blog-page-title {
        font-family: "Noto Sans", sans-serif;
        font-size: 22px;
        line-height: 1.25;
    }

    /* Centred like the store's, and the same component: see store_pager() in
       store.php and blog_pager() in blog.php. */
    .blog-top-pagination-row,
    .blog-footer-row {
        display: flex;
        justify-content: center;
    }

    /* ---- pager ----
       Centred: on desktop it sits at the right of a toolbar next to the sort
       control, and on a phone the toolbar stacks and there is nothing to sit
       beside. */
    .toolbar .pagination-controls,
    .bottom-toolbar .pagination-controls {
        margin-left: auto;
        margin-right: auto;
        justify-content: center;
    }
    .pagination-controls { gap: 10px; flex-wrap: nowrap; }
    .btn-page-nav--icon { width: 40px; min-width: 40px; height: 40px; }
    .page-input { width: 40px; height: 40px; font-size: 13.5px; }
    .page-label, .page-total { font-size: 13.5px; }

    /* ---- filters, as a full-screen panel ----
       The sidebar stays one piece of markup: here it is taken out of the flow
       and given the whole viewport, so a phone shows either the filters or the
       results and never half of each. */
    .category-page-container .sidebar {
        display: block;
        position: fixed;
        /* Stated side by side rather than as `inset`, and with the sizing
           reset alongside: the sidebar is a 280px column with its own width
           and margins on desktop, and every one of those has to be undone for
           it to fill the screen here. */
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        width: auto;
        height: auto;
        max-height: none;
        z-index: 10070;              /* over the fixed bottom bar (10040) */
        margin: 0;
        padding: 0 16px calc(24px + env(safe-area-inset-bottom, 0px));
        background: #fff;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        visibility: hidden;
        opacity: 0;
        /* Fade only. A translate on the open state has to finish before the
           panel is where the layout says it is, and anything that reads its
           position mid-flight — or a device that skips the transition — gets a
           panel offset by the slide distance. Position is not something worth
           animating for a control that covers the screen. */
        transition: opacity 0.2s ease, visibility 0.2s ease;
    }
    .category-page-container .sidebar.is-open {
        visibility: visible;
        opacity: 1;
        margin: 0;
    }

    /* Sticky title bar, so Close is reachable without scrolling back up
       through fifteen filter groups. */
    .sidebar__modal-head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        position: sticky;
        top: 0;
        z-index: 1;
        margin: 0 -16px 8px;
        padding: 14px 16px;
        background: #fff;
        border-bottom: 1px solid var(--border-light, #eaeaea);
    }
    .sidebar__modal-head h2 { font-size: 17px; font-weight: 600; margin: 0; }
    .sidebar__close {
        width: 36px;
        height: 36px;
        flex: 0 0 auto;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0;
        border: 1px solid var(--border-light, #eaeaea);
        border-radius: 50%;
        background: #fff;
        color: var(--text-main, #1a1a1a);
        cursor: pointer;
    }
    .sidebar__close .icon { width: 17px; height: 17px; }

    /* Product suggestions are not filters — they belong on the listing, not
       inside the panel you opened to narrow it. */
    .category-page-container .sidebar .sidebar-bestseller-widget,
    .category-page-container .sidebar .sidebar-recommended-widget { display: none; }

    /* The page behind the panel must not scroll with it. */
    body.filters-open { overflow: hidden; }

    /* The fixed bottom tab bar is site chrome, not part of the filters — with
       the panel covering the screen it would sit over the last filter group
       and read as belonging to it. */
    body.filters-open .mnav { display: none; }
}

/* Desktop keeps the sidebar as a column, so the panel chrome is not painted. */
@media (min-width: 992.02px) {
    .sidebar__modal-head { display: none; }
}
