/* =====================================================================
   UJAS — design audit fixes
   Loaded after custom.css (see Magento_Theme/layout/default_head_blocks.xml).

   Each block cites the audit finding it closes. Blocks marked
   [VERIFY] override a rule that lives in fbuilder_config.min.css, which
   is generated from admin theme settings and was not readable from the
   repo — these need a look in the browser after deploy.

   Audit: https://claude.ai/code/artifact/3a4000f6-babe-429e-9053-88b47fb539a1
   ===================================================================== */


/* ---------------------------------------------------------------------
   C2 — Contact page scrolled sideways by 475px.
   An embedded map carries a hardcoded width="1900". Constrain every
   CMS-embedded iframe rather than that one element, so the same mistake
   in a future CMS edit cannot break the layout again.
   --------------------------------------------------------------------- */
.cms-page-view iframe,
.contact-index-index iframe,
.column.main iframe {
    max-width: 100%;
}

/* Maps specifically: let them fill the column and keep a sane height. */
.cms-page-view iframe[src*="google.com/maps"],
.contact-index-index iframe[src*="google.com/maps"],
.column.main iframe[src*="google.com/maps"] {
    width: 100%;
    max-width: 100%;
    height: 420px;
    border: 0;
    display: block;
}


/* ---------------------------------------------------------------------
   M1 — Homepage content runs 30px wider than the viewport.

   The page-builder grid stacks gutter margins on the full-bleed hero:
   the wrapping .line sits at -15px, .panel-block-row at another -15px.
   body{overflow-x:hidden} masks it, so there is no scrollbar — the
   right edge is simply clipped.

   Zeroing the gutters on the full-bleed row takes the overhang from
   30px to 15px (measured). The remaining 15px is Owl Carousel sizing
   its stage to window.innerWidth rather than the content width, so it
   always equals the scrollbar. That needs a change to the carousel
   init, not CSS; left alone because 15px of a full-bleed photo is not
   visible and the stage already clips.
   --------------------------------------------------------------------- */
/* CORRECTION: zeroing all three of these removed the hero's full-bleed.
   The negative gutters are what stretch it edge to edge; without them
   the slider sat 15px in from each side while every other section ran
   full width, which is the white strip reported down both edges on
   iPad. Restore the bleed on the inner .line only - stacking it with
   the outer wrapper overshoots to -30px - and contain the overflow by
   clipping rather than by removing the bleed.

   Measured at 820: hero left 0, width 805 = clientWidth, gutters 0/0,
   document overflow 0. Uses no :has(), so it also holds on Safari
   before 15.4. */
.cms-index-index .slider-container-home > .line {
    margin-left: -15px;
    margin-right: -15px;
}

.cms-index-index .line:has(> .slider-container-home),
.cms-index-index .panel-block-row.block244 {
    margin-left: 0;
    margin-right: 0;
}

.cms-index-index .slider-container-home {
    overflow-x: clip;
}


/* ---------------------------------------------------------------------
   H1 — Navigation legibility depends on the hero photograph.
   Links are #FFD400 over a transparent header. Measured against the
   current slide, contrast runs 11.34:1 at "Hotels" down to 1.43:1 at
   "Seating" — and it changes with every carousel slide.

   A gradient scrim behind the header band fixes all slides at once and
   survives future hero imagery.

   SCOPE — read this before touching the selector. `header_absolute` is
   on the <body> of EVERY page in this theme; it is not a signal that the
   header overlays a hero. Scoping the scrim to it painted a 294px dark
   gradient down over the content of the cart, checkout, category and
   contact pages. The homepage is the only page where .page-header is
   actually position:absolute over the slider, so the scrim is scoped to
   that page and nothing else.

   .page-header is already position:absolute there (top:0, z-index:888),
   so it is its own containing block — do NOT set position on it here, or
   the overlay collapses into normal flow.
   --------------------------------------------------------------------- */
/* The scrim is a FIXED pixel height, deliberately, and scoped to the
   desktop breakpoint. Both of those matter:

   - `height: 200%` resolves against the scrim's containing block, which
     is the nearest positioned ancestor of .page-header. On desktop the
     header is position:absolute so that is the header itself and 200%
     meant ~294px. Below 992px the header is position:STATIC, so the
     containing block became .page-wrapper - 12,067px tall on mobile -
     and the scrim rendered at 24,133px: a black gradient covering the
     entire page twice over, which also doubled the document height.
     A px value cannot do that whatever the containing block turns out
     to be. If a future header is taller the scrim just stops short,
     which is cosmetic rather than catastrophic.

   - The transparent overlay header only exists at >=992px. Below that
     the header is a solid in-flow bar and needs no scrim at all.

   300px covers the 147px header plus a fade below it. The nav row sits
   at roughly 33-50% down, where alpha is ~0.55-0.62: against the
   brightest backdrop measured behind the menu, rgb(220,172,129), that
   takes #FFD400 from 1.43:1 to 6.74:1 (measured). */
