{
  "version": 3,
  "sources": ["/cdn/shop/t/100/assets/custom-product-v2.js"],
  "sourcesContent": ["/**\n * custom-product-v2.js\n * ---------------------\n * Replacement for assets/custom-product.js, scoped to sections/main-product-v2.liquid.\n *\n * Architecture (see audit discussed with Gregory before writing this):\n * - One canonical gallery-filter function (the old file had four near-duplicates of this).\n * - Overlay compositing is its own module, only activated per-addon via data-enable-overlay \u2014\n *   products with no chestpiece/ear-tip-style accessories never pay for this module doing anything.\n * - Add-to-cart payload assembly matches the EXISTING contract the cart page depends on:\n *     - accessory addons -> real separate line items (their own variant id)\n *     - engraving -> its own line item (handle `stethoscope-engraving` product) AND a\n *       `properties['Engraving Text']` on the MAIN item, because snippets/main-cart-items.liquid\n *       hides the raw engraving line item and reconstructs a merged row from both. Do not change\n *       this shape without updating that cart template in lockstep.\n * - Reimbursement -> its own line item, gated by a checkbox, quantity locked to 1.\n *\n * Deferred / not ported from custom-product.js (flag to Gregory if a product needs these):\n *   inline product video, country-based variant visibility, scroll progress bar,\n *   description read-more truncation.\n */\n\n(() => {\n  'use strict';\n\n  const CENTS = 100;\n\n  // Plain symbol by default (\"$\", not \"CA$\" / \"A$\"). Pass keepCode for the one spot that should\n  // show the locale-qualified symbol (the sticky bar's final price).\n  function formatMoney(cents, keepCode) {\n    const amount = (Number(cents) || 0) / CENTS;\n    const currency = (window.Shopify && window.Shopify.currency && window.Shopify.currency.active) || 'USD';\n    try {\n      return new Intl.NumberFormat(document.documentElement.lang || 'en-US', {\n        style: 'currency',\n        currency,\n        currencyDisplay: keepCode ? 'symbol' : 'narrowSymbol',\n        minimumFractionDigits: amount % 1 === 0 ? 0 : 2,\n      }).format(amount);\n    } catch (e) {\n      return `$${amount.toFixed(2)}`;\n    }\n  }\n\n  /**\n   * The one canonical alt-text convention parser + gallery filter.\n   * Convention (unchanged from the legacy system, so existing product image alt text keeps working):\n   *   \"Color: <variant name>\"        -> tied to that color\n   *   \"... for config_<key>\"         -> overlay-art source image; hidden from the swipeable gallery\n   *                                     UNLESS key === \"main\" (the base photo used for compositing)\n   *   no \"Color:\" substring at all   -> generic, always shown\n   */\n  const GalleryFilter = {\n    parseAlt(altText) {\n      const alt = (altText || '').toLowerCase();\n      const configMatch = alt.match(/for config_([a-z0-9_-]+)/i);\n      const colorIdx = alt.indexOf('color:');\n      let color = null;\n      if (colorIdx !== -1) {\n        let rest = alt.slice(colorIdx + 'color:'.length).trim();\n        const forIdx = rest.indexOf(' for config_');\n        color = (forIdx !== -1 ? rest.slice(0, forIdx) : rest).trim();\n        // Strip a trailing comma/pipe-delimited segment too, in case other tags follow.\n        color = color.split(/[,|]/)[0].trim();\n      }\n      return {\n        color,\n        configKey: configMatch ? configMatch[1].trim() : null,\n        isGeneric: colorIdx === -1,\n      };\n    },\n\n    isOverlayArtOnly(parsed) {\n      return !!parsed.configKey && parsed.configKey !== 'main';\n    },\n\n    matchesColor(parsed, colorName) {\n      if (!colorName) return true;\n      if (parsed.isGeneric) return true;\n      return parsed.color === colorName.toLowerCase();\n    },\n\n    /** Returns { toShow: [li...], preferredActive: li|null } for a given color name. */\n    resolve(mediaItems, colorName) {\n      const toShow = [];\n      let preferredActive = null;\n      mediaItems.forEach((li) => {\n        const img = li.querySelector('img');\n        const alt = img ? img.getAttribute('alt') : li.dataset.mediaAlt;\n        const parsed = this.parseAlt(alt);\n        if (this.isOverlayArtOnly(parsed)) return;\n        if (this.matchesColor(parsed, colorName)) {\n          toShow.push(li);\n          if (parsed.configKey === 'main') preferredActive = li;\n        }\n      });\n      if (!preferredActive) preferredActive = toShow[0] || null;\n      return { toShow, preferredActive };\n    },\n\n    /* Same shape as resolve() above, but driven by the Eko Control Panel's Gallery Manager\n       (see use_gallery_manager_data in main-product-v2.liquid) instead of alt text - map is\n       { \"<color, lowercase>\": [mediaId, mediaId, ...] }, first id = that color's hero/main\n       shot, exactly mirroring the Gallery Manager's own \"first photo = hero\" convention. Every\n       photo's color is either explicitly assigned or it isn't shown at all for that color - no\n       generic/\"always shown\" bucket the way blank alt text silently became one.\n\n       toShow is built by walking `ids` (the Gallery Manager's own saved drag-reorder sequence\n       for this color), not by walking mediaItems in whatever order product.media naturally\n       has them - the two used to produce the same SET but a different ORDER, which is why a\n       color's live thumbnail rail didn't match its order in the Gallery Manager at all\n       (confirmed live: a color whose lifestyle shots were dragged before the last studio shot\n       in the Gallery Manager still showed that studio shot first on the storefront, because\n       nothing here ever reordered the actual DOM - applyGalleryForColor() below does that part,\n       using this order). */\n    resolveFromManagerMap(mediaItems, colorName, managerMap) {\n      const ids = (managerMap && managerMap[(colorName || '').toLowerCase()]) || [];\n      const byRawId = new Map();\n      mediaItems.forEach((li) => {\n        const match = (li.dataset.mediaId || '').match(/-(\\d+)$/);\n        if (match) byRawId.set(match[1], li);\n      });\n      const toShow = [];\n      ids.forEach((id) => {\n        const li = byRawId.get(String(id));\n        if (li) toShow.push(li);\n      });\n      const preferredActive = toShow[0] || null;\n      return { toShow, preferredActive };\n    },\n\n    /** Find the raw overlay-art <img src> for a given config key + color (gallery-image fallback path). */\n    findOverlayImageSrc(allMediaImgEls, configKey, colorName) {\n      for (const img of allMediaImgEls) {\n        const parsed = this.parseAlt(img.getAttribute('alt'));\n        if (parsed.configKey === configKey && parsed.color === (colorName || '').toLowerCase()) {\n          return img.currentSrc || img.src;\n        }\n      }\n      return null;\n    },\n  };\n\n  class OverlayCompositor {\n    constructor(root, getSecondaryTargets, getHeroItem) {\n      this.root = root;\n      // The main device photo's slide. The overlay art is only aligned to that shot, so it is always layered onto it, never onto\n      // whichever slide happens to be showing (viewing a lifestyle photo and then picking ear tips used to paint them over it).\n      this.getHeroItem = getHeroItem;\n      // Any other place a thumbnail-sized copy of the active image is shown (mobile sticky\n      // bar, savings-bar thumbnail, ...) \u2014 returns an array of container elements to keep in\n      // sync, each already holding its own cloned <img> that this only needs to layer over.\n      this.getSecondaryTargets = getSecondaryTargets;\n      this.layers = new Map(); // target key -> { src, text }\n    }\n\n    set(target, src, text) {\n      if (!target) return;\n      if (!src) {\n        this.layers.delete(target);\n      } else {\n        this.layers.set(target, { src, text });\n      }\n      this.render();\n    }\n\n    clear(target) {\n      this.layers.delete(target);\n      this.render();\n    }\n\n    render() {\n      const activeItem = this.root.querySelector('.product__media-item.is-active');\n      const heroItem = this.getHeroItem ? this.getHeroItem() : null;\n      const targetItem = heroItem || activeItem;\n      if (!targetItem) return;\n      const wrapper =\n        targetItem.querySelector('.product__media-wrapper') ||\n        targetItem.querySelector('.product__media') ||\n        targetItem;\n\n      // Clear stale layers from every slide, not just the active one, so a previously viewed\n      // slide never keeps an old overlay.\n      this.root.querySelectorAll('.product__media-item .eko-pdp-v2-overlay').forEach((el) => el.remove());\n\n      // The overlay art is only aligned to the device photo. An addon's own gallery photo\n      // (the ear tips / chestpiece cover shot) must never get the other addon composited on top.\n      if (targetItem.hasAttribute('data-addon-gallery-item')) {\n        this.renderSecondary();\n        return;\n      }\n\n      this.layers.forEach(({ src }, target) => {\n        if (!src) return;\n        const div = document.createElement('div');\n        div.className = `eko-pdp-v2-overlay eko-pdp-v2-overlay--${target}`;\n        const img = document.createElement('img');\n        img.src = src;\n        img.alt = '';\n        img.loading = 'eager';\n        div.appendChild(img);\n        wrapper.appendChild(div);\n      });\n\n      this.renderSecondary();\n    }\n\n    renderSecondary() {\n      // Keep every other thumbnail-sized copy of the active image in sync too (mobile sticky\n      // bar, savings-bar thumbnail) \u2014 otherwise selecting an overlay accessory only shows up\n      // on the big gallery image, not on any of the small \"what you're about to buy\" previews.\n      const secondaryTargets = this.getSecondaryTargets ? this.getSecondaryTargets() : [];\n      secondaryTargets.forEach((el) => {\n        if (!el) return;\n        el.querySelectorAll('.eko-pdp-v2-overlay').forEach((old) => old.remove());\n        this.layers.forEach(({ src }, target) => {\n          if (!src) return;\n          const div = document.createElement('div');\n          div.className = `eko-pdp-v2-overlay eko-pdp-v2-overlay--${target}`;\n          const img = document.createElement('img');\n          img.src = src;\n          img.alt = '';\n          div.appendChild(img);\n          el.appendChild(div);\n        });\n      });\n    }\n  }\n\n  class EkoProductV2 extends HTMLElement {\n    connectedCallback() {\n      this.section = this.closest('.eko-pdp-v2');\n      this.gallery = this.section.querySelector('.eko-pdp-v2__gallery');\n      this.mediaList = this.gallery.querySelector('.product__media-list');\n      // Excludes addon-contributed slides (initAddonGalleryImages()'s clones, marked\n      // data-addon-gallery-item) - those are managed entirely by addon selection, never by\n      // color, so the color filter below must never touch their display state either way.\n      this.mediaItems = () => Array.from(this.mediaList.querySelectorAll('.product__media-item:not([data-addon-gallery-item])'));\n\n      this.addonEls = Array.from(this.querySelectorAll('[data-eko-addon]'));\n      this.overlay = new OverlayCompositor(\n        this.gallery,\n        () => [this.savingsBar?.querySelector('.eko-pdp-v2-savingsbar__thumb'), this.floatPreviewImage].filter(Boolean),\n        () =>\n          this.heroMediaId\n            ? this.mediaList.querySelector(`.product__media-item[data-media-id=\"${this.heroMediaId}\"]:not([data-addon-gallery-item])`)\n            : null\n      );\n      this.currentColor = null;\n      this.selections = new Map(); // addon element -> { variantId, price, group }\n\n      this.configScript = this.querySelector('script[data-eko-pdp-v2-config]');\n      this.config = this.configScript ? JSON.parse(this.configScript.textContent) : {};\n\n      // Opt-in (use_gallery_manager_data block setting) - null unless the Liquid side rendered\n      // it, in which case applyGalleryForColor() below uses this real per-color photo list\n      // instead of parsing every photo's alt text. See the comment next to this script tag in\n      // main-product-v2.liquid for why this exists.\n      const galleryManagerMapScript = this.querySelector('script[data-eko-gallery-manager-map]');\n      this.galleryManagerMap = galleryManagerMapScript ? JSON.parse(galleryManagerMapScript.textContent) : null;\n\n      this.shippingData = this.readShippingData();\n\n      this.initSavingsBar();\n      this.initFloatingPreview();\n      // Before initAddons(): that call auto-selects the default color, which calls\n      // applyGalleryForColor() to filter the lightbox's slides (dataset.filteredOut) \u2014 but that\n      // filtering silently no-ops against an empty this.lightboxSlides array until initLightbox()\n      // populates it. With initAddons() running first (the original order), every lightbox slide\n      // was left with filteredOut simply unset (neither \"true\" nor \"false\") after the very first,\n      // automatic color selection, so the lightbox treated every color's photos as navigable \u2014\n      // wrong ones only stopped appearing once a shopper manually picked a color at least once\n      // (which re-runs applyGalleryForColor after this.lightboxSlides already exists). Confirmed\n      // live: a color's own lightbox photos included another color's hero shot until this\n      // reorder, via elementFromPoint/dataset inspection on a running page.\n      this.initLightbox();\n      this.initAddonGalleryImages();\n      this.initAddons();\n      this.initCarousels();\n      this.initModelSwitchTransition();\n      this.initBadgeTooltips();\n      this.initSpeciesModal();\n      this.initDropHint();\n      this.initGalleryNavArrows();\n      this.initMobileHeader();\n      this.recomputePrice();\n      this.recomputeShipping();\n      this.recomputeSavingsBar();\n      this.initExtendCard();\n      this.alignStepDots();\n      this.drawStepTimeline();\n      this.timelineReady = true;\n      window.addEventListener('resize', () => {\n        this.alignStepDots();\n        this.drawStepTimeline();\n      });\n      // A plain resize listener isn't enough: the line's endpoints go stale the instant ANY\n      // content below it changes height without the viewport itself resizing \u2014 the Reviews.io\n      // app block async-loading its star widget after this first paint is exactly that case\n      // (it renders near the top of the page, pushing every step down by however tall it\n      // turns out to be). ResizeObserver on the whole info column re-measures on every such\n      // shift \u2014 addon selections changing card height, accordions opening, fonts swapping in,\n      // this widget, or anything else \u2014 not just window resizes.\n      if (typeof ResizeObserver !== 'undefined') {\n        const timelineObserver = new ResizeObserver(() => {\n          this.alignStepDots();\n          this.drawStepTimeline();\n        });\n        timelineObserver.observe(this.section.querySelector('.eko-pdp-v2__info'));\n      }\n      this.initAddToCart();\n    }\n\n    readShippingData() {\n      const script = this.querySelector(`#ShippingEtaVariantData-${CSS.escape(this.config.sectionId || '')}`);\n      if (!script) return {};\n      try {\n        return JSON.parse(script.textContent);\n      } catch (e) {\n        return {};\n      }\n    }\n\n    // ---- Swatch carousels (chestpiece cover / ear tips) --------------------\n\n    initCarousels() {\n      // The arrows live in the addon's heading row (matching Figma), the track further down in\n      // the same addon block \u2014 `[data-eko-addon]` is the nearest shared ancestor for both.\n      this.querySelectorAll('[data-eko-addon]').forEach((wrap) => {\n        const track = wrap.querySelector('.eko-pdp-v2-addon__swatches--carousel');\n        const prevBtn = wrap.querySelector('[data-carousel-prev]');\n        const nextBtn = wrap.querySelector('[data-carousel-next]');\n        if (!track || (!prevBtn && !nextBtn)) return;\n\n        const scrollByAmount = () => {\n          const swatch = track.querySelector('.eko-pdp-v2-addon__swatch');\n          const step = swatch ? swatch.getBoundingClientRect().width + 8 : track.clientWidth * 0.8;\n          return step * 2;\n        };\n\n        const updateArrows = () => {\n          const maxScroll = track.scrollWidth - track.clientWidth;\n          if (prevBtn) prevBtn.disabled = track.scrollLeft <= 1;\n          if (nextBtn) nextBtn.disabled = track.scrollLeft >= maxScroll - 1;\n        };\n\n        prevBtn?.addEventListener('click', () => {\n          track.scrollBy({ left: -scrollByAmount(), behavior: 'smooth' });\n        });\n        nextBtn?.addEventListener('click', () => {\n          track.scrollBy({ left: scrollByAmount(), behavior: 'smooth' });\n        });\n        // The row stays within the info column (about 450px) and scrolls with the arrows; it no longer runs\n        // out to the edge of the screen.\n        track.addEventListener('scroll', updateArrows);\n        window.addEventListener('resize', updateArrows);\n        updateArrows();\n      });\n    }\n\n    // ---- Addons -----------------------------------------------------------\n\n    initAddons() {\n      this.addonEls.forEach((addonEl) => {\n        const inputs = Array.from(addonEl.querySelectorAll('.eko-pdp-v2-addon__input'));\n        inputs.forEach((input) => {\n          input.addEventListener('change', () => this.onAddonChange(addonEl, input));\n        });\n\n        const isMain = addonEl.dataset.isMainVariant === 'true';\n        const checked = inputs.find((i) => i.checked);\n        if (checked) this.onAddonChange(addonEl, checked, { initial: true });\n        if (isMain && !checked && inputs[0]) {\n          // The shared engraving modal (sections/product-engraving.liquid) reads the DOM's\n          // :checked radio directly, independent of our own state \u2014 it must actually be checked,\n          // not just treated as selected internally.\n          inputs[0].checked = true;\n          this.onAddonChange(addonEl, inputs[0], { initial: true });\n        }\n\n        // Toggle cards (carry case, diaphragm set, reimbursement, ...): the real checkbox is an invisible\n        // overlay covering the card. If a tap lands on anything else in the card instead (iOS hit-tests\n        // the overlay less forgivingly than desktop does), forward it to the checkbox so it still toggles.\n        const toggleInput = addonEl.querySelector('.eko-pdp-v2-addon__toggle-input');\n        if (toggleInput) {\n          addonEl.classList.add('has-toggle');\n          addonEl.addEventListener('click', (e) => {\n            if (e.target === toggleInput || e.target.closest('a, button, select, textarea')) return;\n            // A click inside the checkbox's own <label> already activates it natively - forwarding it too would toggle twice.\n            const ownLabel = e.target.closest('label');\n            if (ownLabel && ownLabel.contains(toggleInput)) return;\n            toggleInput.click();\n          });\n        }\n\n        // Engraving edit/remove affordances.\n        if (addonEl.dataset.isEngraving === 'true') {\n          this.wireEngravingBridge(addonEl);\n          if (this.engravingUnlockAddon) this.wireEngravingGate(addonEl);\n        }\n      });\n      this.applyPairedAddons();\n      this.applyEngravingGate();\n    }\n\n    onAddonChange(addonEl, input, opts = {}) {\n      const isMain = addonEl.dataset.isMainVariant === 'true';\n      const enableOverlay = addonEl.dataset.enableOverlay === 'true';\n      const overlayTarget = addonEl.dataset.overlayTarget;\n      const value = input.value;\n      // Checkboxes (reimbursement) don't clear .value on uncheck \u2014 only .checked changes.\n      const isNone = input.type === 'checkbox' ? !input.checked : value === 'none';\n\n      // Track selection for cart/price purposes.\n      if (isNone) {\n        this.selections.delete(addonEl);\n      } else {\n        this.selections.set(addonEl, {\n          variantId: input.dataset.variantId,\n          price: Number(input.dataset.price) || 0,\n          bundlePrice: input.dataset.bundleDiscountPrice ? Number(input.dataset.bundleDiscountPrice) : null,\n          noBundleDiscount: addonEl.dataset.noBundleDiscount === 'true',\n          isEngraving: addonEl.dataset.isEngraving === 'true',\n          isReimbursement: addonEl.dataset.isReimbursement === 'true',\n          isAutoAdd: addonEl.dataset.autoAdd === 'true',\n          sellingPlanId: input.dataset.sellingPlan || null,\n          engravingText: this.getEngravingText(addonEl),\n        });\n      }\n\n      // Variant preview panel (\"You've selected: X\").\n      if (addonEl.dataset.showVariantPreview === 'true') {\n        this.renderVariantPreview(addonEl, input, isNone);\n      }\n\n      // Timeline progress dot (not rendered at all for the main color \u2014 see the Liquid\n      // comment next to .eko-pdp-v2-addon__step-dot for why): hollow until this item has an\n      // active selection, filled once it does.\n      const stepDot = addonEl.querySelector('.eko-pdp-v2-addon__step-dot');\n      if (stepDot) stepDot.classList.toggle('is-selected', !isNone);\n      // JS-driven \"on\" state for toggle cards (the CSS :has() version isn't available everywhere).\n      if (input.classList.contains('eko-pdp-v2-addon__toggle-input')) addonEl.classList.toggle('is-on', !isNone);\n\n      // Main color drives the base gallery image; any addon can drive an overlay layer.\n      if (isMain && !isNone) {\n        this.currentColor = input.dataset.colorName || input.value;\n        this.applyGalleryForColor(this.currentColor);\n        this.applyPairedAddons();\n        // Re-apply any active overlays on top of the newly-shown base image.\n        this.overlay.render();\n\n        // Keep ?variant= in sync with the picked color so a refresh (or a shared/bookmarked\n        // link) lands back on it \u2014 Shopify sets product.selected_variant from this param\n        // automatically, and default_variant (top of main-product-v2.liquid) falls back to\n        // cheapest_variant whenever it's nil, which is exactly what was happening with no URL\n        // update at all. Same mechanism the live PDP's custom-product.js already uses\n        // (updateUrlParam) \u2014 not opts.initial, so this only fires on an actual pick, not the\n        // auto-select that runs once on load.\n        if (!opts.initial && input.dataset.variantId) {\n          const url = new URL(window.location.href);\n          url.searchParams.set('variant', input.dataset.variantId);\n          window.history.replaceState({}, '', url.toString());\n          this.updateTopPrice(input.dataset.variantId);\n        }\n      }\n\n      if (enableOverlay) {\n        // Changing an overlay accessory (ear tips, chestpiece cover...) always returns to the main device photo, which is where the\n        // overlays are composited, so the shopper sees what the choice did (not on first load, so a preselected option never\n        // moves the gallery).\n        if (!opts.initial) this.showHeroPhoto();\n        if (isNone) {\n          this.overlay.clear(overlayTarget);\n        } else {\n          const src =\n            input.dataset.overlaySrc ||\n            GalleryFilter.findOverlayImageSrc(\n              Array.from(this.mediaList.querySelectorAll('img')),\n              overlayTarget,\n              this.currentColor\n            );\n          const optionLabel = input.closest('label')?.dataset.optionValue;\n          this.overlay.set(overlayTarget, src, optionLabel);\n        }\n        this.renderSubOverlayPicker(addonEl, input, isNone, overlayTarget);\n      }\n\n      // dataset values are strings, so \"false\" is truthy - compare explicitly.\n      if (!isMain && addonEl.dataset.isEngraving !== 'true') this.applyEngravingGate();\n      // The green progress line follows which add-on dots are filled in.\n      if (this.timelineReady) this.drawStepTimeline();\n\n      if (!opts.initial) {\n        this.recomputePrice();\n        this.recomputeShipping();\n        this.recomputeSavingsBar(isMain ? null : { addonEl, isNone });\n      }\n    }\n\n    // ---- Paired add-on (e.g. diaphragm set follows the selected stethoscope color) --------\n\n    // The selected main color names its partner variant via the `custom.paired_diaphragm_sku`\n    // variant metafield (rendered as data-paired-sku on the swatch). The paired add-on is one\n    // toggle + one photo; this swaps which kit variant that toggle actually adds, along with the\n    // photo and heading price. A blank/unmatched SKU falls back to the first kit variant.\n    applyPairedAddons() {\n      const mainAddon = this.addonEls.find((el) => el.dataset.isMainVariant === 'true');\n      const mainInput = mainAddon?.querySelector('.eko-pdp-v2-addon__input:checked');\n      if (!mainInput) return;\n      const sku = mainInput.dataset.pairedSku || '';\n\n      this.addonEls.forEach((addonEl) => {\n        if (addonEl.dataset.pairedByColor !== 'true') return;\n        let map;\n        try {\n          map = JSON.parse(addonEl.querySelector('script[data-paired-map]')?.textContent || '{}');\n        } catch (e) {\n          return;\n        }\n        const entry = map[sku] || Object.values(map)[0];\n        const toggle = addonEl.querySelector('.eko-pdp-v2-addon__toggle-input');\n        if (!entry || !toggle) return;\n\n        toggle.dataset.variantId = entry.id;\n        toggle.dataset.price = entry.price;\n        toggle.dataset.bundleDiscountPrice = entry.bundle != null ? entry.bundle : '';\n\n        const img = addonEl.querySelector('.eko-pdp-v2-addon__hero img');\n        // Per-color photo (the tube color is in the shot) wins over the kit variant's own image.\n        const heroSrc = mainInput.dataset.pairedImage || entry.image;\n        if (img && heroSrc) {\n          img.removeAttribute('srcset');\n          img.src = heroSrc;\n        }\n\n        const priceEl = addonEl.querySelector('[data-paired-price]');\n        if (priceEl) {\n          const plus = priceEl.querySelector('.eko-pdp-v2-addon__price-plus') ? '<span class=\"eko-pdp-v2-addon__price-plus\">+</span>' : '';\n          priceEl.innerHTML =\n            entry.bundle != null && entry.bundle < entry.price\n              ? `${plus}<s class=\"eko-pdp-v2-addon__price-was\">${formatMoney(entry.price)}</s> ${formatMoney(entry.bundle)}`\n              : `${plus}${formatMoney(entry.price)}`;\n        }\n\n        // Already added: re-register under the new variant so the cart gets the right SKU.\n        if (toggle.checked) this.onAddonChange(addonEl, toggle, { initial: true });\n      });\n    }\n\n    // ---- Engraving gate (CORE 300: engraving needs the diaphragm set, free with it) ---------\n\n    get engravingUnlockAddon() {\n      return this.addonEls.find((el) => el.dataset.unlocksEngraving === 'true') || null;\n    }\n\n    applyEngravingGate() {\n      const unlockAddon = this.engravingUnlockAddon;\n      if (!unlockAddon) return;\n      const engAddon = this.addonEls.find((el) => el.dataset.isEngraving === 'true');\n      if (!engAddon) return;\n\n      const unlocked = this.selections.has(unlockAddon);\n      const wasUnlocked = engAddon.dataset.engravingLocked === 'false';\n      engAddon.dataset.engravingLocked = unlocked ? 'false' : 'true';\n\n      const enabledInput = engAddon.querySelector('input[name=\"engraving-enabled\"][value=\"enabled\"]');\n      if (enabledInput) {\n        // Complimentary while unlocked: a $0 \"bundle price\" flows through effectivePrice().\n        if (enabledInput.dataset.origBundle === undefined) enabledInput.dataset.origBundle = enabledInput.dataset.bundleDiscountPrice || '';\n        enabledInput.dataset.bundleDiscountPrice = unlocked ? '0' : enabledInput.dataset.origBundle;\n        const sel = this.selections.get(engAddon);\n        if (sel) sel.bundlePrice = unlocked ? 0 : enabledInput.dataset.origBundle ? Number(enabledInput.dataset.origBundle) : null;\n      }\n\n      const note = engAddon.querySelector('[data-engraving-lock-note]');\n      if (note) {\n        if (unlocked) {\n          note.hidden = true;\n          delete note.dataset.removed;\n        } else if (!note.dataset.removed) {\n          // The step's own subtext already says engraving needs the set; only the \"removed\" notice is shown.\n          note.hidden = true;\n        }\n      }\n\n      // Locked again while engraving was added (or its editor is open): take it back out.\n      if (!unlocked && (wasUnlocked || this.selections.has(engAddon))) {\n        const hadEngraving = this.selections.has(engAddon);\n        const modal = document.getElementById('engraving-modal');\n        const open = modal && !modal.classList.contains('hidden');\n        if (hadEngraving) engAddon.querySelector('.eko-pdp-v2-addon__engraving-remove')?.click();\n        if (open) engAddon.querySelector('.engraving-cancel')?.click();\n        if (hadEngraving && note) {\n          note.textContent = 'Engraving was removed because it requires the diaphragm set.';\n          note.dataset.removed = 'true';\n          note.hidden = false;\n        }\n      }\n\n      this.renderEngravingPrice(engAddon, unlocked);\n      const includedBadge = document.querySelector('[data-engraving-included]');\n      if (includedBadge) includedBadge.hidden = !unlocked;\n    }\n\n    // The card's price (\"+ $15\") reads \"Included\" while the diaphragm set that makes engraving\n    // complimentary is selected, and goes back to the real price otherwise.\n    renderEngravingPrice(engAddon, unlocked) {\n      const el = engAddon.querySelector('[data-engraving-heading-price]');\n      if (!el) return;\n      if (el.dataset.orig === undefined) el.dataset.orig = el.innerHTML;\n      el.innerHTML = unlocked ? 'Included' : el.dataset.orig;\n    }\n\n    // Capture-phase block on the engraving card while locked, then a lightweight confirm popup.\n    // Capture + stopImmediatePropagation so the shared engraving modal's own bare-class\n    // `.add-engraving` click handler never runs, and preventDefault so the radio never checks.\n    wireEngravingGate(engAddon) {\n      engAddon.dataset.engravingLocked = this.engravingUnlockAddon && !this.selections.has(this.engravingUnlockAddon) ? 'true' : 'false';\n      engAddon.addEventListener(\n        'click',\n        (e) => {\n          // Live check (not the cached data attribute): locked only if the unlocking add-on's own\n          // input isn't checked right now, so an already-added set never triggers the popup.\n          const gate = this.engravingUnlockAddon;\n          const gateInput = gate?.querySelector('.eko-pdp-v2-addon__toggle-input, .eko-pdp-v2-addon__input');\n          if (!gate || gateInput?.checked || this.selections.has(gate)) {\n            engAddon.dataset.engravingLocked = 'false';\n            return;\n          }\n          if (!e.target.closest('.add-engraving, .eko-pdp-v2-addon__radio-label[data-option-value=\"enabled\"]')) return;\n          e.preventDefault();\n          e.stopImmediatePropagation();\n          this.openEngravingUnlockModal(engAddon);\n        },\n        true\n      );\n    }\n\n    openEngravingUnlockModal(engAddon) {\n      const unlockAddon = this.engravingUnlockAddon;\n      if (!unlockAddon) return;\n      const drawer = document.querySelector('engraving-unlock-drawer');\n      if (!drawer || typeof drawer.open !== 'function') {\n        this.openEngravingUnlockDialogFallback(engAddon);\n        return;\n      }\n      this.pendingEngravingAddon = engAddon;\n\n      const toggle = unlockAddon.querySelector('.eko-pdp-v2-addon__toggle-input');\n      const price = Number(toggle?.dataset.price) || 0;\n      const bundle = toggle?.dataset.bundleDiscountPrice ? Number(toggle.dataset.bundleDiscountPrice) : null;\n      const shown = bundle != null && bundle < price ? bundle : price;\n\n      // Same photo as the diaphragm card in the step above (it already follows the selected color).\n      const img = drawer.querySelector('[data-unlock-image]');\n      const src = unlockAddon.querySelector('.eko-pdp-v2-addon__hero img')?.currentSrc || unlockAddon.querySelector('.eko-pdp-v2-addon__hero img')?.src;\n      if (img && src) img.src = src;\n      img?.closest('.eko-pdp-v2-unlock-drawer__media')?.toggleAttribute('hidden', !src);\n\n      drawer.querySelector('[data-unlock-title]').textContent = unlockAddon.dataset.unlockTitle || 'Free Engraving when you add the diaphragm set.';\n      drawer.querySelector('[data-unlock-body]').textContent =\n        unlockAddon.dataset.unlockBody || 'Engraving is complimentary with the Adult + Pediatric Diaphragm Set. Add the set to continue.';\n      drawer.querySelector('[data-unlock-confirm]').textContent = `${unlockAddon.dataset.unlockConfirm || 'Add diaphragm and continue'} +${formatMoney(shown)}`;\n      drawer.querySelector('[data-unlock-dismiss]').textContent = unlockAddon.dataset.unlockCancel || 'Continue Without';\n\n      if (!drawer.dataset.wired) {\n        drawer.dataset.wired = 'true';\n        drawer.querySelector('[data-unlock-dismiss]').addEventListener('click', () => drawer.close());\n        drawer.querySelector('[data-unlock-confirm]').addEventListener('click', () => {\n          const gateAddon = this.engravingUnlockAddon;\n          const input = gateAddon?.querySelector('.eko-pdp-v2-addon__toggle-input');\n          try {\n            if (input && !input.checked) {\n              input.checked = true;\n              input.dispatchEvent(new Event('change', { bubbles: true }));\n            }\n            if (this.pendingEngravingAddon) this.pendingEngravingAddon.dataset.engravingLocked = 'false';\n          } catch (err) {\n            console.error('[eko-product-v2] adding diaphragm set from the recommendation drawer failed', err);\n          }\n          drawer.close();\n          // Let this drawer finish sliding out, then open the engraving drawer through the normal Add engraving path.\n          setTimeout(() => {\n            const engAddonEl = this.pendingEngravingAddon;\n            engAddonEl?.querySelector('.add-engraving')?.click();\n            setTimeout(() => {\n              const modal = document.getElementById('engraving-modal');\n              if (modal && modal.classList.contains('hidden')) window.engravingSvgManager?.openModal?.();\n            }, 80);\n          }, 350);\n        });\n      }\n      drawer.open(engAddon.querySelector('.add-engraving') || engAddon);\n    }\n\n    openEngravingUnlockDialogFallback(engAddon) {\n      const unlockAddon = this.engravingUnlockAddon;\n      if (!unlockAddon) return;\n      if (!this.unlockDialog) {\n        const d = document.createElement('dialog');\n        d.className = 'eko-pdp-v2-unlock';\n        d.innerHTML =\n          '<h3 class=\"eko-pdp-v2-unlock__title\"></h3>' +\n          '<p class=\"eko-pdp-v2-unlock__body\"></p>' +\n          '<div class=\"eko-pdp-v2-unlock__actions\">' +\n          '<button type=\"button\" class=\"eko-pdp-v2-unlock__cancel\"></button>' +\n          '<button type=\"button\" class=\"eko-pdp-v2-unlock__confirm\"></button>' +\n          '</div>';\n        this.appendChild(d);\n        d.querySelector('.eko-pdp-v2-unlock__cancel').addEventListener('click', () => d.close());\n        // Backdrop click closes (clicks on the dialog element itself, not its content).\n        d.addEventListener('click', (e) => {\n          if (e.target === d) d.close();\n        });\n        d.querySelector('.eko-pdp-v2-unlock__confirm').addEventListener('click', () => {\n          d.close();\n          const engAddon = this.pendingEngravingAddon;\n          try {\n            const toggle = this.engravingUnlockAddon?.querySelector('.eko-pdp-v2-addon__toggle-input, .eko-pdp-v2-addon__input');\n            if (toggle && !toggle.checked) {\n              toggle.checked = true;\n              toggle.dispatchEvent(new Event('change', { bubbles: true }));\n            }\n            // Don't depend on the change handler having flipped the gate: unlock explicitly.\n            if (engAddon) engAddon.dataset.engravingLocked = 'false';\n          } catch (err) {\n            console.error('[eko-product-v2] adding diaphragm set from engraving popup failed', err);\n          }\n          // Next tick, after the set's change handling has settled: open the shared engraving\n          // modal via its own Add engraving control, with a direct fallback if that didn't open it.\n          setTimeout(() => {\n            engAddon?.querySelector('.add-engraving')?.click();\n            setTimeout(() => {\n              const modal = document.getElementById('engraving-modal');\n              if (modal && modal.classList.contains('hidden')) window.engravingSvgManager?.openModal?.();\n            }, 60);\n          }, 0);\n        });\n        this.unlockDialog = d;\n      }\n      const d = this.unlockDialog;\n      this.pendingEngravingAddon = engAddon;\n      d.querySelector('.eko-pdp-v2-unlock__title').textContent = unlockAddon.dataset.unlockTitle || 'Add the diaphragm set to engrave';\n      d.querySelector('.eko-pdp-v2-unlock__body').textContent =\n        unlockAddon.dataset.unlockBody || 'Engraving is complimentary with the Adult + Pediatric Diaphragm Set. Add the set to continue.';\n      d.querySelector('.eko-pdp-v2-unlock__confirm').textContent = unlockAddon.dataset.unlockConfirm || 'Add set and continue';\n      d.querySelector('.eko-pdp-v2-unlock__cancel').textContent = unlockAddon.dataset.unlockCancel || 'Not now';\n      if (typeof d.showModal === 'function') d.showModal();\n      else d.setAttribute('open', '');\n    }\n\n    renderVariantPreview(addonEl, input, isNone) {\n      const previewEl = addonEl.querySelector('.eko-pdp-v2-addon__preview');\n      if (!previewEl) return;\n      const previewText = input.dataset.variantPreview || '';\n      const isMain = addonEl.dataset.isMainVariant === 'true';\n\n      // Main color: a name + \"New\" badge heading (built from the swatch's own\n      // data, not a metafield) on top of whatever description text exists \u2014 shown even with\n      // no description, since the heading alone (e.g. \"Ruby Red \u00B7 + $30\") is still the point.\n      // Every other addon (chestpiece cover, ear tips, ...) keeps the older plain-text-only\n      // preview: just the metafield description, hidden entirely when there isn't one.\n      if (isMain) {\n        if (isNone) {\n          previewEl.style.display = 'none';\n          previewEl.innerHTML = '';\n          return;\n        }\n        previewEl.style.display = '';\n        previewEl.innerHTML = '';\n\n        const heading = document.createElement('div');\n        heading.className = 'eko-pdp-v2-addon__preview-heading';\n\n        const name = document.createElement('span');\n        name.className = 'eko-pdp-v2-addon__preview-name';\n        name.textContent = input.dataset.variantTitle || input.value;\n        heading.appendChild(name);\n\n        if (input.dataset.newTag === 'true') {\n          const badge = document.createElement('span');\n          badge.className = 'eko-pdp-v2-addon__preview-new';\n          badge.textContent = 'New';\n          heading.appendChild(badge);\n        }\n\n        previewEl.appendChild(heading);\n\n        if (previewText) {\n          const text = document.createElement('div');\n          text.className = 'eko-pdp-v2-addon__preview-text text-small';\n          text.innerHTML = previewText;\n          previewEl.appendChild(text);\n        }\n        return;\n      }\n\n      if (isNone || !previewText) {\n        previewEl.style.display = 'none';\n        previewEl.innerHTML = '';\n        return;\n      }\n      previewEl.style.display = '';\n      previewEl.innerHTML = previewText;\n    }\n\n    // Bring the main device photo (the one the overlay art is aligned to) into view if another slide is showing.\n    showHeroPhoto() {\n      if (!this.heroMediaId) return;\n      const active = this.mediaList.querySelector('.product__media-item.is-active');\n      if (active && active.dataset.mediaId !== this.heroMediaId) {\n        this.gallery.querySelector('media-gallery')?.setActiveMedia(this.heroMediaId, false);\n      }\n    }\n\n    renderSubOverlayPicker(addonEl, input, isNone, overlayTarget) {\n      const previewEl = addonEl.querySelector('.eko-pdp-v2-addon__preview');\n      if (!previewEl) return;\n      const subs = [1, 2, 3]\n        .map((i) => ({\n          src: input.dataset[`subOverlay${i}Src`],\n          text: input.dataset[`subOverlay${i}Text`],\n          color: input.dataset[`subOverlay${i}Color`],\n        }))\n        .filter((s) => s.src && s.text);\n      // Single-color collections (e.g. Nightfall: just \"Meteorite Black\") only ever populate\n      // image_overlay_1_source \u2014 there's nothing to actually choose between, so no picker;\n      // the one real image already applies via the plain data-overlay-src path above.\n      if (isNone || subs.length < 2) return;\n\n      // renderVariantPreview() hides this panel whenever the variant has no variant_preview\n      // metafield (only some collections do, e.g. CORE 300 Berry), which hid the pills too.\n      previewEl.style.display = '';\n\n      // \"Desert Collection (3 pack)\" etc. \u2014 same label already visible under the swatch\n      // itself, repeated here as the preview card's own heading (per Gregory's reference:\n      // \"You've Selected: Desert Collection (3 pack)\" above \"Includes 1 of each color:\").\n      // Full name (\"Desert Collection (3-pack)\") comes from the input (built in Liquid from the variant title and the\n      // product's pack size); the tile label is only the short name now.\n      const collectionName = input.dataset.collectionHeading || input.closest('label')?.querySelector('.eko-pdp-v2-addon__swatch-label')?.textContent?.trim();\n      if (collectionName) {\n        const heading = document.createElement('div');\n        heading.className = 'eko-pdp-v2-addon__sub-overlays-heading';\n        heading.textContent = collectionName;\n        // Ahead of whatever renderVariantPreview() already put in here (the \"Includes 1 of\n        // each color:\" description) \u2014 heading reads first, matching the reference.\n        previewEl.prepend(heading);\n      }\n\n      const wrap = document.createElement('div');\n      wrap.className = 'eko-pdp-v2-addon__sub-overlays';\n      subs.forEach((s, idx) => {\n        const btn = document.createElement('button');\n        btn.type = 'button';\n        btn.className = 'eko-pdp-v2-addon__sub-overlay' + (idx === 0 ? ' is-active' : '');\n        // Swatch dot from the variant's image_overlay_N_color (Color metafield); no dot if unset.\n        if (s.color) {\n          const dot = document.createElement('span');\n          dot.className = 'eko-pdp-v2-addon__sub-overlay-swatch';\n          dot.style.setProperty('--eko-swatch', s.color);\n          btn.appendChild(dot);\n        }\n        const label = document.createElement('span');\n        label.textContent = s.text;\n        btn.appendChild(label);\n        btn.addEventListener('click', () => {\n          wrap.querySelectorAll('.eko-pdp-v2-addon__sub-overlay').forEach((b) => b.classList.remove('is-active'));\n          btn.classList.add('is-active');\n          this.overlay.set(overlayTarget, s.src, s.text);\n          this.showHeroPhoto();\n        });\n        wrap.appendChild(btn);\n      });\n      previewEl.appendChild(wrap);\n    }\n\n    // ---- Numbered-step connecting line (per Figma's mobile reference) -------\n\n    // Vertically centers each timeline dot on whatever it belongs beside: the card's \"+\" circle when it\n    // has one (carry case, reimbursement, ...), the Extend widget's content, the engraving pills row, or\n    // otherwise (swatch groups like chestpiece cover / ear tips) the add-on's title. Measured, not\n    // hard-coded, so it holds whatever the card's padding, wrapped text or async widgets do to the\n    // layout; re-run from the same ResizeObserver that redraws the timeline line.\n    alignStepDots() {\n      this.querySelectorAll('.eko-pdp-v2-addon__step-dot').forEach((dot) => {\n        const host = dot.parentElement;\n        // Hero cards put the dot inside their own heading row, which already centers it in CSS.\n        if (!host || host.classList.contains('eko-pdp-v2-addon__heading')) return;\n        // Extend's widget aligns to its own checkbox;\n        // otherwise the card's \"+\" circle, engraving row, widget, or title, in that order.\n        const ref =\n          host.querySelector('.extend-offer-toggle__checkbox') ||\n          host.querySelector('.extend-warranty-render input[type=\"checkbox\"]') ||\n          host.querySelector('.eko-pdp-v2-addon__plus') ||\n          host.querySelector('.eko-pdp-v2-addon__engraving') ||\n          host.querySelector('.extend-warranty-render') ||\n          host.querySelector('.eko-pdp-v2-addon__heading');\n        const refRect = ref ? ref.getBoundingClientRect() : null;\n        if (!refRect || !refRect.height) {\n          dot.style.top = '';\n          return;\n        }\n        const hostRect = host.getBoundingClientRect();\n        const center = refRect.top + refRect.height / 2;\n        dot.style.top = `${center - (hostRect.top + host.clientTop) - dot.offsetHeight / 2}px`;\n      });\n    }\n\n    // Extend's SDK renders its offer into .extend-warranty-render some time after load (and not at all\n    // for products it has no plan for). The card + its timeline dot start hidden and appear only once\n    // there is real content, so nothing is ever laid out around an empty box and then shifted.\n    initExtendCard() {\n      this.querySelectorAll('[data-extend-card]').forEach((card) => {\n        const render = card.querySelector('.extend-warranty-render');\n        if (!render) return;\n        const reveal = () => {\n          // Ready once Extend's own offer rendered into .extend-warranty-render, OR once the global\n          // snippet (snippets/extend-product-page.liquid) has injected its \"Add product protection\n          // offered by Extend\" checkbox next to it - that snippet leaves .extend-warranty-render\n          // empty until the shopper ticks the box, so the offer alone is not a reliable signal.\n          const ready =\n            render.childElementCount > 0 ||\n            render.textContent.trim() !== '' ||\n            !!card.querySelector('.extend-offer-toggle');\n          card.classList.toggle('is-pending', !ready);\n          // The theme-wide Extend CSS pins .extend-offer iframe to 160px !important, but Extend sizes the\n          // frame to its content (~119px) - the extra ~40px read as dead padding under the plan buttons.\n          // An inline !important beats that rule, so re-assert Extend's own height with it.\n          card.querySelectorAll('.extend-offer iframe').forEach((frame) => {\n            const h = frame.style.getPropertyValue('height');\n            if (h && frame.style.getPropertyPriority('height') !== 'important') frame.style.setProperty('height', h, 'important');\n            if (frame.style.getPropertyPriority('min-height') !== 'important') frame.style.setProperty('min-height', '0', 'important');\n          });\n          this.alignStepDots();\n          this.drawStepTimeline();\n        };\n        new MutationObserver(reveal).observe(card, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['style', 'class'] });\n        // Timeline dot fills in (like the other add-on cards) once a plan (2 Year / 3 Year) is actually\n        // chosen - ticking the box alone only opens the offer. The plan buttons live in Extend's\n        // iframe, so ask the SDK which plan is selected (falling back to the frame's active button),\n        // re-checking on the checkbox and on a light poll while the box is ticked.\n        const dot = card.querySelector('.eko-pdp-v2-addon__step-dot');\n        const planChosen = () => {\n          const box = card.querySelector('.extend-offer-toggle__checkbox');\n          if (!box || !box.checked) return false;\n          const offer = card.querySelector('.extend-offer');\n          try {\n            const plan = window.ExtendShopify && window.ExtendShopify.getSelectedPlan(offer);\n            if (plan && plan.price > 0) return true;\n          } catch (e) {}\n          try {\n            const doc = offer.querySelector('iframe')?.contentDocument;\n            if (doc && doc.querySelector('.btn-offer.active')) return true;\n          } catch (e) {}\n          return false;\n        };\n        // Price of the plan that is picked right now, in cents: from the Extend SDK's offer instance (the old\n        // ExtendShopify.getSelectedPlan no longer exists), else the value Extend's snippet keeps on the offer element.\n        const planPriceCents = () => {\n          const offer = card.querySelector('.extend-offer');\n          if (!offer) return 0;\n          try {\n            const plan = window.Extend?.buttons?.instance(offer)?.getPlanSelection();\n            if (plan && plan.price > 0) return plan.price;\n          } catch (e) {}\n          return Number(offer.dataset.extendPrice) || 0;\n        };\n        const syncDot = () => {\n          const on = planChosen();\n          if (dot && dot.classList.contains('is-selected') !== on) {\n            dot.classList.toggle('is-selected', on);\n            this.drawStepTimeline();\n          }\n          const cents = on ? planPriceCents() : 0;\n          if (cents !== (this.extendCents || 0)) {\n            this.extendCents = cents;\n            this.recomputePrice();\n          }\n        };\n        // Poll only while the box is ticked (the plan buttons are inside Extend's iframe, which fires nothing\n        // we can listen to); stop as soon as it is unticked instead of ticking forever.\n        let pollTimer = null;\n        const isOpen = () => !!card.querySelector('.extend-offer-toggle__checkbox')?.checked;\n        const poll = () => {\n          syncDot();\n          if (!isOpen()) {\n            clearInterval(pollTimer);\n            pollTimer = null;\n          }\n        };\n        card.addEventListener('change', () => {\n          syncDot();\n          if (isOpen() && !pollTimer) pollTimer = setInterval(poll, 400);\n        });\n        reveal();\n      });\n    }\n\n    drawStepTimeline() {\n      // Standalone product page: no numbered steps, so no connecting line.\n      if (this.dataset.standalone === 'true') return;\n      // The line has to reach every marker sitting on it, not just the numbered step\n      // circles \u2014 .eko-pdp-v2-addon__step-dot (Carry Case, chestpiece cover, ear tips,\n      // engraving, reimbursement, ...) sit on the same line too, including one under the\n      // very last numbered circle (reimbursement, under step 5), which is why stopping at\n      // numbers[last] left the line short of it. querySelectorAll returns both in DOM/visual\n      // top-to-bottom order regardless of the order they're listed in this selector.\n      // Skip markers that aren't rendered (e.g. the Extend card while it is still waiting on its widget).\n      const markers = Array.from(this.querySelectorAll('.eko-pdp-v2-step__number, .eko-pdp-v2-addon__step-dot')).filter(\n        (m) => m.getClientRects().length\n      );\n      if (markers.length < 2) return;\n      const info = this.section.querySelector('.eko-pdp-v2__info');\n      if (!info) return;\n      getComputedStyle(info).position === 'static' && (info.style.position = 'relative');\n\n      let line = info.querySelector(':scope > .eko-pdp-v2-timeline');\n      if (!line) {\n        // Not a <div>: base.css hides empty divs (`div:empty { display: none }`), and this\n        // element is deliberately childless (a plain background-color bar).\n        line = document.createElement('span');\n        line.className = 'eko-pdp-v2-timeline';\n        info.prepend(line);\n      }\n\n      const infoRect = info.getBoundingClientRect();\n      const first = markers[0].getBoundingClientRect();\n      const last = markers[markers.length - 1].getBoundingClientRect();\n      const top = first.top - infoRect.top + first.height / 2;\n      const bottom = last.top - infoRect.top + last.height / 2;\n      line.style.top = `${top}px`;\n      line.style.height = `${bottom - top}px`;\n      line.style.left = `${first.left - infoRect.left + first.width / 2}px`;\n\n      // Green progress segment: from the first step down to the last marker that is \"done\" - steps 1 and 2\n      // (patient type, color) always have a selection, and every add-on dot that is filled in (selected).\n      let doneIdx = Math.min(1, markers.length - 1);\n      markers.forEach((m, i) => {\n        if (m.classList.contains('eko-pdp-v2-addon__step-dot') && m.classList.contains('is-selected')) doneIdx = i;\n      });\n      let progress = info.querySelector(':scope > .eko-pdp-v2-timeline-progress');\n      if (!progress) {\n        progress = document.createElement('span');\n        progress.className = 'eko-pdp-v2-timeline-progress';\n        info.prepend(progress);\n      }\n      const done = markers[doneIdx].getBoundingClientRect();\n      progress.style.top = `${top}px`;\n      progress.style.height = `${Math.max(0, done.top - infoRect.top + done.height / 2 - top)}px`;\n      progress.style.left = line.style.left;\n    }\n\n    // ---- Gallery ------------------------------------------------------------\n\n    // The inline script right after the gallery render (main-product-v2.liquid) strips\n    // src/srcset off every image that isn't eligible for the page's initial default color,\n    // to stop the browser's native lazy-loading from fetching wrong-color images before this\n    // (deferred) script even runs \u2014 see the comment there for why. Restore them the moment an\n    // image's <li>/thumbnail actually becomes the eligible one, i.e. right here, on a real\n    // color switch \u2014 otherwise a newly-selected color's own photos would stay src-less forever.\n    restoreDeferredSrc(el) {\n      const img = el.tagName === 'IMG' ? el : el.querySelector('img');\n      if (!img) return;\n      if (img.dataset.deferredSrc) {\n        img.src = img.dataset.deferredSrc;\n        delete img.dataset.deferredSrc;\n      }\n      if (img.dataset.deferredSrcset) {\n        img.srcset = img.dataset.deferredSrcset;\n        delete img.dataset.deferredSrcset;\n      }\n    }\n\n    applyGalleryForColor(colorName) {\n      const items = this.mediaItems();\n      // Use the Gallery Manager list only for a color that actually has one. A color with no list (store data not filled in\n      // yet, e.g. on a store the Gallery Manager was never run for) used to resolve to ZERO photos and left a blank gallery;\n      // it now falls back to the alt-text rules instead.\n      const managerIds = this.galleryManagerMap && this.galleryManagerMap[(colorName || '').toLowerCase()];\n      const useManager = !!(managerIds && managerIds.length);\n      const { toShow, preferredActive } = useManager\n        ? GalleryFilter.resolveFromManagerMap(items, colorName, this.galleryManagerMap)\n        : GalleryFilter.resolve(items, colorName);\n      const showSet = new Set(toShow);\n      items.forEach((li) => {\n        li.style.display = showSet.has(li) ? '' : 'none';\n        li.classList.remove('is-active');\n        if (showSet.has(li)) this.restoreDeferredSrc(li);\n      });\n      if (preferredActive) preferredActive.classList.add('is-active');\n      this.heroMediaId = preferredActive ? preferredActive.dataset.mediaId : null;\n\n      // toShow is already in the Gallery Manager's saved drag-reorder sequence when\n      // galleryManagerMap is active (see resolveFromManagerMap) - repeatedly appendChild-ing\n      // the shown items in that order actually MOVES each existing node (not a clone) to the\n      // end of its parent, one at a time, which leaves them in exactly that sequence among\n      // themselves; the hidden ones don't matter since display:none takes them out of flow\n      // either way. Without this, the color filter only ever toggled visibility - the visual\n      // order stayed whatever product.media's own (single, global, not-per-color) order\n      // happened to be, which is why a color's live thumbnail rail could show a studio shot\n      // before lifestyle photos the Gallery Manager had it dragged after. Alt-text mode\n      // (GalleryFilter.resolve) has no per-color saved order to honor, so it's left exactly\n      // as before - only reorder when galleryManagerMap actually gave us one.\n      if (useManager && toShow.length) {\n        const mediaParent = toShow[0].parentElement;\n        toShow.forEach((li) => mediaParent.appendChild(li));\n      }\n\n      // The thumbnail rail is a separate <ul> (product-media-gallery-new.liquid's\n      // #Slider-Thumbnails-*), keyed to slides by data-target === the slide's data-media-id \u2014\n      // it needs the same per-color filtering or every color's thumbnails pile up at once.\n      const shownMediaIds = new Set(Array.from(showSet).map((li) => li.dataset.mediaId));\n      const thumbByTarget = new Map();\n      this.gallery.querySelectorAll('.thumbnail-list__item[data-target]:not([data-addon-gallery-item])').forEach((thumb) => {\n        const isShown = shownMediaIds.has(thumb.dataset.target);\n        thumb.style.display = isShown ? '' : 'none';\n        if (isShown) this.restoreDeferredSrc(thumb);\n        thumbByTarget.set(thumb.dataset.target, thumb);\n      });\n      // Same reordering, same reason - this is also what initGalleryNavArrows()'s prev/next\n      // stepping walks (a fresh querySelectorAll each click), so fixing the DOM order here\n      // fixes arrow-key navigation order too, not just the visual rail.\n      if (useManager && toShow.length) {\n        const firstThumb = thumbByTarget.get(toShow[0].dataset.mediaId);\n        const thumbParent = firstThumb && firstThumb.parentElement;\n        if (thumbParent) {\n          toShow.forEach((li) => {\n            const thumb = thumbByTarget.get(li.dataset.mediaId);\n            if (thumb) thumbParent.appendChild(thumb);\n          });\n        }\n      }\n\n      // Same again for the fullscreen lightbox's own slide list (main-product-v2.liquid's\n      // .overlay-gallery__slide, keyed by data-media-id) \u2014 without this, switching color\n      // wouldn't touch it and every color's photos would stay stacked in the lightbox at\n      // once. dataset.filteredOut (not style.display) carries \"does this color use this\n      // slide at all\" separately from style.display, which the lightbox itself already uses\n      // for \"which slide is currently on screen\" \u2014 collapsing the two would make it\n      // impossible to tell \"wrong color\" apart from \"just not the current slide\".\n      // Read from the cached this.lightboxSlides array, NOT a fresh this.section query:\n      // initLightbox() moves .overlay-gallery to a direct child of <body> (to escape the\n      // sticky-gallery stacking-context trap \u2014 see that comment), so once that's happened\n      // .overlay-gallery is no longer inside this.section at all and a query scoped to it\n      // would silently match nothing on every color change after the first.\n      (this.lightboxSlides || []).forEach((slide) => {\n        // Addon-contributed slides are managed by updateAddonGalleryVisibility() only, never\n        // by color - without this, every color switch would mark the currently-selected\n        // addon's own slide filteredOut=true until its addon input was touched again.\n        if (slide.dataset.addonGalleryItem) return;\n        slide.dataset.filteredOut = shownMediaIds.has(slide.dataset.mediaId) ? 'false' : 'true';\n      });\n      // The lightbox only ever shows one slide at a time (the rest are display:none), so\n      // there's no visual DOM order to fix here - just the array stepLightbox()'s prev/next\n      // walks, so re-sequencing the array (not the DOM) is enough.\n      if (useManager && this.lightboxSlides && this.lightboxSlides.length) {\n        const slideByMediaId = new Map(this.lightboxSlides.map((s) => [s.dataset.mediaId, s]));\n        const reordered = [];\n        toShow.forEach((li) => {\n          const slide = slideByMediaId.get(li.dataset.mediaId);\n          if (slide) reordered.push(slide);\n        });\n        this.lightboxSlides.forEach((s) => {\n          if (!reordered.includes(s)) reordered.push(s);\n        });\n        this.lightboxSlides = reordered;\n      }\n\n      // Color switches move the hero itself (a new .is-active), so whichever addon photos are\n      // currently selected need to be re-anchored right after it again too.\n      this.reorderAddonGallerySlides();\n\n      this.updateSavingsBarThumb();\n    }\n\n    // ---- Engraving bridge (reuses the existing global #engraving-modal) -----\n\n    wireEngravingBridge(addonEl) {\n      const noneLabel = addonEl.querySelector('[data-option-value=\"none\"]');\n      const enabledLabel = addonEl.querySelector('[data-option-value=\"enabled\"]');\n      const addPill = addonEl.querySelector('.eko-pdp-v2-addon__pill--add-engraving');\n      const editPill = addonEl.querySelector('.eko-pdp-v2-addon__pill--edit-engraving');\n      const summary = addonEl.querySelector('.eko-pdp-v2-addon__engraving-summary');\n      const textEl = addonEl.querySelector('.eko-pdp-v2-addon__engraving-text');\n      const removeBtn = addonEl.querySelector('.eko-pdp-v2-addon__engraving-remove');\n      const editBtn = addonEl.querySelector('.eko-pdp-v2-addon__engraving-edit');\n\n      // Opening the modal itself is handled by the shared modal's own JS \u2014 it wires real\n      // click listeners directly to the bare `.add-engraving`/`.edit-engraving` classes (see\n      // the Liquid comment above) and, critically, flips `engravingInput.disabled = false` as\n      // part of its own openModal(). Reimplementing that here left the input disabled.\n\n      // Instead of a fullscreen overlay, show that same modal element inline in this card \u2014\n      // moving it doesn't disturb any of its own click/input bindings (they're bound to the\n      // element itself, not to its position in the DOM). One-time and permanent for this page\n      // load: nothing else on the v2 PDP ever needs #engraving-modal back in its original\n      // theme.liquid location, and closing it again just re-adds .hidden (see the CSS override\n      // next to .eko-pdp-v2-addon__engraving-inline), same as it always did.\n      // Preferred home: the slide-in <engraving-drawer> (rendered by the section when there's an engraving\n      // add-on). Falls back to the old inline-in-the-card placement if a template somehow has no drawer.\n      const drawer = document.querySelector('engraving-drawer');\n      const drawerBody = drawer?.querySelector('[data-engraving-drawer-body]');\n      // \"Included\" tag in the drawer's title bar - only for products where engraving is complimentary with\n      // another add-on (CORE 300 + diaphragm set); toggled by applyEngravingGate(). Never shown where\n      // engraving is paid (CORE 500).\n      if (drawer && !drawer.querySelector('[data-engraving-included]')) {\n        const titleText = drawer.querySelector('.eko-drawer__titlebar-text');\n        if (titleText) {\n          const badge = document.createElement('span');\n          badge.className = 'eko-pdp-v2-engraving-drawer__included';\n          badge.dataset.engravingIncluded = '';\n          badge.hidden = true;\n          badge.innerHTML =\n            '<svg viewBox=\"0 0 12 12\" fill=\"none\" aria-hidden=\"true\"><path d=\"M2.5 6.2 5 8.7 9.5 3.3\" stroke=\"currentColor\" stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>Included';\n          titleText.insertAdjacentElement('afterend', badge);\n          this.applyEngravingGate();\n        }\n      }\n      const inlineSlot = drawerBody || addonEl.querySelector('[data-engraving-inline-slot]');\n      const engravingModal = document.getElementById('engraving-modal');\n      if (inlineSlot && engravingModal && engravingModal.parentElement !== inlineSlot) {\n        engravingModal.classList.add('eko-pdp-v2-engraving-inline');\n        // The \"Engraving included\" status row travels with it so it shows inside the drawer.\n        inlineSlot.appendChild(engravingModal);\n        // The shared modal's left button reads \"Remove\" (like the live PDP's). It used to be relabelled\n        // \"Cancel\" here; it is \"Remove\" again and really removes: clears any saved engraving (same as the\n        // summary row's Remove) while the modal's own handler closes the drawer.\n        const cancelBtn = engravingModal.querySelector('.engraving-cancel');\n        if (cancelBtn) {\n          cancelBtn.textContent = 'Remove';\n          cancelBtn.addEventListener('click', () => removeBtn?.click());\n        }\n      }\n\n      removeBtn?.addEventListener('click', () => {\n        const none = noneLabel?.querySelector('input');\n        if (none) {\n          none.checked = true;\n          none.dispatchEvent(new Event('change', { bubbles: true }));\n        }\n        const legacyInput = document.getElementById('engraving-input-old');\n        if (legacyInput) legacyInput.value = '';\n        // This button also carries the bare `remove-engraving` class the shared modal's own\n        // handleRemoveEngraving() targets (it only knows about its own add/edit buttons, not\n        // noneLabel/summary here) - neither of the two changes above fires an event reflect()\n        // listens for (a direct .value= assignment, and a change dispatched on the \"none\" radio\n        // specifically, not the \"enabled\" one reflect watches). setTimeout instead of calling\n        // it inline: don't depend on which of the two same-click listeners on this button runs\n        // first, just run after both are done.\n        setTimeout(() => reflect(), 0);\n      });\n\n      // The existing engraving modal writes text into #engraving-input-old and toggles this\n      // hidden legacy radio group name; watch it so our own UI reflects saved text.\n      const legacyInput = document.getElementById('engraving-input-old');\n      const modalInput = document.getElementById('engraving-input');\n      const isExpanded = () => !!engravingModal && !engravingModal.classList.contains('hidden');\n\n      let drawerShown = false;\n      const reflect = () => {\n        const text = (legacyInput && legacyInput.value.trim()) || '';\n        const expanded = isExpanded();\n        // None/Add engraving only make sense collapsed with nothing saved yet \u2014 hidden the\n        // instant the card expands (even before anything's typed), back the instant it\n        // collapses with no saved text (Cancel with nothing saved, or Remove).\n        if (noneLabel) noneLabel.style.display = expanded || text ? 'none' : '';\n        if (addPill) addPill.style.display = expanded || text ? 'none' : '';\n        // 'inline-block', not '' \u2014 product-engraving.liquid's own <style> block has an\n        // unconditional `.edit-engraving { display: none; }` (its default collapsed state),\n        // which an empty inline value defers right back to instead of overriding.\n        if (editPill) editPill.style.display = !expanded && text ? 'inline-block' : 'none';\n        if (summary) summary.style.display = !expanded && text ? '' : 'none';\n        // Selected look (green border, \"+\" becomes a check) once engraving text is saved.\n        addonEl.classList.toggle('is-on', !!text);\n        if (textEl) textEl.textContent = text;\n        if (text) {\n          const enabledInput = enabledLabel?.querySelector('input');\n          if (enabledInput && !enabledInput.checked) {\n            enabledInput.checked = true;\n            enabledInput.dispatchEvent(new Event('change', { bubbles: true }));\n          }\n        }\n        // \"Engraving included\" checkmark row: only while the card is open AND there's actually\n        // typed text right now \u2014 a blank field never shows it, live as you type/clear, not just\n        // after Save (see modalInput's own 'input' listener below for the live part; this\n        // covers the pre-fill that happens the instant Edit engraving reopens it).\n        // Slide the drawer in/out in step with the modal (it is the drawer's only content).\n        if (drawer && drawerBody) {\n          if (expanded && !drawerShown) {\n            drawerShown = true;\n            drawer.open(addPill || addonEl);\n          } else if (!expanded && drawerShown) {\n            drawerShown = false;\n            drawer.close();\n          }\n        }\n      };\n      legacyInput?.addEventListener('input', reflect);\n      modalInput?.addEventListener('input', reflect);\n      document.addEventListener('eko:engraving-saved', reflect);\n      // The modal sets legacyInput.value via a direct property assignment (no 'input' event),\n      // then dispatches `change` on the enabled radio \u2014 that's the reliable signal to reflect from.\n      enabledLabel?.querySelector('input')?.addEventListener('change', reflect);\n      // Catches every way the modal itself can open/close (Add/Edit engraving, Cancel, the\n      // floating X, Save's own delayed closeModal()) without hooking each one individually \u2014\n      // openModal()/closeModal() only ever toggle #engraving-modal.hidden, and openModal() sets\n      // engravingInput.value synchronously before that toggle, so this always sees the right\n      // value the moment it fires.\n      if (engravingModal) new MutationObserver(reflect).observe(engravingModal, { attributes: true, attributeFilter: ['class'] });\n      reflect();\n    }\n\n    getEngravingText(addonEl) {\n      if (addonEl.dataset.isEngraving !== 'true') return null;\n      const legacyInput = document.getElementById('engraving-input-old');\n      return (legacyInput && legacyInput.value.trim()) || null;\n    }\n\n    // ---- Price --------------------------------------------------------------\n\n    // What the cart actually charges for a selection: its bundle price when it has one (and\n    // bundling isn't disabled for that addon), otherwise list price. A missing metafield is null,\n    // never 0 - see the Liquid note next to data-bundle-discount-price.\n    effectivePrice(sel) {\n      if (!sel) return 0;\n      // Standalone product page (section setting): nothing to bundle with, so always the regular price.\n      if (this.dataset.standalone === 'true') return sel.price;\n      return !sel.noBundleDiscount && sel.bundlePrice != null && sel.bundlePrice < sel.price ? sel.bundlePrice : sel.price;\n    }\n\n    recomputePrice() {\n      let total = 0;\n      let compareTotal = 0;\n      let hasDiscount = false;\n\n      const mainAddon = this.addonEls.find((el) => el.dataset.isMainVariant === 'true');\n      const mainSel = mainAddon ? this.selections.get(mainAddon) : null;\n      const mainPrice = mainSel ? mainSel.price : 0;\n      // The device itself is discounted at checkout too (its own variant_bundle_discount_price,\n      // same as every accessory), so it counts toward the total and the savings, not just list price.\n      const mainEffective = this.effectivePrice(mainSel);\n      if (mainEffective !== mainPrice) hasDiscount = true;\n      total += mainEffective;\n      compareTotal += mainPrice;\n\n      this.selections.forEach((sel, addonEl) => {\n        if (addonEl.dataset.isMainVariant === 'true') return;\n        const effective = this.effectivePrice(sel);\n        if (effective !== sel.price) hasDiscount = true;\n        total += effective;\n        compareTotal += sel.price;\n      });\n\n      // The Extend protection plan, once one is picked, is a real line in the cart (Extend adds it on Add to Cart), so the bar's\n      // price includes it. It has no discount, so it goes on both sides.\n      const extendPrice = this.extendCents || 0; // cents, like every other price here\n      total += extendPrice;\n      compareTotal += extendPrice;\n\n      const finalEl = this.querySelector('.pdp-bundle-price-final');\n      const compareEl = this.querySelector('.pdp-bundle-price-compare');\n      const savingsEl = this.querySelector('.pdp-bundle-savings');\n\n      if (finalEl) finalEl.textContent = formatMoney(total, true);\n      if (compareEl) {\n        if (hasDiscount && compareTotal > total) {\n          compareEl.textContent = formatMoney(compareTotal);\n          compareEl.style.display = '';\n        } else {\n          compareEl.style.display = 'none';\n        }\n      }\n      if (savingsEl) {\n        savingsEl.style.display = hasDiscount ? '' : 'none';\n      }\n\n      this.updateStepPrice(mainSel);\n    }\n\n    // \"Choose your stethoscope color: ~~$449~~ $399\" - follows the selected color.\n    updateStepPrice(mainSel) {\n      const el = this.querySelector('[data-main-price]');\n      if (!el) return;\n      const now = this.effectivePrice(mainSel);\n      if (mainSel && now < mainSel.price) {\n        el.innerHTML = `<s class=\"eko-pdp-v2-step__price-was\">${formatMoney(mainSel.price)}</s> ${formatMoney(now)}`;\n      } else {\n        el.textContent = '';\n      }\n    }\n\n    // The top-of-page price (Dawn's own snippets/price.liquid, rendered once server-side into\n    // #price-v2-<section.id>) is a completely separate element from the savings bar's own\n    // pdp-bundle-price-final span that recomputePrice() above already keeps live \u2014 nothing\n    // updated this one on a color change, so it just sat frozen on whatever variant happened\n    // to be selected at page load. Re-implementing price.liquid's regular/sale/badge/unit-price\n    // logic here in JS would be a second copy of it to keep in sync forever; instead this\n    // fetches the section re-rendered for the new variant (same section_id query-param trick\n    // Dawn's own product-info.js uses for this exact element) and swaps in the returned markup\n    // verbatim, so formatting/currency/sale styling always matches Liquid's own output exactly.\n    updateTopPrice(variantId) {\n      const sectionId = this.config.sectionId;\n      if (!variantId || !sectionId) return;\n      const url = new URL(window.location.href);\n      url.searchParams.set('variant', variantId);\n      url.searchParams.set('section_id', sectionId);\n\n      // A fast double-pick could otherwise let an older response land after a newer one and\n      // leave a stale price on screen \u2014 abort whatever's still in flight first.\n      this.priceFetchController?.abort();\n      const controller = new AbortController();\n      this.priceFetchController = controller;\n\n      fetch(url.toString(), { signal: controller.signal })\n        .then((res) => res.text())\n        .then((text) => {\n          const html = new DOMParser().parseFromString(text, 'text/html');\n          const source = html.getElementById(`price-v2-${sectionId}`);\n          const destination = document.getElementById(`price-v2-${sectionId}`);\n          if (source && destination) destination.innerHTML = source.innerHTML;\n        })\n        .catch((err) => {\n          if (err.name !== 'AbortError') console.error(err);\n        });\n    }\n\n    // ---- Sticky savings bar (progress toward max possible bundle savings) ---\n\n    initSavingsBar() {\n      this.savingsBar = this.querySelector('[data-savings-bar]');\n      if (!this.savingsBar) return;\n      // Computed once from static markup: the best available bundle-discount saving per\n      // optional addon group (the main variant's own saving is added separately in\n      // recomputeSavingsBar, since it depends on which color is currently selected).\n      this.groupMaxSaving = new Map();\n      let maxTotal = 0;\n      this.addonEls.forEach((addonEl) => {\n        if (addonEl.dataset.isMainVariant === 'true') return;\n        const inputs = Array.from(addonEl.querySelectorAll('.eko-pdp-v2-addon__input'));\n        let best = 0;\n        inputs.forEach((input) => {\n          if (input.value === 'none') return;\n          const price = Number(input.dataset.price) || 0;\n          const bundlePrice = input.dataset.bundleDiscountPrice && this.dataset.standalone !== 'true' ? Number(input.dataset.bundleDiscountPrice) : null;\n          if (bundlePrice != null && bundlePrice < price) {\n            best = Math.max(best, price - bundlePrice);\n          }\n          // Engraving that is complimentary with another add-on (CORE 300 + diaphragm set) can end up\n          // fully free, so its best possible saving is its whole price - not just the bundle discount.\n          // Without this, \"Total savings\" could read higher than \"Up to\" once the set was added.\n          if (addonEl.dataset.isEngraving === 'true' && this.engravingUnlockAddon) {\n            best = Math.max(best, price);\n          }\n        });\n        if (best > 0) {\n          this.groupMaxSaving.set(addonEl, best);\n          maxTotal += best;\n        }\n      });\n      this.maxPossibleSaving = maxTotal;\n\n      const toggleBtn = this.savingsBar.querySelector('[data-savings-toggle]');\n      const progressEl = this.savingsBar.querySelector('.eko-pdp-v2-savingsbar__progress');\n      toggleBtn?.addEventListener('click', () => {\n        if (!progressEl) return;\n        // A manual open/close always wins over the automatic \"flash\" below.\n        clearTimeout(this.savingsFlashTimer);\n        this.savingsFlashing = false;\n        const expanded = progressEl.classList.contains('is-open');\n        this.setSavingsOpen(!expanded);\n      });\n\n      this.positionSavingsBarFixed();\n      window.addEventListener('resize', () => this.positionSavingsBarFixed());\n      // The column and the cards keep settling after first paint (fonts, images, Extend, a scrollbar appearing shift them without a\n      // window resize), so measure again whenever they change size, and once everything has loaded.\n      const infoForBar = this.section.querySelector('.eko-pdp-v2__info');\n      if (infoForBar && typeof ResizeObserver !== 'undefined') {\n        let barFrame = 0;\n        const again = () => { cancelAnimationFrame(barFrame); barFrame = requestAnimationFrame(() => this.positionSavingsBarFixed()); };\n        new ResizeObserver(again).observe(infoForBar);\n        new ResizeObserver(again).observe(document.documentElement);\n      }\n      window.addEventListener('load', () => this.positionSavingsBarFixed());\n      document.fonts?.ready?.then(() => this.positionSavingsBarFixed());\n\n      // Desktop's .is-pinned is plain position:fixed (see positionSavingsBarFixed) - visible\n      // from page load per Figma, but with nothing to make it go away again on its own once\n      // scrolled past, unlike mobile's position:sticky (which un-sticks itself once\n      // .eko-pdp-v2__info, its containing block, ends). Hide it once the whole PDP section has\n      // scrolled above the viewport instead of leaving it floating over unrelated content\n      // (related products, footer) for the rest of the page.\n      if (typeof IntersectionObserver !== 'undefined') {\n        const pdpSection = this.closest('.eko-pdp-v2') || this.section;\n        const observer = new IntersectionObserver(\n          ([entry]) => {\n            const scrolledPast = !entry.isIntersecting && entry.boundingClientRect.top < 0;\n            this.savingsBar.classList.toggle('is-past-section', scrolledPast);\n          },\n          { threshold: 0 }\n        );\n        observer.observe(pdpSection);\n      }\n    }\n\n    positionSavingsBarFixed() {\n      if (!this.savingsBar) return;\n      const infoEl = this.section.querySelector('.eko-pdp-v2__info');\n      const isDesktop = window.matchMedia('(min-width: 900px)').matches;\n      if (!isDesktop || !infoEl) {\n        this.savingsBar.classList.remove('is-pinned');\n        this.savingsBar.style.left = '';\n        this.savingsBar.style.width = '';\n        return;\n      }\n      // Width is not CSS: the bar is position:fixed (so it floats at the bottom of the screen), which takes it out of the\n      // column's layout, and a fixed element cannot size itself from its neighbours. This measures the column's content (the\n      // accordions, which span the full content width; the add-on cards as a fallback) and sets left/width inline so the bar\n      // overhangs it by the same amount on both sides, kept 8px off the window edge.\n      const rect = infoEl.getBoundingClientRect();\n      const content = infoEl.querySelector('.product__accordion') || infoEl.querySelector('.eko-pdp-v2-addon--card.max-450');\n      const edge = content ? content.getBoundingClientRect() : { left: rect.left + 24, right: rect.right - 24 };\n      const overhang = 12;\n      const winRight = document.documentElement.clientWidth - 8;\n      const left = Math.max(8, edge.left - overhang);\n      const right = Math.min(winRight, edge.right + overhang);\n      this.savingsBar.classList.add('is-pinned');\n      this.savingsBar.style.left = `${Math.round(left)}px`;\n      this.savingsBar.style.width = `${Math.max(0, Math.round(right - left))}px`;\n    }\n\n    recomputeSavingsBar(changed) {\n      if (!this.savingsBar) return;\n\n      // Matches the cart's \"Total Savings\": the device's own discount plus every accessory and\n      // engraving discount, so \"Save up to\" can't come in under what checkout actually shows.\n      let current = 0;\n      this.selections.forEach((sel) => {\n        current += sel.price - this.effectivePrice(sel);\n      });\n\n      const mainAddon = this.addonEls.find((el) => el.dataset.isMainVariant === 'true');\n      const mainSel = mainAddon ? this.selections.get(mainAddon) : null;\n      const mainSaving = mainSel ? mainSel.price - this.effectivePrice(mainSel) : 0;\n      const max = (this.maxPossibleSaving || 0) + mainSaving;\n      const pct = max > 0 ? Math.min(100, Math.round((current / max) * 100)) : 0;\n\n      const currentEl = this.savingsBar.querySelector('.eko-pdp-v2-savingsbar__current');\n      const maxEl = this.savingsBar.querySelector('.eko-pdp-v2-savingsbar__max-value');\n      const fillEl = this.savingsBar.querySelector('.eko-pdp-v2-savingsbar__fill');\n      const messageEl = this.savingsBar.querySelector('.eko-pdp-v2-savingsbar__message');\n\n      if (currentEl) currentEl.textContent = formatMoney(current);\n      if (maxEl) maxEl.textContent = formatMoney(max);\n      if (fillEl) fillEl.style.width = `${pct}%`;\n\n      // Collapsed-state summary line (the bar's default look) \u2014 mirrors current/max above,\n      // just condensed into the one sentence shown under the price instead of the full\n      // Total-savings row + track, which only appears once the toggle is expanded.\n      const toggleBtn = this.savingsBar.querySelector('[data-savings-toggle]');\n      const toggleTextEl = this.savingsBar.querySelector('.eko-pdp-v2-savingsbar__toggle-text');\n      if (toggleBtn && toggleTextEl) {\n        if (max <= 0) {\n          toggleBtn.style.display = 'none';\n        } else {\n          toggleBtn.style.display = '';\n          if (current <= 0) {\n            toggleTextEl.textContent = `Save up to ${formatMoney(max)}`;\n          } else {\n            toggleTextEl.textContent = `${formatMoney(current)} Savings Applied`;\n          }\n        }\n      }\n\n      // When what was just added actually saves money, briefly open the savings breakdown so the shopper\n      // sees it, then fold it away again after a couple of seconds (unless they opened it themselves).\n      if (changed && !changed.isNone) {\n        const sel = this.selections.get(changed.addonEl);\n        if (sel && sel.price - this.effectivePrice(sel) > 0) this.flashSavingsBar();\n      }\n\n      if (messageEl && changed) {\n        if (changed.isNone) {\n          messageEl.style.display = 'none';\n        } else {\n          const headingEl = changed.addonEl.querySelector('.eko-pdp-v2-addon__heading');\n          let heading = 'Item';\n          if (headingEl) {\n            const clone = headingEl.cloneNode(true);\n            clone.querySelector('.eko-pdp-v2-addon__price-suffix')?.remove();\n            heading = clone.textContent.trim();\n          }\n          const remaining = Math.max(0, max - current);\n          messageEl.textContent =\n            remaining > 0\n              ? `${heading} added \u00B7 ${formatMoney(remaining)} more available`\n              : `${heading} added \u00B7 maximum savings reached`;\n          messageEl.style.display = '';\n        }\n      }\n\n      this.updateSavingsBarThumb();\n    }\n\n    flashSavingsBar() {\n      const progressEl = this.savingsBar?.querySelector('.eko-pdp-v2-savingsbar__progress');\n      const toggleBtn = this.savingsBar?.querySelector('[data-savings-toggle]');\n      if (!progressEl || !toggleBtn) return;\n      clearTimeout(this.savingsFlashTimer);\n      // Already open and NOT because of a previous flash = the shopper opened it; leave it alone.\n      if (progressEl.classList.contains('is-open') && !this.savingsFlashing) return;\n      this.savingsFlashing = true;\n      this.setSavingsOpen(true);\n      this.savingsFlashTimer = setTimeout(() => {\n        if (!this.savingsFlashing) return;\n        this.savingsFlashing = false;\n        this.setSavingsOpen(false);\n      }, 1500);\n    }\n\n    // Open/close the savings breakdown with a slide + fade (CSS transition on .is-open), not an instant show/hide.\n    setSavingsOpen(open) {\n      const progressEl = this.savingsBar?.querySelector('.eko-pdp-v2-savingsbar__progress');\n      const toggleBtn = this.savingsBar?.querySelector('[data-savings-toggle]');\n      if (!progressEl) return;\n      progressEl.classList.toggle('is-open', open);\n      progressEl.setAttribute('aria-hidden', String(!open));\n      toggleBtn?.setAttribute('aria-expanded', String(open));\n    }\n\n    // ---- Mobile floating image preview (ported from the old PDP's .pdp-mobile-sticky-preview) ----\n\n    // A small fixed thumbnail of the device photo that slides in once the shopper has scrolled\n    // past the gallery (mobile only), so what they're configuring stays visible while they work\n    // down the steps. It mirrors the main gallery's hero shot including any chestpiece/ear-tip\n    // overlays (it's a secondary target of OverlayCompositor, same as the savings-bar thumb),\n    // and tapping it opens the fullscreen gallery overlay. Like the old one, it steps aside when\n    // the sticky add-to-cart bar reaches the middle of the screen (the end of the column).\n    initFloatingPreview() {\n      const el = document.createElement('button');\n      el.type = 'button';\n      el.className = 'eko-pdp-v2-floatpreview';\n      el.setAttribute('aria-label', 'View product images');\n      el.innerHTML =\n        '<span class=\"eko-pdp-v2-floatpreview__image\"></span>' +\n        '<span class=\"eko-pdp-v2-floatpreview__label\" aria-hidden=\"true\">' +\n        '<svg viewBox=\"0 0 24 24\" fill=\"none\"><circle cx=\"10.5\" cy=\"10.5\" r=\"7\" stroke=\"currentColor\" stroke-width=\"1.8\"/>' +\n        '<path d=\"m16 16 5 5M10.5 7.5v6M7.5 10.5h6\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\"/></svg>' +\n        '<span>Preview</span></span>';\n      document.body.appendChild(el);\n      this.floatPreview = el;\n      this.floatPreviewImage = el.querySelector('.eko-pdp-v2-floatpreview__image');\n\n      el.addEventListener('click', () => this.openLightbox(this.heroMediaId || undefined));\n\n      const mq = window.matchMedia('(max-width: 899px)');\n      let ticking = false;\n      const update = () => {\n        ticking = false;\n        if (!mq.matches) {\n          el.classList.remove('is-visible');\n          return;\n        }\n        const pastGallery = this.gallery.getBoundingClientRect().bottom < 0;\n        const sectionLeft = this.section.getBoundingClientRect().bottom <= 0;\n        const barAtMiddle = this.savingsBar && this.savingsBar.getBoundingClientRect().top <= window.innerHeight / 2;\n        el.classList.toggle('is-visible', pastGallery && !sectionLeft && !barAtMiddle);\n      };\n      const schedule = () => {\n        if (ticking) return;\n        ticking = true;\n        requestAnimationFrame(update);\n      };\n      window.addEventListener('scroll', schedule, { passive: true });\n      window.addEventListener('resize', schedule);\n      mq.addEventListener('change', schedule);\n      schedule();\n    }\n\n    // The selected color's hero photo <img>: the color's own hero slide, else the active slide, else\n    // the first device slide still showing after the color filter (galleries without a tagged hero).\n    heroImage() {\n      const sel = '.product__media-item:not([data-addon-gallery-item])';\n      return (\n        (this.heroMediaId && this.mediaList.querySelector(`${sel}[data-media-id=\"${this.heroMediaId}\"] img`)) ||\n        this.mediaList.querySelector('.product__media-item.is-active img') ||\n        Array.from(this.mediaList.querySelectorAll(sel)).find((li) => li.style.display !== 'none' && li.querySelector('img'))?.querySelector('img') ||\n        null\n      );\n    }\n\n    // Swaps the base (non-overlay) photo inside a preview container (floating preview, savings-bar\n    // thumb) only once the new photo has actually loaded. The old photo stays until then, so a color\n    // change never flashes a broken/blank image while the new color's file is still downloading (or\n    // while its src is still the deferred, not-yet-restored placeholder). Overlay art is left alone.\n    setPreviewImage(container, source) {\n      if (!container || !source) return;\n      const src = source.currentSrc || source.src || source.dataset.deferredSrc || '';\n      const srcset = source.getAttribute('srcset') || source.dataset.deferredSrcset || '';\n      if (!src) return;\n      const token = (container.__previewToken = (container.__previewToken || 0) + 1);\n      const probe = new Image();\n      const apply = () => {\n        if (container.__previewToken !== token) return;\n        container.querySelectorAll('img:not(.eko-pdp-v2-overlay img)').forEach((el) => el.remove());\n        const img = document.createElement('img');\n        img.alt = source.alt || '';\n        if (srcset) img.srcset = srcset;\n        img.src = src;\n        container.prepend(img);\n        this.overlay?.render();\n      };\n      probe.onload = apply;\n      probe.onerror = () => {};\n      if (srcset) probe.srcset = srcset;\n      probe.src = src;\n      if (probe.complete && probe.naturalWidth) apply();\n    }\n\n    updateFloatingPreview() {\n      if (!this.floatPreviewImage) return;\n      // Always the device (hero) photo, never an add-on's own photo or a lifestyle slide: the\n      // overlay art is only aligned to that shot.\n      this.setPreviewImage(this.floatPreviewImage, this.heroImage());\n    }\n\n    updateSavingsBarThumb() {\n      this.updateFloatingPreview();\n      if (!this.savingsBar) return;\n      const thumbEl = this.savingsBar.querySelector('.eko-pdp-v2-savingsbar__thumb');\n      // Same source as the floating preview: the selected color's hero photo (the overlay art is\n      // only aligned to that shot), not whichever gallery slide happens to be showing.\n      this.setPreviewImage(thumbEl, this.heroImage());\n      // Belt-and-suspenders: re-render so the overlay layer is guaranteed correct regardless\n      // of call order between this and overlay.set()/render().\n      this.overlay?.render();\n    }\n\n    // ---- Shipping ETA ---------------------------------------------------------\n\n    // \"In-stock. Ships in 2-3 days.\" for the selected color (engraving ETA when engraving is\n    // added), or '' when there's nothing to say. Shared by the on-page line and the cart property.\n    getShippingMessage() {\n      const mainAddon = this.addonEls.find((elm) => elm.dataset.isMainVariant === 'true');\n      const mainSel = mainAddon ? this.selections.get(mainAddon) : null;\n      const data = (mainSel && this.shippingData[mainSel.variantId]) || {};\n      const hasEngraving = Array.from(this.selections.values()).some((s) => s.isEngraving);\n      const eta = (hasEngraving && data.engraving) || data.regular || '';\n      if (!mainSel || (!eta && !data.preorder)) return null;\n      return { status: data.preorder ? 'Pre-order.' : 'In-stock.', eta };\n    }\n\n    recomputeShipping() {\n      const el = this.querySelector('[data-shipping-eta]');\n      if (!el) return;\n      const message = this.getShippingMessage();\n      if (!message) {\n        el.hidden = true;\n        return;\n      }\n      const status = document.createElement('strong');\n      status.textContent = message.status;\n      el.querySelector('.eko-pdp-v2-shipping__text').replaceChildren(\n        status,\n        document.createTextNode(message.eta ? ` ${message.eta}` : '')\n      );\n      el.hidden = false;\n    }\n\n    // ---- Mobile: title/price/reviews above the gallery -----------------------\n\n    // Figma's mobile frame puts title/price/reviews ABOVE the photo; desktop keeps them at\n    // the top of the info column instead. Rather than rendering this content twice in Liquid\n    // (real risk: the reviews block is a Shopify app block \u2014 installed-app markup isn't\n    // guaranteed safe to render twice on one page) or fighting CSS `order` across elements\n    // that aren't siblings, this actually MOVES the existing title/price/reviews/rating\n    // nodes in the DOM based on viewport, then moves them straight back \u2014 nothing is ever\n    // duplicated, so no element (including the reviews app block) is ever rendered twice.\n    initMobileHeader() {\n      // Phones: the same move already happened in an inline script right after the rating block (so the page never\n      // jumps after first paint). Reuse that wrapper; this method only has to restore it on desktop and redo it on phones.\n      const earlyHeader = this.section && this.section.querySelector('.eko-pdp-v2__mobile-header');\n      if (earlyHeader) {\n        const gridEl = this.section.querySelector('.eko-pdp-v2__grid');\n        const galleryEl = this.section.querySelector('.eko-pdp-v2__gallery');\n        if (!gridEl || !galleryEl) return;\n        const mqEarly = window.matchMedia('(max-width: 899px)');\n        mqEarly.addEventListener('change', () => {\n          if (mqEarly.matches) gridEl.insertBefore(earlyHeader, galleryEl);\n          else this.prepend(earlyHeader);\n        });\n        return;\n      }\n      const titleEl = this.querySelector('.eko-pdp-v2-title');\n      // .eko-pdp-v2-rating-row (rendered by the 'rating' block, right after price/reviews in\n      // the template's block order) is the last of the four elements to hoist \u2014 everything\n      // from titleEl through this one, inclusive, moves together as a single unit.\n      const ratingRow = this.querySelector('.eko-pdp-v2-rating-row');\n      const grid = this.section.querySelector('.eko-pdp-v2__grid');\n      const gallery = this.section.querySelector('.eko-pdp-v2__gallery');\n      if (!titleEl || !ratingRow || !grid || !gallery) return;\n      if (titleEl.parentNode !== ratingRow.parentNode) return;\n\n      let header;\n      try {\n        const range = document.createRange();\n        range.setStartBefore(titleEl);\n        range.setEndAfter(ratingRow);\n        header = document.createElement('div');\n        header.className = 'eko-pdp-v2__mobile-header';\n        range.surroundContents(header);\n      } catch (e) {\n        return;\n      }\n      // Right after surroundContents(), header sits exactly where titleEl used to (a direct\n      // child of this <eko-product-v2> element) \u2014 that's \"home\" to restore to on desktop.\n      const homeParent = header.parentNode;\n\n      const mq = window.matchMedia('(max-width: 899px)');\n      const apply = () => {\n        if (mq.matches) {\n          grid.insertBefore(header, gallery);\n        } else {\n          homeParent.prepend(header);\n        }\n      };\n      mq.addEventListener('change', apply);\n      apply();\n    }\n\n    // ---- Addon-contributed gallery photos (case/chestpiece/eartips) ---------\n\n    // Case/chestpiece/ear-tip photos live on a DIFFERENT product than this page's own, so\n    // there's no product.media loop to hide/show them from the way color photos work - the\n    // Liquid side (main-product-v2.liquid's 'addon' block case) instead renders one hidden\n    // <template data-addon-gallery-slides> per opted-in addon (add_to_gallery setting),\n    // holding a real main-slide/thumbnail/lightbox-slide triple per variant, each using that\n    // variant's own native Shopify image (the same one shown in Admin's variant list - no alt\n    // text, no Control Panel setup). This clones that template's content into the real gallery\n    // containers once on load (all hidden), then shows/hides the one matching whichever\n    // variant is currently selected whenever that addon's own input changes - entirely\n    // independent of the color filter (see the data-addon-gallery-item exclusions in\n    // mediaItems() and applyGalleryForColor() above).\n    initAddonGalleryImages() {\n      const templates = this.section.querySelectorAll('template[data-addon-gallery-slides]');\n      if (!templates.length) return;\n\n      const thumbList = this.gallery.querySelector('.thumbnail-list');\n      const lightboxList = this.overlayGallery && this.overlayGallery.querySelector('.overlay-gallery__slider');\n      // MediaGallery's own constructor (assets/media-gallery.js) is what normally wires a\n      // thumbnail's click to setActiveMedia - but it only wires up the thumbnails present in\n      // the DOM at that moment, once, synchronously. <media-gallery>'s closing tag (and so its\n      // constructor) is parsed before <eko-product-v2>'s own closing tag reaches this\n      // connectedCallback, so every addon thumb appended below (after that constructor already\n      // ran) gets no click handler at all unless it's wired up by hand here too.\n      const mediaGalleryEl = this.gallery.querySelector('media-gallery');\n\n      // blockId -> { variantId -> { mainSlide, thumb, lightboxSlide } }\n      this.addonGallerySlides = {};\n      // Block ids in the order their <template> appears in the DOM - which is block_order's\n      // own sequence (case, chestpiece, eartips), since Liquid renders blocks in that order.\n      // reorderAddonGallerySlides() below walks this same order every time any addon changes,\n      // so whichever ones are currently selected always land in that fixed relative order,\n      // right after the hero - never \"whichever one happened to change most recently\".\n      this.addonBlockOrder = [];\n\n      templates.forEach((tpl) => {\n        const blockId = tpl.dataset.addonBlockId;\n        this.addonBlockOrder.push(blockId);\n        const clone = tpl.content.cloneNode(true);\n        const byVariant = {};\n        Array.from(clone.querySelectorAll('[data-addon-variant-id]')).forEach((el) => {\n          const variantId = el.dataset.addonVariantId;\n          byVariant[variantId] = byVariant[variantId] || {};\n          if (el.dataset.slot === 'main') byVariant[variantId].mainSlide = el;\n          else if (el.dataset.slot === 'thumb') byVariant[variantId].thumb = el;\n          else if (el.dataset.slot === 'lightbox') byVariant[variantId].lightboxSlide = el;\n        });\n        // Append each piece into its real container now, while it's still easy to tell them\n        // apart by data-slot - once appended they just look like any other slide/thumb/slide.\n        Object.values(byVariant).forEach((set) => {\n          if (set.mainSlide) this.mediaList.appendChild(set.mainSlide);\n          if (set.thumb && thumbList) {\n            thumbList.appendChild(set.thumb);\n            const btn = set.thumb.querySelector('button');\n            if (btn && mediaGalleryEl) {\n              btn.addEventListener('click', () => mediaGalleryEl.setActiveMedia(set.thumb.dataset.target, false));\n            }\n          }\n          if (set.lightboxSlide && lightboxList) {\n            lightboxList.appendChild(set.lightboxSlide);\n            if (this.lightboxSlides) this.lightboxSlides.push(set.lightboxSlide);\n          }\n        });\n        this.addonGallerySlides[blockId] = byVariant;\n      });\n\n      const updateForWrapper = (wrapper) => {\n        const slides = this.addonGallerySlides[wrapper.dataset.addonBlockId];\n        if (!slides) return;\n        // The \"None\"/unchecked state has no data-variant-id at all (see the addon block's own\n        // radio/checkbox markup), so an unset addon naturally resolves to null here - same\n        // check works for both the swatch-radio-group and single-variant-checkbox shapes.\n        const checked = wrapper.querySelector('.eko-pdp-v2-addon__input:checked');\n        const activeVariantId = checked ? checked.dataset.variantId : null;\n        Object.entries(slides).forEach(([variantId, set]) => {\n          const show = variantId === activeVariantId;\n          if (set.mainSlide) {\n            set.mainSlide.style.display = show ? '' : 'none';\n            set.mainSlide.classList.remove('is-active');\n          }\n          if (set.thumb) set.thumb.style.display = show ? '' : 'none';\n          if (set.lightboxSlide) set.lightboxSlide.dataset.filteredOut = show ? 'false' : 'true';\n        });\n        // If the slide being viewed was just hidden, fall back to the hero rather than\n        // leaving the gallery with no active slide (blank).\n        if (!this.mediaList.querySelector('.product__media-item.is-active') && this.heroMediaId && mediaGalleryEl) {\n          mediaGalleryEl.setActiveMedia(this.heroMediaId, false);\n          this.overlay.render();\n        }\n      };\n\n      this.section.querySelectorAll('.eko-pdp-v2-addon[data-add-to-gallery=\"true\"]').forEach((wrapper) => {\n        wrapper.querySelectorAll('.eko-pdp-v2-addon__input').forEach((input) => {\n          input.addEventListener('change', () => {\n            updateForWrapper(wrapper);\n            this.reorderAddonGallerySlides();\n          });\n        });\n        updateForWrapper(wrapper);\n      });\n      this.reorderAddonGallerySlides();\n    }\n\n    // Places whichever addon-contributed photos are currently selected right after the color's\n    // own hero shot, in a fixed relative order (addonBlockOrder - case, chestpiece, eartips),\n    // regardless of which addon was most recently touched or which color is active. Called\n    // after every addon change and again at the end of applyGalleryForColor() (color switches\n    // move the hero itself, so the anchor point needs to be re-found either way).\n    reorderAddonGallerySlides() {\n      if (!this.addonGallerySlides) return;\n      // Anchor on the color's own hero, NOT whichever slide happens to be active - the active\n      // one can be an addon photo (user clicked its thumbnail), which would re-anchor the\n      // addon thumbs relative to themselves and push them above the hero.\n      const hero =\n        (this.heroMediaId && this.mediaList.querySelector(`[data-media-id=\"${this.heroMediaId}\"]:not([data-addon-gallery-item])`)) ||\n        this.mediaList.querySelector('.product__media-item.is-active:not([data-addon-gallery-item])');\n      if (!hero) return;\n      const heroMediaId = hero.dataset.mediaId;\n      const thumbList = this.gallery.querySelector('.thumbnail-list');\n      const heroThumb = thumbList ? thumbList.querySelector(`[data-target=\"${heroMediaId}\"]`) : null;\n\n      let mainAnchor = hero;\n      let thumbAnchor = heroThumb;\n      const orderedLightboxSlides = [];\n\n      this.addonBlockOrder.forEach((blockId) => {\n        const slides = this.addonGallerySlides[blockId];\n        const visible = Object.values(slides).find((set) => set.mainSlide && set.mainSlide.style.display !== 'none');\n        if (!visible) return;\n        if (visible.mainSlide) {\n          mainAnchor.after(visible.mainSlide);\n          mainAnchor = visible.mainSlide;\n        }\n        if (visible.thumb && thumbAnchor) {\n          thumbAnchor.after(visible.thumb);\n          thumbAnchor = visible.thumb;\n        }\n        if (visible.lightboxSlide) orderedLightboxSlides.push(visible.lightboxSlide);\n      });\n\n      if (this.lightboxSlides && orderedLightboxSlides.length) {\n        const withoutAddons = this.lightboxSlides.filter((s) => !orderedLightboxSlides.includes(s));\n        const heroIndex = withoutAddons.findIndex((s) => s.dataset.mediaId === heroMediaId);\n        if (heroIndex !== -1) {\n          withoutAddons.splice(heroIndex + 1, 0, ...orderedLightboxSlides);\n          this.lightboxSlides = withoutAddons;\n        }\n      }\n    }\n\n    // Mobile-only pill (prev / dots / next) overlaid bottom-left on the main image; CSS hides\n    // it at tablet-up, where the round side arrows are used instead. Shows at most 5 dots as\n    // a sliding window so a long rail doesn't turn into a wall of dots.\n    // Mobile: swipe the main photo left/right to move through the gallery (same step() the arrows and\n    // pagination pill use, so it only ever lands on this color's visible slides). Passive listeners and a\n    // horizontal-dominant threshold, so vertical page scrolling over the photo is untouched.\n    initGallerySwipe(step) {\n      const host = this.gallery.querySelector('.pdp-main-image');\n      if (!host) return;\n      const mq = window.matchMedia('(max-width: 899px)');\n      let startX = 0;\n      let startY = 0;\n      let tracking = false;\n      host.addEventListener(\n        'touchstart',\n        (e) => {\n          tracking = mq.matches && e.touches.length === 1;\n          if (!tracking) return;\n          startX = e.touches[0].clientX;\n          startY = e.touches[0].clientY;\n        },\n        { passive: true }\n      );\n      host.addEventListener(\n        'touchend',\n        (e) => {\n          if (!tracking) return;\n          tracking = false;\n          const t = e.changedTouches[0];\n          const dx = t.clientX - startX;\n          const dy = t.clientY - startY;\n          if (Math.abs(dx) > 45 && Math.abs(dx) > Math.abs(dy) * 1.5) step(dx < 0 ? 1 : -1);\n        },\n        { passive: true }\n      );\n      host.addEventListener('touchcancel', () => {\n        tracking = false;\n      }, { passive: true });\n    }\n\n    initMobileGalleryPill(step) {\n      const host = this.gallery.querySelector('.pdp-main-image');\n      if (!host) return;\n      const arrow = (dir) =>\n        `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"${dir < 0 ? 'M19 12H5M11 6l-6 6 6 6' : 'M5 12h14M13 6l6 6-6 6'}\"/></svg>`;\n      const pill = document.createElement('div');\n      pill.className = 'eko-pdp-v2-gallery-pill';\n      pill.innerHTML =\n        `<button type=\"button\" class=\"eko-pdp-v2-gallery-pill__btn\" aria-label=\"Previous image\">${arrow(-1)}</button>` +\n        '<div class=\"eko-pdp-v2-gallery-pill__dots\" aria-hidden=\"true\"></div>' +\n        `<button type=\"button\" class=\"eko-pdp-v2-gallery-pill__btn\" aria-label=\"Next image\">${arrow(1)}</button>`;\n      const [prev, next] = pill.querySelectorAll('button');\n      prev.addEventListener('click', () => step(-1));\n      next.addEventListener('click', () => step(1));\n      host.appendChild(pill);\n\n      const dotsEl = pill.querySelector('.eko-pdp-v2-gallery-pill__dots');\n      const update = () => {\n        const thumbs = Array.from(this.gallery.querySelectorAll('.thumbnail-list__item[data-target]')).filter(\n          (el) => el.style.display !== 'none'\n        );\n        const active = this.mediaList.querySelector('.product__media-item.is-active');\n        const idx = Math.max(0, thumbs.findIndex((el) => el.dataset.target === active?.dataset.mediaId));\n        const total = Math.min(thumbs.length, 5);\n        const start = thumbs.length > 5 ? Math.min(Math.max(idx - 2, 0), thumbs.length - 5) : 0;\n        dotsEl.innerHTML = '';\n        for (let i = 0; i < total; i++) {\n          const dot = document.createElement('span');\n          dot.className = 'eko-pdp-v2-gallery-pill__dot' + (start + i === idx ? ' is-active' : '');\n          dotsEl.appendChild(dot);\n        }\n      };\n      let queued = false;\n      const schedule = () => {\n        if (queued) return;\n        queued = true;\n        requestAnimationFrame(() => {\n          queued = false;\n          update();\n        });\n      };\n      new MutationObserver(schedule).observe(this.mediaList, {\n        subtree: true,\n        attributes: true,\n        attributeFilter: ['class', 'style'],\n      });\n      update();\n    }\n\n    // ---- Gallery prev/next arrows --------------------------------------------\n\n    initGalleryNavArrows() {\n      // These buttons (.gallery-nav-prev/.gallery-nav-next, from the shared\n      // product-media-gallery-new snippet) ship with no click handler at all here \u2014 the only\n      // JS that ever wired them up is custom-product.js, which this section deliberately\n      // doesn't load. Rather than reimplement slide-switching from scratch, reuse the exact\n      // mechanism thumbnail clicks already use (media-gallery.js's setActiveMedia), walking\n      // the THUMBNAIL list (not the raw media list) so this can never land on a slide hidden\n      // by the per-color configurator \u2014 thumbnails are already filtered to the current\n      // variant's visible slides.\n      const prevBtn = this.gallery.querySelector('.gallery-nav-prev');\n      const nextBtn = this.gallery.querySelector('.gallery-nav-next');\n      const mediaGalleryEl = this.gallery.querySelector('media-gallery');\n      if (!mediaGalleryEl || (!prevBtn && !nextBtn)) return;\n\n      const step = (direction) => {\n        const thumbButtons = Array.from(mediaGalleryEl.querySelectorAll('[id^=\"GalleryThumbnails\"] [data-target]')).filter(\n          (el) => el.offsetParent !== null\n        );\n        if (!thumbButtons.length) return;\n        const activeMedia = this.mediaList.querySelector('.product__media-item.is-active');\n        const currentIndex = thumbButtons.findIndex((el) => el.dataset.target === activeMedia?.dataset.mediaId);\n        const fromIndex = currentIndex === -1 ? 0 : currentIndex;\n        const nextIndex = (fromIndex + direction + thumbButtons.length) % thumbButtons.length;\n        mediaGalleryEl.setActiveMedia(thumbButtons[nextIndex].dataset.target, false);\n      };\n\n      prevBtn?.addEventListener('click', () => step(-1));\n      nextBtn?.addEventListener('click', () => step(1));\n\n      this.initMobileGalleryPill(step);\n      this.initGallerySwipe(step);\n    }\n\n    initLightbox() {\n      const overlayGallery = this.section.querySelector('.overlay-gallery');\n      if (!overlayGallery) return;\n      this.overlayGallery = overlayGallery;\n      // .eko-pdp-v2__gallery is position:sticky, which (unlike relative/static) always\n      // creates its own stacking context regardless of z-index \u2014 that traps this\n      // position:fixed overlay's z-index inside it, so the .eko-pdp-v2__info column (a\n      // later, unpositioned sibling with no stacking context of its own) painted on top of\n      // the whole trapped subtree, overlay included, instead of behind it. Moving the\n      // overlay to a direct child of <body> escapes that, exactly like global.js's own\n      // <modal-dialog> (ModalDialog.connectedCallback) does for the same reason.\n      document.body.appendChild(overlayGallery);\n      this.lightboxSlides = Array.from(overlayGallery.querySelectorAll('.overlay-gallery__slide'));\n\n      // Eligible-for-current-color list, kept separate from which one is on screen right now\n      // (style.display) \u2014 see the dataset.filteredOut comment in applyGalleryForColor().\n      const visibleSlides = () => this.lightboxSlides.filter((s) => s.dataset.filteredOut !== 'true');\n      const currentSlide = () => this.lightboxSlides.find((s) => s.style.display !== 'none') || null;\n\n      // Each slide's <img> is created here, on first display, NOT upfront for all of them \u2014\n      // this product alone has ~85 media across every color, and giving every slide a real\n      // src at init time (the previous approach, nominally with loading=\"lazy\" as a backstop)\n      // measured out to ~85 extra full-resolution (1200px, ~5MB total) requests on EVERY page\n      // load regardless of whether the lightbox was ever opened: loading=\"lazy\" doesn't save\n      // you if .src is assigned before .loading is set (the fetch is already dispatched by\n      // then), and it isn't reliably deferred for images inside a display:none ancestor\n      // either \u2014 .overlay-gallery starts `hidden`, which is exactly the case here. Building\n      // the <img> lazily like this means a slide is only ever fetched once a person actually\n      // opens the lightbox and scrolls/steps to it.\n      //\n      // Video/model slides work the same way but clone their <template> (see the Liquid loop\n      // above) instead of building an <img> \u2014 and, unlike an <img>, get torn back down again\n      // (not just hidden) the moment they're navigated away from or the lightbox is closed:\n      // an <iframe> can't be paused from the outside, and leaving a native <video> merely\n      // hidden would keep decoding/playing (and keep making noise) behind the closed overlay.\n      // Removing the cloned node entirely \u2014 rather than just calling video.pause() \u2014 handles\n      // both cases the same way, and means stepping back to this slide later starts it fresh\n      // from a clean <template> clone instead of a paused mid-video frame.\n      const clearSlideMedia = (slide) => {\n        const live = slide?.querySelector('video, iframe, model-viewer');\n        if (!live) return;\n        if (live.tagName === 'VIDEO') live.pause();\n        live.remove();\n      };\n      this.clearLightboxSlideMedia = clearSlideMedia;\n\n      let updatePill = null;\n      const showSlide = (slide) => {\n        if (!slide) return;\n        this.lightboxSlides.forEach((s) => {\n          if (s !== slide) clearSlideMedia(s);\n        });\n        if (slide.dataset.mediaType && slide.dataset.mediaType !== 'image') {\n          if (!slide.querySelector('video, iframe, model-viewer')) {\n            const template = slide.querySelector('template');\n            if (template) slide.appendChild(template.content.cloneNode(true));\n          }\n        } else if (!slide.querySelector('img')) {\n          const img = document.createElement('img');\n          img.src = slide.dataset.mediaSrc;\n          img.alt = slide.dataset.mediaAlt || '';\n          slide.appendChild(img);\n        }\n        this.lightboxSlides.forEach((s) => {\n          s.style.display = s === slide ? '' : 'none';\n        });\n        this.syncLightboxOverlay(slide);\n        updatePill?.();\n      };\n      this.showLightboxSlide = showSlide;\n\n      const step = (direction) => {\n        const visible = visibleSlides();\n        if (!visible.length) return;\n        const fromPos = visible.indexOf(currentSlide());\n        const nextPos = ((fromPos === -1 ? 0 : fromPos) + direction + visible.length) % visible.length;\n        showSlide(visible[nextPos]);\n      };\n      this.stepLightbox = step;\n\n      // Phones: the expanded image swaps the two round side arrows for the same arrows + dots pill as the\n      // main gallery (Figma). Shown by CSS below 900px; the round buttons are hidden there.\n      {\n        const arrow = (dir) =>\n          `<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"${dir < 0 ? 'M19 12H5M11 6l-6 6 6 6' : 'M5 12h14M13 6l6 6-6 6'}\"/></svg>`;\n        const pill = document.createElement('div');\n        pill.className = 'eko-pdp-v2-gallery-pill eko-pdp-v2-gallery-pill--lightbox';\n        pill.innerHTML =\n          `<button type=\"button\" class=\"eko-pdp-v2-gallery-pill__btn\" aria-label=\"Previous image\">${arrow(-1)}</button>` +\n          '<div class=\"eko-pdp-v2-gallery-pill__dots\" aria-hidden=\"true\"></div>' +\n          `<button type=\"button\" class=\"eko-pdp-v2-gallery-pill__btn\" aria-label=\"Next image\">${arrow(1)}</button>`;\n        const [pPrev, pNext] = pill.querySelectorAll('button');\n        pPrev.addEventListener('click', () => step(-1));\n        pNext.addEventListener('click', () => step(1));\n        overlayGallery.appendChild(pill);\n        const dotsEl = pill.querySelector('.eko-pdp-v2-gallery-pill__dots');\n        updatePill = () => {\n          const visible = visibleSlides();\n          const idx = Math.max(0, visible.indexOf(currentSlide()));\n          const total = Math.min(visible.length, 5);\n          const start = visible.length > 5 ? Math.min(Math.max(idx - 2, 0), visible.length - 5) : 0;\n          dotsEl.innerHTML = '';\n          for (let i = 0; i < total; i++) {\n            const dot = document.createElement('span');\n            dot.className = 'eko-pdp-v2-gallery-pill__dot' + (start + i === idx ? ' is-active' : '');\n            dotsEl.appendChild(dot);\n          }\n        };\n      }\n\n      // Phones: swipe left/right through the expanded images (the same gesture as the main gallery).\n      {\n        const mq = window.matchMedia('(max-width: 899px)');\n        let startX = 0;\n        let startY = 0;\n        let tracking = false;\n        overlayGallery.addEventListener('touchstart', (e) => {\n          tracking = mq.matches && e.touches.length === 1;\n          if (!tracking) return;\n          startX = e.touches[0].clientX;\n          startY = e.touches[0].clientY;\n        }, { passive: true });\n        overlayGallery.addEventListener('touchend', (e) => {\n          if (!tracking) return;\n          tracking = false;\n          const t = e.changedTouches[0];\n          const dx = t.clientX - startX;\n          const dy = t.clientY - startY;\n          if (Math.abs(dx) > 45 && Math.abs(dx) > Math.abs(dy) * 1.5) step(dx < 0 ? 1 : -1);\n        }, { passive: true });\n        overlayGallery.addEventListener('touchcancel', () => { tracking = false; }, { passive: true });\n      }\n\n      overlayGallery.querySelector('.overlay-gallery__close')?.addEventListener('click', () => this.closeLightbox());\n      overlayGallery.querySelector('.overlay-gallery__prev')?.addEventListener('click', () => step(-1));\n      overlayGallery.querySelector('.overlay-gallery__next')?.addEventListener('click', () => step(1));\n\n      // Click on the backdrop (not the photo/buttons themselves) closes it too.\n      overlayGallery.addEventListener('click', (e) => {\n        if (e.target === overlayGallery || e.target === overlayGallery.querySelector('.overlay-gallery__container')) {\n          this.closeLightbox();\n        }\n      });\n\n      document.addEventListener('keydown', (e) => {\n        if (overlayGallery.classList.contains('hidden')) return;\n        if (e.key === 'Escape') this.closeLightbox();\n        else if (e.key === 'ArrowLeft') step(-1);\n        else if (e.key === 'ArrowRight') step(1);\n      });\n\n      // The actual \"open\" trigger: product-thumbnail-1.liquid wraps every main-gallery image\n      // in an <a class=\"open-overlay-gallery\" data-media-id=\"...\"> for exactly this (it's the\n      // same class name the old custom-product.js's GalleryOverlayManager used) \u2014 delegated\n      // so it keeps working as media-gallery.js swaps which slide is .is-active.\n      // Video/3D-model slides never reach here at all: .open-overlay-gallery is display:none\n      // on them (see custom-product-v2.css, right next to the rule that un-hides\n      // <deferred-media>/<product-model> so they play inline instead) \u2014 clicking one hits\n      // Dawn's own deferred-media poster-button directly, no code of ours involved.\n      this.mediaList.addEventListener('click', (e) => {\n        const link = e.target.closest('.open-overlay-gallery');\n        if (!link) return;\n        e.preventDefault();\n        const mediaItem = link.closest('.product__media-item');\n        this.openLightbox(mediaItem?.dataset.mediaId);\n      });\n\n      // No initial showSlide() call here on purpose \u2014 .overlay-gallery starts `hidden` via its\n      // own class, so there's nothing to show yet, and calling it would fetch the default\n      // slide's image before anyone has actually opened the lightbox. openLightbox() picks\n      // and shows the right slide (including creating its <img>, per showSlide above) the\n      // moment it's actually invoked.\n    }\n\n    closeLightbox() {\n      this.overlayGallery?.classList.add('hidden');\n      // Same convention as global.js's <modal-dialog> (ModalDialog.hide()) for locking\n      // background scroll while a fullscreen overlay is open.\n      document.body.classList.remove('overflow-hidden');\n      // Whatever slide was on screen might be a playing video/iframe \u2014 stop it rather than\n      // leaving it running (and audible) behind the now-hidden overlay.\n      const current = this.lightboxSlides?.find((s) => s.style.display !== 'none');\n      this.clearLightboxSlideMedia?.(current);\n    }\n\n    openLightbox(mediaId) {\n      const overlayGallery = this.overlayGallery;\n      if (!overlayGallery || !this.lightboxSlides) return;\n      const targetId = mediaId || this.mediaList.querySelector('.product__media-item.is-active')?.dataset.mediaId;\n      const visible = this.lightboxSlides.filter((s) => s.dataset.filteredOut !== 'true');\n      const target = visible.find((s) => s.dataset.mediaId === targetId) || visible[0];\n      this.showLightboxSlide(target);\n      overlayGallery.classList.remove('hidden');\n      document.body.classList.add('overflow-hidden');\n    }\n\n    // Chestpiece-cover/ear-tips overlay compositing (OverlayCompositor, driven by\n    // this.overlay.layers) only ever touched the main gallery image plus a couple of fixed\n    // thumbnail-sized copies (mobile sticky bar, savings-bar thumb) \u2014 the lightbox is neither,\n    // and can show ANY of the color's photos (lifestyle shots included), not just the hero\n    // studio shot the overlay art was actually composited against. Only reapply it when the\n    // slide on screen IS that hero shot (same data-media-id as the main gallery's .is-active\n    // item); otherwise strip any leftover layer a previous slide picked up.\n    syncLightboxOverlay(slide) {\n      if (!slide) return;\n      slide.querySelectorAll(':scope > .eko-pdp-v2-overlay').forEach((el) => el.remove());\n      const activeMain = this.mediaList.querySelector('.product__media-item.is-active');\n      if (!activeMain || activeMain.dataset.mediaId !== slide.dataset.mediaId) return;\n      this.overlay.layers.forEach(({ src }, target) => {\n        if (!src) return;\n        const div = document.createElement('div');\n        div.className = `eko-pdp-v2-overlay eko-pdp-v2-overlay--${target}`;\n        const img = document.createElement('img');\n        img.src = src;\n        img.alt = '';\n        div.appendChild(img);\n        slide.appendChild(div);\n      });\n    }\n\n    // ---- Model switch: a real sliding pill, animated before navigating -------\n\n    // Clicking the non-active model option is a plain <a href> - a real navigation to a\n    // different product page, same as before. What this adds: a single shared\n    // .eko-pdp-v2-modelswitch__indicator element that physically translates + resizes to\n    // whichever option is (or is about to become) active, instead of each option independently\n    // swapping its own background - checked directly against Peloton's bike/bike+ switcher\n    // (onepeloton.com/shop/bike), which Gregory pointed at after the first pass only crossfaded\n    // colors rather than actually sliding a shape. Their own DOM has no separate indicator\n    // element either (confirmed by inspecting it live) - this is the standard, reliable way to\n    // get a real sliding pill regardless of whatever animation library they're using under the\n    // hood. Navigation itself is still delayed ~200ms so the slide gets to finish playing\n    // first, same as before.\n    initModelSwitchTransition() {\n      const ACTIVE = 'eko-pdp-v2-modelswitch__option--active';\n      this.querySelectorAll('.eko-pdp-v2-modelswitch__options').forEach((track) => {\n        const indicator = track.querySelector('.eko-pdp-v2-modelswitch__indicator');\n        if (!indicator) return;\n        const options = Array.from(track.querySelectorAll('.eko-pdp-v2-modelswitch__option'));\n\n        const position = (target) => {\n          const trackRect = track.getBoundingClientRect();\n          const targetRect = target.getBoundingClientRect();\n          indicator.style.width = `${targetRect.width}px`;\n          indicator.style.transform = `translateX(${targetRect.left - trackRect.left}px)`;\n        };\n\n        // First positioning happens with the transition switched off - otherwise the pill\n        // would visibly slide in from the left (width 0 at x:0) the moment the page loads,\n        // instead of just already being where it belongs. Forcing a layout read\n        // (getBoundingClientRect) between turning the transition off and back on is what makes\n        // the browser actually commit the \"off\" state first, rather than coalescing both style\n        // changes into a single frame and animating anyway.\n        const initiallyActive = options.find((el) => el.classList.contains(ACTIVE));\n        const snapTo = (el) => {\n          indicator.style.transition = 'none';\n          position(el);\n          indicator.getBoundingClientRect();\n          indicator.style.transition = '';\n        };\n        if (initiallyActive) {\n          snapTo(initiallyActive);\n          // Only reveal it once it's already sitting exactly on the active option - see the\n          // opacity:0 default and this class in custom-product-v2.css for why. If no option is\n          // active on this page at all (e.g. neither configured model matches the product\n          // being viewed), it just stays invisible - nothing to point the pill at.\n          indicator.classList.add('eko-pdp-v2-modelswitch__indicator--ready');\n        }\n\n        options.forEach((link) => {\n          if (!link.hasAttribute('href')) return; // the active one is a plain div, not a link\n          link.addEventListener('click', (e) => {\n            e.preventDefault();\n            options.forEach((el) => el.classList.remove(ACTIVE));\n            link.classList.add(ACTIVE);\n            position(link);\n            const href = link.href;\n            setTimeout(() => {\n              window.location.href = href;\n            }, 200);\n          });\n        });\n\n        // Option widths can change across breakpoints (the byline wraps differently, etc.) -\n        // re-snap (no animation) rather than let the pill drift out of alignment.\n        if (typeof ResizeObserver !== 'undefined') {\n          const resizeObserver = new ResizeObserver(() => {\n            const active = options.find((el) => el.classList.contains(ACTIVE));\n            if (active) snapTo(active);\n          });\n          resizeObserver.observe(track);\n        }\n      });\n    }\n\n    // ---- Description trust badges (price match / HSA-FSA / risk-free returns) ----\n    // Popup content lives in inert <template>s next to each description block instance (one\n    // set of templates per block.id, in case the block is ever duplicated) - opens in the same\n    // slide-in drawer Compare Models uses (snippets/eko-drawer.liquid + assets/eko-drawer.js),\n    // via the badge-drawer custom element (assets/custom-badge-drawer.js), not a centered modal.\n\n    initBadgeTooltips() {\n      this.querySelectorAll('[data-badge-tooltip-target]').forEach((badge) => {\n        const drawer = this.querySelector('badge-drawer');\n        if (!drawer) return;\n        const open = () => {\n          const titleText = badge.querySelector('.eko-pdp-v2-description__badge-text')?.textContent || '';\n          drawer.open(badge.dataset.badgeTooltipTarget, titleText, badge);\n        };\n        badge.addEventListener('click', open);\n        badge.addEventListener('keydown', (e) => {\n          if (e.key === 'Enter' || e.key === ' ') {\n            e.preventDefault();\n            open();\n          }\n        });\n      });\n    }\n\n    // ---- Species (People/Animals) interstitial \u2014 navigation only, no state --\n    // Each product's own instance of this block sets data-default-species (see\n    // sections/main-product-v2.liquid's species_toggle case) to whichever audience THAT\n    // product is for - not always \"People\". Clicking the non-default button opens the slide-in\n    // species-drawer (assets/custom-species-drawer.js) recommending the other product; clicking\n    // the already-default one does nothing (it has no data-species-drawer). However the drawer\n    // closes (X, overlay, Escape, \"Continue with CORE X\"), it fires eko:species-drawer-closed and\n    // the toggle resets to this product's own default, not a hardcoded species.\n\n    initSpeciesModal() {\n      const group = this.querySelector('.eko-pdp-v2-species');\n      const defaultSpecies = group?.dataset.defaultSpecies || 'people';\n      const options = this.querySelectorAll('.eko-pdp-v2-species__option');\n\n      options.forEach((btn) => {\n        btn.addEventListener('click', () => {\n          const drawerId = btn.dataset.speciesDrawer;\n          if (!drawerId) return; // the already-default species has no drawer\n          options.forEach((b) => b.classList.toggle('is-active', b === btn));\n          this.querySelector(`#${CSS.escape(drawerId)}`)?.open(btn);\n        });\n      });\n\n      this.addEventListener('eko:species-drawer-closed', () => {\n        options.forEach((b) => b.classList.toggle('is-active', b.dataset.species === defaultSpecies));\n      });\n    }\n\n    // ---- Drop a hint -------------------------------------------------------\n\n    initDropHint() {\n      const modal = this.querySelector('[data-drop-hint-modal]');\n      if (!modal) return;\n\n      const close = () => modal.classList.add('hidden');\n      // The button lives in the rating row, which phones move above the gallery (outside this element).\n      (this.section || this).querySelectorAll('[data-drop-hint-open]').forEach((btn) => {\n        btn.addEventListener('click', () => modal.classList.remove('hidden'));\n      });\n      modal.querySelectorAll('[data-drop-hint-close]').forEach((el) => {\n        el.addEventListener('click', close);\n      });\n\n      // No gifting app is wired up in this theme (checked: the old \"Drop a Hint\" button\n      // elsewhere in the store is a dead stub with no backend at all) \u2014 this opens the\n      // visitor's own email client with a pre-filled note instead of pretending to send\n      // anything server-side.\n      const form = modal.querySelector('[data-drop-hint-form]');\n      form?.addEventListener('submit', (e) => {\n        e.preventDefault();\n        const to = form.elements.to.value;\n        const note = form.elements.note.value;\n        const subject = encodeURIComponent(`A hint for you: ${document.title}`);\n        const body = encodeURIComponent(`${note ? note + '\\n\\n' : ''}${location.href}`);\n        window.location.href = `mailto:${encodeURIComponent(to)}?subject=${subject}&body=${body}`;\n        close();\n      });\n    }\n\n    // ---- Add to cart ----------------------------------------------------------\n\n    initAddToCart() {\n      this.section.querySelectorAll('.pdp-atc').forEach((btn) => {\n        btn.addEventListener('click', () => this.addToCart(btn));\n      });\n    }\n\n    clearAddToCartError(button) {\n      button.parentElement?.querySelector('.eko-pdp-v2-atc-error')?.remove();\n    }\n\n    showAddToCartError(button, message) {\n      this.clearAddToCartError(button);\n      const el = document.createElement('span');\n      // .text-red is an existing shared utility (assets/custom.css) - no new CSS needed.\n      el.className = 'eko-pdp-v2-atc-error text-red';\n      el.textContent = message || 'Could not add to cart. Please try again.';\n      button.insertAdjacentElement('afterend', el);\n    }\n\n    async addToCart(button) {\n      const mainAddon = this.addonEls.find((el) => el.dataset.isMainVariant === 'true');\n      const mainSel = mainAddon ? this.selections.get(mainAddon) : null;\n      if (!mainSel || !mainSel.variantId) return;\n      // A second click (or the sticky bar's button) while a request is in flight would add everything twice.\n      if (this.addingToCart) return;\n\n      const accessories = [];\n      const autoAdds = [];\n      const mainProperties = {};\n      let engravingText = null;\n\n      // CORE 300: engraving rides on the diaphragm set (the add-on that unlocks it), so its\n      // \"Engraving Text\" property goes on THAT line item and the cart shows the engraving note under\n      // it. Everything else (CORE 500 etc.) keeps the property on the main device line.\n      const gateAddon = this.engravingUnlockAddon;\n      let gateEntry = null;\n\n      this.selections.forEach((sel, addonEl) => {\n        if (addonEl === mainAddon) return;\n        if (sel.isEngraving) engravingText = sel.engravingText;\n        const entry = { id: Number(sel.variantId), quantity: 1 };\n        // Eko+ / Vet+ trials are Recharge subscriptions: without the plan the cart gets the bare variant.\n        if (sel.sellingPlanId) entry.selling_plan = Number(sel.sellingPlanId);\n        if (addonEl === gateAddon) gateEntry = entry;\n        (sel.isAutoAdd ? autoAdds : accessories).push(entry);\n      });\n\n      if (engravingText) {\n        if (gateEntry) gateEntry.properties = { 'Engraving Text': engravingText };\n        else mainProperties['Engraving Text'] = engravingText;\n      }\n\n      // The cart lists the most recently added line first, so add in reverse of the order\n      // we want to see: auto-added items (Eko+ / Vet+ trial) first so they end up at the\n      // bottom, then the accessories (reversed, so they read in on-page order), and the\n      // main device last so it sits on top.\n      const items = [\n        ...autoAdds,\n        ...accessories.reverse(),\n        {\n          id: Number(mainSel.variantId),\n          quantity: 1,\n          properties: Object.keys(mainProperties).length ? mainProperties : undefined,\n        },\n      ];\n\n      // Matches the live PDP's own add-to-cart (custom-product.js): no drawer/notification,\n      // just a straight redirect to the cart page on success \u2014 the clearest possible\n      // confirmation that something happened, and simpler than wiring up Dawn's cart-update\n      // pub/sub + a section re-render for a UI this theme doesn't otherwise use here.\n      this.addingToCart = true;\n      button.setAttribute('aria-busy', 'true');\n      this.clearAddToCartError(button);\n      try {\n        const res = await fetch(`${window.Shopify?.routes?.root || '/'}cart/add.js`, {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify({ items }),\n        });\n        const data = await res.json();\n        if (!res.ok) throw new Error(data.description || data.message || `cart/add.js failed: ${res.status}`);\n        window.location.href = `${window.Shopify?.routes?.root || '/'}cart`;\n      } catch (err) {\n        console.error('[eko-product-v2] add to cart failed', err);\n        this.showAddToCartError(button, err.message);\n        button.removeAttribute('aria-busy');\n        this.addingToCart = false;\n      }\n    }\n  }\n\n  if (!customElements.get('eko-product-v2')) customElements.define('eko-product-v2', EkoProductV2);\n})();\n"],
  "mappings": "CAsBC,IAAM,CACL,aAMA,SAAS,YAAY,MAAO,SAAU,CACpC,MAAM,QAAU,OAAO,KAAK,GAAK,GAAK,IAChC,SAAY,OAAO,SAAW,OAAO,QAAQ,UAAY,OAAO,QAAQ,SAAS,QAAW,MAClG,GAAI,CACF,OAAO,IAAI,KAAK,aAAa,SAAS,gBAAgB,MAAQ,QAAS,CACrE,MAAO,WACP,SACA,gBAAiB,SAAW,SAAW,eACvC,sBAAuB,OAAS,IAAM,EAAI,EAAI,CAChD,CAAC,EAAE,OAAO,MAAM,CAClB,MAAY,CACV,MAAO,IAAI,OAAO,QAAQ,CAAC,CAAC,EAC9B,CACF,CAUA,MAAM,cAAgB,CACpB,SAAS,QAAS,CAChB,MAAM,KAAO,SAAW,IAAI,YAAY,EAClC,YAAc,IAAI,MAAM,2BAA2B,EACnD,SAAW,IAAI,QAAQ,QAAQ,EACrC,IAAI,MAAQ,KACZ,GAAI,WAAa,GAAI,CACnB,IAAI,KAAO,IAAI,MAAM,SAAW,CAAe,EAAE,KAAK,EACtD,MAAM,OAAS,KAAK,QAAQ,cAAc,EAC1C,OAAS,SAAW,GAAK,KAAK,MAAM,EAAG,MAAM,EAAI,MAAM,KAAK,EAE5D,MAAQ,MAAM,MAAM,MAAM,EAAE,CAAC,EAAE,KAAK,CACtC,CACA,MAAO,CACL,MACA,UAAW,YAAc,YAAY,CAAC,EAAE,KAAK,EAAI,KACjD,UAAW,WAAa,EAC1B,CACF,EAEA,iBAAiB,OAAQ,CACvB,MAAO,CAAC,CAAC,OAAO,WAAa,OAAO,YAAc,MACpD,EAEA,aAAa,OAAQ,UAAW,CAE9B,MADI,CAAC,WACD,OAAO,UAAkB,GACtB,OAAO,QAAU,UAAU,YAAY,CAChD,EAGA,QAAQ,WAAY,UAAW,CAC7B,MAAM,OAAS,CAAC,EAChB,IAAI,gBAAkB,KACtB,kBAAW,QAAS,IAAO,CACzB,MAAM,IAAM,GAAG,cAAc,KAAK,EAC5B,IAAM,IAAM,IAAI,aAAa,KAAK,EAAI,GAAG,QAAQ,SACjD,OAAS,KAAK,SAAS,GAAG,EAC5B,KAAK,iBAAiB,MAAM,GAC5B,KAAK,aAAa,OAAQ,SAAS,IACrC,OAAO,KAAK,EAAE,EACV,OAAO,YAAc,SAAQ,gBAAkB,IAEvD,CAAC,EACI,kBAAiB,gBAAkB,OAAO,CAAC,GAAK,MAC9C,CAAE,OAAQ,eAAgB,CACnC,EAiBA,sBAAsB,WAAY,UAAW,WAAY,CACvD,MAAM,IAAO,YAAc,YAAY,WAAa,IAAI,YAAY,CAAC,GAAM,CAAC,EACtE,QAAU,IAAI,IACpB,WAAW,QAAS,IAAO,CACzB,MAAM,OAAS,GAAG,QAAQ,SAAW,IAAI,MAAM,SAAS,EACpD,OAAO,QAAQ,IAAI,MAAM,CAAC,EAAG,EAAE,CACrC,CAAC,EACD,MAAM,OAAS,CAAC,EAChB,IAAI,QAAS,IAAO,CAClB,MAAM,GAAK,QAAQ,IAAI,OAAO,EAAE,CAAC,EAC7B,IAAI,OAAO,KAAK,EAAE,CACxB,CAAC,EACD,MAAM,gBAAkB,OAAO,CAAC,GAAK,KACrC,MAAO,CAAE,OAAQ,eAAgB,CACnC,EAGA,oBAAoB,eAAgB,UAAW,UAAW,CACxD,UAAW,OAAO,eAAgB,CAChC,MAAM,OAAS,KAAK,SAAS,IAAI,aAAa,KAAK,CAAC,EACpD,GAAI,OAAO,YAAc,WAAa,OAAO,SAAW,WAAa,IAAI,YAAY,EACnF,OAAO,IAAI,YAAc,IAAI,GAEjC,CACA,OAAO,IACT,CACF,EAEA,MAAM,iBAAkB,CACtB,YAAY,KAAM,oBAAqB,YAAa,CAClD,KAAK,KAAO,KAGZ,KAAK,YAAc,YAInB,KAAK,oBAAsB,oBAC3B,KAAK,OAAS,IAAI,GACpB,CAEA,IAAI,OAAQ,IAAK,KAAM,CAChB,SACA,IAGH,KAAK,OAAO,IAAI,OAAQ,CAAE,IAAK,IAAK,CAAC,EAFrC,KAAK,OAAO,OAAO,MAAM,EAI3B,KAAK,OAAO,EACd,CAEA,MAAM,OAAQ,CACZ,KAAK,OAAO,OAAO,MAAM,EACzB,KAAK,OAAO,CACd,CAEA,QAAS,CACP,MAAM,WAAa,KAAK,KAAK,cAAc,gCAAgC,EAErE,YADW,KAAK,YAAc,KAAK,YAAY,EAAI,OAC1B,WAC/B,GAAI,CAAC,WAAY,OACjB,MAAM,QACJ,WAAW,cAAc,yBAAyB,GAClD,WAAW,cAAc,iBAAiB,GAC1C,WAQF,GAJA,KAAK,KAAK,iBAAiB,0CAA0C,EAAE,QAAS,IAAO,GAAG,OAAO,CAAC,EAI9F,WAAW,aAAa,yBAAyB,EAAG,CACtD,KAAK,gBAAgB,EACrB,MACF,CAEA,KAAK,OAAO,QAAQ,CAAC,CAAE,GAAI,EAAG,SAAW,CACvC,GAAI,CAAC,IAAK,OACV,MAAM,IAAM,SAAS,cAAc,KAAK,EACxC,IAAI,UAAY,0CAA0C,MAAM,GAChE,MAAM,IAAM,SAAS,cAAc,KAAK,EACxC,IAAI,IAAM,IACV,IAAI,IAAM,GACV,IAAI,QAAU,QACd,IAAI,YAAY,GAAG,EACnB,QAAQ,YAAY,GAAG,CACzB,CAAC,EAED,KAAK,gBAAgB,CACvB,CAEA,iBAAkB,EAIS,KAAK,oBAAsB,KAAK,oBAAoB,EAAI,CAAC,GACjE,QAAS,IAAO,CAC1B,KACL,GAAG,iBAAiB,qBAAqB,EAAE,QAAS,KAAQ,IAAI,OAAO,CAAC,EACxE,KAAK,OAAO,QAAQ,CAAC,CAAE,GAAI,EAAG,SAAW,CACvC,GAAI,CAAC,IAAK,OACV,MAAM,IAAM,SAAS,cAAc,KAAK,EACxC,IAAI,UAAY,0CAA0C,MAAM,GAChE,MAAM,IAAM,SAAS,cAAc,KAAK,EACxC,IAAI,IAAM,IACV,IAAI,IAAM,GACV,IAAI,YAAY,GAAG,EACnB,GAAG,YAAY,GAAG,CACpB,CAAC,EACH,CAAC,CACH,CACF,CAEA,MAAM,qBAAqB,WAAY,CACrC,mBAAoB,CAClB,KAAK,QAAU,KAAK,QAAQ,aAAa,EACzC,KAAK,QAAU,KAAK,QAAQ,cAAc,sBAAsB,EAChE,KAAK,UAAY,KAAK,QAAQ,cAAc,sBAAsB,EAIlE,KAAK,WAAa,IAAM,MAAM,KAAK,KAAK,UAAU,iBAAiB,qDAAqD,CAAC,EAEzH,KAAK,SAAW,MAAM,KAAK,KAAK,iBAAiB,kBAAkB,CAAC,EACpE,KAAK,QAAU,IAAI,kBACjB,KAAK,QACL,IAAM,CAAC,KAAK,YAAY,cAAc,+BAA+B,EAAG,KAAK,iBAAiB,EAAE,OAAO,OAAO,EAC9G,IACE,KAAK,YACD,KAAK,UAAU,cAAc,uCAAuC,KAAK,WAAW,mCAAmC,EACvH,IACR,EACA,KAAK,aAAe,KACpB,KAAK,WAAa,IAAI,IAEtB,KAAK,aAAe,KAAK,cAAc,gCAAgC,EACvE,KAAK,OAAS,KAAK,aAAe,KAAK,MAAM,KAAK,aAAa,WAAW,EAAI,CAAC,EAM/E,MAAM,wBAA0B,KAAK,cAAc,sCAAsC,EACzF,KAAK,kBAAoB,wBAA0B,KAAK,MAAM,wBAAwB,WAAW,EAAI,KAErG,KAAK,aAAe,KAAK,iBAAiB,EAE1C,KAAK,eAAe,EACpB,KAAK,oBAAoB,EAWzB,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,qBAAqB,EAC1B,KAAK,iBAAiB,EACtB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAgB,GACrB,OAAO,iBAAiB,SAAU,IAAM,CACtC,KAAK,cAAc,EACnB,KAAK,iBAAiB,CACxB,CAAC,EAQG,OAAO,eAAmB,KACH,IAAI,eAAe,IAAM,CAChD,KAAK,cAAc,EACnB,KAAK,iBAAiB,CACxB,CAAC,EACgB,QAAQ,KAAK,QAAQ,cAAc,mBAAmB,CAAC,EAE1E,KAAK,cAAc,CACrB,CAEA,kBAAmB,CACjB,MAAM,OAAS,KAAK,cAAc,2BAA2B,IAAI,OAAO,KAAK,OAAO,WAAa,EAAE,CAAC,EAAE,EACtG,GAAI,CAAC,OAAQ,MAAO,CAAC,EACrB,GAAI,CACF,OAAO,KAAK,MAAM,OAAO,WAAW,CACtC,MAAY,CACV,MAAO,CAAC,CACV,CACF,CAIA,eAAgB,CAGd,KAAK,iBAAiB,kBAAkB,EAAE,QAAS,MAAS,CAC1D,MAAM,MAAQ,KAAK,cAAc,uCAAuC,EAClE,QAAU,KAAK,cAAc,sBAAsB,EACnD,QAAU,KAAK,cAAc,sBAAsB,EACzD,GAAI,CAAC,OAAU,CAAC,SAAW,CAAC,QAAU,OAEtC,MAAM,eAAiB,IAAM,CAC3B,MAAM,OAAS,MAAM,cAAc,2BAA2B,EAE9D,OADa,OAAS,OAAO,sBAAsB,EAAE,MAAQ,EAAI,MAAM,YAAc,IACvE,CAChB,EAEM,aAAe,IAAM,CACzB,MAAM,UAAY,MAAM,YAAc,MAAM,YACxC,UAAS,QAAQ,SAAW,MAAM,YAAc,GAChD,UAAS,QAAQ,SAAW,MAAM,YAAc,UAAY,EAClE,EAEA,SAAS,iBAAiB,QAAS,IAAM,CACvC,MAAM,SAAS,CAAE,KAAM,CAAC,eAAe,EAAG,SAAU,QAAS,CAAC,CAChE,CAAC,EACD,SAAS,iBAAiB,QAAS,IAAM,CACvC,MAAM,SAAS,CAAE,KAAM,eAAe,EAAG,SAAU,QAAS,CAAC,CAC/D,CAAC,EAGD,MAAM,iBAAiB,SAAU,YAAY,EAC7C,OAAO,iBAAiB,SAAU,YAAY,EAC9C,aAAa,CACf,CAAC,CACH,CAIA,YAAa,CACX,KAAK,SAAS,QAAS,SAAY,CACjC,MAAM,OAAS,MAAM,KAAK,QAAQ,iBAAiB,0BAA0B,CAAC,EAC9E,OAAO,QAAS,OAAU,CACxB,MAAM,iBAAiB,SAAU,IAAM,KAAK,cAAc,QAAS,KAAK,CAAC,CAC3E,CAAC,EAED,MAAM,OAAS,QAAQ,QAAQ,gBAAkB,OAC3C,QAAU,OAAO,KAAM,GAAM,EAAE,OAAO,EACxC,SAAS,KAAK,cAAc,QAAS,QAAS,CAAE,QAAS,EAAK,CAAC,EAC/D,QAAU,CAAC,SAAW,OAAO,CAAC,IAIhC,OAAO,CAAC,EAAE,QAAU,GACpB,KAAK,cAAc,QAAS,OAAO,CAAC,EAAG,CAAE,QAAS,EAAK,CAAC,GAM1D,MAAM,YAAc,QAAQ,cAAc,iCAAiC,EACvE,cACF,QAAQ,UAAU,IAAI,YAAY,EAClC,QAAQ,iBAAiB,QAAU,GAAM,CACvC,GAAI,EAAE,SAAW,aAAe,EAAE,OAAO,QAAQ,6BAA6B,EAAG,OAEjF,MAAM,SAAW,EAAE,OAAO,QAAQ,OAAO,EACrC,UAAY,SAAS,SAAS,WAAW,GAC7C,YAAY,MAAM,CACpB,CAAC,GAIC,QAAQ,QAAQ,cAAgB,SAClC,KAAK,oBAAoB,OAAO,EAC5B,KAAK,sBAAsB,KAAK,kBAAkB,OAAO,EAEjE,CAAC,EACD,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,CAC1B,CAEA,cAAc,QAAS,MAAO,KAAO,CAAC,EAAG,CACvC,MAAM,OAAS,QAAQ,QAAQ,gBAAkB,OAC3C,cAAgB,QAAQ,QAAQ,gBAAkB,OAClD,cAAgB,QAAQ,QAAQ,cAChC,MAAQ,MAAM,MAEd,OAAS,MAAM,OAAS,WAAa,CAAC,MAAM,QAAU,QAAU,OAGlE,OACF,KAAK,WAAW,OAAO,OAAO,EAE9B,KAAK,WAAW,IAAI,QAAS,CAC3B,UAAW,MAAM,QAAQ,UACzB,MAAO,OAAO,MAAM,QAAQ,KAAK,GAAK,EACtC,YAAa,MAAM,QAAQ,oBAAsB,OAAO,MAAM,QAAQ,mBAAmB,EAAI,KAC7F,iBAAkB,QAAQ,QAAQ,mBAAqB,OACvD,YAAa,QAAQ,QAAQ,cAAgB,OAC7C,gBAAiB,QAAQ,QAAQ,kBAAoB,OACrD,UAAW,QAAQ,QAAQ,UAAY,OACvC,cAAe,MAAM,QAAQ,aAAe,KAC5C,cAAe,KAAK,iBAAiB,OAAO,CAC9C,CAAC,EAIC,QAAQ,QAAQ,qBAAuB,QACzC,KAAK,qBAAqB,QAAS,MAAO,MAAM,EAMlD,MAAM,QAAU,QAAQ,cAAc,6BAA6B,EAMnE,GALI,SAAS,QAAQ,UAAU,OAAO,cAAe,CAAC,MAAM,EAExD,MAAM,UAAU,SAAS,gCAAgC,GAAG,QAAQ,UAAU,OAAO,QAAS,CAAC,MAAM,EAGrG,QAAU,CAAC,SACb,KAAK,aAAe,MAAM,QAAQ,WAAa,MAAM,MACrD,KAAK,qBAAqB,KAAK,YAAY,EAC3C,KAAK,kBAAkB,EAEvB,KAAK,QAAQ,OAAO,EAShB,CAAC,KAAK,SAAW,MAAM,QAAQ,WAAW,CAC5C,MAAM,IAAM,IAAI,IAAI,OAAO,SAAS,IAAI,EACxC,IAAI,aAAa,IAAI,UAAW,MAAM,QAAQ,SAAS,EACvD,OAAO,QAAQ,aAAa,CAAC,EAAG,GAAI,IAAI,SAAS,CAAC,EAClD,KAAK,eAAe,MAAM,QAAQ,SAAS,CAC7C,CAGF,GAAI,cAAe,CAKjB,GADK,KAAK,SAAS,KAAK,cAAc,EAClC,OACF,KAAK,QAAQ,MAAM,aAAa,MAC3B,CACL,MAAM,IACJ,MAAM,QAAQ,YACd,cAAc,oBACZ,MAAM,KAAK,KAAK,UAAU,iBAAiB,KAAK,CAAC,EACjD,cACA,KAAK,YACP,EACI,YAAc,MAAM,QAAQ,OAAO,GAAG,QAAQ,YACpD,KAAK,QAAQ,IAAI,cAAe,IAAK,WAAW,CAClD,CACA,KAAK,uBAAuB,QAAS,MAAO,OAAQ,aAAa,CACnE,CAGI,CAAC,QAAU,QAAQ,QAAQ,cAAgB,QAAQ,KAAK,mBAAmB,EAE3E,KAAK,eAAe,KAAK,iBAAiB,EAEzC,KAAK,UACR,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,OAAS,KAAO,CAAE,QAAS,MAAO,CAAC,EAEhE,CAQA,mBAAoB,CAElB,MAAM,UADY,KAAK,SAAS,KAAM,IAAO,GAAG,QAAQ,gBAAkB,MAAM,GACnD,cAAc,kCAAkC,EAC7E,GAAI,CAAC,UAAW,OAChB,MAAM,IAAM,UAAU,QAAQ,WAAa,GAE3C,KAAK,SAAS,QAAS,SAAY,CACjC,GAAI,QAAQ,QAAQ,gBAAkB,OAAQ,OAC9C,IAAI,IACJ,GAAI,CACF,IAAM,KAAK,MAAM,QAAQ,cAAc,yBAAyB,GAAG,aAAe,IAAI,CACxF,MAAY,CACV,MACF,CACA,MAAM,MAAQ,IAAI,GAAG,GAAK,OAAO,OAAO,GAAG,EAAE,CAAC,EACxC,OAAS,QAAQ,cAAc,iCAAiC,EACtE,GAAI,CAAC,OAAS,CAAC,OAAQ,OAEvB,OAAO,QAAQ,UAAY,MAAM,GACjC,OAAO,QAAQ,MAAQ,MAAM,MAC7B,OAAO,QAAQ,oBAAsB,MAAM,QAAU,KAAO,MAAM,OAAS,GAE3E,MAAM,IAAM,QAAQ,cAAc,6BAA6B,EAEzD,QAAU,UAAU,QAAQ,aAAe,MAAM,MACnD,KAAO,UACT,IAAI,gBAAgB,QAAQ,EAC5B,IAAI,IAAM,SAGZ,MAAM,QAAU,QAAQ,cAAc,qBAAqB,EAC3D,GAAI,QAAS,CACX,MAAM,KAAO,QAAQ,cAAc,+BAA+B,EAAI,sDAAwD,GAC9H,QAAQ,UACN,MAAM,QAAU,MAAQ,MAAM,OAAS,MAAM,MACzC,GAAG,IAAI,0CAA0C,YAAY,MAAM,KAAK,CAAC,QAAQ,YAAY,MAAM,MAAM,CAAC,GAC1G,GAAG,IAAI,GAAG,YAAY,MAAM,KAAK,CAAC,EAC1C,CAGI,OAAO,SAAS,KAAK,cAAc,QAAS,OAAQ,CAAE,QAAS,EAAK,CAAC,CAC3E,CAAC,CACH,CAIA,IAAI,sBAAuB,CACzB,OAAO,KAAK,SAAS,KAAM,IAAO,GAAG,QAAQ,mBAAqB,MAAM,GAAK,IAC/E,CAEA,oBAAqB,CACnB,MAAM,YAAc,KAAK,qBACzB,GAAI,CAAC,YAAa,OAClB,MAAM,SAAW,KAAK,SAAS,KAAM,IAAO,GAAG,QAAQ,cAAgB,MAAM,EAC7E,GAAI,CAAC,SAAU,OAEf,MAAM,SAAW,KAAK,WAAW,IAAI,WAAW,EAC1C,YAAc,SAAS,QAAQ,kBAAoB,QACzD,SAAS,QAAQ,gBAAkB,SAAW,QAAU,OAExD,MAAM,aAAe,SAAS,cAAc,kDAAkD,EAC9F,GAAI,aAAc,CAEZ,aAAa,QAAQ,aAAe,SAAW,aAAa,QAAQ,WAAa,aAAa,QAAQ,qBAAuB,IACjI,aAAa,QAAQ,oBAAsB,SAAW,IAAM,aAAa,QAAQ,WACjF,MAAM,IAAM,KAAK,WAAW,IAAI,QAAQ,EACpC,MAAK,IAAI,YAAc,SAAW,EAAI,aAAa,QAAQ,WAAa,OAAO,aAAa,QAAQ,UAAU,EAAI,KACxH,CAEA,MAAM,KAAO,SAAS,cAAc,4BAA4B,EAYhE,GAXI,OACE,UACF,KAAK,OAAS,GACd,OAAO,KAAK,QAAQ,SACV,KAAK,QAAQ,UAEvB,KAAK,OAAS,KAKd,CAAC,WAAa,aAAe,KAAK,WAAW,IAAI,QAAQ,GAAI,CAC/D,MAAM,aAAe,KAAK,WAAW,IAAI,QAAQ,EAC3C,MAAQ,SAAS,eAAe,iBAAiB,EACjD,KAAO,OAAS,CAAC,MAAM,UAAU,SAAS,QAAQ,EACpD,cAAc,SAAS,cAAc,qCAAqC,GAAG,MAAM,EACnF,MAAM,SAAS,cAAc,mBAAmB,GAAG,MAAM,EACzD,cAAgB,OAClB,KAAK,YAAc,+DACnB,KAAK,QAAQ,QAAU,OACvB,KAAK,OAAS,GAElB,CAEA,KAAK,qBAAqB,SAAU,QAAQ,EAC5C,MAAM,cAAgB,SAAS,cAAc,2BAA2B,EACpE,gBAAe,cAAc,OAAS,CAAC,SAC7C,CAIA,qBAAqB,SAAU,SAAU,CACvC,MAAM,GAAK,SAAS,cAAc,gCAAgC,EAC7D,KACD,GAAG,QAAQ,OAAS,SAAW,GAAG,QAAQ,KAAO,GAAG,WACxD,GAAG,UAAY,SAAW,WAAa,GAAG,QAAQ,KACpD,CAKA,kBAAkB,SAAU,CAC1B,SAAS,QAAQ,gBAAkB,KAAK,sBAAwB,CAAC,KAAK,WAAW,IAAI,KAAK,oBAAoB,EAAI,OAAS,QAC3H,SAAS,iBACP,QACC,GAAM,CAGL,MAAM,KAAO,KAAK,qBACZ,UAAY,MAAM,cAAc,2DAA2D,EACjG,GAAI,CAAC,MAAQ,WAAW,SAAW,KAAK,WAAW,IAAI,IAAI,EAAG,CAC5D,SAAS,QAAQ,gBAAkB,QACnC,MACF,CACK,EAAE,OAAO,QAAQ,6EAA6E,IACnG,EAAE,eAAe,EACjB,EAAE,yBAAyB,EAC3B,KAAK,yBAAyB,QAAQ,EACxC,EACA,EACF,CACF,CAEA,yBAAyB,SAAU,CACjC,MAAM,YAAc,KAAK,qBACzB,GAAI,CAAC,YAAa,OAClB,MAAM,OAAS,SAAS,cAAc,yBAAyB,EAC/D,GAAI,CAAC,QAAU,OAAO,OAAO,MAAS,WAAY,CAChD,KAAK,kCAAkC,QAAQ,EAC/C,MACF,CACA,KAAK,sBAAwB,SAE7B,MAAM,OAAS,YAAY,cAAc,iCAAiC,EACpE,MAAQ,OAAO,QAAQ,QAAQ,KAAK,GAAK,EACzC,OAAS,QAAQ,QAAQ,oBAAsB,OAAO,OAAO,QAAQ,mBAAmB,EAAI,KAC5F,MAAQ,QAAU,MAAQ,OAAS,MAAQ,OAAS,MAGpD,IAAM,OAAO,cAAc,qBAAqB,EAChD,IAAM,YAAY,cAAc,6BAA6B,GAAG,YAAc,YAAY,cAAc,6BAA6B,GAAG,IAC1I,KAAO,MAAK,IAAI,IAAM,KAC1B,KAAK,QAAQ,kCAAkC,GAAG,gBAAgB,SAAU,CAAC,GAAG,EAEhF,OAAO,cAAc,qBAAqB,EAAE,YAAc,YAAY,QAAQ,aAAe,iDAC7F,OAAO,cAAc,oBAAoB,EAAE,YACzC,YAAY,QAAQ,YAAc,gGACpC,OAAO,cAAc,uBAAuB,EAAE,YAAc,GAAG,YAAY,QAAQ,eAAiB,4BAA4B,KAAK,YAAY,KAAK,CAAC,GACvJ,OAAO,cAAc,uBAAuB,EAAE,YAAc,YAAY,QAAQ,cAAgB,mBAE3F,OAAO,QAAQ,QAClB,OAAO,QAAQ,MAAQ,OACvB,OAAO,cAAc,uBAAuB,EAAE,iBAAiB,QAAS,IAAM,OAAO,MAAM,CAAC,EAC5F,OAAO,cAAc,uBAAuB,EAAE,iBAAiB,QAAS,IAAM,CAE5E,MAAM,MADY,KAAK,sBACE,cAAc,iCAAiC,EACxE,GAAI,CACE,OAAS,CAAC,MAAM,UAClB,MAAM,QAAU,GAChB,MAAM,cAAc,IAAI,MAAM,SAAU,CAAE,QAAS,EAAK,CAAC,CAAC,GAExD,KAAK,wBAAuB,KAAK,sBAAsB,QAAQ,gBAAkB,QACvF,OAAS,IAAK,CACZ,QAAQ,MAAM,8EAA+E,GAAG,CAClG,CACA,OAAO,MAAM,EAEb,WAAW,IAAM,CACI,KAAK,uBACZ,cAAc,gBAAgB,GAAG,MAAM,EACnD,WAAW,IAAM,CACf,MAAM,MAAQ,SAAS,eAAe,iBAAiB,EACnD,OAAS,MAAM,UAAU,SAAS,QAAQ,GAAG,OAAO,qBAAqB,YAAY,CAC3F,EAAG,EAAE,CACP,EAAG,GAAG,CACR,CAAC,GAEH,OAAO,KAAK,SAAS,cAAc,gBAAgB,GAAK,QAAQ,CAClE,CAEA,kCAAkC,SAAU,CAC1C,MAAM,YAAc,KAAK,qBACzB,GAAI,CAAC,YAAa,OAClB,GAAI,CAAC,KAAK,aAAc,CACtB,MAAMA,GAAI,SAAS,cAAc,QAAQ,EACzCA,GAAE,UAAY,oBACdA,GAAE,UACA,qQAMF,KAAK,YAAYA,EAAC,EAClBA,GAAE,cAAc,4BAA4B,EAAE,iBAAiB,QAAS,IAAMA,GAAE,MAAM,CAAC,EAEvFA,GAAE,iBAAiB,QAAU,GAAM,CAC7B,EAAE,SAAWA,IAAGA,GAAE,MAAM,CAC9B,CAAC,EACDA,GAAE,cAAc,6BAA6B,EAAE,iBAAiB,QAAS,IAAM,CAC7EA,GAAE,MAAM,EACR,MAAMC,UAAW,KAAK,sBACtB,GAAI,CACF,MAAM,OAAS,KAAK,sBAAsB,cAAc,2DAA2D,EAC/G,QAAU,CAAC,OAAO,UACpB,OAAO,QAAU,GACjB,OAAO,cAAc,IAAI,MAAM,SAAU,CAAE,QAAS,EAAK,CAAC,CAAC,GAGzDA,YAAUA,UAAS,QAAQ,gBAAkB,QACnD,OAAS,IAAK,CACZ,QAAQ,MAAM,oEAAqE,GAAG,CACxF,CAGA,WAAW,IAAM,CACfA,WAAU,cAAc,gBAAgB,GAAG,MAAM,EACjD,WAAW,IAAM,CACf,MAAM,MAAQ,SAAS,eAAe,iBAAiB,EACnD,OAAS,MAAM,UAAU,SAAS,QAAQ,GAAG,OAAO,qBAAqB,YAAY,CAC3F,EAAG,EAAE,CACP,EAAG,CAAC,CACN,CAAC,EACD,KAAK,aAAeD,EACtB,CACA,MAAM,EAAI,KAAK,aACf,KAAK,sBAAwB,SAC7B,EAAE,cAAc,2BAA2B,EAAE,YAAc,YAAY,QAAQ,aAAe,mCAC9F,EAAE,cAAc,0BAA0B,EAAE,YAC1C,YAAY,QAAQ,YAAc,gGACpC,EAAE,cAAc,6BAA6B,EAAE,YAAc,YAAY,QAAQ,eAAiB,uBAClG,EAAE,cAAc,4BAA4B,EAAE,YAAc,YAAY,QAAQ,cAAgB,UAC5F,OAAO,EAAE,WAAc,WAAY,EAAE,UAAU,EAC9C,EAAE,aAAa,OAAQ,EAAE,CAChC,CAEA,qBAAqB,QAAS,MAAO,OAAQ,CAC3C,MAAM,UAAY,QAAQ,cAAc,4BAA4B,EACpE,GAAI,CAAC,UAAW,OAChB,MAAM,YAAc,MAAM,QAAQ,gBAAkB,GAQpD,GAPe,QAAQ,QAAQ,gBAAkB,OAOrC,CACV,GAAI,OAAQ,CACV,UAAU,MAAM,QAAU,OAC1B,UAAU,UAAY,GACtB,MACF,CACA,UAAU,MAAM,QAAU,GAC1B,UAAU,UAAY,GAEtB,MAAM,QAAU,SAAS,cAAc,KAAK,EAC5C,QAAQ,UAAY,oCAEpB,MAAM,KAAO,SAAS,cAAc,MAAM,EAK1C,GAJA,KAAK,UAAY,iCACjB,KAAK,YAAc,MAAM,QAAQ,cAAgB,MAAM,MACvD,QAAQ,YAAY,IAAI,EAEpB,MAAM,QAAQ,SAAW,OAAQ,CACnC,MAAM,MAAQ,SAAS,cAAc,MAAM,EAC3C,MAAM,UAAY,gCAClB,MAAM,YAAc,MACpB,QAAQ,YAAY,KAAK,CAC3B,CAIA,GAFA,UAAU,YAAY,OAAO,EAEzB,YAAa,CACf,MAAM,KAAO,SAAS,cAAc,KAAK,EACzC,KAAK,UAAY,4CACjB,KAAK,UAAY,YACjB,UAAU,YAAY,IAAI,CAC5B,CACA,MACF,CAEA,GAAI,QAAU,CAAC,YAAa,CAC1B,UAAU,MAAM,QAAU,OAC1B,UAAU,UAAY,GACtB,MACF,CACA,UAAU,MAAM,QAAU,GAC1B,UAAU,UAAY,WACxB,CAGA,eAAgB,CACd,GAAI,CAAC,KAAK,YAAa,OACvB,MAAM,OAAS,KAAK,UAAU,cAAc,gCAAgC,EACxE,QAAU,OAAO,QAAQ,UAAY,KAAK,aAC5C,KAAK,QAAQ,cAAc,eAAe,GAAG,eAAe,KAAK,YAAa,EAAK,CAEvF,CAEA,uBAAuB,QAAS,MAAO,OAAQ,cAAe,CAC5D,MAAM,UAAY,QAAQ,cAAc,4BAA4B,EACpE,GAAI,CAAC,UAAW,OAChB,MAAM,KAAO,CAAC,EAAG,EAAG,CAAC,EAClB,IAAK,IAAO,CACX,IAAK,MAAM,QAAQ,aAAa,CAAC,KAAK,EACtC,KAAM,MAAM,QAAQ,aAAa,CAAC,MAAM,EACxC,MAAO,MAAM,QAAQ,aAAa,CAAC,OAAO,CAC5C,EAAE,EACD,OAAQ,GAAM,EAAE,KAAO,EAAE,IAAI,EAIhC,GAAI,QAAU,KAAK,OAAS,EAAG,OAI/B,UAAU,MAAM,QAAU,GAO1B,MAAM,eAAiB,MAAM,QAAQ,mBAAqB,MAAM,QAAQ,OAAO,GAAG,cAAc,iCAAiC,GAAG,aAAa,KAAK,EACtJ,GAAI,eAAgB,CAClB,MAAM,QAAU,SAAS,cAAc,KAAK,EAC5C,QAAQ,UAAY,yCACpB,QAAQ,YAAc,eAGtB,UAAU,QAAQ,OAAO,CAC3B,CAEA,MAAM,KAAO,SAAS,cAAc,KAAK,EACzC,KAAK,UAAY,iCACjB,KAAK,QAAQ,CAAC,EAAG,MAAQ,CACvB,MAAM,IAAM,SAAS,cAAc,QAAQ,EAI3C,GAHA,IAAI,KAAO,SACX,IAAI,UAAY,iCAAmC,MAAQ,EAAI,aAAe,IAE1E,EAAE,MAAO,CACX,MAAM,IAAM,SAAS,cAAc,MAAM,EACzC,IAAI,UAAY,uCAChB,IAAI,MAAM,YAAY,eAAgB,EAAE,KAAK,EAC7C,IAAI,YAAY,GAAG,CACrB,CACA,MAAM,MAAQ,SAAS,cAAc,MAAM,EAC3C,MAAM,YAAc,EAAE,KACtB,IAAI,YAAY,KAAK,EACrB,IAAI,iBAAiB,QAAS,IAAM,CAClC,KAAK,iBAAiB,gCAAgC,EAAE,QAAS,GAAM,EAAE,UAAU,OAAO,WAAW,CAAC,EACtG,IAAI,UAAU,IAAI,WAAW,EAC7B,KAAK,QAAQ,IAAI,cAAe,EAAE,IAAK,EAAE,IAAI,EAC7C,KAAK,cAAc,CACrB,CAAC,EACD,KAAK,YAAY,GAAG,CACtB,CAAC,EACD,UAAU,YAAY,IAAI,CAC5B,CASA,eAAgB,CACd,KAAK,iBAAiB,6BAA6B,EAAE,QAAS,KAAQ,CACpE,MAAM,KAAO,IAAI,cAEjB,GAAI,CAAC,MAAQ,KAAK,UAAU,SAAS,2BAA2B,EAAG,OAGnE,MAAM,IACJ,KAAK,cAAc,gCAAgC,GACnD,KAAK,cAAc,gDAAgD,GACnE,KAAK,cAAc,yBAAyB,GAC5C,KAAK,cAAc,8BAA8B,GACjD,KAAK,cAAc,yBAAyB,GAC5C,KAAK,cAAc,4BAA4B,EAC3C,QAAU,IAAM,IAAI,sBAAsB,EAAI,KACpD,GAAI,CAAC,SAAW,CAAC,QAAQ,OAAQ,CAC/B,IAAI,MAAM,IAAM,GAChB,MACF,CACA,MAAM,SAAW,KAAK,sBAAsB,EACtC,OAAS,QAAQ,IAAM,QAAQ,OAAS,EAC9C,IAAI,MAAM,IAAM,GAAG,QAAU,SAAS,IAAM,KAAK,WAAa,IAAI,aAAe,CAAC,IACpF,CAAC,CACH,CAKA,gBAAiB,CACf,KAAK,iBAAiB,oBAAoB,EAAE,QAAS,MAAS,CAC5D,MAAM,OAAS,KAAK,cAAc,yBAAyB,EAC3D,GAAI,CAAC,OAAQ,OACb,MAAM,OAAS,IAAM,CAKnB,MAAM,MACJ,OAAO,kBAAoB,GAC3B,OAAO,YAAY,KAAK,IAAM,IAC9B,CAAC,CAAC,KAAK,cAAc,sBAAsB,EAC7C,KAAK,UAAU,OAAO,aAAc,CAAC,KAAK,EAI1C,KAAK,iBAAiB,sBAAsB,EAAE,QAAS,OAAU,CAC/D,MAAM,EAAI,MAAM,MAAM,iBAAiB,QAAQ,EAC3C,GAAK,MAAM,MAAM,oBAAoB,QAAQ,IAAM,aAAa,MAAM,MAAM,YAAY,SAAU,EAAG,WAAW,EAChH,MAAM,MAAM,oBAAoB,YAAY,IAAM,aAAa,MAAM,MAAM,YAAY,aAAc,IAAK,WAAW,CAC3H,CAAC,EACD,KAAK,cAAc,EACnB,KAAK,iBAAiB,CACxB,EACA,IAAI,iBAAiB,MAAM,EAAE,QAAQ,KAAM,CAAE,UAAW,GAAM,QAAS,GAAM,cAAe,GAAM,WAAY,GAAM,gBAAiB,CAAC,QAAS,OAAO,CAAE,CAAC,EAKzJ,MAAM,IAAM,KAAK,cAAc,6BAA6B,EACtD,WAAa,IAAM,CACvB,MAAM,IAAM,KAAK,cAAc,gCAAgC,EAC/D,GAAI,CAAC,KAAO,CAAC,IAAI,QAAS,MAAO,GACjC,MAAM,MAAQ,KAAK,cAAc,eAAe,EAChD,GAAI,CACF,MAAM,KAAO,OAAO,eAAiB,OAAO,cAAc,gBAAgB,KAAK,EAC/E,GAAI,MAAQ,KAAK,MAAQ,EAAG,MAAO,EACrC,MAAY,CAAC,CACb,GAAI,CACF,MAAM,IAAM,MAAM,cAAc,QAAQ,GAAG,gBAC3C,GAAI,KAAO,IAAI,cAAc,mBAAmB,EAAG,MAAO,EAC5D,MAAY,CAAC,CACb,MAAO,EACT,EAGM,eAAiB,IAAM,CAC3B,MAAM,MAAQ,KAAK,cAAc,eAAe,EAChD,GAAI,CAAC,MAAO,MAAO,GACnB,GAAI,CACF,MAAM,KAAO,OAAO,QAAQ,SAAS,SAAS,KAAK,GAAG,iBAAiB,EACvE,GAAI,MAAQ,KAAK,MAAQ,EAAG,OAAO,KAAK,KAC1C,MAAY,CAAC,CACb,OAAO,OAAO,MAAM,QAAQ,WAAW,GAAK,CAC9C,EACM,QAAU,IAAM,CACpB,MAAM,GAAK,WAAW,EAClB,KAAO,IAAI,UAAU,SAAS,aAAa,IAAM,KACnD,IAAI,UAAU,OAAO,cAAe,EAAE,EACtC,KAAK,iBAAiB,GAExB,MAAM,MAAQ,GAAK,eAAe,EAAI,EAClC,SAAW,KAAK,aAAe,KACjC,KAAK,YAAc,MACnB,KAAK,eAAe,EAExB,EAGA,IAAI,UAAY,KAChB,MAAM,OAAS,IAAM,CAAC,CAAC,KAAK,cAAc,gCAAgC,GAAG,QACvE,KAAO,IAAM,CACjB,QAAQ,EACH,OAAO,IACV,cAAc,SAAS,EACvB,UAAY,KAEhB,EACA,KAAK,iBAAiB,SAAU,IAAM,CACpC,QAAQ,EACJ,OAAO,GAAK,CAAC,YAAW,UAAY,YAAY,KAAM,GAAG,EAC/D,CAAC,EACD,OAAO,CACT,CAAC,CACH,CAEA,kBAAmB,CAEjB,GAAI,KAAK,QAAQ,aAAe,OAAQ,OAQxC,MAAM,QAAU,MAAM,KAAK,KAAK,iBAAiB,uDAAuD,CAAC,EAAE,OACxG,GAAM,EAAE,eAAe,EAAE,MAC5B,EACA,GAAI,QAAQ,OAAS,EAAG,OACxB,MAAM,KAAO,KAAK,QAAQ,cAAc,mBAAmB,EAC3D,GAAI,CAAC,KAAM,OACX,iBAAiB,IAAI,EAAE,WAAa,WAAa,KAAK,MAAM,SAAW,YAEvE,IAAI,KAAO,KAAK,cAAc,+BAA+B,EACxD,OAGH,KAAO,SAAS,cAAc,MAAM,EACpC,KAAK,UAAY,sBACjB,KAAK,QAAQ,IAAI,GAGnB,MAAM,SAAW,KAAK,sBAAsB,EACtC,MAAQ,QAAQ,CAAC,EAAE,sBAAsB,EACzC,KAAO,QAAQ,QAAQ,OAAS,CAAC,EAAE,sBAAsB,EACzD,IAAM,MAAM,IAAM,SAAS,IAAM,MAAM,OAAS,EAChD,OAAS,KAAK,IAAM,SAAS,IAAM,KAAK,OAAS,EACvD,KAAK,MAAM,IAAM,GAAG,GAAG,KACvB,KAAK,MAAM,OAAS,GAAG,OAAS,GAAG,KACnC,KAAK,MAAM,KAAO,GAAG,MAAM,KAAO,SAAS,KAAO,MAAM,MAAQ,CAAC,KAIjE,IAAI,QAAU,KAAK,IAAI,EAAG,QAAQ,OAAS,CAAC,EAC5C,QAAQ,QAAQ,CAAC,EAAG,IAAM,CACpB,EAAE,UAAU,SAAS,4BAA4B,GAAK,EAAE,UAAU,SAAS,aAAa,IAAG,QAAU,EAC3G,CAAC,EACD,IAAI,SAAW,KAAK,cAAc,wCAAwC,EACrE,WACH,SAAW,SAAS,cAAc,MAAM,EACxC,SAAS,UAAY,+BACrB,KAAK,QAAQ,QAAQ,GAEvB,MAAM,KAAO,QAAQ,OAAO,EAAE,sBAAsB,EACpD,SAAS,MAAM,IAAM,GAAG,GAAG,KAC3B,SAAS,MAAM,OAAS,GAAG,KAAK,IAAI,EAAG,KAAK,IAAM,SAAS,IAAM,KAAK,OAAS,EAAI,GAAG,CAAC,KACvF,SAAS,MAAM,KAAO,KAAK,MAAM,IACnC,CAUA,mBAAmB,GAAI,CACrB,MAAM,IAAM,GAAG,UAAY,MAAQ,GAAK,GAAG,cAAc,KAAK,EACzD,MACD,IAAI,QAAQ,cACd,IAAI,IAAM,IAAI,QAAQ,YACtB,OAAO,IAAI,QAAQ,aAEjB,IAAI,QAAQ,iBACd,IAAI,OAAS,IAAI,QAAQ,eACzB,OAAO,IAAI,QAAQ,gBAEvB,CAEA,qBAAqB,UAAW,CAC9B,MAAM,MAAQ,KAAK,WAAW,EAIxB,WAAa,KAAK,mBAAqB,KAAK,mBAAmB,WAAa,IAAI,YAAY,CAAC,EAC7F,WAAa,CAAC,EAAE,YAAc,WAAW,QACzC,CAAE,OAAQ,eAAgB,EAAI,WAChC,cAAc,sBAAsB,MAAO,UAAW,KAAK,iBAAiB,EAC5E,cAAc,QAAQ,MAAO,SAAS,EACpC,QAAU,IAAI,IAAI,MAAM,EAoB9B,GAnBA,MAAM,QAAS,IAAO,CACpB,GAAG,MAAM,QAAU,QAAQ,IAAI,EAAE,EAAI,GAAK,OAC1C,GAAG,UAAU,OAAO,WAAW,EAC3B,QAAQ,IAAI,EAAE,GAAG,KAAK,mBAAmB,EAAE,CACjD,CAAC,EACG,iBAAiB,gBAAgB,UAAU,IAAI,WAAW,EAC9D,KAAK,YAAc,gBAAkB,gBAAgB,QAAQ,QAAU,KAanE,YAAc,OAAO,OAAQ,CAC/B,MAAM,YAAc,OAAO,CAAC,EAAE,cAC9B,OAAO,QAAS,IAAO,YAAY,YAAY,EAAE,CAAC,CACpD,CAKA,MAAM,cAAgB,IAAI,IAAI,MAAM,KAAK,OAAO,EAAE,IAAK,IAAO,GAAG,QAAQ,OAAO,CAAC,EAC3E,cAAgB,IAAI,IAU1B,GATA,KAAK,QAAQ,iBAAiB,mEAAmE,EAAE,QAAS,OAAU,CACpH,MAAM,QAAU,cAAc,IAAI,MAAM,QAAQ,MAAM,EACtD,MAAM,MAAM,QAAU,QAAU,GAAK,OACjC,SAAS,KAAK,mBAAmB,KAAK,EAC1C,cAAc,IAAI,MAAM,QAAQ,OAAQ,KAAK,CAC/C,CAAC,EAIG,YAAc,OAAO,OAAQ,CAC/B,MAAM,WAAa,cAAc,IAAI,OAAO,CAAC,EAAE,QAAQ,OAAO,EACxD,YAAc,YAAc,WAAW,cACzC,aACF,OAAO,QAAS,IAAO,CACrB,MAAM,MAAQ,cAAc,IAAI,GAAG,QAAQ,OAAO,EAC9C,OAAO,YAAY,YAAY,KAAK,CAC1C,CAAC,CAEL,CAwBA,IAVC,KAAK,gBAAkB,CAAC,GAAG,QAAS,OAAU,CAIzC,MAAM,QAAQ,mBAClB,MAAM,QAAQ,YAAc,cAAc,IAAI,MAAM,QAAQ,OAAO,EAAI,QAAU,OACnF,CAAC,EAIG,YAAc,KAAK,gBAAkB,KAAK,eAAe,OAAQ,CACnE,MAAM,eAAiB,IAAI,IAAI,KAAK,eAAe,IAAK,GAAM,CAAC,EAAE,QAAQ,QAAS,CAAC,CAAC,CAAC,EAC/E,UAAY,CAAC,EACnB,OAAO,QAAS,IAAO,CACrB,MAAM,MAAQ,eAAe,IAAI,GAAG,QAAQ,OAAO,EAC/C,OAAO,UAAU,KAAK,KAAK,CACjC,CAAC,EACD,KAAK,eAAe,QAAS,GAAM,CAC5B,UAAU,SAAS,CAAC,GAAG,UAAU,KAAK,CAAC,CAC9C,CAAC,EACD,KAAK,eAAiB,SACxB,CAIA,KAAK,0BAA0B,EAE/B,KAAK,sBAAsB,CAC7B,CAIA,oBAAoB,QAAS,CAC3B,MAAM,UAAY,QAAQ,cAAc,4BAA4B,EAC9D,aAAe,QAAQ,cAAc,+BAA+B,EACpE,QAAU,QAAQ,cAAc,wCAAwC,EACxE,SAAW,QAAQ,cAAc,yCAAyC,EAC1E,QAAU,QAAQ,cAAc,sCAAsC,EACtE,OAAS,QAAQ,cAAc,mCAAmC,EAClE,UAAY,QAAQ,cAAc,qCAAqC,EACvE,QAAU,QAAQ,cAAc,mCAAmC,EAenE,OAAS,SAAS,cAAc,kBAAkB,EAClD,WAAa,QAAQ,cAAc,8BAA8B,EAIvE,GAAI,QAAU,CAAC,OAAO,cAAc,2BAA2B,EAAG,CAChE,MAAM,UAAY,OAAO,cAAc,4BAA4B,EACnE,GAAI,UAAW,CACb,MAAM,MAAQ,SAAS,cAAc,MAAM,EAC3C,MAAM,UAAY,wCAClB,MAAM,QAAQ,kBAAoB,GAClC,MAAM,OAAS,GACf,MAAM,UACJ,mMACF,UAAU,sBAAsB,WAAY,KAAK,EACjD,KAAK,mBAAmB,CAC1B,CACF,CACA,MAAM,WAAa,YAAc,QAAQ,cAAc,8BAA8B,EAC/E,eAAiB,SAAS,eAAe,iBAAiB,EAChE,GAAI,YAAc,gBAAkB,eAAe,gBAAkB,WAAY,CAC/E,eAAe,UAAU,IAAI,6BAA6B,EAE1D,WAAW,YAAY,cAAc,EAIrC,MAAM,UAAY,eAAe,cAAc,mBAAmB,EAC9D,YACF,UAAU,YAAc,SACxB,UAAU,iBAAiB,QAAS,IAAM,WAAW,MAAM,CAAC,EAEhE,CAEA,WAAW,iBAAiB,QAAS,IAAM,CACzC,MAAM,KAAO,WAAW,cAAc,OAAO,EACzC,OACF,KAAK,QAAU,GACf,KAAK,cAAc,IAAI,MAAM,SAAU,CAAE,QAAS,EAAK,CAAC,CAAC,GAE3D,MAAME,aAAc,SAAS,eAAe,qBAAqB,EAC7DA,eAAaA,aAAY,MAAQ,IAQrC,WAAW,IAAM,QAAQ,EAAG,CAAC,CAC/B,CAAC,EAID,MAAM,YAAc,SAAS,eAAe,qBAAqB,EAC3D,WAAa,SAAS,eAAe,iBAAiB,EACtD,WAAa,IAAM,CAAC,CAAC,gBAAkB,CAAC,eAAe,UAAU,SAAS,QAAQ,EAExF,IAAI,YAAc,GAClB,MAAM,QAAU,IAAM,CACpB,MAAM,KAAQ,aAAe,YAAY,MAAM,KAAK,GAAM,GACpD,SAAW,WAAW,EAc5B,GAVI,YAAW,UAAU,MAAM,QAAU,UAAY,KAAO,OAAS,IACjE,UAAS,QAAQ,MAAM,QAAU,UAAY,KAAO,OAAS,IAI7D,WAAU,SAAS,MAAM,QAAU,CAAC,UAAY,KAAO,eAAiB,QACxE,UAAS,QAAQ,MAAM,QAAU,CAAC,UAAY,KAAO,GAAK,QAE9D,QAAQ,UAAU,OAAO,QAAS,CAAC,CAAC,IAAI,EACpC,SAAQ,OAAO,YAAc,MAC7B,KAAM,CACR,MAAM,aAAe,cAAc,cAAc,OAAO,EACpD,cAAgB,CAAC,aAAa,UAChC,aAAa,QAAU,GACvB,aAAa,cAAc,IAAI,MAAM,SAAU,CAAE,QAAS,EAAK,CAAC,CAAC,EAErE,CAMI,QAAU,aACR,UAAY,CAAC,aACf,YAAc,GACd,OAAO,KAAK,SAAW,OAAO,GACrB,CAAC,UAAY,cACtB,YAAc,GACd,OAAO,MAAM,GAGnB,EACA,aAAa,iBAAiB,QAAS,OAAO,EAC9C,YAAY,iBAAiB,QAAS,OAAO,EAC7C,SAAS,iBAAiB,sBAAuB,OAAO,EAGxD,cAAc,cAAc,OAAO,GAAG,iBAAiB,SAAU,OAAO,EAMpE,gBAAgB,IAAI,iBAAiB,OAAO,EAAE,QAAQ,eAAgB,CAAE,WAAY,GAAM,gBAAiB,CAAC,OAAO,CAAE,CAAC,EAC1H,QAAQ,CACV,CAEA,iBAAiB,QAAS,CACxB,GAAI,QAAQ,QAAQ,cAAgB,OAAQ,OAAO,KACnD,MAAM,YAAc,SAAS,eAAe,qBAAqB,EACjE,OAAQ,aAAe,YAAY,MAAM,KAAK,GAAM,IACtD,CAOA,eAAe,IAAK,CAClB,OAAK,IAED,KAAK,QAAQ,aAAe,OAAe,IAAI,MAC5C,CAAC,IAAI,kBAAoB,IAAI,aAAe,MAAQ,IAAI,YAAc,IAAI,MAAQ,IAAI,YAAc,IAAI,MAH9F,CAInB,CAEA,gBAAiB,CACf,IAAI,MAAQ,EACR,aAAe,EACf,YAAc,GAElB,MAAM,UAAY,KAAK,SAAS,KAAM,IAAO,GAAG,QAAQ,gBAAkB,MAAM,EAC1E,QAAU,UAAY,KAAK,WAAW,IAAI,SAAS,EAAI,KACvD,UAAY,QAAU,QAAQ,MAAQ,EAGtC,cAAgB,KAAK,eAAe,OAAO,EAC7C,gBAAkB,YAAW,YAAc,IAC/C,OAAS,cACT,cAAgB,UAEhB,KAAK,WAAW,QAAQ,CAAC,IAAK,UAAY,CACxC,GAAI,QAAQ,QAAQ,gBAAkB,OAAQ,OAC9C,MAAM,UAAY,KAAK,eAAe,GAAG,EACrC,YAAc,IAAI,QAAO,YAAc,IAC3C,OAAS,UACT,cAAgB,IAAI,KACtB,CAAC,EAID,MAAM,YAAc,KAAK,aAAe,EACxC,OAAS,YACT,cAAgB,YAEhB,MAAM,QAAU,KAAK,cAAc,yBAAyB,EACtD,UAAY,KAAK,cAAc,2BAA2B,EAC1D,UAAY,KAAK,cAAc,qBAAqB,EAEtD,UAAS,QAAQ,YAAc,YAAY,MAAO,EAAI,GACtD,YACE,aAAe,aAAe,OAChC,UAAU,YAAc,YAAY,YAAY,EAChD,UAAU,MAAM,QAAU,IAE1B,UAAU,MAAM,QAAU,QAG1B,YACF,UAAU,MAAM,QAAU,YAAc,GAAK,QAG/C,KAAK,gBAAgB,OAAO,CAC9B,CAGA,gBAAgB,QAAS,CACvB,MAAM,GAAK,KAAK,cAAc,mBAAmB,EACjD,GAAI,CAAC,GAAI,OACT,MAAM,IAAM,KAAK,eAAe,OAAO,EACnC,SAAW,IAAM,QAAQ,MAC3B,GAAG,UAAY,yCAAyC,YAAY,QAAQ,KAAK,CAAC,QAAQ,YAAY,GAAG,CAAC,GAE1G,GAAG,YAAc,EAErB,CAWA,eAAe,UAAW,CACxB,MAAM,UAAY,KAAK,OAAO,UAC9B,GAAI,CAAC,WAAa,CAAC,UAAW,OAC9B,MAAM,IAAM,IAAI,IAAI,OAAO,SAAS,IAAI,EACxC,IAAI,aAAa,IAAI,UAAW,SAAS,EACzC,IAAI,aAAa,IAAI,aAAc,SAAS,EAI5C,KAAK,sBAAsB,MAAM,EACjC,MAAM,WAAa,IAAI,gBACvB,KAAK,qBAAuB,WAE5B,MAAM,IAAI,SAAS,EAAG,CAAE,OAAQ,WAAW,MAAO,CAAC,EAChD,KAAM,KAAQ,IAAI,KAAK,CAAC,EACxB,KAAM,MAAS,CAEd,MAAM,OADO,IAAI,UAAU,EAAE,gBAAgB,KAAM,WAAW,EAC1C,eAAe,YAAY,SAAS,EAAE,EACpD,YAAc,SAAS,eAAe,YAAY,SAAS,EAAE,EAC/D,QAAU,cAAa,YAAY,UAAY,OAAO,UAC5D,CAAC,EACA,MAAO,KAAQ,CACV,IAAI,OAAS,cAAc,QAAQ,MAAM,GAAG,CAClD,CAAC,CACL,CAIA,gBAAiB,CAEf,GADA,KAAK,WAAa,KAAK,cAAc,oBAAoB,EACrD,CAAC,KAAK,WAAY,OAItB,KAAK,eAAiB,IAAI,IAC1B,IAAI,SAAW,EACf,KAAK,SAAS,QAAS,SAAY,CACjC,GAAI,QAAQ,QAAQ,gBAAkB,OAAQ,OAC9C,MAAM,OAAS,MAAM,KAAK,QAAQ,iBAAiB,0BAA0B,CAAC,EAC9E,IAAI,KAAO,EACX,OAAO,QAAS,OAAU,CACxB,GAAI,MAAM,QAAU,OAAQ,OAC5B,MAAM,MAAQ,OAAO,MAAM,QAAQ,KAAK,GAAK,EACvC,YAAc,MAAM,QAAQ,qBAAuB,KAAK,QAAQ,aAAe,OAAS,OAAO,MAAM,QAAQ,mBAAmB,EAAI,KACtI,aAAe,MAAQ,YAAc,QACvC,KAAO,KAAK,IAAI,KAAM,MAAQ,WAAW,GAKvC,QAAQ,QAAQ,cAAgB,QAAU,KAAK,uBACjD,KAAO,KAAK,IAAI,KAAM,KAAK,EAE/B,CAAC,EACG,KAAO,IACT,KAAK,eAAe,IAAI,QAAS,IAAI,EACrC,UAAY,KAEhB,CAAC,EACD,KAAK,kBAAoB,SAEzB,MAAM,UAAY,KAAK,WAAW,cAAc,uBAAuB,EACjE,WAAa,KAAK,WAAW,cAAc,kCAAkC,EACnF,WAAW,iBAAiB,QAAS,IAAM,CACzC,GAAI,CAAC,WAAY,OAEjB,aAAa,KAAK,iBAAiB,EACnC,KAAK,gBAAkB,GACvB,MAAM,SAAW,WAAW,UAAU,SAAS,SAAS,EACxD,KAAK,eAAe,CAAC,QAAQ,CAC/B,CAAC,EAED,KAAK,wBAAwB,EAC7B,OAAO,iBAAiB,SAAU,IAAM,KAAK,wBAAwB,CAAC,EAGtE,MAAM,WAAa,KAAK,QAAQ,cAAc,mBAAmB,EACjE,GAAI,YAAc,OAAO,eAAmB,IAAa,CACvD,IAAI,SAAW,EACf,MAAM,MAAQ,IAAM,CAAE,qBAAqB,QAAQ,EAAG,SAAW,sBAAsB,IAAM,KAAK,wBAAwB,CAAC,CAAG,EAC9H,IAAI,eAAe,KAAK,EAAE,QAAQ,UAAU,EAC5C,IAAI,eAAe,KAAK,EAAE,QAAQ,SAAS,eAAe,CAC5D,CAUA,GATA,OAAO,iBAAiB,OAAQ,IAAM,KAAK,wBAAwB,CAAC,EACpE,SAAS,OAAO,OAAO,KAAK,IAAM,KAAK,wBAAwB,CAAC,EAQ5D,OAAO,qBAAyB,IAAa,CAC/C,MAAM,WAAa,KAAK,QAAQ,aAAa,GAAK,KAAK,QACtC,IAAI,qBACnB,CAAC,CAAC,KAAK,IAAM,CACX,MAAM,aAAe,CAAC,MAAM,gBAAkB,MAAM,mBAAmB,IAAM,EAC7E,KAAK,WAAW,UAAU,OAAO,kBAAmB,YAAY,CAClE,EACA,CAAE,UAAW,CAAE,CACjB,EACS,QAAQ,UAAU,CAC7B,CACF,CAEA,yBAA0B,CACxB,GAAI,CAAC,KAAK,WAAY,OACtB,MAAM,OAAS,KAAK,QAAQ,cAAc,mBAAmB,EAE7D,GAAI,CADc,OAAO,WAAW,oBAAoB,EAAE,SACxC,CAAC,OAAQ,CACzB,KAAK,WAAW,UAAU,OAAO,WAAW,EAC5C,KAAK,WAAW,MAAM,KAAO,GAC7B,KAAK,WAAW,MAAM,MAAQ,GAC9B,MACF,CAKA,MAAM,KAAO,OAAO,sBAAsB,EACpC,QAAU,OAAO,cAAc,qBAAqB,GAAK,OAAO,cAAc,iCAAiC,EAC/G,KAAO,QAAU,QAAQ,sBAAsB,EAAI,CAAE,KAAM,KAAK,KAAO,GAAI,MAAO,KAAK,MAAQ,EAAG,EAClG,SAAW,GACX,SAAW,SAAS,gBAAgB,YAAc,EAClD,KAAO,KAAK,IAAI,EAAG,KAAK,KAAO,QAAQ,EACvC,MAAQ,KAAK,IAAI,SAAU,KAAK,MAAQ,QAAQ,EACtD,KAAK,WAAW,UAAU,IAAI,WAAW,EACzC,KAAK,WAAW,MAAM,KAAO,GAAG,KAAK,MAAM,IAAI,CAAC,KAChD,KAAK,WAAW,MAAM,MAAQ,GAAG,KAAK,IAAI,EAAG,KAAK,MAAM,MAAQ,IAAI,CAAC,CAAC,IACxE,CAEA,oBAAoB,QAAS,CAC3B,GAAI,CAAC,KAAK,WAAY,OAItB,IAAI,QAAU,EACd,KAAK,WAAW,QAAS,KAAQ,CAC/B,SAAW,IAAI,MAAQ,KAAK,eAAe,GAAG,CAChD,CAAC,EAED,MAAM,UAAY,KAAK,SAAS,KAAM,IAAO,GAAG,QAAQ,gBAAkB,MAAM,EAC1E,QAAU,UAAY,KAAK,WAAW,IAAI,SAAS,EAAI,KACvD,WAAa,QAAU,QAAQ,MAAQ,KAAK,eAAe,OAAO,EAAI,EACtE,KAAO,KAAK,mBAAqB,GAAK,WACtC,IAAM,IAAM,EAAI,KAAK,IAAI,IAAK,KAAK,MAAO,QAAU,IAAO,GAAG,CAAC,EAAI,EAEnE,UAAY,KAAK,WAAW,cAAc,iCAAiC,EAC3E,MAAQ,KAAK,WAAW,cAAc,mCAAmC,EACzE,OAAS,KAAK,WAAW,cAAc,8BAA8B,EACrE,UAAY,KAAK,WAAW,cAAc,iCAAiC,EAE7E,YAAW,UAAU,YAAc,YAAY,OAAO,GACtD,QAAO,MAAM,YAAc,YAAY,GAAG,GAC1C,SAAQ,OAAO,MAAM,MAAQ,GAAG,GAAG,KAKvC,MAAM,UAAY,KAAK,WAAW,cAAc,uBAAuB,EACjE,aAAe,KAAK,WAAW,cAAc,qCAAqC,EAgBxF,GAfI,WAAa,eACX,KAAO,EACT,UAAU,MAAM,QAAU,QAE1B,UAAU,MAAM,QAAU,GACtB,SAAW,EACb,aAAa,YAAc,cAAc,YAAY,GAAG,CAAC,GAEzD,aAAa,YAAc,GAAG,YAAY,OAAO,CAAC,qBAOpD,SAAW,CAAC,QAAQ,OAAQ,CAC9B,MAAM,IAAM,KAAK,WAAW,IAAI,QAAQ,OAAO,EAC3C,KAAO,IAAI,MAAQ,KAAK,eAAe,GAAG,EAAI,GAAG,KAAK,gBAAgB,CAC5E,CAEA,GAAI,WAAa,QACf,GAAI,QAAQ,OACV,UAAU,MAAM,QAAU,WACrB,CACL,MAAM,UAAY,QAAQ,QAAQ,cAAc,4BAA4B,EAC5E,IAAI,QAAU,OACd,GAAI,UAAW,CACb,MAAM,MAAQ,UAAU,UAAU,EAAI,EACtC,MAAM,cAAc,iCAAiC,GAAG,OAAO,EAC/D,QAAU,MAAM,YAAY,KAAK,CACnC,CACA,MAAM,UAAY,KAAK,IAAI,EAAG,IAAM,OAAO,EAC3C,UAAU,YACR,UAAY,EACR,GAAG,OAAO,eAAY,YAAY,SAAS,CAAC,kBAC5C,GAAG,OAAO,sCAChB,UAAU,MAAM,QAAU,EAC5B,CAGF,KAAK,sBAAsB,CAC7B,CAEA,iBAAkB,CAChB,MAAM,WAAa,KAAK,YAAY,cAAc,kCAAkC,EAC9E,UAAY,KAAK,YAAY,cAAc,uBAAuB,EACpE,CAAC,YAAc,CAAC,YACpB,aAAa,KAAK,iBAAiB,EAE/B,aAAW,UAAU,SAAS,SAAS,GAAK,CAAC,KAAK,mBACtD,KAAK,gBAAkB,GACvB,KAAK,eAAe,EAAI,EACxB,KAAK,kBAAoB,WAAW,IAAM,CACnC,KAAK,kBACV,KAAK,gBAAkB,GACvB,KAAK,eAAe,EAAK,EAC3B,EAAG,IAAI,GACT,CAGA,eAAe,KAAM,CACnB,MAAM,WAAa,KAAK,YAAY,cAAc,kCAAkC,EAC9E,UAAY,KAAK,YAAY,cAAc,uBAAuB,EACnE,aACL,WAAW,UAAU,OAAO,UAAW,IAAI,EAC3C,WAAW,aAAa,cAAe,OAAO,CAAC,IAAI,CAAC,EACpD,WAAW,aAAa,gBAAiB,OAAO,IAAI,CAAC,EACvD,CAUA,qBAAsB,CACpB,MAAM,GAAK,SAAS,cAAc,QAAQ,EAC1C,GAAG,KAAO,SACV,GAAG,UAAY,0BACf,GAAG,aAAa,aAAc,qBAAqB,EACnD,GAAG,UACD,qXAKF,SAAS,KAAK,YAAY,EAAE,EAC5B,KAAK,aAAe,GACpB,KAAK,kBAAoB,GAAG,cAAc,iCAAiC,EAE3E,GAAG,iBAAiB,QAAS,IAAM,KAAK,aAAa,KAAK,aAAe,MAAS,CAAC,EAEnF,MAAM,GAAK,OAAO,WAAW,oBAAoB,EACjD,IAAI,QAAU,GACd,MAAM,OAAS,IAAM,CAEnB,GADA,QAAU,GACN,CAAC,GAAG,QAAS,CACf,GAAG,UAAU,OAAO,YAAY,EAChC,MACF,CACA,MAAM,YAAc,KAAK,QAAQ,sBAAsB,EAAE,OAAS,EAC5D,YAAc,KAAK,QAAQ,sBAAsB,EAAE,QAAU,EAC7D,YAAc,KAAK,YAAc,KAAK,WAAW,sBAAsB,EAAE,KAAO,OAAO,YAAc,EAC3G,GAAG,UAAU,OAAO,aAAc,aAAe,CAAC,aAAe,CAAC,WAAW,CAC/E,EACM,SAAW,IAAM,CACjB,UACJ,QAAU,GACV,sBAAsB,MAAM,EAC9B,EACA,OAAO,iBAAiB,SAAU,SAAU,CAAE,QAAS,EAAK,CAAC,EAC7D,OAAO,iBAAiB,SAAU,QAAQ,EAC1C,GAAG,iBAAiB,SAAU,QAAQ,EACtC,SAAS,CACX,CAIA,WAAY,CACV,MAAM,IAAM,sDACZ,OACG,KAAK,aAAe,KAAK,UAAU,cAAc,GAAG,GAAG,mBAAmB,KAAK,WAAW,QAAQ,GACnG,KAAK,UAAU,cAAc,oCAAoC,GACjE,MAAM,KAAK,KAAK,UAAU,iBAAiB,GAAG,CAAC,EAAE,KAAM,IAAO,GAAG,MAAM,UAAY,QAAU,GAAG,cAAc,KAAK,CAAC,GAAG,cAAc,KAAK,GAC1I,IAEJ,CAMA,gBAAgB,UAAW,OAAQ,CACjC,GAAI,CAAC,WAAa,CAAC,OAAQ,OAC3B,MAAM,IAAM,OAAO,YAAc,OAAO,KAAO,OAAO,QAAQ,aAAe,GACvE,OAAS,OAAO,aAAa,QAAQ,GAAK,OAAO,QAAQ,gBAAkB,GACjF,GAAI,CAAC,IAAK,OACV,MAAM,MAAS,UAAU,gBAAkB,UAAU,gBAAkB,GAAK,EACtE,MAAQ,IAAI,MACZ,MAAQ,IAAM,CAClB,GAAI,UAAU,iBAAmB,MAAO,OACxC,UAAU,iBAAiB,kCAAkC,EAAE,QAAS,IAAO,GAAG,OAAO,CAAC,EAC1F,MAAM,IAAM,SAAS,cAAc,KAAK,EACxC,IAAI,IAAM,OAAO,KAAO,GACpB,SAAQ,IAAI,OAAS,QACzB,IAAI,IAAM,IACV,UAAU,QAAQ,GAAG,EACrB,KAAK,SAAS,OAAO,CACvB,EACA,MAAM,OAAS,MACf,MAAM,QAAU,IAAM,CAAC,EACnB,SAAQ,MAAM,OAAS,QAC3B,MAAM,IAAM,IACR,MAAM,UAAY,MAAM,cAAc,MAAM,CAClD,CAEA,uBAAwB,CACjB,KAAK,mBAGV,KAAK,gBAAgB,KAAK,kBAAmB,KAAK,UAAU,CAAC,CAC/D,CAEA,uBAAwB,CAEtB,GADA,KAAK,sBAAsB,EACvB,CAAC,KAAK,WAAY,OACtB,MAAM,QAAU,KAAK,WAAW,cAAc,+BAA+B,EAG7E,KAAK,gBAAgB,QAAS,KAAK,UAAU,CAAC,EAG9C,KAAK,SAAS,OAAO,CACvB,CAMA,oBAAqB,CACnB,MAAM,UAAY,KAAK,SAAS,KAAM,KAAQ,IAAI,QAAQ,gBAAkB,MAAM,EAC5E,QAAU,UAAY,KAAK,WAAW,IAAI,SAAS,EAAI,KACvD,KAAQ,SAAW,KAAK,aAAa,QAAQ,SAAS,GAAM,CAAC,EAE7D,IADe,MAAM,KAAK,KAAK,WAAW,OAAO,CAAC,EAAE,KAAM,GAAM,EAAE,WAAW,GACtD,KAAK,WAAc,KAAK,SAAW,GAChE,MAAI,CAAC,SAAY,CAAC,KAAO,CAAC,KAAK,SAAkB,KAC1C,CAAE,OAAQ,KAAK,SAAW,aAAe,YAAa,GAAI,CACnE,CAEA,mBAAoB,CAClB,MAAM,GAAK,KAAK,cAAc,qBAAqB,EACnD,GAAI,CAAC,GAAI,OACT,MAAM,QAAU,KAAK,mBAAmB,EACxC,GAAI,CAAC,QAAS,CACZ,GAAG,OAAS,GACZ,MACF,CACA,MAAM,OAAS,SAAS,cAAc,QAAQ,EAC9C,OAAO,YAAc,QAAQ,OAC7B,GAAG,cAAc,4BAA4B,EAAE,gBAC7C,OACA,SAAS,eAAe,QAAQ,IAAM,IAAI,QAAQ,GAAG,GAAK,EAAE,CAC9D,EACA,GAAG,OAAS,EACd,CAWA,kBAAmB,CAGjB,MAAM,YAAc,KAAK,SAAW,KAAK,QAAQ,cAAc,4BAA4B,EAC3F,GAAI,YAAa,CACf,MAAM,OAAS,KAAK,QAAQ,cAAc,mBAAmB,EACvD,UAAY,KAAK,QAAQ,cAAc,sBAAsB,EACnE,GAAI,CAAC,QAAU,CAAC,UAAW,OAC3B,MAAM,QAAU,OAAO,WAAW,oBAAoB,EACtD,QAAQ,iBAAiB,SAAU,IAAM,CACnC,QAAQ,QAAS,OAAO,aAAa,YAAa,SAAS,EAC1D,KAAK,QAAQ,WAAW,CAC/B,CAAC,EACD,MACF,CACA,MAAM,QAAU,KAAK,cAAc,mBAAmB,EAIhD,UAAY,KAAK,cAAc,wBAAwB,EACvD,KAAO,KAAK,QAAQ,cAAc,mBAAmB,EACrD,QAAU,KAAK,QAAQ,cAAc,sBAAsB,EAEjE,GADI,CAAC,SAAW,CAAC,WAAa,CAAC,MAAQ,CAAC,SACpC,QAAQ,aAAe,UAAU,WAAY,OAEjD,IAAI,OACJ,GAAI,CACF,MAAM,MAAQ,SAAS,YAAY,EACnC,MAAM,eAAe,OAAO,EAC5B,MAAM,YAAY,SAAS,EAC3B,OAAS,SAAS,cAAc,KAAK,EACrC,OAAO,UAAY,4BACnB,MAAM,iBAAiB,MAAM,CAC/B,MAAY,CACV,MACF,CAGA,MAAM,WAAa,OAAO,WAEpB,GAAK,OAAO,WAAW,oBAAoB,EAC3C,MAAQ,IAAM,CACd,GAAG,QACL,KAAK,aAAa,OAAQ,OAAO,EAEjC,WAAW,QAAQ,MAAM,CAE7B,EACA,GAAG,iBAAiB,SAAU,KAAK,EACnC,MAAM,CACR,CAeA,wBAAyB,CACvB,MAAM,UAAY,KAAK,QAAQ,iBAAiB,qCAAqC,EACrF,GAAI,CAAC,UAAU,OAAQ,OAEvB,MAAM,UAAY,KAAK,QAAQ,cAAc,iBAAiB,EACxD,aAAe,KAAK,gBAAkB,KAAK,eAAe,cAAc,0BAA0B,EAOlG,eAAiB,KAAK,QAAQ,cAAc,eAAe,EAGjE,KAAK,mBAAqB,CAAC,EAM3B,KAAK,gBAAkB,CAAC,EAExB,UAAU,QAAS,KAAQ,CACzB,MAAM,QAAU,IAAI,QAAQ,aAC5B,KAAK,gBAAgB,KAAK,OAAO,EACjC,MAAM,MAAQ,IAAI,QAAQ,UAAU,EAAI,EAClC,UAAY,CAAC,EACnB,MAAM,KAAK,MAAM,iBAAiB,yBAAyB,CAAC,EAAE,QAAS,IAAO,CAC5E,MAAM,UAAY,GAAG,QAAQ,eAC7B,UAAU,SAAS,EAAI,UAAU,SAAS,GAAK,CAAC,EAC5C,GAAG,QAAQ,OAAS,OAAQ,UAAU,SAAS,EAAE,UAAY,GACxD,GAAG,QAAQ,OAAS,QAAS,UAAU,SAAS,EAAE,MAAQ,GAC1D,GAAG,QAAQ,OAAS,aAAY,UAAU,SAAS,EAAE,cAAgB,GAChF,CAAC,EAGD,OAAO,OAAO,SAAS,EAAE,QAAS,KAAQ,CAExC,GADI,IAAI,WAAW,KAAK,UAAU,YAAY,IAAI,SAAS,EACvD,IAAI,OAAS,UAAW,CAC1B,UAAU,YAAY,IAAI,KAAK,EAC/B,MAAM,IAAM,IAAI,MAAM,cAAc,QAAQ,EACxC,KAAO,gBACT,IAAI,iBAAiB,QAAS,IAAM,eAAe,eAAe,IAAI,MAAM,QAAQ,OAAQ,EAAK,CAAC,CAEtG,CACI,IAAI,eAAiB,eACvB,aAAa,YAAY,IAAI,aAAa,EACtC,KAAK,gBAAgB,KAAK,eAAe,KAAK,IAAI,aAAa,EAEvE,CAAC,EACD,KAAK,mBAAmB,OAAO,EAAI,SACrC,CAAC,EAED,MAAM,iBAAoB,SAAY,CACpC,MAAM,OAAS,KAAK,mBAAmB,QAAQ,QAAQ,YAAY,EACnE,GAAI,CAAC,OAAQ,OAIb,MAAM,QAAU,QAAQ,cAAc,kCAAkC,EAClE,gBAAkB,QAAU,QAAQ,QAAQ,UAAY,KAC9D,OAAO,QAAQ,MAAM,EAAE,QAAQ,CAAC,CAAC,UAAW,GAAG,IAAM,CACnD,MAAM,KAAO,YAAc,gBACvB,IAAI,YACN,IAAI,UAAU,MAAM,QAAU,KAAO,GAAK,OAC1C,IAAI,UAAU,UAAU,OAAO,WAAW,GAExC,IAAI,QAAO,IAAI,MAAM,MAAM,QAAU,KAAO,GAAK,QACjD,IAAI,gBAAe,IAAI,cAAc,QAAQ,YAAc,KAAO,QAAU,OAClF,CAAC,EAGG,CAAC,KAAK,UAAU,cAAc,gCAAgC,GAAK,KAAK,aAAe,iBACzF,eAAe,eAAe,KAAK,YAAa,EAAK,EACrD,KAAK,QAAQ,OAAO,EAExB,EAEA,KAAK,QAAQ,iBAAiB,+CAA+C,EAAE,QAAS,SAAY,CAClG,QAAQ,iBAAiB,0BAA0B,EAAE,QAAS,OAAU,CACtE,MAAM,iBAAiB,SAAU,IAAM,CACrC,iBAAiB,OAAO,EACxB,KAAK,0BAA0B,CACjC,CAAC,CACH,CAAC,EACD,iBAAiB,OAAO,CAC1B,CAAC,EACD,KAAK,0BAA0B,CACjC,CAOA,2BAA4B,CAC1B,GAAI,CAAC,KAAK,mBAAoB,OAI9B,MAAM,KACH,KAAK,aAAe,KAAK,UAAU,cAAc,mBAAmB,KAAK,WAAW,mCAAmC,GACxH,KAAK,UAAU,cAAc,+DAA+D,EAC9F,GAAI,CAAC,KAAM,OACX,MAAM,YAAc,KAAK,QAAQ,QAC3B,UAAY,KAAK,QAAQ,cAAc,iBAAiB,EACxD,UAAY,UAAY,UAAU,cAAc,iBAAiB,WAAW,IAAI,EAAI,KAE1F,IAAI,WAAa,KACb,YAAc,UAClB,MAAM,sBAAwB,CAAC,EAiB/B,GAfA,KAAK,gBAAgB,QAAS,SAAY,CACxC,MAAM,OAAS,KAAK,mBAAmB,OAAO,EACxC,QAAU,OAAO,OAAO,MAAM,EAAE,KAAM,KAAQ,IAAI,WAAa,IAAI,UAAU,MAAM,UAAY,MAAM,EACtG,UACD,QAAQ,YACV,WAAW,MAAM,QAAQ,SAAS,EAClC,WAAa,QAAQ,WAEnB,QAAQ,OAAS,cACnB,YAAY,MAAM,QAAQ,KAAK,EAC/B,YAAc,QAAQ,OAEpB,QAAQ,eAAe,sBAAsB,KAAK,QAAQ,aAAa,EAC7E,CAAC,EAEG,KAAK,gBAAkB,sBAAsB,OAAQ,CACvD,MAAM,cAAgB,KAAK,eAAe,OAAQ,GAAM,CAAC,sBAAsB,SAAS,CAAC,CAAC,EACpF,UAAY,cAAc,UAAW,GAAM,EAAE,QAAQ,UAAY,WAAW,EAC9E,YAAc,KAChB,cAAc,OAAO,UAAY,EAAG,EAAG,GAAG,qBAAqB,EAC/D,KAAK,eAAiB,cAE1B,CACF,CAQA,iBAAiB,KAAM,CACrB,MAAM,KAAO,KAAK,QAAQ,cAAc,iBAAiB,EACzD,GAAI,CAAC,KAAM,OACX,MAAM,GAAK,OAAO,WAAW,oBAAoB,EACjD,IAAI,OAAS,EACT,OAAS,EACT,SAAW,GACf,KAAK,iBACH,aACC,GAAM,CACL,SAAW,GAAG,SAAW,EAAE,QAAQ,SAAW,EACzC,WACL,OAAS,EAAE,QAAQ,CAAC,EAAE,QACtB,OAAS,EAAE,QAAQ,CAAC,EAAE,QACxB,EACA,CAAE,QAAS,EAAK,CAClB,EACA,KAAK,iBACH,WACC,GAAM,CACL,GAAI,CAAC,SAAU,OACf,SAAW,GACX,MAAM,EAAI,EAAE,eAAe,CAAC,EACtB,GAAK,EAAE,QAAU,OACjB,GAAK,EAAE,QAAU,OACnB,KAAK,IAAI,EAAE,EAAI,IAAM,KAAK,IAAI,EAAE,EAAI,KAAK,IAAI,EAAE,EAAI,KAAK,KAAK,GAAK,EAAI,EAAI,EAAE,CAClF,EACA,CAAE,QAAS,EAAK,CAClB,EACA,KAAK,iBAAiB,cAAe,IAAM,CACzC,SAAW,EACb,EAAG,CAAE,QAAS,EAAK,CAAC,CACtB,CAEA,sBAAsB,KAAM,CAC1B,MAAM,KAAO,KAAK,QAAQ,cAAc,iBAAiB,EACzD,GAAI,CAAC,KAAM,OACX,MAAM,MAAS,KACb,0JAA0J,IAAM,EAAI,yBAA2B,uBAAuB,YAClN,KAAO,SAAS,cAAc,KAAK,EACzC,KAAK,UAAY,0BACjB,KAAK,UACH,0FAA0F,MAAM,EAAE,CAAC,mKAEb,MAAM,CAAC,CAAC,YAChG,KAAM,CAAC,KAAM,IAAI,EAAI,KAAK,iBAAiB,QAAQ,EACnD,KAAK,iBAAiB,QAAS,IAAM,KAAK,EAAE,CAAC,EAC7C,KAAK,iBAAiB,QAAS,IAAM,KAAK,CAAC,CAAC,EAC5C,KAAK,YAAY,IAAI,EAErB,MAAM,OAAS,KAAK,cAAc,gCAAgC,EAC5D,OAAS,IAAM,CACnB,MAAM,OAAS,MAAM,KAAK,KAAK,QAAQ,iBAAiB,oCAAoC,CAAC,EAAE,OAC5F,IAAO,GAAG,MAAM,UAAY,MAC/B,EACM,OAAS,KAAK,UAAU,cAAc,gCAAgC,EACtE,IAAM,KAAK,IAAI,EAAG,OAAO,UAAW,IAAO,GAAG,QAAQ,SAAW,QAAQ,QAAQ,OAAO,CAAC,EACzF,MAAQ,KAAK,IAAI,OAAO,OAAQ,CAAC,EACjC,MAAQ,OAAO,OAAS,EAAI,KAAK,IAAI,KAAK,IAAI,IAAM,EAAG,CAAC,EAAG,OAAO,OAAS,CAAC,EAAI,EACtF,OAAO,UAAY,GACnB,QAAS,EAAI,EAAG,EAAI,MAAO,IAAK,CAC9B,MAAM,IAAM,SAAS,cAAc,MAAM,EACzC,IAAI,UAAY,gCAAkC,MAAQ,IAAM,IAAM,aAAe,IACrF,OAAO,YAAY,GAAG,CACxB,CACF,EACA,IAAI,OAAS,GACb,MAAM,SAAW,IAAM,CACjB,SACJ,OAAS,GACT,sBAAsB,IAAM,CAC1B,OAAS,GACT,OAAO,CACT,CAAC,EACH,EACA,IAAI,iBAAiB,QAAQ,EAAE,QAAQ,KAAK,UAAW,CACrD,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,QAAS,OAAO,CACpC,CAAC,EACD,OAAO,CACT,CAIA,sBAAuB,CASrB,MAAM,QAAU,KAAK,QAAQ,cAAc,mBAAmB,EACxD,QAAU,KAAK,QAAQ,cAAc,mBAAmB,EACxD,eAAiB,KAAK,QAAQ,cAAc,eAAe,EACjE,GAAI,CAAC,gBAAmB,CAAC,SAAW,CAAC,QAAU,OAE/C,MAAM,KAAQ,WAAc,CAC1B,MAAM,aAAe,MAAM,KAAK,eAAe,iBAAiB,yCAAyC,CAAC,EAAE,OACzG,IAAO,GAAG,eAAiB,IAC9B,EACA,GAAI,CAAC,aAAa,OAAQ,OAC1B,MAAM,YAAc,KAAK,UAAU,cAAc,gCAAgC,EAC3E,aAAe,aAAa,UAAW,IAAO,GAAG,QAAQ,SAAW,aAAa,QAAQ,OAAO,EAEhG,YADY,eAAiB,GAAK,EAAI,cACb,UAAY,aAAa,QAAU,aAAa,OAC/E,eAAe,eAAe,aAAa,SAAS,EAAE,QAAQ,OAAQ,EAAK,CAC7E,EAEA,SAAS,iBAAiB,QAAS,IAAM,KAAK,EAAE,CAAC,EACjD,SAAS,iBAAiB,QAAS,IAAM,KAAK,CAAC,CAAC,EAEhD,KAAK,sBAAsB,IAAI,EAC/B,KAAK,iBAAiB,IAAI,CAC5B,CAEA,cAAe,CACb,MAAM,eAAiB,KAAK,QAAQ,cAAc,kBAAkB,EACpE,GAAI,CAAC,eAAgB,OACrB,KAAK,eAAiB,eAQtB,SAAS,KAAK,YAAY,cAAc,EACxC,KAAK,eAAiB,MAAM,KAAK,eAAe,iBAAiB,yBAAyB,CAAC,EAI3F,MAAM,cAAgB,IAAM,KAAK,eAAe,OAAQ,GAAM,EAAE,QAAQ,cAAgB,MAAM,EACxF,aAAe,IAAM,KAAK,eAAe,KAAM,GAAM,EAAE,MAAM,UAAY,MAAM,GAAK,KAqBpF,gBAAmB,OAAU,CACjC,MAAM,KAAO,OAAO,cAAc,6BAA6B,EAC1D,OACD,KAAK,UAAY,SAAS,KAAK,MAAM,EACzC,KAAK,OAAO,EACd,EACA,KAAK,wBAA0B,gBAE/B,IAAI,WAAa,KACjB,MAAM,UAAa,OAAU,CAC3B,GAAK,MAIL,IAHA,KAAK,eAAe,QAAS,GAAM,CAC7B,IAAM,OAAO,gBAAgB,CAAC,CACpC,CAAC,EACG,MAAM,QAAQ,WAAa,MAAM,QAAQ,YAAc,SACzD,GAAI,CAAC,MAAM,cAAc,6BAA6B,EAAG,CACvD,MAAM,SAAW,MAAM,cAAc,UAAU,EAC3C,UAAU,MAAM,YAAY,SAAS,QAAQ,UAAU,EAAI,CAAC,CAClE,UACS,CAAC,MAAM,cAAc,KAAK,EAAG,CACtC,MAAM,IAAM,SAAS,cAAc,KAAK,EACxC,IAAI,IAAM,MAAM,QAAQ,SACxB,IAAI,IAAM,MAAM,QAAQ,UAAY,GACpC,MAAM,YAAY,GAAG,CACvB,CACA,KAAK,eAAe,QAAS,GAAM,CACjC,EAAE,MAAM,QAAU,IAAM,MAAQ,GAAK,MACvC,CAAC,EACD,KAAK,oBAAoB,KAAK,EAC9B,aAAa,EACf,EACA,KAAK,kBAAoB,UAEzB,MAAM,KAAQ,WAAc,CAC1B,MAAM,QAAU,cAAc,EAC9B,GAAI,CAAC,QAAQ,OAAQ,OACrB,MAAM,QAAU,QAAQ,QAAQ,aAAa,CAAC,EACxC,UAAY,UAAY,GAAK,EAAI,SAAW,UAAY,QAAQ,QAAU,QAAQ,OACxF,UAAU,QAAQ,OAAO,CAAC,CAC5B,EACA,KAAK,aAAe,KAIpB,CACE,MAAM,MAAS,KACb,0JAA0J,IAAM,EAAI,yBAA2B,uBAAuB,YAClN,KAAO,SAAS,cAAc,KAAK,EACzC,KAAK,UAAY,4DACjB,KAAK,UACH,0FAA0F,MAAM,EAAE,CAAC,mKAEb,MAAM,CAAC,CAAC,YAChG,KAAM,CAAC,MAAO,KAAK,EAAI,KAAK,iBAAiB,QAAQ,EACrD,MAAM,iBAAiB,QAAS,IAAM,KAAK,EAAE,CAAC,EAC9C,MAAM,iBAAiB,QAAS,IAAM,KAAK,CAAC,CAAC,EAC7C,eAAe,YAAY,IAAI,EAC/B,MAAM,OAAS,KAAK,cAAc,gCAAgC,EAClE,WAAa,IAAM,CACjB,MAAM,QAAU,cAAc,EACxB,IAAM,KAAK,IAAI,EAAG,QAAQ,QAAQ,aAAa,CAAC,CAAC,EACjD,MAAQ,KAAK,IAAI,QAAQ,OAAQ,CAAC,EAClC,MAAQ,QAAQ,OAAS,EAAI,KAAK,IAAI,KAAK,IAAI,IAAM,EAAG,CAAC,EAAG,QAAQ,OAAS,CAAC,EAAI,EACxF,OAAO,UAAY,GACnB,QAAS,EAAI,EAAG,EAAI,MAAO,IAAK,CAC9B,MAAM,IAAM,SAAS,cAAc,MAAM,EACzC,IAAI,UAAY,gCAAkC,MAAQ,IAAM,IAAM,aAAe,IACrF,OAAO,YAAY,GAAG,CACxB,CACF,CACF,CAGA,CACE,MAAM,GAAK,OAAO,WAAW,oBAAoB,EACjD,IAAI,OAAS,EACT,OAAS,EACT,SAAW,GACf,eAAe,iBAAiB,aAAe,GAAM,CACnD,SAAW,GAAG,SAAW,EAAE,QAAQ,SAAW,EACzC,WACL,OAAS,EAAE,QAAQ,CAAC,EAAE,QACtB,OAAS,EAAE,QAAQ,CAAC,EAAE,QACxB,EAAG,CAAE,QAAS,EAAK,CAAC,EACpB,eAAe,iBAAiB,WAAa,GAAM,CACjD,GAAI,CAAC,SAAU,OACf,SAAW,GACX,MAAM,EAAI,EAAE,eAAe,CAAC,EACtB,GAAK,EAAE,QAAU,OACjB,GAAK,EAAE,QAAU,OACnB,KAAK,IAAI,EAAE,EAAI,IAAM,KAAK,IAAI,EAAE,EAAI,KAAK,IAAI,EAAE,EAAI,KAAK,KAAK,GAAK,EAAI,EAAI,EAAE,CAClF,EAAG,CAAE,QAAS,EAAK,CAAC,EACpB,eAAe,iBAAiB,cAAe,IAAM,CAAE,SAAW,EAAO,EAAG,CAAE,QAAS,EAAK,CAAC,CAC/F,CAEA,eAAe,cAAc,yBAAyB,GAAG,iBAAiB,QAAS,IAAM,KAAK,cAAc,CAAC,EAC7G,eAAe,cAAc,wBAAwB,GAAG,iBAAiB,QAAS,IAAM,KAAK,EAAE,CAAC,EAChG,eAAe,cAAc,wBAAwB,GAAG,iBAAiB,QAAS,IAAM,KAAK,CAAC,CAAC,EAG/F,eAAe,iBAAiB,QAAU,GAAM,EAC1C,EAAE,SAAW,gBAAkB,EAAE,SAAW,eAAe,cAAc,6BAA6B,IACxG,KAAK,cAAc,CAEvB,CAAC,EAED,SAAS,iBAAiB,UAAY,GAAM,CACtC,eAAe,UAAU,SAAS,QAAQ,IAC1C,EAAE,MAAQ,SAAU,KAAK,cAAc,EAClC,EAAE,MAAQ,YAAa,KAAK,EAAE,EAC9B,EAAE,MAAQ,cAAc,KAAK,CAAC,EACzC,CAAC,EAUD,KAAK,UAAU,iBAAiB,QAAU,GAAM,CAC9C,MAAM,KAAO,EAAE,OAAO,QAAQ,uBAAuB,EACrD,GAAI,CAAC,KAAM,OACX,EAAE,eAAe,EACjB,MAAM,UAAY,KAAK,QAAQ,sBAAsB,EACrD,KAAK,aAAa,WAAW,QAAQ,OAAO,CAC9C,CAAC,CAOH,CAEA,eAAgB,CACd,KAAK,gBAAgB,UAAU,IAAI,QAAQ,EAG3C,SAAS,KAAK,UAAU,OAAO,iBAAiB,EAGhD,MAAM,QAAU,KAAK,gBAAgB,KAAM,GAAM,EAAE,MAAM,UAAY,MAAM,EAC3E,KAAK,0BAA0B,OAAO,CACxC,CAEA,aAAa,QAAS,CACpB,MAAM,eAAiB,KAAK,eAC5B,GAAI,CAAC,gBAAkB,CAAC,KAAK,eAAgB,OAC7C,MAAM,SAAW,SAAW,KAAK,UAAU,cAAc,gCAAgC,GAAG,QAAQ,QAC9F,QAAU,KAAK,eAAe,OAAQ,GAAM,EAAE,QAAQ,cAAgB,MAAM,EAC5E,OAAS,QAAQ,KAAM,GAAM,EAAE,QAAQ,UAAY,QAAQ,GAAK,QAAQ,CAAC,EAC/E,KAAK,kBAAkB,MAAM,EAC7B,eAAe,UAAU,OAAO,QAAQ,EACxC,SAAS,KAAK,UAAU,IAAI,iBAAiB,CAC/C,CASA,oBAAoB,MAAO,CACzB,GAAI,CAAC,MAAO,OACZ,MAAM,iBAAiB,8BAA8B,EAAE,QAAS,IAAO,GAAG,OAAO,CAAC,EAClF,MAAM,WAAa,KAAK,UAAU,cAAc,gCAAgC,EAC5E,CAAC,YAAc,WAAW,QAAQ,UAAY,MAAM,QAAQ,SAChE,KAAK,QAAQ,OAAO,QAAQ,CAAC,CAAE,GAAI,EAAG,SAAW,CAC/C,GAAI,CAAC,IAAK,OACV,MAAM,IAAM,SAAS,cAAc,KAAK,EACxC,IAAI,UAAY,0CAA0C,MAAM,GAChE,MAAM,IAAM,SAAS,cAAc,KAAK,EACxC,IAAI,IAAM,IACV,IAAI,IAAM,GACV,IAAI,YAAY,GAAG,EACnB,MAAM,YAAY,GAAG,CACvB,CAAC,CACH,CAeA,2BAA4B,CAC1B,MAAM,OAAS,yCACf,KAAK,iBAAiB,kCAAkC,EAAE,QAAS,OAAU,CAC3E,MAAM,UAAY,MAAM,cAAc,oCAAoC,EAC1E,GAAI,CAAC,UAAW,OAChB,MAAM,QAAU,MAAM,KAAK,MAAM,iBAAiB,iCAAiC,CAAC,EAE9E,SAAY,QAAW,CAC3B,MAAM,UAAY,MAAM,sBAAsB,EACxC,WAAa,OAAO,sBAAsB,EAChD,UAAU,MAAM,MAAQ,GAAG,WAAW,KAAK,KAC3C,UAAU,MAAM,UAAY,cAAc,WAAW,KAAO,UAAU,IAAI,KAC5E,EAQM,gBAAkB,QAAQ,KAAM,IAAO,GAAG,UAAU,SAAS,MAAM,CAAC,EACpE,OAAU,IAAO,CACrB,UAAU,MAAM,WAAa,OAC7B,SAAS,EAAE,EACX,UAAU,sBAAsB,EAChC,UAAU,MAAM,WAAa,EAC/B,EACI,kBACF,OAAO,eAAe,EAKtB,UAAU,UAAU,IAAI,0CAA0C,GAGpE,QAAQ,QAAS,MAAS,CACnB,KAAK,aAAa,MAAM,GAC7B,KAAK,iBAAiB,QAAU,GAAM,CACpC,EAAE,eAAe,EACjB,QAAQ,QAAS,IAAO,GAAG,UAAU,OAAO,MAAM,CAAC,EACnD,KAAK,UAAU,IAAI,MAAM,EACzB,SAAS,IAAI,EACb,MAAM,KAAO,KAAK,KAClB,WAAW,IAAM,CACf,OAAO,SAAS,KAAO,IACzB,EAAG,GAAG,CACR,CAAC,CACH,CAAC,EAIG,OAAO,eAAmB,KACL,IAAI,eAAe,IAAM,CAC9C,MAAM,OAAS,QAAQ,KAAM,IAAO,GAAG,UAAU,SAAS,MAAM,CAAC,EAC7D,QAAQ,OAAO,MAAM,CAC3B,CAAC,EACc,QAAQ,KAAK,CAEhC,CAAC,CACH,CAQA,mBAAoB,CAClB,KAAK,iBAAiB,6BAA6B,EAAE,QAAS,OAAU,CACtE,MAAM,OAAS,KAAK,cAAc,cAAc,EAChD,GAAI,CAAC,OAAQ,OACb,MAAM,KAAO,IAAM,CACjB,MAAM,UAAY,MAAM,cAAc,qCAAqC,GAAG,aAAe,GAC7F,OAAO,KAAK,MAAM,QAAQ,mBAAoB,UAAW,KAAK,CAChE,EACA,MAAM,iBAAiB,QAAS,IAAI,EACpC,MAAM,iBAAiB,UAAY,GAAM,EACnC,EAAE,MAAQ,SAAW,EAAE,MAAQ,OACjC,EAAE,eAAe,EACjB,KAAK,EAET,CAAC,CACH,CAAC,CACH,CAWA,kBAAmB,CAEjB,MAAM,eADQ,KAAK,cAAc,qBAAqB,GACxB,QAAQ,gBAAkB,SAClD,QAAU,KAAK,iBAAiB,6BAA6B,EAEnE,QAAQ,QAAS,KAAQ,CACvB,IAAI,iBAAiB,QAAS,IAAM,CAClC,MAAM,SAAW,IAAI,QAAQ,cACxB,WACL,QAAQ,QAAS,GAAM,EAAE,UAAU,OAAO,YAAa,IAAM,GAAG,CAAC,EACjE,KAAK,cAAc,IAAI,IAAI,OAAO,QAAQ,CAAC,EAAE,GAAG,KAAK,GAAG,EAC1D,CAAC,CACH,CAAC,EAED,KAAK,iBAAiB,4BAA6B,IAAM,CACvD,QAAQ,QAAS,GAAM,EAAE,UAAU,OAAO,YAAa,EAAE,QAAQ,UAAY,cAAc,CAAC,CAC9F,CAAC,CACH,CAIA,cAAe,CACb,MAAM,MAAQ,KAAK,cAAc,wBAAwB,EACzD,GAAI,CAAC,MAAO,OAEZ,MAAM,MAAQ,IAAM,MAAM,UAAU,IAAI,QAAQ,GAE/C,KAAK,SAAW,MAAM,iBAAiB,uBAAuB,EAAE,QAAS,KAAQ,CAChF,IAAI,iBAAiB,QAAS,IAAM,MAAM,UAAU,OAAO,QAAQ,CAAC,CACtE,CAAC,EACD,MAAM,iBAAiB,wBAAwB,EAAE,QAAS,IAAO,CAC/D,GAAG,iBAAiB,QAAS,KAAK,CACpC,CAAC,EAMD,MAAM,KAAO,MAAM,cAAc,uBAAuB,EACxD,MAAM,iBAAiB,SAAW,GAAM,CACtC,EAAE,eAAe,EACjB,MAAM,GAAK,KAAK,SAAS,GAAG,MACtB,KAAO,KAAK,SAAS,KAAK,MAC1B,QAAU,mBAAmB,mBAAmB,SAAS,KAAK,EAAE,EAChE,KAAO,mBAAmB,GAAG,KAAO,KAAO;AAAA;AAAA,EAAS,EAAE,GAAG,SAAS,IAAI,EAAE,EAC9E,OAAO,SAAS,KAAO,UAAU,mBAAmB,EAAE,CAAC,YAAY,OAAO,SAAS,IAAI,GACvF,MAAM,CACR,CAAC,CACH,CAIA,eAAgB,CACd,KAAK,QAAQ,iBAAiB,UAAU,EAAE,QAAS,KAAQ,CACzD,IAAI,iBAAiB,QAAS,IAAM,KAAK,UAAU,GAAG,CAAC,CACzD,CAAC,CACH,CAEA,oBAAoB,OAAQ,CAC1B,OAAO,eAAe,cAAc,uBAAuB,GAAG,OAAO,CACvE,CAEA,mBAAmB,OAAQ,QAAS,CAClC,KAAK,oBAAoB,MAAM,EAC/B,MAAM,GAAK,SAAS,cAAc,MAAM,EAExC,GAAG,UAAY,gCACf,GAAG,YAAc,SAAW,2CAC5B,OAAO,sBAAsB,WAAY,EAAE,CAC7C,CAEA,MAAM,UAAU,OAAQ,CACtB,MAAM,UAAY,KAAK,SAAS,KAAM,IAAO,GAAG,QAAQ,gBAAkB,MAAM,EAC1E,QAAU,UAAY,KAAK,WAAW,IAAI,SAAS,EAAI,KAG7D,GAFI,CAAC,SAAW,CAAC,QAAQ,WAErB,KAAK,aAAc,OAEvB,MAAM,YAAc,CAAC,EACf,SAAW,CAAC,EACZ,eAAiB,CAAC,EACxB,IAAI,cAAgB,KAKpB,MAAM,UAAY,KAAK,qBACvB,IAAI,UAAY,KAEhB,KAAK,WAAW,QAAQ,CAAC,IAAK,UAAY,CACxC,GAAI,UAAY,UAAW,OACvB,IAAI,cAAa,cAAgB,IAAI,eACzC,MAAM,MAAQ,CAAE,GAAI,OAAO,IAAI,SAAS,EAAG,SAAU,CAAE,EAEnD,IAAI,gBAAe,MAAM,aAAe,OAAO,IAAI,aAAa,GAChE,UAAY,YAAW,UAAY,QACtC,IAAI,UAAY,SAAW,aAAa,KAAK,KAAK,CACrD,CAAC,EAEG,gBACE,UAAW,UAAU,WAAa,CAAE,iBAAkB,aAAc,EACnE,eAAe,gBAAgB,EAAI,eAO1C,MAAM,MAAQ,CACZ,GAAG,SACH,GAAG,YAAY,QAAQ,EACvB,CACE,GAAI,OAAO,QAAQ,SAAS,EAC5B,SAAU,EACV,WAAY,OAAO,KAAK,cAAc,EAAE,OAAS,eAAiB,MACpE,CACF,EAMA,KAAK,aAAe,GACpB,OAAO,aAAa,YAAa,MAAM,EACvC,KAAK,oBAAoB,MAAM,EAC/B,GAAI,CACF,MAAM,IAAM,MAAM,MAAM,GAAG,OAAO,SAAS,QAAQ,MAAQ,GAAG,cAAe,CAC3E,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CAAE,KAAM,CAAC,CAChC,CAAC,EACK,KAAO,MAAM,IAAI,KAAK,EAC5B,GAAI,CAAC,IAAI,GAAI,MAAM,IAAI,MAAM,KAAK,aAAe,KAAK,SAAW,uBAAuB,IAAI,MAAM,EAAE,EACpG,OAAO,SAAS,KAAO,GAAG,OAAO,SAAS,QAAQ,MAAQ,GAAG,MAC/D,OAAS,IAAK,CACZ,QAAQ,MAAM,sCAAuC,GAAG,EACxD,KAAK,mBAAmB,OAAQ,IAAI,OAAO,EAC3C,OAAO,gBAAgB,WAAW,EAClC,KAAK,aAAe,EACtB,CACF,CACF,CAEK,eAAe,IAAI,gBAAgB,GAAG,eAAe,OAAO,iBAAkB,YAAY,CACjG,GAAG",
  "names": ["d", "engAddon", "legacyInput"]
}
