{
  "version": 3,
  "sources": ["/cdn/shop/t/11/assets/sb-image-swap.js?cachecheck=202609220912"],
  "sourcesContent": ["/**\r\n * Simple Bundles \u2013 Infinite Options Image Swap v2 (17 Feb 2026)\r\n *\r\n * Swaps the main PDP image when a customer changes an Infinite Options\r\n * bundle dropdown. Uses the bundled_variants metafield for reliable\r\n * product/variant \u2192 image mapping. Should work acrosss any Shopify theme.\r\n *\r\n * \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n * INSTALLATION (pick one):\r\n *\r\n *   Option A \u2013 Theme asset (recommended):\r\n *     1. Upload this file to your theme's assets/ folder\r\n *     2. Add before </body> in layout/theme.liquid:\r\n *        <script src=\"{{ 'sb-image-swap.js' | asset_url }}\" defer></script>\r\n *\r\n *   Option B \u2013 Inline snippet:\r\n *     Wrap in <script>\u2026</script> at the bottom of your product template\r\n *\r\n * \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n * HOW IT WORKS:\r\n *\r\n *   1. On page load, fetches the current product's metafields via\r\n *      /products/{handle}.json to get the bundled_variants data\r\n *   2. Builds a lookup map: option display text \u2192 { handle, variant_id }\r\n *   3. Fetches each child product once (cached) to get all variant images\r\n *   4. On dropdown change, looks up the selection \u2192 swaps the main image\r\n *\r\n *   Because it uses the actual metafield data (handles + variant IDs),\r\n *   it never has to guess product handles from display text.\r\n * \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n */\r\n\r\n(function () {\r\n  'use strict';\r\n\r\n  // ========================================================================\r\n  // CONFIG\r\n  // ========================================================================\r\n  const CONFIG = {\r\n    // Selectors to find the main product image \u2013 tried in order. Most themes use these selectors .\r\n    // Add your theme's selector at the top if none of these match.\r\n    imageSelectors: [\r\n      'product-gallery .product-gallery__media.is-initial img',\r\n      'product-gallery .product-gallery__media img',\r\n      '.product__media-item--variant img',\r\n      '.product__media img.image-magnify-full-size',\r\n      '.product__media-list .product__media img',\r\n      '.product__media img',\r\n      '.product-image-main img',\r\n      '.product__photos img',\r\n      '.product__photo img.photoswipe__image',\r\n      '.product__photo img',\r\n      '.product-gallery__image img',\r\n      '.product__main-photos img',\r\n      '.product-main-slide .image-wrap img',\r\n      '.product-featured-img',\r\n      '.product-single__photo img',\r\n      '.product-single__media img',\r\n      '.product__media-item img',\r\n      '[data-product-media-type=\"image\"] img',\r\n      '.product-single__image img',\r\n      '.product__image-wrapper img',\r\n    ],\r\n\r\n    ioBundleContainer: '#simple-bundles-io-options',\r\n    transitionMs: 200,\r\n    resetLabel: 'Vis modelbillede',\r\n    mobileMaxWidth: 749,\r\n    mobileScrollOffset: 90,\r\n  };\r\n\r\n  // ========================================================================\r\n  // STATE\r\n  // ========================================================================\r\n  let mainImageEl = null;\r\n  let originalSrc = null;\r\n  let originalSrcset = null;\r\n  let originalSizes = null;\r\n  let originalPictureSources = [];\r\n  let originalZoomAttributes = null;\r\n  let resetControlEl = null;\r\n\r\n  // Map: normalized option text \u2192 image URL\r\n  // e.g. \"cotton canvas cap\" \u2192 \"https://cdn.shopify.com/...\"\r\n  const imageMap = {};\r\n\r\n  // Cache for /products/{handle}.js fetches\r\n  const productCache = {};\r\n\r\n  // ========================================================================\r\n  // HELPERS\r\n  // ========================================================================\r\n\r\n  /**\r\n   * Normalize option text for reliable matching.\r\n   * Strips price suffixes like \"(+CA$5.00)\", whitespace, and lowercases.\r\n   */\r\n  function normalizeOptionText(text) {\r\n    return text\r\n      .replace(/\\([\\+\\-]?\\s*[A-Z]{0,3}\\$?\\s*[\\d,.]+\\)/g, '') // (+CA$5.00), (-$3.00), etc.\r\n      .replace(/\\s+/g, ' ')\r\n      .trim()\r\n      .toLowerCase();\r\n  }\r\n\r\n  /**\r\n   * Find the main product image on the page.\r\n   * Tries configured selectors, then falls back to largest image in product area.\r\n   */\r\n  function findMainImage() {\r\n    for (const sel of CONFIG.imageSelectors) {\r\n      const img = document.querySelector(sel);\r\n      if (img && img.src) return img;\r\n    }\r\n\r\n    // Fallback: largest image in the product section\r\n    const section =\r\n      document.querySelector('.product') ||\r\n      document.querySelector('[data-section-type=\"product\"]') ||\r\n      document.querySelector('.product-single') ||\r\n      document.querySelector('main');\r\n\r\n    if (!section) return null;\r\n\r\n    let best = null;\r\n    let bestArea = 0;\r\n    section.querySelectorAll('img').forEach((img) => {\r\n      const area = img.offsetWidth * img.offsetHeight;\r\n      if (area > bestArea && !img.closest(CONFIG.ioBundleContainer)) {\r\n        best = img;\r\n        bestArea = area;\r\n      }\r\n    });\r\n    return best;\r\n  }\r\n\r\n  /**\r\n   * Get the current product handle from the URL.\r\n   */\r\n  function getProductHandle() {\r\n    const match = window.location.pathname.match(/\\/products\\/([^/?#]+)/);\r\n    return match ? match[1] : null;\r\n  }\r\n\r\n  /**\r\n   * Fetch product JSON (cached).\r\n   */\r\n  async function fetchProduct(handle) {\r\n    if (productCache[handle]) return productCache[handle];\r\n    try {\r\n      const res = await fetch(`/products/${handle}.js`);\r\n      if (!res.ok) return null;\r\n      const data = await res.json();\r\n      productCache[handle] = data;\r\n      return data;\r\n    } catch (e) {\r\n      console.warn('[SB-IMG] fetch failed for', handle, e);\r\n      return null;\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Resize a Shopify CDN image URL to a given width.\r\n   */\r\n  function resizeImage(url, width) {\r\n    if (!url) return url;\r\n    // Remove any existing size suffix and add desired one\r\n    return url\r\n      .replace(/_(\\d+x\\d*|\\d*x\\d+)\\./, '.')\r\n      .replace(/\\.(\\w+)(\\?.*)?$/, `_${width}x.$1$2`);\r\n  }\r\n\r\n  /**\r\n   * Save every responsive-image value used by Prestige before the first swap.\r\n   * Mobile may render a <source> from <picture> instead of the img src itself.\r\n   */\r\n  function captureOriginalImageState() {\r\n    if (!mainImageEl) return;\r\n\r\n    originalSrc = mainImageEl.getAttribute('src') || mainImageEl.src;\r\n    originalSrcset = mainImageEl.getAttribute('srcset');\r\n    originalSizes = mainImageEl.getAttribute('sizes');\r\n\r\n    const picture = mainImageEl.closest('picture');\r\n    originalPictureSources = picture\r\n      ? Array.from(picture.querySelectorAll('source')).map((source) => ({\r\n          element: source,\r\n          srcset: source.getAttribute('srcset'),\r\n          sizes: source.getAttribute('sizes'),\r\n        }))\r\n      : [];\r\n\r\n    const zoomEl =\r\n      mainImageEl.closest('[data-zoom]') ||\r\n      mainImageEl.closest('[data-bgset]') ||\r\n      mainImageEl.closest('[data-image]');\r\n\r\n    originalZoomAttributes = zoomEl\r\n      ? {\r\n          element: zoomEl,\r\n          zoom: zoomEl.getAttribute('data-zoom'),\r\n          bgset: zoomEl.getAttribute('data-bgset'),\r\n          image: zoomEl.getAttribute('data-image'),\r\n        }\r\n      : null;\r\n  }\r\n\r\n  function setResetControlVisible(visible) {\r\n    if (resetControlEl) resetControlEl.hidden = !visible;\r\n  }\r\n\r\n  /**\r\n   * On mobile the option controls sit below the gallery. After the new yarn\r\n   * image is ready, move the customer back to the image they just selected.\r\n   */\r\n  function scrollToChangedImage() {\r\n    if (!mainImageEl || window.innerWidth > CONFIG.mobileMaxWidth) return;\r\n\r\n    const gallery =\r\n      mainImageEl.closest('product-gallery') ||\r\n      mainImageEl.closest('.product-gallery') ||\r\n      mainImageEl.closest('.product__media-wrapper') ||\r\n      mainImageEl;\r\n\r\n    const top = gallery.getBoundingClientRect().top + window.scrollY - CONFIG.mobileScrollOffset;\r\n    window.scrollTo({ top: Math.max(0, top), behavior: 'smooth' });\r\n  }\r\n\r\n  /**\r\n   * Add one link-style reset button directly below the complete product gallery.\r\n   * It is only visible after an Infinite Options image swap.\r\n   */\r\n  function ensureResetControl() {\r\n    if (resetControlEl && document.body.contains(resetControlEl)) return;\r\n\r\n    const gallery =\r\n      mainImageEl?.closest('product-gallery') ||\r\n      mainImageEl?.closest('.product-gallery') ||\r\n      mainImageEl?.closest('.product__media-wrapper');\r\n\r\n    if (!gallery) return;\r\n\r\n    resetControlEl = document.createElement('button');\r\n    resetControlEl.type = 'button';\r\n    resetControlEl.textContent = CONFIG.resetLabel;\r\n    resetControlEl.hidden = true;\r\n    resetControlEl.setAttribute('aria-label', 'Vis produktets modelbillede igen');\r\n    resetControlEl.style.cssText = [\r\n      'appearance:none',\r\n      'background:none',\r\n      'border:0',\r\n      'border-bottom:1px solid currentColor',\r\n      'border-radius:0',\r\n      'color:inherit',\r\n      'cursor:pointer',\r\n      'font:inherit',\r\n      'margin-top:16px',\r\n      'padding:0 0 2px',\r\n    ].join(';');\r\n    resetControlEl.addEventListener('click', restoreOriginal);\r\n    gallery.insertAdjacentElement('afterend', resetControlEl);\r\n  }\r\n\r\n  /**\r\n   * Swap the main image with a fade transition.\r\n   */\r\n  function swapImage(newSrc) {\r\n    if (!mainImageEl || !newSrc) return;\r\n\r\n    // Avoid swapping to the same image\r\n    const normCurrent = mainImageEl.src.replace(/[?#].*$/, '').replace(/_\\d+x\\d*\\./, '.');\r\n    const normNew = newSrc.replace(/[?#].*$/, '').replace(/_\\d+x\\d*\\./, '.');\r\n    if (normCurrent === normNew) {\r\n      ensureResetControl();\r\n      setResetControlVisible(true);\r\n      scrollToChangedImage();\r\n      return;\r\n    }\r\n\r\n    mainImageEl.style.transition = `opacity ${CONFIG.transitionMs}ms ease`;\r\n    mainImageEl.style.opacity = '0';\r\n\r\n    setTimeout(() => {\r\n      // Update both img and every responsive <source>. Mobile browsers often\r\n      // render a <source> instead of img.src, so changing only src is not enough.\r\n      mainImageEl.setAttribute('srcset', newSrc);\r\n      const picture = mainImageEl.closest('picture');\r\n      if (picture) {\r\n        picture.querySelectorAll('source').forEach((source) => {\r\n          source.setAttribute('srcset', newSrc);\r\n        });\r\n      }\r\n\r\n      // Update zoom / lightbox wrappers\r\n      const zoomEl =\r\n        mainImageEl.closest('[data-zoom]') ||\r\n        mainImageEl.closest('[data-bgset]') ||\r\n        mainImageEl.closest('[data-image]');\r\n      if (zoomEl) {\r\n        zoomEl.dataset.zoom && (zoomEl.dataset.zoom = resizeImage(newSrc, 2048));\r\n        zoomEl.dataset.bgset && (zoomEl.dataset.bgset = newSrc);\r\n      }\r\n\r\n      mainImageEl.src = newSrc;\r\n      ensureResetControl();\r\n      setResetControlVisible(true);\r\n\r\n      let imageShown = false;\r\n      const show = () => {\r\n        if (imageShown) return;\r\n        imageShown = true;\r\n        mainImageEl.style.opacity = '1';\r\n        scrollToChangedImage();\r\n      };\r\n      mainImageEl.addEventListener('load', show, { once: true });\r\n      // If cached / already loaded\r\n      if (mainImageEl.complete) show();\r\n    }, CONFIG.transitionMs);\r\n  }\r\n\r\n  function restoreOriginal() {\r\n    if (!mainImageEl || !originalSrc) return;\r\n    mainImageEl.style.transition = `opacity ${CONFIG.transitionMs}ms ease`;\r\n    mainImageEl.style.opacity = '0';\r\n    setTimeout(() => {\r\n      mainImageEl.src = originalSrc;\r\n      if (originalSrcset) mainImageEl.setAttribute('srcset', originalSrcset);\r\n      else mainImageEl.removeAttribute('srcset');\r\n      if (originalSizes) mainImageEl.setAttribute('sizes', originalSizes);\r\n      else mainImageEl.removeAttribute('sizes');\r\n\r\n      originalPictureSources.forEach(({ element, srcset, sizes }) => {\r\n        if (!element || !document.body.contains(element)) return;\r\n        if (srcset) element.setAttribute('srcset', srcset);\r\n        else element.removeAttribute('srcset');\r\n        if (sizes) element.setAttribute('sizes', sizes);\r\n        else element.removeAttribute('sizes');\r\n      });\r\n\r\n      if (originalZoomAttributes?.element) {\r\n        const { element, zoom, bgset, image } = originalZoomAttributes;\r\n        if (zoom) element.setAttribute('data-zoom', zoom);\r\n        else element.removeAttribute('data-zoom');\r\n        if (bgset) element.setAttribute('data-bgset', bgset);\r\n        else element.removeAttribute('data-bgset');\r\n        if (image) element.setAttribute('data-image', image);\r\n        else element.removeAttribute('data-image');\r\n      }\r\n\r\n      setResetControlVisible(false);\r\n      mainImageEl.style.opacity = '1';\r\n    }, CONFIG.transitionMs);\r\n  }\r\n\r\n  // ========================================================================\r\n  // CORE: Build the image lookup map from bundled_variants metafield\r\n  // ========================================================================\r\n\r\n  /**\r\n   * Parse the bundled_variants metafield and fetch images for each\r\n   * child product/variant. Populates the global `imageMap`.\r\n   *\r\n   * bundled_variants structure (from SB metafields):\r\n   * [\r\n   *   {\r\n   *     \"variant_title\": \"Default Title\",\r\n   *     \"variant_id\": 123456,\r\n   *     \"handle\": \"sample_product_handle\",\r\n   *     \"product_title\": \"Sample Product\",\r\n   *     \"variant_options\": [{ \"name\": \"Option 1\", \"value\": \"Sample Product\" }]\r\n   *   },\r\n   *   ...\r\n   * ]\r\n   *\r\n   * The dropdown display text matches variant_options[].value\r\n   * (e.g. \"Sample Product\", \"Custom Water Bottle - Cornflower / Dark Berry\")\r\n   */\r\n  async function buildImageMap(bundledVariants) {\r\n    if (!Array.isArray(bundledVariants) || bundledVariants.length === 0) {\r\n      console.warn('[SB-IMG] No bundled_variants data found.');\r\n      return;\r\n    }\r\n\r\n    // Group by product handle to minimize fetches\r\n    const handleGroups = {};\r\n    for (const bv of bundledVariants) {\r\n      if (!bv.handle) continue;\r\n      if (!handleGroups[bv.handle]) handleGroups[bv.handle] = [];\r\n      handleGroups[bv.handle].push(bv);\r\n    }\r\n\r\n    // Fetch each unique child product\r\n    const fetches = Object.keys(handleGroups).map(async (handle) => {\r\n      const product = await fetchProduct(handle);\r\n      if (!product) {\r\n        console.warn('[SB-IMG] Could not fetch product:', handle);\r\n        return;\r\n      }\r\n\r\n      for (const bv of handleGroups[handle]) {\r\n        let imageSrc = null;\r\n\r\n        // 1. Try to find variant-specific image by variant_id\r\n        const matchedVariant = product.variants.find(\r\n          (v) => v.id === bv.variant_id\r\n        );\r\n        if (matchedVariant && matchedVariant.featured_image) {\r\n          imageSrc = matchedVariant.featured_image.src;\r\n        }\r\n\r\n        // 2. Fall back to product featured image\r\n        if (!imageSrc && product.featured_image) {\r\n          imageSrc = product.featured_image;\r\n        }\r\n\r\n        // 3. Fall back to first product image\r\n        if (!imageSrc && product.images && product.images.length > 0) {\r\n          imageSrc = product.images[0];\r\n        }\r\n\r\n        if (!imageSrc) {\r\n          console.log('[SB-IMG] No image available for:', bv.product_title);\r\n          continue;\r\n        }\r\n\r\n        // Request a good PDP size\r\n        imageSrc = resizeImage(imageSrc, 1024);\r\n\r\n        // Map all possible text keys for this variant\r\n        const keys = new Set();\r\n\r\n        // From variant_options (primary \u2013 matches dropdown display text)\r\n        if (bv.variant_options) {\r\n          for (const opt of bv.variant_options) {\r\n            if (opt.value) keys.add(normalizeOptionText(opt.value));\r\n          }\r\n        }\r\n\r\n        // From product_title + variant_title combo\r\n        if (bv.product_title) {\r\n          keys.add(normalizeOptionText(bv.product_title));\r\n          if (bv.variant_title && bv.variant_title !== 'Default Title') {\r\n            keys.add(\r\n              normalizeOptionText(\r\n                `${bv.product_title} - ${bv.variant_title}`\r\n              )\r\n            );\r\n          }\r\n        }\r\n\r\n        for (const key of keys) {\r\n          imageMap[key] = imageSrc;\r\n        }\r\n      }\r\n    });\r\n\r\n    await Promise.all(fetches);\r\n    console.log('[SB-IMG] Image map built:', Object.keys(imageMap).length, 'entries', imageMap);\r\n  }\r\n\r\n  /**\r\n   * Get bundled_variants data. Tries multiple sources:\r\n   *   1. Fetch from /products/{handle}.json (most reliable, works on any theme)\r\n   *   2. Parse from page JSON-LD / script tags (fallback)\r\n   */\r\n  async function getBundledVariants() {\r\n    const handle = getProductHandle();\r\n    if (!handle) return null;\r\n\r\n    // Fetch the full product JSON which includes metafields on some themes\r\n    // But Shopify's /products/{handle}.json doesn't include metafields,\r\n    // so we use the Storefront-compatible metafield endpoint instead.\r\n    try {\r\n      const res = await fetch(\r\n        `/products/${handle}.json`\r\n      );\r\n      if (res.ok) {\r\n        const data = await res.json();\r\n        const product = data.product || data;\r\n\r\n        // Check if metafields are embedded (some themes do this)\r\n        if (product.metafields) {\r\n          const sbMeta =\r\n            product.metafields.simple_bundles ||\r\n            product.metafields['app--4901177--simple_bundles'];\r\n          if (sbMeta && sbMeta.bundled_variants) {\r\n            return JSON.parse(sbMeta.bundled_variants);\r\n          }\r\n        }\r\n      }\r\n    } catch (e) {\r\n      // Continue to next method\r\n    }\r\n\r\n    // Method 2: Look for metafield data in <script type=\"application/json\"> tags\r\n    const scripts = document.querySelectorAll('script[type=\"application/json\"]');\r\n    for (const script of scripts) {\r\n      try {\r\n        const data = JSON.parse(script.textContent);\r\n        // Look for bundled_variants in various structures\r\n        const searchObj = (obj, depth = 0) => {\r\n          if (depth > 5 || !obj || typeof obj !== 'object') return null;\r\n          if (obj.bundled_variants) {\r\n            const val = obj.bundled_variants;\r\n            return typeof val === 'string' ? JSON.parse(val) : val;\r\n          }\r\n          for (const key in obj) {\r\n            const result = searchObj(obj[key], depth + 1);\r\n            if (result) return result;\r\n          }\r\n          return null;\r\n        };\r\n        const found = searchObj(data);\r\n        if (found) return found;\r\n      } catch (e) {\r\n        // Not the right JSON block\r\n      }\r\n    }\r\n\r\n    // Method 3: Use Liquid-injected global (if the merchant added our snippet)\r\n    if (window.__SB_BUNDLED_VARIANTS__) {\r\n      return typeof window.__SB_BUNDLED_VARIANTS__ === 'string'\r\n        ? JSON.parse(window.__SB_BUNDLED_VARIANTS__)\r\n        : window.__SB_BUNDLED_VARIANTS__;\r\n    }\r\n\r\n    return null;\r\n  }\r\n\r\n  // ========================================================================\r\n  // DROPDOWN EVENT HANDLING\r\n  // ========================================================================\r\n\r\n  function handleChange(select) {\r\n    const selected = select.options[select.selectedIndex];\r\n    if (!selected || !selected.value) {\r\n      restoreOriginal();\r\n      return;\r\n    }\r\n\r\n    const raw = selected.textContent;\r\n    const normalized = normalizeOptionText(raw);\r\n\r\n    console.log('[SB-IMG] Dropdown changed:', { raw: raw.trim(), normalized });\r\n\r\n    const imageSrc = imageMap[normalized];\r\n    if (imageSrc) {\r\n      console.log('[SB-IMG] \u2713 Image found:', imageSrc);\r\n      swapImage(imageSrc);\r\n    } else {\r\n      // No image mapped \u2013 try partial matching as a fallback\r\n      const partialMatch = Object.keys(imageMap).find(\r\n        (key) => normalized.includes(key) || key.includes(normalized)\r\n      );\r\n      if (partialMatch) {\r\n        console.log('[SB-IMG] \u2713 Partial match:', partialMatch);\r\n        swapImage(imageMap[partialMatch]);\r\n      } else {\r\n        console.log('[SB-IMG] \u2717 No image for:', normalized);\r\n        // Don't restore original \u2013 keep the last swapped image\r\n      }\r\n    }\r\n  }\r\n\r\n  function attachListeners(container) {\r\n    const selects = container.querySelectorAll('select');\r\n    selects.forEach((sel) => {\r\n      if (sel.dataset.sbImgBound) return;\r\n      sel.dataset.sbImgBound = '1';\r\n      sel.addEventListener('change', () => handleChange(sel));\r\n      console.log('[SB-IMG] Listener attached:', sel.name || sel.id || '(unnamed)');\r\n    });\r\n  }\r\n\r\n  // ========================================================================\r\n  // INIT\r\n  // ========================================================================\r\n\r\n  async function init() {\r\n    // 1. Find main image\r\n    mainImageEl = findMainImage();\r\n    if (!mainImageEl) {\r\n      console.warn('[SB-IMG] Main product image not found. Retrying in 1s...');\r\n      setTimeout(() => {\r\n        mainImageEl = findMainImage();\r\n        if (mainImageEl) {\r\n          console.log('[SB-IMG] Main image found on retry.');\r\n          captureOriginalImageState();\r\n          ensureResetControl();\r\n        } else {\r\n          console.error('[SB-IMG] Could not find main product image.');\r\n        }\r\n      }, 1000);\r\n    } else {\r\n      captureOriginalImageState();\r\n      ensureResetControl();\r\n      console.log('[SB-IMG] Main image found:', originalSrc);\r\n    }\r\n\r\n    // 2. Get bundled_variants and build image map\r\n    const bundledVariants = await getBundledVariants();\r\n\r\n    if (bundledVariants) {\r\n      console.log('[SB-IMG] bundled_variants loaded:', bundledVariants.length, 'entries');\r\n      await buildImageMap(bundledVariants);\r\n    } else {\r\n      console.warn(\r\n        '[SB-IMG] Could not load bundled_variants from metafields.',\r\n        'Add the Liquid snippet (see below) for guaranteed compatibility.',\r\n        '\\n\\nAdd this to your product template:\\n',\r\n        '<script>',\r\n        '  window.__SB_BUNDLED_VARIANTS__ = {{ product.metafields.simple_bundles.bundled_variants.value | json }};',\r\n        '</script>'\r\n      );\r\n\r\n      // Even without metafield data, try the AJAX handle-guessing approach as fallback\r\n      console.log('[SB-IMG] Falling back to AJAX handle lookup...');\r\n    }\r\n\r\n    // 3. Attach to existing dropdowns\r\n    const ioContainer = document.querySelector(CONFIG.ioBundleContainer);\r\n    if (ioContainer) attachListeners(ioContainer);\r\n\r\n    // 4. Watch for dynamically injected dropdowns\r\n    const observer = new MutationObserver((mutations) => {\r\n      for (const mut of mutations) {\r\n        for (const node of mut.addedNodes) {\r\n          if (node.nodeType !== 1) continue;\r\n          const container =\r\n            node.matches && node.matches(CONFIG.ioBundleContainer)\r\n              ? node\r\n              : node.querySelector\r\n                ? node.querySelector(CONFIG.ioBundleContainer)\r\n                : null;\r\n          if (container) attachListeners(container);\r\n          if (node.tagName === 'SELECT') {\r\n            const parent = node.closest(CONFIG.ioBundleContainer);\r\n            if (parent) attachListeners(parent);\r\n          }\r\n        }\r\n      }\r\n    });\r\n    observer.observe(document.body, { childList: true, subtree: true });\r\n\r\n    console.log('[SB-IMG] Initialized.');\r\n  }\r\n\r\n  // Start\r\n  if (document.readyState === 'loading') {\r\n    document.addEventListener('DOMContentLoaded', init);\r\n  } else {\r\n    init();\r\n  }\r\n})();\r\n"],
  "mappings": "CAgCC,UAAY,CACX,aAKA,MAAM,OAAS,CAGb,eAAgB,CACd,yDACA,8CACA,oCACA,8CACA,2CACA,sBACA,0BACA,uBACA,wCACA,sBACA,8BACA,4BACA,sCACA,wBACA,6BACA,6BACA,2BACA,wCACA,6BACA,6BACF,EAEA,kBAAmB,6BACnB,aAAc,IACd,WAAY,mBACZ,eAAgB,IAChB,mBAAoB,EACtB,EAKA,IAAI,YAAc,KACd,YAAc,KACd,eAAiB,KACjB,cAAgB,KAChB,uBAAyB,CAAC,EAC1B,uBAAyB,KACzB,eAAiB,KAIrB,MAAM,SAAW,CAAC,EAGZ,aAAe,CAAC,EAUtB,SAAS,oBAAoB,KAAM,CACjC,OAAO,KACJ,QAAQ,yCAA0C,EAAE,EACpD,QAAQ,OAAQ,GAAG,EACnB,KAAK,EACL,YAAY,CACjB,CAMA,SAAS,eAAgB,CACvB,UAAW,OAAO,OAAO,eAAgB,CACvC,MAAM,IAAM,SAAS,cAAc,GAAG,EACtC,GAAI,KAAO,IAAI,IAAK,OAAO,GAC7B,CAGA,MAAM,QACJ,SAAS,cAAc,UAAU,GACjC,SAAS,cAAc,+BAA+B,GACtD,SAAS,cAAc,iBAAiB,GACxC,SAAS,cAAc,MAAM,EAE/B,GAAI,CAAC,QAAS,OAAO,KAErB,IAAI,KAAO,KACP,SAAW,EACf,eAAQ,iBAAiB,KAAK,EAAE,QAAS,KAAQ,CAC/C,MAAM,KAAO,IAAI,YAAc,IAAI,aAC/B,KAAO,UAAY,CAAC,IAAI,QAAQ,OAAO,iBAAiB,IAC1D,KAAO,IACP,SAAW,KAEf,CAAC,EACM,IACT,CAKA,SAAS,kBAAmB,CAC1B,MAAM,MAAQ,OAAO,SAAS,SAAS,MAAM,uBAAuB,EACpE,OAAO,MAAQ,MAAM,CAAC,EAAI,IAC5B,CAKA,eAAe,aAAa,OAAQ,CAClC,GAAI,aAAa,MAAM,EAAG,OAAO,aAAa,MAAM,EACpD,GAAI,CACF,MAAM,IAAM,MAAM,MAAM,aAAa,MAAM,KAAK,EAChD,GAAI,CAAC,IAAI,GAAI,OAAO,KACpB,MAAM,KAAO,MAAM,IAAI,KAAK,EAC5B,oBAAa,MAAM,EAAI,KAChB,IACT,OAAS,EAAG,CACV,eAAQ,KAAK,4BAA6B,OAAQ,CAAC,EAC5C,IACT,CACF,CAKA,SAAS,YAAY,IAAK,MAAO,CAC/B,OAAK,KAEE,IACJ,QAAQ,uBAAwB,GAAG,EACnC,QAAQ,kBAAmB,IAAI,KAAK,QAAQ,CACjD,CAMA,SAAS,2BAA4B,CACnC,GAAI,CAAC,YAAa,OAElB,YAAc,YAAY,aAAa,KAAK,GAAK,YAAY,IAC7D,eAAiB,YAAY,aAAa,QAAQ,EAClD,cAAgB,YAAY,aAAa,OAAO,EAEhD,MAAM,QAAU,YAAY,QAAQ,SAAS,EAC7C,uBAAyB,QACrB,MAAM,KAAK,QAAQ,iBAAiB,QAAQ,CAAC,EAAE,IAAK,SAAY,CAC9D,QAAS,OACT,OAAQ,OAAO,aAAa,QAAQ,EACpC,MAAO,OAAO,aAAa,OAAO,CACpC,EAAE,EACF,CAAC,EAEL,MAAM,OACJ,YAAY,QAAQ,aAAa,GACjC,YAAY,QAAQ,cAAc,GAClC,YAAY,QAAQ,cAAc,EAEpC,uBAAyB,OACrB,CACE,QAAS,OACT,KAAM,OAAO,aAAa,WAAW,EACrC,MAAO,OAAO,aAAa,YAAY,EACvC,MAAO,OAAO,aAAa,YAAY,CACzC,EACA,IACN,CAEA,SAAS,uBAAuB,QAAS,CACnC,iBAAgB,eAAe,OAAS,CAAC,QAC/C,CAMA,SAAS,sBAAuB,CAC9B,GAAI,CAAC,aAAe,OAAO,WAAa,OAAO,eAAgB,OAQ/D,MAAM,KALJ,YAAY,QAAQ,iBAAiB,GACrC,YAAY,QAAQ,kBAAkB,GACtC,YAAY,QAAQ,yBAAyB,GAC7C,aAEkB,sBAAsB,EAAE,IAAM,OAAO,QAAU,OAAO,mBAC1E,OAAO,SAAS,CAAE,IAAK,KAAK,IAAI,EAAG,GAAG,EAAG,SAAU,QAAS,CAAC,CAC/D,CAMA,SAAS,oBAAqB,CAC5B,GAAI,gBAAkB,SAAS,KAAK,SAAS,cAAc,EAAG,OAE9D,MAAM,QACJ,aAAa,QAAQ,iBAAiB,GACtC,aAAa,QAAQ,kBAAkB,GACvC,aAAa,QAAQ,yBAAyB,EAE3C,UAEL,eAAiB,SAAS,cAAc,QAAQ,EAChD,eAAe,KAAO,SACtB,eAAe,YAAc,OAAO,WACpC,eAAe,OAAS,GACxB,eAAe,aAAa,aAAc,kCAAkC,EAC5E,eAAe,MAAM,QAAU,CAC7B,kBACA,kBACA,WACA,uCACA,kBACA,gBACA,iBACA,eACA,kBACA,iBACF,EAAE,KAAK,GAAG,EACV,eAAe,iBAAiB,QAAS,eAAe,EACxD,QAAQ,sBAAsB,WAAY,cAAc,EAC1D,CAKA,SAAS,UAAU,OAAQ,CACzB,GAAI,CAAC,aAAe,CAAC,OAAQ,OAG7B,MAAM,YAAc,YAAY,IAAI,QAAQ,UAAW,EAAE,EAAE,QAAQ,aAAc,GAAG,EAC9E,QAAU,OAAO,QAAQ,UAAW,EAAE,EAAE,QAAQ,aAAc,GAAG,EACvE,GAAI,cAAgB,QAAS,CAC3B,mBAAmB,EACnB,uBAAuB,EAAI,EAC3B,qBAAqB,EACrB,MACF,CAEA,YAAY,MAAM,WAAa,WAAW,OAAO,YAAY,UAC7D,YAAY,MAAM,QAAU,IAE5B,WAAW,IAAM,CAGf,YAAY,aAAa,SAAU,MAAM,EACzC,MAAM,QAAU,YAAY,QAAQ,SAAS,EACzC,SACF,QAAQ,iBAAiB,QAAQ,EAAE,QAAS,QAAW,CACrD,OAAO,aAAa,SAAU,MAAM,CACtC,CAAC,EAIH,MAAM,OACJ,YAAY,QAAQ,aAAa,GACjC,YAAY,QAAQ,cAAc,GAClC,YAAY,QAAQ,cAAc,EAChC,SACF,OAAO,QAAQ,OAAS,OAAO,QAAQ,KAAO,YAAY,OAAQ,IAAI,GACtE,OAAO,QAAQ,QAAU,OAAO,QAAQ,MAAQ,SAGlD,YAAY,IAAM,OAClB,mBAAmB,EACnB,uBAAuB,EAAI,EAE3B,IAAI,WAAa,GACjB,MAAM,KAAO,IAAM,CACb,aACJ,WAAa,GACb,YAAY,MAAM,QAAU,IAC5B,qBAAqB,EACvB,EACA,YAAY,iBAAiB,OAAQ,KAAM,CAAE,KAAM,EAAK,CAAC,EAErD,YAAY,UAAU,KAAK,CACjC,EAAG,OAAO,YAAY,CACxB,CAEA,SAAS,iBAAkB,CACrB,CAAC,aAAe,CAAC,cACrB,YAAY,MAAM,WAAa,WAAW,OAAO,YAAY,UAC7D,YAAY,MAAM,QAAU,IAC5B,WAAW,IAAM,CAef,GAdA,YAAY,IAAM,YACd,eAAgB,YAAY,aAAa,SAAU,cAAc,EAChE,YAAY,gBAAgB,QAAQ,EACrC,cAAe,YAAY,aAAa,QAAS,aAAa,EAC7D,YAAY,gBAAgB,OAAO,EAExC,uBAAuB,QAAQ,CAAC,CAAE,QAAS,OAAQ,KAAM,IAAM,CACzD,CAAC,SAAW,CAAC,SAAS,KAAK,SAAS,OAAO,IAC3C,OAAQ,QAAQ,aAAa,SAAU,MAAM,EAC5C,QAAQ,gBAAgB,QAAQ,EACjC,MAAO,QAAQ,aAAa,QAAS,KAAK,EACzC,QAAQ,gBAAgB,OAAO,EACtC,CAAC,EAEG,wBAAwB,QAAS,CACnC,KAAM,CAAE,QAAS,KAAM,MAAO,KAAM,EAAI,uBACpC,KAAM,QAAQ,aAAa,YAAa,IAAI,EAC3C,QAAQ,gBAAgB,WAAW,EACpC,MAAO,QAAQ,aAAa,aAAc,KAAK,EAC9C,QAAQ,gBAAgB,YAAY,EACrC,MAAO,QAAQ,aAAa,aAAc,KAAK,EAC9C,QAAQ,gBAAgB,YAAY,CAC3C,CAEA,uBAAuB,EAAK,EAC5B,YAAY,MAAM,QAAU,GAC9B,EAAG,OAAO,YAAY,EACxB,CAyBA,eAAe,cAAc,gBAAiB,CAC5C,GAAI,CAAC,MAAM,QAAQ,eAAe,GAAK,gBAAgB,SAAW,EAAG,CACnE,QAAQ,KAAK,0CAA0C,EACvD,MACF,CAGA,MAAM,aAAe,CAAC,EACtB,UAAW,MAAM,gBACV,GAAG,SACH,aAAa,GAAG,MAAM,IAAG,aAAa,GAAG,MAAM,EAAI,CAAC,GACzD,aAAa,GAAG,MAAM,EAAE,KAAK,EAAE,GAIjC,MAAM,QAAU,OAAO,KAAK,YAAY,EAAE,IAAI,MAAO,QAAW,CAC9D,MAAM,QAAU,MAAM,aAAa,MAAM,EACzC,GAAI,CAAC,QAAS,CACZ,QAAQ,KAAK,oCAAqC,MAAM,EACxD,MACF,CAEA,UAAW,MAAM,aAAa,MAAM,EAAG,CACrC,IAAI,SAAW,KAGf,MAAM,eAAiB,QAAQ,SAAS,KACrC,GAAM,EAAE,KAAO,GAAG,UACrB,EAeA,GAdI,gBAAkB,eAAe,iBACnC,SAAW,eAAe,eAAe,KAIvC,CAAC,UAAY,QAAQ,iBACvB,SAAW,QAAQ,gBAIjB,CAAC,UAAY,QAAQ,QAAU,QAAQ,OAAO,OAAS,IACzD,SAAW,QAAQ,OAAO,CAAC,GAGzB,CAAC,SAAU,CACb,QAAQ,IAAI,mCAAoC,GAAG,aAAa,EAChE,QACF,CAGA,SAAW,YAAY,SAAU,IAAI,EAGrC,MAAM,KAAO,IAAI,IAGjB,GAAI,GAAG,gBACL,UAAW,OAAO,GAAG,gBACf,IAAI,OAAO,KAAK,IAAI,oBAAoB,IAAI,KAAK,CAAC,EAKtD,GAAG,gBACL,KAAK,IAAI,oBAAoB,GAAG,aAAa,CAAC,EAC1C,GAAG,eAAiB,GAAG,gBAAkB,iBAC3C,KAAK,IACH,oBACE,GAAG,GAAG,aAAa,MAAM,GAAG,aAAa,EAC3C,CACF,GAIJ,UAAW,OAAO,KAChB,SAAS,GAAG,EAAI,QAEpB,CACF,CAAC,EAED,MAAM,QAAQ,IAAI,OAAO,EACzB,QAAQ,IAAI,4BAA6B,OAAO,KAAK,QAAQ,EAAE,OAAQ,UAAW,QAAQ,CAC5F,CAOA,eAAe,oBAAqB,CAClC,MAAM,OAAS,iBAAiB,EAChC,GAAI,CAAC,OAAQ,OAAO,KAKpB,GAAI,CACF,MAAM,IAAM,MAAM,MAChB,aAAa,MAAM,OACrB,EACA,GAAI,IAAI,GAAI,CACV,MAAM,KAAO,MAAM,IAAI,KAAK,EACtB,QAAU,KAAK,SAAW,KAGhC,GAAI,QAAQ,WAAY,CACtB,MAAM,OACJ,QAAQ,WAAW,gBACnB,QAAQ,WAAW,8BAA8B,EACnD,GAAI,QAAU,OAAO,iBACnB,OAAO,KAAK,MAAM,OAAO,gBAAgB,CAE7C,CACF,CACF,MAAY,CAEZ,CAGA,MAAM,QAAU,SAAS,iBAAiB,iCAAiC,EAC3E,UAAW,UAAU,QACnB,GAAI,CACF,MAAM,KAAO,KAAK,MAAM,OAAO,WAAW,EAEpC,UAAY,CAAC,IAAK,MAAQ,IAAM,CACpC,GAAI,MAAQ,GAAK,CAAC,KAAO,OAAO,KAAQ,SAAU,OAAO,KACzD,GAAI,IAAI,iBAAkB,CACxB,MAAM,IAAM,IAAI,iBAChB,OAAO,OAAO,KAAQ,SAAW,KAAK,MAAM,GAAG,EAAI,GACrD,CACA,UAAW,OAAO,IAAK,CACrB,MAAM,OAAS,UAAU,IAAI,GAAG,EAAG,MAAQ,CAAC,EAC5C,GAAI,OAAQ,OAAO,MACrB,CACA,OAAO,IACT,EACM,MAAQ,UAAU,IAAI,EAC5B,GAAI,MAAO,OAAO,KACpB,MAAY,CAEZ,CAIF,OAAI,OAAO,wBACF,OAAO,OAAO,yBAA4B,SAC7C,KAAK,MAAM,OAAO,uBAAuB,EACzC,OAAO,wBAGN,IACT,CAMA,SAAS,aAAa,OAAQ,CAC5B,MAAM,SAAW,OAAO,QAAQ,OAAO,aAAa,EACpD,GAAI,CAAC,UAAY,CAAC,SAAS,MAAO,CAChC,gBAAgB,EAChB,MACF,CAEA,MAAM,IAAM,SAAS,YACf,WAAa,oBAAoB,GAAG,EAE1C,QAAQ,IAAI,6BAA8B,CAAE,IAAK,IAAI,KAAK,EAAG,UAAW,CAAC,EAEzE,MAAM,SAAW,SAAS,UAAU,EACpC,GAAI,SACF,QAAQ,IAAI,+BAA2B,QAAQ,EAC/C,UAAU,QAAQ,MACb,CAEL,MAAM,aAAe,OAAO,KAAK,QAAQ,EAAE,KACxC,KAAQ,WAAW,SAAS,GAAG,GAAK,IAAI,SAAS,UAAU,CAC9D,EACI,cACF,QAAQ,IAAI,iCAA6B,YAAY,EACrD,UAAU,SAAS,YAAY,CAAC,GAEhC,QAAQ,IAAI,gCAA4B,UAAU,CAGtD,CACF,CAEA,SAAS,gBAAgB,UAAW,CAClB,UAAU,iBAAiB,QAAQ,EAC3C,QAAS,KAAQ,CACnB,IAAI,QAAQ,aAChB,IAAI,QAAQ,WAAa,IACzB,IAAI,iBAAiB,SAAU,IAAM,aAAa,GAAG,CAAC,EACtD,QAAQ,IAAI,8BAA+B,IAAI,MAAQ,IAAI,IAAM,WAAW,EAC9E,CAAC,CACH,CAMA,eAAe,MAAO,CAEpB,YAAc,cAAc,EACvB,aAaH,0BAA0B,EAC1B,mBAAmB,EACnB,QAAQ,IAAI,6BAA8B,WAAW,IAdrD,QAAQ,KAAK,0DAA0D,EACvE,WAAW,IAAM,CACf,YAAc,cAAc,EACxB,aACF,QAAQ,IAAI,qCAAqC,EACjD,0BAA0B,EAC1B,mBAAmB,GAEnB,QAAQ,MAAM,6CAA6C,CAE/D,EAAG,GAAI,GAQT,MAAM,gBAAkB,MAAM,mBAAmB,EAE7C,iBACF,QAAQ,IAAI,oCAAqC,gBAAgB,OAAQ,SAAS,EAClF,MAAM,cAAc,eAAe,IAEnC,QAAQ,KACN,4DACA,mEACA;AAAA;AAAA;AAAA,EACA,WACA,4GACA,YACF,EAGA,QAAQ,IAAI,gDAAgD,GAI9D,MAAM,YAAc,SAAS,cAAc,OAAO,iBAAiB,EAC/D,aAAa,gBAAgB,WAAW,EAG3B,IAAI,iBAAkB,WAAc,CACnD,UAAW,OAAO,UAChB,UAAW,QAAQ,IAAI,WAAY,CACjC,GAAI,KAAK,WAAa,EAAG,SACzB,MAAM,UACJ,KAAK,SAAW,KAAK,QAAQ,OAAO,iBAAiB,EACjD,KACA,KAAK,cACH,KAAK,cAAc,OAAO,iBAAiB,EAC3C,KAER,GADI,WAAW,gBAAgB,SAAS,EACpC,KAAK,UAAY,SAAU,CAC7B,MAAM,OAAS,KAAK,QAAQ,OAAO,iBAAiB,EAChD,QAAQ,gBAAgB,MAAM,CACpC,CACF,CAEJ,CAAC,EACQ,QAAQ,SAAS,KAAM,CAAE,UAAW,GAAM,QAAS,EAAK,CAAC,EAElE,QAAQ,IAAI,uBAAuB,CACrC,CAGI,SAAS,aAAe,UAC1B,SAAS,iBAAiB,mBAAoB,IAAI,EAElD,KAAK,CAET,GAAG",
  "names": []
}