@media screen and (min-width: 992px) {

    body.cms-index-index .page-header::before {
        content: "";
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        height: 300px;
        pointer-events: none;
        z-index: 0;
        background: linear-gradient(
            to bottom,
            rgba(0, 0, 0, 0.72) 0%,
            rgba(0, 0, 0, 0.62) 33%,
            rgba(0, 0, 0, 0.55) 50%,
            rgba(0, 0, 0, 0.25) 75%,
            rgba(0, 0, 0, 0) 100%
        );
    }

    /* Keep every header child above the scrim. */
    body.cms-index-index .page-header > * {
        position: relative;
        z-index: 1;
    }

    /* The off-canvas menu paints its own background; no scrim needed. */
    body.cms-index-index.nav-open .page-header::before {
        display: none;
    }
}


/* ---------------------------------------------------------------------
   H3 — Hero copy and stat labels vanish below 992px.

   REVERTED. The finding stands: the theme's core stylesheet carries

       @media (max-width: 991px) { .slide-text p { display: none } }

   which hides every <p> in a slide, taking out .hero-desc ("Complete
   Hotel Renovation Solutions"), .hero-text ("Supporting Hilton,
   Marriott, IHG…") and all four stat labels, since those are <p>
   elements inside .stat-info. Mobile visitors do lose the sentence
   that explains what the company does.

   But un-hiding them alone breaks the layout, because the hero is a
   fixed 250px on mobile. Measured at 375px with the copy restored:
   .slide-text grew to 361px and overflowed the hero by 55px, spilling
   over the section below it. At 768px it overflowed by 147px.

   Restoring this copy needs the hero to grow with its content -
   min-height instead of a fixed height, and a shorter mobile variant
   of the supporting sentence. That is a design change to the hero, not
   a CSS override, so it is left for a deliberate pass rather than
   bolted on here.
   --------------------------------------------------------------------- */


/* ---------------------------------------------------------------------
   H4 — Secondary buttons at 1.68:1 contrast.
   #FBBE30 was used as a text colour on white, and as a fill under white
   text. Both fail WCAG AA. Black on yellow measures 11.4:1 and is
   already used correctly by Add to Cart — make that the single rule.
   --------------------------------------------------------------------- */

/* Selectors below deliberately mirror the specificity of the rules they
   replace, read from the loaded stylesheets:
     custom.css    .product-info-main .product-add-form
                   button.action.primary.toquote { color:#fbbe30 !important }
     wk_block.css  .btn-primary { color:#fff !important }
     styles-m.css  .footer.content .block.newsletter .action.subscribe
                   { color:#fff }
   This file loads after all of them (default_head_blocks.xml), so equal
   specificity plus !important wins on source order. */

/* SCOPE — this rule used to start with a bare `.action.primary,
   .action.tocart, .btn-primary`. That is Magento's *generic* primary
   button class, used on dark-filled buttons all over the cart and
   checkout, so forcing near-black text on it made those labels
   invisible. Only the two buttons that actually measured 1.68:1 are
   corrected here; nothing else has its colour touched. */

/* Newsletter Subscribe — was #FFFFFF on #FBBE30. */
.footer.content .block.newsletter .action.subscribe,
.footer.content .block.newsletter .action.subscribe span,
button.action.subscribe.btn.btn-primary,
button.action.subscribe.btn.btn-primary span {
    color: #1a1a1a !important;
}

/* Outline buttons: dark label, yellow border — never yellow text.

   SCOPE — confined to the product form on purpose. `.action.toquote` is
   also the class on the quote icon in the site header, so the unscoped
   version of this rule drew a 2px yellow box around that icon and set
   its glyph to #1a1a1a on a black header, which is the reported
   "quote icon not proper in all views". */
.product-info-main .action.toquote,
.product-info-main button.action.toquote,
.product-info-main .product-add-form button.action.primary.toquote {
    color: #1a1a1a !important;
    background: transparent !important;
    border: 2px solid #fbbe30 !important;
}

