{
  "version": 3,
  "sources": ["/cdn/shop/t/2/assets/configurator-shared.js?cb=20251205v3"],
  "sourcesContent": ["/**\n * ConfiguratorShared\n * Single source of truth for shared configurator logic (DRY):\n * - number normalization\n * - slug/model canonicalization for zip-code-validator app proxy endpoints\n * - abortable, debounced JSON POST client for installation-cost API\n */\n(function () {\n  const root = window;\n\n  const normalizeNumber = (value, fallback = 0) => {\n    const n = typeof value === 'string' ? Number(value) : value;\n    return Number.isFinite(n) ? n : fallback;\n  };\n\n  const formatUSD = (dollars, { minDecimals = 0, maxDecimals = 0 } = {}) => {\n    const n = normalizeNumber(dollars, 0);\n    return new Intl.NumberFormat('en-US', {\n      style: 'currency',\n      currency: 'USD',\n      minimumFractionDigits: minDecimals,\n      maximumFractionDigits: maxDecimals,\n    }).format(n);\n  };\n\n  const formatPriceSmart = (dollars) => {\n    const n = normalizeNumber(dollars, 0);\n    const hasCents = Math.abs(n % 1) > 0.000001;\n    return formatUSD(n, { minDecimals: hasCents ? 2 : 0, maxDecimals: hasCents ? 2 : 0 });\n  };\n\n  /**\n   * Prefer theme currency formatter if present; fallback to Intl.\n   * @param {number} dollars\n   * @param {{ noDecimals?: boolean }} opts\n   */\n  const formatMoneyFromDollars = (dollars, opts = {}) => {\n    const cents = Math.round(normalizeNumber(dollars, 0) * 100);\n    const noDecimals = Boolean(opts.noDecimals);\n    if (root.Utility?.formatMoney) {\n      const fmt = noDecimals ? '${{ amount_no_decimals }}' : '${{ amount }}';\n      return root.Utility.formatMoney(cents, fmt);\n    }\n    return formatUSD(cents / 100, { minDecimals: noDecimals ? 0 : 2, maxDecimals: noDecimals ? 0 : 2 });\n  };\n\n  const slugify = (value) => String(value ?? '')\n    .toLowerCase()\n    .trim()\n    .replace(/\\s+/g, '-');\n\n  const canonicalModel = (rawModel) => {\n    const slug = slugify(rawModel);\n\n    if (slug.startsWith('aspect')) return 'aspect';\n    if (slug.startsWith('signature')) return 'signature';\n    if (slug.startsWith('portland')) return 'portland';\n    if (slug.startsWith('spruce')) return 'spruce';\n    if (slug.startsWith('carriage')) return 'carriage-house';\n\n    // Handle Summit models: transform to API format\n    // Examples:\n    //   summit-800-model-a \u2192 summit-800-a\n    //   summit-800-a \u2192 summit-800-a (already correct)\n    //   summit-680-model-c \u2192 summit-680-c\n    if (slug.startsWith('summit-')) {\n      // Match pattern: summit-{size}-{anything}-{letter} or summit-{size}-{letter}\n      // Extract summit-{size} and the final letter variant\n      const match = slug.match(/^(summit-\\d+)(?:-.*)?-([a-z])$/);\n      if (match) {\n        return `${match[1]}-${match[2]}`; // e.g., summit-800-a\n      }\n      // If no letter variant, just return summit-{size}\n      const sizeMatch = slug.match(/^(summit-\\d+)/);\n      if (sizeMatch) {\n        return sizeMatch[1]; // e.g., summit-800\n      }\n    }\n    \n    if (slug.startsWith('summit')) return 'summit';\n\n    return slug;\n  };\n\n  const pickFirst = (obj, keys) => {\n    for (const k of keys) {\n      const v = obj?.[k];\n      if (v != null && String(v).trim() !== '') return v;\n    }\n    return '';\n  };\n\n  const buildInstallationCostPayload = ({ zip, model, depth, length, interior, kitchen, roofStyle, base_product_price }) => {\n    // Helper to normalize option values to kebab-case (lowercase with hyphens)\n    // Converts \"Lean To\" -> \"lean-to\", \"Shell Only\" -> \"shell-only\", etc.\n    const normalizeOption = (val) => val ? slugify(val) : undefined;\n    \n    const payload = {\n      zip: String(zip || '').trim(),\n      model: canonicalModel(model),\n      depth: depth == null ? undefined : normalizeNumber(depth, undefined),\n      length: length == null ? undefined : normalizeNumber(length, undefined),\n      interior: normalizeOption(interior),\n      kitchen: normalizeOption(kitchen),\n      roofStyle: normalizeOption(roofStyle),\n    };\n    \n    // Add base_product_price for all models (required by final-price API)\n    if (base_product_price != null) {\n      payload.base_product_price = normalizeNumber(base_product_price, undefined);\n    }\n    \n    return payload;\n  };\n\n  /**\n   * Map \"installation option value\" -> API cost keys.\n   * Examples:\n   * - \"product-only\" => [] (no installation, returns empty - cost should be 0)\n   * - \"shell-only\" => [\"shell\"]\n   * - \"shell-lifestyle\" => [\"shell\", \"lifestyle\"]  (total is sum)\n   * - \"shell-kitchenbath\" => [\"shell\", \"kitchenbath\"] (total is sum)\n   */\n  const getInstallationCostKeys = (value) => {\n    const v = String(value || '').toLowerCase();\n    const keys = [];\n\n    // If lifestyle or kitchenbath is included, their breakdown cost ALREADY includes the base shell install.\n    // So we should only push 'shell' if it is a purely shell-only option.\n    if (v.includes('lifestyle')) {\n      keys.push('lifestyle');\n    } else if (v.includes('kitchenbath')) {\n      keys.push('kitchenbath');\n    } else if (v.includes('shell')) {\n      keys.push('shell');\n    }\n\n    return keys;\n  };\n\n  /**\n   * Check if an installation option is a \"product only\" type (no installation service).\n   * These options should have $0 installation cost.\n   */\n  const isProductOnlyOption = (value) => {\n    const v = String(value || '').toLowerCase();\n    // \"product-only\", \"product_only\", \"productonly\", \"product\" without other keywords\n    // Also covers: \"none\", \"no-installation\", \"diy\"\n    return (\n      v.includes('product') && !v.includes('shell') && !v.includes('lifestyle') && !v.includes('kitchenbath')\n    ) || v === 'none' || v === 'diy' || v.includes('no-install');\n  };\n\n  /**\n   * Compute the installation total for the selected option. Some options are additive:\n   * \"shell + lifestyle\" == shell + lifestyle.\n   * \n   * IMPORTANT: \"product-only\" and similar options return 0 (no installation fee).\n   * Only options containing \"shell\", \"lifestyle\", or \"kitchenbath\" incur installation costs.\n   */\n  const computeInstallationCost = (data, selectedType) => {\n    if (!data) return 0;\n    \n    // Product-only options have no installation cost\n    if (isProductOnlyOption(selectedType)) {\n      return 0;\n    }\n    \n    const keys = getInstallationCostKeys(selectedType);\n    \n    // If no valid keys found and not product-only, return 0 (safe default)\n    // Do NOT default to 'shell' as that would incorrectly charge for installation\n    if (!keys.length) {\n      return 0;\n    }\n    \n    return keys.reduce((sum, k) => sum + normalizeNumber(data?.[k], 0), 0);\n  };\n\n  const selectInstallationCost = (data, selectedType) => {\n    // Back-compat alias (older code expects this name)\n    return computeInstallationCost(data, selectedType);\n  };\n\n  const createInstallationCostClient = ({\n    endpointPath = '/apps/zip-code-validator/api/final-price',\n    timeoutMs = 15000,\n    debounceMs = 250,\n  } = {}) => {\n    let abortController = null;\n    let timer = null;\n    let lastPayloadKey = null;\n    let lastRaw = null;\n\n    // Handle both absolute and relative URLs correctly\n    let endpointUrl;\n    try {\n      // If endpointPath is already absolute, use it directly\n      if (endpointPath.startsWith('http://') || endpointPath.startsWith('https://')) {\n        endpointUrl = endpointPath;\n      } else {\n        // For relative paths, construct with base URL\n        endpointUrl = new URL(endpointPath, window.location.origin).toString();\n      }\n    } catch (e) {\n      console.error('Invalid endpoint URL:', endpointPath, e);\n      endpointUrl = endpointPath; // Fallback to original\n    }\n\n    const fetchInstallationCost = async (payload) => {\n      if (abortController) {\n        abortController.abort();\n      }\n      abortController = new AbortController();\n\n      const timeoutId = setTimeout(() => {\n        if (abortController) {\n          abortController.abort();\n        }\n      }, timeoutMs);\n      \n      try {\n        const res = await fetch(endpointUrl, {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify(payload),\n          signal: abortController.signal,\n        });\n\n        if (!res.ok) {\n          throw new Error(`Installation cost request failed (${res.status})`);\n        }\n        \n        const json = await res.json();\n        // Debug: print raw installation API response in the browser console.\n        // eslint-disable-next-line no-console\n        console.log('[final-price api] response:', json.data);\n        const ok = Boolean(json?.success ?? json?.status);\n        if (!ok) {\n          throw new Error(json?.message || 'Installation cost request failed');\n        }\n        return json?.data || null;\n      } catch (error) {\n        // Handle abort errors gracefully\n        if (error.name === 'AbortError') {\n          throw new Error('Installation cost request timed out or was cancelled');\n        }\n        throw error;\n      } finally {\n        clearTimeout(timeoutId);\n        abortController = null;\n      }\n    };\n\n    /**\n     * Debounced fetch with simple de-dupe keying.\n     * @returns Promise<{ installation: { ...raw, selected_type, selected_cost }, raw }>\n     */\n    const queue = ({ payload, selectedType }) => new Promise((resolve, reject) => {\n      const payloadKey = JSON.stringify(payload);\n      const selected = String(selectedType || 'shell');\n\n      // If the payload hasn't changed and we already have raw data, return immediately with\n      // a recomputed selected_cost (no network).\n      if (payloadKey === lastPayloadKey && lastRaw) {\n        const selected_cost = computeInstallationCost(lastRaw, selected);\n        resolve({\n          raw: lastRaw,\n          meta: { didFetch: false, fromCache: true, payloadKey },\n          installation: { ...lastRaw, selected_type: selected, selected_cost },\n        });\n        return;\n      }\n\n      lastPayloadKey = payloadKey;\n\n      if (timer) {\n        clearTimeout(timer);\n        timer = null;\n      }\n      \n      timer = setTimeout(async () => {\n        timer = null; // Clear timer reference when executing\n        try {\n          const raw = await fetchInstallationCost(payload);\n          lastRaw = raw;\n          const selected_cost = computeInstallationCost(raw, selected);\n          resolve({\n            raw,\n            meta: { didFetch: true, fromCache: false, payloadKey },\n            installation: raw ? { ...raw, selected_type: selected, selected_cost } : null,\n          });\n        } catch (e) {\n          reject(e);\n        }\n      }, debounceMs);\n    });\n\n    return { queue };\n  };\n\n  /**\n   * Shared glue: given a configurator-like context and a client, build payload, queue request,\n   * and return normalized structures usable by all modals.\n   *\n   * @param {{\n   *  client: { queue: Function },\n   *  zip: string,\n   *  modelRaw: string,\n   *  depth?: number,\n   *  length?: number,\n   *  currentConfig?: any,\n   *  selectedType?: string,\n   * }} args\n   */\n  const queueInstallationPricing = async ({\n    client,\n    zip,\n    modelRaw,\n    depth,\n    length,\n    currentConfig,\n    selectedType,\n    base_product_price,\n  }) => {\n    if (!client?.queue) throw new Error('Missing installation cost client');\n\n    const interior = pickFirst(currentConfig, ['interior', 'interiorFinish', 'mainFloorFinish', 'flooring']);\n    const kitchen = pickFirst(currentConfig, ['kitchen']);\n    const roofStyle = pickFirst(currentConfig, ['roofStyle', 'roof', 'roofType']);\n\n    const payload = buildInstallationCostPayload({\n      zip,\n      model: modelRaw,\n      depth,\n      length,\n      interior: interior || undefined,\n      kitchen: kitchen || undefined,\n      roofStyle: roofStyle || undefined,\n      base_product_price,\n    });\n\n    const result = await client.queue({ payload, selectedType });\n    if (!result) return null;\n\n    // Extract data from new API response format\n    // API returns: { product_price, shipping_cost, installation_cost, total_price, breakdown: { product, shipping, installation } }\n    const raw = result.raw || {};\n    const breakdownInstallation = raw.breakdown?.installation || {};\n    const breakdownShipping = raw.breakdown?.shipping || {};\n    \n    // Build installation object with prices from breakdown\n    const installationWithPrices = {\n      shell: normalizeNumber(breakdownInstallation.shell, 0),\n      lifestyle: normalizeNumber(breakdownInstallation.lifestyle, 0),\n      kitchenbath: normalizeNumber(breakdownInstallation.kitchenbath, 0),\n      selected_type: breakdownInstallation.selected_type || selectedType,\n      selected_cost: normalizeNumber(breakdownInstallation.selected_cost || raw.installation_cost, 0),\n      state_code: breakdownInstallation.state_code,\n      zone: breakdownInstallation.zone,\n    };\n\n    const finalPriceData = {\n      breakdown: { \n        installation: installationWithPrices,\n        // Store individual prices for easy access in services tab\n        installationPrices: {\n          shell: normalizeNumber(breakdownInstallation.shell, 0),\n          lifestyle: normalizeNumber(breakdownInstallation.lifestyle, 0),\n          kitchenbath: normalizeNumber(breakdownInstallation.kitchenbath, 0),\n        },\n        // Store shipping breakdown for detailed display\n        shipping: {\n          base_subtotal: normalizeNumber(breakdownShipping.base_subtotal, 0),\n          depth_surcharge: normalizeNumber(breakdownShipping.depth_surcharge, 0),\n          interior_surcharge: normalizeNumber(breakdownShipping.interior_surcharge, 0),\n          inflation_surcharge: normalizeNumber(breakdownShipping.inflation_surcharge, 0),\n        },\n        // Store product breakdown\n        product: {\n          base_price: normalizeNumber(raw.breakdown?.product?.base_price || raw.product_price, 0),\n        },\n      },\n      installation_cost: normalizeNumber(raw.installation_cost || installationWithPrices.selected_cost, 0),\n      // Include shipping cost from API response (new format uses shipping_cost at top level)\n      shipping_cost: normalizeNumber(raw.shipping_cost, 0),\n      // Include total price from API\n      total_price: normalizeNumber(raw.total_price, 0),\n      product_price: normalizeNumber(raw.product_price, 0),\n    };\n\n    return { \n      payload, \n      raw: result.raw, \n      installation: installationWithPrices, \n      finalPriceData, \n      meta: result.meta || null \n    };\n  };\n\n  const applyInstallationCostsToServiceOptions = ({\n    container,\n    installation,\n    optionValues = null,\n    cardSelector = '[data-service-option]',\n    valueDatasetKey = 'value',\n    priceSelector = '[data-service-price]',\n    formatPrice = formatMoneyFromDollars,\n    prefixPlus = false,\n  }) => {\n    if (!container || !installation) return;\n\n    const ensurePriceEl = (card) => {\n      const existing = card.querySelector(priceSelector);\n      if (existing) return existing;\n\n      // Create a placeholder price element so it can be updated dynamically after the ZIP API returns.\n      const header =\n        card.querySelector('[data-service-header]') ||\n        card.querySelector('.service-option-header') ||\n        card;\n\n      const span = document.createElement('span');\n      span.className = 'service-price';\n      span.setAttribute('data-service-price', '');\n\n      const checkmark = header.querySelector('[data-service-checkmark]') || header.querySelector('.service-checkmark');\n      if (checkmark && checkmark.parentNode === header) {\n        header.insertBefore(span, checkmark);\n      } else {\n        header.appendChild(span);\n      }\n      return span;\n    };\n\n    const updateCard = (card, value) => {\n      const keys = getInstallationCostKeys(value);\n      if (!keys.length) return;\n      \n      // For lifestyle option (value contains \"lifestyle\"), show only lifestyle price, not shell + lifestyle\n      const valueLower = String(value || '').toLowerCase().trim();\n      const containsLifestyle = valueLower.includes('lifestyle');\n      const keysToUse = containsLifestyle ? ['lifestyle'] : keys;\n      \n      const cost = keysToUse.reduce((sum, k) => sum + normalizeNumber(installation?.[k], 0), 0);\n\n      card.dataset.price = String(cost);\n      const priceEl = ensurePriceEl(card);\n      const formatted = formatPrice(cost, { noDecimals: true });\n      priceEl.textContent = prefixPlus ? `+${formatted}` : formatted;\n    };\n\n    // If explicit option values were provided, update those; otherwise, discover from DOM.\n    if (Array.isArray(optionValues) && optionValues.length) {\n      for (const value of optionValues) {\n        const card = container.querySelector(`${cardSelector}[data-${valueDatasetKey}=\"${CSS.escape(value)}\"]`);\n        if (!card) continue;\n        updateCard(card, value);\n      }\n      return;\n    }\n\n    container.querySelectorAll(cardSelector).forEach((card) => {\n      const value = card?.dataset?.[valueDatasetKey];\n      if (!value) return;\n      updateCard(card, value);\n    });\n  };\n\n  /**\n   * Robust notification helper - ensures errors/success messages are always shown to users.\n   * Falls back gracefully if notification element is not available.\n   * @param {'error' | 'success'} type - Notification type\n   * @param {string} message - The message to display\n   * @param {string} [title] - Optional title (defaults based on type)\n   */\n  const showNotification = (type, message, title) => {\n    const defaultTitle = type === 'error' ? 'Error' : 'Success';\n    const displayTitle = title || defaultTitle;\n    const displayMessage = typeof message === 'string' ? message : String(message || 'An error occurred');\n\n    // Try using the existing Utility helper first\n    if (type === 'error' && typeof root.Utility?.handleError === 'function') {\n      try {\n        root.Utility.handleError(displayMessage, displayTitle);\n        return;\n      } catch (e) {\n        // Fall through to fallback\n      }\n    }\n\n    if (type === 'success' && typeof root.Utility?.handleSuccess === 'function') {\n      try {\n        root.Utility.handleSuccess(displayMessage, displayTitle);\n        return;\n      } catch (e) {\n        // Fall through to fallback\n      }\n    }\n\n    // Fallback: try notification element directly\n    if (root.notificationEle && typeof root.notificationEle.updateNotification === 'function') {\n      try {\n        root.notificationEle.updateNotification(displayTitle, displayMessage, {\n          type: type,\n          timeout: type === 'error' ? 10000 : 5000\n        });\n        return;\n      } catch (e) {\n        // Fall through to console\n      }\n    }\n\n    // Final fallback: console output (never silently fail)\n    if (type === 'error') {\n      console.error(`[${displayTitle}]`, displayMessage);\n    } else {\n      console.info(`[${displayTitle}]`, displayMessage);\n    }\n  };\n\n  /**\n   * Show an error notification to the user\n   * @param {string} message - Error message\n   * @param {string} [title] - Optional title\n   */\n  const showError = (message, title) => showNotification('error', message, title);\n\n  /**\n   * Show a success notification to the user\n   * @param {string} message - Success message\n   * @param {string} [title] - Optional title\n   */\n  const showSuccess = (message, title) => showNotification('success', message, title);\n\n  /**\n   * Show contact link on API error\n   * @param {HTMLElement} container - The container element (form or step2Section)\n   * @param {string} [contactUrl='/pages/contact-us'] - The contact page URL\n   */\n  const showContactLinkOnError = (container, contactUrl = '/pages/contact-us') => {\n    if (!container) return;\n    \n    // Remove any existing contact link\n    const existingLink = container.querySelector('[data-contact-error-link]');\n    if (existingLink) existingLink.remove();\n    \n    // Also check parent for existing link\n    const parentExisting = container.parentElement?.querySelector('[data-contact-error-link]');\n    if (parentExisting) parentExisting.remove();\n\n    // Find the footer with Save Design button\n    const footer = container.closest('[data-panel-content-wrapper]')?.parentElement?.querySelector('[data-panel-footer]') || \n                   container.querySelector('[data-panel-footer]') ||\n                   container.closest('[data-configurator-step=\"2\"]')?.querySelector('[data-panel-footer]');\n    \n    if (footer) {\n      // Remove any existing contact link in footer\n      const footerExisting = footer.querySelector('[data-contact-error-link]');\n      if (footerExisting) footerExisting.remove();\n      \n      const contactLink = document.createElement('div');\n      contactLink.setAttribute('data-contact-error-link', '');\n      contactLink.className = 'text-center mt-3';\n      contactLink.innerHTML = `\n        <p class=\"mb-0 fs-sm\" style=\"color: #666;\">\n          Having trouble? <a href=\"${contactUrl}\" class=\"text-primary fw-medium\" style=\"text-decoration: underline;\">Contact us</a> for assistance.\n        </p>\n      `;\n      footer.appendChild(contactLink);\n    }\n  };\n\n  /**\n   * Validates a ZIP code by making an immediate (non-debounced) API call.\n   * This is used to gate step progression - the step should NOT advance until this returns successfully.\n   * \n   * @param {{\n   *   zip: string,\n   *   model: string,\n   *   depth?: number,\n   *   length?: number,\n   *   interior?: string,\n   *   kitchen?: string,\n   *   roofStyle?: string,\n   *   endpointPath?: string,\n   *   timeoutMs?: number,\n   * }} params\n   * @returns {Promise<{ data: object, raw: object }>} - Resolves with API data on success\n   * @throws {Error} - Rejects with error on failure (non-200, validation error, timeout, etc.)\n   */\n  const validateZipCodeWithAPI = async ({\n    zip,\n    model,\n    depth,\n    length,\n    interior,\n    kitchen,\n    roofStyle,\n    base_product_price,\n    endpointPath = '/apps/zip-code-validator/api/final-price',\n    timeoutMs = 15000,\n  }) => {\n    const zipTrimmed = String(zip || '').trim();\n    \n    // Basic client-side validation first\n    if (!zipTrimmed) {\n      throw new Error('Please enter a valid ZIP code.');\n    }\n    \n    // US ZIP code format validation (5 digits or 5+4 format)\n    const zipPattern = /^\\d{5}(-\\d{4})?$/;\n    if (!zipPattern.test(zipTrimmed)) {\n      throw new Error('Please enter a valid 5-digit ZIP code.');\n    }\n\n    // DUPLICATE CALL PROTECTION: Check if this exact request is already in progress\n    if (!window._zipValidationCalls) {\n      window._zipValidationCalls = new Map();\n    }\n    \n    // Create a unique key for this request\n    const requestKey = `${zipTrimmed}_${model}_${depth}_${length}_${base_product_price}`;\n    const existingCall = window._zipValidationCalls.get(requestKey);\n    const now = Date.now();\n    \n    // If a request with the same parameters is already in progress (within 3 seconds), return it\n    if (existingCall && (now - existingCall.timestamp) < 3000) {\n      console.log('[ZIP Validation] Returning cached promise for duplicate call', { requestKey });\n      return existingCall.promise;\n    }\n\n    const payload = buildInstallationCostPayload({\n      zip: zipTrimmed,\n      model,\n      depth,\n      length,\n      interior,\n      kitchen,\n      roofStyle,\n      base_product_price,\n    });\n\n    // Handle URL construction\n    let endpointUrl;\n    try {\n      if (endpointPath.startsWith('http://') || endpointPath.startsWith('https://')) {\n        endpointUrl = endpointPath;\n      } else {\n        endpointUrl = new URL(endpointPath, root.location.origin).toString();\n      }\n    } catch (e) {\n      endpointUrl = endpointPath;\n    }\n\n    const abortController = new AbortController();\n    const timeoutId = setTimeout(() => abortController.abort(), timeoutMs);\n\n    // Create the API promise and store it to prevent duplicate calls\n    const apiPromise = (async () => {\n      try {\n        console.log('[ZIP Validation] Making API request', { requestKey, endpoint: endpointUrl });\n        \n        const res = await fetch(endpointUrl, {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify(payload),\n          signal: abortController.signal,\n        });\n\n        // CRITICAL: Check for non-200 response status\n        if (!res.ok) {\n          if (res.status === 400) {\n            throw new Error('Invalid ZIP code. Please check and try again.');\n          } else if (res.status === 404) {\n            throw new Error('Service not available for this ZIP code.');\n          } else if (res.status >= 500) {\n            throw new Error('Service temporarily unavailable. Please try again.');\n          }\n          throw new Error(`Validation failed (${res.status}). Please try again.`);\n        }\n\n        const json = await res.json();\n        \n        // Check for API-level success/failure\n        const isSuccess = Boolean(json?.success ?? json?.status);\n        if (!isSuccess) {\n          const apiMessage = json?.message || json?.error || 'ZIP code validation failed.';\n          throw new Error(apiMessage);\n        }\n\n        // Check if data is present\n        const data = json?.data;\n        if (!data) {\n          throw new Error('No pricing data available for this ZIP code.');\n        }\n\n        return { data, raw: json, payload };\n      } catch (error) {\n        if (error.name === 'AbortError') {\n          throw new Error('Request timed out. Please check your connection and try again.');\n        }\n        // Re-throw with user-friendly message if it's a network error\n        if (error.message === 'Failed to fetch' || error.message.includes('NetworkError')) {\n          throw new Error('Network error. Please check your connection and try again.');\n        }\n        throw error;\n      } finally {\n        clearTimeout(timeoutId);\n        // Remove from cache when done (success or error)\n        window._zipValidationCalls.delete(requestKey);\n      }\n    })();\n\n    // Store the promise immediately to prevent duplicate calls\n    window._zipValidationCalls.set(requestKey, { promise: apiPromise, timestamp: now });\n    \n    // Clean up old entries (older than 10 seconds)\n    for (const [key, value] of window._zipValidationCalls.entries()) {\n      if (now - value.timestamp > 10000) {\n        window._zipValidationCalls.delete(key);\n      }\n    }\n\n    return apiPromise;\n  };\n\n  /**\n   * Format phone number to 123-456-7890 format\n   */\n  const formatPhoneNumber = (phone) => {\n    if (!phone) return undefined;\n    // Remove all non-digits\n    const digits = String(phone).replace(/\\D/g, '');\n    // Remove leading 1 if present\n    const cleaned = digits.startsWith('1') && digits.length === 11 ? digits.slice(1) : digits;\n    // Format to 123-456-7890\n    if (cleaned.length === 10) {\n      return `${cleaned.slice(0, 3)}-${cleaned.slice(3, 6)}-${cleaned.slice(6)}`;\n    }\n    return phone; // Return as-is if can't format\n  };\n\n  /**\n   * Convert kebab-case to Title Case\n   * Example: 'iron-gray' -> 'Iron Gray'\n   */\n  const kebabToTitleCase = (str) => {\n    if (!str) return '';\n    return String(str)\n      .split('-')\n      .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n      .join(' ');\n  };\n\n  /**\n   * Build customerData payload for Save Design API\n   */\n  const buildCustomerData = (formData) => {\n    const phone = formatPhoneNumber(formData.phone);\n    return {\n      first_name: String(formData.firstName || '').trim(),\n      last_name: String(formData.lastName || '').trim(),\n      email: String(formData.email || '').trim(),\n      zip: String(formData.zipCode || '').trim(),\n      ...(phone && { phone }),\n      is_demo: false\n    };\n  };\n\n  /**\n   * Build designData payload for Save Design API\n   * \n   * Required fields by model type:\n   * - Aspect: orientation, interiorKit, accentColor, layout, kitchen, exterior, roof, mainFloorFinish, bathFloorFinish, fixtureFinish, counterFinish, cabinetFinish\n   * - Summit: orientation, interiorKit, summitCode, summitOption1, summitOption2, summitOption3, mainFloorFinish, bathFloorFinish, fixtureFinish, counterFinish, cabinetFinish\n   * - Signature/Portland: interiorKit, mainFloorFinish, frontElevation, backElevation, leftElevation, rightElevation, exteriorAddon, roofAddon\n   */\n  const buildDesignData = (configurator) => {\n    const modelSlug = configurator.getModelSlug?.() || configurator.modelConfig?.name || 'summit';\n    const modelsData = configurator.modelsData || {};\n    const currentConfig = configurator.currentConfig || {};\n    \n    const designData = {\n      model: modelSlug,\n      size: modelsData.size || configurator.modelConfig?.size,\n      area: normalizeNumber(modelsData.squareFootage || configurator.modelConfig?.squareFootage, undefined),\n      length: normalizeNumber(modelsData.length || configurator.modelConfig?.length, undefined),\n      depth: normalizeNumber(modelsData.depth || configurator.modelConfig?.depth, undefined),\n    };\n\n    // ============================================================================\n    // COMMON FIELDS (all models)\n    // ============================================================================\n    \n    // Orientation (L/R)\n    if (currentConfig.orientation) {\n      // Normalize to \"L\" or \"R\" for API\n      const orientVal = String(currentConfig.orientation).toLowerCase();\n      designData.orientation = orientVal.startsWith('l') ? 'L' : 'R';\n    }\n    \n    // Interior Kit (lifestyle/shell-only)\n    if (currentConfig.interiorOptions || currentConfig.interiorPackage || currentConfig.interior) {\n      designData.interiorKit = currentConfig.interiorOptions || currentConfig.interiorPackage || currentConfig.interior;\n    }\n    \n    // Siding\n    if (currentConfig.siding) designData.siding = currentConfig.siding;\n    \n    // Handle sidingColor - can be string or object with main/accent properties\n    if (currentConfig.sidingColor) {\n      if (typeof currentConfig.sidingColor === 'object' && currentConfig.sidingColor !== null) {\n        if (currentConfig.sidingColor.accent) {\n          designData.sidingColor = currentConfig.sidingColor.main || currentConfig.sidingColor;\n          designData.accentColor = currentConfig.sidingColor.accent;\n        } else if (currentConfig.sidingColor.main) {\n          designData.sidingColor = currentConfig.sidingColor.main;\n        }\n      } else {\n        designData.sidingColor = currentConfig.sidingColor;\n      }\n    }\n    \n    // Add accentColor if it exists separately\n    if (currentConfig.accentColor) designData.accentColor = currentConfig.accentColor;\n    \n    // Other color fields\n    if (currentConfig.trimColor) designData.trimColor = currentConfig.trimColor;\n    if (currentConfig.doorColor) designData.doorColor = currentConfig.doorColor;\n    if (currentConfig.fasciaColor) designData.fasciaColor = currentConfig.fasciaColor;\n    if (currentConfig.soffitColor) designData.soffitColor = currentConfig.soffitColor;\n    if (currentConfig.eavesColor) designData.eavesColor = currentConfig.eavesColor;\n    if (currentConfig.windowCasement) designData.windowCasement = currentConfig.windowCasement;\n    \n    // Interior finish options (shared by Aspect, Summit, Signature, Portland when Lifestyle selected)\n    if (currentConfig.mainFloorFinish || currentConfig.flooring) {\n      designData.mainFloorFinish = currentConfig.mainFloorFinish || currentConfig.flooring;\n    }\n    if (currentConfig.bathFloorFinish) designData.bathFloorFinish = currentConfig.bathFloorFinish;\n    if (currentConfig.fixtureFinish || currentConfig.fixture) {\n      designData.fixtureFinish = currentConfig.fixtureFinish || currentConfig.fixture;\n    }\n    if (currentConfig.counterFinish || currentConfig.countertop) {\n      designData.counterFinish = currentConfig.counterFinish || currentConfig.countertop;\n    }\n    if (currentConfig.cabinetFinish || currentConfig.cabinets) {\n      designData.cabinetFinish = currentConfig.cabinetFinish || currentConfig.cabinets;\n    }\n\n    // ============================================================================\n    // ASPECT-SPECIFIC FIELDS\n    // ============================================================================\n    if (modelSlug.startsWith('aspect') || modelSlug === 'aspect') {\n      if (currentConfig.layout) designData.layout = currentConfig.layout;\n      if (currentConfig.kitchen || currentConfig.kitchenConfig) {\n        designData.kitchen = currentConfig.kitchen || currentConfig.kitchenConfig;\n      }\n      if (currentConfig.exterior || currentConfig.windowConfig || currentConfig.exteriorGlassOption) {\n        designData.exterior = currentConfig.exterior || currentConfig.windowConfig || currentConfig.exteriorGlassOption;\n      }\n      if (currentConfig.roof || currentConfig.roofType || currentConfig.roofStyle) {\n        designData.roof = currentConfig.roof || currentConfig.roofType || currentConfig.roofStyle;\n      }\n      if (currentConfig.smallHomeKitchen) designData.smallHomeKitchen = currentConfig.smallHomeKitchen;\n    }\n\n    // ============================================================================\n    // SUMMIT-SPECIFIC FIELDS\n    // ============================================================================\n    if (modelSlug.startsWith('summit')) {\n      // Summit NX Code (e.g., \"A-1-a\")\n      if (currentConfig.summitNxCode) {\n        designData.summitCode = currentConfig.summitNxCode;\n      }\n      \n      // Summit options (derive from configuration selections)\n      // These map to entryLocation, windowOptions, sideOptions etc.\n      if (currentConfig.entryLocation || currentConfig.layout) {\n        designData.summitOption1 = currentConfig.entryLocation || currentConfig.layout;\n      }\n      if (currentConfig.windowOptions || currentConfig.exterior || currentConfig.windowConfig) {\n        designData.summitOption2 = currentConfig.windowOptions || currentConfig.exterior || currentConfig.windowConfig;\n      }\n      if (currentConfig.sideOptions) {\n        designData.summitOption3 = currentConfig.sideOptions;\n      }\n    }\n\n    // ============================================================================\n    // SIGNATURE/PORTLAND-SPECIFIC FIELDS\n    // ============================================================================\n    if (modelSlug.includes('signature') || modelSlug.includes('portland')) {\n      // Panel elevations (these store the elevation code like \"PE12-4-W3C-D72C\")\n      if (currentConfig.frontPanel) designData.frontElevation = currentConfig.frontPanel;\n      if (currentConfig.backPanel) designData.backElevation = currentConfig.backPanel;\n      if (currentConfig.leftPanel) designData.leftElevation = currentConfig.leftPanel;\n      if (currentConfig.rightPanel) designData.rightElevation = currentConfig.rightPanel;\n      \n      // Exterior addon (metal wainscot)\n      if (currentConfig.exteriorAddon) {\n        designData.exteriorAddon = currentConfig.exteriorAddon === 'metal-wainscot' ? 'metal' : 'none';\n      }\n      \n      // Roof addon (shortened back roof - B6)\n      if (currentConfig.roofAddon) {\n        const roofAddonVal = String(currentConfig.roofAddon).toLowerCase();\n        designData.roofAddon = roofAddonVal.includes('b6') || roofAddonVal.includes('shortened') ? 'b6' : 'none';\n      }\n      \n      // Roof style\n      if (currentConfig.roofStyle || currentConfig.roof || currentConfig.roofType) {\n        designData.roofStyle = currentConfig.roofStyle || currentConfig.roof || currentConfig.roofType;\n      }\n    }\n\n    // ============================================================================\n    // CARRIAGE HOUSE FIELDS\n    // ============================================================================\n    if (modelSlug.includes('carriage')) {\n      if (currentConfig.roofStyle) designData.roofStyle = currentConfig.roofStyle;\n      // Map garageAccess config key to garageDoor API field, default to back-door\n      designData.garageDoor = currentConfig.garageAccess\n        ? String(currentConfig.garageAccess).trim().toLowerCase().replace(/\\s+/g, '-')\n        : 'back-door';\n      // Default stairOption \u2014 carriage house only has interior stairs currently\n      designData.stairOption = 'interior-stair';\n      // Compute useCase for installation pricing and summary image rendering\n      designData.useCase = 'carriage-house-' + (designData.roofStyle || 'gable');\n    }\n    \n    // Add roofStyle for other models that support it (fallback)\n    if (currentConfig.roofStyle && !designData.roofStyle) {\n      designData.roofStyle = currentConfig.roofStyle;\n    }\n\n    // Remove undefined values\n    Object.keys(designData).forEach(key => {\n      if (designData[key] === undefined) delete designData[key];\n    });\n\n    return designData;\n  };\n\n  /**\n   * Build labelData payload for Save Design API\n   */\n  const buildLabelData = (configurator) => {\n    const modelSlug = configurator.getModelSlug?.() || configurator.modelConfig?.name || 'summit';\n    const currentConfig = configurator.currentConfig || {};\n    const modelConfig = configurator.modelConfig || {};\n    \n    const labelData = {\n      model: kebabToTitleCase(modelSlug.split('-')[0]) // 'summit' -> 'Summit'\n    };\n\n    // Add Summit-specific labels\n    if (modelSlug.startsWith('summit')) {\n      if (currentConfig.siding) {\n        const step = modelConfig.steps?.find(s => s.options?.siding);\n        const option = step?.options?.siding?.choices?.find(c => c.value === currentConfig.siding);\n        labelData.siding = option?.displayName || kebabToTitleCase(currentConfig.siding);\n      }\n      if (currentConfig.sidingColor) {\n        const step = modelConfig.steps?.find(s => s.options?.sidingColor);\n        const option = step?.options?.sidingColor?.choices?.find(c => c.value === currentConfig.sidingColor);\n        labelData.sidingColor = option?.displayName || kebabToTitleCase(currentConfig.sidingColor);\n      }\n      if (currentConfig.summitNxCode) {\n        labelData.summitNxCode = currentConfig.summitNxCode;\n      }\n      if (currentConfig.interiorOptions || currentConfig.interiorPackage) {\n        const interiorValue = currentConfig.interiorOptions || currentConfig.interiorPackage;\n        const step = modelConfig.steps?.find(s => s.options?.interiorOptions);\n        const option = step?.options?.interiorOptions?.choices?.find(c => c.value === interiorValue);\n        labelData.interiorKit = option?.displayName || kebabToTitleCase(interiorValue);\n      }\n    }\n\n    return labelData;\n  };\n\n  /**\n   * Build costData payload for Save Design API\n   */\n  /**\n   * Resolve the product price from the pricing matrix if available.\n   * Falls back to basePrice if no matrix or no match found.\n   */\n  const resolveMatrixPrice = (configurator) => {\n    const pricing = configurator.modelConfig?.pricing || configurator.pricingConfig;\n    const currentConfig = configurator.currentConfig || {};\n    if (!pricing?.matrix) return null;\n\n    const availableKeys = Object.keys(pricing.matrix);\n    if (availableKeys.length === 0) return null;\n\n    // Collect all current config values for matching\n    const configValues = new Set();\n    for (const key of Object.keys(currentConfig)) {\n      const val = currentConfig[key];\n      if (typeof val === 'string' && val) configValues.add(val);\n    }\n\n    // Try to find a matrix key where all pipe-separated parts match config values\n    let matrixEntry = null;\n    for (const key of availableKeys) {\n      const parts = key.split('|');\n      if (parts.every(p => configValues.has(p))) {\n        matrixEntry = pricing.matrix[key];\n        break;\n      }\n    }\n\n    if (!matrixEntry) return null;\n\n    // Resolve interior option price from the matched entry\n    const interior = currentConfig.interiorOptions || currentConfig.interiorPackage || 'shell-only';\n    // Try exact match, then common aliases\n    return matrixEntry[interior] ?? matrixEntry['shell-only'] ?? null;\n  };\n\n  const buildCostData = (configurator) => {\n    const basePrice = normalizeNumber(configurator.basePrice || 0, 0);\n    const configPrice = normalizeNumber(configurator.configPrice || 0, 0);\n    const finalPriceData = configurator.finalPriceData || {};\n    const currentConfig = configurator.currentConfig || {};\n\n    // Use matrix-resolved price if available, otherwise fall back to basePrice\n    const matrixPrice = resolveMatrixPrice(configurator);\n    const productPrice = matrixPrice !== null ? matrixPrice : basePrice + configPrice;\n\n    const costData = {\n      total: productPrice,\n      shell: matrixPrice !== null ? matrixPrice : basePrice,\n    };\n\n    // Add interior kit cost\n    const interiorOption = currentConfig.interiorOptions || currentConfig.interiorPackage;\n    if (interiorOption && interiorOption !== 'shell-only') {\n      const modelConfig = configurator.modelConfig || {};\n      const step = modelConfig.steps?.find(s => s.options?.interiorOptions);\n      const option = step?.options?.interiorOptions?.choices?.find(c => c.value === interiorOption);\n      if (option?.price) {\n        costData.interiorKit = normalizeNumber(option.price, 0);\n      }\n    }\n\n    // Add installation costs\n    const installationPrices = finalPriceData.breakdown?.installationPrices || {};\n    if (installationPrices.shell) costData.shellInstall = normalizeNumber(installationPrices.shell, 0);\n    if (installationPrices.lifestyle) costData.lifestyleInstall = normalizeNumber(installationPrices.lifestyle, 0);\n    if (installationPrices.kitchenbath) costData.lifestylePlusInstall = normalizeNumber(installationPrices.kitchenbath, 0);\n    \n    const selectedInstallation = currentConfig.installation || 'shell';\n    const installationCost = computeInstallationCost(\n      finalPriceData.breakdown?.installation,\n      selectedInstallation\n    ) || 0;\n    if (installationCost > 0) {\n      costData.installationSelection = normalizeNumber(installationCost, 0);\n    }\n\n    // Add shipping\n    if (finalPriceData.shipping_cost) {\n      costData.shipping = normalizeNumber(finalPriceData.shipping_cost, 0);\n    }\n\n    // Add permit plan\n    if (configurator.permitPlanSelected === 'include' && configurator.permitPlanPrice) {\n      costData.permitPlanSetSelection = normalizeNumber(configurator.permitPlanPrice, 0);\n    }\n\n    // Add other costs from config\n    const addCostFromConfig = (key, costKey) => {\n      const step = configurator.modelConfig?.steps?.find(s => s.options?.[key]);\n      const option = step?.options?.[key]?.choices?.find(c => c.value === currentConfig[key]);\n      if (option?.price) {\n        costData[costKey] = normalizeNumber(option.price, 0);\n      }\n    };\n\n    addCostFromConfig('soffitColor', 'soffitColor');\n    addCostFromConfig('roofAddon', 'roofAddon');\n    addCostFromConfig('exteriorAddon', 'exteriorAddon');\n    addCostFromConfig('siding', 'siding');\n    addCostFromConfig('sidingColor', 'sidingColor');\n    addCostFromConfig('trimColor', 'trimColor');\n    addCostFromConfig('doorColor', 'doorColor');\n    addCostFromConfig('roofStyle', 'roofStyle');\n\n    // Remove zero/undefined values (optional - API may accept them)\n    Object.keys(costData).forEach(key => {\n      if (costData[key] === undefined || costData[key] === null) delete costData[key];\n    });\n\n    return costData;\n  };\n\n  /**\n   * Call Save Design API\n   * @param {Object} configurator - The configurator instance\n   * @param {Object} formData - Form data with firstName, lastName, email, zipCode, phone\n   * @param {Object} options - Optional: screenshots (exteriorScreenshotData, interiorScreenshotData, summaryScreenshotData)\n   * @returns {Promise<Object>} API response\n   */\n  const saveDesignToAPI = async (configurator, formData, options = {}) => {\n    const API_TOKEN = window.STUDIO_SHED_DESIGN_API_TOKEN || ''; // Should be set in theme settings\n    const API_ENDPOINT = window.STUDIO_SHED_DESIGN_API_ENDPOINT || 'https://design.studio-shed.com/api/designs';\n    const requestId = options.requestId || `api_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;\n    \n    // Log entry point with stack trace to identify all callers\n    console.log('[Save Design API] FUNCTION CALLED', {\n      requestId,\n      timestamp: new Date().toISOString(),\n      endpoint: API_ENDPOINT,\n      email: formData?.email,\n      zipCode: formData?.zipCode,\n      stackTrace: new Error().stack\n    });\n    \n    // Check for duplicate calls using a global map\n    if (!window._saveDesignAPICalls) {\n      window._saveDesignAPICalls = new Map();\n    }\n    \n    // Check if this exact request is already in progress (same email + zip within 5 seconds)\n    const requestKey = `${formData?.email}_${formData?.zipCode}`;\n    const existingCall = window._saveDesignAPICalls.get(requestKey);\n    const now = Date.now();\n    \n    if (existingCall && (now - existingCall.timestamp) < 5000) {\n      console.warn('[Save Design API] DUPLICATE CALL DETECTED - returning cached promise', {\n        requestId,\n        existingRequestId: existingCall.requestId,\n        timestamp: new Date().toISOString(),\n        timeSinceLastCall: now - existingCall.timestamp\n      });\n      // Return the existing promise instead of blocking\n      return existingCall.promise;\n    }\n    \n    // Clean up old entries (older than 10 seconds)\n    for (const [key, value] of window._saveDesignAPICalls.entries()) {\n      if (now - value.timestamp > 10000) {\n        window._saveDesignAPICalls.delete(key);\n      }\n    }\n    \n    // Debug logging\n    console.log('[Save Design API] Checking configuration...', {\n      hasToken: !!API_TOKEN,\n      tokenLength: API_TOKEN ? API_TOKEN.length : 0,\n      endpoint: API_ENDPOINT\n    });\n    \n    if (!API_TOKEN) {\n      console.warn('[Save Design API] API token not configured. Set window.STUDIO_SHED_DESIGN_API_TOKEN to enable design saving.');\n      return { success: false, error: 'API token not configured' };\n    }\n\n    const payload = {\n      customerData: buildCustomerData(formData),\n      designData: buildDesignData(configurator),\n      labelData: buildLabelData(configurator),\n      costData: buildCostData(configurator),\n    };\n\n    // Add optional screenshots\n    if (options.exteriorScreenshotData) payload.exteriorScreenshotData = options.exteriorScreenshotData;\n    if (options.interiorScreenshotData) payload.interiorScreenshotData = options.interiorScreenshotData;\n    if (options.summaryScreenshotData) payload.summaryScreenshotData = options.summaryScreenshotData;\n\n    console.log('[Save Design API] Making request to:', API_ENDPOINT, {\n      requestId,\n      timestamp: new Date().toISOString()\n    });\n    console.log('[Save Design API] Payload keys:', Object.keys(payload));\n\n    // Create the API promise and store it to prevent duplicate calls\n    const apiPromise = (async () => {\n      try {\n        const response = await fetch(API_ENDPOINT, {\n          method: 'POST',\n          headers: {\n            'Authorization': `Bearer ${API_TOKEN}`,\n            'Content-Type': 'application/json',\n            'Accept': 'application/json'\n          },\n          body: JSON.stringify(payload)\n        });\n\n        console.log('[Save Design API] Response status:', response.status, response.statusText);\n\n        if (!response.ok) {\n          const errorData = await response.json().catch(() => ({}));\n          console.error('[Save Design API] Error response:', errorData);\n          throw new Error(errorData.message || `API request failed with status ${response.status}`);\n        }\n\n        const result = await response.json();\n        console.log('[Save Design API] Success:', {\n          requestId,\n          ...result\n        });\n\n        // Store visitor identity for HubSpot chat widget identification\n        if (window.VisitorIdentity && formData.email) {\n          window.VisitorIdentity.store(formData.email, formData.firstName, formData.lastName);\n        }\n\n        return { success: true, ...result };\n      } catch (error) {\n        // Enhanced error logging for CORS/network issues\n        if (error.name === 'TypeError' && error.message === 'Failed to fetch') {\n          console.error('[Save Design API] Network/CORS error. Check:', {\n            endpoint: API_ENDPOINT,\n            hasToken: !!API_TOKEN,\n            tokenLength: API_TOKEN ? API_TOKEN.length : 0,\n            error: error.message,\n            suggestion: 'This might be a CORS issue. Verify the endpoint allows requests from your domain.'\n          });\n        } else {\n          console.error('[Save Design API] Request failed:', {\n            requestId,\n            error: error.message\n          });\n        }\n        \n        throw error;\n      } finally {\n        // Remove from tracking map when done (success or error)\n        window._saveDesignAPICalls.delete(requestKey);\n      }\n    })();\n\n    // Store the promise immediately to prevent duplicate calls\n    window._saveDesignAPICalls.set(requestKey, { requestId, timestamp: now, promise: apiPromise });\n\n    return apiPromise;\n  };\n\n  // ============================================================================\n  // PERMIT MANAGER - Shared permit system for all configurators\n  // ============================================================================\n\n  /**\n   * PermitManager - Centralized permit plan handling for all configurator modals.\n   * Eliminates duplicate permit code across carriage, portland, signature, aspect, and summit modals.\n   * \n   * @param {Object} config - Configuration object\n   * @param {Object} config.configurator - Parent configurator instance (required)\n   * @param {string} config.drawerId - DOM ID of the permit drawer element (required)\n   * @param {number} [config.basePrice=6995] - Base permit price in dollars (Signature: 5995, others: 6995)\n   * @param {Function} [config.formatPrice] - Price formatter function (defaults to formatPriceSmart)\n   * @param {Function} [config.onSelect] - Callback after permit selection (e.g., recalculate totals)\n   * @param {HTMLElement} [config.step2Section] - Step 2 section element for DOM queries\n   */\n  class PermitManager {\n    constructor(config) {\n      if (!config?.configurator) {\n        console.error('[PermitManager] Missing required configurator instance');\n        return;\n      }\n      if (!config?.drawerId) {\n        console.error('[PermitManager] Missing required drawerId');\n        return;\n      }\n\n      this.configurator = config.configurator;\n      this.drawerId = config.drawerId;\n      this.basePrice = normalizeNumber(config.basePrice, 6995);\n      this.formatPrice = config.formatPrice || formatPriceSmart;\n      this.onSelect = config.onSelect || null;\n      this.step2Section = config.step2Section || null;\n\n      // State - also synced to configurator for backward compatibility\n      this.permitDrawer = null;\n      this.defaultSelected = config.defaultSelected\n        ?? config.configurator?.permitPlanSelected\n        ?? 'exclude';\n      this.selected = this.defaultSelected;  // 'exclude', 'none', or 'include'\n      this.price = this.selected === 'include' ? this.basePrice : 0;\n      \n      console.log(`[Permit Constructor] Set! Argument inside config: ${config.defaultSelected}. Final resolved default: ${this.defaultSelected}`);\n\n      // Sync initial state to configurator\n      this._syncToConfigurator();\n    }\n\n    /**\n     * Sync permit state to the parent configurator for backward compatibility\n     * with existing price calculation code.\n     */\n    _syncToConfigurator() {\n      if (this.configurator) {\n        this.configurator.permitPlanSelected = this.selected;\n        this.configurator.permitPlanPrice = this.price;\n        this.configurator.permitPlanBasePrice = this.basePrice;\n      }\n    }\n\n    /**\n     * Get the drawer element, caching the reference.\n     * @returns {HTMLElement|null}\n     */\n    _getDrawer() {\n      if (!this.permitDrawer) {\n        this.permitDrawer = document.getElementById(this.drawerId);\n      }\n      return this.permitDrawer;\n    }\n\n    /**\n     * Initialize the permit drawer - attach event listeners and sync initial state.\n     * Call this once after the drawer HTML is in the DOM.\n     */\n    setup() {\n      const drawer = this._getDrawer();\n      if (!drawer) {\n        console.warn(`[PermitManager] Drawer not found: #${this.drawerId}`);\n        return;\n      }\n\n      // Ensure the include option reflects the current base permit price\n      const includeOption = drawer.querySelector('[data-permit-option][data-permit-value=\"include\"]');\n      if (includeOption) {\n        includeOption.dataset.permitPrice = String(this.basePrice);\n      }\n\n      // Update price text in drawer if element exists\n      const includePriceText = drawer.querySelector('[data-permit-drawer-price]');\n      if (includePriceText) {\n        includePriceText.textContent = this.basePrice > 0 ? this.formatPrice(this.basePrice) : 'No charge';\n      }\n\n      // Attach click listeners to permit options\n      drawer.querySelectorAll('[data-permit-option]').forEach(card => {\n        card.addEventListener('click', (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          this.handleSelect(card);\n        });\n      });\n\n      // Set initial selection state\n      this.updateSelection();\n      this.updateSummaryDisplay();\n    }\n\n    /**\n     * Open the permit drawer with current selection state.\n     */\n    open() {\n      const drawer = this._getDrawer();\n      if (!drawer) {\n        console.warn(`[PermitManager] Cannot open - drawer not found: #${this.drawerId}`);\n        return;\n      }\n\n      this.updateSelection();\n\n      // Try native drawer method first, fallback to class toggle\n      if (typeof drawer.openDrawer === 'function') {\n        drawer.openDrawer();\n      } else {\n        drawer.classList.add('open');\n        drawer.setAttribute('aria-hidden', 'false');\n        if (typeof SiteOverlay !== 'undefined' && SiteOverlay.showOverlay) {\n          SiteOverlay.showOverlay();\n        }\n      }\n    }\n\n    /**\n     * Sync the drawer UI to reflect the current selection state.\n     */\n    updateSelection() {\n      const drawer = this._getDrawer();\n      if (!drawer) return;\n\n      drawer.querySelectorAll('[data-permit-option]').forEach(card => {\n        const isSelected = card.dataset.permitValue === this.selected;\n        card.classList.toggle('selected', isSelected);\n        card.classList.toggle('config-card--selected', isSelected);\n      });\n    }\n\n    /**\n     * Handle user selection of a permit option.\n     * @param {HTMLElement} card - The clicked option card\n     */\n    handleSelect(card) {\n      const value = card.dataset.permitValue;\n      const price = parseInt(card.dataset.permitPrice, 10) || 0;\n\n      // Update state\n      this.selected = value;\n      this.price = price;\n      this._syncToConfigurator();\n\n      // Update drawer UI\n      const drawer = this._getDrawer();\n      if (drawer) {\n        drawer.querySelectorAll('[data-permit-option]').forEach(c => {\n          c.classList.remove('selected', 'config-card--selected');\n        });\n        card.classList.add('selected', 'config-card--selected');\n      }\n\n      // Update summary display\n      this.updateSummaryDisplay();\n\n      // Call onSelect callback (e.g., to recalculate total)\n      if (typeof this.onSelect === 'function') {\n        this.onSelect();\n      }\n    }\n\n    /**\n     * Update the permit display in the summary section.\n     * @param {HTMLElement} [container] - Optional container element, defaults to step2Section's panel wrapper\n     */\n    updateSummaryDisplay(container) {\n      const contentWrapper = container || \n        (this.step2Section ? this.step2Section.querySelector('[data-panel-content-wrapper]') : null) ||\n        (this.configurator?.step2Section ? this.configurator.step2Section.querySelector('[data-panel-content-wrapper]') : null);\n      \n      if (!contentWrapper) return;\n\n      // Update price display\n      const priceDisplay = contentWrapper.querySelector('[data-permit-price-display]');\n      if (priceDisplay) {\n        priceDisplay.textContent = this.price > 0 ? this.formatPrice(this.price) : 'No charge';\n      }\n\n      // Update permit line item label      \n      const permitLineItem = contentWrapper.querySelector('[data-summary-line-item=\"permit\"], [data-item-id=\"permit\"]');\n      if (permitLineItem) {\n        const label = permitLineItem.querySelector('[data-line-item-label], .line-item-label');\n        if (label) {\n          label.textContent = this.selected === 'include' ? 'Include Permit Plans' : 'No Permit Plans';\n        }\n      }\n    }\n\n    /**\n     * Get the current permit state.\n     * @returns {{ selected: string, price: number, basePrice: number }}\n     */\n    getState() {\n      return {\n        selected: this.selected,\n        price: this.price,\n        basePrice: this.basePrice\n      };\n    }\n\n    /**\n     * Reset permit to default state.\n     */\n    reset() {\n      this.selected = this.defaultSelected;\n      this.price = this.selected === 'include' ? this.basePrice : 0;\n      this._syncToConfigurator();\n      this.updateSelection();\n      this.updateSummaryDisplay();\n    }\n\n    /**\n     * Set permit state programmatically.\n     * @param {{ selected?: string, price?: number }} state\n     */\n    setState(state) {\n      if (state.selected !== undefined) {\n        this.selected = state.selected;\n      }\n      if (state.price !== undefined) {\n        this.price = normalizeNumber(state.price, 0);\n      }\n      this._syncToConfigurator();\n      this.updateSelection();\n    }\n  }\n\n  // ============================================================================\n  // FOUNDATION MANAGER - Shared foundation type drawer logic\n  // ============================================================================\n\n  /**\n   * FoundationManager centralizes foundation type drawer logic across all configurator models.\n   * Similar to PermitManager, it provides a single source of truth for:\n   * - Drawer setup and event handling\n   * - Selection state management\n   * - Summary display updates\n   * - Backward compatibility with existing configurator properties\n   */\n  class FoundationManager {\n    constructor(config) {\n      if (!config?.configurator) {\n        console.error('[FoundationManager] Missing required configurator instance');\n        return;\n      }\n\n      this.configurator = config.configurator;\n      this.drawerId = config.drawerId || 'foundation-type-drawer';\n      this.defaultValue = config.defaultValue || 'wood-frame';\n      this.step2Section = config.step2Section || null;\n      this.onSelect = config.onSelect || null;\n\n      // State - synced to configurator for backward compatibility\n      this.foundationDrawer = null;\n      this.selected = this.defaultValue;\n\n      // Labels for summary display (can be customized per model)\n      this.labels = config.labels || {\n        'concrete-slab': 'Foundation: Concrete Slab',\n        'wood-frame': 'Foundation: Wood Frame Floor',\n        'not-sure': 'Foundation TBD'\n      };\n\n      // Sync initial state to configurator\n      this._syncToConfigurator();\n    }\n\n    /**\n     * Sync foundation state to the parent configurator for backward compatibility\n     * with existing code that reads foundationTypeSelected directly.\n     */\n    _syncToConfigurator() {\n      if (this.configurator) {\n        this.configurator.foundationTypeSelected = this.selected;\n      }\n    }\n\n    /**\n     * Get the drawer element, caching the reference.\n     * @returns {HTMLElement|null}\n     */\n    _getDrawer() {\n      if (!this.foundationDrawer) {\n        this.foundationDrawer = document.getElementById(this.drawerId);\n      }\n      return this.foundationDrawer;\n    }\n\n    /**\n     * Initialize the foundation drawer - attach event listeners and sync initial state.\n     * Call this once after the drawer HTML is in the DOM.\n     */\n    setup() {\n      const drawer = this._getDrawer();\n      if (!drawer) {\n        console.warn(`[FoundationManager] Drawer not found: #${this.drawerId}`);\n        return;\n      }\n\n      // Attach click listeners to foundation options\n      drawer.querySelectorAll('[data-foundation-option]').forEach(card => {\n        card.addEventListener('click', (e) => {\n          e.preventDefault();\n          e.stopPropagation();\n          this.handleSelect(card);\n        });\n      });\n\n      // Set initial selection state\n      this.updateSelection();\n    }\n\n    /**\n     * Open the foundation drawer with current selection state.\n     */\n    open() {\n      const drawer = this._getDrawer();\n      if (!drawer) {\n        console.warn(`[FoundationManager] Cannot open - drawer not found: #${this.drawerId}`);\n        return;\n      }\n\n      this.updateSelection();\n\n      // Try native drawer method first, fallback to class toggle\n      if (typeof drawer.openDrawer === 'function') {\n        drawer.openDrawer();\n      } else {\n        drawer.classList.add('open');\n        drawer.setAttribute('aria-hidden', 'false');\n        if (typeof SiteOverlay !== 'undefined' && SiteOverlay.showOverlay) {\n          SiteOverlay.showOverlay();\n        }\n      }\n    }\n\n    /**\n     * Sync the drawer UI to reflect the current selection state.\n     */\n    updateSelection() {\n      const drawer = this._getDrawer();\n      if (!drawer) return;\n\n      drawer.querySelectorAll('[data-foundation-option]').forEach(card => {\n        const isSelected = card.dataset.foundationValue === this.selected;\n        card.classList.toggle('selected', isSelected);\n        card.classList.toggle('config-card--selected', isSelected);\n      });\n    }\n\n    /**\n     * Handle user selection of a foundation option.\n     * @param {HTMLElement} card - The clicked option card\n     */\n    handleSelect(card) {\n      const value = card.dataset.foundationValue;\n\n      // Update state\n      this.selected = value;\n      this._syncToConfigurator();\n\n      // Update drawer UI\n      const drawer = this._getDrawer();\n      if (drawer) {\n        drawer.querySelectorAll('[data-foundation-option]').forEach(c => {\n          c.classList.remove('selected', 'config-card--selected');\n        });\n        card.classList.add('selected', 'config-card--selected');\n      }\n\n      // Update summary display\n      this.updateSummaryDisplay();\n\n      // Call onSelect callback if provided\n      if (typeof this.onSelect === 'function') {\n        this.onSelect();\n      }\n    }\n\n    /**\n     * Update the foundation display in the summary section.\n     */\n    updateSummaryDisplay() {\n      const contentWrapper = \n        (this.step2Section ? this.step2Section.querySelector('[data-panel-content-wrapper]') : null) ||\n        (this.configurator?.step2Section ? this.configurator.step2Section.querySelector('[data-panel-content-wrapper]') : null);\n      \n      if (!contentWrapper) return;\n\n      const foundationLabel = contentWrapper.querySelector('[data-foundation-label]');\n      if (foundationLabel) {\n        foundationLabel.textContent = this.labels[this.selected] || 'Foundation TBD';\n      }\n    }\n\n    /**\n     * Get the current foundation state.\n     * @returns {{ selected: string }}\n     */\n    getState() {\n      return {\n        selected: this.selected\n      };\n    }\n\n    /**\n     * Reset foundation to default state.\n     */\n    reset() {\n      this.selected = this.defaultValue;\n      this._syncToConfigurator();\n      this.updateSelection();\n    }\n\n    /**\n     * Set foundation state programmatically.\n     * @param {{ selected?: string }} state\n     */\n    setState(state) {\n      if (state.selected !== undefined) {\n        this.selected = state.selected;\n      }\n      this._syncToConfigurator();\n      this.updateSelection();\n    }\n  }\n\n  // ============================================================================\n  // FORM VALIDATION\n  // ============================================================================\n\n  /**\n   * Default validation rules for configurator forms.\n   * Can be extended or overridden when creating a validator.\n   */\n  const DEFAULT_FORM_VALIDATION_RULES = {\n    firstName: {\n      required: true,\n      minLength: 2,\n      pattern: /^[a-zA-Z\\s'\\-]+$/,\n      messages: {\n        required: 'First name is required',\n        minLength: 'First name must be at least 2 characters',\n        pattern: 'First name can only contain letters, spaces, hyphens, and apostrophes'\n      }\n    },\n    lastName: {\n      required: true,\n      minLength: 2,\n      pattern: /^[a-zA-Z\\s'\\-]+$/,\n      messages: {\n        required: 'Last name is required',\n        minLength: 'Last name must be at least 2 characters',\n        pattern: 'Last name can only contain letters, spaces, hyphens, and apostrophes'\n      }\n    },\n    email: {\n      required: true,\n      pattern: /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/,\n      messages: {\n        required: 'Email is required',\n        pattern: 'Please enter a valid email address'\n      }\n    },\n    zipCode: {\n      required: true,\n      pattern: /^\\d{5}(-\\d{4})?$/,\n      messages: {\n        required: 'ZIP code is required',\n        pattern: 'Please enter a valid 5-digit ZIP code'\n      }\n    },\n    phone: {\n      required: false,\n      pattern: /^[\\d\\s\\-\\(\\)\\+]*$/,\n      messages: {\n        pattern: 'Please enter a valid phone number'\n      }\n    }\n  };\n\n  /**\n   * Creates a reusable form validator for configurator forms.\n   * Provides real-time validation, error display, and form submission validation.\n   * \n   * @param {HTMLFormElement} form - The form element to validate\n   * @param {Object} options - Validation options\n   * @param {Object} options.rules - Custom validation rules to merge with defaults\n   * @param {boolean} options.validateOnBlur - Whether to validate on blur (default: true)\n   * @param {boolean} options.validateOnInput - Whether to clear errors on input (default: true)\n   * @returns {Object} - Validator instance with methods\n   * \n   * @example\n   * const validator = createFormValidator(form);\n   * validator.attachListeners();\n   * \n   * form.addEventListener('submit', (e) => {\n   *   e.preventDefault();\n   *   const { valid, firstInvalid } = validator.validateAll();\n   *   if (!valid) {\n   *     firstInvalid?.focus();\n   *     return;\n   *   }\n   *   // proceed with submission\n   * });\n   */\n  const createFormValidator = (form, options = {}) => {\n    if (!form || !(form instanceof HTMLFormElement)) {\n      console.warn('[FormValidator] Invalid form element provided');\n      return null;\n    }\n\n    const rules = { ...DEFAULT_FORM_VALIDATION_RULES, ...(options.rules || {}) };\n    const validateOnBlur = options.validateOnBlur !== false;\n    const validateOnInput = options.validateOnInput !== false;\n\n    /**\n     * Validate a single input field against its rules.\n     * @param {HTMLInputElement} input - The input element to validate\n     * @returns {{ valid: boolean, message?: string }}\n     */\n    const validateField = (input) => {\n      const name = input.name;\n      const value = input.value.trim();\n      const rule = rules[name];\n\n      // No rule defined for this field - consider it valid\n      if (!rule) return { valid: true };\n\n      // Required check\n      if (rule.required && !value) {\n        return { valid: false, message: rule.messages?.required || `${name} is required` };\n      }\n\n      // Skip other validations if empty and not required\n      if (!value && !rule.required) return { valid: true };\n\n      // Min length check\n      if (rule.minLength && value.length < rule.minLength) {\n        return { \n          valid: false, \n          message: rule.messages?.minLength || `Minimum ${rule.minLength} characters required` \n        };\n      }\n\n      // Max length check\n      if (rule.maxLength && value.length > rule.maxLength) {\n        return { \n          valid: false, \n          message: rule.messages?.maxLength || `Maximum ${rule.maxLength} characters allowed` \n        };\n      }\n\n      // Pattern check\n      if (rule.pattern && !rule.pattern.test(value)) {\n        return { valid: false, message: rule.messages?.pattern || 'Invalid format' };\n      }\n\n      // Custom validator function\n      if (typeof rule.validate === 'function') {\n        const customResult = rule.validate(value, input);\n        if (customResult !== true) {\n          return { \n            valid: false, \n            message: typeof customResult === 'string' ? customResult : (rule.messages?.custom || 'Invalid value') \n          };\n        }\n      }\n\n      return { valid: true };\n    };\n\n    /**\n     * Show error state on a field with visual feedback.\n     * @param {HTMLInputElement} input - The input element\n     * @param {string} message - Error message to display\n     */\n    const showFieldError = (input, message) => {\n      input.classList.add('is-invalid');\n      input.classList.remove('is-valid');\n      input.setCustomValidity(message);\n\n      // Add/update error message element\n      const inputGroup = input.closest('.input-group') || input.parentElement;\n      let errorEl = inputGroup.querySelector('.invalid-feedback');\n      if (!errorEl) {\n        errorEl = document.createElement('div');\n        errorEl.className = 'invalid-feedback';\n        inputGroup.appendChild(errorEl);\n      }\n      errorEl.textContent = message;\n      errorEl.style.display = 'block';\n    };\n\n    /**\n     * Clear error state on a field.\n     * @param {HTMLInputElement} input - The input element\n     * @param {boolean} showValid - Whether to show valid state (default: true)\n     */\n    const clearFieldError = (input, showValid = true) => {\n      input.classList.remove('is-invalid');\n      if (showValid && input.value.trim()) {\n        input.classList.add('is-valid');\n      } else {\n        input.classList.remove('is-valid');\n      }\n      input.setCustomValidity('');\n\n      const inputGroup = input.closest('.input-group') || input.parentElement;\n      const errorEl = inputGroup.querySelector('.invalid-feedback');\n      if (errorEl) {\n        errorEl.textContent = '';\n        errorEl.style.display = 'none';\n      }\n    };\n\n    /**\n     * Validate all form fields and return the result.\n     * @returns {{ valid: boolean, firstInvalid: HTMLInputElement|null, errors: Object }}\n     */\n    const validateAll = () => {\n      const inputs = form.querySelectorAll('input');\n      let isValid = true;\n      let firstInvalid = null;\n      const errors = {};\n\n      inputs.forEach(input => {\n        const result = validateField(input);\n        if (!result.valid) {\n          showFieldError(input, result.message);\n          errors[input.name] = result.message;\n          isValid = false;\n          if (!firstInvalid) firstInvalid = input;\n        } else {\n          clearFieldError(input);\n        }\n      });\n\n      return { valid: isValid, firstInvalid, errors };\n    };\n\n    /**\n     * Reset all validation states on the form.\n     */\n    const resetValidation = () => {\n      const inputs = form.querySelectorAll('input');\n      inputs.forEach(input => {\n        input.classList.remove('is-invalid', 'is-valid');\n        input.setCustomValidity('');\n        const inputGroup = input.closest('.input-group') || input.parentElement;\n        const errorEl = inputGroup.querySelector('.invalid-feedback');\n        if (errorEl) {\n          errorEl.textContent = '';\n          errorEl.style.display = 'none';\n        }\n      });\n    };\n\n    /**\n     * Attach real-time validation event listeners to form inputs.\n     * Call this after the form is added to the DOM.\n     */\n    const attachListeners = () => {\n      const inputs = form.querySelectorAll('input');\n      \n      inputs.forEach(input => {\n        // Skip if listeners already attached\n        if (input.dataset.validatorAttached === 'true') return;\n        input.dataset.validatorAttached = 'true';\n\n        // Validate on blur (when user leaves field)\n        if (validateOnBlur) {\n          input.addEventListener('blur', () => {\n            const result = validateField(input);\n            if (!result.valid) {\n              showFieldError(input, result.message);\n            } else {\n              clearFieldError(input);\n            }\n          });\n        }\n\n        // Clear error on input (user is fixing the error)\n        if (validateOnInput) {\n          input.addEventListener('input', () => {\n            // Only re-validate if field was previously invalid\n            if (input.classList.contains('is-invalid')) {\n              const result = validateField(input);\n              if (result.valid) {\n                clearFieldError(input);\n              }\n            }\n          });\n        }\n      });\n    };\n\n    /**\n     * Check if the form is currently valid without showing errors.\n     * @returns {boolean}\n     */\n    const isValid = () => {\n      const inputs = form.querySelectorAll('input');\n      for (const input of inputs) {\n        const result = validateField(input);\n        if (!result.valid) return false;\n      }\n      return true;\n    };\n\n    return {\n      validateField,\n      validateAll,\n      showFieldError,\n      clearFieldError,\n      resetValidation,\n      attachListeners,\n      isValid,\n      rules\n    };\n  };\n\n  // ============================================================================\n  // SUMMIT NX CODE RESOLVER\n  // ============================================================================\n\n  /**\n   * Complete mapping of Summit model options to NX component codes.\n   * Structure varies by model complexity:\n   *   - 1-level:  { 'option-value': 'CODE' }\n   *   - 2-level:  { 'opt1-value': { 'opt2-value': 'CODE' } }\n   *   - 3-level:  { 'opt1-value': { 'opt2-value': { 'opt3-value': 'CODE' } } }\n   */\n\n  const SUMMIT_NX_OPTIONS = {\n    308: {\n      a: {\n        'front-entry': { 'more-glass': 'A-1-a', 'more-privacy': 'A-1-b' },\n        'side-entry':  { 'more-glass': 'A-2-a', 'more-privacy': 'A-2-b' }\n      },\n      b: {\n        'front-entry': { 'more-glass': 'B-1-a', 'more-privacy': 'B-1-b' },\n        'side-entry':  { 'more-glass': 'B-2-a', 'more-privacy': 'B-2-b' }\n      }\n    },\n    440: {\n      a: {\n        'more-operable': { 'side-window': 'A-1-a', 'side-door': 'A-1-b' },\n        'more-glass':    { 'side-window': 'A-2-a', 'side-door': 'A-2-b' }\n      },\n      b: {\n        'more-operable': { 'side-window': 'B-1-a', 'side-door': 'B-1-b' },\n        'more-glass':    { 'side-window': 'B-2-a', 'side-door': 'B-2-b' }\n      }\n    },\n    476: {\n      a: { 'side-window': 'A-1', 'side-door': 'A-2' },\n      b: { 'side-window': 'B-1', 'side-door': 'B-2' }\n    },\n    608: {\n      a: { 'side-window': 'A-1', 'side-door': 'A-2' },\n      b: { 'side-window': 'B-1', 'side-door': 'B-2' }\n    },\n    680: {\n      a: {\n        'front-door': { 'bedroom-window': 'A-1-a', 'bedroom-door': 'A-1-b' },\n        'side-door':  { 'front-windows': 'A-2-a', 'front-slider-doors': 'A-2-b' }\n      },\n      b: {\n        'front-door': { 'bedroom-window': 'B-1-a', 'bedroom-door': 'B-1-b' },\n        'back-door':  { 'front-windows': 'B-2-a', 'front-slider-doors': 'B-2-b' }\n      },\n      c: {\n        'front-door': { 'bedroom-window': 'C-1-a', 'bedroom-door': 'C-1-b' },\n        'side-door':  { 'front-windows': 'C-2-a', 'front-slider-doors': 'C-2-b' }\n      }\n    },\n    800: {\n      a: {\n        'standard': {\n          'front-door': { 'side-window': 'A-1-a-i', 'side-slider-door': 'A-1-a-ii' },\n          'side-door':  { 'front-window': 'A-1-b-i', 'front-slider-door': 'A-1-b-ii' }\n        },\n        'add-half-bath': {\n          'front-door': { 'side-window': 'A-2-a-i', 'side-slider-door': 'A-2-a-ii' },\n          'side-door':  { 'front-window': 'A-2-b-i', 'front-slider-door': 'A-2-b-ii' }\n        }\n      },\n      b: {\n        'single-bathroom': { 'bedroom-window': 'B-1-a', 'bedroom-door': 'B-1-b' },\n        'add-half-bath':   { 'bedroom-window': 'B-2-a', 'bedroom-door': 'B-2-b' },\n        'add-full-bath':   { 'bedroom-window': 'B-3-a', 'bedroom-door': 'B-3-b' }\n      }\n    },\n    1000: {\n      a: {\n        'more-storage': {\n          'double-door': { 'bedroom-door': 'A-1-a-i', 'bedroom-window': 'A-1-a-ii' },\n          'single-door': { 'bedroom-door': 'A-1-b-i', 'bedroom-window': 'A-1-b-ii' }\n        },\n        'kitchen-window': {\n          'double-door': { 'bedroom-door': 'A-2-a-i', 'bedroom-window': 'A-2-a-ii' },\n          'single-door': { 'bedroom-door': 'A-2-b-i', 'bedroom-window': 'A-2-b-ii' }\n        },\n        'kitchen-door': {\n          'double-door': { 'bedroom-door': 'A-3-a-i', 'bedroom-window': 'A-3-a-ii' },\n          'single-door': { 'bedroom-door': 'A-3-b-i', 'bedroom-window': 'A-3-b-ii' }\n        }\n      },\n      b: {\n        'larger-living': {\n          'bedroom-window': { 'kitchen-window': 'B-1-a-i', 'kitchen-door': 'B-1-a-ii' },\n          'bedroom-door':   { 'kitchen-window': 'B-1-b-i', 'kitchen-door': 'B-1-b-ii' }\n        },\n        'larger-kitchen': {\n          'bedroom-window': { 'kitchen-window': 'B-2-a-i', 'kitchen-door': 'B-2-a-ii' },\n          'bedroom-door':   { 'kitchen-window': 'B-2-b-i', 'kitchen-door': 'B-2-b-ii' }\n        },\n        'larger-entry': {\n          'bedroom-window': { 'kitchen-window': 'B-3-a-i', 'kitchen-door': 'B-3-a-ii' },\n          'bedroom-door':   { 'kitchen-window': 'B-3-b-i', 'kitchen-door': 'B-3-b-ii' }\n        }\n      }\n    }\n  };\n\n  /**\n   * Resolve the summitNxCode from the mapping table using current config values.\n   *\n   * @param {string} modelSlug - e.g., \"summit-608-a\", \"summit-1000-b\"\n   * @param {Object} currentConfig - the configurator's currentConfig object\n   * @returns {string|null} the resolved code (e.g., \"A-2\", \"B-2-b-ii\") or null if not found\n   *\n   * Algorithm: extracts model size (608) and variant (a) from the slug,\n   * then recursively walks the nested mapping. At each level, it checks\n   * every value in currentConfig against the keys of the current mapping node.\n   * When it reaches a string leaf, that's the code.\n   */\n  const resolveSummitNxCode = (modelSlug, currentConfig) => {\n    // Extract size and variant from slug: \"summit-608-a\" -> size=608, variant=\"a\"\n    const match = String(modelSlug).match(/summit-(\\d+)-([a-z])/i);\n    if (!match) return null;\n\n    const size = parseInt(match[1], 10);\n    const variant = match[2].toLowerCase();\n\n    const sizeMap = SUMMIT_NX_OPTIONS[size];\n    if (!sizeMap) return null;\n\n    const variantMap = sizeMap[variant];\n    if (!variantMap) return null;\n\n    // Collect all current config values into a flat set for fast lookup\n    const configValues = new Set();\n    for (const key of Object.keys(currentConfig)) {\n      const val = currentConfig[key];\n      if (typeof val === 'string') configValues.add(val);\n    }\n\n    // Recursively walk the mapping tree, matching config values at each level\n    const walk = (node) => {\n      if (typeof node === 'string') return node; // Leaf \u2014 this is the code\n      if (!node || typeof node !== 'object') return null;\n\n      const keys = Object.keys(node);\n      for (const key of keys) {\n        if (configValues.has(key)) {\n          const result = walk(node[key]);\n          if (result) return result;\n        }\n      }\n      return null;\n    };\n\n    return walk(variantMap);\n  };\n\n  /* \u2500\u2500 Step History Manager \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n   * Pushes a history entry when entering Step 2 so the browser back\n   * button returns the user to Step 1 instead of leaving the page.\n   */\n  const StepHistoryManager = {\n    _activeConfigurator: null,\n\n    /** Call when entering step 2 */\n    pushStep2(configuratorInstance) {\n      this._activeConfigurator = configuratorInstance;\n      const url = new URL(window.location.href);\n      url.searchParams.set('step', '2');\n      window.history.pushState({ configuratorStep: 2 }, '', url.toString());\n    },\n\n    /** Call when leaving step 2 programmatically (e.g., click title) */\n    exitStep2() {\n      this._activeConfigurator = null;\n      window.history.back();\n    },\n\n    /** Initialize the popstate listener (call once on page load) */\n    init() {\n      window.addEventListener('popstate', (e) => {\n        if (this._activeConfigurator && (!e.state || !e.state.configuratorStep)) {\n          const configurator = this._activeConfigurator;\n          this._activeConfigurator = null;\n          configurator.hideStep2({ fromPopstate: true });\n        }\n      });\n    }\n  };\n\n  StepHistoryManager.init();\n\n  root.ConfiguratorShared = Object.assign(root.ConfiguratorShared || {}, {\n    resolveSummitNxCode,\n    StepHistoryManager,\n    normalizeNumber,\n    formatUSD,\n    formatPriceSmart,\n    formatMoneyFromDollars,\n    slugify,\n    canonicalModel,\n    pickFirst,\n    buildInstallationCostPayload,\n    getInstallationCostKeys,\n    isProductOnlyOption,\n    computeInstallationCost,\n    selectInstallationCost,\n    createInstallationCostClient,\n    applyInstallationCostsToServiceOptions,\n    queueInstallationPricing,\n    validateZipCodeWithAPI,\n    showNotification,\n    showError,\n    showSuccess,\n    showContactLinkOnError,\n    saveDesignToAPI,\n    buildCustomerData,\n    buildDesignData,\n    buildLabelData,\n    buildCostData,\n    PermitManager,\n    FoundationManager,\n    createFormValidator,\n    DEFAULT_FORM_VALIDATION_RULES,\n  });\n})();\n\n\n"],
  "mappings": "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",
  "names": ["isValid"]
}
