{"version":3,"file":"header-section.min.js","mappings":"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","sources":["webpack://lange/./sections/header/hamburger-button-control.ts","webpack://lange/webpack/bootstrap","webpack://lange/./sections/header/announcement-bar.ts","webpack://lange/./sections/header/search-product-preview/index.ts","webpack://lange/../node_modules/uuid/dist/rng.js","webpack://lange/../node_modules/uuid/dist/stringify.js","webpack://lange/../node_modules/uuid/dist/v4.js","webpack://lange/./lib/shopify/image-url.ts","webpack://lange/../node_modules/@integrabeauty/assert/src/index.ts","webpack://lange/./lib/shopify/predictive-search.ts","webpack://lange/./sections/header/shopify-search-input/index.ts","webpack://lange/./lib/dom-ready.ts","webpack://lange/./sections/header/dynamic-header-script.ts","webpack://lange/./sections/header/cart-button.ts","webpack://lange/./lib/cart-event-queue.ts","webpack://lange/./sections/header/cart-button-control.ts","webpack://lange/./sections/header/search-button.ts","webpack://lange/./sections/header/profile-button.ts","webpack://lange/./sections/header/hamburger-button.ts","webpack://lange/./sections/header/menu-panels-element.ts","webpack://lange/./sections/header/menu-panels.ts"],"sourcesContent":["function onMenuOpened(_event: GlobalEventHandlersEventMap['menu-opened']) {\n  const hamburgerIcon = document.querySelector('hamburger-button');\n  if (hamburgerIcon) {\n    hamburgerIcon.dataset.opened = 'true';\n  }\n}\n\nfunction onMenuClosed(_event: GlobalEventHandlersEventMap['menu-closed']) {\n  const hamburgerIcon = document.querySelector('hamburger-button');\n  if (hamburgerIcon) {\n    hamburgerIcon.dataset.opened = 'false';\n  }\n}\n\naddEventListener('menu-closed', onMenuClosed);\naddEventListener('menu-opened', onMenuOpened);\n","// The module cache\nlet __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tlet cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tlet module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","import type { CustomElement } from '@integrabeauty/custom-elements';\n\n/**\n * The promotional announcement bar at the very top of the page, above the header, with an optional\n * limited time badge and countdown clock.\n *\n * The announcement-bar snippet writes the bar and its shadow root in Liquid, so the browser builds\n * the shadow root while it reads the page and the bar is styled before this script runs. This\n * script only adds the close button and the ended sale check.\n *\n * The clock is a countdown-clock-v2 the header renders into the bar's `clock` slot, and it hides\n * itself when the time runs out, leaving the rest of the bar up. The shopper can dismiss the bar.\n */\nclass AnnouncementBar extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Stop time as unix epoch time.\n     *\n     * @see https://www.epochconverter.com\n     */\n    clockEndTime: string;\n\n    /**\n     * Whether the header rendered the countdown clock into the bar.\n     */\n    clockShow: 'false' | 'true';\n  };\n\n  private onCloseClickedBound = this.onCloseClicked.bind(this);\n\n  public connectedCallback() {\n    // A browser without declarative shadow DOM leaves the snippet's template in the light DOM as an\n    // ordinary template element, so we build the shadow root from it here. The bar shows as plain\n    // text in those browsers until this runs.\n    if (!this.shadowRoot) {\n      const template = this.querySelector('template');\n      this.attachShadow({ mode: 'open' }).append(template.content.cloneNode(true));\n      template.remove();\n    }\n\n    const closeButton = this.shadowRoot.querySelector<HTMLElement>('.close');\n    closeButton.addEventListener('click', this.onCloseClickedBound);\n\n    // The header only renders the clock while the sale is still running, so when the clock is here\n    // but its time already ran out, the page came from a cache rendered before the end. The message\n    // belongs to the sale that just ended, so we hide the whole bar. A clock that runs out while\n    // the shopper is on the page only hides itself.\n    if (this.dataset.clockShow === 'true') {\n      const endTimestamp = parseInt(this.dataset.clockEndTime, 10) * 1000;\n      if (endTimestamp < Date.now()) {\n        this.hidden = true;\n      }\n    }\n  }\n\n  public disconnectedCallback() {\n    const closeButton = this.shadowRoot.querySelector<HTMLElement>('.close');\n    closeButton?.removeEventListener('click', this.onCloseClickedBound);\n  }\n\n  private onCloseClicked(_event: MouseEvent) {\n    this.hidden = true;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'announcement-bar': AnnouncementBar;\n  }\n}\n\nif (!customElements.get('announcement-bar')) {\n  customElements.define('announcement-bar', AnnouncementBar);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * This element represents a product match from a user's search query which is displayed inline in\n * the search preview drawer.\n */\nclass SearchProductPreview extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Product thumb URL\n     */\n    imageUrl: string;\n\n    /**\n     * Product title\n     */\n    title: string;\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.render();\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    const image = this.shadowRoot.querySelector('img');\n    image.src = this.dataset.imageUrl;\n    image.alt = this.dataset.title;\n\n    const title = this.shadowRoot.querySelector('span.title');\n    title.textContent = this.dataset.title;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'search-product-preview': SearchProductPreview;\n  }\n}\n\nif (!customElements.get('search-product-preview')) {\n  customElements.define('search-product-preview', SearchProductPreview);\n}\n","const rnds8 = new Uint8Array(16);\nexport default function rng() {\n    return crypto.getRandomValues(rnds8);\n}\n","import validate from './validate.js';\nconst byteToHex = [];\nfor (let i = 0; i < 256; ++i) {\n    byteToHex.push((i + 0x100).toString(16).slice(1));\n}\nexport function unsafeStringify(arr, offset = 0) {\n    return (byteToHex[arr[offset + 0]] +\n        byteToHex[arr[offset + 1]] +\n        byteToHex[arr[offset + 2]] +\n        byteToHex[arr[offset + 3]] +\n        '-' +\n        byteToHex[arr[offset + 4]] +\n        byteToHex[arr[offset + 5]] +\n        '-' +\n        byteToHex[arr[offset + 6]] +\n        byteToHex[arr[offset + 7]] +\n        '-' +\n        byteToHex[arr[offset + 8]] +\n        byteToHex[arr[offset + 9]] +\n        '-' +\n        byteToHex[arr[offset + 10]] +\n        byteToHex[arr[offset + 11]] +\n        byteToHex[arr[offset + 12]] +\n        byteToHex[arr[offset + 13]] +\n        byteToHex[arr[offset + 14]] +\n        byteToHex[arr[offset + 15]]).toLowerCase();\n}\nfunction stringify(arr, offset = 0) {\n    const uuid = unsafeStringify(arr, offset);\n    if (!validate(uuid)) {\n        throw TypeError('Stringified UUID is invalid');\n    }\n    return uuid;\n}\nexport default stringify;\n","import rng from './rng.js';\nimport { unsafeStringify } from './stringify.js';\nfunction v4(options, buf, offset) {\n    if (!buf && !options && crypto.randomUUID) {\n        return crypto.randomUUID();\n    }\n    return _v4(options, buf, offset);\n}\nfunction _v4(options, buf, offset) {\n    options = options || {};\n    const rnds = options.random ?? options.rng?.() ?? rng();\n    if (rnds.length < 16) {\n        throw new Error('Random bytes length must be >= 16');\n    }\n    rnds[6] = (rnds[6] & 0x0f) | 0x40;\n    rnds[8] = (rnds[8] & 0x3f) | 0x80;\n    if (buf) {\n        offset = offset || 0;\n        if (offset < 0 || offset + 16 > buf.length) {\n            throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);\n        }\n        for (let i = 0; i < 16; ++i) {\n            buf[offset + i] = rnds[i];\n        }\n        return buf;\n    }\n    return unsafeStringify(rnds);\n}\nexport default v4;\n","// Shopify serves resized images two ways. The old way splices the size into the filename, so\n// `foo.jpg` becomes `foo_120x120.jpg`, and that is what the deprecated `img_url` Liquid filter\n// emits. It is also what the retired slate fork alongside this file does. The current way, what the\n// `image_url` filter emits and what Hydrogen's image loader builds from JS, is query params on the\n// original URL. Both come back byte identical, so this is the same images through the shape Shopify\n// still maintains.\n\ntype Crop = 'bottom' | 'center' | 'left' | 'right' | 'top';\n\ntype Size = 'compact' | 'grande' | 'icon' | 'large' | 'medium' | 'original' | 'pico' | 'small' |\n  'thumb';\n\n// Shopify's named sizes are all square, and these are the pixel values its docs give for each one.\n// There is no entry for master because master means the original image at whatever size it happens\n// to be, and asking for no dimensions at all already gets you that.\nconst namedSizePixels: Record<Size, number> = {\n  compact: 160,\n  grande: 600,\n  icon: 32,\n  large: 480,\n  medium: 240,\n  original: 1024,\n  pico: 16,\n  small: 100,\n  thumb: 50\n};\n\nexport interface ImageUrlOptions {\n  /**\n   * Which part of the image to keep when the requested dimensions do not match its aspect ratio.\n   */\n  crop?: Crop;\n\n  /**\n   * Requested height in pixels.\n   */\n  height?: number;\n\n  /**\n   * Encoding quality from 10 to 90. Leave it out to let Shopify pick one for the output format.\n   */\n  quality?: number;\n\n  /**\n   * One of Shopify's named sizes, used only when neither a width nor a height is given.\n   */\n  size?: Size;\n\n  /**\n   * Requested width in pixels.\n   */\n  width?: number;\n}\n\n/**\n * Builds a URL for a Shopify CDN image at a particular size.\n *\n * Pass a width, a height, or both. You can pass one of Shopify's named sizes instead, and with none\n * of the three you get the original image back. Shopify tops out at 5760 in either direction, and\n * asking for something bigger than the original just hands back the original rather than failing.\n *\n * The returned URL is protocol relative so that nothing gets forced onto `http`.\n *\n * There is no format option. Shopify's own only accepts `jpg` and `pjpg`, and it negotiates webp\n * and avif off the `Accept` header anyway.\n *\n * @see https://shopify.dev/docs/api/liquid/filters/image_url\n * @example build('//langehair.com/cdn/shop/files/Foo.jpg?v=1765908254', { width: 120 })\n */\nexport function build(src: string, options: ImageUrlOptions) {\n  // Real image URLs on the site are protocol relative, and URL needs a scheme to parse anything. So\n  // we bolt one on for parsing and take it back off at the end.\n  const absolute = src.startsWith('//') ? `https:${src}` : src;\n  const url = new URL(absolute);\n\n  // A named size is only there for callers that have no dimensions of their own, so an explicit\n  // width or height wins over it. The named sizes are square, so the one number covers both.\n  const named = options.size && !options.width && !options.height ?\n    namedSizePixels[options.size] :\n    undefined;\n  const width = options.width || named;\n  const height = options.height || named;\n\n  if (width) {\n    url.searchParams.set('width', `${width}`);\n  }\n\n  if (height) {\n    url.searchParams.set('height', `${height}`);\n  }\n\n  if (options.crop) {\n    url.searchParams.set('crop', options.crop);\n  }\n\n  if (options.quality) {\n    url.searchParams.set('quality', `${options.quality}`);\n  }\n\n  return url.toString().replace(/^https?:/, '');\n}\n","/**\n * Throws an assertion error if the given condition is falsy.\n *\n * @param expression any expression, although one that evaluates to true or false is preferred by\n * convention\n * @param message optional error text\n */\nexport function assert(expression: unknown, message?: string) {\n  if (!expression) {\n    throw new AssertionError(message);\n  }\n}\n\nexport class AssertionError extends Error {\n  constructor(message = 'Assertion error') {\n    super(message);\n    this.name = this.constructor.name;\n  }\n}\n","import { assert } from '@integrabeauty/assert';\n\n/**\n * Input options to searching Shopify.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/predictive-search#get-locale-search-suggest\n */\nexport interface SuggestInput {\n  /**\n   * The handle of the blog to search within. Required when use_graphql is true, ignored otherwise.\n   *\n   * @example \"how-to\"\n   */\n  blog_handle?: string;\n\n  /**\n   * The default fields searched on are title, product_type, variants.title, and vendor. For the\n   * best search experience, you should search on the default field set.\n   *\n   * There are constraints for translated storefronts, see the docs.\n   */\n  fields?: Field[];\n\n  /**\n   * Limits the number of results based on limit_scope. The value can range from 1 to 10, and the\n   * default is 10.\n   */\n  limit?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;\n\n  /**\n   * Decides how limit spreads across result types. With all, the limit counts results across every\n   * type together. With each, the limit applies to each type separately. The default is all.\n   */\n  limit_scope?: 'all' | 'each';\n\n  /**\n   * Whether page and article results match partial words on the last search term. With last, a\n   * search for \"winter snow\" can match a page containing \"snowshoe\". With none, partial words are\n   * not matched. The default is last.\n   */\n  prefix?: 'last' | 'none';\n\n  /**\n   * The search query.\n   */\n  query: string;\n\n  /**\n   * Specifies the type of results requested.\n   *\n   * The following are the accepted values, which can be combined in a comma-separated list:\n   *\n   * * product\n   * * page\n   * * article\n   * * collection\n   * * query\n   *\n   * The default value is query,product,collection,page.\n   *\n   * To change the default value, you can use Search Settings in the Search & Discovery app.\n   */\n  resource_types?: ResourceType[];\n\n  /**\n   * The id of a section to render the search results with. When set, the request goes to the\n   * suggest endpoint without the json extension and the response is the section's server rendered\n   * html rather than json.\n   *\n   * @example \"predictive-search\"\n   */\n  section_id?: string;\n\n  /**\n   * Specifies whether to display results for unavailable products.\n   *\n   * The following are the accepted values:\n   *\n   * * show: Show unavailable products.\n   * * hide: Hide unavailable products.\n   * * last: Show unavailable products below other matching results.\n   *\n   * The default value is last.\n   *\n   * To change the default value, you can use Search Settings in the Search & Discovery app.\n   *\n   * This parameter is only applicable to type product.\n   */\n  unavailable_products?: 'hide' | 'last' | 'show';\n\n  /**\n   * Search through the Storefront GraphQL API instead of the AJAX suggest endpoint. This path only\n   * returns articles from the one blog named by blog_handle, and it reads only query and limit from\n   * the other options.\n   */\n  use_graphql?: boolean;\n}\n\nexport type ProductField = 'body' | 'product_type' | 'tag' | 'title' | 'variants.barcode' |\n  'variants.sku' | 'variants.title' | 'vendor';\nexport type PageField = 'author' | 'body' | 'title';\nexport type ArticleField = 'author' | 'body' | 'tag' | 'title';\nexport type CollectionField = 'title';\nexport type Field = ArticleField | CollectionField | PageField | ProductField;\n\nexport type ResourceType = 'article' | 'collection' | 'page' | 'product' | 'query';\n\nexport interface Product {\n  /**\n   * Whether the product is available for sale (e.g. in stock)\n   */\n  available: boolean;\n\n  /**\n   * HTML formatted product description\n   */\n  body: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  compare_at_price_max: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  compare_at_price_min: string;\n\n  featured_image: FeaturedImage;\n\n  /**\n   * Shopify product handle\n   *\n   * @example \"my-product-slug\"\n   */\n  handle: string;\n\n  /**\n   * Shopify product id\n   *\n   * @example 123324324\n   */\n  id: number;\n\n  /**\n   * Product image url\n   *\n   * @example \"https://cdn.shopify.com/.../image.jpg?v=1234\"\n   */\n  image: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  price: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  price_max: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  price_min: string;\n\n  /**\n   * @example [\"best-sellers\", \"hair-care-routine\"]\n   */\n  tags: string[];\n\n  /**\n   * @example \"My Product\"\n   */\n  title: string;\n\n  /**\n   * @example \"hair-care\"\n   */\n  type: string;\n\n  /**\n   * @example \"/products/my-handle?_pos=1&_psq=hair&_ss=e&_v=1.0\"\n   */\n  url: string;\n\n  /**\n   * A product variant is returned only when the query matches terms specific to the variant title.\n   * Only the variant with the most matching terms is returned. When a variant is returned, the\n   * following product_object fields will match those of the variant:\n   *\n   * * featured_image\n   * * image\n   * * url\n   *\n   * cspell:ignore snowbo\n   *\n   * For example, a store has a snowboard with a blue variant and a light blue variant. If you\n   * search for snowbo, then only the snowboard product is returned. However, if you search for\n   * light blue snowbo, then the snowboard product is returned with the light blue variant.\n   */\n  variants: Variant[];\n\n  /**\n   * @example \"my company name\"\n   */\n  vendor: string;\n}\n\nexport interface Variant {\n  available: boolean;\n\n  /**\n   * might be wrong\n   */\n  compare_at_price: number;\n\n  featured_image: FeaturedImage;\n\n  id: number;\n  image: string;\n\n  /**\n   * might be wrong\n   */\n  price: number;\n  title: string;\n  url: string;\n}\n\n/**\n * A query represents an auto-complete suggestion. For example, based on what someone has typed in,\n * zero or more queries will be generated that represent more specific or similar queries to\n * consider using.\n */\nexport interface Query {\n  /**\n   * @example \"<mark>hair</mark><span>spray</span>\"\n   */\n  styled_text: string;\n\n  /**\n   * @example \"hairspray\"\n   */\n  text: string;\n\n  /**\n   * @example \"/search?_pos=1&_psq=hair&_ss=e&_v=1.0&q=hairspray\"\n   */\n  url: string;\n}\n\nexport interface FeaturedImage {\n  alt: string;\n  aspect_ratio: number;\n  height: number;\n  url: string;\n  width: number;\n}\n\nexport interface Collection {\n  body: string;\n  featured_image: FeaturedImage;\n  handle: string;\n  id: number;\n}\n\nexport interface Page {\n  author: string;\n  body: string;\n  handle: string;\n  id: number;\n  published_at: string;\n  title: string;\n  url: string;\n}\n\nexport interface Article {\n  author: string;\n  body: string;\n  handle: string;\n  id: number;\n  image: string;\n  published_at: string;\n  summary_html: string;\n  tags: string[];\n  title: string;\n  url: string;\n}\n\nexport interface ShopifySuggestResponseBody {\n  resources: {\n    results: {\n      articles: Article[];\n      collections: Collection[];\n      pages: Page[];\n      products: Product[];\n      queries: Query[];\n    };\n  };\n}\n\nexport interface ShopifyErrorResponseBody {\n  description: string;\n  message: string;\n  status: string;\n}\n\n/**\n * Represents the response body.\n *\n * In a full implementation that parsed the response body of error responses, this should actually\n * be a union of ShopifySuggestResponseBody and also ShopifyErrorResponseBody. However, we are not\n * actually leveraging ShopifyErrorResponseBody. We are just leaving the type defined for\n * documentation purposes. The promise returned by suggest only ever resolves on success so we can\n * limit the output type to the success type.\n */\nexport type ResponseBody = ShopifySuggestResponseBody;\n\n/**\n * Send a search request to the Shopify api and receive a search response.\n *\n * If section id is provided, this returns a string. If section id is not provided, this returns an\n * object.\n *\n * I did not bother to make the output type derived from the presence of the section id. For now the\n * output type has to be specified via the generic value if the output should be interpreted as a\n * string. Without specifying the generic parameter this defaults to producing an object.\n *\n * @param input various search criteria\n */\nexport async function suggest<OutputType = ResponseBody>(input: SuggestInput) {\n  assert(typeof input.query === 'string' && input.query.length, 'Invalid input');\n\n  if (input.use_graphql) {\n    const responseBody = await suggestBlogArticles(input);\n    return <OutputType>responseBody;\n  }\n\n  let path = Shopify.routes.root + 'search/suggest';\n  if (!input.section_id) {\n    path += '.json';\n  }\n\n  const url = new URL(path, location.href);\n  url.searchParams.set('q', input.query);\n\n  if (input.section_id) {\n    url.searchParams.set('section_id', input.section_id);\n  }\n\n  if (Number.isInteger(input.limit)) {\n    url.searchParams.set('resources[limit]', input.limit.toString());\n  }\n\n  if (input.limit_scope) {\n    url.searchParams.set('resources[limit_scope]', input.limit_scope);\n  }\n\n  if (Array.isArray(input.resource_types) && input.resource_types.length > 0) {\n    url.searchParams.set('resources[type]', input.resource_types.join(','));\n  }\n\n  if (input.prefix) {\n    url.searchParams.set('options[prefix]', input.prefix);\n  }\n\n  if (Array.isArray(input.fields) && input.fields.length > 0) {\n    url.searchParams.set('resources[options][fields]', input.fields.join(','));\n  }\n\n  if (input.unavailable_products) {\n    url.searchParams.set('resources[options][unavailable_products]', input.unavailable_products);\n  }\n\n  let response: Response;\n  try {\n    response = await fetch(url);\n  } catch (error) {\n    const suggestError = new SuggestError('Failed to fetch search results', { cause: error });\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // If not using section id, fetch json response body for errors including 422, 417, and 429. See\n  // https://shopify.dev/docs/api/ajax/reference/predictive-search#invalid-parameter-error. We could\n  // fetch and then parse these response bodies, but basically there is no need except for maybe the\n  // description hint for 422. Those kinds of errors would be obvious and permanent programming\n  // errors though so we would quickly get those resolved. So just recording the status in the\n  // thrown error is sufficient. We do not really need to parse the body.\n\n  if (!response.ok) {\n    const suggestError = new SuggestError(\n      `Received status ${response.status} fetching search results`);\n    suggestError.status = response.status;\n    suggestError.input = input;\n    suggestError.url = url.toString();\n\n    // Try to get the Retry-After header value and embed this in the thrown error so that we grant\n    // callers the ability to check it.\n    //\n    // See https://shopify.dev/docs/api/ajax/reference/predictive-search#too-many-requests.\n    if (response.status === 429) {\n      const retryAfter = response.headers.get('Retry-After');\n      const retryAfterSeconds = parseInt(retryAfter, 10);\n      if (Number.isInteger(retryAfterSeconds)) {\n        suggestError.retry_after = retryAfterSeconds;\n      }\n    }\n\n    // For certain errors that have documented response bodies, try to fetch the response body so\n    // that we can get more details into the error.\n    if ([417, 422, 429, 500].includes(response.status)) {\n      try {\n        const responseBody = await response.json();\n        suggestError.response_body = responseBody;\n      } catch (error) {\n        console.log(error);\n      }\n    }\n\n    throw suggestError;\n  }\n\n  let responseText: string;\n\n  try {\n    responseText = await response.text();\n  } catch (error) {\n    const suggestError = new SuggestError('Failed to read the search results response body',\n      { cause: error });\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // If section id was provided, the response body is a string containing html formatted text that\n  // was server side rendered according to the specified section liquid and the search results. We\n  // just return the string. The caller may have a preference on how to parse the html or whether to\n  // parse it at all, so we do not parse on the caller's behalf.\n\n  if (input.section_id) {\n    return <OutputType>responseText;\n  }\n\n  // Without a section id we are fetching from the suggest.json endpoint so we parse the results and\n  // do some basic validation of the parsed object.\n\n  // Shopify occasionally answers this endpoint with a success status and an empty body, typically a\n  // dropped or truncated connection in a mobile in-app browser. We have verified that refetching\n  // one of these same query urls ourselves returns a full, valid result set, so the empty body is a\n  // visitor-side truncation rather than a broken endpoint or a missing resource. There is nothing\n  // to parse and no results to show, so degrade to an empty result set rather than throwing on the\n  // empty input. The caller renders an empty preview (falling back to search history) instead of an\n  // error, and the next keystroke issues a fresh request that normally succeeds.\n\n  if (!responseText) {\n    // Typing the literal as the concrete response body shape gets it checked at construction, then\n    // the return asserts to the caller-chosen generic the same way the other return sites do, since\n    // a concrete value is not assignable to an unconstrained type parameter on its own.\n\n    const responseBody: ResponseBody = {\n      resources: {\n        results: { articles: [], collections: [], pages: [], products: [], queries: [] }\n      }\n    };\n\n    return <OutputType>responseBody;\n  }\n\n  let responseBody;\n\n  try {\n    responseBody = JSON.parse(responseText);\n  } catch (error) {\n    const suggestError = new SuggestError(\n      'Failed to parse the search results response body as JSON', { cause: error });\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.response_body = responseText;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // We expect the type of the response body object to always be the documented type. However,\n  // Shopify could change this out from under us at any time, so we keep this as a runtime check\n  // instead of a hard cast.\n\n  if (!isShopifySuggestResponseBody(responseBody)) {\n    const suggestError = new SuggestError('Unexpected response body');\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.response_body = responseBody;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  return <OutputType>responseBody;\n}\n\n/**\n * The response body of the blog article search query, holding only the fields that query asks for.\n */\ninterface BlogArticleSearchResponseBody {\n  data: {\n    blog: {\n      articles: {\n        nodes: {\n          authorV2: { name: string; };\n          excerptHtml: string;\n          handle: string;\n\n          /**\n           * @example \"gid://shopify/Article/634932658276\"\n           */\n          id: string;\n\n          image: { url: string; };\n          publishedAt: string;\n          tags: string[];\n          title: string;\n        }[];\n      };\n    };\n  };\n  errors?: unknown[];\n}\n\nfunction isBlogArticleSearchResponseBody(value: any): value is BlogArticleSearchResponseBody {\n  return Array.isArray(value?.data?.blog?.articles?.nodes);\n}\n\n/**\n * Searches the articles of one blog through the Storefront GraphQL API. This works without an\n * access token, since Shopify allows reading articles without one. The results are reshaped into\n * the suggest response body so callers can treat both paths the same.\n *\n * @see https://shopify.dev/docs/api/storefront/2026-10/queries/blog\n */\nasync function suggestBlogArticles(input: SuggestInput) {\n  assert(typeof input.blog_handle === 'string' && input.blog_handle.length, 'Invalid blog handle');\n\n  const url = new URL('/api/2026-10/graphql.json', location.href);\n\n  const query = `query ($blogHandle: String!, $query: String!, $first: Int!) {\n    blog(handle: $blogHandle) {\n      articles(first: $first, query: $query, sortKey: RELEVANCE) {\n        nodes {\n          id\n          handle\n          title\n          tags\n          publishedAt\n          excerptHtml\n          authorV2 { name }\n          image { url }\n        }\n      }\n    }\n  }`;\n\n  const variables = {\n    blogHandle: input.blog_handle,\n    query: input.query,\n    first: Number.isInteger(input.limit) ? input.limit : 10\n  };\n\n  let response: Response;\n  try {\n    response = await fetch(url, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ query, variables })\n    });\n  } catch (error) {\n    const suggestError = new SuggestError('Failed to fetch blog article search results',\n      { cause: error });\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  if (!response.ok) {\n    const suggestError = new SuggestError(\n      `Received status ${response.status} fetching search results`);\n    suggestError.status = response.status;\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  let responseBody;\n  try {\n    responseBody = await response.json();\n  } catch (error) {\n    const suggestError = new SuggestError(\n      'Failed to parse the blog article search response body as JSON', { cause: error });\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // GraphQL reports a bad query with a success status and an errors array in the body. A blog that\n  // does not exist comes back as a null blog rather than as an error, which the type guard rejects.\n\n  if (!isBlogArticleSearchResponseBody(responseBody) || Array.isArray(responseBody.errors)) {\n    const suggestError = new SuggestError('Unexpected response body');\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.response_body = responseBody;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // The GraphQL response comes back in a different shape than the AJAX suggest endpoint's JSON, so\n  // we convert each article into the shape the AJAX endpoint uses. That way callers get the same\n  // search results format whichever path served the search. We don't assume every article has\n  // everything filled in, so an article with no image or no author just ends up with that field\n  // empty rather than throwing. The body is always empty, since we leave it out of the query to\n  // keep the response small.\n\n  const articles: Article[] = [];\n  for (const node of responseBody.data.blog.articles.nodes) {\n    articles.push({\n      author: node.authorV2?.name,\n      body: undefined,\n      handle: node.handle,\n      id: parseInt(node.id.split('/').pop(), 10),\n      image: node.image?.url,\n      published_at: node.publishedAt,\n      summary_html: node.excerptHtml,\n      tags: node.tags,\n      title: node.title,\n      url: `${Shopify.routes.root}blogs/${input.blog_handle}/${node.handle}`\n    });\n  }\n\n  const suggestResponseBody: ResponseBody = {\n    resources: {\n      results: { articles, collections: [], pages: [], products: [], queries: [] }\n    }\n  };\n\n  return suggestResponseBody;\n}\n\nexport function isShopifySuggestResponseBody(value: any): value is ShopifySuggestResponseBody {\n  return typeof value === 'object' && value !== null && typeof value.resources === 'object' &&\n    value.resources !== null;\n}\n\n/**\n * Represents a Shopify Suggest API error.\n */\nexport class SuggestError extends Error {\n  /**\n   * The input to the suggest function to help with diagnosing what caused the error when it might\n   * be related to the input\n   */\n  public declare input: SuggestInput;\n\n  /**\n   * Response status code\n   *\n   * @example 200\n   */\n  public declare status: number;\n\n  /**\n   * The response body. This could be a string or an object or something else.\n   */\n  public declare response_body: any;\n\n  /**\n   * If the status is 429, indicating a rate limit error, then the suggest function also tries to\n   * access the Retry-After header and stash the value in the error.\n   *\n   * This is the number of seconds. The caller of suggest should make sure to delay any future calls\n   * by at least this amount of time to prevent continuing to receive more 429s.\n   */\n  public declare retry_after: number;\n\n  /**\n   * The request or response url involved in the request that erred.\n   */\n  public declare url: string;\n\n  constructor(message?: string, options?: ErrorOptions) {\n    super(message, options);\n    this.name = this.constructor.name;\n  }\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { v4 as uuidv4 } from 'uuid';\nimport { build } from '../../../lib/shopify/image-url.js';\nimport { suggest, type Query, type Product } from '../../../lib/shopify/predictive-search.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the search input and the search input results preview.\n */\nclass ShopifySearchInput extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-trigger-focus'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Boolean represented as string to indicate whether user is on search results page\n     */\n    isSearchPage: string;\n\n    /**\n     * Mechanism to externally focus the user on the input element\n     */\n    triggerFocus: string;\n  };\n\n  private onFormSubmitBound = this.onFormSubmit.bind(this);\n  private onFocusBound = this.onFocus.bind(this);\n  private onDocumentClickBound = this.onDocumentClick.bind(this);\n  private onSearchCloseBound = this.onSearchClose.bind(this);\n  private onKeyUpBound = this.onKeyUp.bind(this);\n  private onTypingPausedDebounced = debounce(this.onTypingPaused.bind(this));\n  private onKeyDownBound = this.onKeyDown.bind(this);\n  private onInputBound = this.onInput.bind(this);\n  private onBlurBound = this.onBlur.bind(this);\n  private onPageShowBound = this.onPageShow.bind(this);\n  private onProductPreviewClickBound = this.onProductPreviewClick.bind(this);\n  private onSearchForClickBound = this.onSearchForClick.bind(this);\n  private onSuggestedQueryClickBound = this.onSuggestedQueryClick.bind(this);\n  private suggestionActiveIndex = 0;\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    const formEl = this.shadowRoot.querySelector('form');\n    formEl.addEventListener('submit', this.onFormSubmitBound);\n\n    const iconReset = this.shadowRoot.querySelector('.icon-close');\n    iconReset.addEventListener('click', this.onSearchCloseBound);\n\n    const searchInput = this.shadowRoot.querySelector('input');\n    searchInput.addEventListener('keyup', this.onKeyUpBound);\n    searchInput.addEventListener('keydown', this.onKeyDownBound);\n    searchInput.addEventListener('input', this.onInputBound);\n    searchInput.addEventListener('focus', this.onFocusBound);\n    searchInput.addEventListener('blur', this.onBlurBound);\n    searchInput.addEventListener('click', onInputClick);\n\n    addEventListener('pageshow', this.onPageShowBound);\n\n    this.render();\n  }\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && oldValue !== newValue) {\n      this.render();\n    }\n  }\n\n  public disconnectedCallback() {\n    const iconReset = this.shadowRoot.querySelector('.icon-close');\n    iconReset?.removeEventListener('click', this.onSearchCloseBound);\n\n    const searchInput = this.shadowRoot.querySelector('input');\n    if (searchInput) {\n      searchInput.removeEventListener('keyup', this.onKeyUpBound);\n      searchInput.removeEventListener('keydown', this.onKeyDownBound);\n      searchInput.removeEventListener('input', this.onInputBound);\n      searchInput.removeEventListener('focus', this.onFocusBound);\n      searchInput.removeEventListener('blur', this.onBlurBound);\n      searchInput.removeEventListener('click', onInputClick);\n    }\n\n    const formEl = this.shadowRoot.querySelector('form');\n    formEl?.removeEventListener('submit', this.onFormSubmitBound);\n\n    removeEventListener('pageshow', this.onPageShowBound);\n\n    // showSearchPreview puts this on the document and hideSearchPreview takes it back off, so it is\n    // still there if we get torn down while the preview pane is open. Nothing would remove it then,\n    // and document would hold onto us and keep sending us clicks from a page we left.\n    document.removeEventListener('click', this.onDocumentClickBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    const searchInput = this.shadowRoot.querySelector('input');\n    if (searchInput.value.length === 0) {\n      this.shadowRoot.querySelector<HTMLButtonElement>('.icon-search').disabled = true;\n    }\n\n    if (this.dataset.isSearchPage === 'true') {\n      const urlParams = new URLSearchParams(location.search);\n      const searchTerm = urlParams.get('q');\n      searchInput.value = searchTerm;\n      this.updateSearchPageReset();\n\n      // The search parameter is not guaranteed to exist on the search page so this optional\n      // chaining is important.\n\n      if (searchTerm?.length > 0) {\n        const searchIcon = this.shadowRoot.querySelector<HTMLButtonElement>('.icon-search');\n        searchIcon.disabled = true;\n        this.suggest(searchInput.value, false).catch(handleSuggestError);\n      }\n\n      this.renderPreviewPane(searchInput.value, [], []);\n    }\n\n    if (this.dataset.triggerFocus) {\n      const searchInput = this.shadowRoot.querySelector('input');\n      searchInput.focus();\n    }\n  }\n\n  private showSearchPreview() {\n    const scrollbarWidth = innerWidth - document.documentElement.clientWidth;\n    if (Number.isInteger(scrollbarWidth)) {\n      document.documentElement.style.setProperty('--scrollbar-width', scrollbarWidth + 'px');\n    }\n\n    document.documentElement.dataset.scrollLocked = 'true';\n\n    const suggestionEl = this.shadowRoot.querySelector('ul');\n    suggestionEl.classList.add('visible');\n\n    const iconReset = this.shadowRoot.querySelector('.icon-close');\n    iconReset.classList.add('visible');\n    this.updateSearchPageReset();\n\n    document.addEventListener('click', this.onDocumentClickBound);\n\n    const formEl = this.shadowRoot.querySelector('form');\n    formEl.ariaExpanded = 'true';\n    formEl.classList.add('visible');\n\n    const headerEl = document.querySelector<HTMLElement>('header .wrapper');\n    if (headerEl) {\n      headerEl.dataset.search = 'search-visible';\n    }\n  }\n\n  private hideSearchPreview() {\n    // We are not the only thing that sets the scroll lock flag, and it is just on or off, so it\n    // does not say who wanted it. A click that opens a modal also closes this pane, and the modal\n    // opens first, so deleting the flag here would let the page scroll under a modal that is still\n    // up. The backdrop is active for as long as a modal is open, so in that case we leave the flag\n    // alone and let whatever is up remove it when it closes.\n    const backdrop = document.querySelector('#modal-backdrop');\n    if (!backdrop?.classList.contains('active')) {\n      delete document.documentElement.dataset.scrollLocked;\n    }\n\n    const suggestion = this.shadowRoot.querySelector('ul');\n    suggestion.classList.remove('visible');\n\n    document.removeEventListener('click', this.onDocumentClickBound);\n\n    const form = this.shadowRoot.querySelector('form');\n    form.classList.remove('visible');\n\n    if (this.dataset.isSearchPage !== 'true') {\n      const reset = this.shadowRoot.querySelector('.icon-close');\n      reset.classList.remove('visible');\n\n      const header = document.querySelector<HTMLElement>('header .wrapper');\n      if (header) {\n        header.dataset.search = 'search-hidden';\n      }\n    }\n  }\n\n  private onSearchClose(event: Event) {\n    event.preventDefault();\n    event.stopPropagation();\n\n    this.hideSearchPreview();\n\n    const input = this.shadowRoot.querySelector('input');\n    input.value = '';\n    this.updateSearchPageReset();\n\n    if (this.dataset.isSearchPage === 'true' || innerWidth >= 1024) {\n      input.focus();\n    }\n\n    const button = this.shadowRoot.querySelector<HTMLButtonElement>('.icon-search');\n    button.disabled = true;\n\n    const resetEvent = new Event('search-input-reset', { bubbles: true, composed: true });\n    this.dispatchEvent(resetEvent);\n  }\n\n  /**\n   * The search page keeps the search input open, so its close button only clears the text. It shows\n   * only while there is text to clear, since on an empty input it looked like a close button that\n   * did nothing. Elsewhere the button closes the search, so it is left alone.\n   */\n  private updateSearchPageReset() {\n    if (this.dataset.isSearchPage !== 'true') {\n      return;\n    }\n\n    const input = this.shadowRoot.querySelector('input');\n    const reset = this.shadowRoot.querySelector('.icon-close');\n    reset.classList.toggle('visible', input.value.length > 0);\n  }\n\n  private onInput(_event: Event) {\n    this.updateSearchPageReset();\n\n    // This runs on every change rather than once typing pauses, so the button is already enabled if\n    // Enter comes right after the last letter. A form ignores Enter while its submit button is\n    // disabled.\n    this.updateSearchButton();\n  }\n\n  private onKeyDown(event: KeyboardEvent) {\n    if (event.key === 'ArrowDown') {\n      event.stopPropagation();\n      const anchors =\n        this.shadowRoot.querySelectorAll('li.search-preview-item a.search-preview-link');\n      if (this.suggestionActiveIndex >= anchors.length) {\n        return;\n      }\n\n      this.suggestionActiveIndex++;\n      this.markSuggestionActiveAtIndex(this.suggestionActiveIndex);\n    } else if (event.key === 'ArrowUp') {\n      event.stopPropagation();\n      if (this.suggestionActiveIndex === 0) {\n        return;\n      }\n\n      this.suggestionActiveIndex--;\n      if (this.suggestionActiveIndex === 0) {\n        this.shadowRoot.querySelector('a.active')?.classList.remove('active');\n        this.shadowRoot.querySelector('input').focus();\n      } else {\n        this.markSuggestionActiveAtIndex(this.suggestionActiveIndex);\n      }\n    } else if (event.key === 'Enter') {\n      const activeAnchor =\n        this.shadowRoot.querySelector<HTMLAnchorElement>('a.search-preview-link.active');\n      if (activeAnchor) {\n        event.preventDefault();\n        activeAnchor.click();\n      }\n    }\n  }\n\n  private onKeyUp(event: KeyboardEvent) {\n    // These keys don't change the text, so we skip them here before the debounce. Letting one\n    // through would cancel the update still waiting from the last letter typed.\n    if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') {\n      return;\n    }\n\n    this.onTypingPausedDebounced(event);\n  }\n\n  private onTypingPaused(event: KeyboardEvent) {\n    const searchInput = this.shadowRoot.querySelector('input');\n    if (event.key === 'Escape' || searchInput.value.length === 0) {\n      this.hideSearchPreview();\n    } else if (searchInput.value.length >= 3) {\n      this.suggest(searchInput.value).catch(handleSuggestError);\n    } else {\n      this.renderPreviewPane(searchInput.value, [], []);\n      this.showSearchPreview();\n    }\n  }\n\n  /**\n   * Disables the search button while the input is empty, or on the search page while the input\n   * still matches the search being shown, and enables it otherwise.\n   */\n  private updateSearchButton() {\n    const searchInput = this.shadowRoot.querySelector('input');\n    const searchButton = this.shadowRoot.querySelector<HTMLButtonElement>('.icon-search');\n    if (searchInput.value.length === 0) {\n      searchButton.disabled = true;\n      return;\n    }\n\n    if (this.dataset.isSearchPage === 'true') {\n      const url = new URL(location.href);\n\n      // The q parameter is not guaranteed to be found, so this may be undefined.\n      const urlQueryParam = url.searchParams.get('q');\n\n      if (urlQueryParam && searchInput.value === urlQueryParam) {\n        // The button remains disabled because the input has not changed\n        searchButton.disabled = true;\n      } else {\n        searchButton.disabled = false;\n      }\n    } else {\n      // On all pages other than the search page, enable the search button on load.\n      searchButton.disabled = false;\n    }\n  }\n\n  private onPageShow(event: PageTransitionEvent) {\n    // Going back to this page can restore it from the back/forward cache exactly as we left it,\n    // including the search button the submit handler disabled. So we work out the button state\n    // again, otherwise the visitor could not search from the restored page without typing first.\n    if (event.persisted) {\n      this.updateSearchButton();\n    }\n  }\n\n  private onDocumentClick() {\n    this.hideSearchPreview();\n  }\n\n  private onFocus(event: FocusEvent) {\n    event.stopPropagation();\n\n    const form = this.shadowRoot.querySelector('form');\n    form.classList.add('focus');\n\n    // The global scroll-padding-top on <html> makes the browser nudge the page upward on every\n    // keystroke to keep the caret that far below the top of the viewport. The input is in the\n    // sticky header and already visible, so the padding is not needed while it is focused.\n    document.documentElement.dataset.searchFocused = 'true';\n\n    const input = this.shadowRoot.querySelector('input');\n    if (input.value.length >= 1) {\n      this.showSearchPreview();\n    }\n  }\n\n  private onBlur(_event: FocusEvent) {\n    const form = this.shadowRoot.querySelector('form');\n    form.classList.remove('focus');\n\n    // Restore scroll-padding-top so anchor link jumps still clear the header. Deferred because\n    // onSearchClose refocuses the input immediately after blur on the search page, and the\n    // attribute should stay if focus returns before the timeout fires. A 0ms timeout doesn't fix\n    // the issue, so we wait for 100ms. It's safe because the CSS property we're toggling via the\n    // data attribute only plays a role after user interaction with focusable elements or anchor\n    // links.\n    setTimeout(() => {\n      const input = this.shadowRoot.querySelector('input');\n      if (this.shadowRoot.activeElement !== input) {\n        delete document.documentElement.dataset.searchFocused;\n      }\n    }, 100);\n  }\n\n  private onFormSubmit(_event: SubmitEvent) {\n    // After a submit the browser takes a moment to leave for the results page, and this page stays\n    // usable in the meantime. We disable the search button so another click, or another Enter in\n    // the input, does not submit the same search again. A form ignores Enter while its submit\n    // button is disabled.\n    const searchButton = this.shadowRoot.querySelector<HTMLButtonElement>('.icon-search');\n    searchButton.disabled = true;\n\n    const input = this.shadowRoot.querySelector('input');\n    const query = input.value;\n\n    const searchEvent = new CustomEvent<SearchedEvent['detail']>('searched', {\n      bubbles: true,\n      composed: true,\n      detail: { event_id: uuidv4(), query }\n    });\n    this.dispatchEvent(searchEvent);\n  }\n\n  private async suggest(query: string, showPreviewDrawer = true) {\n    const body = await suggest({\n      query,\n      resource_types: ['product', 'query'],\n      limit: 4,\n      limit_scope: 'each',\n      prefix: 'last'\n    });\n\n    // The visitor may have kept typing or cleared the input while the request was out, and showing\n    // results for the old text would bring back a panel that was just hidden.\n    if (this.shadowRoot.querySelector('input').value !== query) {\n      return;\n    }\n\n    const results = body.resources.results;\n    this.renderPreviewPane(query, results.queries, results.products);\n\n    if (showPreviewDrawer) {\n      this.showSearchPreview();\n    }\n  }\n\n  private renderPreviewPane(query: string, suggestions: Query[], products: Product[]) {\n    const previewEl = this.shadowRoot.querySelector('ul');\n    previewEl.innerHTML = '';\n    this.suggestionActiveIndex = 0;\n\n    let position = 1;\n    let filteredQueries: Query[] = [];\n\n    if (suggestions.length > 1) {\n      filteredQueries = suggestions.filter(q => q.text !== query);\n\n      if (filteredQueries.length > 0) {\n        const liHeaderEl = document.createElement('li');\n        liHeaderEl.textContent = 'Suggestions';\n        liHeaderEl.classList.add('search-preview-heading');\n        liHeaderEl.role = 'heading';\n        liHeaderEl.ariaLevel = '3';\n        liHeaderEl.tabIndex = 0;\n        previewEl.appendChild(liHeaderEl);\n\n        for (const suggestedQuery of filteredQueries) {\n          const liEl = this.createSearchQuerySuggestionListElement(suggestedQuery, position, query);\n          previewEl.appendChild(liEl);\n          position++;\n        }\n      }\n    }\n\n    if (products.length > 0) {\n      const liHeaderEl = document.createElement('li');\n      liHeaderEl.textContent = 'Products';\n      liHeaderEl.classList.add('search-preview-heading');\n      liHeaderEl.role = 'heading';\n      liHeaderEl.ariaLevel = '3';\n      liHeaderEl.tabIndex = 0;\n      previewEl.appendChild(liHeaderEl);\n\n      for (const product of products) {\n        const liEl = this.createProductSuggestionListElement(product, position, query);\n        previewEl.appendChild(liEl);\n        position++;\n      }\n    }\n\n    if (query.length) {\n      let userQuery = suggestions.find(q => q.text === query);\n      if (!userQuery) {\n        userQuery = {\n          text: query,\n          styled_text: query,\n          url: `/search?q=${query}`\n        };\n      }\n\n      const anchorEl = document.createElement('a');\n      anchorEl.href = userQuery.url;\n      anchorEl.dataset.searchTerm = userQuery.text;\n      anchorEl.textContent = 'Search';\n      anchorEl.addEventListener('click', this.onSearchForClickBound);\n\n      const liEl = document.createElement('li');\n      liEl.classList.add('search-cta');\n      liEl.appendChild(anchorEl);\n\n      previewEl.appendChild(liEl);\n    }\n  }\n\n  private createProductSuggestionListElement(product: Product, position: number,\n    currentQuery: string) {\n    const preview = document.createElement('search-product-preview');\n\n    // Predictive search hands back the full size featured image, but the preview shows it at 32px.\n    // We ask the CDN for a 64px wide copy, which covers screens with twice the pixel density.\n    const imageUrl = product.featured_image.url;\n    if (imageUrl) {\n      preview.dataset.imageUrl = build(imageUrl, { width: 64 });\n    }\n\n    preview.dataset.title = product.title;\n\n    const anchor = document.createElement('a');\n    anchor.href = createSuggestedProductUrl(product.handle, position, currentQuery);\n    anchor.ariaLabel = `See product details for ${product.title}`;\n    anchor.dataset.searchTerm = currentQuery;\n    anchor.classList.add('search-preview-link');\n    anchor.classList.add('product');\n    anchor.addEventListener('click', this.onProductPreviewClickBound);\n    anchor.appendChild(preview);\n\n    const item = document.createElement('li');\n    item.classList.add('search-preview-item');\n    item.appendChild(anchor);\n\n    return item;\n  }\n\n  private onProductPreviewClick(_event: MouseEvent) {\n    const input = this.shadowRoot.querySelector('input');\n\n    type Detail = SearchedEvent['detail'];\n    const searchEvent = new CustomEvent<Detail>('searched', {\n      bubbles: true,\n      composed: true,\n      detail: { event_id: uuidv4(), query: input.value }\n    });\n    this.dispatchEvent(searchEvent);\n  }\n\n  private markSuggestionActiveAtIndex(index: number) {\n    const old = this.shadowRoot.querySelector('a.search-preview-link.active');\n    if (old) {\n      old.classList.remove('active');\n    }\n\n    const anchors =\n      this.shadowRoot.querySelectorAll('li.search-preview-item a.search-preview-link');\n    const adjustedIndex = index - 1;\n    const anchorEl = Array.from(anchors)[adjustedIndex];\n    anchorEl?.classList.add('active');\n  }\n\n  private onSearchForClick(event: MouseEvent) {\n    if (!(event.target instanceof HTMLAnchorElement)) {\n      return;\n    }\n\n    const query = event.target.dataset.searchTerm;\n    type Detail = SearchedEvent['detail'];\n    const searchEvent = new CustomEvent<Detail>('searched', {\n      bubbles: true,\n      composed: true,\n      detail: { event_id: uuidv4(), query }\n    });\n    this.dispatchEvent(searchEvent);\n  }\n\n  private onSuggestedQueryClick(event: MouseEvent) {\n    // The suggestion text is wrapped in mark and span elements, so the click usually lands on one\n    // of those rather than on the anchor this listener is attached to.\n    if (!(event.currentTarget instanceof HTMLAnchorElement)) {\n      return;\n    }\n\n    const query = event.currentTarget.dataset.searchTerm;\n    type Detail = SearchedEvent['detail'];\n    const searchEvent = new CustomEvent<Detail>('searched', {\n      bubbles: true,\n      composed: true,\n      detail: { event_id: uuidv4(), query }\n    });\n    this.dispatchEvent(searchEvent);\n  }\n\n  private createSearchQuerySuggestionListElement(query: Query, position: number,\n    currentQuery: string) {\n    const anchor = document.createElement('a');\n    anchor.href = createSuggestedSearchTermUrl(position, query.text, currentQuery);\n    anchor.innerHTML = query.styled_text;\n    anchor.dataset.searchTerm = query.text;\n    anchor.classList.add('search-preview-link');\n    anchor.classList.add('suggestion');\n    anchor.ariaLabel = `See all results for \"${query.text}\"`;\n    anchor.addEventListener('click', this.onSuggestedQueryClickBound);\n\n    const item = document.createElement('li');\n    item.classList.add('search-preview-item');\n    item.appendChild(anchor);\n\n    return item;\n  }\n}\n\nfunction handleSuggestError(error: any) {\n  console.warn(error);\n  renderErrorMessage();\n}\n\nfunction renderErrorMessage() {\n  const resultList = document.querySelector('.results .overflow ol');\n  if (!resultList) {\n    return;\n  }\n\n  const liEl = document.createElement('li');\n  const headerEl = document.createElement('h4');\n  headerEl.textContent = 'Oh no! There was a problem searching our products. Please try ' +\n    'again in a few moments.';\n  liEl.appendChild(headerEl);\n\n  resultList.appendChild(liEl);\n}\n\nfunction createSuggestedSearchTermUrl(position: number, suggestedQuery: string,\n  currentQuery: string) {\n  const url = new URL('/search', location.origin);\n  url.searchParams.set('_pos', position.toString());\n  url.searchParams.set('_psq', currentQuery);\n  url.searchParams.set('_ss', 'e');\n  url.searchParams.set('_v', '1.0');\n  url.searchParams.set('q', suggestedQuery);\n  return url.href;\n}\n\nfunction createSuggestedProductUrl(productHandle: string, position: number, currentQuery: string) {\n  const url = new URL(`/products/${productHandle}`, location.origin);\n  url.searchParams.set('_pos', position.toString());\n  url.searchParams.set('_psq', currentQuery);\n  url.searchParams.set('_ss', 'e');\n  url.searchParams.set('_v', '1.0');\n\n  return url.href;\n}\n\n/**\n * Clicking the search input should not close the search preview pane. Any click that reaches the\n * document closes the pane, so we stop the input's clicks here before they get there. The focus\n * handler then decides whether to show the pane for the term already in the input.\n */\nfunction onInputClick(event: MouseEvent) {\n  event.stopPropagation();\n}\n\nfunction debounce<F extends (...params: any[]) => void>(func: F, timeout = 300) {\n  let timer: ReturnType<typeof setTimeout> = null;\n  return (...args: unknown[]) => {\n    clearTimeout(timer);\n    timer = setTimeout(() => func(...args), timeout);\n  };\n}\n\n/**\n * Fired when the user clears the content of the search input.\n */\ntype SearchInputResetEvent = Event;\n\n/**\n * Fired when the visitor searches our products from the header search input.\n *\n * We fire this from several places. Submitting the form, clicking the Search link in the preview\n * pane, clicking a suggested search, clicking one of the visitor's past searches, and clicking a\n * suggested product. The event looks the same in all five cases, so a listener cannot tell which\n * one of them happened. The suggested product case is the surprising one, because the visitor goes\n * straight to a product page and never sees a page of search results.\n */\ntype SearchedEvent = CustomEvent<{\n  /**\n   * Identifies this one occurrence of the event. An analytics tool that receives the same search\n   * twice can use it to tell a repeated delivery apart from a second search.\n   */\n  event_id: string;\n\n  /**\n   * What we are searching for. This is usually text the visitor typed, so it can be anything at\n   * all, and it does not have to match a real product or return any results. When the visitor\n   * clicks a suggestion or one of their past searches, this carries that text instead of whatever\n   * they had typed.\n   */\n  query: string;\n}>;\n\ndeclare global {\n  interface WindowEventMap {\n    'search-input-reset': SearchInputResetEvent;\n    searched: SearchedEvent;\n  }\n\n  interface HTMLElementTagNameMap {\n    'shopify-search-input': ShopifySearchInput;\n  }\n}\n\nif (!customElements.get('shopify-search-input')) {\n  customElements.define('shopify-search-input', ShopifySearchInput);\n}\n","/**\n * Releases the handle returned from the ready() function.\n *\n * This is idempotent. Additional calls are pointless but harmless.\n */\nexport type ReleaseHandle = () => void;\n\n/**\n * A subset of events that pertain to document ready state.\n */\ntype LoadingEventMap = Pick<DocumentEventMap, 'DOMContentLoaded' | 'load'>;\n\n/**\n * Calls the provided callback when the document is ready. This works both when the document is\n * already ready and when it is not, so callers do not have to care. This exists because it ensures\n * that each caller consistently expresses its dependency on document readiness, and to save a bit\n * of boilerplate.\n *\n * A document is considered ready when it has been parsed. This corresponds to the ready state of\n * 'interactive'. When the document has been fully loaded, the ready state is 'complete'. The\n * callback runs either way, so callers do not have to check which one they are in.\n *\n * Ready means the html is parsed and the DOM is there to query. It does not mean the page's other\n * scripts have run. Deferred scripts and module scripts run after the ready state turns\n * 'interactive' and before DOMContentLoaded fires, so a callback here can run before them. When a\n * caller needs some other script to have run already, this is the wrong tool, and it should wait on\n * whatever that script produces instead.\n *\n * That remedy has a catch of its own when the calling bundle is loaded with async. An async bundle\n * can execute after DOMContentLoaded has already fired, so a plain listener for that event never\n * runs and the work silently never happens. Nothing here can detect that, because by the time this\n * file executes the event is long gone. Catching it needs something registered at parse time,\n * before any bundle loads.\n *\n * There is a second trap right next to that one, and it applies only when waiting for\n * DOMContentLoaded. The ready state stays 'interactive' after DOMContentLoaded has fired, all the\n * way until 'complete', so the ready state cannot tell you whether DOMContentLoaded is still coming\n * or has already gone. Waiting for load is not exposed to this, because the ready state turns\n * 'complete' and load fires in the same task, so seeing 'complete' means load has fired too.\n *\n * When the document is already ready, the callback is invoked via setTimeout. This way, if the\n * callback throws, the error surfaces to the global error handler in a way that is similar to what\n * happens when an event listener throws.\n *\n * We might one day want to move this to an NPM library.\n *\n * Beware of a quirk here. setTimeout yields, and the busier the frontend, the longer it takes to\n * fire. Page load is exactly when the frontend is busiest. The timeout is a floor and not a\n * guarantee, so there is no real bound on how long that yield lasts. What this means is that using\n * this library can introduce more delay than you might expect. Compare code that runs on async load\n * and checks ready state and invokes immediately to code that runs after setTimeout. The former\n * runs in the same tick, the latter runs in \"some later tick\". That later tick can be surprisingly\n * much later. That can cause initial paint issues.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event\n * @see https://html.spec.whatwg.org/multipage/parsing.html#the-end\n */\nexport function ready(callback: (event?: Event) => void,\n  type: keyof LoadingEventMap = 'DOMContentLoaded') {\n  if (document.readyState === 'complete' || (type === 'DOMContentLoaded' &&\n    document.readyState === 'interactive')) {\n    const timer = setTimeout(callback, 0);\n    const handle: ReleaseHandle = () => clearTimeout(timer);\n    return handle;\n  } else {\n    addEventListener(type, callback);\n    const handle: ReleaseHandle = () => removeEventListener(type, callback);\n    return handle;\n  }\n}\n","import { ready } from '../../lib/dom-ready.js';\n\ninterface ScrollState {\n  currentPosition: number;\n  direction: string;\n  previousPosition: number;\n}\n\nconst scrollState: ScrollState = {\n  currentPosition: 0,\n  previousPosition: 0,\n  direction: 'down'\n};\n\nlet timerId: ReturnType<typeof setTimeout>;\n\nfunction onResize(_event: Event) {\n  const scrollActions = handleScrollActions(scrollState);\n  if (scrollActions) {\n    scrollState.currentPosition = scrollActions.currentPosition;\n    scrollState.previousPosition = scrollActions.previousPosition;\n    scrollState.direction = scrollActions.direction;\n  }\n\n  setCssVariables();\n}\n\nfunction onScroll(_event: Event) {\n  clearTimeout(timerId);\n  timerId = setTimeout(onTimeout, 1000 / 60);\n}\n\nfunction onTimeout() {\n  const scrollActions = handleScrollActions(scrollState);\n  if (scrollActions) {\n    scrollState.currentPosition = scrollActions.currentPosition;\n    scrollState.previousPosition = scrollActions.previousPosition;\n    scrollState.direction = scrollActions.direction;\n  }\n\n  // make sure the css variables are set after the show/hide animation is complete\n  setTimeout(setCssVariables, 220);\n}\n\nfunction onReady() {\n  // updateHeaderPromoVisibility expects to be able to access the dom, so we must wait to fire it\n  // until the dom is queryable.\n  handleScrollActions(scrollState);\n  setCssVariables();\n\n  addEventListener('resize', onResize, { passive: true });\n  addEventListener('scroll', onScroll, { passive: true });\n}\n\n/**\n * In the liquid, wrapper is a class on a basic div element, nested under the header tag the section\n * schema generates. This is a custom interface that attaches explicit dataset properties to the\n * basic div element so that we have type safety.\n */\ninterface StickyHeaderElement extends HTMLElement {\n  readonly dataset: {\n    /**\n     * Shopper country ISO code. Used to decide promo banner hide behavior, which only applies in\n     * the US. Treated as US when absent.\n     */\n    country: string;\n\n    /**\n     * The most recent scroll direction, either up or down. Removed while at the top of the page.\n     */\n    scrollDirection: string;\n\n    /**\n     * The current vertical scroll offset in pixels.\n     */\n    scrollPosition: string;\n\n    /**\n     * The search bar visibility state, set when the bar is hidden in response to scrolling down.\n     */\n    search: string;\n\n    /**\n     * The header display state used to drive CSS, such as sitting at the top of the page or having\n     * the promo banner hidden or shown.\n     */\n    state: string;\n  };\n}\n\n/**\n * Beware this is run on some pages without a header class so it returns early with undefined,\n * meaning that the caller cannot assume the return value is a defined object.\n */\nfunction handleScrollActions(scrollState: ScrollState) {\n  // Scoping the match to a wrapper nested inside a header element keeps this bound to the genuine\n  // header even when third-party DOM tooling (in-browser translation, reader overlays, extensions)\n  // clones or re-parents a copy of the wrapper elsewhere in the page. An unscoped match returns\n  // whichever copy comes first in the document and would write scroll state onto that stray node;\n  // scoped this way a stray copy no longer qualifies and simply falls through to the early return.\n  const headerWrapper = document.querySelector<StickyHeaderElement>('header .wrapper');\n  if (!headerWrapper) {\n    return;\n  }\n\n  const headerHeight = headerWrapper.offsetHeight;\n\n  const containingElement = <HTMLElement> (document.documentElement || document.body.parentNode ||\n    document.body);\n  const currentPosition = scrollY || containingElement.scrollTop;\n  const previousPosition = scrollState.currentPosition;\n  const direction = currentPosition >= previousPosition ? 'down' : 'up';\n\n  if (currentPosition === 0) {\n    headerWrapper.dataset.state = 'initial';\n    headerWrapper.dataset.scrollPosition = currentPosition.toString();\n    delete headerWrapper.dataset.scrollDirection;\n  } else if (currentPosition > 0) {\n    const scrollTolerance = 100;\n\n    headerWrapper.dataset.scrollPosition = currentPosition.toString();\n    headerWrapper.dataset.scrollDirection = direction;\n\n    const headerBanner = headerWrapper.querySelector('announcement-bar');\n\n    // The announcement bar is the promo strip at the very top of the header. Not every page renders\n    // one, and on the pages that do, the bar hides itself when the shopper dismisses it or when the\n    // page came from a cache rendered before its sale ended. It hides itself by setting its hidden\n    // attribute, so reading that attribute back here is how we find out whether the strip is still\n    // on screen.\n    //\n    // Both promo rules below depend on this. There is nothing to scroll out of the way when the\n    // strip is gone, so neither promo state should be set, and the height we compare the scroll\n    // position against is zero rather than the height of the strip.\n    const headerBannerVisible = headerBanner && !headerBanner.hidden;\n    const headerBannerHeight = headerBannerVisible ? headerBanner.offsetHeight : 0;\n\n    const countryISOCode = headerWrapper.dataset.country || 'US';\n\n    // Hide the promo if needed\n    if (countryISOCode === 'US' && headerBannerVisible && direction === 'down' &&\n      currentPosition > headerBannerHeight) {\n      headerWrapper.dataset.state = 'promo-hidden';\n    }\n\n    // Show the promo if needed\n    if (headerBannerVisible && direction === 'up' &&\n      currentPosition <= headerHeight + scrollTolerance) {\n      headerWrapper.dataset.state = 'promo-visible';\n    }\n\n    // Hide the search bar if needed\n    if (direction === 'down' && currentPosition >= previousPosition + scrollTolerance / 2) {\n      headerWrapper.dataset.search = 'search-hidden';\n    }\n  }\n\n  const outputScrollState: ScrollState = {\n    currentPosition,\n    previousPosition,\n    direction\n  };\n\n  return outputScrollState;\n}\n\nfunction setCssVariables() {\n  const headerWrapper = document.querySelector<StickyHeaderElement>('header .wrapper');\n  if (!headerWrapper) {\n    return;\n  }\n\n  const menuBar = headerWrapper.querySelector<HTMLElement>('.content');\n  const menuBarHeight = menuBar?.offsetHeight || 60;\n  const contentPosition = headerWrapper.getBoundingClientRect().bottom;\n\n  // The wrapper only renders inside a header element that the section schema generates, and the\n  // lookup above matches the wrapper only when it is nested under such a header, so walking back up\n  // to that header is structurally guaranteed to resolve. The guard remains because the header\n  // subtree is a common target of third-party DOM manipulation (in-browser translation,\n  // accessibility and reader overlays, browser extensions) that can re-parent or clone the wrapper\n  // out from under its header between the moment this deferred callback is scheduled and the moment\n  // it runs. That is outside our control, so we skip the write instead of letting it throw.\n  const header = headerWrapper.closest('header');\n  if (header) {\n    header.style.setProperty('--menubar-height', menuBarHeight + 'px');\n  }\n\n  document.body.style.setProperty('--content-position', contentPosition + 'px');\n}\n\nready(onReady);\n","import type { CustomElement } from '@integrabeauty/custom-elements';\n\n/**\n * The header's cart icon button, with a badge showing the number of items in the cart. The badge\n * reflects the quantity data attribute and is hidden when that quantity is zero.\n *\n * The header-cart-button snippet writes the button and its shadow root in Liquid, so the browser\n * builds the shadow root while it reads the page and the button is styled and showing the count\n * before this script runs. This script only keeps the badge and the button's label in sync with the\n * quantity.\n */\nclass CartButton extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-quantity'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * The number of items in the cart, shown as the badge count. The badge is hidden when this is\n     * zero.\n     */\n    quantity: string;\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n    this.render();\n  }\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && name === 'data-quantity' && oldValue !== newValue) {\n      this.render();\n    }\n  }\n\n  private initializeShadowRoot() {\n    // A browser without declarative shadow DOM leaves the snippet's template in the light DOM as an\n    // ordinary template element, so we build the shadow root from it here. The snippet explains why\n    // the shadow root delegates focus.\n    if (!this.shadowRoot) {\n      const template = this.querySelector('template');\n      this.attachShadow({ mode: 'open', delegatesFocus: true });\n      this.shadowRoot.append(template.content.cloneNode(true));\n      template.remove();\n    }\n  }\n\n  private render() {\n    const cartCounter = this.shadowRoot.querySelector<HTMLElement>('#cart-counter');\n    cartCounter.textContent = this.dataset.quantity;\n\n    const quantity = parseInt(this.dataset.quantity, 10);\n    let itemsText = '';\n    if (quantity === 0) {\n      cartCounter.hidden = true;\n      cartCounter.removeAttribute('aria-label');\n    } else {\n      cartCounter.hidden = false;\n      if (quantity === 1) {\n        itemsText = 'one item added';\n      } else {\n        itemsText = quantity.toString() + ' items added';\n      }\n    }\n\n    const button = this.shadowRoot.querySelector('button');\n    button?.setAttribute('aria-label', 'View shopping cart ' + itemsText);\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cart-button': CartButton;\n  }\n}\n\nif (!customElements.get('cart-button')) {\n  customElements.define('cart-button', CartButton);\n}\n","// Accessors for the ambient cart event queue seeded and filled by the always-on cart lifecycle\n// snippet. The snippet registers a parse-time listener that buffers every cart event into the\n// queue, so a section whose bundle loads later can replay what it missed. Reaching the queue\n// through this lib, rather than the global directly, keeps the coupling to the ambient layer\n// explicit. The buffered events come off the queue untyped, so the guards here narrow each one\n// during replay.\n\ntype CartUpdatedEvent = GlobalEventHandlersEventMap['cart-updated'];\ntype CartUpdateErredEvent = GlobalEventHandlersEventMap['cart-update-erred'];\n\n/**\n * Returns the ambient queue of buffered cart events. A consumer replays this as it initializes,\n * then listens live for further events.\n */\nexport function getEventQueue() {\n  return cart_lifecycle.queue;\n}\n\n/**\n * Whether the value is a cart updated event. A consumer replaying the queue narrows each buffered\n * event with this before handling it.\n */\nexport function isCartUpdatedEvent(value: any): value is CartUpdatedEvent {\n  return value instanceof Event && value.type === 'cart-updated';\n}\n\n/**\n * Whether the value is a cart update erred event.\n */\nexport function isCartUpdateErredEvent(value: any): value is CartUpdateErredEvent {\n  return value instanceof Event && value.type === 'cart-update-erred';\n}\n","import './cart-button.js';\nimport { getEventQueue, isCartUpdatedEvent } from '../../lib/cart-event-queue.js';\nimport { ready } from '../../lib/dom-ready.js';\n\n// Drives the header cart buttons. It keeps their badge quantity in sync with cart updates and\n// handles activation, closing other header overlays and requesting the cart open. This lifts both\n// the cart state concern and the activation behavior out of the button element, leaving the element\n// to only render whatever quantity it is given into its own shadow DOM. Coordination lives here, in\n// the header, because the header owns the buttons.\n\nfunction onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n  const quantity = event.detail.cart.item_count.toString();\n\n  // There can be more than one cart button on a page, such as a separate mobile and desktop button.\n  // We update every instance. The element handled this for free when each instance listened for\n  // itself; here we have to reach out and find them all.\n\n  const buttons = document.querySelectorAll('cart-button');\n  for (const button of buttons) {\n    button.dataset.quantity = quantity;\n  }\n}\n\nfunction onCartButtonClick(event: MouseEvent) {\n  event.stopPropagation();\n\n  // The listener is bound to the host, so currentTarget is the cart-button element. event.target\n  // resolves to the host here too. The host has no padding of its own and its inner button carries\n  // the padding, so every click lands on that inner button, and the browser rewrites the target to\n  // the host as the event crosses the shadow boundary, which keeps the inner button hidden from\n  // listeners outside the shadow tree. So target and currentTarget are the same element here, and\n  // target would work just as well today. We use currentTarget because it is the host by definition\n  // no matter how the element is laid out, whereas target equals the host only as long as\n  // everything clickable stays inside the shadow tree.\n  //\n  // Every event below is therefore dispatched from the host, except the focus restore request.\n\n  const cartButton = event.currentTarget;\n  if (!isCartButton(cartButton)) {\n    return;\n  }\n\n  // Ask the modal manager to remember this button so focus returns to it once the drawer closes.\n  // The modal manager remembers whatever element the event started from, so we dispatch from the\n  // inner button rather than the host. The host's shadow root delegates focus to that button, but a\n  // browser that builds the shadow root from the snippet's template without supporting the\n  // template's delegates focus attribute leaves the host unable to take focus, so focusing the host\n  // would do nothing there.\n\n  const innerButton = cartButton.shadowRoot.querySelector('button');\n  const focusEvent = new Event('focus-stack-push-requested', { bubbles: true, composed: true });\n  innerButton.dispatchEvent(focusEvent);\n\n  // Close the other header overlays before opening the cart.\n\n  const searchResetEvent = new Event('search-input-reset', { bubbles: true, composed: true });\n  cartButton.dispatchEvent(searchResetEvent);\n\n  const menuCloseEvent = new Event('menu-close-requested', { bubbles: true, composed: true });\n  cartButton.dispatchEvent(menuCloseEvent);\n\n  type Detail = WindowEventMap['cart-open-requested']['detail'];\n  const cartOpenEvent = new CustomEvent<Detail>('cart-open-requested', {\n    bubbles: true,\n    composed: true,\n    detail: {\n      source: 'header-cart-button-click'\n    }\n  });\n  cartButton.dispatchEvent(cartOpenEvent);\n}\n\ntype CartButton = HTMLElementTagNameMap['cart-button'];\n\n/**\n * Whether the value is a cart button element.\n */\nfunction isCartButton(value: any): value is CartButton {\n  return value instanceof HTMLElement && value.localName === 'cart-button';\n}\n\nfunction onReady() {\n  const buttons = document.querySelectorAll('cart-button');\n\n  // In the Shopify theme editor, the cart button should not be clickable. And we do not need to\n  // replay any events or do anything else.\n\n  // This module expects that the Shopify global is correctly declared and initialized. We\n  // intentionally are not being defensive about its presence here.\n\n  if (Shopify.designMode) {\n    // Since the cart is never actually initialized in design mode, we want to mock the product\n    // count. Generate a random number between 1 and 100.\n    const quantity = Math.floor(Math.random() * 100) + 1;\n\n    for (const button of buttons) {\n      button.dataset.quantity = quantity.toString();\n    }\n\n    return;\n  }\n\n  // Listen for activation on the host rather than the native button nested in its shadow tree, so\n  // the entire element is the clickable target.\n\n  for (const button of buttons) {\n    button.addEventListener('click', onCartButtonClick);\n  }\n\n  // Cart updated events may have been queued by the inline header script before this module ran.\n  // Replay them first so the badge reflects the latest known cart, then begin listening for live\n  // updates.\n\n  for (const event of getEventQueue()) {\n    if (isCartUpdatedEvent(event)) {\n      try {\n        onCartUpdated(event);\n      } catch (error) {\n        console.warn(error);\n      }\n    }\n  }\n\n  addEventListener('cart-updated', onCartUpdated);\n}\n\nready(onReady);\n","import type { CustomElement } from '@integrabeauty/custom-elements';\n\n/**\n * The header's search icon button, which shows the search input when clicked.\n *\n * The header-search-button snippet writes the button and its shadow root in Liquid, so the browser\n * builds the shadow root while it reads the page and the button is styled before this script runs.\n * This script only adds the click behavior.\n */\nclass SearchButton extends HTMLElement implements CustomElement {\n  private onSearchButtonClickedBound = this.onSearchButtonClicked.bind(this);\n  private onSearchClosedBound = this.onSearchClosed.bind(this);\n\n  public connectedCallback() {\n    // A browser without declarative shadow DOM leaves the snippet's template in the light DOM as an\n    // ordinary template element, so we build the shadow root from it here.\n    if (!this.shadowRoot) {\n      const template = this.querySelector('template');\n      this.attachShadow({ mode: 'open' }).append(template.content.cloneNode(true));\n      template.remove();\n    }\n\n    this.addEventListener('click', this.onSearchButtonClickedBound);\n    addEventListener('search-input-reset', this.onSearchClosedBound);\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('click', this.onSearchButtonClickedBound);\n    removeEventListener('search-input-reset', this.onSearchClosedBound);\n    document.removeEventListener('click', onDocumentClick);\n  }\n\n  private onSearchButtonClicked(event: MouseEvent) {\n    event.stopPropagation();\n    // close the menu if open\n    const menuCloseEvent = new Event('menu-close-requested', { bubbles: true, composed: true });\n    this.dispatchEvent(menuCloseEvent);\n\n    const button = this.shadowRoot.querySelector('button');\n    button.hidden = true;\n\n    const headerWrapper = document.querySelector<HTMLElement>('header .wrapper');\n    if (headerWrapper) {\n      headerWrapper.dataset.search = 'search-visible';\n\n      const searchElement = headerWrapper.querySelector('shopify-search-input');\n      if (searchElement) {\n        // Suppress scroll-padding-top early so the focus that follows does not scroll the page.\n        document.documentElement.dataset.searchFocused = 'true';\n        searchElement.hidden = false;\n        searchElement.dataset.triggerFocus = Date.now().toString();\n      }\n    }\n\n    // A click in the header already closes the search through the menu panels script, and the\n    // search input only closes its results pane on an outside click. Without this, a click on the\n    // page below the header would leave the bar up.\n    document.addEventListener('click', onDocumentClick);\n  }\n\n  private onSearchClosed() {\n    if (this.classList.contains('search-page')) {\n      return;\n    }\n\n    document.removeEventListener('click', onDocumentClick);\n\n    const button = this.shadowRoot.querySelector('button');\n    button.hidden = false;\n\n    const headerWrapper = document.querySelector<HTMLElement>('header .wrapper');\n    if (headerWrapper) {\n      headerWrapper.dataset.search = 'search-hidden';\n\n      const searchElement = headerWrapper.querySelector('shopify-search-input');\n      if (searchElement) {\n        searchElement.hidden = true;\n      }\n    }\n  }\n}\n\n/**\n * Clicks in the search input stop propagation, so they never get here and the bar stays open while\n * the visitor types.\n */\nfunction onDocumentClick(_event: MouseEvent) {\n  const searchReset = new Event('search-input-reset');\n  dispatchEvent(searchReset);\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'search-button': SearchButton;\n  }\n}\n\nif (!customElements.get('search-button')) {\n  customElements.define('search-button', SearchButton);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\n\n/**\n * The header's profile button, which goes to the account page for a logged in customer and to the\n * login page otherwise.\n *\n * The header-profile-button snippet writes the button and its shadow root in Liquid, so the browser\n * builds the shadow root while it reads the page and the button is styled before this script runs.\n * This script only adds the click behavior.\n */\nclass ProfileButton extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * URL to navigate to login page\n     */\n    accountLoginUrl: string;\n\n    /**\n     * URL to navigate to account page\n     */\n    accountUrl: string;\n\n    /**\n     * Whether the customer is logged in. The stylesheet reads this attribute too.\n     */\n    authenticated: 'false' | 'true';\n  };\n\n  private onButtonClickedBound = this.onButtonClicked.bind(this);\n\n  public connectedCallback() {\n    // A browser without declarative shadow DOM leaves the snippet's template in the light DOM as an\n    // ordinary template element, so we build the shadow root from it here.\n    if (!this.shadowRoot) {\n      const template = this.querySelector('template');\n      this.attachShadow({ mode: 'open' }).append(template.content.cloneNode(true));\n      template.remove();\n    }\n\n    this.addEventListener('click', this.onButtonClickedBound);\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('click', this.onButtonClickedBound);\n  }\n\n  private onButtonClicked() {\n    if (this.dataset.authenticated === 'true') {\n      location.href = this.dataset.accountUrl;\n    } else {\n      location.href = this.dataset.accountLoginUrl;\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'profile-button': ProfileButton;\n  }\n}\n\nif (!customElements.get('profile-button')) {\n  customElements.define('profile-button', ProfileButton);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\n\n/**\n * The header's hamburger button, which opens the main menu drawer when clicked.\n *\n * The header-hamburger-button snippet writes the button and its shadow root in Liquid, so the\n * browser builds the shadow root while it reads the page and the button is styled before this\n * script runs. This script only adds the click behavior and keeps the button's aria in step with\n * the menu.\n */\nclass HamburgerButton extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-opened'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Whether the menu is open\n     */\n    opened: 'false' | 'true' | null;\n  };\n\n  private onHamburgerButtonClickedBound = this.onHamburgerButtonClicked.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n    this.addEventListener('click', this.onHamburgerButtonClickedBound);\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('click', this.onHamburgerButtonClickedBound);\n  }\n\n  public attributeChangedCallback(_name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && oldValue !== newValue) {\n      this.render();\n    }\n  }\n\n  /**\n   * A browser without declarative shadow DOM leaves the snippet's template in the light DOM as an\n   * ordinary template element, so we build the shadow root from it here. This runs from the\n   * attribute callback too, since on upgrade that fires before the connected callback.\n   */\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      const template = this.querySelector('template');\n      this.attachShadow({ mode: 'open' }).append(template.content.cloneNode(true));\n      template.remove();\n    }\n  }\n\n  private render() {\n    const button = this.shadowRoot.querySelector('button');\n\n    if (this.dataset.opened === 'true') {\n      button?.setAttribute('aria-expanded', 'true');\n      button?.setAttribute('aria-label', 'Close menu');\n    } else {\n      button?.setAttribute('aria-expanded', 'false');\n      button?.setAttribute('aria-label', 'Open menu');\n    }\n  }\n\n  private onHamburgerButtonClicked(event: MouseEvent) {\n    event.stopPropagation();\n\n    if (this.dataset.opened === 'true') {\n      const menuCloseEvent = new Event('menu-close-requested', { bubbles: true, composed: true });\n      this.dispatchEvent(menuCloseEvent);\n    } else {\n      if (!(event.currentTarget instanceof HTMLElement)) {\n        return;\n      }\n\n      const focusEvent = new Event('focus-stack-push-requested', {\n        bubbles: true,\n        composed: true\n      });\n      event.currentTarget.dispatchEvent(focusEvent);\n\n      type MenuOpenRequestedDetail = WindowEventMap['menu-open-requested']['detail'];\n      const menuOpenEvent = new CustomEvent<MenuOpenRequestedDetail>('menu-open-requested', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          element_id: 'menu-panel-main-mobile'\n        }\n      });\n      this.dispatchEvent(menuOpenEvent);\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'hamburger-button': HamburgerButton;\n  }\n}\n\nif (!customElements.get('hamburger-button')) {\n  customElements.define('hamburger-button', HamburgerButton);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    /**\n     * Announces that a menu panel has finished closing. Dispatched from the panel element itself\n     * and bubbles to window, so the panel is identified by the event target rather than a detail\n     * payload.\n     */\n    'menu-closed': Event;\n\n    /**\n     * Announces that a menu panel has finished opening. Dispatched from the panel element itself\n     * and bubbles to window, so the panel is identified by the event target rather than a detail\n     * payload.\n     */\n    'menu-opened': Event;\n  }\n}\n\n/**\n * Holds the header's drop down menu panels and shows one of them at a time. `data-active-panel`\n * picks which panel shows, and `data-modal-state` opens and closes the menu. Changing the active\n * panel while the menu is open swaps panels without closing the menu.\n *\n * This exists so the menu can be opened and closed by setting an attribute, the same way as the\n * other modals. The header's menu script asks the modal manager to open or close the menu, and the\n * manager sets `data-modal-state`.\n *\n * Sends `menu-opened` from a panel each time that panel shows, and `menu-closed` from the panel\n * that was showing when the menu closes.\n */\nclass MenuPanelsElement extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-active-panel', 'data-modal-state'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * The id of the panel to show. Deleted when the menu closes, so it is undefined whenever no\n     * panel is showing.\n     */\n    activePanel: string;\n\n    /**\n     * Whether the menu is shown, as \"open\". Anything else, or no value at all, means closed.\n     *\n     * Set `data-active-panel` before opening, since opening shows whichever panel it names. A\n     * change only takes effect while the element is on the page.\n     */\n    modalState: string;\n  };\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    if (!this.isConnected || oldValue === newValue) {\n      return;\n    }\n\n    if (name === 'data-modal-state') {\n      if (newValue === 'open') {\n        this.open();\n      } else if (oldValue === 'open') {\n        this.close();\n      }\n    } else if (name === 'data-active-panel' && this.dataset.modalState === 'open') {\n      hidePanel(this.findPanel(oldValue));\n\n      const panel = this.findPanel(newValue);\n      revealPanel(panel);\n      afterPanelShown(panel);\n    }\n  }\n\n  private open() {\n    const panel = this.findPanel(this.dataset.activePanel);\n    revealPanel(panel);\n    afterPanelShown(panel);\n  }\n\n  private close() {\n    const panel = this.findPanel(this.dataset.activePanel);\n    hidePanel(panel);\n    const menuClosed = new Event('menu-closed', { bubbles: true });\n    panel?.dispatchEvent(menuClosed);\n  }\n\n  private findPanel(id: string) {\n    if (!id) {\n      return null;\n    }\n\n    return this.querySelector<HTMLElement>('#' + id);\n  }\n}\n\nfunction revealPanel(panel: HTMLElement) {\n  if (panel) {\n    panel.inert = false;\n    panel.hidden = false;\n  }\n}\n\nfunction hidePanel(panel: HTMLElement) {\n  if (panel) {\n    panel.hidden = true;\n    panel.inert = true;\n  }\n}\n\n/**\n * The steps that follow a panel becoming visible, whether the menu just opened or it swapped panels\n * while open.\n */\nfunction afterPanelShown(panel: HTMLElement) {\n  if (!panel) {\n    return;\n  }\n\n  const firstFocusable = panel.querySelector<HTMLElement>('.menu-button');\n  if (firstFocusable) {\n    firstFocusable.focus({ focusVisible: true });\n  }\n\n  // The special offers carousel mounts while its panel is still hidden, so it can't measure its\n  // slides until now. The refresh is also where it works out which slides are off screen and makes\n  // them inert, so do not drop it.\n  const carousel = panel.querySelector('carousel-element');\n  if (carousel) {\n    carousel.dataset.refresh = 'true';\n  }\n\n  const menuOpened = new Event('menu-opened', { bubbles: true });\n  panel.dispatchEvent(menuOpened);\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'menu-panels-element': MenuPanelsElement;\n  }\n}\n\nif (!customElements.get('menu-panels-element')) {\n  customElements.define('menu-panels-element', MenuPanelsElement);\n}\n","import { ready } from '../../lib/dom-ready.js';\n\nfunction onMenuOpenRequested(event: WindowEventMap['menu-open-requested']) {\n  // A request must name the panel to open; there is nothing to act on without one\n  if (!event.detail.element_id) {\n    return;\n  }\n\n  const menuPanelsElement = document.querySelector('menu-panels-element');\n  if (!menuPanelsElement) {\n    return;\n  }\n\n  const panel = menuPanelsElement.querySelector<HTMLElement>('#' + event.detail.element_id);\n\n  // If the id does not point to something in the dom, ignore the request.\n  if (!panel) {\n    return;\n  }\n\n  // close the search box if open\n  const searchReset = new Event('search-input-reset');\n  dispatchEvent(searchReset);\n\n  const stickyHeader = document.querySelector('header .sticky-header');\n  stickyHeader.classList.add('menu-shown');\n\n  const menuButtons = document.querySelectorAll<HTMLElement>('header .nav .menu-button');\n  for (const button of menuButtons) {\n    const controlledPanel = button.getAttribute('aria-controls');\n    if (controlledPanel === event.detail.element_id) {\n      button.setAttribute('aria-expanded', 'true');\n      button.classList.add('active');\n    } else if (controlledPanel) {\n      button.setAttribute('aria-expanded', 'false');\n      button.classList.remove('active');\n    }\n  }\n\n  // When the menu is already open this only swaps panels, and the element leaves the menu open.\n  menuPanelsElement.dataset.activePanel = event.detail.element_id;\n\n  if (menuPanelsElement.dataset.modalState !== 'open') {\n    removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n    addEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n    // The modal manager hears this and sets the element's data-modal-state, which shows the menu.\n    const event = new Event('modal-open-requested', { bubbles: true, composed: true });\n    menuPanelsElement.dispatchEvent(event);\n  }\n}\n\nfunction onMenuCloseRequested(_event: WindowEventMap['menu-close-requested']) {\n  removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n  const stickyHeader = document.querySelector('header .sticky-header');\n  stickyHeader.classList.remove('menu-shown');\n\n  const menuButtons = document.querySelectorAll<HTMLElement>('header .nav .menu-button');\n  for (const button of menuButtons) {\n    const controlledPanel = button.getAttribute('aria-controls');\n    if (controlledPanel) {\n      button.setAttribute('aria-expanded', 'false');\n      button.classList.remove('active');\n    }\n  }\n\n  const menuPanelsElement = document.querySelector('menu-panels-element');\n  if (menuPanelsElement?.dataset.modalState === 'open') {\n    // The element hides whichever panel is active when it closes, so close before clearing it.\n    const event = new Event('modal-close-requested', { bubbles: true, composed: true });\n    menuPanelsElement.dispatchEvent(event);\n    delete menuPanelsElement.dataset.activePanel;\n  }\n}\n\n/**\n * Closes the open menu panel when the user performs a light-dismiss gesture (a click on the\n * backdrop or an escape key press). Rather than tearing the panel down directly, it re-dispatches\n * the header's own menu-close-requested event so every close funnels through the single close path\n * in onMenuCloseRequested. This listener is only attached while a panel is open.\n */\nfunction onModalDismissRequested(_event: WindowEventMap['modal-dismiss-requested']) {\n  const closeRequested = new Event('menu-close-requested');\n  dispatchEvent(closeRequested);\n}\n\nfunction onHeaderClicked(_event: MouseEvent) {\n  // close the search box if open\n  const searchReset = new Event('search-input-reset');\n  dispatchEvent(searchReset);\n\n  // close the menu if open\n  const closeRequested = new Event('menu-close-requested');\n  dispatchEvent(closeRequested);\n}\n\nfunction onMenuButtonClicked(event: MouseEvent) {\n  if (!(event.currentTarget instanceof HTMLElement)) {\n    return;\n  }\n\n  const panelId = event.currentTarget.getAttribute('aria-controls');\n  if (!panelId) {\n    return;\n  }\n\n  event.stopPropagation();\n\n  const opened = event.currentTarget.getAttribute('aria-expanded') === 'true';\n  if (opened) {\n    const closeRequested = new Event('menu-close-requested');\n    dispatchEvent(closeRequested);\n  } else {\n    const focusRequested = new Event('focus-stack-push-requested', {\n      bubbles: true,\n      composed: true\n    });\n    event.currentTarget.dispatchEvent(focusRequested);\n\n    requestMenuOpen(panelId);\n  }\n}\n\nfunction mobileBackButtonClicked(_event: MouseEvent) {\n  requestMenuOpen('menu-panel-main-mobile');\n}\n\nfunction requestMenuOpen(elementId: string) {\n  type Detail = WindowEventMap['menu-open-requested']['detail'];\n\n  const event = new CustomEvent<Detail>('menu-open-requested', {\n    detail: {\n      element_id: elementId\n    }\n  });\n\n  dispatchEvent(event);\n}\n\n// Swipe detection variables\n\nlet touchStartX = 0;\nlet touchEndX = 0;\nlet touchStartY = 0;\nlet touchEndY = 0;\nlet isSwiping = false;\n\nfunction onMenuPanelTouchStart(event: TouchEvent) {\n  touchStartX = event.touches[0].clientX;\n  touchStartY = event.touches[0].clientY;\n  isSwiping = false;\n}\n\nfunction onMenuPanelTouchMove(event: TouchEvent) {\n  touchEndX = event.touches[0].clientX;\n  touchEndY = event.touches[0].clientY;\n\n  const deltaX = Math.abs(touchStartX - touchEndX);\n  const deltaY = Math.abs(touchStartY - touchEndY);\n\n  if (deltaX > deltaY) {\n    // Horizontal swipe detected\n    isSwiping = true;\n  } else {\n    // Vertical swipe detected\n    isSwiping = false;\n  }\n}\n\nfunction onMenuPanelTouchEnd(event: TouchEvent) {\n  const dx = Math.abs(touchStartX - touchEndX);\n  const dy = Math.abs(touchStartY - touchEndY);\n\n  const minimumSwipeDistanceThreshold = 100;\n\n  if (event.target instanceof HTMLElement && !isCarouselElement(event.target)) {\n    // Only detect horizontal swipes where horizontal movement is dominant\n    if (dx > dy && dx >= minimumSwipeDistanceThreshold && isSwiping) {\n      if (typeof event.cancelable !== 'boolean' || event.cancelable) {\n        // do not trigger the default \"go back/forward\" action\n        event.preventDefault();\n      }\n\n      const panel = event.target.closest<HTMLElement>('.menu-panel');\n      if (panel) {\n        onHorizontalSwipe(panel);\n      }\n    }\n  }\n\n  isSwiping = false;\n}\n\nfunction isCarouselElement(element: HTMLElement) {\n  // Check if the element is a carousel or a child of a carousel\n  if (element instanceof HTMLElement) {\n    const carousel = element.closest('carousel-element');\n    if (carousel) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nfunction onHorizontalSwipe(panel: HTMLElement) {\n  if (innerWidth >= 960) {\n    // If not mobile, do nothing\n    return;\n  }\n\n  const isMainMenu = panel.id === 'menu-panel-main-mobile';\n  if (isMainMenu) {\n    // If the main menu is swiped, close it\n    const closeRequested = new Event('menu-close-requested');\n    dispatchEvent(closeRequested);\n  } else {\n    // else go back to the main menu\n    requestMenuOpen('menu-panel-main-mobile');\n  }\n}\n\nfunction onReady() {\n  const headerWrapper = document.querySelector<HTMLElement>('header .wrapper');\n  if (!headerWrapper) {\n    // this is not a mega menu header, probably a minimal header or other\n    return;\n  }\n\n  addEventListener('menu-open-requested', onMenuOpenRequested);\n  addEventListener('menu-close-requested', onMenuCloseRequested);\n\n  const menuPanels = document.querySelector('menu-panels-element');\n  if (menuPanels) {\n    // the menu panels are initially hidden with an inline style, so we need to remove it to make\n    // them work\n    menuPanels.style.removeProperty('display');\n\n    menuPanels.addEventListener('touchstart', onMenuPanelTouchStart, { passive: true });\n    menuPanels.addEventListener('touchmove', onMenuPanelTouchMove, { passive: true });\n    menuPanels.addEventListener('touchend', onMenuPanelTouchEnd, { passive: false });\n  }\n\n  headerWrapper.addEventListener('click', onHeaderClicked);\n\n  const menuButtons = document.querySelectorAll<HTMLElement>(\n    'header .nav .menu-button, #menu-panels .menu-button');\n  for (const button of menuButtons) {\n    button.addEventListener('click', onMenuButtonClicked);\n  }\n\n  const mobileBackButtons = document.querySelectorAll<HTMLElement>('#menu-panels button.title');\n  for (const button of mobileBackButtons) {\n    button.addEventListener('click', mobileBackButtonClicked);\n  }\n}\n\nready(onReady);\n"],"names":["addEventListener","_event","hamburgerIcon","document","querySelector","dataset","opened","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","AnnouncementBar","HTMLElement","onCloseClickedBound","this","onCloseClicked","bind","connectedCallback","shadowRoot","template","attachShadow","mode","append","content","cloneNode","remove","clockShow","parseInt","clockEndTime","Date","now","hidden","disconnectedCallback","closeButton","removeEventListener","customElements","get","define","SearchProductPreview","initializeShadowRoot","render","innerHTML","image","src","imageUrl","alt","title","textContent","rnds8","Uint8Array","byteToHex","i","push","toString","slice","unsafeStringify","arr","offset","toLowerCase","dist_v4","options","buf","crypto","randomUUID","_ref","_options$random","rnds","random","rng","getRandomValues","length","Error","RangeError","_v4","namedSizePixels","compact","grande","icon","large","medium","original","pico","small","thumb","assert","expression","message","AssertionError","constructor","super","name","async","suggest","input","query","use_graphql","responseBody","blog_handle","url","URL","location","href","variables","blogHandle","first","Number","isInteger","limit","response","fetch","method","headers","body","JSON","stringify","error","suggestError","SuggestError","cause","ok","status","json","value","Array","isArray","_value$data","data","blog","articles","nodes","errors","response_body","node","_node$authorV","_node$image","author","authorV2","handle","id","split","pop","published_at","publishedAt","summary_html","excerptHtml","tags","Shopify","routes","root","resources","results","collections","pages","products","queries","suggestBlogArticles","path","section_id","responseText","searchParams","set","limit_scope","resource_types","join","prefix","fields","unavailable_products","retryAfter","retryAfterSeconds","retry_after","includes","console","log","text","parse","ShopifySearchInput","observedAttributes","onFormSubmitBound","onFormSubmit","onFocusBound","onFocus","onDocumentClickBound","onDocumentClick","onSearchCloseBound","onSearchClose","onKeyUpBound","onKeyUp","onTypingPausedDebounced","func","timeout","timer","_len","arguments","args","_key","clearTimeout","setTimeout","debounce","onTypingPaused","onKeyDownBound","onKeyDown","onInputBound","onInput","onBlurBound","onBlur","onPageShowBound","onPageShow","onProductPreviewClickBound","onProductPreviewClick","onSearchForClickBound","onSearchForClick","onSuggestedQueryClickBound","onSuggestedQueryClick","suggestionActiveIndex","searchInput","onInputClick","attributeChangedCallback","oldValue","newValue","isConnected","iconReset","formEl","disabled","isSearchPage","searchTerm","URLSearchParams","search","updateSearchPageReset","catch","handleSuggestError","renderPreviewPane","triggerFocus","focus","showSearchPreview","scrollbarWidth","innerWidth","documentElement","clientWidth","style","setProperty","scrollLocked","classList","add","ariaExpanded","headerEl","hideSearchPreview","backdrop","contains","header","event","preventDefault","stopPropagation","resetEvent","Event","bubbles","composed","dispatchEvent","toggle","updateSearchButton","key","anchors","querySelectorAll","markSuggestionActiveAtIndex","_this$shadowRoot$quer","activeAnchor","click","searchButton","urlQueryParam","persisted","searchFocused","activeElement","searchEvent","CustomEvent","detail","event_id","showPreviewDrawer","suggestions","previewEl","position","filteredQueries","filter","q","liHeaderEl","createElement","role","ariaLevel","tabIndex","appendChild","suggestedQuery","liEl","createSearchQuerySuggestionListElement","product","createProductSuggestionListElement","userQuery","find","styled_text","anchorEl","currentQuery","preview","featured_image","absolute","startsWith","named","size","width","height","crop","quality","replace","build","anchor","productHandle","origin","createSuggestedProductUrl","ariaLabel","item","index","old","adjustedIndex","from","target","HTMLAnchorElement","currentTarget","createSuggestedSearchTermUrl","warn","resultList","renderErrorMessage","ready","callback","type","readyState","scrollState","currentPosition","previousPosition","direction","timerId","onResize","scrollActions","handleScrollActions","setCssVariables","onScroll","onTimeout","headerWrapper","headerHeight","offsetHeight","containingElement","parentNode","scrollY","scrollTop","state","scrollPosition","scrollDirection","scrollTolerance","headerBanner","headerBannerVisible","headerBannerHeight","country","menuBar","menuBarHeight","contentPosition","getBoundingClientRect","bottom","closest","passive","CartButton","delegatesFocus","cartCounter","quantity","itemsText","removeAttribute","button","setAttribute","isCartUpdatedEvent","onCartUpdated","cart","item_count","buttons","onCartButtonClick","cartButton","localName","innerButton","focusEvent","searchResetEvent","menuCloseEvent","cartOpenEvent","source","designMode","Math","floor","cart_lifecycle","queue","SearchButton","onSearchButtonClickedBound","onSearchButtonClicked","onSearchClosedBound","onSearchClosed","searchElement","searchReset","ProfileButton","onButtonClickedBound","onButtonClicked","authenticated","accountUrl","accountLoginUrl","HamburgerButton","onHamburgerButtonClickedBound","onHamburgerButtonClicked","_name","menuOpenEvent","element_id","MenuPanelsElement","open","close","modalState","hidePanel","findPanel","panel","revealPanel","afterPanelShown","activePanel","menuClosed","inert","firstFocusable","focusVisible","carousel","refresh","menuOpened","onMenuOpenRequested","menuPanelsElement","menuButtons","controlledPanel","getAttribute","onModalDismissRequested","onMenuCloseRequested","closeRequested","onHeaderClicked","onMenuButtonClicked","panelId","focusRequested","requestMenuOpen","mobileBackButtonClicked","elementId","touchStartX","touchEndX","touchStartY","touchEndY","isSwiping","onMenuPanelTouchStart","touches","clientX","clientY","onMenuPanelTouchMove","deltaX","abs","deltaY","onMenuPanelTouchEnd","dx","dy","element","isCarouselElement","cancelable","isMainMenu","onHorizontalSwipe","menuPanels","removeProperty","mobileBackButtons"],"sourceRoot":""}