.product-page-main {
    padding: 0; 
    width: 100%;
    max-width: 100%;

}

.opsim-product-single {
    display: flex;
    flex-direction: column;
    width: 100%;
}

.product-gallery {
    display: flex;
    flex-direction: column;
    width: 100%;
    margin: 0;
    padding: 0 10px; 
}

/* ── Product image carousel ────────────────────────────────────────────
 *
 * The large image area IS the carousel: one slide visible at a time, no
 * thumbnail rail underneath.
 *
 * The scroll/snap/swipe behaviour is entirely the browser's — a flex track
 * with `scroll-snap-type: x mandatory` and full-width slides. That gives
 * native momentum swipe on touch for free, and single-product.js only reads
 * the resulting scroll position (to print the counter and enable the two
 * buttons) and calls scrollTo() when a button is pressed.
 *
 *   .opsim-gallery          – frame; positioning context, clips the controls
 *   .opsim-gallery-track    – the scroller (the ONLY overflow in this component)
 *   .opsim-gallery-slide    – one full-width frame, stretched to the track
 *   .opsim-gallery-nav      – the two flat rectangular controls
 *   .opsim-gallery-counter  – 01 / 12
 */

.opsim-gallery {
    /*
     * The frame fills its column, full stop — no max-width and no max-height.
     *
     * A viewport-relative cap used to live here, and it was wrong twice over.
     * It shrank the frame on any short window (on a 725px-tall viewport it
     * resolved to a 429px frame inside a 531px column, standing the product
     * between two ~50px bands of white), and a `max-height` on an
     * `aspect-ratio` box is unusable anyway: Chrome resolves the image's
     * percentage height against the height the ratio ASKED for rather than
     * the smaller one the clamp GRANTED, so the image renders taller than its
     * own frame. The aspect ratio is now the only thing deciding the height,
     * so the frame, the image and the controls cannot disagree, and the
     * gallery has the same visual presence it had before the carousel.
     */
    position: relative;
    width: 100%;
    margin: 5px 0 0 0;
    /* A neutral ground while an image decodes, so the column does not flash
       white-on-white. */
    background: var(--opsim-bg-secondary, #F9FAFB);
    border: 1px solid var(--opsim-border);
    border-radius: var(--opsim-radius);
    /* Clips the controls and the counter to the frame: neither can ever be
       drawn outside the gallery box, at any width. */
    overflow: hidden;
    /* A flex item's default min-width is `auto` — "never shrink below my
       content". A 1000px-wide slide would then push the column past its share
       and give the whole PAGE a horizontal scrollbar. Explicit 0 keeps the
       overflow where it belongs: inside the track. */
    min-width: 0;
}

.opsim-gallery-track {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    /* mandatory, not proximity: a half-swipe must always land on one image. */
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    /* Keep a swipe that reaches the last image from turning into a browser
       back-gesture or a scroll of the page behind it. */
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    /* The counter and the arrows are the UI; the track's own bar is noise. */
    scrollbar-width: none;      /* Firefox */
    -ms-overflow-style: none;   /* IE 11 */
    width: 100%;
    min-width: 0;
    /*
     * The gallery frame follows the STORE-WIDE product image ratio —
     * Customizer → OPSIM - WooCommerce → Product Images → Product Image Ratio.
     *
     * `--fp-img-ratio` is that setting, printed once on `:root` by
     * customize_css() and resolved by OPSIM\Helpers\ProductImage. It is the
     * same variable the product cards read in product-card.css, so there is
     * exactly ONE source of truth for the shape of a product image anywhere in
     * the store, and changing it moves the shop grid and this gallery together.
     *
     * The shape itself is declared on `.opsim-gallery-slide` below, not here.
     * The track is a scroll container, so it takes its height from the slides;
     * every slide is the same width and ratio, so the frame does not resize as
     * the customer swipes between a 4:5 and a square shot.
     */
}

.opsim-gallery-track::-webkit-scrollbar {
    display: none;              /* Chrome / Safari / Edge */
}

.opsim-gallery-slide {
    /* Exactly one slide fills the track, whatever the track's width is —
       this is what makes "one image at a time" hold at every breakpoint
       without a single media query. */
    flex: 0 0 100%;
    width: 100%;
    min-width: 0;
    scroll-snap-align: center;
    /* Never let a fast flick skip past an image. */
    scroll-snap-stop: always;
    /*
     * The frame follows the STORE-WIDE product image ratio — Customizer →
     * OPSIM - WooCommerce → Product Images → Product Image Ratio.
     *
     * `--fp-img-ratio` is that setting, printed once on `:root` by
     * customize_css() and resolved by OPSIM\Helpers\ProductImage. It is the
     * SAME variable `.fp-product-image-link` reads in product-card.css, so one
     * source of truth shapes every product image in the store and changing it
     * moves the shop grid and this gallery together.
     *
     * It has to sit on the SLIDE, not on the track. The image inside is
     * absolutely positioned, and its `height: 100%` needs a containing block
     * with a definite height: when the slide only stretched to the track,
     * Chrome left that height indefinite and fell back to the image's own
     * intrinsic ratio from its width/height attributes — which silently
     * matched while the frame ratio came from the same photography, and broke
     * the moment the two could differ (a 16:9 frame held a 4:5-tall image).
     * An aspect-ratio here makes the height definite before the image is laid
     * out.
     *
     * Reserving the box also keeps the summary column from jumping as the
     * images decode.
     */
    aspect-ratio: var(--fp-img-ratio, var(--opsim-gallery-ratio, 1));

    /* A flex row so the image can take its height by stretching — see the
       image rule for why it must not take it as a percentage. */
    display: flex;
    /* Positioning context for anything overlaid on a slide later. */
    position: relative;
    box-sizing: border-box;
}

/*
 * "Natural / Original" — the one setting that cannot mean a fixed box.
 *
 * For a product card natural means "take the photo's own shape". The gallery
 * has a better answer to that question than any one image does:
 * `--opsim-gallery-ratio` is OPSIM\Helpers\GalleryFit\ratio(), the shape THIS
 * product's photography actually is, printed inline by single-content.php from
 * image metadata. Natural therefore resolves to the gallery's own proportions
 * rather than to `auto`, which would leave the slide no definite height at all
 * and collapse the frame around an absolutely positioned image.
 *
 * `.has-natural-ratio` is on <body> (OPSIM\Helpers\ProductImage\body_class()),
 * the same class the cards use — so this is still the one setting talking.
 */
.has-natural-ratio .opsim-gallery-slide {
    aspect-ratio: var(--opsim-gallery-ratio, 1);
}

/*
 * The image takes the slide's box by STRETCHING to it, not by asking for
 * `height: 100%`.
 *
 * That distinction is the whole bug behind "the gallery ignores the ratio". A
 * percentage height needs a containing block with a definite height, and an
 * absolutely positioned child of an `aspect-ratio` box does not get one in
 * Chrome — the declaration is dropped and the image falls back to its OWN
 * intrinsic ratio, taken from the width/height attributes WordPress prints.
 * While the frame ratio came from the same photography the two agreed and
 * nothing looked wrong; the moment the frame could differ (a 16:9 frame around
 * a 1000x1250 photo) the image rendered 684px tall inside a 308px frame.
 *
 * `align-self: stretch` on an in-flow flex item with an auto cross size is
 * resolved from the flex line, so there is no percentage to fail to resolve
 * and no intrinsic ratio to fall back to. min-height/min-width 0 stop the
 * image's intrinsic size from setting a floor the frame cannot go below.
 */
.opsim-gallery-image,
.opsim-gallery-video {
    width: 100%;
    height: auto;
    align-self: stretch;
    min-width: 0;
    min-height: 0;
    display: block;

    /*
     * FULL-BLEED: the image covers the whole frame, at every ratio.
     *
     * `contain` was here before and is what produced the empty bands either
     * side of a portrait photo in a square frame — it fits the WHOLE image
     * inside the box, so any difference between the photo's shape and the
     * chosen Product Image Ratio has to show up as blank ground. With 600x750
     * photos in a 1:1 frame that was 55px of nothing on each side at 1440px.
     *
     * `cover` scales to fill instead. Aspect ratio is preserved — the image is
     * never stretched — and the overflow is clipped equally on both sides by
     * `object-position: center`, so the product stays centred. The cost is
     * real and unavoidable: whatever does not fit is cut off. A 0.8 photo in a
     * 1:1 frame loses 20% of its height, 10% top and 10% bottom.
     *
     * The way to have BOTH full-bleed and nothing cut off is to pick a
     * Product Image Ratio that matches the photography (4:5 here, or
     * "Natural / Original"): the frame then equals the image's own shape and
     * cover and contain render identically, with no crop and no bands.
     *
     * To go back to letterboxing, this one declaration is the whole switch.
     */
    object-fit: cover;
    object-position: center;
}

/* ── Controls ──────────────────────────────────────────────────────────
 *
 * Flat rectangles, one hairline border, small radius. No shadow, no hover
 * state, no transition — deliberately, per the storefront's design language.
 * They sit INSIDE the frame, which is why the gallery clips its overflow. */
.opsim-gallery-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    z-index: 2;
    width: 34px;
    height: 46px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.92);
    border: 1px solid var(--opsim-border);
    border-radius: var(--opsim-radius);
    box-shadow: none;
    transition: none;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}

