{
  "version": 3,
  "sources": ["/cdn/shop/t/26/assets/novateez-required-options.js?fresh=1792202139253"],
  "sourcesContent": ["/**\n * Nova Teez \u2014 required custom-option feedback\n * ------------------------------------------------------------------\n * When a product uses Hulk Product Options, clicking \"Add to cart\"\n * without completing required option groups silently does nothing.\n * This script adds the missing feedback: it highlights the required\n * groups that are empty, shows a visible message next to the options,\n * and scrolls to it. It never blocks or alters the add-to-cart\n * flow itself \u2014 it only surfaces what is missing.\n */\n(() => {\n  'use strict';\n\n  const BANNER_ID = 'novateez-required-options-banner';\n  const HIGHLIGHT_CLASS = 'novateez-missing-option';\n  const STYLES_ID = 'novateez-required-options-styles';\n\n  const STYLES = `\n    #${BANNER_ID} {\n      background: #b3261e;\n      color: #ffffff;\n      font-size: 14px;\n      font-weight: 600;\n      line-height: 1.45;\n      padding: 12px 18px;\n      border-radius: 10px;\n      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n      text-align: center;\n      font-family: inherit;\n      margin: 0 0 16px 0;\n    }\n    .${HIGHLIGHT_CLASS} {\n      outline: 2px solid #e5484d;\n      outline-offset: 4px;\n      border-radius: 6px;\n    }\n  `;\n\n  function injectStyles() {\n    if (document.getElementById(STYLES_ID)) return;\n    const style = document.createElement('style');\n    style.id = STYLES_ID;\n    style.textContent = STYLES;\n    document.head.appendChild(style);\n  }\n\n  function getBanner() {\n    let banner = document.getElementById(BANNER_ID);\n    if (!banner) {\n      banner = document.createElement('div');\n      banner.id = BANNER_ID;\n      banner.setAttribute('role', 'alert');\n      banner.style.display = 'none';\n      document.body.appendChild(banner);\n    }\n    return banner;\n  }\n\n  function hulkContainers() {\n    return document.querySelectorAll('[id^=\"hulkapps_options_\"]');\n  }\n\n  function pageHasHulkOptions() {\n    return hulkContainers().length > 0;\n  }\n\n  function isAddToCartButton(el) {\n    if (!(el instanceof HTMLButtonElement)) return false;\n    if (el.disabled) return false;\n    const text = (el.textContent || '').trim().toLowerCase();\n    const cls = String(el.className || '');\n    const isAddToCartText = text === 'add to cart' || /^add to cart\\s*\\(\\d+\\)$/.test(text);\n    const isAddToCartClass = /(^|\\s)(add-to-cart-button|sticky-add-to-cart__button)(\\s|$)/.test(cls);\n    // Exclude dynamic checkout buttons (Shop Pay, PayPal, etc.)\n    const isDynamicCheckout = /shopify-payment-button|dynamic-checkout/i.test(cls) || /buy with|pay now|check out with/i.test(text);\n    return (isAddToCartText || isAddToCartClass) && !isDynamicCheckout;\n  }\n\n  function optionGroups() {\n    const groups = [];\n    hulkContainers().forEach((container) => {\n      container.querySelectorAll('.hulkapps_option').forEach((group) => {\n        if (String(group.className).split(/\\s+/).includes('required')) groups.push(group);\n      });\n    });\n    return groups;\n  }\n\n  function groupIsVisible(group) {\n    return !!(group.offsetParent || group.getClientRects().length);\n  }\n\n  function groupIsSatisfied(group) {\n    const inputs = group.querySelectorAll('input, select, textarea');\n    for (const input of inputs) {\n      if (input.type === 'radio' || input.type === 'checkbox') {\n        if (input.checked) return true;\n      } else if (input.type === 'file') {\n        if (input.files && input.files.length > 0) return true;\n      } else if (input instanceof HTMLSelectElement) {\n        if (input.selectedIndex > 0 && input.value) return true;\n      } else if (input instanceof HTMLTextAreaElement) {\n        if (input.value && input.value.trim()) return true;\n      } else if (input.type === 'text' || input.type === 'number' || input.type === 'email' || input.type === 'tel' || input.type === 'url') {\n        // Only count visible text inputs, to ignore hidden helper fields\n        if (input.offsetParent && input.value && input.value.trim()) return true;\n      }\n    }\n    return false;\n  }\n\n  function groupName(group) {\n    const nameEl = group.querySelector('.hulkapps_option_name');\n    const name = (nameEl ? nameEl.textContent : '') || 'a required option';\n    return name.replace(/\\*/g, '').replace(/\\s+/g, ' ').trim();\n  }\n\n  function missingGroups() {\n    return optionGroups().filter((group) => groupIsVisible(group) && !groupIsSatisfied(group));\n  }\n\n  // Pending delayed scroll correction, if any. Only one may be in flight;\n  // it is cancelled whenever feedback is re-rendered or cleared.\n  let correctionTimer = null;\n\n  function clearFeedback() {\n    if (correctionTimer) {\n      clearTimeout(correctionTimer);\n      correctionTimer = null;\n    }\n    document.querySelectorAll('.' + HIGHLIGHT_CLASS).forEach((el) => el.classList.remove(HIGHLIGHT_CLASS));\n    const banner = getBanner();\n    banner.style.display = 'none';\n    banner.textContent = '';\n  }\n\n  function scrollToElement(banner) {\n    // Find the nearest scrollable ancestor (this theme may scroll inside\n    // a .page-wrapper div rather than the window).\n    let el = banner;\n    let container = null;\n    while (el && el !== document.documentElement) {\n      const s = getComputedStyle(el);\n      if (/auto|scroll|overlay/.test(s.overflowY) && el.scrollHeight > el.clientHeight + 5) {\n        container = el;\n        break;\n      }\n      el = el.parentElement;\n    }\n    const scrollTopFor = (offset) => {\n      const rect = banner.getBoundingClientRect();\n      const cRect = container ? container.getBoundingClientRect() : { top: 0 };\n      const current = container ? container.scrollTop : window.scrollY;\n      return Math.max(0, current + (rect.top - cRect.top) - offset);\n    };\n    const apply = (top, instant) => {\n      if (container) container.scrollTo({ top, behavior: instant ? 'instant' : 'smooth' });\n      else window.scrollTo({ top, behavior: instant ? 'instant' : 'smooth' });\n    };\n    // The banner sits directly above the first missing choice, so scrolling\n    // it to just below the header brings both the message and the choice\n    // that needs an answer on screen.\n    if (correctionTimer) clearTimeout(correctionTimer);\n    apply(scrollTopFor(140), false);\n    // The theme also scrolls on add-to-cart clicks, which can displace this\n    // position. One delayed correction, cancelled by any re-render or by\n    // feedback clearing, puts the banner back on screen.\n    correctionTimer = setTimeout(() => {\n      correctionTimer = null;\n      const r = banner.getBoundingClientRect();\n      if (r.top < 0 || r.bottom > window.innerHeight) {\n        apply(scrollTopFor(140), true);\n      }\n    }, 900);\n  }\n\n  function renderFeedback(missing, scroll) {\n    // Any re-render cancels a pending scroll correction \u2014 the positions it\n    // computed may no longer be relevant.\n    if (correctionTimer) {\n      clearTimeout(correctionTimer);\n      correctionTimer = null;\n    }\n    injectStyles();\n    const names = missing.map(groupName);\n    const label = names.length === 1 ? 'option' : 'options';\n    const message = 'Please complete the required ' + label + ': ' + names.join(', ') + '.';\n    const banner = getBanner();\n    // Keep the message directly above the first choice that still needs an\n    // answer, so the explanation is always next to what needs fixing.\n    const anchor = missing[0];\n    if (anchor && anchor.parentNode && banner.nextElementSibling !== anchor) {\n      anchor.parentNode.insertBefore(banner, anchor);\n    }\n    banner.textContent = message;\n    banner.style.display = 'block';\n    document.querySelectorAll('.' + HIGHLIGHT_CLASS).forEach((el) => el.classList.remove(HIGHLIGHT_CLASS));\n    missing.forEach((group) => group.classList.add(HIGHLIGHT_CLASS));\n    if (scroll) {\n      // Scroll so the message and the first choice that still needs an\n      // answer are both on screen.\n      scrollToElement(banner);\n    }\n  }\n\n  // Whether the customer has attempted an add-to-cart while options were\n  // incomplete \u2014 feedback on changes is only shown after such an attempt,\n  // so normal browsing never shows errors prematurely.\n  let attempted = false;\n\n  // Main click listener \u2014 runs on capture so feedback appears immediately\n  document.addEventListener(\n    'click',\n    (event) => {\n      if (!pageHasHulkOptions()) return;\n      const button = event.target instanceof Element ? event.target.closest('button') : null;\n      if (!button || !isAddToCartButton(button)) return;\n      const missing = missingGroups();\n      if (missing.length > 0) {\n        attempted = true;\n        renderFeedback(missing, true);\n      } else {\n        // Everything is valid \u2014 make sure no stale feedback or pending\n        // scroll correction is left behind; the normal add-to-cart flow\n        // (including Hulk's own logic) proceeds untouched.\n        clearFeedback();\n      }\n    },\n    { capture: true }\n  );\n\n  // Update or clear feedback as the customer changes options \u2014 only after\n  // an unsuccessful add-to-cart attempt, never during normal browsing\n  document.addEventListener(\n    'change',\n    (event) => {\n      if (!attempted || !pageHasHulkOptions()) return;\n      const target = event.target;\n      if (target instanceof Element && target.closest('[id^=\"hulkapps_options_\"]')) {\n        const missing = missingGroups();\n        if (missing.length === 0) {\n          clearFeedback();\n          attempted = false;\n        } else {\n          // Keep the message in sync with what is still missing (no re-scroll)\n          renderFeedback(missing, false);\n        }\n      }\n    },\n    { capture: true }\n  );\n})();\n"],
  "mappings": "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",
  "names": []
}