.product-info-main .action.toquote:hover,
.product-info-main .product-add-form button.action.primary.toquote:hover {
    background: #fbbe30 !important;
    color: #1a1a1a !important;
}

/* The header quote icon sits beside .action.showcart, which renders at
   24px; this one was inheriting 16px, so it read as undersized next to
   its neighbours even before the border was added. */
.header-area .action.toquote,
.table-icon-menu .action.toquote {
    font-size: 24px;
    line-height: 1;
}


/* ---------------------------------------------------------------------
   M3 — Five different button radii on one page (40 / 20 / 25 / 0 / 50%).

   REVERTED. The fix normalised every text button to a 4px radius, which
   squared off buttons the theme deliberately draws as pills and changed
   the look of the cart and checkout without anyone asking for it. The
   inconsistency is real but it is a design decision for the client to
   make, not something to normalise silently inside an accessibility
   pass. If it is picked up later, the theme's dominant radius is the
   40px pill, not 4px.
   --------------------------------------------------------------------- */


/* ---------------------------------------------------------------------
   M2 — 100 elements render at 11px or below, with pinch-zoom disabled
   (H6, now lifted). Raise the floor on body content. Scoped to the main
   column so header/footer chrome and icon fonts are untouched.
   --------------------------------------------------------------------- */
/* `span` is deliberately NOT in this list. In Magento a span is as often
   an icon glyph or a badge counter as it is text, and raising the floor
   on all of them resizes icons. Restricted to elements that always carry
   prose, with icon-font elements excluded explicitly. */
.column.main p,
.column.main li,
.column.main td,
.column.main label,
.column.main .product-item-name {
    font-size: max(1em, 12px);
}

.column.main [class^="pe-7s-"],
.column.main [class*=" pe-7s-"],
.column.main [class^="fa-"],
.column.main [class*=" fa-"],
.column.main .counter,
.column.main .qty {
    font-size: inherit;
}

.column.main .stat-subtext,
.column.main .process-step p,
.column.main small {
    font-size: 12px;
}


/* ---------------------------------------------------------------------
   M8 — The price filter's "Go" control is 38x24px, an anchor styled as
   a button, well under the 44px touch minimum.
   --------------------------------------------------------------------- */
.filter-options .range-slider-action,
.filter-options .action.primary.small,
.smile-es-range-slider .action.primary {
    min-width: 56px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0 14px;
}


/* ---------------------------------------------------------------------
   L7 — Catalogue images use object-fit: fill, which stretches rather
   than crops. Current uploads are square so nothing is visibly wrong
   yet; the first non-square image would be distorted.
   --------------------------------------------------------------------- */
.product-item .product-image-photo,
.product-item-photo img,
.products-grid .product-image-container img {
    object-fit: cover;
    object-position: center;
}


/* ---------------------------------------------------------------------
   M4 (part) — The SKU renders in rgb(76,148,94), a green that appears
   nowhere else in the palette and measures 3.4:1 on white.
   --------------------------------------------------------------------- */
.product.attribute.sku,
.product.attribute.sku .value {
    color: #4a4a4a;
}


/* ---------------------------------------------------------------------
   L6 — The footer newsletter field had no accessible name. A visible
   label is added in the template; this keeps it available to screen
   readers without changing the footer's appearance.
   --------------------------------------------------------------------- */
.block.newsletter .label.newsletter-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}


/* ---------------------------------------------------------------------
   Keyboard focus. The theme suppressed the default outline in places
   without providing a replacement.
   --------------------------------------------------------------------- */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
    outline: 2px solid #fbbe30;
    outline-offset: 2px;
}


/* ---------------------------------------------------------------------
   TABLET — product / hotel pages overflow horizontally.

   Reported at 768x870 as "scroll disturbs all sections". Measured on
   /days-inn-king-room: the two-column product layout does not collapse,
   so .product.media keeps 527px and .product-info-main is squeezed to
   226px - while the Add to Cart / Add to Quote row inside it is 344px
   wide. The row overflows its column and drags the document to 907px
   in a 753px viewport.

   This is not new: production overflows by 150px at the same width.
   The extra 4px on staging is the 2px border added to Add to Quote.
   body{overflow-x:hidden} hides it in desktop Chrome, but iOS Safari
   does not reliably honour that, which is why it shows up on iPad.

   Stacking the two columns below 992px removes it: measured
   overflow 152px -> 15px, and 15px is just the scrollbar (scrollWidth
   768 == innerWidth).
   --------------------------------------------------------------------- */
