{"version":3,"file":"header-section.min.js","mappings":"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","sources":["webpack://lange/./sections/header/mobile-menu-button-control.ts","webpack://lange/webpack/bootstrap","webpack://lange/./sections/header/announcement-bar/index.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/index.ts","webpack://lange/./lib/cart-event-queue.ts","webpack://lange/./sections/header/cart-button-control.ts","webpack://lange/./sections/header/search-button/index.ts","webpack://lange/./sections/header/profile-button/index.ts","webpack://lange/./sections/header/mobile-menu-button/index.ts","webpack://lange/./sections/header/menu-panels.ts","webpack://lange/./sections/header/preview-indicator.ts"],"sourcesContent":["function onMenuOpened(_event: GlobalEventHandlersEventMap['menu-opened']) {\n  const hamburgerIcon = document.querySelector('mobile-menu-button');\n  if (hamburgerIcon) {\n    hamburgerIcon.dataset.opened = 'true';\n  }\n}\n\nfunction onMenuClosed(_event: GlobalEventHandlersEventMap['menu-closed']) {\n  const hamburgerIcon = document.querySelector('mobile-menu-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';\nimport html from './index.html';\nimport styles from './index.scss';\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 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     * Should the badge be displayed\n     */\n    badgeShow: 'false' | 'true';\n\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    this.initializeShadowRoot();\n\n    const closeButton = this.shadowRoot.querySelector<HTMLElement>('.close');\n    closeButton.addEventListener('click', this.onCloseClickedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    const closeButton = this.shadowRoot.querySelector<HTMLElement>('.close');\n    closeButton?.removeEventListener('click', this.onCloseClickedBound);\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    // 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.hideBanner();\n      }\n    }\n\n    const badge = this.shadowRoot.querySelector<HTMLElement>('.badge');\n    if (this.dataset.badgeShow === 'true') {\n      badge.hidden = false;\n    } else {\n      badge.hidden = true;\n    }\n  }\n\n  private hideBanner() {\n    // this has to hide the entire header banner which should be the ancestor of this component\n    const headerBanner = this.closest<HTMLElement>('.banner');\n    if (headerBanner) {\n      headerBanner.hidden = true;\n    }\n  }\n\n  private onCloseClicked(_event: MouseEvent) {\n    this.hideBanner();\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 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  limit_scope?: 'all' | 'each';\n\n  prefix?: string;\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  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\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  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((<Error>error).message, { 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((<Error>error).message, { 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((<Error>error).message, { 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\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 onKeyUpBoundDebounced = debounce(this.onKeyUp.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.onKeyUpBoundDebounced);\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.onKeyUpBoundDebounced);\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\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    // The Enter key submits the form on keydown, and the submit handler disables the search button.\n    // Its keyup arrives afterward, and we skip it so the button is not enabled again while the\n    // browser is still leaving for the results page.\n    if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') {\n      return;\n    }\n\n    this.updateSearchButton();\n\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<HTMLElement>('.banner');\n\n    // The banner is the promo strip at the very top of the header. Not every page renders one, and\n    // on the pages that do, the announcement bar inside it hides the whole strip when the shopper\n    // dismisses it or when its countdown runs out. It hides it by setting the hidden attribute on\n    // this element, so reading that attribute back here is how we find out whether the strip is\n    // still 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';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the cart icon with a badge showing the number of items in the cart. The badge reflects\n * the quantity data attribute and is hidden when that quantity is zero.\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    if (!this.shadowRoot) {\n      // This host carries no tabindex or role of its own and is not focusable on its own. The\n      // native button in its shadow tree is the focusable element. delegatesFocus routes a focus\n      // call on the host to that inner button, which is what lets focus land back here when the\n      // modal manager restores focus to the cart button after the cart drawer closes.\n\n      this.attachShadow({ mode: 'open', delegatesFocus: true });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\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/index.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, which is also why the host is the\n  // focus restore target. The host delegates focus to its inner button, so restoring focus to the\n  // host lands focus on that button.\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\n  const focusEvent = new Event('focus-stack-push-requested', { bubbles: true, composed: true });\n  cartButton.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';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the search button. Display search input when clicked.\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    this.initializeShadowRoot();\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 initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\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  dispatchEvent(new Event('search-input-reset'));\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';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the profile button. Redirect to log in page or account page when clicked.\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    this.initializeShadowRoot();\n\n    this.addEventListener('click', this.onButtonClickedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('click', this.onButtonClickedBound);\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 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  private render() {\n    const button = this.shadowRoot.querySelector('button');\n\n    if (this.dataset.authenticated === 'true') {\n      button.setAttribute('aria-label', 'Go to your account page');\n    } else {\n      button.setAttribute('aria-label', 'Log in to your account');\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';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the hamburger icon. Opens the main menu drawer when clicked.\n */\nclass MobileMenuButton 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 onMobileMenuButtonClickedBound = this.onMobileMenuButtonClicked.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n    this.addEventListener('click', this.onMobileMenuButtonClickedBound);\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('click', this.onMobileMenuButtonClickedBound);\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  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 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 onMobileMenuButtonClicked(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    'mobile-menu-button': MobileMenuButton;\n  }\n}\n\nif (!customElements.get('mobile-menu-button')) {\n  customElements.define('mobile-menu-button', MobileMenuButton);\n}\n","import { ready } from '../../lib/dom-ready.js';\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 * In the liquid, menu-panels is an id on a basic div element. This is a custom interface that\n * attaches explicit dataset properties to the basic div element so that we have type safety.\n */\ninterface MenuPanelsElement extends HTMLElement {\n  readonly dataset: {\n    /**\n     * The id of the panel that is currently open. Deleted when the menu closes, so it is undefined\n     * whenever no panel is showing.\n     */\n    activePanel: string | undefined;\n  };\n}\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<MenuPanelsElement>('#menu-panels');\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  const activePanel = menuPanelsElement.dataset.activePanel;\n  const previousPanel = menuPanelsElement.querySelector<HTMLElement>('#' + activePanel);\n  if (previousPanel) {\n    previousPanel.hidden = true;\n    previousPanel.inert = true;\n  }\n\n  menuPanelsElement.dataset.activePanel = event.detail.element_id;\n\n  // close the search box if open\n  dispatchEvent(new Event('search-input-reset'));\n\n  const stickyHeader = document.querySelector('header .sticky-header');\n  stickyHeader.classList.add('menu-shown');\n\n  panel.inert = false;\n  panel.hidden = false;\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  // only dispatch modal-open-requested if there was no previous panel\n  if (!previousPanel) {\n    removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n    addEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n    const showEvent = new Event('modal-open-requested', { bubbles: true, composed: true });\n    menuPanelsElement.dispatchEvent(showEvent);\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  panel.dispatchEvent(new Event('menu-opened', { bubbles: true }));\n}\n\nfunction onMenuCloseRequested(_event: WindowEventMap['menu-close-requested']) {\n  removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n  const menuPanelsElement = document.querySelector<MenuPanelsElement>('#menu-panels');\n  const activePanel = menuPanelsElement?.dataset.activePanel;\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  if (activePanel) {\n    const panel = menuPanelsElement?.querySelector<HTMLElement>('#' + activePanel);\n    if (panel) {\n      panel.hidden = true;\n      panel.inert = true;\n    }\n\n    if (menuPanelsElement) {\n      delete menuPanelsElement.dataset.activePanel;\n    }\n\n    if (menuPanelsElement) {\n      const event = new Event('modal-close-requested', { bubbles: true, composed: true });\n      menuPanelsElement.dispatchEvent(event);\n    }\n\n    if (panel) {\n      panel.dispatchEvent(new Event('menu-closed', { bubbles: true }));\n    }\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  dispatchEvent(new Event('menu-close-requested'));\n}\n\nfunction onHeaderClicked(_event: MouseEvent) {\n  // close the search box if open\n  dispatchEvent(new Event('search-input-reset'));\n\n  // close the menu if open\n  dispatchEvent(new Event('menu-close-requested'));\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    dispatchEvent(new Event('menu-close-requested'));\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    dispatchEvent(new Event('menu-close-requested'));\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<MenuPanelsElement>('#menu-panels');\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","import { ready } from '../../lib/dom-ready.js';\n\n/**\n * Shopify's preview bar is the only thing that tells you you are looking at a preview theme, and\n * people close it and then forget. So they report a bug that is already fixed, or they QA the\n * staging theme thinking it is live. We stamp PREVIEW across the header logo as a second signal\n * that sticks around after the preview bar is gone.\n */\nfunction onReady() {\n  if (!Shopify.previewMode) {\n    return;\n  }\n\n  // The theme editor also reports preview mode, and there it is already obvious which theme you are\n  // on, so the label would just cover the logo.\n\n  if (Shopify.designMode) {\n    return;\n  }\n\n  // Ignore automated agents. Slightly improves the accuracy of Google Lighthouse by avoiding the\n  // repaint.\n\n  if (navigator.webdriver) {\n    return;\n  }\n\n  // Every header variant renders the same logo snippet wrapped in a.logo, so one selector covers\n  // all of them.\n\n  const logo = document.querySelector('a.logo > svg');\n  if (!logo) {\n    return;\n  }\n\n  // The logo is inline SVG, so both pieces go inside it as more SVG rather than as something\n  // positioned on top. That way they scale with the logo wherever the logo is drawn, and there is\n  // no position to keep in step if the header layout moves later.\n  //\n  // The logo is solid black, so text laid straight over it fuses with the letters and stops being\n  // readable. A translucent white sheet across the whole thing washes it out to a pale grey first,\n  // which leaves the label sitting on something light enough to read against. The logo is still\n  // perfectly recognizable underneath.\n\n  const wash = document.createElementNS('http://www.w3.org/2000/svg', 'rect');\n  wash.setAttribute('x', '0');\n  wash.setAttribute('y', '0');\n  wash.setAttribute('width', '130');\n  wash.setAttribute('height', '59.32');\n  wash.setAttribute('fill', '#ffffff');\n  wash.setAttribute('fill-opacity', '0.9');\n\n  const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');\n  label.textContent = 'PREVIEW';\n  label.setAttribute('x', '65');\n  label.setAttribute('y', '38');\n  label.setAttribute('text-anchor', 'middle');\n  label.setAttribute('font-family', 'sans-serif');\n  label.setAttribute('font-size', '17');\n  label.setAttribute('font-weight', '700');\n  label.setAttribute('fill', '#ff6b00');\n\n  // The logo already carries a title naming the brand, and both of these are decoration on top of\n  // it, so we keep them out of the accessibility tree.\n\n  wash.setAttribute('aria-hidden', 'true');\n  label.setAttribute('aria-hidden', 'true');\n\n  // Append order is paint order, so the sheet has to go down before the label.\n\n  logo.append(wash);\n  logo.append(label);\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","initializeShadowRoot","shadowRoot","render","disconnectedCallback","closeButton","removeEventListener","attachShadow","mode","innerHTML","clockShow","parseInt","clockEndTime","Date","now","hideBanner","badge","badgeShow","hidden","headerBanner","closest","customElements","get","define","SearchProductPreview","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","AssertionError","constructor","message","super","name","async","suggest","input","expression","assert","query","path","Shopify","routes","root","section_id","url","URL","location","href","response","responseText","responseBody","searchParams","set","Number","isInteger","limit","limit_scope","Array","isArray","resource_types","join","prefix","fields","unavailable_products","fetch","error","suggestError","SuggestError","cause","ok","status","retryAfter","headers","retryAfterSeconds","retry_after","includes","json","response_body","console","log","text","resources","results","articles","collections","pages","products","queries","JSON","parse","value","ShopifySearchInput","observedAttributes","onFormSubmitBound","onFormSubmit","onFocusBound","onFocus","onDocumentClickBound","onDocumentClick","onSearchCloseBound","onSearchClose","onKeyUpBoundDebounced","func","timeout","timer","_len","arguments","args","_key","clearTimeout","setTimeout","debounce","onKeyUp","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","remove","header","event","preventDefault","stopPropagation","resetEvent","Event","bubbles","composed","dispatchEvent","toggle","key","anchors","querySelectorAll","markSuggestionActiveAtIndex","_this$shadowRoot$quer","activeAnchor","click","updateSearchButton","searchButton","urlQueryParam","persisted","searchFocused","activeElement","searchEvent","CustomEvent","detail","event_id","showPreviewDrawer","body","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","handle","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","headerBannerVisible","headerBannerHeight","country","menuBar","menuBarHeight","contentPosition","getBoundingClientRect","bottom","passive","CartButton","delegatesFocus","cartCounter","quantity","itemsText","removeAttribute","button","setAttribute","isCartUpdatedEvent","onCartUpdated","cart","item_count","buttons","onCartButtonClick","cartButton","localName","focusEvent","searchResetEvent","menuCloseEvent","cartOpenEvent","source","designMode","Math","floor","cart_lifecycle","queue","SearchButton","onSearchButtonClickedBound","onSearchButtonClicked","onSearchClosedBound","onSearchClosed","searchElement","ProfileButton","onButtonClickedBound","onButtonClicked","authenticated","accountUrl","accountLoginUrl","MobileMenuButton","onMobileMenuButtonClickedBound","onMobileMenuButtonClicked","_name","menuOpenEvent","element_id","onMenuOpenRequested","menuPanelsElement","panel","activePanel","previousPanel","inert","menuButtons","controlledPanel","getAttribute","onModalDismissRequested","showEvent","firstFocusable","focusVisible","carousel","refresh","onMenuCloseRequested","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","id","onHorizontalSwipe","menuPanels","removeProperty","mobileBackButtons","previewMode","navigator","webdriver","logo","wash","createElementNS","label","append"],"sourceRoot":""}