{"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-element/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 handle of the blog to search within. Required when use_graphql is true, ignored otherwise.\n   *\n   * @example \"how-to\"\n   */\n  blog_handle?: string;\n\n  /**\n   * The default fields searched on are title, product_type, variants.title, and vendor. For the\n   * best search experience, you should search on the default field set.\n   *\n   * There are constraints for translated storefronts, see the docs.\n   */\n  fields?: Field[];\n\n  /**\n   * Limits the number of results based on limit_scope. The value can range from 1 to 10, and the\n   * default is 10.\n   */\n  limit?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;\n\n  /**\n   * Decides how limit spreads across result types. With all, the limit counts results across every\n   * type together. With each, the limit applies to each type separately. The default is all.\n   */\n  limit_scope?: 'all' | 'each';\n\n  /**\n   * Whether page and article results match partial words on the last search term. With last, a\n   * search for \"winter snow\" can match a page containing \"snowshoe\". With none, partial words are\n   * not matched. The default is last.\n   */\n  prefix?: 'last' | 'none';\n\n  /**\n   * The search query.\n   */\n  query: string;\n\n  /**\n   * Specifies the type of results requested.\n   *\n   * The following are the accepted values, which can be combined in a comma-separated list:\n   *\n   * * product\n   * * page\n   * * article\n   * * collection\n   * * query\n   *\n   * The default value is query,product,collection,page.\n   *\n   * To change the default value, you can use Search Settings in the Search & Discovery app.\n   */\n  resource_types?: ResourceType[];\n\n  /**\n   * The id of a section to render the search results with. When set, the request goes to the\n   * suggest endpoint without the json extension and the response is the section's server rendered\n   * html rather than json.\n   *\n   * @example \"predictive-search\"\n   */\n  section_id?: string;\n\n  /**\n   * Specifies whether to display results for unavailable products.\n   *\n   * The following are the accepted values:\n   *\n   * * show: Show unavailable products.\n   * * hide: Hide unavailable products.\n   * * last: Show unavailable products below other matching results.\n   *\n   * The default value is last.\n   *\n   * To change the default value, you can use Search Settings in the Search & Discovery app.\n   *\n   * This parameter is only applicable to type product.\n   */\n  unavailable_products?: 'hide' | 'last' | 'show';\n\n  /**\n   * Search through the Storefront GraphQL API instead of the AJAX suggest endpoint. This path only\n   * returns articles from the one blog named by blog_handle, and it reads only query and limit from\n   * the other options.\n   */\n  use_graphql?: boolean;\n}\n\nexport type ProductField = 'body' | 'product_type' | 'tag' | 'title' | 'variants.barcode' |\n  'variants.sku' | 'variants.title' | 'vendor';\nexport type PageField = 'author' | 'body' | 'title';\nexport type ArticleField = 'author' | 'body' | 'tag' | 'title';\nexport type CollectionField = 'title';\nexport type Field = ArticleField | CollectionField | PageField | ProductField;\n\nexport type ResourceType = 'article' | 'collection' | 'page' | 'product' | 'query';\n\nexport interface Product {\n  /**\n   * Whether the product is available for sale (e.g. in stock)\n   */\n  available: boolean;\n\n  /**\n   * HTML formatted product description\n   */\n  body: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  compare_at_price_max: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  compare_at_price_min: string;\n\n  featured_image: FeaturedImage;\n\n  /**\n   * Shopify product handle\n   *\n   * @example \"my-product-slug\"\n   */\n  handle: string;\n\n  /**\n   * Shopify product id\n   *\n   * @example 123324324\n   */\n  id: number;\n\n  /**\n   * Product image url\n   *\n   * @example \"https://cdn.shopify.com/.../image.jpg?v=1234\"\n   */\n  image: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  price: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  price_max: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  price_min: string;\n\n  /**\n   * @example [\"best-sellers\", \"hair-care-routine\"]\n   */\n  tags: string[];\n\n  /**\n   * @example \"My Product\"\n   */\n  title: string;\n\n  /**\n   * @example \"hair-care\"\n   */\n  type: string;\n\n  /**\n   * @example \"/products/my-handle?_pos=1&_psq=hair&_ss=e&_v=1.0\"\n   */\n  url: string;\n\n  /**\n   * A product variant is returned only when the query matches terms specific to the variant title.\n   * Only the variant with the most matching terms is returned. When a variant is returned, the\n   * following product_object fields will match those of the variant:\n   *\n   * * featured_image\n   * * image\n   * * url\n   *\n   * cspell:ignore snowbo\n   *\n   * For example, a store has a snowboard with a blue variant and a light blue variant. If you\n   * search for snowbo, then only the snowboard product is returned. However, if you search for\n   * light blue snowbo, then the snowboard product is returned with the light blue variant.\n   */\n  variants: Variant[];\n\n  /**\n   * @example \"my company name\"\n   */\n  vendor: string;\n}\n\nexport interface Variant {\n  available: boolean;\n\n  /**\n   * might be wrong\n   */\n  compare_at_price: number;\n\n  featured_image: FeaturedImage;\n\n  id: number;\n  image: string;\n\n  /**\n   * might be wrong\n   */\n  price: number;\n  title: string;\n  url: string;\n}\n\n/**\n * A query represents an auto-complete suggestion. For example, based on what someone has typed in,\n * zero or more queries will be generated that represent more specific or similar queries to\n * consider using.\n */\nexport interface Query {\n  /**\n   * @example \"<mark>hair</mark><span>spray</span>\"\n   */\n  styled_text: string;\n\n  /**\n   * @example \"hairspray\"\n   */\n  text: string;\n\n  /**\n   * @example \"/search?_pos=1&_psq=hair&_ss=e&_v=1.0&q=hairspray\"\n   */\n  url: string;\n}\n\nexport interface FeaturedImage {\n  alt: string;\n  aspect_ratio: number;\n  height: number;\n  url: string;\n  width: number;\n}\n\nexport interface Collection {\n  body: string;\n  featured_image: FeaturedImage;\n  handle: string;\n  id: number;\n}\n\nexport interface Page {\n  author: string;\n  body: string;\n  handle: string;\n  id: number;\n  published_at: string;\n  title: string;\n  url: string;\n}\n\nexport interface Article {\n  author: string;\n  body: string;\n  handle: string;\n  id: number;\n  image: string;\n  published_at: string;\n  summary_html: string;\n  tags: string[];\n  title: string;\n  url: string;\n}\n\nexport interface ShopifySuggestResponseBody {\n  resources: {\n    results: {\n      articles: Article[];\n      collections: Collection[];\n      pages: Page[];\n      products: Product[];\n      queries: Query[];\n    };\n  };\n}\n\nexport interface ShopifyErrorResponseBody {\n  description: string;\n  message: string;\n  status: string;\n}\n\n/**\n * Represents the response body.\n *\n * In a full implementation that parsed the response body of error responses, this should actually\n * be a union of ShopifySuggestResponseBody and also ShopifyErrorResponseBody. However, we are not\n * actually leveraging ShopifyErrorResponseBody. We are just leaving the type defined for\n * documentation purposes. The promise returned by suggest only ever resolves on success so we can\n * limit the output type to the success type.\n */\nexport type ResponseBody = ShopifySuggestResponseBody;\n\n/**\n * Send a search request to the Shopify api and receive a search response.\n *\n * If section id is provided, this returns a string. If section id is not provided, this returns an\n * object.\n *\n * I did not bother to make the output type derived from the presence of the section id. For now the\n * output type has to be specified via the generic value if the output should be interpreted as a\n * string. Without specifying the generic parameter this defaults to producing an object.\n *\n * @param input various search criteria\n */\nexport async function suggest<OutputType = ResponseBody>(input: SuggestInput) {\n  assert(typeof input.query === 'string' && input.query.length, 'Invalid input');\n\n  if (input.use_graphql) {\n    const responseBody = await suggestBlogArticles(input);\n    return <OutputType>responseBody;\n  }\n\n  let path = Shopify.routes.root + 'search/suggest';\n  if (!input.section_id) {\n    path += '.json';\n  }\n\n  const url = new URL(path, location.href);\n  url.searchParams.set('q', input.query);\n\n  if (input.section_id) {\n    url.searchParams.set('section_id', input.section_id);\n  }\n\n  if (Number.isInteger(input.limit)) {\n    url.searchParams.set('resources[limit]', input.limit.toString());\n  }\n\n  if (input.limit_scope) {\n    url.searchParams.set('resources[limit_scope]', input.limit_scope);\n  }\n\n  if (Array.isArray(input.resource_types) && input.resource_types.length > 0) {\n    url.searchParams.set('resources[type]', input.resource_types.join(','));\n  }\n\n  if (input.prefix) {\n    url.searchParams.set('options[prefix]', input.prefix);\n  }\n\n  if (Array.isArray(input.fields) && input.fields.length > 0) {\n    url.searchParams.set('resources[options][fields]', input.fields.join(','));\n  }\n\n  if (input.unavailable_products) {\n    url.searchParams.set('resources[options][unavailable_products]', input.unavailable_products);\n  }\n\n  let response: Response;\n  try {\n    response = await fetch(url);\n  } catch (error) {\n    const suggestError = new SuggestError('Failed to fetch search results', { cause: error });\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // If not using section id, fetch json response body for errors including 422, 417, and 429. See\n  // https://shopify.dev/docs/api/ajax/reference/predictive-search#invalid-parameter-error. We could\n  // fetch and then parse these response bodies, but basically there is no need except for maybe the\n  // description hint for 422. Those kinds of errors would be obvious and permanent programming\n  // errors though so we would quickly get those resolved. So just recording the status in the\n  // thrown error is sufficient. We do not really need to parse the body.\n\n  if (!response.ok) {\n    const suggestError = new SuggestError(\n      `Received status ${response.status} fetching search results`);\n    suggestError.status = response.status;\n    suggestError.input = input;\n    suggestError.url = url.toString();\n\n    // Try to get the Retry-After header value and embed this in the thrown error so that we grant\n    // callers the ability to check it.\n    //\n    // See https://shopify.dev/docs/api/ajax/reference/predictive-search#too-many-requests.\n    if (response.status === 429) {\n      const retryAfter = response.headers.get('Retry-After');\n      const retryAfterSeconds = parseInt(retryAfter, 10);\n      if (Number.isInteger(retryAfterSeconds)) {\n        suggestError.retry_after = retryAfterSeconds;\n      }\n    }\n\n    // For certain errors that have documented response bodies, try to fetch the response body so\n    // that we can get more details into the error.\n    if ([417, 422, 429, 500].includes(response.status)) {\n      try {\n        const responseBody = await response.json();\n        suggestError.response_body = responseBody;\n      } catch (error) {\n        console.log(error);\n      }\n    }\n\n    throw suggestError;\n  }\n\n  let responseText: string;\n\n  try {\n    responseText = await response.text();\n  } catch (error) {\n    const suggestError = new SuggestError('Failed to read the search results response body',\n      { cause: error });\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // If section id was provided, the response body is a string containing html formatted text that\n  // was server side rendered according to the specified section liquid and the search results. We\n  // just return the string. The caller may have a preference on how to parse the html or whether to\n  // parse it at all, so we do not parse on the caller's behalf.\n\n  if (input.section_id) {\n    return <OutputType>responseText;\n  }\n\n  // Without a section id we are fetching from the suggest.json endpoint so we parse the results and\n  // do some basic validation of the parsed object.\n\n  // Shopify occasionally answers this endpoint with a success status and an empty body, typically a\n  // dropped or truncated connection in a mobile in-app browser. We have verified that refetching\n  // one of these same query urls ourselves returns a full, valid result set, so the empty body is a\n  // visitor-side truncation rather than a broken endpoint or a missing resource. There is nothing\n  // to parse and no results to show, so degrade to an empty result set rather than throwing on the\n  // empty input. The caller renders an empty preview (falling back to search history) instead of an\n  // error, and the next keystroke issues a fresh request that normally succeeds.\n\n  if (!responseText) {\n    // Typing the literal as the concrete response body shape gets it checked at construction, then\n    // the return asserts to the caller-chosen generic the same way the other return sites do, since\n    // a concrete value is not assignable to an unconstrained type parameter on its own.\n\n    const responseBody: ResponseBody = {\n      resources: {\n        results: { articles: [], collections: [], pages: [], products: [], queries: [] }\n      }\n    };\n\n    return <OutputType>responseBody;\n  }\n\n  let responseBody;\n\n  try {\n    responseBody = JSON.parse(responseText);\n  } catch (error) {\n    const suggestError = new SuggestError(\n      'Failed to parse the search results response body as JSON', { cause: error });\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.response_body = responseText;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // We expect the type of the response body object to always be the documented type. However,\n  // Shopify could change this out from under us at any time, so we keep this as a runtime check\n  // instead of a hard cast.\n\n  if (!isShopifySuggestResponseBody(responseBody)) {\n    const suggestError = new SuggestError('Unexpected response body');\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.response_body = responseBody;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  return <OutputType>responseBody;\n}\n\n/**\n * The response body of the blog article search query, holding only the fields that query asks for.\n */\ninterface BlogArticleSearchResponseBody {\n  data: {\n    blog: {\n      articles: {\n        nodes: {\n          authorV2: { name: string; };\n          excerptHtml: string;\n          handle: string;\n\n          /**\n           * @example \"gid://shopify/Article/634932658276\"\n           */\n          id: string;\n\n          image: { url: string; };\n          publishedAt: string;\n          tags: string[];\n          title: string;\n        }[];\n      };\n    };\n  };\n  errors?: unknown[];\n}\n\nfunction isBlogArticleSearchResponseBody(value: any): value is BlogArticleSearchResponseBody {\n  return Array.isArray(value?.data?.blog?.articles?.nodes);\n}\n\n/**\n * Searches the articles of one blog through the Storefront GraphQL API. This works without an\n * access token, since Shopify allows reading articles without one. The results are reshaped into\n * the suggest response body so callers can treat both paths the same.\n *\n * @see https://shopify.dev/docs/api/storefront/2026-10/queries/blog\n */\nasync function suggestBlogArticles(input: SuggestInput) {\n  assert(typeof input.blog_handle === 'string' && input.blog_handle.length, 'Invalid blog handle');\n\n  const url = new URL('/api/2026-10/graphql.json', location.href);\n\n  const query = `query ($blogHandle: String!, $query: String!, $first: Int!) {\n    blog(handle: $blogHandle) {\n      articles(first: $first, query: $query, sortKey: RELEVANCE) {\n        nodes {\n          id\n          handle\n          title\n          tags\n          publishedAt\n          excerptHtml\n          authorV2 { name }\n          image { url }\n        }\n      }\n    }\n  }`;\n\n  const variables = {\n    blogHandle: input.blog_handle,\n    query: input.query,\n    first: Number.isInteger(input.limit) ? input.limit : 10\n  };\n\n  let response: Response;\n  try {\n    response = await fetch(url, {\n      method: 'POST',\n      headers: { 'Content-Type': 'application/json' },\n      body: JSON.stringify({ query, variables })\n    });\n  } catch (error) {\n    const suggestError = new SuggestError('Failed to fetch blog article search results',\n      { cause: error });\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  if (!response.ok) {\n    const suggestError = new SuggestError(\n      `Received status ${response.status} fetching search results`);\n    suggestError.status = response.status;\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  let responseBody;\n  try {\n    responseBody = await response.json();\n  } catch (error) {\n    const suggestError = new SuggestError(\n      'Failed to parse the blog article search response body as JSON', { cause: error });\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // GraphQL reports a bad query with a success status and an errors array in the body. A blog that\n  // does not exist comes back as a null blog rather than as an error, which the type guard rejects.\n\n  if (!isBlogArticleSearchResponseBody(responseBody) || Array.isArray(responseBody.errors)) {\n    const suggestError = new SuggestError('Unexpected response body');\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.response_body = responseBody;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // The GraphQL response comes back in a different shape than the AJAX suggest endpoint's JSON, so\n  // we convert each article into the shape the AJAX endpoint uses. That way callers get the same\n  // search results format whichever path served the search. We don't assume every article has\n  // everything filled in, so an article with no image or no author just ends up with that field\n  // empty rather than throwing. The body is always empty, since we leave it out of the query to\n  // keep the response small.\n\n  const articles: Article[] = [];\n  for (const node of responseBody.data.blog.articles.nodes) {\n    articles.push({\n      author: node.authorV2?.name,\n      body: undefined,\n      handle: node.handle,\n      id: parseInt(node.id.split('/').pop(), 10),\n      image: node.image?.url,\n      published_at: node.publishedAt,\n      summary_html: node.excerptHtml,\n      tags: node.tags,\n      title: node.title,\n      url: `${Shopify.routes.root}blogs/${input.blog_handle}/${node.handle}`\n    });\n  }\n\n  const suggestResponseBody: ResponseBody = {\n    resources: {\n      results: { articles, collections: [], pages: [], products: [], queries: [] }\n    }\n  };\n\n  return suggestResponseBody;\n}\n\nexport function isShopifySuggestResponseBody(value: any): value is ShopifySuggestResponseBody {\n  return typeof value === 'object' && value !== null && typeof value.resources === 'object' &&\n    value.resources !== null;\n}\n\n/**\n * Represents a Shopify Suggest API error.\n */\nexport class SuggestError extends Error {\n  /**\n   * The input to the suggest function to help with diagnosing what caused the error when it might\n   * be related to the input\n   */\n  public declare input: SuggestInput;\n\n  /**\n   * Response status code\n   *\n   * @example 200\n   */\n  public declare status: number;\n\n  /**\n   * The response body. This could be a string or an object or something else.\n   */\n  public declare response_body: any;\n\n  /**\n   * If the status is 429, indicating a rate limit error, then the suggest function also tries to\n   * access the Retry-After header and stash the value in the error.\n   *\n   * This is the number of seconds. The caller of suggest should make sure to delay any future calls\n   * by at least this amount of time to prevent continuing to receive more 429s.\n   */\n  public declare retry_after: number;\n\n  /**\n   * The request or response url involved in the request that erred.\n   */\n  public declare url: string;\n\n  constructor(message?: string, options?: ErrorOptions) {\n    super(message, options);\n    this.name = this.constructor.name;\n  }\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { v4 as uuidv4 } from 'uuid';\nimport { build } from '../../../lib/shopify/image-url.js';\nimport { suggest, type Query, type Product } from '../../../lib/shopify/predictive-search.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the search input and the search input results preview.\n */\nclass ShopifySearchInput extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-trigger-focus'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Boolean represented as string to indicate whether user is on search results page\n     */\n    isSearchPage: string;\n\n    /**\n     * Mechanism to externally focus the user on the input element\n     */\n    triggerFocus: string;\n  };\n\n  private onFormSubmitBound = this.onFormSubmit.bind(this);\n  private onFocusBound = this.onFocus.bind(this);\n  private onDocumentClickBound = this.onDocumentClick.bind(this);\n  private onSearchCloseBound = this.onSearchClose.bind(this);\n  private 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 type { CustomElement } from '@integrabeauty/custom-elements';\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    /**\n     * Announces that a menu panel has finished closing. Dispatched from the panel element itself\n     * and bubbles to window, so the panel is identified by the event target rather than a detail\n     * payload.\n     */\n    'menu-closed': Event;\n\n    /**\n     * Announces that a menu panel has finished opening. Dispatched from the panel element itself\n     * and bubbles to window, so the panel is identified by the event target rather than a detail\n     * payload.\n     */\n    'menu-opened': Event;\n  }\n}\n\n/**\n * Holds the header's drop down menu panels and shows one of them at a time. `data-active-panel`\n * picks which panel shows, and `data-modal-state` opens and closes the menu. Changing the active\n * panel while the menu is open swaps panels without closing the menu.\n *\n * This exists so the menu can be opened and closed by setting an attribute, the same way as the\n * other modals. For now the header's menu script sets `data-modal-state` itself. Later the modal\n * manager sets it instead.\n *\n * Sends `menu-opened` from a panel each time that panel shows, and `menu-closed` from the panel\n * that was showing when the menu closes.\n */\nclass MenuPanelsElement extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-active-panel', 'data-modal-state'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * The id of the panel to show. Deleted when the menu closes, so it is undefined whenever no\n     * panel is showing.\n     */\n    activePanel: string;\n\n    /**\n     * Whether the menu is shown, as \"open\". Anything else, or no value at all, means closed.\n     *\n     * Set `data-active-panel` before opening, since opening shows whichever panel it names. A\n     * change only takes effect while the element is on the page.\n     */\n    modalState: string;\n  };\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    if (!this.isConnected || oldValue === newValue) {\n      return;\n    }\n\n    if (name === 'data-modal-state') {\n      if (newValue === 'open') {\n        this.open();\n      } else if (oldValue === 'open') {\n        this.close();\n      }\n    } else if (name === 'data-active-panel' && this.dataset.modalState === 'open') {\n      hidePanel(this.findPanel(oldValue));\n\n      const panel = this.findPanel(newValue);\n      revealPanel(panel);\n      afterPanelShown(panel);\n    }\n  }\n\n  private open() {\n    const panel = this.findPanel(this.dataset.activePanel);\n    revealPanel(panel);\n\n    const event = new Event('modal-open-requested', { bubbles: true, composed: true });\n    this.dispatchEvent(event);\n\n    afterPanelShown(panel);\n  }\n\n  private close() {\n    const panel = this.findPanel(this.dataset.activePanel);\n    hidePanel(panel);\n\n    const event = new Event('modal-close-requested', { bubbles: true, composed: true });\n    this.dispatchEvent(event);\n\n    panel?.dispatchEvent(new Event('menu-closed', { bubbles: true }));\n  }\n\n  private findPanel(id: string) {\n    if (!id) {\n      return null;\n    }\n\n    return this.querySelector<HTMLElement>('#' + id);\n  }\n}\n\nfunction revealPanel(panel: HTMLElement) {\n  if (panel) {\n    panel.inert = false;\n    panel.hidden = false;\n  }\n}\n\nfunction hidePanel(panel: HTMLElement) {\n  if (panel) {\n    panel.hidden = true;\n    panel.inert = true;\n  }\n}\n\n/**\n * The steps that follow a panel becoming visible, whether the menu just opened or it swapped panels\n * while open.\n */\nfunction afterPanelShown(panel: HTMLElement) {\n  if (!panel) {\n    return;\n  }\n\n  const firstFocusable = panel.querySelector<HTMLElement>('.menu-button');\n  if (firstFocusable) {\n    firstFocusable.focus({ focusVisible: true });\n  }\n\n  // The special offers carousel mounts while its panel is still hidden, so it can't measure its\n  // slides until now. The refresh is also where it works out which slides are off screen and makes\n  // them inert, so do not drop it.\n  const carousel = panel.querySelector('carousel-element');\n  if (carousel) {\n    carousel.dataset.refresh = 'true';\n  }\n\n  panel.dispatchEvent(new Event('menu-opened', { bubbles: true }));\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'menu-panels-element': MenuPanelsElement;\n  }\n}\n\nif (!customElements.get('menu-panels-element')) {\n  customElements.define('menu-panels-element', MenuPanelsElement);\n}\n","import { ready } from '../../lib/dom-ready.js';\n\nfunction onMenuOpenRequested(event: WindowEventMap['menu-open-requested']) {\n  // A request must name the panel to open; there is nothing to act on without one\n  if (!event.detail.element_id) {\n    return;\n  }\n\n  const menuPanelsElement = document.querySelector('menu-panels-element');\n  if (!menuPanelsElement) {\n    return;\n  }\n\n  const panel = menuPanelsElement.querySelector<HTMLElement>('#' + event.detail.element_id);\n\n  // If the id does not point to something in the dom, ignore the request.\n  if (!panel) {\n    return;\n  }\n\n  // close the search box if open\n  dispatchEvent(new Event('search-input-reset'));\n\n  const stickyHeader = document.querySelector('header .sticky-header');\n  stickyHeader.classList.add('menu-shown');\n\n  const menuButtons = document.querySelectorAll<HTMLElement>('header .nav .menu-button');\n  for (const button of menuButtons) {\n    const controlledPanel = button.getAttribute('aria-controls');\n    if (controlledPanel === event.detail.element_id) {\n      button.setAttribute('aria-expanded', 'true');\n      button.classList.add('active');\n    } else if (controlledPanel) {\n      button.setAttribute('aria-expanded', 'false');\n      button.classList.remove('active');\n    }\n  }\n\n  // When the menu is already open this only swaps panels, and the element leaves the menu open.\n  menuPanelsElement.dataset.activePanel = event.detail.element_id;\n\n  if (menuPanelsElement.dataset.modalState !== 'open') {\n    removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n    addEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n    menuPanelsElement.dataset.modalState = 'open';\n  }\n}\n\nfunction onMenuCloseRequested(_event: WindowEventMap['menu-close-requested']) {\n  removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n  const stickyHeader = document.querySelector('header .sticky-header');\n  stickyHeader.classList.remove('menu-shown');\n\n  const menuButtons = document.querySelectorAll<HTMLElement>('header .nav .menu-button');\n  for (const button of menuButtons) {\n    const controlledPanel = button.getAttribute('aria-controls');\n    if (controlledPanel) {\n      button.setAttribute('aria-expanded', 'false');\n      button.classList.remove('active');\n    }\n  }\n\n  const menuPanelsElement = document.querySelector('menu-panels-element');\n  if (menuPanelsElement?.dataset.modalState === 'open') {\n    // The element hides whichever panel is active when it closes, so close before clearing it.\n    menuPanelsElement.dataset.modalState = 'closed';\n    delete menuPanelsElement.dataset.activePanel;\n  }\n}\n\n/**\n * Closes the open menu panel when the user performs a light-dismiss gesture (a click on the\n * backdrop or an escape key press). Rather than tearing the panel down directly, it re-dispatches\n * the header's own menu-close-requested event so every close funnels through the single close path\n * in onMenuCloseRequested. This listener is only attached while a panel is open.\n */\nfunction onModalDismissRequested(_event: WindowEventMap['modal-dismiss-requested']) {\n  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('menu-panels-element');\n  if (menuPanels) {\n    // the menu panels are initially hidden with an inline style, so we need to remove it to make\n    // them work\n    menuPanels.style.removeProperty('display');\n\n    menuPanels.addEventListener('touchstart', onMenuPanelTouchStart, { passive: true });\n    menuPanels.addEventListener('touchmove', onMenuPanelTouchMove, { passive: true });\n    menuPanels.addEventListener('touchend', onMenuPanelTouchEnd, { passive: false });\n  }\n\n  headerWrapper.addEventListener('click', onHeaderClicked);\n\n  const menuButtons = document.querySelectorAll<HTMLElement>(\n    'header .nav .menu-button, #menu-panels .menu-button');\n  for (const button of menuButtons) {\n    button.addEventListener('click', onMenuButtonClicked);\n  }\n\n  const mobileBackButtons = document.querySelectorAll<HTMLElement>('#menu-panels button.title');\n  for (const button of mobileBackButtons) {\n    button.addEventListener('click', mobileBackButtonClicked);\n  }\n}\n\nready(onReady);\n","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","assert","expression","message","AssertionError","constructor","super","name","async","suggest","input","query","use_graphql","responseBody","blog_handle","url","URL","location","href","variables","blogHandle","first","Number","isInteger","limit","response","fetch","method","headers","body","JSON","stringify","error","suggestError","SuggestError","cause","ok","status","json","value","Array","isArray","_value$data","data","blog","articles","nodes","errors","response_body","node","_node$authorV","_node$image","author","authorV2","handle","id","split","pop","published_at","publishedAt","summary_html","excerptHtml","tags","Shopify","routes","root","resources","results","collections","pages","products","queries","suggestBlogArticles","path","section_id","responseText","searchParams","set","limit_scope","resource_types","join","prefix","fields","unavailable_products","retryAfter","retryAfterSeconds","retry_after","includes","console","log","text","parse","ShopifySearchInput","observedAttributes","onFormSubmitBound","onFormSubmit","onFocusBound","onFocus","onDocumentClickBound","onDocumentClick","onSearchCloseBound","onSearchClose","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","suggestions","previewEl","position","filteredQueries","filter","q","liHeaderEl","createElement","role","ariaLevel","tabIndex","appendChild","suggestedQuery","liEl","createSearchQuerySuggestionListElement","product","createProductSuggestionListElement","userQuery","find","styled_text","anchorEl","currentQuery","preview","featured_image","absolute","startsWith","named","size","width","height","crop","quality","replace","build","anchor","productHandle","origin","createSuggestedProductUrl","ariaLabel","item","index","old","adjustedIndex","from","target","HTMLAnchorElement","currentTarget","createSuggestedSearchTermUrl","warn","resultList","renderErrorMessage","ready","callback","type","readyState","scrollState","currentPosition","previousPosition","direction","timerId","onResize","scrollActions","handleScrollActions","setCssVariables","onScroll","onTimeout","headerWrapper","headerHeight","offsetHeight","containingElement","parentNode","scrollY","scrollTop","state","scrollPosition","scrollDirection","scrollTolerance","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","MenuPanelsElement","open","close","modalState","hidePanel","findPanel","panel","revealPanel","afterPanelShown","activePanel","inert","firstFocusable","focusVisible","carousel","refresh","onMenuOpenRequested","menuPanelsElement","menuButtons","controlledPanel","getAttribute","onModalDismissRequested","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","onHorizontalSwipe","menuPanels","removeProperty","mobileBackButtons","previewMode","navigator","webdriver","logo","wash","createElementNS","label","append"],"sourceRoot":""}