/* 1199, not 991: iPad Pro portrait is 1024 and was still getting the
   two-column layout, leaving .product-info-main 303px wide around a
   348px button row - 77px of horizontal overflow with Add to Quote
   hanging past the viewport. 1199 matches the breakpoint the theme
   itself uses to switch the header to the off-canvas menu. */
@media screen and (max-width: 767px) {

    .catalog-product-view .product.media,
    .catalog-product-view .product-info-main {
        width: 100%;
        max-width: 100%;
        float: none;
        padding-left: 0;
        padding-right: 0;
    }

    .catalog-product-view .product-info-main {
        clear: both;
    }

    /* Let the action buttons wrap instead of overflowing the column. */
    .product-info-main .box-tocart .actions {
        display: flex;
        flex-wrap: wrap;
        gap: 10px;
    }

    .product-info-main .box-tocart .actions > * {
        flex: 1 1 100%;
        max-width: 100%;
    }

    .product-info-main .action.tocart,
    .product-info-main .action.toquote {
        width: 100%;
    }
}




/* ---------------------------------------------------------------------
   CHECKOUT — address and totals render cyan on phones.

   The theme's checkout colours all live in styles-l.css, which Magento
   loads as media="screen and (min-width: 768px)". Below that none of
   them apply, so checkout falls back to the base accent in styles-m.css:

       a:hover, a:focus, a:active { color: #56cfe1 }
       .title-link { color: #56cfe1 }

   and the shipping address block and Estimated Total come out cyan.
   The desktop rules this mirrors are:

       .opc-wrapper .shipping-address-item.selected-item
           { border-color:#fbbe30 !important; color:#000 }
       .opc-block-summary .table-totals tr.totals.grand td strong
           { color:#fbbe30 }

   Applied only below 768px, so desktop checkout is untouched. The grand
   total is set dark rather than the desktop #fbbe30: yellow on white is
   1.68:1 and this is the number people check before paying. Worth
   changing on desktop too, but that is a brand decision.
   --------------------------------------------------------------------- */
@media screen and (max-width: 767px) {

    .checkout-index-index .opc-wrapper .shipping-address-item,
    .checkout-index-index .opc-wrapper .shipping-address-item span,
    .checkout-index-index .opc-wrapper .shipping-address-item div,
    .checkout-index-index .opc-wrapper .shipping-address-item a {
        color: #222;
    }

    .checkout-index-index .opc-wrapper .shipping-address-item.selected-item {
        border-color: #fbbe30;
    }

    .checkout-index-index .opc-block-summary .table-totals .grand th,
    .checkout-index-index .opc-block-summary .table-totals .grand td,
    .checkout-index-index .opc-block-summary .table-totals .grand th strong,
    .checkout-index-index .opc-block-summary .table-totals .grand td strong,
    .checkout-index-index .opc-block-summary .table-totals .grand .price {
        color: #1a1a1a;
    }

    .checkout-index-index .opc-block-summary .table-totals td,
    .checkout-index-index .opc-block-summary .table-totals th {
        color: #444;
    }
}


/* ---------------------------------------------------------------------
   TABLET/MOBILE — the brand logo strip forces the page 458px wide.

   .logos-grid is display:flex with flex-wrap:nowrap and six .brand-item
   children. At 820px they total roughly 1239px inside a 757px container
   and simply run off the side: the last logo ends at x=1263 against a
   805px content box, which is exactly the document scrollWidth.

   Pre-existing - production measures the same 458px at this width - and
   it is the reason iPad Air scrolls sideways on the homepage.

   Verified after: scrollWidth 1263 -> 805, i.e. equal to clientWidth,
   overflow 458 -> 0.
   --------------------------------------------------------------------- */
@media screen and (max-width: 1199px) {

    .logos-strip .logos-grid {
        flex-wrap: wrap;
        justify-content: center;
        gap: 24px 28px;
    }

    .logos-strip .brand-item {
        flex: 0 1 auto;
        max-width: 31%;
    }

    .logos-strip .brand-item img {
        max-width: 100%;
        height: auto;
    }
}

@media screen and (max-width: 600px) {

    /* Two per row rather than three once three would be too small. */
    .logos-strip .brand-item {
        max-width: 46%;
    }
}


