/* --------------------------------------------------------------
   MBPOS LIGHTBOX  (v1.2.3)
   Self-contained modal — replaces BricksExtras xdynamiclightbox.

   USAGE
   ─────
   Base modal:        class="mbpos-lightbox"
   Product detail:    class="mbpos-lightbox mbpos-lightbox--product"
   Checkout:          class="mbpos-lightbox mbpos-lightbox--checkout"
-------------------------------------------------------------- */

/* ── Backdrop ─────────────────────────────────────────────── */
.mbpos-lightbox {
    display: flex;
    align-items: center;
    justify-content: center;

    position: fixed;
    inset: 0;
    z-index: 99999;

    background: rgba(0, 0, 0, 0.55);

    opacity: 0;
    pointer-events: none;
    transition: opacity 0.22s ease;
}

.mbpos-lightbox.is-open {
    opacity: 1;
    pointer-events: auto;
}

/* ── Panel (the white card) ───────────────────────────────── */
.mbpos-lightbox__panel {
    position: relative;
    background: var(--mbpos-color-surface, #fff);
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.18);

    width: 100%;
    overflow: hidden;

    /* Entrance animation: scale + slide — matches BricksExtras
       start_scale:.95  start_translate_y:5rem settings */
    transform: translateY(50px) scale(0.95);
    transition: transform 0.25s ease, opacity 0.25s ease;
    opacity: 0;
}

.mbpos-lightbox.is-open .mbpos-lightbox__panel {
    transform: translateY(0) scale(1);
    opacity: 1;
}

/* ta2: the two bare-class close-button blocks that lived here are GONE — they
   were 2 of the SIX competing owners. The single owner is the [data-mbpos]
   block further down this sheet. */

/* ── Bricks Element Builder Preview ──────────────────────── */
/* Styles for the mbposlightbox element's builder Vue template.
   These only load in the builder iframe. */

.mbpos-lightbox-builder-wrapper {
    display: block;
}

/* Placeholder — shown when hidden in builder */
.mbpos-lightbox-builder-placeholder {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    border: 2px dashed rgba(0,0,0,0.15);
    border-radius: 8px;
    color: rgba(0,0,0,0.35);
    font-size: 12px;
    background: rgba(0,0,0,0.02);
    min-height: 36px;
    user-select: none;
}

/* Container — position:fixed makes it overlay the builder canvas
   exactly like BricksExtras' .glightbox-container did.
   v-show controls visibility without mount/unmount transitions. */
