{
  "version": 3,
  "sources": ["/cdn/shop/t/6/assets/trh-fabric-browser.js?cb=20260501a"],
  "sourcesContent": ["/**\n * TRH Fabric Browser - Shared Fabric & Trim Selection Module\n * ===========================================================\n * A self-contained module for browsing, searching, and selecting fabrics and\n * trims from the Shopify Storefront API. Designed to be reused across all\n * made-to-order product calculators (drapery, pillows, furniture, cushions).\n *\n * @module TRHFabricBrowser\n * @version 1.0.0\n *\n * Usage:\n *   TRHFabricBrowser.init({ ...config });\n *   TRHFabricBrowser.getSelectedFabric();\n *   TRHFabricBrowser.selectFabric(fabricObj);\n *\n * Custom Events (dispatched on document):\n *   - trh:fabric-selected   detail: { fabric }\n *   - trh:trim-selected     detail: { trim }\n *   - trh:fabric-cleared\n *   - trh:trim-cleared\n *   - trh:products-loaded   detail: { fabricCount, trimCount }\n */\n(function () {\n  'use strict';\n\n  /* =========================================================================\n   * CONSTANTS\n   * ========================================================================= */\n\n  /** @const {string} CSS class prefix for BEM naming */\n  const PREFIX = 'trh-fb';\n\n  /** @const {string} Shopify Storefront API version */\n  const API_VERSION = '2024-01';\n\n  /** @const {number} Default fabric width in inches when no metafield is found */\n  const DEFAULT_FABRIC_WIDTH = 54;\n\n  /** @const {number} Maximum number of products to fetch from the API */\n  const MAX_PRODUCTS = 250;\n\n  /** @const {number} Maximum search suggestions to display */\n  const MAX_SUGGESTIONS = 10;\n\n  /** @const {number} Debounce delay in milliseconds for search input */\n  const DEBOUNCE_MS = 200;\n\n  /** @const {string[]} Tags that identify a product as fabric */\n  const FABRIC_TAGS = ['cotton', 'linen', 'silk', 'velvet'];\n\n  /** @const {string[]} Terms that exclude a product from fabric results */\n  const FABRIC_EXCLUDE_TERMS = ['leather', 'vinyl', 'pillow', 'cover', 'trim', 'fringe'];\n\n  /** @const {string[]} Terms that exclude a product from trim results */\n  const TRIM_EXCLUDE_TERMS = ['pillow', 'cover'];\n\n  /* =========================================================================\n   * MODULE STATE\n   * ========================================================================= */\n\n  /** @type {Object|null} Current configuration object */\n  let _config = null;\n\n  /** @type {Array<Object>} All loaded fabric products */\n  let _allFabrics = [];\n\n  /** @type {Array<Object>} All loaded trim products */\n  let _allTrims = [];\n\n  /** @type {Object|null} Currently selected fabric */\n  let _selectedFabric = null;\n\n  /** @type {Object|null} Currently selected trim */\n  let _selectedTrim = null;\n\n  /** @type {boolean} Whether products have been loaded from the API */\n  let _productsLoaded = false;\n\n  /** @type {boolean} Whether a product load is currently in progress */\n  let _loadingProducts = false;\n\n  /** @type {Promise|null} Tracks the in-flight loadProducts() promise */\n  let _loadingPromise = null;\n\n  /** @type {Array<Object>} Current search results for zoom navigation */\n  let _currentSearchResults = [];\n\n  /** @type {string|null} Current zoom modal type ('fabric' or 'trim') */\n  let _zoomType = null;\n\n  /** @type {boolean} Flag to prevent dropdown close during \"choose another\" */\n  let _isChoosingAnother = false;\n\n  /** @type {Object} References to dynamically created DOM elements */\n  let _els = {};\n\n  /* =========================================================================\n   * UTILITIES\n   * ========================================================================= */\n\n  /**\n   * Creates a simple debounce wrapper. Uses lodash if available, otherwise\n   * falls back to a minimal inline implementation.\n   * @param {Function} fn - The function to debounce\n   * @param {number} wait - Delay in milliseconds\n   * @returns {Function} Debounced function\n   */\n  function debounce(fn, wait) {\n    if (typeof _ !== 'undefined' && typeof _.debounce === 'function') {\n      return _.debounce(fn, wait);\n    }\n    let timer = null;\n    return function () {\n      var context = this;\n      var args = arguments;\n      clearTimeout(timer);\n      timer = setTimeout(function () {\n        fn.apply(context, args);\n      }, wait);\n    };\n  }\n\n  /**\n   * Normalizes a string for accent-insensitive search comparison.\n   * Strips diacritical marks via Unicode NFD decomposition.\n   * @param {string} str - The string to normalize\n   * @returns {string} Lowercased, accent-stripped string\n   */\n  function normalizeForSearch(str) {\n    return str\n      .normalize('NFD')\n      .replace(/[\\u0300-\\u036f]/g, '')\n      .toLowerCase();\n  }\n\n  /**\n   * Dispatches a custom event on the document.\n   * @param {string} eventName - Event name (e.g., 'trh:fabric-selected')\n   * @param {Object} [detail={}] - Event detail payload\n   */\n  function dispatchEvent(eventName, detail) {\n    document.dispatchEvent(\n      new CustomEvent(eventName, { detail: detail || {} })\n    );\n  }\n\n  /**\n   * Creates a DOM element with optional class name, attributes, and text.\n   * @param {string} tag - HTML tag name\n   * @param {Object} [opts] - Options\n   * @param {string} [opts.className] - CSS class(es)\n   * @param {Object} [opts.attrs] - Key/value attribute pairs\n   * @param {string} [opts.text] - Text content\n   * @param {string} [opts.html] - Inner HTML (use with caution)\n   * @returns {HTMLElement}\n   */\n  function createElement(tag, opts) {\n    var el = document.createElement(tag);\n    if (!opts) return el;\n    if (opts.className) el.className = opts.className;\n    if (opts.attrs) {\n      Object.keys(opts.attrs).forEach(function (key) {\n        el.setAttribute(key, opts.attrs[key]);\n      });\n    }\n    if (opts.text) el.textContent = opts.text;\n    if (opts.html) el.innerHTML = opts.html;\n    return el;\n  }\n\n  /**\n   * Escapes HTML entities to prevent XSS in dynamically generated content.\n   * @param {string} str - Raw string\n   * @returns {string} Escaped string safe for innerHTML\n   */\n  function escapeHtml(str) {\n    if (!str) return '';\n    var div = document.createElement('div');\n    div.appendChild(document.createTextNode(str));\n    return div.innerHTML;\n  }\n\n  /* =========================================================================\n   * STYLES\n   * ========================================================================= */\n\n  /**\n   * Injects the module's scoped CSS into the document head. Uses BEM naming\n   * with the `trh-fb` prefix to avoid collisions with existing styles.\n   * Idempotent - will not inject a second time.\n   */\n  function injectStyles() {\n    if (document.getElementById(PREFIX + '-styles')) return;\n\n    var style = document.createElement('style');\n    style.id = PREFIX + '-styles';\n    style.textContent = [\n      /* Search suggestions dropdown */\n      '.' + PREFIX + '__suggestions {',\n      '  position: absolute;',\n      '  top: 100%;',\n      '  left: 0;',\n      '  right: 0;',\n      '  background: #fff;',\n      '  border: 1px solid #ddd;',\n      '  border-top: none;',\n      '  border-radius: 0 0 6px 6px;',\n      '  max-height: 350px;',\n      '  overflow-y: auto;',\n      '  z-index: 1000;',\n      '  display: none;',\n      '  box-shadow: 0 4px 12px rgba(0,0,0,.15);',\n      '}',\n      '.' + PREFIX + '__suggestions--active {',\n      '  display: block;',\n      '  z-index: 1001;',\n      '}',\n\n      /* Individual suggestion row */\n      '.' + PREFIX + '__suggestion {',\n      '  padding: 12px;',\n      '  cursor: pointer;',\n      '  border-bottom: 1px solid #eee;',\n      '  font-size: 14px;',\n      '  display: flex;',\n      '  align-items: center;',\n      '  gap: 10px;',\n      '  transition: background .2s;',\n      '  min-height: 50px;',\n      '  position: relative;',\n      '}',\n      '.' + PREFIX + '__suggestion:hover,',\n      '.' + PREFIX + '__suggestion--selected {',\n      '  background: #f0f8ff;',\n      '  color: #007cba;',\n      '}',\n      '.' + PREFIX + '__suggestion:last-child {',\n      '  border-bottom: none;',\n      '}',\n\n      /* Suggestion image thumbnail */\n      '.' + PREFIX + '__suggestion-image {',\n      '  width: 40px;',\n      '  height: 40px;',\n      '  object-fit: cover;',\n      '  border-radius: 4px;',\n      '  border: 1px solid #ddd;',\n      '  flex-shrink: 0;',\n      '  background: #f3f4f6;',\n      '  cursor: zoom-in;',\n      '}',\n\n      /* Suggestion text details */\n      '.' + PREFIX + '__suggestion-details {',\n      '  flex: 1;',\n      '  min-width: 0;',\n      '}',\n      '.' + PREFIX + '__suggestion-name {',\n      '  font-weight: 500;',\n      '  margin: 0 0 2px 0;',\n      '  white-space: nowrap;',\n      '  overflow: hidden;',\n      '  text-overflow: ellipsis;',\n      '}',\n      '.' + PREFIX + '__suggestion-price {',\n      '  font-size: 12px;',\n      '  color: #666;',\n      '  margin: 0;',\n      '}',\n\n      /* No results message */\n      '.' + PREFIX + '__no-results {',\n      '  padding: 12px;',\n      '  color: #666;',\n      '  font-style: italic;',\n      '  text-align: center;',\n      '}',\n\n      /* Swatch preview */\n      '.' + PREFIX + '__swatch-preview {',\n      '  display: none;',\n      '  margin-top: 5px;',\n      '  align-items: center;',\n      '  gap: 6px;',\n      '  position: relative;',\n      '}',\n      '.' + PREFIX + '__swatch-preview--visible {',\n      '  display: flex;',\n      '}',\n      '.' + PREFIX + '__swatch-image {',\n      '  width: 40px;',\n      '  height: 40px;',\n      '  object-fit: cover;',\n      '  border-radius: 4px;',\n      '  border: 1px solid #ddd;',\n      '  background: #f3f4f6;',\n      '}',\n      '.' + PREFIX + '__swatch-image--clickable {',\n      '  cursor: zoom-in;',\n      '}',\n      '.' + PREFIX + '__swatch-price {',\n      '  font-size: 12px;',\n      '  color: #666;',\n      '  margin: 0;',\n      '}',\n      '.' + PREFIX + '__swatch-clear {',\n      '  background: transparent;',\n      '  border: none;',\n      '  outline: none;',\n      '  box-shadow: none;',\n      '  cursor: pointer;',\n      '  font-size: 18px;',\n      '  color: #999;',\n      '  display: none;',\n      '  transition: color .2s;',\n      '  line-height: 1;',\n      '  padding: 0;',\n      '  margin: 0 0 0 8px;',\n      '  font-weight: normal;',\n      '  width: auto;',\n      '  height: auto;',\n      '  min-width: unset;',\n      '  min-height: unset;',\n      '}',\n      '.' + PREFIX + '__swatch-clear--visible {',\n      '  display: inline-block;',\n      '}',\n      '.' + PREFIX + '__swatch-clear:hover {',\n      '  color: #666;',\n      '}',\n\n      /* Zoom modal */\n      '.' + PREFIX + '__zoom-overlay {',\n      '  display: none;',\n      '  position: fixed;',\n      '  top: 0;',\n      '  left: 0;',\n      '  right: 0;',\n      '  bottom: 0;',\n      '  background: rgba(0,0,0,.9);',\n      '  z-index: 10000;',\n      '  justify-content: center;',\n      '  align-items: center;',\n      '  cursor: pointer;',\n      '}',\n      '.' + PREFIX + '__zoom-overlay--visible {',\n      '  display: flex;',\n      '}',\n      '.' + PREFIX + '__zoom-content {',\n      '  background: #fff;',\n      '  border-radius: 12px;',\n      '  padding: 20px;',\n      '  max-width: 80vw;',\n      '  max-height: 80vh;',\n      '  text-align: center;',\n      '  position: relative;',\n      '  overflow-y: auto;',\n      '  cursor: default;',\n      '}',\n      '.' + PREFIX + '__zoom-close {',\n      '  position: absolute;',\n      '  top: 10px;',\n      '  right: 15px;',\n      '  font-size: 32px;',\n      '  font-weight: bold;',\n      '  cursor: pointer;',\n      '  color: #fff;',\n      '  background: rgba(0,0,0,0.6);',\n      '  width: 40px;',\n      '  height: 40px;',\n      '  border-radius: 50%;',\n      '  display: flex;',\n      '  align-items: center;',\n      '  justify-content: center;',\n      '  z-index: 100;',\n      '  transition: background .2s, transform .2s;',\n      '  border: none;',\n      '  line-height: 1;',\n      '}',\n      '.' + PREFIX + '__zoom-close:hover {',\n      '  background: rgba(0,0,0,0.8);',\n      '  transform: scale(1.1);',\n      '}',\n      '.' + PREFIX + '__zoom-image {',\n      '  max-width: 400px;',\n      '  max-height: 400px;',\n      '  object-fit: contain;',\n      '  border-radius: 8px;',\n      '}',\n      '.' + PREFIX + '__zoom-title {',\n      '  font-size: 18px;',\n      '  font-weight: bold;',\n      '  margin: 10px 0;',\n      '}',\n      '.' + PREFIX + '__zoom-price {',\n      '  font-size: 14px;',\n      '  color: #666;',\n      '  margin-bottom: 10px;',\n      '}',\n      '.' + PREFIX + '__zoom-actions {',\n      '  display: flex;',\n      '  justify-content: center;',\n      '  gap: 8px;',\n      '  flex-wrap: wrap;',\n      '}',\n      '.' + PREFIX + '__zoom-btn {',\n      '  background: #28a745;',\n      '  color: #fff;',\n      '  border: none;',\n      '  padding: 8px 16px;',\n      '  border-radius: 6px;',\n      '  font-size: 14px;',\n      '  cursor: pointer;',\n      '  margin: 5px;',\n      '  transition: background .2s, transform .2s, box-shadow .2s;',\n      '}',\n      '.' + PREFIX + '__zoom-btn:hover {',\n      '  background: #218838;',\n      '  transform: scale(1.05);',\n      '  box-shadow: 0 4px 8px rgba(0,0,0,.15);',\n      '}',\n      '.' + PREFIX + '__zoom-btn:active {',\n      '  transform: scale(1);',\n      '  box-shadow: 0 2px 4px rgba(0,0,0,.1);',\n      '}',\n      '.' + PREFIX + '__zoom-btn--secondary {',\n      '  background: #6c757d;',\n      '}',\n      '.' + PREFIX + '__zoom-btn--secondary:hover {',\n      '  background: #5a6268;',\n      '}',\n      '.' + PREFIX + '__zoom-nav {',\n      '  position: absolute;',\n      '  top: 50%;',\n      '  transform: translateY(-50%);',\n      '  background: #6c757d;',\n      '  color: #fff;',\n      '  border: none;',\n      '  padding: 8px 12px;',\n      '  border-radius: 6px;',\n      '  font-size: 14px;',\n      '  cursor: pointer;',\n      '  transition: background .2s, transform .2s, box-shadow .2s;',\n      '}',\n      '.' + PREFIX + '__zoom-nav:hover:not(:disabled) {',\n      '  background: #5a6268;',\n      '  transform: translateY(-50%) scale(1.05);',\n      '  box-shadow: 0 4px 8px rgba(0,0,0,.15);',\n      '}',\n      '.' + PREFIX + '__zoom-nav:active:not(:disabled) {',\n      '  transform: translateY(-50%) scale(1);',\n      '  box-shadow: 0 2px 4px rgba(0,0,0,.1);',\n      '}',\n      '.' + PREFIX + '__zoom-nav:disabled {',\n      '  opacity: 0.5;',\n      '  cursor: not-allowed;',\n      '  background: #6c757d;',\n      '}',\n      '.' + PREFIX + '__zoom-nav--prev {',\n      '  left: 10px;',\n      '}',\n      '.' + PREFIX + '__zoom-nav--next {',\n      '  right: 10px;',\n      '}',\n\n      /* Catalog styles are inside blob iframe \u2014 see buildCatalogHTML() */\n\n      /* Search input selected state */\n      '.' + PREFIX + '__search-input--selected {',\n      '  background: #f8f9fa;',\n      '  border-color: #28a745;',\n      '}',\n\n      /* Responsive adjustments */\n      '@media (max-width: 400px) {',\n      '  .' + PREFIX + '__suggestion { padding: 8px; min-height: 45px; }',\n      '  .' + PREFIX + '__suggestion-image,',\n      '  .' + PREFIX + '__swatch-image { width: 35px; height: 35px; }',\n      '  .' + PREFIX + '__zoom-image { max-width: 250px; max-height: 250px; }',\n      '  .' + PREFIX + '__zoom-nav { padding: 6px 10px; font-size: 12px; }',\n      '}'\n    ].join('\\n');\n\n    document.head.appendChild(style);\n  }\n\n  /* =========================================================================\n   * DOM CREATION\n   * ========================================================================= */\n\n  /**\n   * Creates and injects the zoom modal into the DOM. The zoom modal displays\n   * a large preview of a fabric/trim with navigation and selection controls.\n   * @returns {HTMLElement} The zoom modal overlay element\n   */\n  function createZoomModal() {\n    var overlay = createElement('div', {\n      className: PREFIX + '__zoom-overlay',\n      attrs: { id: PREFIX + '-zoom-overlay', role: 'dialog', 'aria-modal': 'true', 'aria-label': 'Product zoom view' }\n    });\n\n    var content = createElement('div', { className: PREFIX + '__zoom-content' });\n\n    var closeBtn = createElement('button', {\n      className: PREFIX + '__zoom-close',\n      attrs: { type: 'button', 'aria-label': 'Close zoom view' },\n      html: '&times;'\n    });\n\n    var img = createElement('img', {\n      className: PREFIX + '__zoom-image',\n      attrs: { id: PREFIX + '-zoom-image', alt: '' }\n    });\n\n    var title = createElement('div', {\n      className: PREFIX + '__zoom-title',\n      attrs: { id: PREFIX + '-zoom-title' }\n    });\n\n    var price = createElement('div', {\n      className: PREFIX + '__zoom-price',\n      attrs: { id: PREFIX + '-zoom-price' }\n    });\n\n    var actions = createElement('div', { className: PREFIX + '__zoom-actions' });\n\n    var selectBtn = createElement('button', {\n      className: PREFIX + '__zoom-btn',\n      attrs: { type: 'button', id: PREFIX + '-zoom-select' },\n      text: 'Select This'\n    });\n\n    var chooseBtn = createElement('button', {\n      className: PREFIX + '__zoom-btn ' + PREFIX + '__zoom-btn--secondary',\n      attrs: { type: 'button', id: PREFIX + '-zoom-choose-another' },\n      text: 'Choose Another'\n    });\n\n    actions.appendChild(selectBtn);\n    actions.appendChild(chooseBtn);\n\n    var prevBtn = createElement('button', {\n      className: PREFIX + '__zoom-nav ' + PREFIX + '__zoom-nav--prev',\n      attrs: { type: 'button', id: PREFIX + '-zoom-prev' },\n      text: 'Previous'\n    });\n\n    var nextBtn = createElement('button', {\n      className: PREFIX + '__zoom-nav ' + PREFIX + '__zoom-nav--next',\n      attrs: { type: 'button', id: PREFIX + '-zoom-next' },\n      text: 'Next'\n    });\n\n    content.appendChild(closeBtn);\n    content.appendChild(img);\n    content.appendChild(title);\n    content.appendChild(price);\n    content.appendChild(actions);\n    content.appendChild(prevBtn);\n    content.appendChild(nextBtn);\n    overlay.appendChild(content);\n\n    _els.zoomOverlay = overlay;\n    _els.zoomImage = img;\n    _els.zoomTitle = title;\n    _els.zoomPrice = price;\n    _els.zoomSelectBtn = selectBtn;\n    _els.zoomChooseBtn = chooseBtn;\n    _els.zoomPrevBtn = prevBtn;\n    _els.zoomNextBtn = nextBtn;\n    _els.zoomCloseBtn = closeBtn;\n\n    return overlay;\n  }\n\n  /**\n   * Creates the catalog lightbox overlay with an iframe. The catalog content\n   * is loaded via a blob URL (see buildCatalogHTML), giving perfect CSS\n   * isolation from the Craft theme.\n   * @param {string} type - 'fabric' or 'trim'\n   * @returns {HTMLElement} The overlay element (appended to document.body)\n   */\n  function createCatalogModal(type) {\n    /* Overlay \u2014 simple fixed backdrop with centered iframe */\n    var overlay = document.createElement('div');\n    overlay.id = PREFIX + '-catalog-overlay-' + type;\n    overlay.style.cssText = 'display:none;position:fixed;top:0;left:0;right:0;bottom:0;' +\n      'background:rgba(0,0,0,.85);z-index:10000;justify-content:center;align-items:center;';\n    overlay.setAttribute('role', 'dialog');\n    overlay.setAttribute('aria-modal', 'true');\n\n    /* Iframe \u2014 blob URL gives us perfect CSS isolation from Craft theme */\n    var iframe = document.createElement('iframe');\n    iframe.id = PREFIX + '-catalog-iframe-' + type;\n    iframe.style.cssText = 'width:94vw;max-width:1200px;height:90vh;border:none;' +\n      'border-radius:12px;background:#fff;';\n    iframe.setAttribute('sandbox', 'allow-scripts');\n\n    overlay.appendChild(iframe);\n\n    if (type === 'fabric') {\n      _els.catalogFabricOverlay = overlay;\n      _els.catalogFabricIframe = iframe;\n    } else {\n      _els.catalogTrimOverlay = overlay;\n      _els.catalogTrimIframe = iframe;\n    }\n\n    return overlay;\n  }\n\n  /**\n   * Builds a self-contained HTML document for the catalog iframe.\n   * Includes search, product type filter, sort, and a card grid.\n   * Communication with the parent page is via postMessage.\n   * @param {string} type - 'fabric' or 'trim'\n   * @param {Array<Object>} products - Product items to display\n   * @returns {string} Complete HTML document string\n   */\n  function buildCatalogHTML(type, products) {\n    var label = type === 'fabric' ? 'Fabric' : 'Trim';\n\n    /* Serialize product data for embedding in the iframe script */\n    var itemsJSON = JSON.stringify(products.map(function (p) {\n      return {\n        id: p.id, title: p.title, price: p.price,\n        imageUrl: p.imageUrl, altText: p.altText || p.title,\n        fabricWidth: p.fabricWidth || null,\n        tags: p.tags || [], productType: p.productType || ''\n      };\n    }));\n\n    /* Build product type filter options */\n    var typeSet = {};\n    products.forEach(function (p) { if (p.productType) typeSet[p.productType] = true; });\n    var typeOpts = Object.keys(typeSet).sort().map(function (t) {\n      return '<option value=\"' + t.replace(/\"/g, '&quot;') + '\">' + t + '</option>';\n    }).join('');\n\n    return [\n      '<!DOCTYPE html><html><head><meta charset=\"utf-8\">',\n      '<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">',\n      '<style>',\n      '*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}',\n      'body{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,sans-serif;background:#fff;color:#333}',\n      '.main{display:flex;flex-direction:column;height:100vh}',\n      '.header{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;border-bottom:1px solid #e0e0e0}',\n      '.title{font-family:\"Times New Roman\",Garamond,serif;font-size:22px;font-weight:600;letter-spacing:1px;text-transform:uppercase}',\n      '.close-btn{font-size:28px;cursor:pointer;color:#333;background:none;width:36px;height:36px;',\n      '  border-radius:50%;display:flex;align-items:center;justify-content:center;border:none;line-height:1}',\n      '.close-btn:hover{background:#f0f0f0}',\n      '.toolbar{display:flex;gap:12px;padding:14px 24px;border-bottom:1px solid #e0e0e0;flex-wrap:wrap;align-items:center}',\n      '.search-input{flex:1;min-width:200px;padding:10px 14px;border:1px solid #ccc;border-radius:6px;font-size:15px;outline:none}',\n      '.search-input:focus{border-color:#888}',\n      '.filter-select{padding:8px 12px;border:1px solid #ccc;border-radius:6px;font-size:14px;background:#fff;cursor:pointer}',\n      '.count{padding:8px 24px;font-size:13px;color:#888}',\n      '.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(175px,1fr));gap:16px;padding:16px 24px 24px;overflow-y:auto;flex:1}',\n      '.card{border:1px solid #e0e0e0;border-radius:8px;overflow:hidden;cursor:pointer;',\n      '  transition:border-color .2s,box-shadow .2s;background:#fff}',\n      '.card:hover{border-color:#888;box-shadow:0 3px 10px rgba(0,0,0,.12)}',\n      '.card-img{width:100%;aspect-ratio:1/1;object-fit:cover;display:block;background:#f0f0f0}',\n      '.card-info{padding:10px}',\n      '.card-name{font-size:13px;font-weight:600;line-height:1.3;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',\n      '.card-price{font-size:13px;color:#666}',\n      '.empty{grid-column:1/-1;text-align:center;padding:60px 20px;color:#888;font-size:16px}',\n      '@media(max-width:600px){.grid{grid-template-columns:repeat(2,1fr);gap:10px;padding:10px}.toolbar{padding:10px 14px}}',\n      '</style></head><body><div class=\"main\">',\n      '<div class=\"header\">',\n      '  <div class=\"title\">' + label + ' Collection</div>',\n      '  <button class=\"close-btn\" id=\"closeBtn\">&times;</button>',\n      '</div>',\n      '<div class=\"toolbar\">',\n      '  <input type=\"text\" class=\"search-input\" id=\"searchInput\" placeholder=\"Search ' + type + 's by name...\">',\n      '  <select class=\"filter-select\" id=\"typeFilter\"><option value=\"\">All Types</option>' + typeOpts + '</select>',\n      '  <select class=\"filter-select\" id=\"sortSelect\">',\n      '    <option value=\"name-asc\">Name A\\u2013Z</option>',\n      '    <option value=\"name-desc\">Name Z\\u2013A</option>',\n      '    <option value=\"price-asc\">Price: Low\\u2013High</option>',\n      '    <option value=\"price-desc\">Price: High\\u2013Low</option>',\n      '  </select>',\n      '</div>',\n      '<div class=\"count\" id=\"count\"></div>',\n      '<div class=\"grid\" id=\"grid\"></div>',\n      '</div>',\n      '<script>',\n      'var items=' + itemsJSON + ';',\n      'var itemType=\"' + type + '\";',\n      'function esc(s){var d=document.createElement(\"div\");d.textContent=s;return d.innerHTML}',\n      'function render(){',\n      '  var term=document.getElementById(\"searchInput\").value.toLowerCase().trim();',\n      '  var typeVal=document.getElementById(\"typeFilter\").value;',\n      '  var sortVal=document.getElementById(\"sortSelect\").value;',\n      '  var filtered=items.filter(function(p){',\n      '    if(term&&p.title.toLowerCase().indexOf(term)===-1)return false;',\n      '    if(typeVal&&p.productType!==typeVal)return false;',\n      '    return true;',\n      '  });',\n      '  filtered.sort(function(a,b){',\n      '    switch(sortVal){',\n      '      case\"name-desc\":return b.title.localeCompare(a.title);',\n      '      case\"price-asc\":return a.price-b.price;',\n      '      case\"price-desc\":return b.price-a.price;',\n      '      default:return a.title.localeCompare(b.title);',\n      '    }',\n      '  });',\n      '  document.getElementById(\"count\").textContent=\"Showing \"+filtered.length+\" of \"+items.length+\" \"+itemType+(items.length!==1?\"s\":\"\");',\n      '  var grid=document.getElementById(\"grid\");',\n      '  grid.innerHTML=\"\";',\n      '  if(filtered.length===0){grid.innerHTML=\\'<div class=\"empty\">No \\'+itemType+\\'s found.</div>\\';return}',\n      '  var frag=document.createDocumentFragment();',\n      '  filtered.forEach(function(item){',\n      '    var card=document.createElement(\"div\");card.className=\"card\";',\n      '    card.innerHTML=\\'<img class=\"card-img\" src=\"\\'+esc(item.imageUrl)+\\'\" alt=\"\\'+esc(item.altText)+\\'\" loading=\"lazy\">\\'',\n      '      +\\'<div class=\"card-info\"><div class=\"card-name\">\\'+esc(item.title)+\\'</div>\\'',\n      '      +\\'<div class=\"card-price\">$\\'+item.price.toFixed(2)+\\'/yd</div></div>\\';',\n      '    card.addEventListener(\"click\",function(){',\n      '      parent.postMessage({type:\"trhCatalogSelect\",itemType:itemType,item:item},\"*\");',\n      '    });',\n      '    frag.appendChild(card);',\n      '  });',\n      '  grid.appendChild(frag);',\n      '}',\n      'var _t;document.getElementById(\"searchInput\").addEventListener(\"input\",function(){clearTimeout(_t);_t=setTimeout(render,200)});',\n      'document.getElementById(\"typeFilter\").addEventListener(\"change\",render);',\n      'document.getElementById(\"sortSelect\").addEventListener(\"change\",render);',\n      'document.getElementById(\"closeBtn\").addEventListener(\"click\",function(){parent.postMessage({type:\"trhCatalogClose\"},\"*\")});',\n      'document.addEventListener(\"keydown\",function(e){if(e.key===\"Escape\")parent.postMessage({type:\"trhCatalogClose\"},\"*\")});',\n      'render();',\n      'setTimeout(function(){document.getElementById(\"searchInput\").focus()},100);',\n      '<\\/script></body></html>'\n    ].join('\\n');\n  }\n\n  /**\n   * Creates a swatch preview element for displaying the selected item\n   * inline below the search field.\n   * @param {string} type - 'fabric' or 'trim'\n   * @returns {HTMLElement} The swatch preview container\n   */\n  function createSwatchPreview(type) {\n    var container = createElement('div', {\n      className: PREFIX + '__swatch-preview',\n      attrs: { id: PREFIX + '-swatch-' + type }\n    });\n\n    var img = createElement('img', {\n      className: PREFIX + '__swatch-image',\n      attrs: { id: PREFIX + '-swatch-image-' + type, alt: '' }\n    });\n\n    var infoWrap = createElement('div', {\n      attrs: { style: 'display: flex; align-items: center; gap: 0; flex: 1;' }\n    });\n\n    var price = createElement('span', {\n      className: PREFIX + '__swatch-price',\n      attrs: { id: PREFIX + '-swatch-price-' + type }\n    });\n\n    var clearBtn = createElement('button', {\n      className: PREFIX + '__swatch-clear',\n      attrs: {\n        type: 'button',\n        id: PREFIX + '-swatch-clear-' + type,\n        title: 'Clear selection',\n        'aria-label': 'Clear ' + type + ' selection'\n      },\n      html: '&times;'\n    });\n\n    infoWrap.appendChild(price);\n    infoWrap.appendChild(clearBtn);\n    container.appendChild(img);\n    container.appendChild(infoWrap);\n\n    if (type === 'fabric') {\n      _els.fabricSwatch = container;\n      _els.fabricSwatchImage = img;\n      _els.fabricSwatchPrice = price;\n      _els.fabricSwatchClear = clearBtn;\n    } else {\n      _els.trimSwatch = container;\n      _els.trimSwatchImage = img;\n      _els.trimSwatchPrice = price;\n      _els.trimSwatchClear = clearBtn;\n    }\n\n    return container;\n  }\n\n  /* =========================================================================\n   * GRAPHQL - PRODUCT LOADING\n   * ========================================================================= */\n\n  /**\n   * Builds the GraphQL query for fetching products with width metafields.\n   * Queries all six known metafield namespace/key combinations for fabric width.\n   * @returns {string} The GraphQL query string\n   */\n  function buildProductQuery() {\n    var productFields = [\n      '        id title productType availableForSale tags',\n      '        collections(first: 10) { edges { node { title } } }',\n      '        widthCustom: metafield(namespace: \"custom\", key: \"width\") { value }',\n      '        widthFabric: metafield(namespace: \"fabric\", key: \"width\") { value }',\n      '        widthTextiles: metafield(namespace: \"textiles\", key: \"width\") { value }',\n      '        widthCategory: metafield(namespace: \"category\", key: \"width\") { value }',\n      '        widthEmpty: metafield(key: \"width\") { value }',\n      '        widthCapital: metafield(key: \"Width\") { value }',\n      '        variants(first: 100) {',\n      '          edges {',\n      '            node {',\n      '              id title price { amount } availableForSale',\n      '              image {',\n      '                url(transform: { maxWidth: 800, maxHeight: 800 })',\n      '                altText',\n      '              }',\n      '            }',\n      '          }',\n      '        }',\n      '        images(first: 1) {',\n      '          edges { node { url(transform: { maxWidth: 800, maxHeight: 800 }) altText } }',\n      '        }'\n    ].join('\\n');\n\n    return [\n      'query getAllProducts($first: Int!) {',\n      '  products(first: $first) {',\n      '    edges {',\n      '      node {',\n      productFields,\n      '      }',\n      '    }',\n      '  }',\n      '  trimCollection: collection(handle: \"trim\") {',\n      '    products(first: 100) {',\n      '      edges {',\n      '        node {',\n      productFields,\n      '        }',\n      '      }',\n      '    }',\n      '  }',\n      '}'\n    ].join('\\n');\n  }\n\n  /**\n   * Builds the GraphQL query for fetching a single product's width metafield\n   * and variant IDs. Used when a fabric is selected from the catalog popup.\n   * @returns {string} The GraphQL query string\n   */\n  function buildSingleProductQuery() {\n    return [\n      'query getProduct($id: ID!) {',\n      '  product(id: $id) {',\n      '    widthCustom: metafield(namespace: \"custom\", key: \"width\") { value }',\n      '    widthFabric: metafield(namespace: \"fabric\", key: \"width\") { value }',\n      '    widthTextiles: metafield(namespace: \"textiles\", key: \"width\") { value }',\n      '    widthCategory: metafield(namespace: \"category\", key: \"width\") { value }',\n      '    widthEmpty: metafield(key: \"width\") { value }',\n      '    widthCapital: metafield(key: \"Width\") { value }',\n      '    variants(first: 1) {',\n      '      edges { node { id } }',\n      '    }',\n      '  }',\n      '}'\n    ].join('\\n');\n  }\n\n  /**\n   * Resolves fabric width from an array of metafield values. Returns the first\n   * non-null numeric value, or the default width.\n   * @param {Array<string|null>} sources - Array of metafield value strings\n   * @returns {number} Resolved fabric width in inches\n   */\n  function resolveWidth(sources) {\n    for (var i = 0; i < sources.length; i++) {\n      if (sources[i] != null) {\n        var parsed = parseFloat(sources[i]);\n        if (!isNaN(parsed) && parsed > 0) return parsed;\n      }\n    }\n    return DEFAULT_FABRIC_WIDTH;\n  }\n\n  /**\n   * Extracts width metafield sources from a product node.\n   * @param {Object} node - GraphQL product node\n   * @returns {Array<string|null>} Array of metafield values\n   */\n  function extractWidthSources(node) {\n    return [\n      node.widthCustom ? node.widthCustom.value : null,\n      node.widthFabric ? node.widthFabric.value : null,\n      node.widthTextiles ? node.widthTextiles.value : null,\n      node.widthCategory ? node.widthCategory.value : null,\n      node.widthEmpty ? node.widthEmpty.value : null,\n      node.widthCapital ? node.widthCapital.value : null\n    ];\n  }\n\n  /**\n   * Determines whether a product node qualifies as a fabric.\n   * A product is a fabric if it has a 'fabric' tag/type/title or has one of\n   * the recognized textile tags (cotton, linen, silk, velvet), AND is not\n   * excluded by leather/vinyl/pillow/cover terms.\n   * @param {Object} node - GraphQL product node\n   * @returns {boolean}\n   */\n  function isFabricProduct(node) {\n    var t = node.title.toLowerCase();\n    var pt = node.productType.toLowerCase();\n    var tg = node.tags.map(function (g) { return g.toLowerCase(); });\n    var collectionNames = node.collections.edges.map(function (c) {\n      return c.node.title.toLowerCase();\n    });\n\n    var isFabric = tg.indexOf('fabric') !== -1 ||\n      pt.indexOf('fabric') !== -1 ||\n      t.indexOf('fabric') !== -1 ||\n      tg.some(function (g) { return FABRIC_TAGS.indexOf(g) !== -1; });\n\n    if (!isFabric) return false;\n\n    var isExcluded = FABRIC_EXCLUDE_TERMS.some(function (term) {\n      return t.indexOf(term) !== -1 ||\n        tg.indexOf(term) !== -1 ||\n        collectionNames.some(function (c) { return c.indexOf(term) !== -1; });\n    });\n\n    return !isExcluded;\n  }\n\n  /**\n   * Determines whether a product node qualifies as a trim.\n   * @param {Object} node - GraphQL product node\n   * @returns {boolean}\n   */\n  function isTrimProduct(node) {\n    var t = node.title.toLowerCase();\n    var pt = node.productType.toLowerCase();\n    var tg = node.tags.map(function (g) { return g.toLowerCase(); });\n\n    var isTrim = tg.indexOf('trim') !== -1 ||\n      pt.indexOf('trim') !== -1 ||\n      t.indexOf('trim') !== -1;\n\n    if (!isTrim) return false;\n\n    var isExcluded = TRIM_EXCLUDE_TERMS.some(function (term) {\n      return t.indexOf(term) !== -1;\n    });\n\n    return !isExcluded;\n  }\n\n  /**\n   * Extracts a variant ID number from a full Shopify GID string.\n   * @param {string} fullId - e.g., \"gid://shopify/ProductVariant/123456\"\n   * @returns {string} Numeric variant ID, e.g., \"123456\"\n   */\n  function extractVariantId(fullId) {\n    var str = String(fullId);\n    return str.indexOf('/') !== -1 ? str.split('/').pop() : str;\n  }\n\n  /**\n   * Maps a product node and its variants into an array of selectable items.\n   * @param {Object} node - GraphQL product node\n   * @param {boolean} includeFabricWidth - Whether to include fabricWidth property\n   * @returns {Array<Object>} Array of product items\n   */\n  function mapVariantsToItems(node, includeFabricWidth) {\n    var mainImage = node.images.edges[0] ? node.images.edges[0].node : null;\n    var fabricWidth = includeFabricWidth ? resolveWidth(extractWidthSources(node)) : null;\n\n    return node.variants.edges\n      .filter(function (v) { return v.node.availableForSale; })\n      .map(function (v) {\n        var variantTitle = v.node.title && v.node.title !== node.title ? v.node.title : '';\n        var displayTitle = variantTitle ? node.title + ' - ' + variantTitle : node.title;\n\n        var variantImage = v.node.image;\n        var imageUrl = (variantImage ? variantImage.url : null) || (mainImage ? mainImage.url : null);\n        var altText = (variantImage ? variantImage.altText : null) ||\n          (mainImage ? mainImage.altText : null) || node.title;\n\n        if (!imageUrl) return null;\n\n        var item = {\n          id: extractVariantId(v.node.id),\n          title: displayTitle,\n          price: parseFloat(v.node.price.amount),\n          imageUrl: imageUrl,\n          altText: altText,\n          tags: node.tags || [],\n          productType: node.productType || ''\n        };\n\n        if (includeFabricWidth) {\n          item.fabricWidth = fabricWidth;\n        }\n\n        return item;\n      })\n      .filter(Boolean);\n  }\n\n  /**\n   * Fetches all products from the Shopify Storefront API and categorizes them\n   * into fabrics and trims. Stores results in module state.\n   * @returns {Promise<void>}\n   */\n  async function loadProducts() {\n    if (_loadingProducts) return _loadingPromise;\n    if (!_config) {\n      console.error('[TRHFabricBrowser] Cannot load products: not initialized.');\n      return Promise.resolve();\n    }\n\n    _loadingProducts = true;\n    _loadingPromise = _doLoadProducts();\n    return _loadingPromise;\n  }\n\n  async function _doLoadProducts() {\n\n    try {\n      var response = await fetch(\n        'https://' + _config.storeDomain + '/api/' + API_VERSION + '/graphql.json',\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            'X-Shopify-Storefront-Access-Token': _config.storefrontToken\n          },\n          body: JSON.stringify({\n            query: buildProductQuery(),\n            variables: { first: MAX_PRODUCTS }\n          })\n        }\n      );\n\n      var data = await response.json();\n\n      if (!data.data || !data.data.products) {\n        console.error('[TRHFabricBrowser] Unexpected API response:', data);\n        return;\n      }\n\n      var edges = data.data.products.edges;\n\n      _allFabrics = [];\n      _allTrims = [];\n\n      // Track product IDs to avoid duplicates when merging collections\n      var seenTrimIds = {};\n\n      edges.forEach(function (edge) {\n        var node = edge.node;\n\n        if (isFabricProduct(node)) {\n          var items = mapVariantsToItems(node, true);\n          _allFabrics = _allFabrics.concat(items);\n        }\n\n        if (isTrimProduct(node)) {\n          var trimItems = mapVariantsToItems(node, false);\n          _allTrims = _allTrims.concat(trimItems);\n          seenTrimIds[node.id] = true;\n        }\n      });\n\n      // Also load trims from the dedicated trim collection to ensure\n      // all trim products are included even if beyond the 250 product limit\n      if (data.data.trimCollection && data.data.trimCollection.products) {\n        data.data.trimCollection.products.edges.forEach(function (edge) {\n          var node = edge.node;\n          if (!seenTrimIds[node.id]) {\n            var trimItems = mapVariantsToItems(node, false);\n            _allTrims = _allTrims.concat(trimItems);\n            seenTrimIds[node.id] = true;\n          }\n        });\n      }\n\n      _productsLoaded = true;\n\n      console.log(\n        '[TRHFabricBrowser] Loaded ' + _allFabrics.length + ' fabrics, ' +\n        _allTrims.length + ' trims'\n      );\n\n      dispatchEvent('trh:products-loaded', {\n        fabricCount: _allFabrics.length,\n        trimCount: _allTrims.length\n      });\n    } catch (err) {\n      console.error('[TRHFabricBrowser] loadProducts error:', err);\n    } finally {\n      _loadingProducts = false;\n    }\n  }\n\n  /**\n   * Fetches the width metafield and variant ID for a single product.\n   * Used when a fabric is selected from the catalog popup and we need\n   * metafield data that wasn't included in the postMessage payload.\n   * @param {string} productId - Numeric Shopify product ID\n   * @returns {Promise<Object>} Object with { fabricWidth, variantId }\n   */\n  async function fetchProductMetafields(productId) {\n    var result = { fabricWidth: DEFAULT_FABRIC_WIDTH, variantId: null };\n\n    try {\n      var response = await fetch(\n        'https://' + _config.storeDomain + '/api/' + API_VERSION + '/graphql.json',\n        {\n          method: 'POST',\n          headers: {\n            'Content-Type': 'application/json',\n            'X-Shopify-Storefront-Access-Token': _config.storefrontToken\n          },\n          body: JSON.stringify({\n            query: buildSingleProductQuery(),\n            variables: { id: 'gid://shopify/Product/' + productId }\n          })\n        }\n      );\n\n      if (!response.ok) return result;\n\n      var data = await response.json();\n      var product = data.data ? data.data.product : null;\n      if (!product) return result;\n\n      var widthSources = extractWidthSources(product);\n      result.fabricWidth = resolveWidth(widthSources);\n\n      var variants = product.variants ? product.variants.edges : [];\n      if (variants.length > 0) {\n        result.variantId = extractVariantId(variants[0].node.id);\n      }\n    } catch (err) {\n      console.error('[TRHFabricBrowser] fetchProductMetafields error:', err);\n    }\n\n    return result;\n  }\n\n  /* =========================================================================\n   * SEARCH / SUGGESTIONS\n   * ========================================================================= */\n\n  /**\n   * Filters loaded products by a normalized search term and renders matching\n   * suggestions into the specified dropdown container.\n   * @param {string} term - Raw search input value\n   * @param {string} type - 'fabric' or 'trim'\n   * @returns {Promise<void>}\n   */\n  async function showSuggestions(term, type) {\n    var suggestionsEl = type === 'fabric'\n      ? document.getElementById(_config.fabricSuggestionsId)\n      : document.getElementById(_config.trimSuggestionsId);\n\n    if (!suggestionsEl) return;\n\n    suggestionsEl.innerHTML = '';\n\n    if (!term || term.trim() === '') {\n      suggestionsEl.style.display = 'none';\n      suggestionsEl.classList.remove(PREFIX + '__suggestions--active');\n      return;\n    }\n\n    if (!_productsLoaded) await loadProducts();\n\n    var products = type === 'fabric' ? _allFabrics : _allTrims;\n    var normalizedTerm = normalizeForSearch(term);\n\n    var matches = products\n      .filter(function (p) {\n        return normalizeForSearch(p.title).indexOf(normalizedTerm) !== -1;\n      })\n      .slice(0, MAX_SUGGESTIONS);\n\n    if (matches.length === 0) {\n      suggestionsEl.innerHTML = '<div class=\"' + PREFIX + '__no-results\">No ' +\n        type + 's found</div>';\n    } else {\n      matches.forEach(function (item) {\n        var row = createElement('div', { className: PREFIX + '__suggestion' });\n\n        var img = createElement('img', {\n          className: PREFIX + '__suggestion-image',\n          attrs: {\n            src: item.imageUrl,\n            alt: escapeHtml(item.altText),\n            loading: 'lazy'\n          }\n        });\n\n        var details = createElement('div', { className: PREFIX + '__suggestion-details' });\n        var name = createElement('div', {\n          className: PREFIX + '__suggestion-name',\n          text: item.title\n        });\n        var price = createElement('div', {\n          className: PREFIX + '__suggestion-price',\n          text: '$' + item.price.toFixed(2) + '/yd'\n        });\n\n        details.appendChild(name);\n        details.appendChild(price);\n        row.appendChild(img);\n        row.appendChild(details);\n\n        row.addEventListener('click', function (e) {\n          if (e.target.classList.contains(PREFIX + '__suggestion-image')) {\n            showZoomModal(item, type, matches);\n          } else {\n            type === 'fabric' ? selectFabric(item) : selectTrim(item);\n          }\n        });\n\n        suggestionsEl.appendChild(row);\n      });\n    }\n\n    suggestionsEl.style.display = 'block';\n    suggestionsEl.classList.add(PREFIX + '__suggestions--active');\n  }\n\n  /* =========================================================================\n   * ZOOM MODAL\n   * ========================================================================= */\n\n  /**\n   * Opens the zoom modal to display a full-size image of the given item.\n   * When searchList is provided, enables previous/next navigation and\n   * select/choose-another buttons.\n   * @param {Object} item - Product item with imageUrl, title, price\n   * @param {string} type - 'fabric' or 'trim'\n   * @param {Array<Object>|null} searchList - Navigable list, or null for swatch-only view\n   */\n  function showZoomModal(item, type, searchList) {\n    _zoomType = type;\n    var items = searchList || (type === 'fabric' ? _allFabrics : _allTrims);\n    _currentSearchResults = items;\n    var idx = -1;\n    for (var i = 0; i < items.length; i++) {\n      if (items[i].id === item.id) { idx = i; break; }\n    }\n\n    _els.zoomImage.src = item.imageUrl;\n    _els.zoomImage.alt = item.altText || item.title;\n    _els.zoomTitle.textContent = item.title;\n    _els.zoomPrice.textContent = '$' + item.price.toFixed(2) + '/yd';\n\n    _els.zoomOverlay.classList.add(PREFIX + '__zoom-overlay--visible');\n\n    // Store current item data for selection\n    _els.zoomSelectBtn._currentItem = item;\n    _els.zoomPrevBtn._currentIndex = idx;\n    _els.zoomNextBtn._currentIndex = idx;\n\n    if (searchList === null) {\n      _els.zoomPrevBtn.style.display = 'none';\n      _els.zoomNextBtn.style.display = 'none';\n      _els.zoomSelectBtn.style.display = 'none';\n      _els.zoomChooseBtn.style.display = 'none';\n    } else {\n      _els.zoomPrevBtn.style.display = 'block';\n      _els.zoomNextBtn.style.display = 'block';\n      _els.zoomSelectBtn.style.display = 'inline-block';\n      _els.zoomChooseBtn.style.display = 'inline-block';\n\n      _els.zoomPrevBtn.disabled = (idx <= 0);\n      _els.zoomNextBtn.disabled = (idx >= items.length - 1);\n    }\n  }\n\n  /**\n   * Closes the zoom modal. Optionally flags a \"choose another\" state to\n   * prevent the suggestions dropdown from closing on the same click.\n   * @param {boolean} [chooseAnother=false] - Whether user wants to choose another item\n   */\n  function closeZoomModal(chooseAnother) {\n    _els.zoomOverlay.classList.remove(PREFIX + '__zoom-overlay--visible');\n    _isChoosingAnother = !!chooseAnother;\n    if (chooseAnother) {\n      setTimeout(function () { _isChoosingAnother = false; }, 100);\n    }\n    _zoomType = null;\n    _currentSearchResults = [];\n  }\n\n  /**\n   * Handles the \"Select This\" button in the zoom modal - selects the current\n   * item and closes both the zoom and catalog modals.\n   */\n  function selectFromZoom() {\n    var item = _els.zoomSelectBtn._currentItem;\n    if (!item) return;\n\n    var typeToClose = _zoomType;\n    if (_zoomType === 'fabric') {\n      selectFabric(item);\n    } else {\n      selectTrim(item);\n    }\n    closeZoomModal();\n    closeCatalogModal(typeToClose);\n  }\n\n  /**\n   * Navigates the zoom modal to a different item in the search results.\n   * @param {number} direction - -1 for previous, +1 for next\n   */\n  function navigateZoom(direction) {\n    var currentIdx = direction === -1\n      ? _els.zoomPrevBtn._currentIndex\n      : _els.zoomNextBtn._currentIndex;\n\n    var items = _currentSearchResults;\n    var newIdx = currentIdx + direction;\n\n    if (newIdx < 0 || newIdx >= items.length) return;\n\n    showZoomModal(items[newIdx], _zoomType, _currentSearchResults);\n  }\n\n  /* =========================================================================\n   * CATALOG BROWSER (POPUP)\n   * ========================================================================= */\n\n  /**\n   * Opens the Shopify collection page in a popup window. The collection page\n   * already has selection code (main-collection-product-grid.liquid) that adds\n   * \"Select\" buttons and sends postMessage back when a product is chosen.\n   * @param {string} type - 'fabric' or 'trim'\n   */\n  function showCatalog(type) {\n    var domain = _config.storefrontDomain || _config.storeDomain;\n    var collection = type === 'fabric' ? 'fabric-1' : 'trim';\n    var productLabel = _config.productLabel || 'Calculator';\n    var url = 'https://' + domain + '/collections/' + collection +\n      '?calculator=true&calculatorType=' + type + '&productLabel=' + encodeURIComponent(productLabel);\n\n    var popup = window.open(url, 'trh_' + type + '_catalog',\n      'width=1200,height=900,resizable=yes,scrollbars=yes');\n\n    if (!popup || popup.closed) {\n      alert('Please allow popups for this site to browse the ' + type + ' collection.');\n    }\n  }\n\n  /**\n   * Closes the catalog modal (no-op for popup window approach; the popup\n   * closes itself after selection via window.close()).\n   * @param {string} type - 'fabric' or 'trim'\n   */\n  function closeCatalogModal(type) {\n    /* Popup windows close themselves after postMessage. Nothing to do here. */\n  }\n\n  /* =========================================================================\n   * SELECTION MANAGEMENT\n   * ========================================================================= */\n\n  /**\n   * Updates the visibility of the clear button on a swatch preview.\n   * @param {string} type - 'fabric' or 'trim'\n   */\n  function updateSwatchClearVisibility(type) {\n    var clearBtn = type === 'fabric' ? _els.fabricSwatchClear : _els.trimSwatchClear;\n    var selected = type === 'fabric' ? _selectedFabric : _selectedTrim;\n\n    if (!clearBtn) return;\n\n    if (selected) {\n      clearBtn.classList.add(PREFIX + '__swatch-clear--visible');\n    } else {\n      clearBtn.classList.remove(PREFIX + '__swatch-clear--visible');\n    }\n  }\n\n  /**\n   * Selects a fabric item. Updates the search input field, shows the swatch\n   * preview, hides suggestions, and fires the selection callback and custom event.\n   * @param {Object} fabric - Fabric item object\n   * @param {string} fabric.id - Variant ID\n   * @param {string} fabric.title - Display title\n   * @param {number} fabric.price - Price per yard\n   * @param {string} fabric.imageUrl - Thumbnail image URL\n   * @param {string} fabric.altText - Image alt text\n   * @param {number} [fabric.fabricWidth] - Fabric width in inches\n   */\n  function selectFabric(fabric) {\n    _selectedFabric = fabric;\n\n    var field = document.getElementById(_config.fabricFieldId);\n    if (field) {\n      field.value = fabric.title + ' ($' + fabric.price.toFixed(2) + '/yd)';\n      field.classList.add(PREFIX + '__search-input--selected');\n    }\n\n    if (_els.fabricSwatchImage) {\n      _els.fabricSwatchImage.src = fabric.imageUrl;\n      _els.fabricSwatchImage.alt = fabric.altText || fabric.title;\n      _els.fabricSwatchImage.classList.add(PREFIX + '__swatch-image--clickable');\n      _els.fabricSwatchImage.onclick = function () {\n        showZoomModal(fabric, 'fabric', null);\n      };\n    }\n\n    if (_els.fabricSwatchPrice) {\n      _els.fabricSwatchPrice.textContent = '$' + fabric.price.toFixed(2) + '/yd';\n    }\n\n    if (_els.fabricSwatch) {\n      _els.fabricSwatch.classList.add(PREFIX + '__swatch-preview--visible');\n    }\n\n    var suggestionsEl = document.getElementById(_config.fabricSuggestionsId);\n    if (suggestionsEl) {\n      suggestionsEl.style.display = 'none';\n      suggestionsEl.classList.remove(PREFIX + '__suggestions--active');\n    }\n\n    updateSwatchClearVisibility('fabric');\n\n    if (typeof _config.onFabricSelect === 'function') {\n      _config.onFabricSelect(fabric);\n    }\n\n    dispatchEvent('trh:fabric-selected', { fabric: fabric });\n\n    console.log('[TRHFabricBrowser] Fabric selected:', fabric.title);\n  }\n\n  /**\n   * Selects a trim item. Updates the search input field, shows the swatch\n   * preview, hides suggestions, and fires the selection callback and custom event.\n   * @param {Object} trim - Trim item object\n   * @param {string} trim.id - Variant ID\n   * @param {string} trim.title - Display title\n   * @param {number} trim.price - Price per yard\n   * @param {string} trim.imageUrl - Thumbnail image URL\n   * @param {string} trim.altText - Image alt text\n   */\n  function selectTrim(trim) {\n    _selectedTrim = trim;\n\n    var field = document.getElementById(_config.trimFieldId);\n    if (field) {\n      field.value = trim.title + ' ($' + trim.price.toFixed(2) + '/yd)';\n      field.classList.add(PREFIX + '__search-input--selected');\n    }\n\n    if (_els.trimSwatchImage) {\n      _els.trimSwatchImage.src = trim.imageUrl;\n      _els.trimSwatchImage.alt = trim.altText || trim.title;\n      _els.trimSwatchImage.classList.add(PREFIX + '__swatch-image--clickable');\n      _els.trimSwatchImage.onclick = function () {\n        showZoomModal(trim, 'trim', null);\n      };\n    }\n\n    if (_els.trimSwatchPrice) {\n      _els.trimSwatchPrice.textContent = '$' + trim.price.toFixed(2) + '/yd';\n    }\n\n    if (_els.trimSwatch) {\n      _els.trimSwatch.classList.add(PREFIX + '__swatch-preview--visible');\n    }\n\n    var suggestionsEl = document.getElementById(_config.trimSuggestionsId);\n    if (suggestionsEl) {\n      suggestionsEl.style.display = 'none';\n      suggestionsEl.classList.remove(PREFIX + '__suggestions--active');\n    }\n\n    updateSwatchClearVisibility('trim');\n\n    if (typeof _config.onTrimSelect === 'function') {\n      _config.onTrimSelect(trim);\n    }\n\n    dispatchEvent('trh:trim-selected', { trim: trim });\n\n    console.log('[TRHFabricBrowser] Trim selected:', trim.title);\n  }\n\n  /**\n   * Clears the current fabric or trim selection. Resets the search field,\n   * hides the swatch preview, and fires the clear callback and custom event.\n   * @param {string} type - 'fabric' or 'trim'\n   */\n  function clearSelection(type) {\n    if (type === 'fabric') {\n      _selectedFabric = null;\n\n      var fabricField = document.getElementById(_config.fabricFieldId);\n      if (fabricField) {\n        fabricField.value = '';\n        fabricField.classList.remove(PREFIX + '__search-input--selected');\n      }\n\n      if (_els.fabricSwatch) {\n        _els.fabricSwatch.classList.remove(PREFIX + '__swatch-preview--visible');\n      }\n\n      var fabricSuggestions = document.getElementById(_config.fabricSuggestionsId);\n      if (fabricSuggestions) {\n        fabricSuggestions.style.display = 'none';\n        fabricSuggestions.classList.remove(PREFIX + '__suggestions--active');\n      }\n\n      updateSwatchClearVisibility('fabric');\n\n      if (typeof _config.onFabricClear === 'function') {\n        _config.onFabricClear();\n      }\n\n      dispatchEvent('trh:fabric-cleared');\n\n      console.log('[TRHFabricBrowser] Fabric selection cleared.');\n    } else {\n      _selectedTrim = null;\n\n      var trimField = document.getElementById(_config.trimFieldId);\n      if (trimField) {\n        trimField.value = '';\n        trimField.classList.remove(PREFIX + '__search-input--selected');\n      }\n\n      if (_els.trimSwatch) {\n        _els.trimSwatch.classList.remove(PREFIX + '__swatch-preview--visible');\n      }\n\n      var trimSuggestions = document.getElementById(_config.trimSuggestionsId);\n      if (trimSuggestions) {\n        trimSuggestions.style.display = 'none';\n        trimSuggestions.classList.remove(PREFIX + '__suggestions--active');\n      }\n\n      updateSwatchClearVisibility('trim');\n\n      if (typeof _config.onTrimClear === 'function') {\n        _config.onTrimClear();\n      }\n\n      dispatchEvent('trh:trim-cleared');\n\n      console.log('[TRHFabricBrowser] Trim selection cleared.');\n    }\n  }\n\n  /* =========================================================================\n   * POSTMESSAGE HANDLER (catalog popup communication)\n   * ========================================================================= */\n\n  /**\n   * Handles incoming postMessage events from catalog popup windows. Processes\n   * 'fabricSelected' and 'trimSelected' message types, fetches additional\n   * metafield data when needed, and applies the selection.\n   * @param {MessageEvent} event - The message event\n   */\n  async function handlePostMessage(event) {\n    var data = event.data;\n    if (!data || typeof data !== 'object') return;\n\n    /* Catalog iframe selections (blob URL iframe) */\n    if (data.type === 'trhCatalogSelect' && data.item) {\n      if (data.itemType === 'fabric') {\n        selectFabric(data.item);\n      } else {\n        selectTrim(data.item);\n      }\n      closeCatalogModal(data.itemType);\n      return;\n    }\n\n    if (data.type === 'trhCatalogClose') {\n      closeCatalogModal('fabric');\n      closeCatalogModal('trim');\n      return;\n    }\n\n    /* Legacy popup window selections */\n    if (data.type === 'fabricSelected' && data.fabricData) {\n      await handleCatalogFabricSelection(data.fabricData);\n    }\n\n    if (data.type === 'trimSelected' && data.trimData) {\n      await handleCatalogTrimSelection(data.trimData);\n    }\n  }\n\n  /**\n   * Processes a fabric selection received from the catalog popup window.\n   * Fetches width metafield data from the API if a product ID is provided.\n   * @param {Object} fabricData - Data from the collection page postMessage\n   * @param {string} [fabricData.productId] - Shopify product ID\n   * @param {string} [fabricData.variantId] - Shopify variant ID\n   * @param {string} fabricData.title - Product title\n   * @param {string} [fabricData.variant] - Variant title\n   * @param {number} [fabricData.price] - Price per yard\n   * @param {string} [fabricData.imageUrl] - Product image URL\n   * @param {number} [fabricData.fabricWidth] - Pre-resolved fabric width\n   */\n  async function handleCatalogFabricSelection(fabricData) {\n    var variantId = fabricData.variantId || fabricData.id || null;\n    var fabricWidth = DEFAULT_FABRIC_WIDTH;\n\n    if (fabricData.productId) {\n      var meta = await fetchProductMetafields(fabricData.productId);\n      fabricWidth = meta.fabricWidth;\n      if (!variantId && meta.variantId) {\n        variantId = meta.variantId;\n      }\n    } else if (fabricData.fabricWidth) {\n      fabricWidth = fabricData.fabricWidth;\n    }\n\n    var displayTitle = fabricData.variant\n      ? fabricData.title + ' - ' + fabricData.variant\n      : fabricData.title;\n\n    var fabric = {\n      id: variantId,\n      title: displayTitle,\n      price: fabricData.price || 0,\n      imageUrl: fabricData.imageUrl || '',\n      altText: fabricData.title || '',\n      fabricWidth: fabricWidth\n    };\n\n    selectFabric(fabric);\n    closeCatalogModal('fabric');\n  }\n\n  /**\n   * Processes a trim selection received from the catalog popup window.\n   * Fetches the variant ID from the API if not provided in the postMessage.\n   * @param {Object} trimData - Data from the collection page postMessage\n   * @param {string} [trimData.productId] - Shopify product ID\n   * @param {string} [trimData.variantId] - Shopify variant ID\n   * @param {string} trimData.title - Product title\n   * @param {string} [trimData.variant] - Variant title\n   * @param {number} [trimData.price] - Price per yard\n   * @param {string} [trimData.imageUrl] - Product image URL\n   */\n  async function handleCatalogTrimSelection(trimData) {\n    var variantId = trimData.variantId || trimData.id || null;\n\n    if (!variantId && trimData.productId) {\n      var meta = await fetchProductMetafields(trimData.productId);\n      if (meta.variantId) {\n        variantId = meta.variantId;\n      }\n    }\n\n    var displayTitle = trimData.variant\n      ? trimData.title + ' - ' + trimData.variant\n      : trimData.title;\n\n    var trim = {\n      id: variantId,\n      title: displayTitle,\n      price: trimData.price || 0,\n      imageUrl: trimData.imageUrl || '',\n      altText: trimData.title || ''\n    };\n\n    selectTrim(trim);\n    closeCatalogModal('trim');\n  }\n\n  /* =========================================================================\n   * EVENT BINDING\n   * ========================================================================= */\n\n  /** @type {Array<Object>} Tracked event listeners for cleanup */\n  var _boundListeners = [];\n\n  /**\n   * Adds an event listener and tracks it for later cleanup.\n   * @param {EventTarget} target - The DOM element or object\n   * @param {string} event - Event name\n   * @param {Function} handler - Event handler\n   * @param {Object} [options] - addEventListener options\n   */\n  function addTrackedListener(target, event, handler, options) {\n    target.addEventListener(event, handler, options);\n    _boundListeners.push({ target: target, event: event, handler: handler, options: options });\n  }\n\n  /**\n   * Removes all tracked event listeners. Called during cleanup/destroy.\n   */\n  function removeAllListeners() {\n    _boundListeners.forEach(function (entry) {\n      entry.target.removeEventListener(entry.event, entry.handler, entry.options);\n    });\n    _boundListeners = [];\n  }\n\n  /**\n   * Binds all interactive event listeners for search inputs, dropdowns,\n   * modals, keyboard shortcuts, and postMessage communication.\n   */\n  function bindEvents() {\n    var fabricField = document.getElementById(_config.fabricFieldId);\n    var trimField = _config.trimFieldId ? document.getElementById(_config.trimFieldId) : null;\n    var fabricSuggestions = document.getElementById(_config.fabricSuggestionsId);\n    var trimSuggestions = _config.trimSuggestionsId\n      ? document.getElementById(_config.trimSuggestionsId)\n      : null;\n\n    // Debounced search handlers\n    if (fabricField) {\n      var debouncedFabricSearch = debounce(function (e) {\n        showSuggestions(e.target.value, 'fabric');\n      }, DEBOUNCE_MS);\n\n      addTrackedListener(fabricField, 'input', debouncedFabricSearch);\n    }\n\n    if (trimField && _config.showTrim) {\n      var debouncedTrimSearch = debounce(function (e) {\n        showSuggestions(e.target.value, 'trim');\n      }, DEBOUNCE_MS);\n\n      addTrackedListener(trimField, 'input', debouncedTrimSearch);\n    }\n\n    // Swatch clear buttons\n    if (_els.fabricSwatchClear) {\n      addTrackedListener(_els.fabricSwatchClear, 'click', function () {\n        clearSelection('fabric');\n      });\n    }\n\n    if (_els.trimSwatchClear) {\n      addTrackedListener(_els.trimSwatchClear, 'click', function () {\n        clearSelection('trim');\n      });\n    }\n\n    // Click-away to close suggestions\n    var clickAwayHandler = debounce(function (e) {\n      if (_isChoosingAnother || (e.target && e.target.closest && e.target.closest('.' + PREFIX + '__zoom-btn--secondary'))) {\n        return;\n      }\n\n      if (fabricField && fabricSuggestions) {\n        var isFabricArea = fabricField.contains(e.target) ||\n          fabricSuggestions.contains(e.target) ||\n          (_els.fabricSwatch && _els.fabricSwatch.contains(e.target));\n        if (!isFabricArea) {\n          fabricSuggestions.style.display = 'none';\n          fabricSuggestions.classList.remove(PREFIX + '__suggestions--active');\n        }\n      }\n\n      if (trimField && trimSuggestions) {\n        var isTrimArea = trimField.contains(e.target) ||\n          trimSuggestions.contains(e.target) ||\n          (_els.trimSwatch && _els.trimSwatch.contains(e.target));\n        if (!isTrimArea) {\n          trimSuggestions.style.display = 'none';\n          trimSuggestions.classList.remove(PREFIX + '__suggestions--active');\n        }\n      }\n    }, 50);\n\n    addTrackedListener(document, 'click', clickAwayHandler);\n\n    // Escape key handler\n    addTrackedListener(document, 'keydown', function (e) {\n      if (e.key === 'Escape') {\n        closeZoomModal();\n\n        if (fabricSuggestions) {\n          fabricSuggestions.style.display = 'none';\n          fabricSuggestions.classList.remove(PREFIX + '__suggestions--active');\n        }\n        if (trimSuggestions) {\n          trimSuggestions.style.display = 'none';\n          trimSuggestions.classList.remove(PREFIX + '__suggestions--active');\n        }\n\n        closeCatalogModal('fabric');\n        closeCatalogModal('trim');\n      }\n    });\n\n    // Zoom modal events\n    addTrackedListener(_els.zoomOverlay, 'click', function (e) {\n      if (e.target === _els.zoomOverlay) closeZoomModal();\n    });\n\n    addTrackedListener(_els.zoomCloseBtn, 'click', function () {\n      closeZoomModal();\n    });\n\n    addTrackedListener(_els.zoomSelectBtn, 'click', selectFromZoom);\n\n    addTrackedListener(_els.zoomChooseBtn, 'click', function () {\n      closeZoomModal(true);\n    });\n\n    addTrackedListener(_els.zoomPrevBtn, 'click', function () {\n      if (!this.disabled) navigateZoom(-1);\n    });\n\n    addTrackedListener(_els.zoomNextBtn, 'click', function () {\n      if (!this.disabled) navigateZoom(1);\n    });\n\n    // Catalog lightbox events (close on overlay background click)\n    if (_els.catalogFabricOverlay) {\n      addTrackedListener(_els.catalogFabricOverlay, 'click', function (e) {\n        if (e.target === _els.catalogFabricOverlay) closeCatalogModal('fabric');\n      });\n    }\n\n    if (_els.catalogTrimOverlay) {\n      addTrackedListener(_els.catalogTrimOverlay, 'click', function (e) {\n        if (e.target === _els.catalogTrimOverlay) closeCatalogModal('trim');\n      });\n    }\n\n    // PostMessage listener for catalog popup selections (legacy support)\n    addTrackedListener(window, 'message', handlePostMessage);\n  }\n\n  /* =========================================================================\n   * INITIALIZATION & CLEANUP\n   * ========================================================================= */\n\n  /**\n   * Injects all dynamically created DOM elements (modals, swatch previews)\n   * into the page. Elements are appended to document.body for modals, and\n   * adjacent to the search inputs for swatch previews.\n   */\n  function injectDOM() {\n    // Inject styles\n    injectStyles();\n\n    // Create and inject zoom modal\n    var zoomModal = createZoomModal();\n    document.body.appendChild(zoomModal);\n\n    // Create and inject catalog modals\n    var fabricCatalogModal = createCatalogModal('fabric');\n    document.body.appendChild(fabricCatalogModal);\n\n    if (_config.showTrim) {\n      var trimCatalogModal = createCatalogModal('trim');\n      document.body.appendChild(trimCatalogModal);\n    }\n\n    // Create and inject swatch previews next to search inputs\n    var fabricField = document.getElementById(_config.fabricFieldId);\n    if (fabricField && fabricField.parentNode) {\n      var fabricSwatch = createSwatchPreview('fabric');\n      // Insert after the suggestions container if it exists, otherwise after field\n      var fabricSuggestions = document.getElementById(_config.fabricSuggestionsId);\n      if (fabricSuggestions && fabricSuggestions.parentNode === fabricField.parentNode) {\n        fabricField.parentNode.insertBefore(fabricSwatch, fabricSuggestions.nextSibling);\n      } else {\n        fabricField.parentNode.appendChild(fabricSwatch);\n      }\n    }\n\n    if (_config.showTrim && _config.trimFieldId) {\n      var trimField = document.getElementById(_config.trimFieldId);\n      if (trimField && trimField.parentNode) {\n        var trimSwatch = createSwatchPreview('trim');\n        var trimSuggestions = document.getElementById(_config.trimSuggestionsId);\n        if (trimSuggestions && trimSuggestions.parentNode === trimField.parentNode) {\n          trimField.parentNode.insertBefore(trimSwatch, trimSuggestions.nextSibling);\n        } else {\n          trimField.parentNode.appendChild(trimSwatch);\n        }\n      }\n    }\n\n    // Add BEM class to suggestions containers\n    if (_config.fabricSuggestionsId) {\n      var fSug = document.getElementById(_config.fabricSuggestionsId);\n      if (fSug) fSug.classList.add(PREFIX + '__suggestions');\n    }\n    if (_config.trimSuggestionsId) {\n      var tSug = document.getElementById(_config.trimSuggestionsId);\n      if (tSug) tSug.classList.add(PREFIX + '__suggestions');\n    }\n  }\n\n  /**\n   * Removes all dynamically created DOM elements and cleans up event listeners.\n   * Called when re-initializing or when the module is no longer needed.\n   */\n  function cleanup() {\n    removeAllListeners();\n\n    // Remove injected DOM elements\n    var elementsToRemove = [\n      _els.zoomOverlay,\n      _els.catalogFabricOverlay,\n      _els.catalogTrimOverlay,\n      _els.fabricSwatch,\n      _els.trimSwatch\n    ];\n\n    elementsToRemove.forEach(function (el) {\n      if (el && el.parentNode) {\n        el.parentNode.removeChild(el);\n      }\n    });\n\n    // Remove injected styles\n    var styleEl = document.getElementById(PREFIX + '-styles');\n    if (styleEl && styleEl.parentNode) {\n      styleEl.parentNode.removeChild(styleEl);\n    }\n\n    // Remove BEM classes from suggestions containers\n    if (_config && _config.fabricSuggestionsId) {\n      var fSug = document.getElementById(_config.fabricSuggestionsId);\n      if (fSug) fSug.classList.remove(PREFIX + '__suggestions');\n    }\n    if (_config && _config.trimSuggestionsId) {\n      var tSug = document.getElementById(_config.trimSuggestionsId);\n      if (tSug) tSug.classList.remove(PREFIX + '__suggestions');\n    }\n\n    // Reset state\n    _els = {};\n    _selectedFabric = null;\n    _selectedTrim = null;\n    _currentSearchResults = [];\n    _zoomType = null;\n    _isChoosingAnother = false;\n  }\n\n  /* =========================================================================\n   * PUBLIC API\n   * ========================================================================= */\n\n  /**\n   * @namespace TRHFabricBrowser\n   * @description Public API for the TRH Fabric Browser module.\n   */\n  window.TRHFabricBrowser = {\n\n    /**\n     * Initializes the fabric browser module. Sets up configuration, injects\n     * dynamic DOM elements (modals, swatch previews), binds all event listeners,\n     * and begins loading products from the Storefront API.\n     *\n     * Can be called multiple times - will clean up previous instance first.\n     *\n     * @param {Object} config - Configuration object\n     * @param {string} config.storeDomain - Shopify .myshopify.com domain for API calls\n     * @param {string} config.storefrontToken - Storefront API access token\n     * @param {string} [config.storefrontDomain] - Public-facing domain for popup URLs\n     *   (defaults to storeDomain if not provided)\n     * @param {string} config.fabricFieldId - DOM ID of the fabric search input element\n     * @param {string} [config.trimFieldId] - DOM ID of the trim search input element\n     * @param {string} config.fabricSuggestionsId - DOM ID of fabric suggestions container\n     * @param {string} [config.trimSuggestionsId] - DOM ID of trim suggestions container\n     * @param {boolean} [config.showTrim=false] - Whether to enable trim selection\n     * @param {Function} [config.onFabricSelect] - Callback invoked when a fabric is selected.\n     *   Receives the fabric object as its argument.\n     * @param {Function} [config.onTrimSelect] - Callback invoked when a trim is selected.\n     *   Receives the trim object as its argument.\n     * @param {Function} [config.onFabricClear] - Callback invoked when fabric selection is cleared.\n     * @param {Function} [config.onTrimClear] - Callback invoked when trim selection is cleared.\n     *\n     * @example\n     * TRHFabricBrowser.init({\n     *   storeDomain: 'z10i3p-9c.myshopify.com',\n     *   storefrontToken: '52130f6f2cd60f3e7a7003d2da243ff2',\n     *   fabricFieldId: 'singleFabricField',\n     *   fabricSuggestionsId: 'searchSuggestions',\n     *   trimFieldId: 'singleTrimField',\n     *   trimSuggestionsId: 'trimSearchSuggestions',\n     *   showTrim: true,\n     *   onFabricSelect: function(fabric) {\n     *     document.getElementById('fabricWidth').value = fabric.fabricWidth || 54;\n     *   }\n     * });\n     */\n    init: function (config) {\n      // Clean up any previous initialization\n      if (_config) {\n        cleanup();\n      }\n\n      // Validate required config\n      if (!config.storeDomain) {\n        console.error('[TRHFabricBrowser] config.storeDomain is required.');\n        return;\n      }\n      if (!config.storefrontToken) {\n        console.error('[TRHFabricBrowser] config.storefrontToken is required.');\n        return;\n      }\n      if (!config.fabricFieldId) {\n        console.error('[TRHFabricBrowser] config.fabricFieldId is required.');\n        return;\n      }\n      if (!config.fabricSuggestionsId) {\n        console.error('[TRHFabricBrowser] config.fabricSuggestionsId is required.');\n        return;\n      }\n\n      _config = {\n        storeDomain: config.storeDomain,\n        storefrontToken: config.storefrontToken,\n        storefrontDomain: config.storefrontDomain || config.storeDomain,\n        fabricFieldId: config.fabricFieldId,\n        trimFieldId: config.trimFieldId || null,\n        fabricSuggestionsId: config.fabricSuggestionsId,\n        trimSuggestionsId: config.trimSuggestionsId || null,\n        showTrim: !!config.showTrim,\n        onFabricSelect: config.onFabricSelect || null,\n        onTrimSelect: config.onTrimSelect || null,\n        onFabricClear: config.onFabricClear || null,\n        onTrimClear: config.onTrimClear || null\n      };\n\n      injectDOM();\n      bindEvents();\n\n      // Begin loading products in the background\n      loadProducts();\n\n      console.log('[TRHFabricBrowser] Initialized.', {\n        showTrim: _config.showTrim,\n        domain: _config.storeDomain\n      });\n    },\n\n    /**\n     * Returns the currently selected fabric object, or null if no fabric\n     * is selected.\n     * @returns {Object|null} The selected fabric object, or null\n     */\n    getSelectedFabric: function () {\n      return _selectedFabric;\n    },\n\n    /**\n     * Returns the currently selected trim object, or null if no trim\n     * is selected.\n     * @returns {Object|null} The selected trim object, or null\n     */\n    getSelectedTrim: function () {\n      return _selectedTrim;\n    },\n\n    /**\n     * Returns a copy of all loaded fabric products.\n     * @returns {Array<Object>} Array of fabric product objects\n     */\n    getAllFabrics: function () {\n      return _allFabrics.slice();\n    },\n\n    /**\n     * Returns a copy of all loaded trim products.\n     * @returns {Array<Object>} Array of trim product objects\n     */\n    getAllTrims: function () {\n      return _allTrims.slice();\n    },\n\n    /**\n     * Programmatically selects a fabric. Equivalent to the user clicking\n     * a search suggestion. Updates UI, fires callbacks and custom events.\n     * @param {Object} fabric - Fabric object to select\n     * @param {string} fabric.id - Variant ID\n     * @param {string} fabric.title - Display title\n     * @param {number} fabric.price - Price per yard\n     * @param {string} fabric.imageUrl - Thumbnail image URL\n     * @param {string} [fabric.altText] - Image alt text\n     * @param {number} [fabric.fabricWidth] - Fabric width in inches\n     */\n    selectFabric: function (fabric) {\n      if (!_config) {\n        console.error('[TRHFabricBrowser] Not initialized. Call init() first.');\n        return;\n      }\n      selectFabric(fabric);\n    },\n\n    /**\n     * Programmatically selects a trim. Equivalent to the user clicking\n     * a search suggestion. Updates UI, fires callbacks and custom events.\n     * @param {Object} trim - Trim object to select\n     * @param {string} trim.id - Variant ID\n     * @param {string} trim.title - Display title\n     * @param {number} trim.price - Price per yard\n     * @param {string} trim.imageUrl - Thumbnail image URL\n     * @param {string} [trim.altText] - Image alt text\n     */\n    selectTrim: function (trim) {\n      if (!_config) {\n        console.error('[TRHFabricBrowser] Not initialized. Call init() first.');\n        return;\n      }\n      selectTrim(trim);\n    },\n\n    /**\n     * Clears the current fabric or trim selection. Resets the search input,\n     * hides the swatch preview, and fires the appropriate callbacks and\n     * custom events.\n     * @param {string} type - 'fabric' or 'trim'\n     */\n    clearSelection: function (type) {\n      if (!_config) {\n        console.error('[TRHFabricBrowser] Not initialized. Call init() first.');\n        return;\n      }\n      clearSelection(type);\n    },\n\n    /**\n     * Opens the catalog browser popup for the specified product type.\n     * If the popup is blocked by the browser, a modal with instructions\n     * and a retry button will be shown instead.\n     * @param {string} type - 'fabric' or 'trim'\n     */\n    showCatalog: function (type) {\n      if (!_config) {\n        console.error('[TRHFabricBrowser] Not initialized. Call init() first.');\n        return;\n      }\n      showCatalog(type);\n    },\n\n    /**\n     * Forces a reload of all products from the Storefront API, discarding\n     * the current cached data. Useful after product catalog changes or when\n     * recovering from a load failure.\n     * @returns {Promise<void>}\n     */\n    reloadProducts: function () {\n      if (!_config) {\n        console.error('[TRHFabricBrowser] Not initialized. Call init() first.');\n        return Promise.resolve();\n      }\n      _productsLoaded = false;\n      _allFabrics = [];\n      _allTrims = [];\n      return loadProducts();\n    },\n\n    /**\n     * Destroys the module instance. Removes all injected DOM elements,\n     * unbinds all event listeners, and resets internal state.\n     * The module can be re-initialized by calling init() again.\n     */\n    destroy: function () {\n      cleanup();\n      _config = null;\n      _allFabrics = [];\n      _allTrims = [];\n      _productsLoaded = false;\n      _loadingProducts = false;\n      console.log('[TRHFabricBrowser] Destroyed.');\n    }\n  };\n})();\n"],
  "mappings": "CAsBC,UAAY,CACX,aAOA,MAAM,OAAS,SAGT,YAAc,UAed,YAAc,CAAC,SAAU,QAAS,OAAQ,QAAQ,EAGlD,qBAAuB,CAAC,UAAW,QAAS,SAAU,QAAS,OAAQ,QAAQ,EAG/E,mBAAqB,CAAC,SAAU,OAAO,EAO7C,IAAI,QAAU,KAGV,YAAc,CAAC,EAGf,UAAY,CAAC,EAGb,gBAAkB,KAGlB,cAAgB,KAGhB,gBAAkB,GAGlB,iBAAmB,GAGnB,gBAAkB,KAGlB,sBAAwB,CAAC,EAGzB,UAAY,KAGZ,mBAAqB,GAGrB,KAAO,CAAC,EAaZ,SAAS,SAAS,GAAI,KAAM,CAC1B,GAAI,OAAO,EAAM,KAAe,OAAO,EAAE,UAAa,WACpD,OAAO,EAAE,SAAS,GAAI,IAAI,EAE5B,IAAI,MAAQ,KACZ,OAAO,UAAY,CACjB,IAAI,QAAU,KACV,KAAO,UACX,aAAa,KAAK,EAClB,MAAQ,WAAW,UAAY,CAC7B,GAAG,MAAM,QAAS,IAAI,CACxB,EAAG,IAAI,CACT,CACF,CAQA,SAAS,mBAAmB,IAAK,CAC/B,OAAO,IACJ,UAAU,KAAK,EACf,QAAQ,mBAAoB,EAAE,EAC9B,YAAY,CACjB,CAOA,SAAS,cAAc,UAAW,OAAQ,CACxC,SAAS,cACP,IAAI,YAAY,UAAW,CAAE,OAAQ,QAAU,CAAC,CAAE,CAAC,CACrD,CACF,CAYA,SAAS,cAAc,IAAK,KAAM,CAChC,IAAI,GAAK,SAAS,cAAc,GAAG,EACnC,OAAK,OACD,KAAK,YAAW,GAAG,UAAY,KAAK,WACpC,KAAK,OACP,OAAO,KAAK,KAAK,KAAK,EAAE,QAAQ,SAAU,IAAK,CAC7C,GAAG,aAAa,IAAK,KAAK,MAAM,GAAG,CAAC,CACtC,CAAC,EAEC,KAAK,OAAM,GAAG,YAAc,KAAK,MACjC,KAAK,OAAM,GAAG,UAAY,KAAK,OAC5B,EACT,CAOA,SAAS,WAAW,IAAK,CACvB,GAAI,CAAC,IAAK,MAAO,GACjB,IAAI,IAAM,SAAS,cAAc,KAAK,EACtC,WAAI,YAAY,SAAS,eAAe,GAAG,CAAC,EACrC,IAAI,SACb,CAWA,SAAS,cAAe,CACtB,GAAI,UAAS,eAAe,OAAS,SAAS,EAE9C,KAAI,MAAQ,SAAS,cAAc,OAAO,EAC1C,MAAM,GAAK,OAAS,UACpB,MAAM,YAAc,CAElB,IAAM,OAAS,kBACf,wBACA,eACA,aACA,cACA,sBACA,4BACA,sBACA,gCACA,uBACA,sBACA,mBACA,mBACA,4CACA,IACA,IAAM,OAAS,0BACf,oBACA,mBACA,IAGA,IAAM,OAAS,iBACf,mBACA,qBACA,mCACA,qBACA,mBACA,yBACA,eACA,gCACA,sBACA,wBACA,IACA,IAAM,OAAS,sBACf,IAAM,OAAS,2BACf,yBACA,oBACA,IACA,IAAM,OAAS,4BACf,yBACA,IAGA,IAAM,OAAS,uBACf,iBACA,kBACA,uBACA,wBACA,4BACA,oBACA,yBACA,qBACA,IAGA,IAAM,OAAS,yBACf,aACA,kBACA,IACA,IAAM,OAAS,sBACf,sBACA,uBACA,yBACA,sBACA,6BACA,IACA,IAAM,OAAS,uBACf,qBACA,iBACA,eACA,IAGA,IAAM,OAAS,iBACf,mBACA,iBACA,wBACA,wBACA,IAGA,IAAM,OAAS,qBACf,mBACA,qBACA,yBACA,cACA,wBACA,IACA,IAAM,OAAS,8BACf,mBACA,IACA,IAAM,OAAS,mBACf,iBACA,kBACA,uBACA,wBACA,4BACA,yBACA,IACA,IAAM,OAAS,8BACf,qBACA,IACA,IAAM,OAAS,mBACf,qBACA,iBACA,eACA,IACA,IAAM,OAAS,mBACf,6BACA,kBACA,mBACA,sBACA,qBACA,qBACA,iBACA,mBACA,2BACA,oBACA,gBACA,uBACA,yBACA,iBACA,kBACA,sBACA,uBACA,IACA,IAAM,OAAS,4BACf,2BACA,IACA,IAAM,OAAS,yBACf,iBACA,IAGA,IAAM,OAAS,mBACf,mBACA,qBACA,YACA,aACA,cACA,eACA,gCACA,oBACA,6BACA,yBACA,qBACA,IACA,IAAM,OAAS,4BACf,mBACA,IACA,IAAM,OAAS,mBACf,sBACA,yBACA,mBACA,qBACA,sBACA,wBACA,wBACA,sBACA,qBACA,IACA,IAAM,OAAS,iBACf,wBACA,eACA,iBACA,qBACA,uBACA,qBACA,iBACA,iCACA,iBACA,kBACA,wBACA,mBACA,yBACA,6BACA,kBACA,+CACA,kBACA,oBACA,IACA,IAAM,OAAS,uBACf,iCACA,2BACA,IACA,IAAM,OAAS,iBACf,sBACA,uBACA,yBACA,wBACA,IACA,IAAM,OAAS,iBACf,qBACA,uBACA,oBACA,IACA,IAAM,OAAS,iBACf,qBACA,iBACA,yBACA,IACA,IAAM,OAAS,mBACf,mBACA,6BACA,cACA,qBACA,IACA,IAAM,OAAS,eACf,yBACA,iBACA,kBACA,uBACA,wBACA,qBACA,qBACA,iBACA,+DACA,IACA,IAAM,OAAS,qBACf,yBACA,4BACA,2CACA,IACA,IAAM,OAAS,sBACf,yBACA,0CACA,IACA,IAAM,OAAS,0BACf,yBACA,IACA,IAAM,OAAS,gCACf,yBACA,IACA,IAAM,OAAS,eACf,wBACA,cACA,iCACA,yBACA,iBACA,kBACA,uBACA,wBACA,qBACA,qBACA,+DACA,IACA,IAAM,OAAS,oCACf,yBACA,6CACA,2CACA,IACA,IAAM,OAAS,qCACf,0CACA,0CACA,IACA,IAAM,OAAS,wBACf,kBACA,yBACA,yBACA,IACA,IAAM,OAAS,qBACf,gBACA,IACA,IAAM,OAAS,qBACf,iBACA,IAKA,IAAM,OAAS,6BACf,yBACA,2BACA,IAGA,8BACA,MAAQ,OAAS,mDACjB,MAAQ,OAAS,sBACjB,MAAQ,OAAS,gDACjB,MAAQ,OAAS,wDACjB,MAAQ,OAAS,qDACjB,GACF,EAAE,KAAK;AAAA,CAAI,EAEX,SAAS,KAAK,YAAY,KAAK,EACjC,CAWA,SAAS,iBAAkB,CACzB,IAAI,QAAU,cAAc,MAAO,CACjC,UAAW,OAAS,iBACpB,MAAO,CAAE,GAAI,OAAS,gBAAiB,KAAM,SAAU,aAAc,OAAQ,aAAc,mBAAoB,CACjH,CAAC,EAEG,QAAU,cAAc,MAAO,CAAE,UAAW,OAAS,gBAAiB,CAAC,EAEvE,SAAW,cAAc,SAAU,CACrC,UAAW,OAAS,eACpB,MAAO,CAAE,KAAM,SAAU,aAAc,iBAAkB,EACzD,KAAM,SACR,CAAC,EAEG,IAAM,cA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",
  "names": []
}