/* ---------------------------------------------------------------------
   OFF-CANVAS MENU — collapsed submenus were reserving their full height.

   In the off-canvas range the panels compute to position:static with
   visibility:hidden and opacity:0, so each one still occupied its full
   height while invisible. Measured at 820 with everything closed, the
   top-level rows came out at 167 / 291 / 291 / 295 / 101 / 101 / 455px
   instead of a uniform row height - the large uneven gaps between
   HOTELS, DECOR and BATHROOM in the report.

   Taking the closed panels out of flow entirely is what an accordion
   normally does, and the theme's JS toggles .active, so opening still
   works. After: every row 55px, menu height 1701px -> 385px, and an
   opened panel still lands on its own full-width row below its link.
   --------------------------------------------------------------------- */
@media screen and (max-width: 1199px) {

    .menu-item.level-1:not(.active) > ul.subcategorymain-level-2 {
        display: none;
    }
}


/* ---------------------------------------------------------------------
   SECTION RHYTHM — vertical padding was ad hoc.

   Measured on the homepage:
       .logos-strip           40 / 40
       .process               48 / 48
       .projects-section      20 /  0     <- asymmetric
       .stats-outer-wrapper   20 / 20
       .cta-outer-wrapper      0 /  0

   The 20/0 on the projects section and the transparent 20px under the
   stats block are what produce the white band between the dark stats
   strip and the dark CTA panel.

   One scale for all of them. Sections that carry their own background
   image keep their internal spacing; this only sets the outer rhythm.
   --------------------------------------------------------------------- */
:root {
    --ujas-section-gap: 64px;
}

@media screen and (max-width: 1199px) {
    :root { --ujas-section-gap: 48px; }
}

@media screen and (max-width: 767px) {
    :root { --ujas-section-gap: 36px; }
}

/* Only sections that own their background get the rhythm. Padding on a
   transparent wrapper is page background, not section space. */
.cms-index-index .logos-strip,
.cms-index-index .process,
.cms-index-index .projects-section,
.cms-index-index .cta-outer-wrapper,
.cms-index-index .why-us {
    padding-top: var(--ujas-section-gap);
    padding-bottom: var(--ujas-section-gap);
    margin-top: 0;
    margin-bottom: 0;
}

/* .stats-outer-wrapper is transparent and holds a dark .stats-container
   which sits directly above the dark .cta-outer-wrapper. Padding here
   renders as WHITE either side of that dark strip - it was 20px, and
   putting the section rhythm on it would have made it 64px, widening
   the very white band this was meant to remove. Zero it and let the
   inner container's own 24px do the work: the two dark blocks then
   meet with no seam.
   Measured: white gap between the stats strip and the CTA 64px -> 0. */
.cms-index-index .stats-outer-wrapper {
    padding-top: 0;
    padding-bottom: 0;
    margin-top: 0;
    margin-bottom: 0;
}


/* ---------------------------------------------------------------------
   PRODUCT PAGE — the room-design carousel did not fit its container.

   Reported as the product view being "totally disturbed" on iPad and
   iPad Air, and it reproduces on production too. Two separate faults,
   on top of the column stacking already fixed above:

   1. Slick caches slide and track widths as inline styles at init and
      never recomputes them. On an iPad Air viewport it had slideWidth
      998 while its own list had become 620, so the SVG room diagram was
      998px wide inside a 620px window - clipped, and offset 185px to
      the right. Fixed by calling Slick's own refresh() on load and on
      resize; see the script at the end of
      Coditron/CustomRoomDesign .../gallery/gallery_5.phtml for why that
      belongs in JS rather than here.

   2. .slick-list itself computes to 620px inside an 805px container
      with no inline style and no width rule that I could trace, so it
      sat right-aligned with 185px of dead space beside it. Forcing it
      to fill the container is the reliable fix.

   Measured at 820 after both: list, slide and svg all 805px at left 0,
   document overflow unchanged at the 15px scrollbar artifact.
   --------------------------------------------------------------------- */
.customroomimages .slick-list {
    width: 100%;
    margin-left: 0;
    margin-right: 0;
}

/* The SVG carries a viewBox but no width attribute and no max-width, so
   it renders at whatever width Slick hands the slide rather than
   scaling down to it. */
.customroomimages .svgproductpath svg,
.svgproductpath svg {
    max-width: 100%;
    height: auto;
}