.mbpos-lightbox-builder-container {
    position: fixed;
    inset: 0;
    z-index: 99999;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.mbpos-lightbox-builder-backdrop {
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    pointer-events: auto;
}

/* Builder panel — deliberately NOT .mbpos-lightbox__panel so
   frontend opacity/transform/transition CSS never applies here */
.mbpos-lightbox-builder-panel {
    position: relative;
    z-index: 1;
    background: var(--mbpos-color-surface, #fff);
    border-radius: var(--mbpos-radius-s, 8px);
    box-shadow: 0 24px 64px rgba(0,0,0,0.18);
    width: 90%;
    max-width: 700px;
    max-height: 90vh;
    overflow-y: auto;
    pointer-events: auto;
}

.mbpos-lightbox-builder-header {
    display: flex;
    justify-content: flex-end;
    padding: 8px;
    border-bottom: 1px solid var(--mbpos-color-border-soft, #f0f0f0);
}

/* ── Bricks Builder visibility ────────────────────────────── */
/* When toggled visible in the builder, show the modal in-place
   so all child elements are editable. The <template> tag content
   is made display:block so EPO shortcode placeholders are visible.
   These rules ONLY apply inside the Bricks builder iframe —
   they have zero effect on the frontend. */

/* Bricks builder uses .brx-body.iframe on the body inside the builder iframe */
.brx-body.iframe .mbpos-lightbox {
    position: relative !important;
    inset: auto !important;
    opacity: 1 !important;
    pointer-events: auto !important;
    display: block !important;
    background: rgba(0, 0, 0, 0.04) !important;
    border: 2px dashed rgba(0, 0, 0, 0.15) !important;
    border-radius: var(--mbpos-radius-s, 8px) !important;
    z-index: auto !important;
    margin-top: var(--mbpos-space-xs, 8px) !important;
}

.brx-body.iframe .mbpos-lightbox__panel {
    position: relative !important;
    transform: none !important;
    opacity: 1 !important;
    max-width: 100% !important;
    max-height: none !important;
    box-shadow: none !important;
    border-radius: var(--mbpos-radius-s, 8px) !important;
}

/* Make template children visible in builder */
.brx-body.iframe template.mbpos-lightbox__content {
    display: block !important;
}

.brx-body.iframe .mbpos-lightbox__content {
    display: block !important;
}

/* Checkout modal — full width in builder since it sits in the sidebar */
.brx-body.iframe .mbpos-lightbox--checkout .mbpos-lightbox__panel {
    max-width: 100% !important;
}

/* ── Body scroll lock ─────────────────────────────────────── */
body.mbpos-lightbox-open {
    overflow: hidden;
}

/* [lock1] The shared scroll lock's own class — set and cleared ONLY by
   assets/js/pos-scroll-lock.js, held while any overlay owner holds it.
   overflow:hidden handles Android/Chrome; iOS Safari ignores it, which is
   why the module also blocks touchmove. Kept separate from the visual
   state classes below so releasing a lock can never disturb a component's
   own appearance. */
body.mbpos-scroll-locked {
    overflow: hidden;
}

/* [lock1a] The other half of the chaining fix, and the half JS cannot do well.
   The touchstart probe in pos-scroll-lock.js catches "this region has nothing
   to scroll". This catches "this region scrolled to its end and the gesture
   kept going" — boundary chaining, including during momentum, where a
   touchmove handler has already stopped firing.
   THIS SELECTOR LIST MUST MATCH SCROLLABLE in pos-scroll-lock.js exactly;
   the lock1a fence (§7) fails if the two drift apart. */
.mbpos-overview-body,
.mbpos-lightbox__panel,
#mb-pwp-modal,
.mbpos-pickup-modal,
.mbpos-receipt-modal__panel {
    overscroll-behavior: contain;
}

/* Cart overview drawer — mobile scroll lock.
   overflow:hidden on body handles Android/Chrome.
   iOS Safari is handled via JS touchmove blocking — no position:fixed
   needed, which avoids the scroll-jump repaint on open/close. */
body.mbpos-drawer-open {
    overflow: hidden;
}

/* =============================================================
   VARIANT: PRODUCT DETAIL  (.mbpos-lightbox--product)
   Matches BricksExtras global class 'products-detail' (eajyia):
     width: 70rem, max-height: 90vh, overflow: hidden,
     border-radius: var(--mbpos-radius-s), content padding: 0,
     .mbpos-products-detail__footer is position:absolute bottom:0
     so the panel must be position:relative with a set height.
   ============================================================= */
.mbpos-lightbox--product .mbpos-lightbox__panel {
    max-width: 700px;
    height: 90vh;          /* fixed height so absolute footer anchors */
    max-height: 90vh;
    border-radius: var(--mbpos-radius-s, 8px);
    overflow: hidden;      /* clip the absolute footer at border-radius */
}

/* Content area — scrollable, padding 0 to match original */
.mbpos-lightbox--product .mbpos-lightbox__content {
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    padding: 0;
    /* leave room for the absolute footer (~8rem tall) */
    padding-bottom: 80px;
}

/* The absolute footer must anchor to the panel, not the content */
.mbpos-lightbox--product .mbpos-products-detail__footer {
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
}

/* EPO radio/checkbox styles moved to Bricks Global CSS under .mbpos-product-modal */

/* Mobile landscape — 80vw, 70vh */
@media (max-width: 900px) and (orientation: landscape) {
    .mbpos-lightbox--product .mbpos-lightbox__panel {
        max-width: 80vw;
        height: 70vh;
        max-height: 70vh;
    }
}

/* Mobile portrait — full-width sheet from bottom */
@media (max-width: 600px) {
    .mbpos-lightbox--product {
        align-items: flex-end;
    }

    .mbpos-lightbox--product .mbpos-lightbox__panel {
        max-width: 100%;
        height: 92dvh;
        max-height: 92dvh;
        border-radius: 16px 16px 0 0;
        transform: translateY(100%) scale(1);
    }

    .mbpos-lightbox--product.is-open .mbpos-lightbox__panel {
        transform: translateY(0) scale(1);
    }
}

/* =============================================================
   VARIANT: CHECKOUT  (.mbpos-lightbox--checkout)
   Matches BricksExtras global class 'mbpos-overview-footer__checkout--form':
     max-height: 80vh, border-radius: var(--radius-m),
     content-padding: var(--space-2xs), mobile landscape: 90vw
   ============================================================= */
.mbpos-lightbox--checkout .mbpos-lightbox__panel {
    max-width: 560px;
    min-height: 200px;
    max-height: 80vh;
    border-radius: var(--mbpos-radius, 12px);
    overflow-y: auto;
    overscroll-behavior: contain;
}
.mbpos-lightbox--checkout .mbpos-lightbox__content {
    padding: var(--mbpos-space-2xs, 8px);
}

/* Mobile landscape */
@media (max-width: 900px) and (orientation: landscape) {
    .mbpos-lightbox--checkout .mbpos-lightbox__panel {
        max-width: 90vw;
    }
}

/* Mobile portrait — sheet from bottom */
@media (max-width: 600px) {
    .mbpos-lightbox--checkout {
        align-items: flex-end;
    }

    .mbpos-lightbox--checkout .mbpos-lightbox__panel {
        max-width: 100%;
        max-height: 92dvh;
        border-radius: 16px 16px 0 0;
        transform: translateY(100%) scale(1);
    }

    .mbpos-lightbox--checkout.is-open .mbpos-lightbox__panel {
        transform: translateY(0) scale(1);
    }
}

/* =============================================================
   MBPOS CART ITEM — Core layout styles
   Injected by plugin so these work on any site without needing
   Bricks global class definitions.
   ============================================================= */

.mbpos-cart-list {
    list-style: none;
    margin: 0 0 0 0;
    padding-left: 0;
}

.mbpos-cart-item {
    display: block;
    padding: var(--mbpos-space-2xs, 8.2px);
    background: var(--mbpos-color-surface, #ffffff);
    /* sk2: cart nodes can be detached to <body>, so text colour must be set
       here, not inherited — otherwise item titles take the SITE theme's body
       colour and go dark-on-dark under a dark POS theme. */
    color: var(--mbpos-color-text, #1D1C1B);
    border-radius: var(--mbpos-radius-btn, 8px);   /* st87: Bricks cart rows are 8px */
}

.mbpos-cart-item__head {
    display: flex;
    align-items: flex-start;
    column-gap: var(--mbpos-space-2xs, 8.2px);
    font-size: var(--mbpos-font-size-s, 14.2px);
}

.mbpos-cart-item__thumb {
    width: 75px;
    height: 75px;
    object-fit: cover;
    border-radius: var(--mbpos-radius-s, 6px);
    flex-shrink: 0;
}

.mbpos-cart-item__detail {
    display: flex;
    width: 100%;
    justify-content: space-between;
    align-items: flex-start;
}

.mbpos-cart-item__title {
    font-weight: 600;
    font-size: calc(var(--mbpos-font-size-s, 14.2px) + 2px);
    max-width: 175px;
    /* sk2b: explicit, not inherited — titles are h4s, and site themes ship
       `h4 { color: … }` rules that beat inheritance from .mbpos-cart-item,
       which left titles dark-on-dark under a dark POS theme. */
    color: var(--mbpos-color-heading, #1D1C1B);
}

.mbpos-cart-item__price {
    font-weight: 600;
    font-size: calc(var(--mbpos-font-size-s, 14.2px) + 2px);
    white-space: nowrap;
}

.mbpos-cart-item__footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: var(--mbpos-space-xs, 10.2px);
}

/* ta1: this sheet's DUPLICATE .mbpos-cart-item__qty / --btn / --input rules are
   gone. They collided with mbpos-pos-layout.css (same classes, different rules)
   and enqueue order decided the winner. Single owner now lives in
   mbpos-pos-layout.css. This sheet also declares no tokens, so its
   fallback-less var()s computed to auto/initial wherever it loaded alone. */

.mbpos-cart-item__remove {
    display: flex;
    align-items: center;
}

.mbpos-cart-item__remove--btn a {
    color: var(--mbpos-color-danger, rgba(250,67,98,0.8));
}

/* State classes */
.mbpos-cart-item.is-new {
    animation: mbpos-fade-in-up 0.35s ease forwards;
}

/* Gift item — styling for the hoisted-to-body case (drawer on mobile).
   The main gift rules live in mbpos-pos-layout.css with ID-scoped specificity;
   these cover the drawer when it's moved outside #mbpos-pos-layout. */
.mbpos-cart-item--gift,
.mbpos-cart-item[data-is-auto-gift="1"],
.mbpos-cart-item[data-is-loyalty-gift="1"] {
    background: var(--mbpos-gift-bg, var(--mbpos-color-success-subtle, #f0fdf4));
    border: 1.5px solid var(--mbpos-gift-border-color, var(--mbpos-color-success, #22c55e));
    border-radius: var(--mbpos-radius-s, 6px);
    padding: var(--mbpos-gift-padding, var(--mbpos-space-2xs, 5px)) var(--mbpos-gift-padding-h, var(--mbpos-space-xs, 7.5px));
    margin: var(--mbpos-space-3xs, 2.5px) 0;
}
.mbpos-cart-item--gift .mbpos-cart-item__title,
.mbpos-cart-item[data-is-auto-gift="1"] .mbpos-cart-item__title,
.mbpos-cart-item[data-is-loyalty-gift="1"] .mbpos-cart-item__title {
    color: var(--mbpos-color-success-strong, #15803d);
}
.mbpos-cart-item--gift.is-new {
    animation: mbpos-gift-pop 0.45s ease forwards;
}
@keyframes mbpos-gift-pop {
    0%   { box-shadow: 0 0 0 0 rgba(22,163,74,0.35); }
    50%  { box-shadow: 0 0 0 6px rgba(22,163,74,0.12); }
    100% { box-shadow: 0 0 0 0 transparent; }
}

.mbpos-cart-item.on-wait {
    position: relative;
    opacity: 0.4;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

.mbpos-cart-item.on-wait::after {
    content: '';
    position: absolute;
    inset: 0;
    background: rgba(255,255,255,0.6);
    border-radius: inherit;
    z-index: 10;
}

.mbpos-cart-item.on-wait::before {
    content: '';
    position: absolute;
    top: 50%; left: 50%;
    z-index: 11;
    width: 20px; height: 20px;
    margin: -10px 0 0 -10px;
    border: 2px solid rgba(0,0,0,0.15);
    border-top-color: #000;
    border-radius: 50%;
    animation: mbpos-spin 0.6s linear infinite;
}

@keyframes mbpos-fade-in-up {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
}


/* ── Plugin-injected modal description (se8; moved here ca2) ─────────────
   The renderer injects this <p> because the Bricks template has no element
   for it. Styling lives in this frontend-loaded sheet (global-style.css is
   admin-only). The modal content has padding:0, so the description needs
   its own side gutter to line up with the title/options instead of sitting
   flush-left. --mbpos-modal-gutter overrides the gutter per template; the
   --space-xs fallback matches the EPO group's own horizontal padding. */
.mbpos-products-detail__autodesc {
	margin: 0 0 14px;
	padding-left: var(--mbpos-modal-gutter, var(--mbpos-space-xs, 10px));
	padding-right: var(--mbpos-modal-gutter, var(--mbpos-space-xs, 10px));
	font-size: 14px;
	line-height: 1.5;
	color: var(--mbpos-color-text-muted, #666);
}

/* ── Star ratings (fd5) ────────────────────────────────────────────────
   ★★★★☆ (n): a gold filled layer is clipped by width to the average %, over
   a grey empty layer, so partial stars render exactly. Used on cards and in
   the modal; injected right after the product title. */
.mbpos-rating {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	margin: 2px 0 6px;
	font-size: 14px;
	line-height: 1;
	vertical-align: middle;
}
.mbpos-rating__stars {
	position: relative;
	display: inline-block;
	white-space: nowrap;
	font-size: 15px;
	letter-spacing: 1px;
}
.mbpos-rating__empty { color: var(--mbpos-star-empty, #d8d8d8); }
.mbpos-rating__fill {
	position: absolute;
	top: 0;
	left: 0;
	overflow: hidden;
	color: var(--mbpos-star, #f5a623);
	white-space: nowrap;
}
.mbpos-rating__count {
	font-size: 12px;
	color: var(--mbpos-star-count, #888);
}

/* fd6: in the modal the rating is wrapped in a link to the product's #reviews
   section (new tab). Keep it looking like the plain rating — no underline, no
   link colour — with a subtle hover affordance + a focus ring for keyboards. */
.mbpos-rating-link {
	display: inline-flex;
	text-decoration: none;
	color: inherit;
	border-radius: 4px;
}
.mbpos-rating-link:hover .mbpos-rating__count {
	color: var(--mbpos-star, #f5a623);
	text-decoration: underline;
}
.mbpos-rating-link:focus-visible {
	outline: 2px solid var(--mbpos-star, #f5a623);
	outline-offset: 2px;
}

/* ta5: the panel takes focus on open (see mbpos-lightbox.js) so that Escape,
   the Tab-trap and screen readers all work. It is a tabindex="-1" container, not
   a control -- it must never paint a ring. The close button's :focus-visible ring
   below is deliberate and stays: a keyboard user who Tabs to it SHOULD see it. */
[data-mbpos] .mbpos-lightbox__panel:focus,
[data-mbpos] .mbpos-lightbox__panel:focus-visible {
	outline: none;
}

/* ═══════════════════════════════════════════════════════════════════════
   LIGHTBOX CLOSE BUTTON — ONE OWNER, ONE STATE  [ta3]
   ───────────────────────────────────────────────────────────────────────
   ta3 REPLACES ta2's two-state chip (dark chip + JS luminance detection
   flipping to .is-on-light). That was over-engineered and it failed in the
   two ways it was always going to:

     · DELAY — detection cannot run until the image has decoded, so the
       modal painted first and the button re-tinted afterwards. Visible lag.
       Unfixable without removing the detection: you cannot measure pixels
       that do not exist yet.
     · TWO STATES = TWO WAYS TO BE WRONG — a mid-tone image tripped the
       light chip; a black image left a dark chip on a dark backdrop.

   One state cannot be wrong. Solid white circle, near-black glyph, always.
   Legible on black. On white, the hairline ring + drop shadow carry it.
   No JS, no timing, no failure path. (This is what YouTube and Google
   Photos ship, for exactly this reason.)

   backdrop-filter is also gone — it cost a paint and bought nothing once
   the fill became opaque.

   SPECIFICITY: (0,2,0) base / (0,3,0) states — beats the :where() reset's
   (0,0,1)/(0,1,1) and Elementor's kit rules. No !important.
   Every state re-declares the FULL `border` shorthand, because the reset
   removes borders in :hover/:focus/:active with the shorthand (wiping
   style AND width) — restoring only border-color is what made the border
   blink in and out before ta2.

   The three --mbpos-close-btn-* tokens stay overridable from the
   Appearance tab, but ALL THREE now have sane defaults, so setting one in
   isolation can no longer produce an invisible glyph.

   cb1: also covers .mbpos-pickup-modal__close-btn. Previously the pickup
   modal template (templates/pickup-modal.php) carried its own copy of
   this rule inline, and picked up a second, stale duplicate further down
   the same file (st91) that won the cascade and silently diverged from
   this one. Single definition here removes the possibility of that drift
   — the pickup modal template no longer defines this selector at all.
   Safe to share: mbpos-lightbox.css is already wp_enqueue_style()'d in
   <head> on every page the pickup modal can render on (the modal needs
   the product lightbox too), so there's no load-order or extra-request
   cost to moving this out of the modal's inline <style> block.
   ═══════════════════════════════════════════════════════════════════════ */

[data-mbpos] .mbpos-lightbox__close,
[data-mbpos] .mbpos-pickup-modal__close-btn {
	position:        absolute;
	top:             12px;
	right:           12px;
	z-index:         10;
	display:         inline-flex;
	align-items:     center;
	justify-content: center;
	box-sizing:      border-box;
	width:           36px;
	height:          36px;
	padding:         0;
	margin:          0;
	font:            inherit;
	font-size:       20px;
	line-height:     1;
	cursor:          pointer;
	border-radius:   999px;

	/* Solid opaque chip + contrasting glyph. The ring and shadow are what make
	   it read when the image behind it is ALSO white.
	   ta4: all three tokens are ALWAYS emitted by the Appearance tab (bg=surface,
	   fg=text, ring=border by default), so these var() fallbacks are a safety net
	   for a page that somehow renders without the inline token block — NOT the
	   normal path. ta3 shipped assuming --mbpos-close-btn-bg was unset; it was
	   not (it defaulted to 'subtle-dark' = rgba(0,0,0,0.08)), so the white
	   fallback never fired and the button stayed invisible on dark photos. */
	background:      var(--mbpos-close-btn-bg,   #ffffff);
	color:           var(--mbpos-close-btn-fg,   #1D1C1B);
	border:          1px solid var(--mbpos-close-btn-ring, rgba(0,0,0,0.16));
	box-shadow:      0 2px 8px rgba(0,0,0,0.30);

	-webkit-tap-highlight-color: transparent;
	transition:      background 0.15s, border-color 0.15s, transform 0.15s;
}

[data-mbpos] .mbpos-lightbox__close:hover,
[data-mbpos] .mbpos-pickup-modal__close-btn:hover {
	background:   var(--mbpos-close-btn-bg-hover, #f2f2f2);
	color:        var(--mbpos-close-btn-fg, #1D1C1B);
	border:       1px solid rgba(0,0,0,0.34);
	box-shadow:   0 3px 12px rgba(0,0,0,0.38);
}

[data-mbpos] .mbpos-lightbox__close:active,
[data-mbpos] .mbpos-pickup-modal__close-btn:active {
	background:   var(--mbpos-close-btn-bg-hover, #e8e8e8);
	border:       1px solid rgba(0,0,0,0.34);
	transform:    scale(0.94);
}

/* :focus-visible only — a persisted :focus after a mouse click would pin the
   ring on. Keyboard users still get a real, visible indicator. */
[data-mbpos] .mbpos-lightbox__close:focus,
[data-mbpos] .mbpos-pickup-modal__close-btn:focus {
	outline: none;
}
[data-mbpos] .mbpos-lightbox__close:focus-visible,
[data-mbpos] .mbpos-pickup-modal__close-btn:focus-visible {
	outline:        2px solid var(--mbpos-color-primary, #171717);
	outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
	[data-mbpos] .mbpos-lightbox__close,
	[data-mbpos] .mbpos-pickup-modal__close-btn        { transition: none; }
	[data-mbpos] .mbpos-lightbox__close:active,
	[data-mbpos] .mbpos-pickup-modal__close-btn:active { transform:  none; }
}

/* rv1: individual reviews in the modal — deliberately in THIS file, not
   mbpos-pos-layout.css, which is shortcode-only and never loads on a Bricks
   page (see the ce3 note on mbpos-pos-layout.css). The modal is built from
   the same <template> either way, cloned into .mbpos-lightbox__content--live
   on Bricks — gal8's cropping bug was exactly this gap, styling that only
   ever reached the shortcode path. This file is confirmed loaded everywhere. */
.mbpos-products-detail__reviews {
	margin-top: 12px;
	padding-top: 12px;
	padding-left: var(--mbpos-space-xs);
	padding-right: var(--mbpos-space-xs);
	border-top: 1px solid var(--mbpos-color-border, #E7E5E4);
}

/* fsn4: rating (left) + optional "Full product details" link (right),
   directly under the title. Confirmed-everywhere file (this one), not
   mbpos-pos-layout.css, so the Bricks-native modal gets this too, same
   reasoning as the reviews block above. margin-left:auto on the link
   (not justify-content:space-between on the row) so a single child —
   rating alone, or link alone — still lands on its correct side
   regardless of which one is present. */
.mbpos-products-detail__title-row {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-top: 4px;
}
.mbpos-products-detail__title-row .mbpos-products-detail__pagelink--text {
	margin: 0 0 0 auto;
}

/* fsn4/fsn4b: description clamp — protects the EPO block below from a long
   description pushing it off-screen. Only applied (via the is-clamped
   class, added by JS) when the product actually has EPO/combo options.

   fsn4b: switched from -webkit-line-clamp to max-height. -webkit-line-clamp
   is only reliable on plain text/inline content — a description built from
   nested block-level markup (headers, lists, divs — e.g. a page-builder
   product description) makes WebKit silently abandon the clamp entirely,
   rendering the full height with no visible truncation. scrollHeight then
   equals clientHeight, the overflow check sees nothing wrong, and the
   toggle never appears — the exact failure this feature exists to
   prevent, just for HTML-rich descriptions specifically. max-height clips
   reliably no matter what's nested inside.

   The full max-height VALUE (not just a line count) comes from JS via the
   CSS var — 'Nem' when clamping is on for that breakpoint, or the literal
   token 'none' when the store's setting is 0 for that breakpoint (see
   mbposApplyDescClamp). line-height is fixed at 1.5 here specifically so
   the em-based height is predictable regardless of whatever line-height
   the surrounding context would otherwise inherit. */
.mbpos-products-detail__desc.is-clamped {
	line-height: 1.5;
	max-height: var(--mbpos-desc-max-height, 6em);
	overflow: hidden;
}
@media (max-width: 600px) {
	.mbpos-products-detail__desc.is-clamped {
		max-height: var(--mbpos-desc-max-height-mobile, 4.5em);
	}
}
/* fsn4f/fsn4g/fsn4h: fade the bottom of the box into the background while
   clamped, as a visual cue there's more text before the reader even
   reaches the "Read more" toggle. mask-image (not an overlay div with a
   hardcoded background colour) so it fades into whatever's actually
   behind it — works regardless of light/dark mode or any admin colour
   customisation, no colour token to keep in sync. Removed entirely once
   expanded (full text, nothing left to hint at). -webkit- prefix for
   Safari; unsupported browsers just show no fade — harmless, not a
   broken state.

   fsn4h: percentage stop (bottom 50%), not a fixed em height. A fixed em
   value is a fixed height regardless of how tall the clamped box actually
   is — proportionally tiny on a 6-line desktop clamp, disproportionately
   large on a 2-line mobile one. A percentage scales with the box itself,
   so "roughly the bottom half fades" holds true at any clamp-lines
   setting, at any breakpoint, without separate tuning per case. Also the
   backdrop the overlaid toggle sits against; by the very bottom edge the
   mask reaches full transparency, so the toggle's own text always reads
   against the plain page background there, never fighting with whatever
   content is underneath. */
.mbpos-products-detail__desc.is-clamped:not(.is-expanded) {
	-webkit-mask-image: linear-gradient(to bottom, #000 50%, transparent 100%);
	        mask-image: linear-gradient(to bottom, #000 50%, transparent 100%);
}
.mbpos-products-detail__desc.is-clamped.is-expanded {
	max-height: none;
	overflow: visible;
}
/* fsn4g: positioning context for the toggle's overlay state below. */
.mbpos-products-detail__desc-wrap {
	position: relative;
}
/* fsn4d: scoped + full state contract, per CSS-ISOLATION-STANDARD.md rule 2/3.
   The unscoped version of this rule was exactly the documented canary case
   that standard exists for — Hello Elementor (and themes like it) style
   `button` and `button:hover` globally; without an explicit :hover here
   the theme's own hover paint (its accent colour, often pink) wins,
   because a bare `.mbpos-products-detail__desc-toggle` selector has no
   specificity advantage over the theme's `button:hover`. [data-mbpos]
   descendant scoping bumps this to (0,2,0), which beats it, and every
   state (base/hover/focus/focus-visible/active) is declared explicitly so
   nothing is left for the theme to fill in.

   fsn4d: right-aligned by default (margin-left:auto — a block-level flex
   container still respects margin auto, same trick the title-row's
   pagelink uses) to sit visually where "Full product details" does.

   fsn4g: OVERLAID on top of the fade (position:absolute, anchored to
   .desc-wrap's bottom-right corner) specifically while clamped-and-
   collapsed — see the next rule. That's the one geometrically fixed,
   content-independent spot: the fade zone is always exactly the bottom
   ~2.2em of the box, whatever's inside it, so overlapping it is safe
   regardless of how the description is structured. Once expanded there's
   no fade and no fixed spot — overlapping arbitrary rich-HTML content at
   whatever point the text happens to end was rejected in fsn4d for
   exactly that reason (risk of landing on a heading or list item), so
   this base rule (normal in-flow, right-aligned) is what applies whenever
   the overlay condition below doesn't. */
[data-mbpos] .mbpos-products-detail__desc-toggle {
	display: flex;
	align-items: center;
	gap: 4px;
	width: fit-content;
	margin: 4px 0 0 auto;
	padding: 2px 0;
	font: inherit;
	font-size: 13px;
	font-weight: 600;
	line-height: 1;
	text-align: right;
	color: var(--mbpos-color-primary, #C2410C);
	background: none;
	border: none;
	box-sizing: border-box;
	cursor: pointer;
	-webkit-tap-highlight-color: transparent;
}
[data-mbpos] .mbpos-products-detail__desc.is-clamped:not(.is-expanded) + .mbpos-products-detail__desc-toggle {
	position: absolute;
	right: 0;
	bottom: 0;
	margin: 0;
}
[data-mbpos] .mbpos-products-detail__desc-toggle:hover {
	background: none;
	color: var(--mbpos-color-primary, #C2410C);
}
[data-mbpos] .mbpos-products-detail__desc-toggle:hover .mbpos-products-detail__desc-toggle-label {
	text-decoration: underline;
}
[data-mbpos] .mbpos-products-detail__desc-toggle:focus {
	background: none;
	color: var(--mbpos-color-primary, #C2410C);
	outline: none;
}
[data-mbpos] .mbpos-products-detail__desc-toggle:focus-visible {
	outline: 2px solid var(--mbpos-color-primary, #C2410C);
	outline-offset: 2px;
}
[data-mbpos] .mbpos-products-detail__desc-toggle:active {
	background: none;
	color: var(--mbpos-color-primary, #C2410C);
}
.mbpos-products-detail__desc-toggle[hidden] { display: none; }
.mbpos-products-detail__desc-toggle-icon {
	flex: none;
	transition: transform 0.15s;
}
.mbpos-products-detail__desc-toggle[aria-expanded="true"] .mbpos-products-detail__desc-toggle-icon {
	transform: rotate(180deg);
}
.mbpos-products-detail__reviews[hidden] { display: none; }
.mbpos-review__heading {
	margin: 0 0 8px;
	font-size: 15px;
	font-weight: 700;
	color: var(--mbpos-color-heading, #1C1917);
}
.mbpos-review {
	padding: 10px 0;
}
.mbpos-review + .mbpos-review {
	border-top: 1px solid var(--mbpos-color-border, #E7E5E4);
}
.mbpos-review__head {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
	margin-bottom: 4px;
}
.mbpos-review__stars {
	font-size: 13px;
	letter-spacing: 1px;
	white-space: nowrap;
}
.mbpos-review__star { color: var(--mbpos-star-empty, #d8d8d8); }
.mbpos-review__star.is-filled { color: var(--mbpos-star, #f5a623); }
.mbpos-review__author {
	font-size: 13px;
	font-weight: 700;
	color: var(--mbpos-color-heading, #1C1917);
}
.mbpos-review__date {
	font-size: 12px;
	color: var(--mbpos-color-text-soft, #A8A29E);
}
.mbpos-review__content {
	margin: 0;
	font-size: 13px;
	line-height: 1.5;
	color: var(--mbpos-color-text, #44403C);
}
.mbpos-review__see-all {
	display: block;
	margin-top: 10px;
	font-size: 13px;
	font-weight: 600;
	color: var(--mbpos-color-primary, #C2410C);
	text-decoration: none;
}
.mbpos-review__see-all:hover { text-decoration: underline; }

/* vp3: "under the title" pagelink variant — a plain text link, not the
   photo-chip treatment (mbpos-pos-layout.css), since it isn't sitting on a
   photo anymore. Styled to match .mbpos-review__see-all (same "leaves the
   modal" family) rather than reusing the chip's own bg/fg/border tokens,
   which were designed for legibility against an arbitrary product photo —
   a need this variant no longer has. */
.mbpos-products-detail__pagelink--text {
	display: inline-block;
	margin: 12px 0 8px;
	font-size: 13px;
	font-weight: 600;
	color: var(--mbpos-color-primary, #C2410C);
	text-decoration: none;
}
.mbpos-products-detail__pagelink--text:hover { text-decoration: underline; }

/* ht1: heading sizes for CONTENT rendered inside a product description —
   a store owner's own <h2>/<h3> written while editing the product, not
   any of the plugin's own UI headings (those already have their own
   specific classes with their own sizes and are unaffected by this).
   Deliberately in THIS file, not mbpos-pos-layout.css (shortcode-only) —
   the description renders inside the modal, which gets cloned into a
   Bricks page's --live wrapper same as everywhere else this session has
   hit that exact gap (gal8, rv1) — this file is confirmed loaded
   everywhere. [data-mbpos][data-mbpos] (the attribute repeated, not a
   typo) is a specificity-boosting trick already used elsewhere this
   session: real, non-zero specificity a theme's own bare `h2 { }` rule
   can't beat regardless of which stylesheet happens to load first,
   without resorting to !important. */
[data-mbpos][data-mbpos] .mbpos-products-detail__desc :where(h1, h2) {
	font-size:   var(--mbpos-content-heading-size, 20px);
	line-height: 1.3;
	font-weight: 700;
	margin:      0.6em 0 0.3em;
}
[data-mbpos][data-mbpos] .mbpos-products-detail__desc :where(h3) {
	font-size:   calc(var(--mbpos-content-heading-size, 20px) * 0.85);
	line-height: 1.35;
	font-weight: 700;
	margin:      0.6em 0 0.3em;
}
[data-mbpos][data-mbpos] .mbpos-products-detail__desc :where(h4, h5, h6) {
	font-size:   var(--mbpos-font-size-base, 14px);
	line-height: 1.4;
	font-weight: 700;
	margin:      0.6em 0 0.3em;
}


/* ═══════════════════════════════════════════════════════════════════════════
   [ap2 Slice B] Express wallet block — Apple Pay / Google Pay above the form.
   ---------------------------------------------------------------------------
   The block is rendered EMPTY and `hidden` by the checkout form, and revealed
   by mbpos-express.js only once a wallet has actually resolved as available.
   Nothing is reserved before that: a device with no wallet must see the contact
   fields exactly where they have always been, with no shift and no orphaned
   divider.

   Every rule is [data-mbpos]-scoped per CSS-ISOLATION-STANDARD. The tick box in
   particular needs explicit `appearance` and sizing rather than inherited
   values: an unstyled checkbox inside .payment_box rendered INVISIBLY in
   testing (structure was correct — it simply could not be seen), because
   themes routinely reset label display and checkbox appearance. A DOM fence
   cannot catch that, so the styling is deliberately explicit here.
   ═══════════════════════════════════════════════════════════════════════════ */

[data-mbpos] .mbpos-express-block[hidden] { display: none !important; }

[data-mbpos] .mbpos-express-block {
    display: block;
    margin: 0 0 18px;
}

[data-mbpos] .mbpos-express-block__buttons {
    display: block;
    min-height: 0;            /* never reserve height before the wallet resolves */
}

/* ── the "Use Apple Pay details" tick box ─────────────────────────────────── */
[data-mbpos] .mbpos-express-block__contact {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 10px 0 0;
    padding: 0;
    font-size: 14px;
    line-height: 1.35;
    cursor: pointer;
    min-width: 0;             /* Hello Elementor sets min-width:0 on flex kids */
    width: auto;
}

[data-mbpos] .mbpos-express-block__contact[hidden] { display: none !important; }

[data-mbpos] .mbpos-express-block__contact .mbpos-express-contact__input {
    -webkit-appearance: checkbox;
    appearance: checkbox;
    width: 17px;
    height: 17px;
    min-width: 17px;          /* stops flex from collapsing it to zero */
    margin: 0;
    padding: 0;
    flex: 0 0 auto;
    opacity: 1;
    position: static;         /* some themes absolutely position form inputs */
    cursor: pointer;
}

[data-mbpos] .mbpos-express-block__contact .mbpos-express-contact__label {
    display: inline-block;
    min-width: 0;
    cursor: pointer;
}

/* ── "or enter your details" divider ──────────────────────────────────────── */
[data-mbpos] .mbpos-express-block__divider {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 16px 0 0;
    font-size: 13px;
    opacity: 0.75;
}

[data-mbpos] .mbpos-express-block__divider::before,
[data-mbpos] .mbpos-express-block__divider::after {
    content: "";
    flex: 1 1 auto;
    height: 1px;
    background: currentColor;
    opacity: 0.25;
}

[data-mbpos] .mbpos-express-block__divider > span {
    flex: 0 0 auto;
    white-space: nowrap;
}

/* [ap2d] Pending skeleton — space reserved for a wallet button whose
   availability has not resolved yet.
   ---------------------------------------------------------------------------
   Only applied when ApplePaySession.canMakePayments() has already said (for
   free, synchronously) that this device could plausibly have a wallet. A
   non-Apple device reserves nothing and sees no gap, which is the invariant
   express-block-dom-test-ap2.js group C protects.

   Deliberately a neutral shimmer with no text: a wallet button captioned
   "loading" reads as broken, and if availability resolves false we would have
   advertised something that then disappears. */
[data-mbpos] .mbpos-express-block.is-pending .mbpos-express-block__buttons {
    min-height: 44px;
    border-radius: 6px;
    background: linear-gradient(90deg,
        rgba(0,0,0,0.05) 25%, rgba(0,0,0,0.10) 37%, rgba(0,0,0,0.05) 63%);
    background-size: 400% 100%;
    animation: mbpos-express-shimmer 1.3s ease-in-out infinite;
}

[data-mbpos] .mbpos-express-block.is-pending .mbpos-express-block__contact,
[data-mbpos] .mbpos-express-block.is-pending .mbpos-express-block__divider {
    visibility: hidden;   /* holds layout, shows nothing half-built */
}

@keyframes mbpos-express-shimmer {
    0%   { background-position: 100% 50%; }
    100% { background-position: 0 50%; }
}

@media (prefers-reduced-motion: reduce) {
    [data-mbpos] .mbpos-express-block.is-pending .mbpos-express-block__buttons {
        animation: none;
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   [ap2i] iOS Safari focus-zoom on the checkout fields.
   ---------------------------------------------------------------------------
   Safari zooms the page in whenever a focused input's font-size is UNDER 16px,
   and it deliberately does NOT zoom back out on blur — the new zoom level is
   treated as the user's chosen view until they pinch out. There is no reliable
   way to zoom out from script: the old `maximum-scale=1` viewport trick is
   ignored by modern Safari, and it would disable pinch-zoom entirely, which is
   an accessibility regression.

   So the only robust fix is to remove the REASON for the zoom: 16px inputs.

   --mbpos-font-size-s is 12px and is shared across the POS, so it is not
   touched — this overrides the CHECKOUT fields only, and only where a coarse
   pointer says we are on a touch device. Desktop keeps its 12px sizing.

   16px is a floor, not a preference: below it Safari zooms, at or above it it
   does not. Do not "tidy" this to 14px.
   ═══════════════════════════════════════════════════════════════════════════ */
@media (pointer: coarse) {
    [data-mbpos] input.mbpos-checkout__input,
    [data-mbpos] textarea.mbpos-checkout__textarea,
    [data-mbpos] .mbpos-checkout select,
    [data-mbpos] .mbpos-checkout input[type="text"],
    [data-mbpos] .mbpos-checkout input[type="email"],
    [data-mbpos] .mbpos-checkout input[type="tel"],
    [data-mbpos] .mbpos-checkout input[type="number"] {
        font-size: 16px;
    }
}