.opsim-gallery-nav svg {
    width: 16px;
    height: 16px;
    display: block;
    fill: none;
    stroke: var(--opsim-text-primary, #111827);
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.opsim-gallery-nav:disabled,
.opsim-gallery-nav[disabled] {
    opacity: 0.3;
    cursor: default;
    pointer-events: none;
}

.opsim-gallery-nav:focus-visible {
    /* Drawn inside, so the ring cannot spill over the frame's rounded edge. */
    outline: 2px solid var(--opsim-brand, #531628);
    outline-offset: -2px;
}

.opsim-gallery-prev { left: 10px; }
.opsim-gallery-next { right: 10px; }

/* ── Counter ───────────────────────────────────────────────────────────
 *
 * The total is rendered by PHP from the real gallery length; the current
 * index is rewritten by single-product.js as the track scrolls. */
.opsim-gallery-counter {
    position: absolute;
    right: 10px;
    bottom: 10px;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 4px;
    padding: 4px 9px;
    background: rgba(255, 255, 255, 0.92);
    border: 1px solid var(--opsim-border);
    border-radius: var(--opsim-radius);
    font-size: 0.72rem;
    font-weight: 500;
    letter-spacing: 0.06em;
    line-height: 1;
    color: var(--opsim-text-primary, #111827);
    /* Digits keep their column as 01 → 02 → 12, so the box never twitches. */
    font-variant-numeric: tabular-nums;
    pointer-events: none;
}

.opsim-gallery-counter-sep {
    opacity: 0.45;
}


/* ── Video slide ───────────────────────────────────────────────────────
 *
 * An ordinary `.opsim-gallery-slide` (same width, ratio and snap) whose poster
 * and <video> share one grid cell. Not absolute positioning: an absolutely
 * positioned child of an aspect-ratio box gets no definite height in Chrome
 * (see the image rule above). Both take their box from that rule.
 */
.opsim-gallery-slide--video {
    display: grid;
    grid-template: minmax(0, 1fr) / minmax(0, 1fr);
}

.opsim-gallery-poster,
.opsim-gallery-video {
    grid-area: 1 / 1;
}

/* The video is a picture, not a player: taps go to the toggle, and iOS never
   offers its full-screen affordance. */
.opsim-gallery-video {
    pointer-events: none;
}

/* The poster covers the video until its first frame is painted. Hiding it
   (and the play/pause icon swap) is done by single-product.js with the
   `hidden` attribute and inline styles, so no rule here depends on a class
   added at runtime. */
.opsim-gallery-poster {
    z-index: 1;
}

/* Video indicator and play/pause: the counter's chip, in the other corners. */
.opsim-gallery-video-badge,
.opsim-gallery-video-toggle {
    position: absolute;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255, 255, 255, 0.92);
    border: 1px solid var(--opsim-border);
    border-radius: var(--opsim-radius);
    color: var(--opsim-text-primary, #111827);
}

.opsim-gallery-video-badge {
    top: 10px;
    left: 10px;
    padding: 4px 7px;
    pointer-events: none;
}

.opsim-gallery-video-toggle {
    left: 10px;
    bottom: 10px;
    width: 30px;
    height: 30px;
    padding: 0;
    box-shadow: none;
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
}

.opsim-gallery-video-badge svg,
.opsim-gallery-video-toggle svg {
    width: 12px;
    height: 12px;
    display: block;
}

/* Paused shows the play glyph, playing shows pause: the `hidden` attribute on
   the icon wrappers, toggled by single-product.js. */
.opsim-gallery-video-toggle > [hidden] {
    display: none;
}

.opsim-gallery-video-toggle:focus-visible {
    outline: 2px solid var(--opsim-brand, #531628);
    outline-offset: -2px;
}

@media (prefers-reduced-motion: reduce) {
    .opsim-gallery-track {
        scroll-behavior: auto;
    }
}

.product-summary {
    display: flex;
    flex-direction: column;
    gap: 15px;
    padding: 10px 10px 30px 10px; 
    width: 100%;
    text-align: center; 
}

.product-title {
    font-size: 1.25rem;
    font-weight: 400;
    margin: 0;
    line-height: 1.3;
    color: #111827;
    text-transform: uppercase;
}

/*
 * Price row: current price, discount badge, struck-through regular price.
 *
 * The badge itself is .fp-price-discount, styled once in
 * template-parts/product/product-card.css and deliberately reused here rather
 * than restyled — it is the same badge the shop grid, the featured sections and
 * the recommended row show, and giving the product page its own copy of the
 * rule is how the two start to drift. That stylesheet is always present on a
 * product page (page_context()'s has_product_cards includes is_product, for the
 * recommended row below).
 *
 * align-items: center keeps the small badge optically centred against the much
 * larger price, at every viewport width, with no positioning of its own.
 */
.product-price-box {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    /* Inert while the row fits, which it does at every supported width. It only
       engages on a very narrow viewport with a long price, where wrapping is
       preferable to pushing the summary column into a horizontal scroll. */
    flex-wrap: wrap;
}

.price-main {
    display: flex;
    flex-direction: row;
    align-items: baseline;
    gap: 4px;
    font-size: 1.25rem;
    font-weight: 800;
    color: var(--opsim-brand, #531628);
}

.price-main .currency {
    font-size: 0.9rem;
    font-weight: 600;
}

.price-compare {
    font-size: 0.9rem;
    color: #687079; /* AA contrast; #9ca3af was 2.5:1. */
    text-decoration: line-through;
    font-weight: 400;
}

.opsim-product-short-description {
    color: #4b5563;
    font-size: 0.92rem;
    line-height: 1.6;
    margin: 2px 0 10px 0;
    text-align: left;
}

.opsim-product-short-description p {
    margin: 0 0 8px 0;
    line-height: 1.6;
}

.opsim-product-short-description p:last-child {
    margin-bottom: 0;
}

.opsim-product-short-description ul,
.opsim-product-short-description ol {
    margin: 0 0 8px 0;
    padding-left: 20px;
    text-align: left;
}

.opsim-product-short-description li {
    margin-bottom: 4px;
}

@media (min-width: 992px) {
    .product-page-main {
        max-width: 1300px;
        margin: 0 auto;
        padding-top: 40px; 
        padding-bottom: 40px;
    }

    .opsim-product-single {
        flex-direction: row;
        align-items: flex-start;
        gap: 40px; 
        padding: 0 40px;
    }

    .product-gallery {
        flex: 0 0 45%; 
        max-width: 45%;
        /* A flex item's default min-width is `auto`, i.e. "never shrink below
           my content". One unbreakable string — a long SKU, a pasted URL, a
           wide table in the description — is then enough to push a column past
           its share and give the whole page a horizontal scrollbar. Explicit 0
           lets these two columns honour their 45%/1 split no matter what they
           contain. This is the same fix on both columns; neither is optional. */
        min-width: 0;
        padding: 0; 
        position: -webkit-sticky; 
        position: sticky;
        top: calc(120px + var(--opsim-admin-bar, 0px)); 
        align-self: flex-start; /* CRITICAL for sticky in flex */
        z-index: 10;
    }

    /* The admin-bar offset is folded into the top: calc() above, so the old
     * hardcoded `.admin-bar .product-gallery { top: 152px }` override is gone. */

    .product-summary {
        flex: 1;
        min-width: 0; /* See .product-gallery above. */
        padding: 0; 
        text-align: left; 
    }

    .product-price-box {
        justify-content: flex-start; 
    }

    .product-title {
        font-size: 1.25rem; 
        margin-top: -4px; 
        text-align: left;
    }
}

.opsim-product-description,
.product-description-content {
    margin-top: 30px;
    padding-top: 24px;
    border-top: 1px solid #e5e7eb;
    color: #4b5563;
    font-size: 0.95rem;
    line-height: 1.7;
    text-align: left;
    width: 100%;
    clear: both;
    box-sizing: border-box;
    overflow-wrap: break-word;
    word-wrap: break-word;
}

.opsim-product-description::after,
.description-inner::after {
    content: "";
    display: table;
    clear: both;
}

.opsim-product-description p,
.product-description-content p {
    margin: 0 0 16px 0;
    line-height: 1.7;
    color: #4b5563;
}

.opsim-product-description p:last-child,
.product-description-content p:last-child {
    margin-bottom: 0;
}

.opsim-product-description h1,
.opsim-product-description h2,
.opsim-product-description h3,
.opsim-product-description h4,
.opsim-product-description h5,
.opsim-product-description h6 {
    color: #111827;
    font-weight: 700;
    line-height: 1.35;
    margin: 22px 0 10px 0;
}

.opsim-product-description h2 { font-size: 1.25rem; }
.opsim-product-description h3 { font-size: 1.1rem; }
.opsim-product-description h4 { font-size: 1rem; }

.opsim-product-description ul,
.opsim-product-description ol {
    margin: 0 0 16px 0;
    padding-left: 22px;
    color: #4b5563;
}

.opsim-product-description li {
    margin-bottom: 6px;
    line-height: 1.6;
}

.opsim-product-description blockquote {
    border-left: 3px solid var(--opsim-brand, #5E558F);
    padding: 12px 18px;
    margin: 16px 0;
    background: #f9fafb;
    border-radius: 0 var(--opsim-radius) var(--opsim-radius) 0;
    font-style: italic;
    color: #374151;
}

.opsim-product-description img,
.product-description-content img {
    max-width: 100% !important;
    height: auto !important;
    display: inline-block;
    vertical-align: middle;
    border-radius: var(--opsim-radius, 5px);
    box-sizing: border-box;
}

.opsim-product-description figure {
    margin: 0 0 16px 0;
    max-width: 100% !important;
}

.opsim-product-description figure img {
    width: 100%;
    height: auto;
}

.opsim-product-description figcaption {
    font-size: 0.8rem;
    color: #687079;
    text-align: center;
    margin-top: 6px;
}

.opsim-product-description .alignleft,
.product-description-content .alignleft {
    float: left;
    margin: 4px 20px 16px 0;
    max-width: 50%;
}

.opsim-product-description .alignright,
.product-description-content .alignright {
    float: right;
    margin: 4px 0 16px 20px;
    max-width: 50%;
}

.opsim-product-description .aligncenter,
.product-description-content .aligncenter {
    display: block;
    margin: 16px auto;
    text-align: center;
    clear: both;
}

.opsim-product-description .alignnone,
.product-description-content .alignnone {
    max-width: 100%;
}

.opsim-product-description .wp-block-media-text {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    align-items: center;
    margin: 20px 0;
}

.opsim-product-description .wp-block-media-text__media img {
    width: 100%;
    height: auto;
    display: block;
}

.opsim-product-description .wp-block-columns {
    display: flex;
    gap: 20px;
    margin: 20px 0;
    flex-wrap: nowrap;
}

.opsim-product-description .wp-block-column {
    flex: 1;
    min-width: 0;
}

.opsim-product-description .wp-block-gallery,
.opsim-product-description .gallery {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 12px;
    margin: 20px 0;
}

.opsim-product-description iframe,
.opsim-product-description video,
.opsim-product-description embed {
    max-width: 100% !important;
    border-radius: var(--opsim-radius, 5px);
}

.opsim-product-description .wp-block-embed {
    max-width: 100%;
    margin: 20px 0;
    overflow: hidden;
}

.opsim-product-description table {
    width: 100%;
    max-width: 100%;
    border-collapse: collapse;
    margin: 20px 0;
    display: block;
    overflow-x: auto;
}

.opsim-product-description th,
.opsim-product-description td {
    padding: 10px 14px;
    border: 1px solid #e5e7eb;
    text-align: left;
    font-size: 0.88rem;
}

.opsim-product-description th {
    background: #f9fafb;
    font-weight: 700;
}

@media (max-width: 767px) {
    .product-summary {
        padding: 6px 8px 16px 8px;
        gap: 10px;
        text-align: start !important;
        align-items: flex-start !important;
    }
    .product-title {
        font-size: 1.05rem !important;
        font-weight: 400 !important; 
        line-height: 1.35;
        letter-spacing: 0.3px;
        text-align: start !important;
        width: 100%;
    }
    .product-price-box {
        gap: 8px;
        justify-content: flex-start !important;
        width: 100%;
    }
    .price-main {
        font-size: 1.15rem !important;
        font-weight: 700 !important;
        text-align: start !important;
    }
    .price-main .currency {
        font-size: 0.8rem !important;
    }
    .price-compare {
        font-size: 0.82rem !important;
    }
    .opsim-product-short-description {
        font-size: 0.82rem !important;
        line-height: 1.5;
        margin: 2px 0 6px 0;
    }
    .opsim-product-short-description p {
        font-size: 0.82rem !important;
        line-height: 1.5;
        margin-bottom: 6px;
    }
    /* Compact controls — still large enough to be a comfortable touch target,
       and still inside the frame at 320px. */
    .opsim-gallery-nav {
        width: 28px;
        height: 40px;
    }

    .opsim-gallery-nav svg {
        width: 14px;
        height: 14px;
    }

    .opsim-gallery-prev { left: 8px; }
    .opsim-gallery-next { right: 8px; }

    .opsim-gallery-counter {
        right: 8px;
        bottom: 8px;
        padding: 3px 7px;
        font-size: 0.68rem;
    }

    .opsim-gallery-video-badge {
        top: 8px;
        left: 8px;
        padding: 3px 6px;
    }

    .opsim-gallery-video-toggle {
        left: 8px;
        bottom: 8px;
        width: 28px;
        height: 28px;
    }

    .opsim-product-description,
    .product-description-content {
        margin-top: 18px;
        padding-top: 14px;
        font-size: 0.85rem;
    }

    /* Unfloat all images on mobile so text flows naturally underneath */
    .opsim-product-description .alignleft,
    .opsim-product-description .alignright,
    .opsim-product-description [style*="float: left"],
    .opsim-product-description [style*="float:left"],
    .opsim-product-description [style*="float: right"],
    .opsim-product-description [style*="float:right"],
    .product-description-content .alignleft,
    .product-description-content .alignright {
        float: none !important;
        display: block !important;
        margin: 0 auto 16px auto !important;
        max-width: 100% !important;
        width: 100% !important;
    }

    .opsim-product-description img,
    .product-description-content img {
        width: 100% !important;
        max-width: 100% !important;
        height: auto !important;
        margin-left: auto !important;
        margin-right: auto !important;
        display: block !important;
    }

    .opsim-product-description .wp-block-media-text {
        grid-template-columns: 1fr !important;
        gap: 16px !important;
    }

    .opsim-product-description .wp-block-columns {
        flex-direction: column !important;
        gap: 16px !important;
    }

    .opsim-product-description .wp-block-gallery,
    .opsim-product-description .gallery {
        grid-template-columns: repeat(2, 1fr) !important;
        gap: 8px !important;
    }
}

.sticky-checkout-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background: #fff;
    padding: 8px 15px;
    box-shadow: 0 -10px 20px rgba(0, 0, 0, 0.05);
    z-index: 99999; 
    transform: translateY(110%);
    transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
    display: block;
}

.sticky-checkout-bar.is-visible {
    transform: translateY(0);
}

/* Base style lives in assets/css/base.css (button contract) */
.sticky-atc-btn {
    width: 100%; 
    -webkit-tap-highlight-color: transparent;
}

.sticky-btn-inner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: 100%;
}

.sticky-btn-inner .main-text {
    font-size: 14px;
    font-weight: 700;
    text-transform: uppercase;
    white-space: nowrap;
    letter-spacing: 0.5px;
}

.sticky-atc-btn .icon {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.sticky-atc-btn .icon svg {
    width: 18px;
    height: 18px;
    fill: currentColor;
    display: block;
}

@media (max-width: 767px) {
    .sticky-checkout-bar {
        padding: 6px 12px;
    }
    .sticky-atc-btn {
        min-height: 38px !important;
        padding: 7px 12px !important;
        border-radius: var(--opsim-radius) !important;
    }
    .sticky-btn-inner {
        gap: 8px;
    }
    .sticky-btn-inner .main-text {
        font-size: 12.5px !important;
        font-weight: 600 !important;
        letter-spacing: 0.3px;
    }
    .sticky-atc-btn .icon svg {
        width: 15px !important;
        height: 15px !important;
    }
}

@media (max-width: 380px) {
    /* Very small phones (320-380px): the controls shrink again so the two of
       them plus the counter never crowd the image. */
    .opsim-gallery-nav {
        width: 26px;
        height: 36px;
    }
    .sticky-btn-inner {
        gap: 6px;
    }
    .sticky-btn-inner .main-text {
        font-size: 11.5px !important;
    }
}

/* @media (min-width: 992px) {
    .sticky-checkout-bar {
        display: none !important;
    }
} */

.sticky-checkout-bar {
    border-top: 1px solid rgba(0,0,0,0.1);
}


/* Breadcrumb above the product title (template-parts/navigation/breadcrumb.php). */
.product-summary > .opsim-breadcrumb {
    margin-bottom: -5px;
}
.product-summary > .opsim-breadcrumb .opsim-breadcrumb-list {
    justify-content: center;
}
@media (min-width: 992px) {
    .product-summary > .opsim-breadcrumb .opsim-breadcrumb-list {
        justify-content: flex-start;
    }
}

/* Specifications — template-parts/product/specifications.php.
   Scoped under .opsim-product-specs: woocommerce-general styles bare tables. */
.opsim-product-specs {
    margin-top: 30px;
    padding-top: 24px;
    border-top: 1px solid #e5e7eb;
    width: 100%;
    text-align: left;
}
.opsim-product-specs .opsim-product-specs-title {
    margin: 0 0 12px;
    font-size: 1rem;
    font-weight: 700;
    color: var(--opsim-text-primary);
}
.opsim-product-specs .opsim-product-specs-table {
    width: 100%;
    margin: 0;
    border-collapse: collapse;
    font-size: 0.9rem;
}
.opsim-product-specs .opsim-product-specs-table th,
.opsim-product-specs .opsim-product-specs-table td {
    padding: 10px 0;
    border: 0;
    border-bottom: 1px solid #f0f1f3;
    background: none;
    vertical-align: top;
    text-align: start;
}
.opsim-product-specs .opsim-product-specs-table th {
    width: 40%;
    padding-inline-end: 16px;
    font-weight: 600;
    color: var(--opsim-text-primary);
}
.opsim-product-specs .opsim-product-specs-table td {
    color: var(--opsim-text-secondary);
    overflow-wrap: anywhere;
}

/* ══════════════════════════════════════════════════════════════════════
   Star rating + the rating line under the product title.

   Moved here from reviews/reviews.css, unchanged: the line is above the
   fold, and reviews.css is loaded non-blocking (see defer_style() in
   inc/performance/assets.php). The reviews section below uses the same stars.
   ══════════════════════════════════════════════════════════════════════ */

/*
 * Star tokens belong to every root that can contain a rating — NOT to the
 * reviews section alone.
 *
 * The rating line under the product title is rendered by single-content.php
 * into .product-summary, which is nowhere inside .opsim-reviews and so
 * inherits nothing from it. While these three lived on .opsim-reviews only,
 * `width: var(--opsim-star-size)` on that line resolved to nothing, the
 * glyphs collapsed to zero, and the absolutely positioned filled layer
 * printed straight over the "1 avis" text next to it — the overlap that
 * looked like a black smear against the count.
 *
 * Every var() that reads them also carries a literal fallback below, so a
 * stray .opsim-stars dropped into a third context still renders at a sane
 * size instead of collapsing again.
 */
.opsim-reviews,
.opsim-product-rating {
    --opsim-star-size: 16px;
    --opsim-star-color: var(--opsim-brand, #111827);
    --opsim-star-empty: rgba(var(--opsim-brand-rgb, 17, 24, 39), 0.18);
}

/* ── Read-only star rating ─────────────────────────────────────────────
   Two identical five-glyph rows stacked on top of each other; the filled
   one is clipped to --opsim-stars-fill percent. That is what renders a
   4.3 as 4.3 without a half-star asset or a third icon state.

   The filled layer is absolutely positioned, so the empty layer alone
   defines the element's size and the two can never disagree in width.   */

.opsim-stars {
    position: relative;
    display: inline-flex;
    /* Never squashed by a flex parent, whatever shares the row with it. */
    flex: none;
    line-height: 0;
    vertical-align: middle;
    /* The glyph row has no text baseline of its own (line-height: 0), so in a
       baseline-aligned flex row it would hang off the bottom. Centring it
       against the line box keeps the stars optically level with the name and
       the date next to them. */
    align-self: center;
}

.opsim-stars-layer {
    display: inline-flex;
    line-height: 0;
}

.opsim-stars-empty {
    color: var(--opsim-star-empty, rgba(17, 24, 39, 0.18));
}

.opsim-stars-full {
    position: absolute;
    top: 0;
    left: 0;
    width: var(--opsim-stars-fill, 0%);
    overflow: hidden;
    color: var(--opsim-star-color, #111827);
}

.opsim-star {
    width: var(--opsim-star-size, 16px);
    height: var(--opsim-star-size, 16px);
    /* Spacing lives INSIDE each star's box, never as a flex gap on the row.
       A gap makes the row wider than 5 x star, so `width: 80%` on the filled
       layer stops landing on the 4th star's edge and cuts a sliver into the
       5th — the filled and empty rows then disagree by a fraction of a star
       and the rating reads as misaligned. With uniform boxes and no gap the
       row is exactly five units wide, so every fifth of it is exactly one
       star and any rating lands on a real boundary.

       content-box is required: base.css sets border-box globally, which
       would otherwise fold the padding back into --opsim-star-size and
       shrink the glyph instead of spacing it. */
    padding: 0 1px;
    box-sizing: content-box;
    flex: none;
    fill: currentColor;
    display: block;
}

.opsim-stars-sm { --opsim-star-size: 14px; }
.opsim-stars-lg { --opsim-star-size: 20px; }

/* ── Rating line under the product title ──────────────────────────────── */

.opsim-product-rating {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Shrinkable, so a long empty-state label ("Soyez le premier…") wraps
       inside the summary column instead of widening it. */
    min-width: 0;
}

.opsim-product-rating-link {
    display: inline-flex;
    align-items: center;
    /* Stars and the count stay side by side for as long as they fit, and
       wrap rather than overflow when they do not. The row gap is small
       because a wrapped count belongs tight under its stars, not 8px adrift. */
    flex-wrap: wrap;
    column-gap: 8px;
    row-gap: 2px;
    min-width: 0;
    font-size: 0.8125rem;
    line-height: 1.4;
    color: var(--opsim-text-secondary);
    text-decoration: none;
}

.opsim-product-rating-count {
    /* Underlined at rest rather than on hover: it is the only cue that this
       line is a link to the reviews below, and the section carries no hover
       or motion effects at all. */
    text-decoration: underline;
    text-underline-offset: 2px;
    min-width: 0;
    overflow-wrap: anywhere;
}

/* Keyboard focus is not a hover effect — it is the one state a keyboard user
   has, so it stays. */
.opsim-product-rating-link:focus-visible {
    outline: 2px solid var(--opsim-brand);
    outline-offset: 2px;
}

@media (min-width: 992px) {
    /* Matches .product-title / .product-price-box, which both drop their
       centring at this width. */
    .opsim-product-rating {
        justify-content: flex-start;
    }
}