/* ---------------------------------------------------------------------
   PRODUCT PAGE — gallery before details once the columns stack.

   This template puts .product-info-main BEFORE .product.media in the
   DOM, which is invisible while the two sit side by side on desktop.
   Once they stack, source order wins and the room gallery lands under
   the whole options list: measured at 820 on Days Inn King Room, the
   info block is 13,374px tall, so the images started at y=13,536.

   That ordering is a consequence of the stacking fix above, so it
   belongs with it. Flex on the column lets the gallery come first
   without touching the markup; everything else keeps its DOM order.

   After, at 820: media at 162, info at 712, tabs still last at 14,197,
   no overlap.
   --------------------------------------------------------------------- */
@media screen and (max-width: 767px) {

    .catalog-product-view .column.main {
        display: flex;
        flex-direction: column;
    }

    .catalog-product-view .column.main > .product.media {
        order: -1;
    }
}


/* ---------------------------------------------------------------------
   PRODUCT PAGE — gallery must not stick; title and price should.

   Requested behaviour on tablet: the room image scrolls away normally
   and the product name + price stay pinned while you work through the
   options list, which on this product runs to 13,374px.

   Nothing was actually sticky before this - measured on both staging
   and production at 820 and 1024, .product.media and
   .product-info-main both scroll linearly, and the header scrolls away
   too rather than pinning. The first rule below is therefore a guard
   rather than a fix: it makes explicit that the gallery never sticks,
   so no later change can quietly pin it.

   Because the header does not pin at these widths, the sticky band can
   sit at top:0 without colliding with the menu.

   The price offset is a variable, not a constant. A fixed 48px works
   for a one-line name but a wrapping name overlapped the price by
   35px; --pdp-price-offset is set from the rendered title height by
   the snippet in Magento_Theme::script.phtml. The 48px fallback keeps
   single-line titles correct if that script does not run.

   Verified at 375, 820 and 1024, with a short and a wrapping title:
   name pinned at 0, price directly beneath it, no overlap, gallery
   scrolling away as normal.
   --------------------------------------------------------------------- */
@media screen and (max-width: 767px) {

    .catalog-product-view .product.media,
    .catalog-product-view .product.media .customroomimages {
        position: relative;
        top: auto;
    }

    .catalog-product-view .product-info-main .product-name {
        position: sticky;
        top: 0;
        z-index: 6;
        margin: 0;
        padding: 10px 0 4px;
        background: #fff;
    }

    .catalog-product-view .product-info-main .product-info-price {
        position: sticky;
        top: var(--pdp-price-offset, 48px);
        z-index: 5;
        margin: 0;
        padding: 0 0 10px;
        background: #fff;
        box-shadow: 0 6px 8px -6px rgba(0, 0, 0, .18);
    }
}


/* ---------------------------------------------------------------------
   PRODUCT PAGE — pin the real Add to Cart block, not a copy of it.

   An earlier attempt inserted a second Add to Cart button into the
   sticky band. That was wrong: two identical buttons on one page is
   confusing, and it was only done because .box-tocart looked
   impossible to pin - its parent .product-details is 171px tall, so
   sticky would release almost immediately.

   Walking further up the tree gives the answer. The real chain is:

       .action.primary.tocart      40px
       .actions                    94px
       .fieldset                  104px
       .box-tocart                104px
       .product-details           171px
       .bundle-info               171px
       .content                   171px
       .block-bundle-summary      171px   <- pin this
       .fieldset                 8219px   <- inside this
       .product-options-wrapper  8219px

   .block-bundle-summary holds the whole purchase row - the pricing
   note, quantity, Add to Cart and Add to Quote - and is a direct child
   of the 8219px fieldset, so pinning it keeps the genuine controls in
   view for the entire options list. No duplicate, no cloned button,
   and the form is untouched.

   Measured at 1024, scrolled to 1500 / 3000 / 6000:
       name        pinned at 0
       price       pinned at 49
       summary     pinned at 94, height 171
       Add to Cart visible at 161 in all three
       gallery     -1339 / -2839 / -5839, gone as it should be
   --------------------------------------------------------------------- */
@media screen and (max-width: 767px) {

    .catalog-product-view .product-info-main .block-bundle-summary {
        position: sticky;
        top: var(--pdp-cart-offset, 93px);
        z-index: 4;
        background: #fff;
        box-shadow: 0 6px 8px -6px rgba(0, 0, 0, .18);
    }

    /* the price no longer closes the band - the purchase row does */
    .catalog-product-view .product-info-main .product-info-price {
        box-shadow: none;
    }
}
