{
  "version": 3,
  "sources": ["/cdn/shop/t/16/assets/custom-cart-v6.js?cb=1778394014044218200"],
  "sourcesContent": ["/**\r\n * IMPROVED MODERN CART FOR CAMPING AUSTRALIA\r\n * Fixed decimal formatting and image handling\r\n */\r\n\r\n(function() {\r\n  'use strict';\r\n  // Block the original cart system\r\n  window.CUSTOM_CART_OVERRIDE = true;\r\n  window.Colors = window.Colors || {};\r\n  window.Colors.cart = null;\r\n  \r\n  class ModernCartSystem {\r\n    constructor() {\r\n      this.cartDrawer = null;\r\n      this.cart = { items: [], total_price: 0, item_count: 0 };\r\n      this.isOpen = false;\r\n      this.quantityUpdateTimeout = null;\r\n      // Every cart write (add, quantity edit, duplicate merge) runs one at a\r\n      // time through _writeChain. Concurrent writes raced on the server, and\r\n      // a /cart.js read that crossed a write rolled the drawer back to the\r\n      // state before it. _writeGen moves whenever a write is queued or lands,\r\n      // so a read can tell it is out of date.\r\n      this._writeChain = Promise.resolve();\r\n      this._writeGen = 0;\r\n      this._writesPending = 0;\r\n      // Adds the server does not have yet: { quantity, placeholder, bumpKey }.\r\n      // Kept so a server cart applied meanwhile still shows them.\r\n      this._pendingAdds = new Set();\r\n      // Quantity edits waiting on the debounce, by line key.\r\n      this._pendingQty = {};\r\n      this.init();\r\n    }\r\n\r\n    async init() {     \r\n      if (document.readyState === 'loading') {\r\n        document.addEventListener('DOMContentLoaded', () => this.setup());\r\n      } else {\r\n        this.setup();\r\n      }\r\n    }\r\n\r\n    async setup() {\r\n      this.createModernCartHTML();\r\n      this.setupEventListeners();\r\n      const loaded = await this._syncFromServer();\r\n      this.renderCart();\r\n      // Merge leftover duplicate lines once per page load, not after every\r\n      // add as before (an extra write the old code fired outside any order).\r\n      if (loaded) {\r\n        this._enqueueWrite(() => this.cleanupDuplicates())\r\n          .catch(error => console.error('Duplicate cleanup failed:', error));\r\n      }\r\n    }\r\n\r\n    createModernCartHTML() {\r\n      const existingCart = document.querySelector('.side-cart, .modern-side-cart');\r\n      if (existingCart) {\r\n        existingCart.remove();\r\n      }\r\n\r\n      const cartHTML = `\r\n        <style>\r\n          .modern-side-cart {\r\n            position: fixed;\r\n            top: 0;\r\n            left: 0;\r\n            width: 100%;\r\n            height: 100%;\r\n            z-index: 10000;\r\n            display: none;\r\n            pointer-events: none;\r\n          }\r\n          .modern-side-cart.is-open {\r\n            display: block;\r\n            pointer-events: auto;\r\n          }\r\n          .cart-overlay {\r\n            position: fixed;\r\n            top: 0;\r\n            left: 0;\r\n            width: 100%;\r\n            height: 100%;\r\n            background: rgba(0, 0, 0, 0.5);\r\n            opacity: 0;\r\n            transition: opacity 0.3s ease;\r\n          }\r\n          .modern-side-cart.is-open .cart-overlay {\r\n            opacity: 1;\r\n          }\r\n          .modern-cart-drawer {\r\n            position: fixed;\r\n            top: 0;\r\n            right: 0;\r\n            width: 100%;\r\n            max-width: 420px;\r\n            height: 100%;\r\n            background: white;\r\n            box-shadow: -2px 0 10px rgba(0, 0, 0, 0.15);\r\n            transform: translateX(100%);\r\n            transition: transform 0.3s ease;\r\n            display: flex;\r\n            flex-direction: column;\r\n            z-index: 10001;\r\n          }\r\n          .modern-side-cart.is-open .modern-cart-drawer {\r\n            transform: translateX(0);\r\n          }\r\n          .modern-cart-content {\r\n            flex: 1;\r\n            overflow-y: auto;\r\n            -webkit-overflow-scrolling: touch;\r\n            /* Kill the iOS rubber-band. Session replays showed shoppers\r\n               dragging a drawer with nothing to scroll (31 overscroll events\r\n               in one session) and the drag state eating the following tap on\r\n               the checkout button. */\r\n            overscroll-behavior: contain;\r\n          }\r\n          .modern-cart-footer {\r\n            border-top: 1px solid #eee;\r\n            padding: 1rem;\r\n            flex-shrink: 0;\r\n            background: white;\r\n          }\r\n          .modern-checkout-btn {\r\n            width: 100%;\r\n            /* Brand navy \u2014 same dark blue used in the site header gradient\r\n               endpoint (#1e3048). Generic #333 was off-brand. It is the dark\r\n               tone of the same hue family as the #3B5998 header above\r\n               (hsl 214/41%/20% vs hsl 221/44%/41%), so the two read as one\r\n               palette with the CTA carrying the heavier weight. */\r\n            background: #1e3048;\r\n            color: white;\r\n            border: none;\r\n            padding: 1rem;\r\n            border-radius: 4px;\r\n            font-weight: 600;\r\n            cursor: pointer;\r\n            display: flex;\r\n            align-items: center;\r\n            justify-content: center;\r\n            gap: 0.5rem;\r\n            transition: background 0.2s ease;\r\n            font-size: 16px;\r\n          }\r\n          .modern-checkout-btn:hover {\r\n            background: #2B4157;\r\n          }\r\n          .modern-checkout-btn[disabled] {\r\n            opacity: 0.75;\r\n            cursor: default;\r\n          }\r\n          /* The only other way out of the drawer used to be a 22px X \u2014 the\r\n             \"Continue Shopping\" link lives inside .empty-cart-state, which is\r\n             display:none whenever the cart has something in it. On mobile the\r\n             drawer is full-width so .cart-overlay is covered and untappable,\r\n             which left shoppers with a non-empty cart no visible exit. */\r\n          .modern-continue-btn {\r\n            width: 100%;\r\n            margin-top: 8px;\r\n            min-height: 44px;\r\n            background: transparent;\r\n            color: #1e3048;\r\n            border: none;\r\n            padding: 0.75rem 1rem;\r\n            border-radius: 4px;\r\n            font-weight: 600;\r\n            font-size: 15px;\r\n            cursor: pointer;\r\n            text-decoration: underline;\r\n          }\r\n          .modern-continue-btn:hover {\r\n            background: #f4f6f8;\r\n          }\r\n          /* Suppressed while a swipe-to-close drag is in progress so the\r\n             drawer tracks the finger instead of easing behind it. */\r\n          .modern-cart-drawer.is-dragging {\r\n            transition: none !important;\r\n          }\r\n          /* Afterpay / Zip instalment messaging. Hidden until a provider\r\n             actually renders something, so an empty cart \u2014 or an uninstalled\r\n             app \u2014 leaves no orphaned gap above the checkout button. */\r\n          .cart-bnpl {\r\n            display: none;\r\n            flex-direction: column;\r\n            align-items: center;\r\n            gap: 4px;\r\n            margin-top: 10px;\r\n            text-align: center;\r\n            font-size: 12px;\r\n            line-height: 1.35;\r\n          }\r\n          .cart-bnpl.is-populated {\r\n            display: flex;\r\n          }\r\n          .cart-bnpl > * {\r\n            max-width: 100%;\r\n          }\r\n          /* The cart drawer and the mobile menu are the site's two slide-in\r\n             panels, so their headers are kept identical on purpose. Every\r\n             value here mirrors .ca-mobile-menu-header in\r\n             enhanced-mega-menu.css \u2014 change that rule and change this one.\r\n\r\n             - the gradient is a REAL two-stop gradient (slate to near-black),\r\n               unlike the flat fills elsewhere in this file\r\n             - min-height 52px gives the 44px close target below room to sit\r\n               centred without the negative-margin trick this rule used to\r\n               need, and matches the menu panel exactly\r\n             - padding-top: env(safe-area-inset-top) keeps the header clear of\r\n               the notch / dynamic island. The cart drawer previously lacked\r\n               this while the menu panel had it, so on a notched iPhone the\r\n               two panels sat at different heights. */\r\n          .modern-cart-header {\r\n            background: linear-gradient(135deg, #2d3748 0%, #1a202c 100%) !important;\r\n            padding: 0 16px !important;\r\n            padding-top: env(safe-area-inset-top) !important;\r\n            min-height: 52px !important;\r\n            display: flex !important;\r\n            align-items: center !important;\r\n          }\r\n          .modern-cart-header .header-content {\r\n            width: 100%;\r\n          }\r\n          .modern-cart-header .cart-icon-wrapper {\r\n            padding: 2px 6px !important;\r\n            gap: 8px !important;\r\n          }\r\n          .modern-cart-header .cart-icon {\r\n            width: 18px !important;\r\n            height: 18px !important;\r\n          }\r\n          .modern-cart-header .cart-title {\r\n            font-size: 15px !important;\r\n            margin: 0 !important;\r\n            font-weight: 600 !important;\r\n          }\r\n          /* 44x44 touch target (Apple HIG minimum) with the glyph still\r\n             rendering at 16px. Was 22x22, which shoppers missed \u2014 one replay\r\n             shows a tap at the exact corner where the X sits landing on the\r\n             header instead, leaving the drawer stuck open. The 52px header\r\n             above now hosts it centred with 4px clearance, so the negative\r\n             margins this needed while the header was 39px are gone. */\r\n          .modern-cart-header .modern-close-btn {\r\n            padding: 0 !important;\r\n            margin: 0 !important;\r\n            background: transparent !important;\r\n            border: 0 !important;\r\n            min-height: 0 !important;\r\n            min-width: 0 !important;\r\n            line-height: 1 !important;\r\n            display: inline-flex !important;\r\n            align-items: center !important;\r\n            justify-content: center !important;\r\n            width: 44px !important;\r\n            height: 44px !important;\r\n            color: #ffffff !important;\r\n            cursor: pointer !important;\r\n            -webkit-tap-highlight-color: rgba(255, 255, 255, 0.2);\r\n          }\r\n          .modern-cart-header .modern-close-btn svg {\r\n            width: 16px !important;\r\n            height: 16px !important;\r\n            display: block !important;\r\n          }\r\n          .modern-cart-header .header-content {\r\n            min-height: 0 !important;\r\n            line-height: 1 !important;\r\n          }\r\n          .modern-cart-header .cart-item-count {\r\n            font-size: 11px !important;\r\n            min-width: 16px !important;\r\n            height: 16px !important;\r\n            line-height: 16px !important;\r\n          }\r\n          /* Quantity is fixed on a line still being added (its edits would\r\n             have nowhere to go) and on a cut-to-length line (its Length\r\n             property must match the quantity; change it by re-adding). */\r\n          .cart-item.qty-locked .qty-btn {\r\n            opacity: 0.35;\r\n            cursor: not-allowed;\r\n          }\r\n          .cart-item.qty-locked .qty-input {\r\n            pointer-events: none;\r\n          }\r\n          @media (max-width: 768px) {\r\n            .modern-cart-drawer {\r\n              max-width: 100%;\r\n            }\r\n            .modern-checkout-btn {\r\n              padding: 1.25rem;\r\n              font-size: 18px;\r\n            }\r\n          }\r\n        </style>\r\n        <div class=\"modern-side-cart\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"ModernCartHeading\">\r\n          <div class=\"cart-overlay\"></div>\r\n          <div class=\"modern-cart-drawer\">\r\n            <div class=\"side-cart-header enhanced-header modern-cart-header\">\r\n              <div class=\"header-content\">\r\n                <div class=\"cart-icon-wrapper\">\r\n                  <svg class=\"cart-icon\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n                    <circle cx=\"9\" cy=\"21\" r=\"1\"></circle>\r\n                    <circle cx=\"20\" cy=\"21\" r=\"1\"></circle>\r\n                    <path d=\"m1 1 4 4.07L9 19a2 2 0 0 0 2 1.61h7.39a2 2 0 0 0 2-1.61L23 6H6\"></path>\r\n                  </svg>\r\n                  <span class=\"cart-item-count\">0</span>\r\n                </div>\r\n                <h2 class=\"cart-title\" id=\"ModernCartHeading\">Your Cart</h2>\r\n                <button class=\"close enhanced-close modern-close-btn\" aria-label=\"Close cart\">\r\n                  <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\r\n                    <path d=\"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z\"/>\r\n                  </svg>\r\n                </button>\r\n              </div>\r\n            </div>\r\n            <div class=\"modern-cart-content\">\r\n              <div class=\"cart-items-container\"></div>\r\n              <div class=\"empty-cart-state\" style=\"display: none;\">\r\n                <div class=\"empty-content\" style=\"text-align:center;padding:40px 20px;\">\r\n                  <svg width=\"80\" height=\"80\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1\">\r\n                    <circle cx=\"9\" cy=\"21\" r=\"1\"></circle>\r\n                    <circle cx=\"20\" cy=\"21\" r=\"1\"></circle>\r\n                    <path d=\"M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6\"></path>\r\n                  </svg>\r\n                  <h3>Your cart is empty</h3>\r\n                  <p>Discover our premium camping gear and start your adventure!</p>\r\n                  <a href=\"/collections/all\" class=\"continue-shopping-btn\">Continue Shopping</a>\r\n                </div>\r\n              </div>\r\n            </div>\r\n            <div class=\"modern-cart-footer\">\r\n              <div class=\"cart-summary\">\r\n                <div class=\"subtotal-row\" style=\"display:flex;justify-content:space-between;margin-bottom:8px;\">\r\n                  <span class=\"subtotal-label\">Subtotal</span>\r\n                  <span class=\"subtotal-amount\">$0.00</span>\r\n                </div>\r\n                <div class=\"shipping-note\">\r\n                  <small>Shipping calculated at checkout</small>\r\n                </div>\r\n              </div>\r\n              <div class=\"cart-bnpl\" data-cart-bnpl></div>\r\n              <div class=\"checkout-actions\" style=\"margin-top:12px;\">\r\n                <button type=\"button\" class=\"modern-checkout-btn\">\r\n                  <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"currentColor\" style=\"margin-right: 8px;\">\r\n                    <path d=\"M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zM9 6c0-1.66 1.34-3 3-3s3 1.34 3 3v2H9V6z\"/>\r\n                  </svg>\r\n                  <span class=\"modern-checkout-btn__label\">CHECKOUT SECURELY</span>\r\n                  <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"currentColor\">\r\n                    <path d=\"M8 1l6 7-6 7V9H2V7h6V1z\"/>\r\n                  </svg>\r\n                </button>\r\n                <button type=\"button\" class=\"modern-continue-btn\">Continue shopping</button>\r\n              </div>\r\n            </div>\r\n          </div>\r\n        </div>\r\n      `;\r\n\r\n      document.body.insertAdjacentHTML('beforeend', cartHTML);\r\n      \r\n      this.cartDrawer = document.querySelector('.modern-side-cart');\r\n      this.cartOverlay = document.querySelector('.cart-overlay');\r\n      this.cartItemsContainer = document.querySelector('.cart-items-container');\r\n    }\r\n\r\n    setupEventListeners() {\r\n      document.addEventListener('click', (e) => {\r\n        // Handle add to cart buttons - FIXED for type=\"button\"\r\n        const addToCartBtn = e.target.closest('[data-cart-add], .btn.product-form__cart-submit, [name=\"add\"], .product-card__add-to-cart, .add-to-cart');\r\n        if (addToCartBtn && (addToCartBtn.type === 'submit' || addToCartBtn.hasAttribute('data-cart-add') || addToCartBtn.classList.contains('product-card__add-to-cart') || addToCartBtn.classList.contains('add-to-cart'))) {\r\n          e.preventDefault();\r\n          // Busy/debounce guarding lives INSIDE handleAddToCart, which owns\r\n          // the cartBusy flag end-to-end. Setting it here left the button\r\n          // permanently dead whenever a guard in the handler returned early\r\n          // (nothing ever cleared the flag) \u2014 the \"taps do nothing until\r\n          // reload\" sessions in analytics.\r\n          this.handleAddToCart(e);\r\n          return;\r\n        }\r\n        \r\n        // Cart toggle buttons - EXPANDED selectors\r\n        const cartToggleSelectors = [\r\n          '[data-action=\"toggle-cart\"]',\r\n          '.cart-count',\r\n          '.cart-icon',\r\n          '.icon-cart', \r\n          '.cart-icon-wrapper',\r\n          '.header-cart',\r\n          '.cart-link',\r\n          '.cart-toggle',\r\n          '.mini-cart-toggle',\r\n          '.cart-drawer-toggle',\r\n          '[data-cart-toggle]',\r\n          '[data-action=\"open-cart\"]',\r\n          '.js-cart-toggle',\r\n          '.header__cart',\r\n          '.site-header__cart',\r\n          '.cart-summary',\r\n          '.cart-button',\r\n          'svg[class*=\"cart\"]',\r\n          'svg[class*=\"bag\"]',\r\n          '[class*=\"cart-icon\"]',\r\n          '[class*=\"bag-icon\"]',\r\n          'a[href=\"/cart\"]',\r\n          'a[href*=\"/cart\"]'\r\n        ];\r\n        \r\n        const cartToggle = e.target.closest(cartToggleSelectors.join(', '));\r\n        if (cartToggle) {\r\n          if (cartToggle.href && cartToggle.href.includes('/cart')) {\r\n            e.preventDefault();\r\n          }\r\n          e.preventDefault();\r\n          e.stopPropagation();\r\n          this.openCart({ trackView: true });\r\n          return;\r\n        }\r\n        \r\n        // Close cart\r\n        if (e.target.closest('.modern-close-btn, .cart-overlay, .modern-continue-btn')) {\r\n          e.preventDefault();\r\n          this.closeCart();\r\n          return;\r\n        }\r\n        \r\n        // Direct checkout button handler (more responsive)\r\n        const checkoutBtn = e.target.closest('.modern-checkout-btn, .checkout-btn, .enhanced-checkout');\r\n        if (checkoutBtn) {\r\n          e.preventDefault();\r\n          e.stopPropagation();\r\n          this.handleCheckout();\r\n          return;\r\n        }\r\n        \r\n        // Cart interactions (only inside cart drawer)\r\n        if (e.target.closest('.modern-side-cart')) {\r\n          if (e.target.closest('.qty-btn')) {\r\n            e.preventDefault();\r\n            this.handleQuantityChange(e);\r\n            return;\r\n          }\r\n          \r\n          if (e.target.closest('.remove-item')) {\r\n            e.preventDefault();\r\n            this.handleRemoveItem(e);\r\n            return;\r\n          }\r\n        }\r\n      });\r\n\r\n      // Capture-phase submit interceptor \u2014 stops the legacy jQuery\r\n      // formOverride handler in colors.js from firing /cart/add.js\r\n      // a SECOND time when the product form is submitted via routes\r\n      // our click handler doesn't see (Enter key in qty input, autofill\r\n      // submit, programmatic form.submit()). Capture phase guarantees\r\n      // we run before the jQuery delegated handler bound in bubble phase.\r\n      //\r\n      // IMPORTANT: match e.target directly with matches(), NOT closest().\r\n      // Shopify renders app blocks (ship-planner freight calculator,\r\n      // others) INSIDE the product form, technically nesting their own\r\n      // <form> elements. closest() would walk up from a nested form's\r\n      // submit and incorrectly match the outer product form, routing\r\n      // freight-calculator submits into add-to-cart. matches() only fires\r\n      // when the submitting form IS the product form.\r\n      document.addEventListener('submit', (e) => {\r\n        const form = e.target;\r\n        if (!form || form.tagName !== 'FORM') return;\r\n        if (!form.matches('form.shopify-product-form, form[action*=\"/cart/add\"]')) return;\r\n        e.preventDefault();\r\n        e.stopImmediatePropagation();\r\n\r\n        // Synthesize a target so handleAddToCart can locate form + button.\r\n        const addBtn = form.querySelector(\r\n          '[data-cart-add], .btn.product-form__cart-submit, [name=\"add\"], .add-to-cart'\r\n        );\r\n        if (!addBtn) return;\r\n        this.handleAddToCart({ target: addBtn, preventDefault: () => {} });\r\n      }, true);\r\n\r\n      // Quantity input changes\r\n      document.addEventListener('change', (e) => {\r\n        if (e.target.matches('.qty-input') && e.target.closest('.modern-side-cart')) {\r\n          this.handleQuantityInputChange(e);\r\n        }\r\n      });\r\n\r\n      // Keyboard navigation\r\n      document.addEventListener('keydown', (e) => {\r\n        if (e.key === 'Escape' && this.isOpen) {\r\n          this.closeCart();\r\n        }\r\n      });\r\n\r\n      // Swipe-right-to-close. The drawer enters from the right, so pushing it\r\n      // back off-screen is the gesture shoppers already try \u2014 replays show\r\n      // repeated horizontal drags inside an open drawer that did nothing,\r\n      // because on mobile the drawer is full-width and there is no overlay\r\n      // left to tap. The drag only engages once the gesture is clearly\r\n      // horizontal, so vertical scrolling of the item list is untouched.\r\n      this.setupSwipeToClose();\r\n\r\n      // Android's back button / iOS's back-swipe should dismiss the drawer,\r\n      // not navigate the shopper off the product page. openCart pushes a\r\n      // history entry that closeCart unwinds.\r\n      //\r\n      // Decided by the landed-on state, NOT by a \"we caused this\" flag:\r\n      // history.back() fires popstate asynchronously, so a flag set while\r\n      // closing can still be armed after the shopper has reopened the drawer,\r\n      // and would then swallow their next real back press. Landing on an\r\n      // entry that is still ours means nothing needs closing; landing off it\r\n      // with the drawer open means they pressed back.\r\n      window.addEventListener('popstate', () => {\r\n        // If this pop is our own close unwinding, _settleUnwind may re-push\r\n        // the entry (shopper reopened mid-flight) and consume the event.\r\n        if (this._settleUnwind()) return;\r\n        if (history.state && history.state.modernCartOpen) return;\r\n        if (this.isOpen) this.closeCart({ fromPopState: true });\r\n      });\r\n\r\n      // A tab restored from the back/forward cache or re-focused hours later\r\n      // carries a stale in-memory cart (items bought/expired elsewhere).\r\n      // Re-sync from the server so the drawer, the badge counts and the\r\n      // checkout gate all reflect reality \u2014 the stale copy is how shoppers\r\n      // ended up bounced onto an \"empty cart\" page they never navigated to.\r\n      // resetCheckoutButton matters here too: a shopper who taps CHECKOUT and\r\n      // then comes back would otherwise land on a page whose primary CTA is\r\n      // still disabled and reading \"REDIRECTING\u2026\".\r\n      window.addEventListener('pageshow', (e) => {\r\n        if (e.persisted) {\r\n          this.resetCheckoutButton();\r\n          this.refreshCart();\r\n        }\r\n      });\r\n      document.addEventListener('visibilitychange', () => {\r\n        if (document.visibilityState === 'visible') {\r\n          this.resetCheckoutButton();\r\n          this.refreshCart();\r\n        }\r\n      });\r\n    }\r\n\r\n    setupSwipeToClose() {\r\n      const panel = this.cartDrawer && this.cartDrawer.querySelector('.modern-cart-drawer');\r\n      if (!panel) return;\r\n\r\n      const ENGAGE = 12;    // px of travel before we commit to an axis\r\n      const THRESHOLD = 60; // px of rightward travel that closes the drawer\r\n      let startX = 0;\r\n      let startY = 0;\r\n      let dx = 0;\r\n      let tracking = false;\r\n      let engaged = false;\r\n\r\n      panel.addEventListener('touchstart', (e) => {\r\n        if (!this.isOpen || e.touches.length !== 1) return;\r\n        startX = e.touches[0].clientX;\r\n        startY = e.touches[0].clientY;\r\n        dx = 0;\r\n        tracking = true;\r\n        engaged = false;\r\n      }, { passive: true });\r\n\r\n      panel.addEventListener('touchmove', (e) => {\r\n        if (!tracking || e.touches.length !== 1) return;\r\n        dx = e.touches[0].clientX - startX;\r\n        const dy = e.touches[0].clientY - startY;\r\n\r\n        if (!engaged) {\r\n          if (Math.abs(dx) < ENGAGE && Math.abs(dy) < ENGAGE) return;\r\n          // Mostly vertical, or heading left \u2014 leave it to the item scroller.\r\n          if (dx <= 0 || Math.abs(dx) <= Math.abs(dy)) {\r\n            tracking = false;\r\n            return;\r\n          }\r\n          engaged = true;\r\n          panel.classList.add('is-dragging');\r\n        }\r\n\r\n        panel.style.transform = `translateX(${Math.max(0, dx)}px)`;\r\n      }, { passive: true });\r\n\r\n      const settle = () => {\r\n        if (!tracking) return;\r\n        const commit = engaged && dx > THRESHOLD;\r\n        tracking = false;\r\n        engaged = false;\r\n        panel.classList.remove('is-dragging');\r\n        panel.style.transform = '';\r\n        if (commit) this.closeCart();\r\n      };\r\n\r\n      panel.addEventListener('touchend', settle, { passive: true });\r\n      panel.addEventListener('touchcancel', settle, { passive: true });\r\n    }\r\n\r\n    // ---- Cart writes and reads ------------------------------------------\r\n\r\n    // A request that never answers would otherwise hold the write queue \u2014\r\n    // and with it every later add, edit and the checkout button \u2014 for the\r\n    // life of the page.\r\n    _fetchWithTimeout(url, options = {}, ms = 15000) {\r\n      if (typeof AbortController !== 'function') return fetch(url, options);\r\n      const controller = new AbortController();\r\n      const timer = setTimeout(() => controller.abort(), ms);\r\n      return fetch(url, Object.assign({}, options, { signal: controller.signal }))\r\n        .finally(() => clearTimeout(timer));\r\n    }\r\n\r\n    // Runs task after every write queued before it. Resolves or rejects with\r\n    // the task's own result; a failed write never blocks the ones behind it.\r\n    _enqueueWrite(task) {\r\n      this._writesPending++;\r\n      this._writeGen++;\r\n      const run = this._writeChain.then(() => task());\r\n      this._writeChain = run.then(() => {}, () => {}).then(() => {\r\n        this._writesPending--;\r\n        this._writeGen++;\r\n      });\r\n      return run;\r\n    }\r\n\r\n    // Reads /cart.js. Resolves to the cart, or null when the answer is out of\r\n    // date: a write was queued or landed while the read was on the wire, or\r\n    // quantity edits are still waiting to be sent. Whichever write that was\r\n    // brings its own fresh cart, so dropping this one loses nothing.\r\n    async _fetchCart() {\r\n      const gen = this._writeGen;\r\n      const response = await this._fetchWithTimeout('/cart.js', {\r\n        headers: { 'Accept': 'application/json' }\r\n      });\r\n      if (!response.ok) throw new Error(`Cart read failed: ${response.status}`);\r\n      const cart = await response.json();\r\n      if (gen !== this._writeGen || this._writesPending ||\r\n          Object.keys(this._pendingQty).length) {\r\n        return null;\r\n      }\r\n      return cart;\r\n    }\r\n\r\n    // Resolves true when the drawer now shows the server cart.\r\n    async _syncFromServer() {\r\n      try {\r\n        const cart = await this._fetchCart();\r\n        if (!cart) return false;\r\n        this._applyServerCart(cart);\r\n        this.renderCart();\r\n        this.updateCartCounts();\r\n        return true;\r\n      } catch (error) {\r\n        console.error('Cart refresh failed:', error);\r\n        return false;\r\n      }\r\n    }\r\n\r\n    // Replaces the cached cart with one from the server, keeping on top of it\r\n    // what the server does not have yet: debounced quantity edits and adds\r\n    // still queued or on the wire. Without this, a quantity response landing\r\n    // between two adds blanked the second one's line until it finished.\r\n    _applyServerCart(cart) {\r\n      const next = Object.assign({}, cart);\r\n      next.items = Array.isArray(cart && cart.items) ? cart.items.slice() : [];\r\n      let overlaid = false;\r\n      Object.keys(this._pendingQty).forEach(key => {\r\n        const line = next.items.find(it => String(it.key) === key);\r\n        if (!line) return;\r\n        line.quantity = this._pendingQty[key];\r\n        line.line_price = (line.price || 0) * line.quantity;\r\n        overlaid = true;\r\n      });\r\n      this._pendingAdds.forEach(add => {\r\n        if (add.placeholder) {\r\n          next.items.push(add.placeholder);\r\n          overlaid = true;\r\n          return;\r\n        }\r\n        const line = add.bumpKey && next.items.find(it => String(it.key) === add.bumpKey);\r\n        if (!line) return;\r\n        line.quantity = (line.quantity || 0) + add.quantity;\r\n        line.line_price = (line.price || 0) * line.quantity;\r\n        overlaid = true;\r\n      });\r\n      this.cart = next;\r\n      // The server's own totals include discounts; only recompute when the\r\n      // lines above changed.\r\n      if (overlaid) this._recomputeTotals();\r\n    }\r\n\r\n    _recomputeTotals() {\r\n      this.cart.item_count = this.cart.items.reduce((n, it) => n + (it.quantity || 0), 0);\r\n      this.cart.total_price = this.cart.items.reduce((n, it) => n + (it.line_price || it.final_line_price || 0), 0);\r\n    }\r\n\r\n    // Undo one failed add's optimistic change, leaving other adds alone.\r\n    _rollbackAdd(add) {\r\n      if (add.placeholder) {\r\n        this.cart.items = this.cart.items.filter(it => it !== add.placeholder);\r\n      } else if (add.bumpKey) {\r\n        const line = this._itemByKey(add.bumpKey);\r\n        if (line) {\r\n          line.quantity = Math.max(0, (line.quantity || 0) - add.quantity);\r\n          line.line_price = (line.price || 0) * line.quantity;\r\n        }\r\n      }\r\n      this._recomputeTotals();\r\n    }\r\n\r\n    // Build a synthetic line item from window.product + form data. Used\r\n    // for the truly-optimistic pre-fetch render so the drawer reflects\r\n    // the new item within one frame instead of after the network round-trip.\r\n    _buildOptimisticItem(variantId, quantity) {\r\n      try {\r\n        const product = window.product;\r\n        if (!product || !Array.isArray(product.variants)) return null;\r\n        const variant = product.variants.find(v => String(v.id) === String(variantId));\r\n        if (!variant) return null;\r\n        const qty = parseInt(quantity) || 1;\r\n        const price = variant.price || product.price || 0;\r\n        // Find the currently visible product image so the drawer thumbnail\r\n        // matches what the user just looked at.\r\n        let imageUrl = '';\r\n        const visible = document.querySelector('[data-product-single-media-wrapper]:not(.hide) img');\r\n        if (visible) imageUrl = visible.currentSrc || visible.src;\r\n        if (!imageUrl && variant.featured_image && variant.featured_image.src) {\r\n          imageUrl = variant.featured_image.src;\r\n        }\r\n        if (!imageUrl && product.featured_image) {\r\n          imageUrl = typeof product.featured_image === 'string'\r\n            ? product.featured_image\r\n            : (product.featured_image.src || '');\r\n        }\r\n        return {\r\n          _optimistic: true,\r\n          id: variant.id,\r\n          variant_id: variant.id,\r\n          key: 'optimistic-' + variant.id + '-' + Date.now(),\r\n          quantity: qty,\r\n          price: price,\r\n          line_price: price * qty,\r\n          final_line_price: price * qty,\r\n          original_price: price,\r\n          original_line_price: price * qty,\r\n          product_id: product.id,\r\n          product_title: product.title,\r\n          product_handle: product.handle,\r\n          variant_title: variant.title === 'Default Title' ? '' : variant.title,\r\n          title: product.title + (variant.title && variant.title !== 'Default Title' ? ' - ' + variant.title : ''),\r\n          sku: variant.sku || '',\r\n          vendor: product.vendor || '',\r\n          url: '/products/' + (product.handle || ''),\r\n          image: imageUrl,\r\n          featured_image: imageUrl ? { url: imageUrl } : null\r\n        };\r\n      } catch (err) {\r\n        console.error('_buildOptimisticItem failed:', err);\r\n        return null;\r\n      }\r\n    }\r\n\r\n    async cleanupDuplicates() {\r\n      // Merge only PLAIN duplicate lines of the same variant (a leftover of\r\n      // the old double-add bug). Lines with properties or selling plans \u2014\r\n      // bundle components, app-added lines \u2014 legitimately share a variant\r\n      // across lines and must never be touched. All mutations go by line\r\n      // KEY: the old change-by-variant-id call was ambiguous with multiple\r\n      // lines, so Shopify set the FIRST line to the summed quantity while\r\n      // the duplicate survived \u2014 inflating the cart on every page load.\r\n      const isPlain = it => !it.selling_plan_allocation &&\r\n        (!it.properties || Object.keys(it.properties).length === 0);\r\n\r\n      // The cached quantities must be the server's to sum them. A shopper\r\n      // who is already adding or editing wins; the merge waits for the next\r\n      // page load.\r\n      if (this._pendingAdds.size || Object.keys(this._pendingQty).length) return;\r\n\r\n      const byVariant = {};\r\n      this.cart.items.forEach(item => {\r\n        if (!isPlain(item)) return;\r\n        const vid = String(item.variant_id);\r\n        (byVariant[vid] = byVariant[vid] || []).push(item);\r\n      });\r\n      const dupGroups = Object.values(byVariant).filter(group => group.length > 1);\r\n      if (!dupGroups.length) return;\r\n\r\n      // One /cart/update.js call does the whole merge (updates is a map of\r\n      // line-key \u2192 quantity, response is the full updated cart) \u2014 the old\r\n      // per-line change.js loop cost 2\u00D7N sequential ~1.3s round-trips plus\r\n      // a trailing /cart.js GET.\r\n      const updates = {};\r\n      for (const group of dupGroups) {\r\n        const total = group.reduce((n, it) => n + (it.quantity || 0), 0);\r\n        group.slice(1).forEach(extra => { updates[extra.key] = 0; });\r\n        updates[group[0].key] = total;\r\n      }\r\n      const response = await this._fetchWithTimeout('/cart/update.js', {\r\n        method: 'POST',\r\n        headers: { 'Content-Type': 'application/json' },\r\n        body: JSON.stringify({ updates })\r\n      });\r\n      if (response.ok) {\r\n        this._applyServerCart(await response.json());\r\n        this.renderCart();\r\n        this.updateCartCounts();\r\n      }\r\n    }\r\n\r\n    // Line item properties and selling plan from the product form. The add\r\n    // used to post only id + quantity, silently dropping these \u2014 cut-to-length\r\n    // orders lost their \"Length\", and app-injected properties never arrived.\r\n    // properties[_source] is skipped: it is an unused tag on collection cards,\r\n    // and sending it would split a card add and a PDP add of the same item\r\n    // into two cart lines.\r\n    _lineExtrasFromForm(formData) {\r\n      const properties = {};\r\n      let sellingPlan = null;\r\n      for (const [name, value] of formData.entries()) {\r\n        if (name === 'selling_plan') {\r\n          if (value) sellingPlan = value;\r\n          continue;\r\n        }\r\n        const match = /^properties\\[(.+)\\]$/.exec(name);\r\n        if (!match || match[1] === '_source') continue;\r\n        if (typeof value !== 'string' || value === '') continue;\r\n        properties[match[1]] = value;\r\n      }\r\n      // Cut-to-length: the quantity IS the metres and Length restates it.\r\n      // Shopify merges adds with identical properties, so two 7 m cuts became\r\n      // one line of 14 labelled \"7 m\". A private per-add id keeps every cut\r\n      // its own line, so Length always matches that line's quantity.\r\n      if (properties.Length) {\r\n        properties._cut = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);\r\n      }\r\n      return { properties, sellingPlan };\r\n    }\r\n\r\n    // A line's quantity can't be edited in the drawer while it is still an\r\n    // optimistic placeholder (no server line to update yet), while an add is\r\n    // still raising it, or when it is a cut-to-length line (Length must keep\r\n    // matching the quantity).\r\n    _qtyLocked(item) {\r\n      if (!item) return false;\r\n      if (item._optimistic || (item.properties && item.properties.Length)) return true;\r\n      for (const add of this._pendingAdds) {\r\n        if (add.bumpKey === String(item.key)) return true;\r\n      }\r\n      return false;\r\n    }\r\n\r\n    _escapeHtml(value) {\r\n      const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' };\r\n      return String(value).replace(/[&<>\"']/g, ch => map[ch]);\r\n    }\r\n\r\n    // Visible line item properties (Length, bundle options). Underscore-\r\n    // prefixed ones are private and stay hidden, as they do at checkout.\r\n    _propertiesHtml(item) {\r\n      const props = (item && item.properties) || {};\r\n      return Object.keys(props)\r\n        .filter(name => name.charAt(0) !== '_' && props[name] !== '' && props[name] != null)\r\n        .map(name => `<div class=\"item-variant\">${this._escapeHtml(name)}: ${this._escapeHtml(props[name])}</div>`)\r\n        .join('');\r\n    }\r\n\r\nasync handleAddToCart(e) {\r\n      if (!e || !e.target) {\r\n        console.error('Invalid event passed to handleAddToCart:', e);\r\n        return;\r\n      }\r\n\r\n      const addBtn = e.target.closest('[data-cart-add], .btn.product-form__cart-submit, [name=\"add\"], .product-card__add-to-cart, .add-to-cart');\r\n\r\n      if (!addBtn) {\r\n        console.error('No add to cart button found');\r\n        return;\r\n      }\r\n\r\n      // Re-entrancy guard \u2014 checked here, set just before the fetch below,\r\n      // ALWAYS cleared in the finally. No early-return path may run after\r\n      // the flag is set, or the button dies for the life of the page.\r\n      if (addBtn.disabled || addBtn.dataset.cartBusy === '1') return;\r\n\r\n      // A button that names its own variant (data-variant-id) adds that, with\r\n      // no form needed: section buttons, comparison tables, accessories and\r\n      // the add() API. Otherwise the enclosing product form supplies it.\r\n      let variantId;\r\n      let quantity;\r\n      let lineExtras;\r\n      if (addBtn.dataset.variantId) {\r\n        variantId = addBtn.dataset.variantId;\r\n        quantity = parseInt(addBtn.dataset.quantity) || 1;\r\n        lineExtras = { properties: {}, sellingPlan: null };\r\n      } else {\r\n        const form = e.target.closest('form');\r\n        if (!form) {\r\n          console.error('No form found for add to cart');\r\n          return;\r\n        }\r\n        const formData = new FormData(form);\r\n        variantId = formData.get('id'); // Keep as string\r\n        quantity = parseInt(formData.get('quantity')) || 1;\r\n        lineExtras = this._lineExtrasFromForm(formData);\r\n      }\r\n\r\n      if (!variantId) {\r\n        console.error('No variant ID found');\r\n        return;\r\n      }\r\n\r\n      // Buttons outside the main product form (accessory rows) opt out of\r\n      // the drawer popping open, so a shopper can add several in a row.\r\n      const openDrawer = addBtn.dataset.cartOpen !== 'false';\r\n      const isPlainAdd = !lineExtras.sellingPlan &&\r\n        Object.keys(lineExtras.properties).length === 0;\r\n\r\n      // Per-variant time debounce \u2014 kills the mobile double-tap on the same\r\n      // product (3 product_added in 6s with cart-token rotation, per session\r\n      // replays) WITHOUT swallowing a legitimate quick-add of a different\r\n      // product. The old single global timestamp silently dropped the second\r\n      // of two different products added within 1.5s.\r\n      const now = Date.now();\r\n      this._lastAddTimes = this._lastAddTimes || {};\r\n      const lastAdd = this._lastAddTimes[variantId];\r\n      if (lastAdd && (now - lastAdd) < 1500) {\r\n        console.log('Add-to-cart debounced (' + (now - lastAdd) + 'ms since last add of this variant)');\r\n        return;\r\n      }\r\n      this._lastAddTimes[variantId] = now;\r\n\r\n      const originalText = addBtn.textContent;\r\n      addBtn.textContent = 'Adding...';\r\n      addBtn.disabled = true;\r\n\r\n      // True-optimistic update: synthesize the line item from window.product\r\n      // and the form, drop it into the drawer BEFORE firing the fetch. Result\r\n      // is the new item appears within ~50ms of the click instead of waiting\r\n      // ~500-1500ms for the round-trip. The fetch still runs in the background;\r\n      // on success the synthetic line is replaced with the canonical one,\r\n      // on failure it's rolled back.\r\n      const optimisticItem = this._buildOptimisticItem(variantId, quantity);\r\n      const pendingAdd = { quantity: parseInt(quantity), placeholder: null, bumpKey: null };\r\n      if (optimisticItem) {\r\n        if (!Array.isArray(this.cart.items)) this.cart.items = [];\r\n        // Only a plain add merges into a plain line; an add carrying\r\n        // properties or a selling plan becomes its own line on the server.\r\n        const existingExact = isPlainAdd && this.cart.items.find(\r\n          it => String(it.variant_id) === String(variantId) && !it._optimistic &&\r\n            !it.selling_plan_allocation &&\r\n            (!it.properties || Object.keys(it.properties).length === 0)\r\n        );\r\n        if (existingExact) {\r\n          existingExact.quantity = (existingExact.quantity || 0) + pendingAdd.quantity;\r\n          existingExact.line_price = (existingExact.price || 0) * existingExact.quantity;\r\n          pendingAdd.bumpKey = String(existingExact.key);\r\n        } else {\r\n          if (!isPlainAdd) optimisticItem.properties = lineExtras.properties;\r\n          this.cart.items.push(optimisticItem);\r\n          pendingAdd.placeholder = optimisticItem;\r\n        }\r\n        this._recomputeTotals();\r\n      }\r\n      this._pendingAdds.add(pendingAdd);\r\n      if (openDrawer) {\r\n        try { this.openCart(); } catch (uiErr) { console.error('openCart (early) failed:', uiErr); }\r\n      }\r\n      try { this.renderCart(); } catch (rErr) { console.error('renderCart (optimistic) failed:', rErr); }\r\n      try { this.updateCartCounts(); } catch (cErr) { console.error('updateCartCounts (optimistic) failed:', cErr); }\r\n\r\n      // Everything between the guards above and here is synchronous, so no\r\n      // second tap can interleave; from this point the finally owns cleanup.\r\n      addBtn.dataset.cartBusy = '1';\r\n      let addResult = false;\r\n\r\n      try {\r\n        // Always /cart/add.js, never change.js with a quantity computed from\r\n        // our cached cart: the server increments and merges identical lines\r\n        // itself, so a stale cache (another tab, an add from outside the\r\n        // drawer) can no longer overwrite \u2014 or even lower \u2014 the real quantity.\r\n        const line = { id: variantId, quantity: parseInt(quantity) };\r\n        if (!isPlainAdd) {\r\n          line.properties = lineExtras.properties;\r\n          if (lineExtras.sellingPlan) line.selling_plan = lineExtras.sellingPlan;\r\n        }\r\n        // Queued behind any earlier add or quantity edit. The body is read\r\n        // inside the task so the write counts as finished only once it has.\r\n        const { response, result } = await this._enqueueWrite(async () => {\r\n          const res = await this._fetchWithTimeout('/cart/add.js', {\r\n            method: 'POST',\r\n            headers: {\r\n              'Content-Type': 'application/json',\r\n              'Accept': 'application/json'\r\n            },\r\n            body: JSON.stringify({ items: [line] })\r\n          });\r\n          return { response: res, result: await res.json().catch(() => ({})) };\r\n        });\r\n\r\n        if (!response.ok) {\r\n          console.error('Add to cart failed:', {\r\n            status: response.status,\r\n            statusText: response.statusText,\r\n            result: result\r\n          });\r\n\r\n          // Shopify puts the reason in message, description, or both (a bare\r\n          // \"Cart Error\" message with the detail in description), so read both.\r\n          const rawMessage = [result.message, result.description]\r\n            .filter(part => typeof part === 'string' && part)\r\n            .filter((part, i, parts) => parts.indexOf(part) === i)\r\n            .join(' ');\r\n          let errorMessage = 'Unable to add item to cart.';\r\n          if (rawMessage) {\r\n            // Clean up technical error messages for users\r\n            if (rawMessage.includes('expected integer') || rawMessage.includes('(?-mix')) {\r\n              errorMessage = 'There was an issue adding this item. Please try again.';\r\n            } else if (rawMessage.includes('inventory')) {\r\n              errorMessage = 'This item is currently out of stock.';\r\n            } else if (rawMessage.includes('maximum quantity')) {\r\n              errorMessage = 'You have reached the maximum quantity for this item.';\r\n            } else {\r\n              errorMessage = rawMessage;\r\n            }\r\n          } else if (response.status === 422) {\r\n            errorMessage = 'The maximum quantity of this item is already in your cart.';\r\n          } else if (response.status === 400) {\r\n            errorMessage = 'There was an issue processing your request. Please try again.';\r\n          }\r\n\r\n          throw new Error(errorMessage);\r\n        }\r\n        // NOTE: do NOT explicitly merge the POST /cart/add.js response\r\n        // here. The response shape varies (single item vs batch) and the\r\n        // explicit merge was producing a transient empty-cart flash \u2014\r\n        // user reported \"item appeared, drawer went empty, then button\r\n        // turned green\". The optimistic synthetic line is already in\r\n        // this.cart.items and rendering correctly; the read in the finally\r\n        // below replaces it with the canonical server state.\r\n\r\n        addResult = true;\r\n        addBtn.textContent = 'Added \u2713';\r\n        addBtn.style.background = '#10b981'; // Green during success\r\n        addBtn.style.color = 'white';\r\n        addBtn.style.border = '2px solid #10b981';\r\n\r\n      } catch (error) {\r\n        console.error('Add to cart error:', error);\r\n        // Roll back this add's optimistic change so the drawer matches\r\n        // reality. Other adds still on the wire keep theirs.\r\n        try { this._rollbackAdd(pendingAdd); } catch (e) { /* noop */ }\r\n        try { this.renderCart(); } catch (e) { /* noop */ }\r\n        try { this.updateCartCounts(); } catch (e) { /* noop */ }\r\n\r\n        // A stock ceiling \u2014 sold out, or the max (often the last unit on a\r\n        // DENY-policy variant) is already in the cart \u2014 is an EXPECTED outcome,\r\n        // not a site fault. Shopify 422s these. Present them calmly inline: a\r\n        // neutral state and NO blocking alert(). The old red \"Error - Try again\"\r\n        // + alert() made a normal out-of-stock read as a broken site, especially\r\n        // against the PDP \"In stock \u2014 ships from our warehouse\" dispatch badge \u2014\r\n        // which is exactly what the recorded rod-holder session showed.\r\n        const msg = (error && error.message) || '';\r\n        // Every /cart/add.js 422 is a stock/quantity ceiling, and Shopify phrases\r\n        // it several ways (\"maximum quantity\u2026\", \"only N left in stock\",\r\n        // \"All N are in your cart\", \"can't add more\u2026\"). Match them all so a normal\r\n        // out-of-stock never renders as a technical error.\r\n        const isStockLimit = /out of stock|in stock|maximum|already in your cart|in your cart|insufficient|sold out|only \\d+ (left|available)|can'?t add|cannot add|can only add/i.test(msg);\r\n\r\n        if (isStockLimit) {\r\n          const soldOut = /out of stock|insufficient|sold out/i.test(msg);\r\n          addBtn.textContent = soldOut ? 'Sold out' : 'Max in cart';\r\n          addBtn.style.background = 'transparent';\r\n          addBtn.style.color = '#6b7280';        // neutral grey \u2014 not alarm red\r\n          addBtn.style.border = '1px solid #d1d5db';\r\n        } else {\r\n          // Genuine failure (network / 400 / 5xx / parse). Keep it visible on the\r\n          // button, but drop the blocking alert() \u2014 a modal popup on the\r\n          // storefront is its own UX bug.\r\n          addBtn.textContent = 'Error - Try again';\r\n          addBtn.style.background = 'transparent';\r\n          addBtn.style.color = '#ef4444';\r\n          addBtn.style.border = '1px solid #ef4444';\r\n        }\r\n      } finally {\r\n        this._pendingAdds.delete(pendingAdd);\r\n        // Reconcile to the server cart, success or not: it swaps the\r\n        // placeholder for the real line, and after a timed-out add shows\r\n        // whether it landed after all. If another write is still queued\r\n        // the read stands down and that write's own read does this.\r\n        // The 50ms lets Shopify's cart cookie commit before /cart.js reads.\r\n        setTimeout(() => this._syncFromServer(), 50);\r\n        // Release the soft-busy flag so the next legitimate click can fire.\r\n        if (addBtn) delete addBtn.dataset.cartBusy;\r\n        setTimeout(() => {\r\n          addBtn.textContent = originalText;\r\n          addBtn.disabled = false;\r\n          addBtn.style.background = ''; // Reset to CSS default\r\n          addBtn.style.color = ''; // Reset to CSS default  \r\n          addBtn.style.border = ''; // Reset to CSS default\r\n        }, 2000);\r\n      }\r\n      return addResult;\r\n    }\r\n\r\n    // Public entry point for every other add-to-cart on the site, so the\r\n    // drawer, counts and checkout wait all see the add. Resolves true once\r\n    // the server has the line, false if it was refused (stock limit, error),\r\n    // undefined if it was ignored as a duplicate tap.\r\n    //   button     \u2014 element that shows Adding\u2026/Added \u2713 (optional)\r\n    //   openDrawer \u2014 false keeps the drawer shut (accessory rows)\r\n    add({ variantId, quantity = 1, button = null, openDrawer = true } = {}) {\r\n      if (!variantId) return Promise.resolve(false);\r\n      const target = button || document.createElement('button');\r\n      target.dataset.variantId = String(variantId);\r\n      target.dataset.quantity = String(parseInt(quantity) || 1);\r\n      if (!openDrawer) target.dataset.cartOpen = 'false';\r\n      if (!target.matches('[data-cart-add]')) target.setAttribute('data-cart-add', '');\r\n      return this.handleAddToCart({ target, preventDefault: () => {} });\r\n    }\r\n\r\n    // FIXED: This method was looking for wrong class names\r\n    async handleQuantityChange(e) {\r\n      const btn = e.target.closest('.qty-btn');\r\n      if (!btn) return;\r\n\r\n      const itemContainer = btn.closest('.cart-item');\r\n      if (!itemContainer || itemContainer.classList.contains('qty-locked')) return;\r\n\r\n      const lineKey = itemContainer.dataset.key;\r\n      const input = itemContainer.querySelector('.qty-input');\r\n      const currentQty = parseInt(input.value) || 0;\r\n\r\n      let newQty = currentQty;\r\n\r\n      // FIXED: Check for the actual classes in your HTML\r\n      if (btn.classList.contains('qty-increase') || btn.textContent.includes('+')) {\r\n        newQty = currentQty + 1;\r\n      } else if (btn.classList.contains('qty-decrease') || btn.textContent.includes('\u2212')) {\r\n        newQty = Math.max(0, currentQty - 1);\r\n      }\r\n\r\n      // Update the input immediately for responsiveness\r\n      input.value = newQty;\r\n      this.updateCartItemPrice(itemContainer, newQty);\r\n      this.updateSubtotalOptimistically();\r\n      this.updateCountsOptimistically();\r\n\r\n      this.queueQuantityUpdate(lineKey, newQty, 500);\r\n    }\r\n\r\n    async handleQuantityInputChange(e) {\r\n      const input = e.target;\r\n      const itemContainer = input.closest('.cart-item');\r\n      if (!itemContainer || itemContainer.classList.contains('qty-locked')) return;\r\n      const lineKey = itemContainer.dataset.key;\r\n      const newQty = Math.max(0, parseInt(input.value) || 0);\r\n\r\n      this.updateCartItemPrice(itemContainer, newQty);\r\n      this.updateSubtotalOptimistically();\r\n      this.updateCountsOptimistically();\r\n\r\n      this.queueQuantityUpdate(lineKey, newQty, 1000);\r\n    }\r\n\r\n    // Pending quantity edits accumulate per line and flush together in ONE\r\n    // /cart/update.js call. The previous single change.js-per-line timer was\r\n    // both slower and lossy: editing line A then quickly touching line B\r\n    // cancelled A's pending update \u2014 the drawer showed A's new quantity but\r\n    // the server never received it.\r\n    queueQuantityUpdate(lineKey, quantity, delayMs) {\r\n      if (!lineKey || String(lineKey).startsWith('optimistic-')) return;\r\n      this._pendingQty[lineKey] = quantity;\r\n      clearTimeout(this.quantityUpdateTimeout);\r\n      this.quantityUpdateTimeout = setTimeout(() => this.flushQuantityUpdates(), delayMs);\r\n    }\r\n\r\n    _itemByKey(lineKey) {\r\n      return this.cart.items.find(item => String(item.key) === String(lineKey));\r\n    }\r\n\r\n    // ---- GA4 -----------------------------------------------------------\r\n    // Shopify's own tag reports view_cart from the /cart PAGE only, and this\r\n    // theme replaced that page with a drawer. Result: 9 view_cart events\r\n    // against 1,575 add_to_cart in the 90 days to 23 Aug 2026 \u2014 the cart step\r\n    // was effectively invisible. gtag itself is loaded by Shopify through\r\n    // content_for_header; the numeric gtag('config') in theme.liquid is a\r\n    // separate, unrelated tag, so always feature-test before calling.\r\n\r\n    _trackEvent(name, params) {\r\n      if (typeof window.gtag !== 'function') return;\r\n      try {\r\n        window.gtag('event', name, params);\r\n      } catch (error) {\r\n        // Analytics must never break the cart.\r\n        console.error('Cart analytics event failed:', name, error);\r\n      }\r\n    }\r\n\r\n    _cartCurrency() {\r\n      return (this.cart && this.cart.currency) ||\r\n        (typeof Shopify !== 'undefined' && Shopify.currency && Shopify.currency.active) ||\r\n        'AUD';\r\n    }\r\n\r\n    // Optimistic lines are placeholders that have not reached the server yet\r\n    // and carry no reliable ids or prices \u2014 reporting them would invent items.\r\n    _ga4Items(items) {\r\n      return (items || [])\r\n        .filter(item => item && !item._optimistic)\r\n        .map((item, index) => {\r\n          const entry = {\r\n            item_id: String(item.sku || item.variant_id || ''),\r\n            item_name: item.product_title || item.title || '',\r\n            quantity: item.quantity || 0,\r\n            index: index\r\n          };\r\n          if (typeof item.price === 'number') entry.price = item.price / 100;\r\n          if (item.vendor) entry.item_brand = item.vendor;\r\n          if (item.product_type) entry.item_category = item.product_type;\r\n          if (item.variant_title && item.variant_title !== 'Default Title') {\r\n            entry.item_variant = item.variant_title;\r\n          }\r\n          return entry;\r\n        });\r\n    }\r\n\r\n    updateCartItemPrice(itemContainer, newQty) {\r\n      const cartItem = this._itemByKey(itemContainer.dataset.key);\r\n\r\n      if (cartItem) {\r\n        const newLinePrice = cartItem.price * newQty;\r\n        const priceElement = itemContainer.querySelector('.final-price');\r\n        if (priceElement) {\r\n          priceElement.textContent = this.formatMoney(newLinePrice);\r\n        }\r\n      }\r\n    }\r\n\r\n    updateSubtotalOptimistically() {\r\n      let newSubtotal = 0;\r\n      document.querySelectorAll('.cart-item').forEach(item => {\r\n        const quantity = parseInt(item.querySelector('.qty-input').value);\r\n        const cartItem = this._itemByKey(item.dataset.key);\r\n        if (cartItem) {\r\n          newSubtotal += cartItem.price * quantity;\r\n        }\r\n      });\r\n\r\n      const subtotalElement = document.querySelector('.subtotal-amount');\r\n      if (subtotalElement) {\r\n        subtotalElement.textContent = this.formatMoney(newSubtotal);\r\n      }\r\n    }\r\n\r\n    // Counts previously only caught up when the debounced server round-trip\r\n    // landed, so decrementing a line showed the new \"$399.00\" beside a stale\r\n    // \"2\" for ~2s. Derive them from the same inputs the subtotal uses.\r\n    updateCountsOptimistically() {\r\n      let count = 0;\r\n      document.querySelectorAll('.cart-item').forEach(item => {\r\n        const input = item.querySelector('.qty-input');\r\n        if (!input) return;\r\n        count += Math.max(0, parseInt(input.value) || 0);\r\n      });\r\n\r\n      const drawerBadge = document.querySelector('.cart-item-count');\r\n      if (drawerBadge) {\r\n        drawerBadge.textContent = count;\r\n        drawerBadge.style.display = count > 0 ? 'flex' : 'none';\r\n      }\r\n      document.querySelectorAll('.cart-count, [data-cart-count]').forEach(element => {\r\n        element.textContent = count;\r\n        element.style.display = count > 0 ? 'inline' : 'none';\r\n      });\r\n    }\r\n\r\n    async handleRemoveItem(e) {\r\n      const itemContainer = e.target.closest('.cart-item');\r\n\r\n      // Immediate flush \u2014 the removal rides along with any pending qty edits.\r\n      await this.flushQuantityUpdates({ [itemContainer.dataset.key]: 0 });\r\n    }\r\n\r\n    // Queued behind every earlier write, so checkout can await \"every add\r\n    // and quantity edit has landed\". Resolves true when the server has the\r\n    // edits (or there were none), false when the update failed.\r\n    flushQuantityUpdates(extra) {\r\n      clearTimeout(this.quantityUpdateTimeout);\r\n      if (extra) Object.assign(this._pendingQty, extra);\r\n      return this._enqueueWrite(() => this._flushQuantityUpdatesNow())\r\n        .catch((error) => {\r\n          console.error('Quantity flush failed:', error);\r\n          return false;\r\n        })\r\n        .then((ok) => {\r\n          // Resync once out of the queue \u2014 a read from inside it would\r\n          // always count as out of date.\r\n          if (!ok) this._syncFromServer();\r\n          return ok;\r\n        });\r\n    }\r\n\r\n    async _flushQuantityUpdatesNow() {\r\n      // Taken when the task runs, not when it was queued, so edits made\r\n      // while earlier writes were on the wire ride along.\r\n      const updates = Object.assign({}, this._pendingQty);\r\n      this._pendingQty = {};\r\n      // Optimistic placeholders only exist client-side \u2014 nothing to mutate\r\n      // server-side yet; the canonical reconcile will replace them.\r\n      Object.keys(updates).forEach((key) => {\r\n        if (String(key).startsWith('optimistic-')) delete updates[key];\r\n      });\r\n      if (!Object.keys(updates).length) return true;\r\n\r\n      // Capture the lines this call removes BEFORE the response replaces\r\n      // this.cart \u2014 remove_from_cart has to describe the item that is going\r\n      // away, and by then it is gone. Quantity reductions that stop short of\r\n      // zero are deliberately not reported.\r\n      const removedItems = Object.keys(updates)\r\n        .filter(key => updates[key] === 0)\r\n        .map(key => this._itemByKey(key))\r\n        .filter(Boolean);\r\n\r\n      // Optimistic: fade removed lines out immediately so the click feels\r\n      // instant. The diff renderer yanks them once the server confirms.\r\n      const fadedNodes = [];\r\n      if (this.cartItemsContainer) {\r\n        Object.keys(updates).forEach((key) => {\r\n          if (updates[key] !== 0) return;\r\n          const node = this.cartItemsContainer.querySelector(`.cart-item[data-key=\"${key}\"]`);\r\n          if (node) {\r\n            node.style.transition = 'opacity 150ms ease, transform 150ms ease';\r\n            node.style.opacity = '0';\r\n            node.style.transform = 'translateX(8px)';\r\n            node.style.pointerEvents = 'none';\r\n            fadedNodes.push(node);\r\n          }\r\n        });\r\n      }\r\n\r\n      try {\r\n        // One /cart/update.js call for every pending line \u2014 same full-cart\r\n        // response as change.js, without a round-trip per edited line.\r\n        const response = await this._fetchWithTimeout('/cart/update.js', {\r\n          method: 'POST',\r\n          headers: {\r\n            'Content-Type': 'application/json',\r\n            'Accept': 'application/json'\r\n          },\r\n          body: JSON.stringify({ updates })\r\n        });\r\n\r\n        if (!response.ok) {\r\n          const errorData = await response.json().catch(() => null);\r\n          console.error('Cart update failed:', response.status, errorData);\r\n          throw new Error(`Cart update failed: ${response.status}`);\r\n        }\r\n\r\n        this._applyServerCart(await response.json());\r\n        this.renderCart();\r\n        this.updateCartCounts();\r\n\r\n        // Reported only after the server confirms, so a failed update never\r\n        // shows up as a removal that did not happen.\r\n        if (removedItems.length) {\r\n          this._trackEvent('remove_from_cart', {\r\n            currency: this._cartCurrency(),\r\n            value: removedItems.reduce(\r\n              (sum, item) => sum + (item.line_price || item.final_line_price || 0), 0\r\n            ) / 100,\r\n            items: this._ga4Items(removedItems)\r\n          });\r\n        }\r\n        return true;\r\n\r\n      } catch (error) {\r\n        console.error('Cart update error:', error);\r\n        // Restore the optimistically-hidden nodes so the user can retry\r\n        fadedNodes.forEach((node) => {\r\n          node.style.opacity = '';\r\n          node.style.transform = '';\r\n          node.style.pointerEvents = '';\r\n        });\r\n        alert('Unable to update cart. Please refresh the page and try again.');\r\n        return false;\r\n      }\r\n    }\r\n\r\n    resetCheckoutButton() {\r\n      this._checkoutInFlight = false;\r\n      const btn = document.querySelector('.modern-checkout-btn');\r\n      if (!btn) return;\r\n      btn.disabled = false;\r\n      const label = btn.querySelector('.modern-checkout-btn__label');\r\n      if (label) label.textContent = 'CHECKOUT SECURELY';\r\n    }\r\n\r\n    // Resolves with the promise's value, or undefined once ms have passed.\r\n    _settleWithin(promise, ms) {\r\n      let timer;\r\n      const timeout = new Promise(resolve => { timer = setTimeout(resolve, ms); });\r\n      return Promise.race([promise, timeout]).finally(() => clearTimeout(timer));\r\n    }\r\n\r\n    async handleCheckout() {\r\n      // The verification round-trip below plus the redirect itself measured\r\n      // ~4.3s in one mobile session, during which the button said nothing and\r\n      // looked untapped. Claim the tap immediately, and swallow a second one.\r\n      if (this._checkoutInFlight) return;\r\n      this._checkoutInFlight = true;\r\n\r\n      const btn = document.querySelector('.modern-checkout-btn');\r\n      const label = btn && btn.querySelector('.modern-checkout-btn__label');\r\n      if (btn) btn.disabled = true;\r\n      if (label) label.textContent = 'CHECKING CART\u2026';\r\n\r\n      // Let every write the drawer already shows reach the server first. The\r\n      // drawer renders an add before /cart/add.js returns and holds quantity\r\n      // edits on a debounce, so redirecting straight away sent shoppers to\r\n      // checkout ahead of their own changes \u2014 items \"arrived later\". The\r\n      // flush queues behind every add, so awaiting it covers those too.\r\n      // Capped so a hung request can't trap the shopper on this button.\r\n      const flushed = await this._settleWithin(this.flushQuantityUpdates(), 10000);\r\n      if (flushed === false) {\r\n        // The flush already told the shopper and resynced the drawer.\r\n        this.resetCheckoutButton();\r\n        return;\r\n      }\r\n\r\n      // Verify against the SERVER cart before redirecting. The cached copy\r\n      // can be stale (tab restored from bfcache days later, checkout already\r\n      // completed in another tab, expired cart cookie) \u2014 redirecting with an\r\n      // empty real cart makes Shopify bounce /checkout \u2192 /cart, dumping the\r\n      // shopper on an \"empty cart\" page they never chose to visit.\r\n      // (A read that comes back out of date leaves the drawer's copy, which\r\n      // already includes the write that overtook it.)\r\n      await this._syncFromServer();\r\n\r\n      if (!this.cart || this.cart.item_count === 0) {\r\n        console.log('\u26A0\uFE0F Cart is empty, showing alert');\r\n        this.resetCheckoutButton();\r\n        alert('Your cart is empty');\r\n        return;\r\n      }\r\n\r\n      if (label) label.textContent = 'REDIRECTING\u2026';\r\n      window.location.href = '/checkout';\r\n    }\r\n\r\n    // A read that crosses a write stands down (see _fetchCart), so this can\r\n    // no longer clobber an add or edit that is still landing.\r\n    refreshCart() {\r\n      return this._syncFromServer();\r\n    }\r\n\r\n    renderCart() {\r\n      if (!this.cartItemsContainer) return;\r\n\r\n      // Update cart count\r\n      const countElement = document.querySelector('.cart-item-count');\r\n      if (countElement) {\r\n        countElement.textContent = this.cart.item_count;\r\n        countElement.style.display = this.cart.item_count > 0 ? 'flex' : 'none';\r\n      }\r\n\r\n      // Update subtotal with proper decimals\r\n      const subtotalElement = document.querySelector('.subtotal-amount');\r\n      if (subtotalElement) {\r\n        subtotalElement.textContent = this.formatMoney(this.cart.total_price);\r\n      }\r\n\r\n      // Show/hide empty state\r\n      const emptyState = document.querySelector('.empty-cart-state');\r\n      if (this.cart.items.length === 0) {\r\n        this.cartItemsContainer.innerHTML = '';\r\n        if (emptyState) emptyState.style.display = 'flex';\r\n        this.emitCartUpdated();\r\n        return;\r\n      }\r\n      if (emptyState) emptyState.style.display = 'none';\r\n\r\n      // \u2500\u2500 Diff-based render \u2500\u2500\r\n      // Previous version did `innerHTML = ...` on every cart change, which\r\n      // nuked the DOM, flickered images, lost qty-input focus, and made\r\n      // existing lines visibly reorder. Now: keep the DOM stable, update\r\n      // quantity/price in place, only insert/remove nodes for added/removed\r\n      // items. Nodes are keyed by the LINE key (not variant id \u2014 a variant\r\n      // can span multiple lines via bundles/properties); the optimistic\r\n      // placeholder carries a synthetic key, and the canonical line claims\r\n      // its node in place so the swap is flicker-free.\r\n      const existing = new Map();\r\n      Array.from(this.cartItemsContainer.children).forEach((el) => {\r\n        const key = el.getAttribute('data-key');\r\n        if (key) existing.set(key, el);\r\n      });\r\n\r\n      const keepKeys = new Set();\r\n      const fragment = document.createDocumentFragment();\r\n      let appended = false;\r\n\r\n      this.cart.items.forEach((item) => {\r\n        const key = String(item.key);\r\n        keepKeys.add(key);\r\n        let el = existing.get(key);\r\n        if (!el) {\r\n          // Claim the optimistic placeholder rendered for this variant, if\r\n          // any \u2014 refresh it in place instead of remove+insert.\r\n          el = Array.from(this.cartItemsContainer.children).find((node) =>\r\n            (node.getAttribute('data-key') || '').startsWith('optimistic-') &&\r\n            node.getAttribute('data-variant-id') === String(item.variant_id));\r\n          if (el) {\r\n            existing.delete(el.getAttribute('data-key'));\r\n            el.setAttribute('data-key', key);\r\n            existing.set(key, el);\r\n          }\r\n        }\r\n        if (el) {\r\n          this.updateCartItemNode(el, item);\r\n        } else {\r\n          const tpl = document.createElement('template');\r\n          tpl.innerHTML = this.renderCartItemHtml(item).trim();\r\n          const node = tpl.content.firstElementChild;\r\n          if (node) {\r\n            fragment.appendChild(node);\r\n            appended = true;\r\n          }\r\n        }\r\n      });\r\n\r\n      if (appended) this.cartItemsContainer.appendChild(fragment);\r\n\r\n      // Remove any lines that no longer exist in the cart\r\n      existing.forEach((el, key) => {\r\n        if (!keepKeys.has(key)) el.remove();\r\n      });\r\n\r\n      this.emitCartUpdated();\r\n    }\r\n\r\n    // Lets other components (the mobile sticky ATC bar) react to cart state\r\n    // without polling. Fired from both renderCart exits, so it covers the\r\n    // optimistic render as well as the canonical one.\r\n    emitCartUpdated() {\r\n      document.dispatchEvent(new CustomEvent('cart:updated', {\r\n        detail: { cart: this.cart }\r\n      }));\r\n      this.renderBnpl();\r\n    }\r\n\r\n    // Afterpay + Zip instalment messaging priced off the live cart total.\r\n    //\r\n    // The figures are always rendered by the providers' own widgets \u2014 we never\r\n    // compute instalments ourselves. Showing a self-calculated \"4 payments of\r\n    // $X\" that disagreed with what Afterpay actually charges would be a\r\n    // pricing misrepresentation, so the only number we supply is the total.\r\n    //\r\n    // Debounced because renderCart fires twice per add (optimistic, then\r\n    // canonical) and again on every quantity tap; Zip's render() walks the\r\n    // document, so it should not run on each of those.\r\n    renderBnpl() {\r\n      clearTimeout(this._bnplTimeout);\r\n      this._bnplTimeout = setTimeout(() => {\r\n        try {\r\n          this._renderBnplNow();\r\n        } catch (e) {\r\n          // Never let a third-party widget break the cart.\r\n          console.error('BNPL messaging failed to render:', e);\r\n        }\r\n      }, 250);\r\n    }\r\n\r\n    _renderBnplNow() {\r\n      const slot = document.querySelector('[data-cart-bnpl]');\r\n      if (!slot) return;\r\n\r\n      const cents = (this.cart && this.cart.total_price) || 0;\r\n      const hasItems = !!(this.cart && this.cart.items && this.cart.items.length);\r\n      if (!cents || !hasItems) {\r\n        slot.classList.remove('is-populated');\r\n        slot.innerHTML = '';\r\n        this._afterpayEl = null;\r\n        return;\r\n      }\r\n      const amount = (cents / 100).toFixed(2);\r\n\r\n      // \u2500\u2500 Afterpay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n      // <square-placement> lists data-amount in observedAttributes and\r\n      // re-renders itself on change, so it is created once and then only\r\n      // has its amount updated. Verified live: 399 -> $99.75, 798 -> $199.50,\r\n      // 3129 -> $782.25.\r\n      if (customElements.get('square-placement')) {\r\n        if (!this._afterpayEl || !slot.contains(this._afterpayEl)) {\r\n          const el = document.createElement('square-placement');\r\n          const existing = document.querySelector('square-placement[data-mpid]');\r\n          const mpid = (window.Shopify && window.Shopify.shop) ||\r\n                       (existing && existing.getAttribute('data-mpid')) || '';\r\n          el.setAttribute('data-mpid', mpid);\r\n          el.setAttribute('data-currency', window.afterpay_cart_currency || window.afterpay_shop_currency || 'AUD');\r\n          el.setAttribute('data-platform', 'shopify-snippet');\r\n          el.setAttribute('data-consumer-locale', window.afterpay_js_locale || 'en_AU');\r\n          el.setAttribute('data-page-type', 'cart');\r\n          el.setAttribute('data-size', 'xs');\r\n          el.setAttribute('data-logo-type', 'compact-badge');\r\n          el.setAttribute('data-payment-amount-is-bold', 'true');\r\n          // Mirror the merchant's own limit settings rather than hard-coding.\r\n          el.setAttribute('data-show-lower-limit', String(window.afterpay_hide_lower_limit !== 'true'));\r\n          el.setAttribute('data-show-upper-limit', String(window.afterpay_hide_upper_limit !== 'true'));\r\n          el.setAttribute('data-show-if-outside-limits', String(window.afterpay_show_if_outside_limits !== 'false'));\r\n          slot.appendChild(el);\r\n          this._afterpayEl = el;\r\n        }\r\n        // SKUs drive Afterpay's own eligibility rules.\r\n        this._afterpayEl.setAttribute(\r\n          'data-item-skus',\r\n          this.cart.items.map(i => i.sku).filter(Boolean).join(',')\r\n        );\r\n        this._afterpayEl.setAttribute('data-amount', amount);\r\n      }\r\n\r\n      // \u2500\u2500 Zip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n      // Zip exposes no reactive attribute, so its node is rebuilt and\r\n      // $zmJs.render() re-run. Verified live: $399 -> \"from $10/week\",\r\n      // $3129 -> \"up to 6 months interest free\".\r\n      if (window.$zmJs && typeof window.$zmJs.render === 'function') {\r\n        let zipHost = slot.querySelector('[data-cart-bnpl-zip]');\r\n        if (!zipHost) {\r\n          zipHost = document.createElement('div');\r\n          zipHost.setAttribute('data-cart-bnpl-zip', '');\r\n          slot.appendChild(zipHost);\r\n        }\r\n        if (zipHost.getAttribute('data-rendered-for') !== amount) {\r\n          zipHost.setAttribute('data-rendered-for', amount);\r\n          zipHost.innerHTML = '';\r\n          const w = document.createElement('div');\r\n          w.setAttribute('data-zm-widget', 'popup');\r\n          w.setAttribute('data-zm-asset', 'productwidget');\r\n          w.setAttribute('data-zm-popup-asset', 'termsdialog');\r\n          w.setAttribute('data-zm-price', amount);\r\n          w.setAttribute('data-zm-symbol', 'null');\r\n          zipHost.appendChild(w);\r\n          window.$zmJs.render();\r\n        }\r\n      }\r\n\r\n      slot.classList.toggle('is-populated', slot.children.length > 0);\r\n    }\r\n\r\n    updateCartItemNode(el, item) {\r\n      // Update quantity input only if value actually differs (avoid stealing\r\n      // focus mid-edit).\r\n      const qty = el.querySelector('.qty-input');\r\n      if (qty && document.activeElement !== qty && Number(qty.value) !== item.quantity) {\r\n        qty.value = item.quantity;\r\n      }\r\n      const price = el.querySelector('.final-price');\r\n      if (price) price.textContent = this.formatMoney(item.line_price);\r\n      // Optimistic placeholders are rendered with item._optimistic === true;\r\n      // when the server item arrives, drop the dim class.\r\n      if (item._optimistic) {\r\n        el.classList.add('is-optimistic');\r\n      } else {\r\n        el.classList.remove('is-optimistic');\r\n      }\r\n      const locked = this._qtyLocked(item);\r\n      el.classList.toggle('qty-locked', locked);\r\n      el.querySelectorAll('.qty-btn').forEach(b => { b.disabled = locked; });\r\n      if (qty) qty.readOnly = locked;\r\n      const props = el.querySelector('.item-properties');\r\n      if (props) props.innerHTML = this._propertiesHtml(item);\r\n    }\r\n\r\n    renderCartItemHtml(item) {\r\n      return `\r\n        <div class=\"cart-item${item._optimistic ? ' is-optimistic' : ''}${this._qtyLocked(item) ? ' qty-locked' : ''}\" data-key=\"${item.key}\" data-variant-id=\"${item.variant_id}\">\r\n          <div class=\"item-image\">\r\n            <img src=\"${this.getProductImage(item)}\" alt=\"${item.product_title}\" loading=\"lazy\">\r\n          </div>\r\n\r\n          <div class=\"item-details\">\r\n            <div class=\"item-header\">\r\n              <h4 class=\"item-title\">${item.product_title}</h4>\r\n              <button class=\"remove-item\" type=\"button\" aria-label=\"Remove ${item.product_title}\">\r\n                <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\r\n                  <path d=\"M3 6h18l-2 14H5L3 6z\"/>\r\n                  <path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>\r\n                  <line x1=\"10\" y1=\"11\" x2=\"10\" y2=\"17\"/>\r\n                  <line x1=\"14\" y1=\"11\" x2=\"14\" y2=\"17\"/>\r\n                </svg>\r\n              </button>\r\n            </div>\r\n\r\n            ${item.variant_title && item.variant_title !== 'Default Title' ?\r\n              `<div class=\"item-variant\">${item.variant_title}</div>` : ''}\r\n            <div class=\"item-properties\">${this._propertiesHtml(item)}</div>\r\n\r\n            <div class=\"item-controls\">\r\n              <div class=\"quantity-controls\">\r\n                <button class=\"qty-btn qty-decrease\" type=\"button\" aria-label=\"Decrease quantity\"${this._qtyLocked(item) ? ' disabled' : ''}>\u2212</button>\r\n                <input type=\"number\" class=\"qty-input\" value=\"${item.quantity}\" min=\"0\" aria-label=\"Quantity\"${this._qtyLocked(item) ? ' readonly' : ''}>\r\n                <button class=\"qty-btn qty-increase\" type=\"button\" aria-label=\"Increase quantity\"${this._qtyLocked(item) ? ' disabled' : ''}>+</button>\r\n              </div>\r\n\r\n              <div class=\"item-price\">\r\n                <span class=\"final-price\">${this.formatMoney(item.line_price)}</span>\r\n              </div>\r\n            </div>\r\n          </div>\r\n        </div>\r\n      `;\r\n    }\r\n\r\n    getProductImage(item) {\r\n      // Try featured_image first, then image, then fallback\r\n      if (item.featured_image && item.featured_image.url) {\r\n        return item.featured_image.url;\r\n      }\r\n      if (item.image) {\r\n        return item.image;\r\n      }\r\n      if (item.featured_image) {\r\n        return item.featured_image;\r\n      }\r\n      // Fallback placeholder\r\n      return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIGZpbGw9IiNlNWU3ZWIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6bTAgMTZINVY1aDE0djE0em0tNS04bDIuNSAzTDEzIDEydjEuOGwyLTJWOGwtMi4yIDIuM3oiLz48L3N2Zz4=';\r\n    }\r\n\r\n    updateCartCounts() {\r\n      document.querySelectorAll('.cart-count, [data-cart-count]').forEach(element => {\r\n        element.textContent = this.cart.item_count;\r\n        // The header renders the badge with hidden-count (display: none\r\n        // !important) when the page loads on an empty cart, which beat the\r\n        // inline style \u2014 the count stayed hidden after the first add.\r\n        element.classList.toggle('hidden-count', this.cart.item_count === 0);\r\n        if (this.cart.item_count > 0) {\r\n          element.style.display = 'inline';\r\n        } else {\r\n          element.style.display = 'none';\r\n        }\r\n      });\r\n    }\r\n\r\n    // FIXED: Proper decimal formatting with comma separators\r\n    formatMoney(cents, showCurrency = true) {\r\n      const dollars = (cents / 100);\r\n      const formatted = dollars.toLocaleString('en-AU', {\r\n        minimumFractionDigits: 2,\r\n        maximumFractionDigits: 2\r\n      });\r\n      return showCurrency ? `$${formatted}` : formatted;\r\n    }\r\n\r\n    openCart({ trackView = false } = {}) {\r\n      if (!this.cartDrawer) return;\r\n\r\n      this.isOpen = true;\r\n      this.cartDrawer.classList.add('is-open');\r\n      document.body.classList.add('cart-open');\r\n      document.body.style.overflow = 'hidden';\r\n\r\n      // The drawer IS the cart view on this theme, so a shopper opening it\r\n      // is a view_cart. Only shopper-initiated opens count: the drawer also\r\n      // opens itself on every add, which made view_cart mirror add_to_cart.\r\n      // Skipped on /cart itself, where Shopify's tag already reports one, and\r\n      // when no confirmed line exists. Value covers confirmed lines only, to\r\n      // match the items reported.\r\n      const viewedItems = this.cart && Array.isArray(this.cart.items)\r\n        ? this.cart.items.filter(item => !item._optimistic)\r\n        : [];\r\n      if (trackView && viewedItems.length &&\r\n          !/^\\/cart(\\/|$|\\?)/.test(window.location.pathname)) {\r\n        this._trackEvent('view_cart', {\r\n          currency: this._cartCurrency(),\r\n          value: viewedItems.reduce(\r\n            (sum, item) => sum + (item.line_price || item.final_line_price || 0), 0\r\n          ) / 100,\r\n          items: this._ga4Items(viewedItems)\r\n        });\r\n      }\r\n\r\n      // Give the drawer its own history entry so back dismisses it. Wrapped\r\n      // because a blocked/exhausted history API must not stop the drawer\r\n      // opening \u2014 the X, the swipe and the overlay still close it.\r\n      if (!this._historyPushed) {\r\n        if (this._unwindPending) {\r\n          // A previous close's history.back() is still in flight. Pushing now\r\n          // would strand the entry it is about to land on, costing the shopper\r\n          // an inert back press later. Re-establish our entry once the\r\n          // traversal completes instead (see the popstate handler).\r\n          this._pushAfterUnwind = true;\r\n        } else {\r\n          this._pushCartHistoryEntry();\r\n        }\r\n      }\r\n\r\n      const closeBtn = this.cartDrawer.querySelector('.modern-close-btn');\r\n      if (closeBtn) closeBtn.focus();\r\n    }\r\n\r\n    closeCart({ fromPopState = false } = {}) {\r\n      if (!this.cartDrawer) return;\r\n\r\n      this.isOpen = false;\r\n      this.cartDrawer.classList.remove('is-open');\r\n      document.body.classList.remove('cart-open');\r\n      document.body.style.overflow = '';\r\n\r\n      // Closing again voids any intent to re-establish an entry on reopen.\r\n      this._pushAfterUnwind = false;\r\n\r\n      // Unwind the entry openCart pushed, unless we are already here because\r\n      // the shopper popped it themselves. isOpen is already false above, so\r\n      // the popstate this triggers is a no-op in the handler.\r\n      if (this._historyPushed) {\r\n        this._historyPushed = false;\r\n        if (!fromPopState) {\r\n          this._unwindPending = true;\r\n          try {\r\n            history.back();\r\n          } catch (e) {\r\n            this._unwindPending = false;\r\n            console.warn('Cart history unwind failed:', e);\r\n          }\r\n          // Safety net: if the traversal never lands (blocked history, no\r\n          // entry to go back to) the pending flag would wedge open/close\r\n          // bookkeeping permanently. Settle it either way.\r\n          clearTimeout(this._unwindTimeout);\r\n          this._unwindTimeout = setTimeout(() => this._settleUnwind(), 600);\r\n        }\r\n      }\r\n    }\r\n\r\n    _pushCartHistoryEntry() {\r\n      try {\r\n        history.pushState({ modernCartOpen: true }, '');\r\n        this._historyPushed = true;\r\n      } catch (e) {\r\n        console.warn('Cart history entry unavailable:', e);\r\n      }\r\n    }\r\n\r\n    // Called when a close's history.back() resolves \u2014 or is given up on.\r\n    // Returns true if it consumed the event by re-pushing our entry.\r\n    _settleUnwind() {\r\n      clearTimeout(this._unwindTimeout);\r\n      this._unwindPending = false;\r\n      if (this._pushAfterUnwind) {\r\n        this._pushAfterUnwind = false;\r\n        // The shopper reopened the drawer while the unwind was in flight.\r\n        if (this.isOpen && !this._historyPushed) {\r\n          this._pushCartHistoryEntry();\r\n          return true;\r\n        }\r\n      }\r\n      return false;\r\n    }\r\n  }\r\n\r\n  // Initialize the modern cart system\r\n  window.modernCart = new ModernCartSystem();\r\n\r\n  // Block original cart functions\r\n  window.addEventListener('DOMContentLoaded', function() {\r\n    const originalToggleFunctions = [\r\n      'toggleCart',\r\n      'openCart', \r\n      'closeCart',\r\n      'ajaxCart'\r\n    ];\r\n\r\n    originalToggleFunctions.forEach(funcName => {\r\n      if (window[funcName]) {\r\n        window[funcName] = function() {\r\n          if (funcName.includes('open') || funcName.includes('toggle')) {\r\n            window.modernCart.openCart();\r\n          } else if (funcName.includes('close')) {\r\n            window.modernCart.closeCart();\r\n          }\r\n        };\r\n      }\r\n    });\r\n  });\r\n\r\n\r\n})();"],
  "mappings": "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",
  "names": []
}
