{"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/./lib/countdown-timer.ts","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/search-history.ts","webpack://lange/./sections/header/shopify-search-input/index.ts","webpack://lange/./lib/dom-ready.ts","webpack://lange/./sections/header/dynamic-header-script.ts","webpack://lange/./sections/header/cart-button/index.ts","webpack://lange/./lib/cart-event-queue.ts","webpack://lange/./sections/header/cart-button-control.ts","webpack://lange/./sections/header/search-button/index.ts","webpack://lange/./sections/header/profile-button/index.ts","webpack://lange/./sections/header/mobile-menu-button/index.ts","webpack://lange/./sections/header/menu-panels.ts","webpack://lange/./sections/header/preview-indicator.ts"],"sourcesContent":["function onMenuOpened(_event: GlobalEventHandlersEventMap['menu-opened']) {\n  const hamburgerIcon = document.querySelector('mobile-menu-button');\n  if (hamburgerIcon) {\n    hamburgerIcon.dataset.opened = 'true';\n  }\n}\n\nfunction onMenuClosed(_event: GlobalEventHandlersEventMap['menu-closed']) {\n  const hamburgerIcon = document.querySelector('mobile-menu-button');\n  if (hamburgerIcon) {\n    hamburgerIcon.dataset.opened = 'false';\n  }\n}\n\naddEventListener('menu-closed', onMenuClosed);\naddEventListener('menu-opened', onMenuOpened);\n","// The module cache\nlet __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tlet cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tlet module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// We have now more than one element that displays a countdown clock on the page. When the elements\n// use separate `setInterval()` calls to display their time the effect of them not being\n// synchronized can be seen even if the interval is low (50-100ms).\n//\n// This module provides a very simple synchronization method and some shared utilities for timestamp\n// calculations.\n//\n// Clock synchronization is based on a single `setInterval()` call. This starts a timer which emits\n// `timer-tick` events. Each page element can listen to the events to update the displayed time at\n// the very same moment other elements do.\n//\n// The timer has to be initialized with the `startTimer()` function. There is no possibility to stop\n// the timer, as we would have to implement a reference counter which would be prone to problems,\n// i.e. a bug in a simple component could stop all timers on the page. Instead of stopping the timer\n// the components should stop listening to the events.\n\ndeclare global {\n  /**\n   * Sentinel marking that the shared tick interval has been started, so repeated `startTimer()`\n   * calls do not each spin up their own interval.\n   *\n   * This lives on the global scope rather than in a module variable on purpose. Each section is its\n   * own webpack entry and we do not share chunks, so this file is copied into every bundle that\n   * imports it. A module variable would therefore be per-bundle, letting every section on the page\n   * start its own interval and defeating the synchronization. The tick events are already\n   * dispatched globally, so the guard has to be global too.\n   */\n  var countdown_timer_interval_started: boolean | undefined;\n}\n\n/**\n * The remaining time between two timestamps, broken down into whole units plus a human readable\n * summary. Each numeric component is the remainder within its larger unit, so `hours`, `minutes`,\n * and `seconds` never exceed their natural maximums (e.g. `seconds` is always 0-59).\n */\ninterface TimeLeft {\n  /** Whole days remaining. */\n  days?: number;\n\n  /** Whole hours remaining within the current day (0-23). */\n  hours?: number;\n\n  /** Whole minutes remaining within the current hour (0-59). */\n  minutes?: number;\n\n  /** Whole seconds remaining within the current minute (0-59). */\n  seconds?: number;\n\n  /** Human readable summary of the remaining time, e.g. `\"1 day 2 hours 3 minutes 4 seconds\"`. */\n  text?: string;\n}\n\n/**\n * Calculate the remaining time between currentTimestamp and endTimestamp and express it as an\n * object with number of days, hours, minutes, seconds as integers and a human readable `text`\n *\n * Timestamps should be in milliseconds.\n *\n * This is not guaranteed to return an object. It may return null if the current time is past the\n * end time or if the end time is falsy. In other words, a null return value indicates that either\n * there is no time left or it is not possible to compute it.\n */\nexport function computeTimeLeft(currentTimestamp: number, endTimestamp: number) {\n  if (currentTimestamp > endTimestamp || !endTimestamp) {\n    return null;\n  }\n\n  const secondsLeft = Math.round((endTimestamp - currentTimestamp) / 1000);\n  const seconds = secondsLeft % 60;\n\n  const minutesLeft = (secondsLeft - seconds) / 60;\n  const minutes = minutesLeft % 60;\n\n  const hoursLeft = (minutesLeft - minutes) / 60;\n  const hours = hoursLeft % 24;\n\n  const days = (hoursLeft - hours) / 24;\n\n  const text = [\n    days.toString(),\n    days === 1 ? 'day' : 'days',\n    hours.toString(),\n    hours === 1 ? 'hour' : 'hours',\n    minutes.toString(),\n    minutes === 1 ? 'minute' : 'minutes',\n    seconds.toString(),\n    seconds === 1 ? 'second' : 'seconds'\n  ].join(' ');\n\n  const timeLeft: TimeLeft = {\n    days,\n    hours,\n    minutes,\n    seconds,\n    text\n  };\n\n  return timeLeft;\n}\n\n/**\n * Start the shared heartbeat that drives every countdown on the page.\n *\n * Once started, a tick event fires on the global scope once a second carrying the current\n * timestamp. Each countdown section listens for that event and repaints from the same timestamp, so\n * every countdown on the page advances at the exact same instant instead of drifting out of step\n * with each other.\n *\n * Any section that wants a countdown may call this, in any order, as many times as it likes. Only\n * the very first call anywhere on the page actually creates the interval. Every later call finds\n * the guard already set and skips that part. The guard lives on the global scope rather than in a\n * module variable on purpose. This file is copied into each webpack bundle because we do not share\n * chunks, so a module variable would be per bundle and would let every section start its own\n * interval, which would bring back the drift this module exists to prevent. The sentinel\n * declaration near the top of the file explains that reasoning in full.\n *\n * The immediate tick deliberately sits outside the guard so it runs on every call rather than only\n * the first. Some sections paint their starting value themselves when they connect, but others set\n * their displayed time only inside their tick listener. A section of that second kind that connects\n * after the interval is already running would otherwise show an empty clock for up to a second,\n * until the next regular beat arrives. Firing one immediate tick per caller hands each one a value\n * straight away. The extra repaints this causes are cheap because a section only touches the DOM\n * when its displayed value actually changed.\n */\nexport function startTimer() {\n  setTimeout(tick, 0);\n\n  if (!window.countdown_timer_interval_started) {\n    window.countdown_timer_interval_started = true;\n    setInterval(tick, 1000);\n  }\n}\n\n/**\n * Emit a single tick event stamped with the current time.\n *\n * This is the callback for both the one-shot immediate timeout and the recurring interval that\n * startTimer sets up. The order in which those two fire never matters. Each event carries its own\n * timestamp, and every listener recomputes its whole display from whatever timestamp it receives,\n * holding no state from one tick to the next. So a tick that arrives out of order, or an extra tick\n * from a second caller, at worst causes one redundant repaint, which a section ignores when its\n * displayed value has not actually changed. That is also why there is no timer id to capture or\n * clear here. The immediate timeout has already elapsed by the time this runs, and the interval is\n * meant to run for the life of the page.\n */\nfunction tick() {\n  const event = new CustomEvent<TimerTickEvent['detail']>('timer-tick', {\n    detail: {\n      timestamp: Date.now()\n    }\n  });\n  dispatchEvent(event);\n}\n\n/**\n * This event is fired per tick of the countdown timer. It represents one tick. It captures the\n * precise point in time at which the tick occurred.\n *\n * Events have a native timeStamp property, which at first glance suggests this does not need a\n * custom property as a simple Event would suffice. However, event.timeStamp is not an epoch, it is\n * a high resolution timestamp relative to the page load. Listeners typically compare this event's\n * timestamp to Date.now(). In order to do that comparison using the native event.timeStamp, they\n * have to use performance.timeOrigin + event.timeStamp. We have seen the performance global have\n * issues, the comparison involves more boilerplate, and there is a mental tax on using it because\n * of its subtleties and misleading name. Not to mention the fact that most people write \"timestamp\"\n * in all lowercase but some camel case fanatic thought better. Using a custom property actually\n * reduces the complexity despite its reinvention of the wheel.\n *\n * The name \"timestamp\" conventionally refers to an ISO string. However, given that the native APIs\n * have adopted the term (e.g. the DOM High Resolution Time API), we are stuck with the bad name in\n * the browser setting, and must live with it.\n */\ntype TimerTickEvent = CustomEvent<{\n  /**\n   * The precise point in time at which the tick occurred. Measures as milliseconds since epoch.\n   *\n   * Despite the name, this has nothing to do with event.timeStamp. Do not confuse the two.\n   *\n   * Despite the name, this is not an ISO string. It is a number representing milliseconds since\n   * epoch.\n   */\n  timestamp: number;\n}>;\n\ndeclare global {\n  interface WindowEventMap {\n    'timer-tick': TimerTickEvent;\n  }\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { startTimer, computeTimeLeft } from '../../../lib/countdown-timer.js';\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 shopper can dismiss it, and it hides itself once the countdown expires.\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     * If false the entire clock and the \"Limited time\" badge will disappear\n     */\n    clockShow: 'false' | 'true';\n  };\n\n  private endTimestamp: number | null = null;\n  private onCloseClickedBound = this.onCloseClicked.bind(this);\n  private onTimerTickBound = this.onTimerTick.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    if (this.dataset.clockEndTime) {\n      this.endTimestamp = parseInt(this.dataset.clockEndTime, 10) * 1000;\n    }\n\n    startTimer();\n    addEventListener('timer-tick', this.onTimerTickBound);\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    removeEventListener('timer-tick', this.onTimerTickBound);\n\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    const timer = this.shadowRoot.querySelector<HTMLElement>('.timer');\n    if (this.dataset.clockShow === 'true') {\n      if (this.shouldShowClock()) {\n        // the clock should be displayed - it is active\n        timer.hidden = false;\n      } else {\n        // the clock was set to be displayed but the message expired already hide the entire banner\n        this.hideBanner();\n      }\n    } else {\n      // the clock was disabled but we still have a message to show - hide the timer\n      timer.hidden = true;\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    this.renderClock(Date.now());\n  }\n\n  /**\n   * We have a few requirements to show the countdown clock. Calculate the decision\n   */\n  private shouldShowClock() {\n    const currentTimestamp = Date.now();\n\n    let endTimestamp = 0;\n    if (this.dataset.clockEndTime) {\n      endTimestamp = parseInt(this.dataset.clockEndTime, 10) * 1000;\n    }\n\n    return this.dataset.clockShow === 'true' && endTimestamp >= currentTimestamp;\n  }\n\n  private onTimerTick(event: WindowEventMap['timer-tick']) {\n    this.renderClock(event.detail.timestamp);\n  }\n\n  private renderClock(timestamp: number) {\n    const timer = this.shadowRoot.querySelector<HTMLElement>('.timer');\n    if (!this.shouldShowClock()) {\n      if (timer) {\n        timer.hidden = true;\n      }\n\n      return;\n    }\n\n    const timeLeft = computeTimeLeft(timestamp, this.endTimestamp);\n\n    this.setClockSegmentValue('.days', timeLeft.days);\n    this.setClockSegmentValue('.hours', timeLeft.hours);\n    this.setClockSegmentValue('.minutes', timeLeft.minutes);\n    this.setClockSegmentValue('.seconds', timeLeft.seconds);\n\n    timer?.setAttribute('aria-label', timeLeft.text);\n  }\n\n  private setClockSegmentValue(selector: string, value: number) {\n    const stringValue = value < 10 ? '0' + value : value.toString();\n    const element = this.shadowRoot.querySelector(selector);\n    if (element && element.textContent !== stringValue) {\n      element.textContent = stringValue;\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 users search query which is displayed inline the\n * search preview drawer.\n */\nclass SearchProductPreview extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Product thumb URL\n     */\n    imageUrl: string;\n\n    /**\n     * Product title\n     */\n    title: string;\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.render();\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    const image = this.shadowRoot.querySelector('img');\n    image.src = this.dataset.imageUrl;\n    image.alt = this.dataset.title;\n\n    const title = this.shadowRoot.querySelector('span.title');\n    title.textContent = this.dataset.title;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'search-product-preview': SearchProductPreview;\n  }\n}\n\nif (!customElements.get('search-product-preview')) {\n  customElements.define('search-product-preview', SearchProductPreview);\n}\n","const rnds8 = new Uint8Array(16);\nexport default function rng() {\n    return crypto.getRandomValues(rnds8);\n}\n","import validate from './validate.js';\nconst byteToHex = [];\nfor (let i = 0; i < 256; ++i) {\n    byteToHex.push((i + 0x100).toString(16).slice(1));\n}\nexport function unsafeStringify(arr, offset = 0) {\n    return (byteToHex[arr[offset + 0]] +\n        byteToHex[arr[offset + 1]] +\n        byteToHex[arr[offset + 2]] +\n        byteToHex[arr[offset + 3]] +\n        '-' +\n        byteToHex[arr[offset + 4]] +\n        byteToHex[arr[offset + 5]] +\n        '-' +\n        byteToHex[arr[offset + 6]] +\n        byteToHex[arr[offset + 7]] +\n        '-' +\n        byteToHex[arr[offset + 8]] +\n        byteToHex[arr[offset + 9]] +\n        '-' +\n        byteToHex[arr[offset + 10]] +\n        byteToHex[arr[offset + 11]] +\n        byteToHex[arr[offset + 12]] +\n        byteToHex[arr[offset + 13]] +\n        byteToHex[arr[offset + 14]] +\n        byteToHex[arr[offset + 15]]).toLowerCase();\n}\nfunction stringify(arr, offset = 0) {\n    const uuid = unsafeStringify(arr, offset);\n    if (!validate(uuid)) {\n        throw TypeError('Stringified UUID is invalid');\n    }\n    return uuid;\n}\nexport default stringify;\n","import rng from './rng.js';\nimport { unsafeStringify } from './stringify.js';\nfunction v4(options, buf, offset) {\n    if (!buf && !options && crypto.randomUUID) {\n        return crypto.randomUUID();\n    }\n    return _v4(options, buf, offset);\n}\nfunction _v4(options, buf, offset) {\n    options = options || {};\n    const rnds = options.random ?? options.rng?.() ?? rng();\n    if (rnds.length < 16) {\n        throw new Error('Random bytes length must be >= 16');\n    }\n    rnds[6] = (rnds[6] & 0x0f) | 0x40;\n    rnds[8] = (rnds[8] & 0x3f) | 0x80;\n    if (buf) {\n        offset = offset || 0;\n        if (offset < 0 || offset + 16 > buf.length) {\n            throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);\n        }\n        for (let i = 0; i < 16; ++i) {\n            buf[offset + i] = rnds[i];\n        }\n        return buf;\n    }\n    return unsafeStringify(rnds);\n}\nexport default v4;\n","// Shopify serves resized images two ways. The old way splices the size into the filename, so\n// `foo.jpg` becomes `foo_120x120.jpg`, and that is what the deprecated `img_url` Liquid filter\n// emits. It is also what the retired slate fork alongside this file does. The current way, what the\n// `image_url` filter emits and what Hydrogen's image loader builds from JS, is query params on the\n// original URL. Both come back byte identical, so this is the same images through the shape Shopify\n// still maintains.\n\ntype Crop = 'bottom' | 'center' | 'left' | 'right' | 'top';\n\ntype Size = 'compact' | 'grande' | 'icon' | 'large' | 'medium' | 'original' | 'pico' | 'small' |\n  'thumb';\n\n// Shopify's named sizes are all square, and these are the pixel values its docs give for each one.\n// There is no entry for master because master means the original image at whatever size it happens\n// to be, and asking for no dimensions at all already gets you that.\nconst namedSizePixels: Record<Size, number> = {\n  compact: 160,\n  grande: 600,\n  icon: 32,\n  large: 480,\n  medium: 240,\n  original: 1024,\n  pico: 16,\n  small: 100,\n  thumb: 50\n};\n\nexport interface ImageUrlOptions {\n  /**\n   * Which part of the image to keep when the requested dimensions do not match its aspect ratio.\n   */\n  crop?: Crop;\n\n  /**\n   * Requested height in pixels.\n   */\n  height?: number;\n\n  /**\n   * Encoding quality from 10 to 90. Leave it out to let Shopify pick one for the output format.\n   */\n  quality?: number;\n\n  /**\n   * One of Shopify's named sizes, used only when neither a width nor a height is given.\n   */\n  size?: Size;\n\n  /**\n   * Requested width in pixels.\n   */\n  width?: number;\n}\n\n/**\n * Builds a URL for a Shopify CDN image at a particular size.\n *\n * Pass a width, a height, or both. You can pass one of Shopify's named sizes instead, and with none\n * of the three you get the original image back. Shopify tops out at 5760 in either direction, and\n * asking for something bigger than the original just hands back the original rather than failing.\n *\n * The returned URL is protocol relative so that nothing gets forced onto `http`.\n *\n * There is no format option. Shopify's own only accepts `jpg` and `pjpg`, and it negotiates webp\n * and avif off the `Accept` header anyway.\n *\n * @see https://shopify.dev/docs/api/liquid/filters/image_url\n * @example build('//langehair.com/cdn/shop/files/Foo.jpg?v=1765908254', { width: 120 })\n */\nexport function build(src: string, options: ImageUrlOptions) {\n  // Real image URLs on the site are protocol relative, and URL needs a scheme to parse anything. So\n  // we bolt one on for parsing and take it back off at the end.\n  const absolute = src.startsWith('//') ? `https:${src}` : src;\n  const url = new URL(absolute);\n\n  // A named size is only there for callers that have no dimensions of their own, so an explicit\n  // width or height wins over it. The named sizes are square, so the one number covers both.\n  const named = options.size && !options.width && !options.height ?\n    namedSizePixels[options.size] :\n    undefined;\n  const width = options.width || named;\n  const height = options.height || named;\n\n  if (width) {\n    url.searchParams.set('width', `${width}`);\n  }\n\n  if (height) {\n    url.searchParams.set('height', `${height}`);\n  }\n\n  if (options.crop) {\n    url.searchParams.set('crop', options.crop);\n  }\n\n  if (options.quality) {\n    url.searchParams.set('quality', `${options.quality}`);\n  }\n\n  return url.toString().replace(/^https?:/, '');\n}\n","/**\n * Throws an assertion error if the given condition is falsy.\n *\n * @param expression any expression, although one that evaluates to true or false is preferred by\n * convention\n * @param message optional error text\n */\nexport function assert(expression: unknown, message?: string) {\n  if (!expression) {\n    throw new AssertionError(message);\n  }\n}\n\nexport class AssertionError extends Error {\n  constructor(message = 'Assertion error') {\n    super(message);\n    this.name = this.constructor.name;\n  }\n}\n","import { assert } from '@integrabeauty/assert';\n\n/**\n * Input options to searching Shopify.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/predictive-search#get-locale-search-suggest\n */\nexport interface SuggestInput {\n  /**\n   * The default fields searched on are title, product_type, variants.title, and vendor. For the\n   * best search experience, you should search on the default field set.\n   *\n   * There are constraints for translated storefronts, see the docs.\n   */\n  fields?: Field[];\n\n  /**\n   * Limits the number of results based on limit_scope. The value can range from 1 to 10, and the\n   * default is 10.\n   */\n  limit?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;\n\n  limit_scope?: 'all' | 'each';\n\n  prefix?: string;\n\n  /**\n   * The search query.\n   */\n  query: string;\n\n  /**\n   * Specifies the type of results requested.\n   *\n   * The following are the accepted values, which can be combined in a comma-separated list:\n   *\n   * * product\n   * * page\n   * * article\n   * * collection\n   * * query\n   *\n   * The default value is query,product,collection,page.\n   *\n   * To change the default value, you can use Search Settings in the Search & Discovery app.\n   */\n  resource_types?: ResourceType[];\n\n  section_id?: string;\n\n  /**\n   * Specifies whether to display results for unavailable products.\n   *\n   * The following are the accepted values:\n   *\n   * * show: Show unavailable products.\n   * * hide: Hide unavailable products.\n   * * last: Show unavailable products below other matching results.\n   *\n   * The default value is last.\n   *\n   * To change the default value, you can use Search Settings in the Search & Discovery app.\n   *\n   * This parameter is only applicable to type product.\n   */\n  unavailable_products?: 'hide' | 'last' | 'show';\n}\n\nexport type ProductField = 'body' | 'product_type' | 'tag' | 'title' | 'variants.barcode' |\n  'variants.sku' | 'variants.title' | 'vendor';\nexport type PageField = 'author' | 'body' | 'title';\nexport type ArticleField = 'author' | 'body' | 'tag' | 'title';\nexport type CollectionField = 'title';\nexport type Field = ArticleField | CollectionField | PageField | ProductField;\n\nexport type ResourceType = 'article' | 'collection' | 'page' | 'product' | 'query';\n\nexport interface Product {\n  /**\n   * Whether the product is available for sale (e.g. in stock)\n   */\n  available: boolean;\n\n  /**\n   * HTML formatted product description\n   */\n  body: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  compare_at_price_max: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  compare_at_price_min: string;\n\n  featured_image: FeaturedImage;\n\n  /**\n   * Shopify product handle\n   *\n   * @example \"my-product-slug\"\n   */\n  handle: string;\n\n  /**\n   * Shopify product id\n   *\n   * @example 123324324\n   */\n  id: number;\n\n  /**\n   * Product image url\n   *\n   * @example \"https://cdn.shopify.com/.../image.jpg?v=1234\"\n   */\n  image: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  price: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  price_max: string;\n\n  /**\n   * @example \"30.00\"\n   */\n  price_min: string;\n\n  /**\n   * @example [\"best-sellers\", \"hair-care-routine\"]\n   */\n  tags: string[];\n\n  /**\n   * @example \"My Product\"\n   */\n  title: string;\n\n  /**\n   * @example \"hair-care\"\n   */\n  type: string;\n\n  /**\n   * @example \"/products/my-handle?_pos=1&_psq=hair&_ss=e&_v=1.0\"\n   */\n  url: string;\n\n  /**\n   * A product variant is returned only when the query matches terms specific to the variant title.\n   * Only the variant with the most matching terms is returned. When a variant is returned, the\n   * following product_object fields will match those of the variant:\n   *\n   * * featured_image\n   * * image\n   * * url\n   *\n   * cspell:ignore snowbo\n   *\n   * For example, a store has a snowboard with a blue variant and a light blue variant. If you\n   * search for snowbo, then only the snowboard product is returned. However, if you search for\n   * light blue snowbo, then the snowboard product is returned with the light blue variant.\n   */\n  variants: Variant[];\n\n  /**\n   * @example \"my company name\"\n   */\n  vendor: string;\n}\n\nexport interface Variant {\n  available: boolean;\n  /**\n   * might be wrong\n   */\n  compare_at_price: number;\n\n  featured_image: FeaturedImage;\n\n  id: number;\n  image: string;\n  /**\n   * might be wrong\n   */\n  price: number;\n  title: string;\n  url: string;\n}\n\n/**\n * A query represents an auto-complete suggestion. For example, based on what someone has typed in,\n * zero or more queries will be generated that represent more specific or similar queries to\n * consider using.\n */\nexport interface Query {\n  /**\n   * @example \"<mark>hair</mark><span>spray</span>\"\n   */\n  styled_text: string;\n\n  /**\n   * @example \"hairspray\"\n   */\n  text: string;\n\n  /**\n   * @example \"/search?_pos=1&_psq=hair&_ss=e&_v=1.0&q=hairspray\"\n   */\n  url: string;\n}\n\nexport interface FeaturedImage {\n  alt: string;\n  aspect_ratio: number;\n  height: number;\n  url: string;\n  width: number;\n}\n\nexport interface Collection {\n  body: string;\n  featured_image: FeaturedImage;\n  handle: string;\n  id: number;\n}\n\nexport interface Page {\n  author: string;\n  body: string;\n  handle: string;\n  id: number;\n  published_at: string;\n  title: string;\n  url: string;\n}\n\nexport interface Article {\n  author: string;\n  body: string;\n  handle: string;\n  id: number;\n  image: string;\n  published_at: string;\n  summary_html: string;\n  tags: string[];\n  title: string;\n  url: string;\n}\n\nexport interface ShopifySuggestResponseBody {\n  resources: {\n    results: {\n      articles: Article[];\n      collections: Collection[];\n      pages: Page[];\n      products: Product[];\n      queries: Query[];\n    };\n  };\n}\n\nexport interface ShopifyErrorResponseBody {\n  description: string;\n  message: string;\n  status: string;\n}\n\n/**\n * Represents the response body.\n *\n * In a full implementation that parsed the response body of error responses, this should actually\n * be a union of ShopifySuggestResponseBody and also ShopifyErrorResponseBody. However, we are not\n * actually leveraging ShopifyErrorResponseBody. We are just leaving the type defined for\n * documentation purposes. The promise returned by suggest only ever resolves on success so we can\n * limit the output type to the success type.\n */\nexport type ResponseBody = ShopifySuggestResponseBody;\n\n/**\n * Send a search request to the Shopify api and receive a search response.\n *\n * If section id is provided, this returns a string. If section id is not provided, this returns an\n * object.\n *\n * I did not bother to make the output type derived from the presence of the section id. For now the\n * output type has to be specified via the generic value if the output should be interpreted as a\n * string. Without specifying the generic parameter this defaults to producing an object.\n *\n * @param input various search criteria\n */\nexport async function suggest<OutputType = ResponseBody>(input: SuggestInput) {\n  assert(typeof input.query === 'string' && input.query.length, 'Invalid input');\n\n  let path = Shopify.routes.root + 'search/suggest';\n  if (!input.section_id) {\n    path += '.json';\n  }\n\n  const url = new URL(path, location.href);\n  url.searchParams.set('q', input.query);\n\n  if (input.section_id) {\n    url.searchParams.set('section_id', input.section_id);\n  }\n\n  if (Number.isInteger(input.limit)) {\n    url.searchParams.set('resources[limit]', input.limit.toString());\n  }\n\n  if (input.limit_scope) {\n    url.searchParams.set('resources[limit_scope]', input.limit_scope);\n  }\n\n  if (Array.isArray(input.resource_types) && input.resource_types.length > 0) {\n    url.searchParams.set('resources[type]', input.resource_types.join(','));\n  }\n\n  if (input.prefix) {\n    url.searchParams.set('options[prefix]', input.prefix);\n  }\n\n  if (Array.isArray(input.fields) && input.fields.length > 0) {\n    url.searchParams.set('resources[options][fields]', input.fields.join(','));\n  }\n\n  if (input.unavailable_products) {\n    url.searchParams.set('resources[options][unavailable_products]', input.unavailable_products);\n  }\n\n  let response: Response;\n  try {\n    response = await fetch(url);\n  } catch (error) {\n    const suggestError = new SuggestError((<Error>error).message, { cause: error });\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // If not using section id, fetch json response body for errors including 422, 417, and 429. See\n  // https://shopify.dev/docs/api/ajax/reference/predictive-search#invalid-parameter-error. We could\n  // fetch and then parse these response bodies, but basically there is no need except for maybe the\n  // description hint for 422. Those kinds of errors would be obvious and permanent programming\n  // errors though so we would quickly get those resolves. So just recording the status in the\n  // thrown error is sufficient. We do not really need to parse the body.\n\n  if (!response.ok) {\n    const suggestError = new SuggestError(\n      `Received status ${response.status} fetching search results`);\n    suggestError.status = response.status;\n    suggestError.input = input;\n    suggestError.url = url.toString();\n\n    // Try to get the Retry-After header value and embed this in the thrown error so that we grant\n    // callers the ability to check it.\n    //\n    // See https://shopify.dev/docs/api/ajax/reference/predictive-search#too-many-requests.\n    if (response.status === 429) {\n      const retryAfter = response.headers.get('Retry-After');\n      const retryAfterSeconds = parseInt(retryAfter, 10);\n      if (Number.isInteger(retryAfterSeconds)) {\n        suggestError.retry_after = retryAfterSeconds;\n      }\n    }\n\n    // For certain errors that have documented response bodies, try to fetch the response body so\n    // that we can get more details into the error.\n    if ([417, 422, 429, 500].includes(response.status)) {\n      try {\n        const responseBody = await response.json();\n        suggestError.response_body = responseBody;\n      } catch (error) {\n        console.log(error);\n      }\n    }\n\n    throw suggestError;\n  }\n\n  let responseText: string;\n\n  try {\n    responseText = await response.text();\n  } catch (error) {\n    const suggestError = new SuggestError((<Error>error).message, { cause: error });\n    suggestError.input = input;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // If section id was provided, the response body is a string containing html formatted text that\n  // was server side rendered according to the specified section liquid and the search results. We\n  // just return the string. The caller may have a preference on how to parse the html or whether to\n  // parse it at all, so we do not parse on the caller's behalf.\n\n  if (input.section_id) {\n    return <OutputType>responseText;\n  }\n\n  // Without a section id we are fetching from the suggest.json endpoint so we parse the results and\n  // do some basic validation of the parsed object.\n\n  // Shopify occasionally answers this endpoint with a success status and an empty body, typically a\n  // dropped or truncated connection in a mobile in-app browser. We have verified that refetching\n  // one of these same query urls ourselves returns a full, valid result set, so the empty body is a\n  // visitor-side truncation rather than a broken endpoint or a missing resource. There is nothing\n  // to parse and no results to show, so degrade to an empty result set rather than throwing on the\n  // empty input. The caller renders an empty preview (falling back to search history) instead of an\n  // error, and the next keystroke issues a fresh request that normally succeeds.\n\n  if (!responseText) {\n    // Typing the literal as the concrete response body shape gets it checked at construction, then\n    // the return asserts to the caller-chosen generic the same way the other return sites do, since\n    // a concrete value is not assignable to an unconstrained type parameter on its own.\n\n    const responseBody: ResponseBody = {\n      resources: {\n        results: { articles: [], collections: [], pages: [], products: [], queries: [] }\n      }\n    };\n\n    return <OutputType>responseBody;\n  }\n\n  let responseBody;\n\n  try {\n    responseBody = JSON.parse(responseText);\n  } catch (error) {\n    const suggestError = new SuggestError((<Error>error).message, { cause: error });\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.response_body = responseText;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  // We expect the type of the response body object to always be the documented type. However,\n  // Shopify could change this out from under us at any time, so we keep this as a runtime check\n  // instead of a hard cast.\n\n  if (!isShopifySuggestResponseBody(responseBody)) {\n    const suggestError = new SuggestError('Unexpected response body');\n    suggestError.input = input;\n    suggestError.status = response.status;\n    suggestError.response_body = responseBody;\n    suggestError.url = url.toString();\n    throw suggestError;\n  }\n\n  return <OutputType>responseBody;\n}\n\nexport function isShopifySuggestResponseBody(value: any): value is ShopifySuggestResponseBody {\n  return typeof value === 'object' && value !== null && typeof value.resources === 'object' &&\n    value.resources !== null;\n}\n\n/**\n * Represents a Shopify Suggest API error.\n */\nexport class SuggestError extends Error {\n  /**\n   * The input to the suggest function to help with diagnosing what caused the error when it might\n   * be related to the input\n   */\n  public declare input: SuggestInput;\n\n  /**\n   * Response status code\n   *\n   * @example 200\n   */\n  public declare status: number;\n\n  /**\n   * The response body. This could be a string or an object or something else.\n   */\n  public declare response_body: any;\n\n  /**\n   * If the status is 429, indicating a rate limit error, then the suggest function also tries to\n   * access the Retry-After header and stash the value in the error.\n   *\n   * This is the number of seconds. The caller of suggest should make sure to delay any future calls\n   * by at least this amount of time to prevent continuing to receive more 429s.\n   */\n  public declare retry_after: number;\n\n  /**\n   * The request or response url involved in the request that erred.\n   */\n  public declare url: string;\n\n  constructor(message?: string, options?: ErrorOptions) {\n    super(message, options);\n    this.name = this.constructor.name;\n  }\n}\n","const storageKey = 'search_history';\n\n/**\n * Whether search history may be stored and shown for the given Shopify market handle.\n *\n * The UK market does not get search history. This only withholds it on an affirmative match, so an\n * unknown or missing market still gets history.\n */\nexport function isAllowedInMarket(market: string) {\n  return market !== 'gb';\n}\n\n/**\n * Appends a single value to search history\n *\n * This function is infallible.\n */\nexport function append(value: string) {\n  if (typeof value === 'string' && value.length > 0) {\n    const values = read();\n    values.push(value);\n    write(values);\n  }\n}\n\n/**\n * Saves queries to search history.\n *\n * This does some preprocessing of the array before persisting.\n *\n * This does not error when the write fails.\n */\nexport function write(queries: string[]) {\n  try {\n    writeUnsafe(queries);\n  } catch (error) {\n    console.debug(error);\n  }\n}\n\nfunction writeUnsafe(queries: string[]) {\n  let values: string[] = [];\n  for (const query of queries) {\n    if (query) {\n      let value = query;\n      value = value.trim();\n      value = value.replace(/\\s+/g, ' ');\n      value = value.slice(0, 25);\n      value = value.toLowerCase();\n      if (!values.includes(value)) {\n        values.push(value);\n      }\n    }\n  }\n\n  values = values.slice(-10);\n\n  localStorage.setItem(storageKey, JSON.stringify(values));\n}\n\nfunction reset() {\n  try {\n    localStorage.removeItem(storageKey);\n  } catch (error) {\n    console.debug(error);\n  }\n}\n\nfunction isStringArray(value: any): value is string[] {\n  return Array.isArray(value) && value.every(item => typeof item === 'string');\n}\n\n/**\n * Attempts to read search history from client side storage. This method is infallible. On error,\n * the error is logged and an empty array is returned.\n */\nexport function read() {\n  try {\n    const serialized = localStorage.getItem(storageKey);\n    if (serialized) {\n      const values = <string[]>JSON.parse(serialized);\n      if (isStringArray(values)) {\n        return values;\n      }\n    }\n  } catch (error) {\n    // Prevent poison pill\n    reset();\n    console.debug(error);\n  }\n\n  return [];\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';\nimport { append, isAllowedInMarket, read } from './search-history.js';\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     * The handle of the visitor's current Shopify market, e.g. \"us\" or \"gb\". May be empty when the\n     * market could not be determined.\n     */\n    marketHandle: 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 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 onSearchHistoryClickBound = this.onSearchHistoryClick.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('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('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    const iconReset = this.shadowRoot.querySelector('.icon-close');\n\n    if (this.dataset.isSearchPage === 'true') {\n      iconReset.classList.add('visible');\n\n      const urlParams = new URLSearchParams(location.search);\n      const searchTerm = urlParams.get('q');\n      searchInput.value = searchTerm;\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 appendSearchHistory(query: string) {\n    if (isAllowedInMarket(this.dataset.marketHandle)) {\n      append(query);\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\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\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  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 (searchInput.value.length === 0) {\n      return;\n    }\n\n    if (event.key === 'Escape') {\n      this.hideSearchPreview();\n    } else if (searchInput.value.length >= 3) {\n      if (searchInput.value.length >= 3) {\n        this.suggest(searchInput.value).catch(handleSuggestError);\n      }\n    } else if (searchInput.value.length >= 1) {\n      this.renderPreviewPane(searchInput.value, [], []);\n      this.showSearchPreview();\n    } else {\n      this.renderPreviewPane('', [], []);\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(event: MouseEvent) {\n    if (event.target instanceof Element && !event.target.closest('.results-container')) {\n      this.hideSearchPreview();\n    }\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    this.appendSearchHistory(query);\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    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    const searchHistoryItemsToRender = 10 - (filteredQueries.length + products.length);\n\n    if (searchHistoryItemsToRender > 0 && isAllowedInMarket(this.dataset.marketHandle)) {\n      // Retrieve search history and filter out any items matching current user initiate query\n      let searchHistory = read();\n      searchHistory = searchHistory.filter(q => q !== query);\n\n      // Retrieve array of strings from Query object to compare against search history\n      const suggestedTermsArray = filteredQueries.map(q => q.text);\n\n      // Filter out all search history items that are already included in the suggestions\n      searchHistory = searchHistory.filter(q => !suggestedTermsArray.includes(q));\n\n      // Filter out all search history items that are a subset of a higher specificity user searched\n      // term\n      searchHistory = searchHistory.filter(q => !query.includes(q));\n\n      if (searchHistory.length > searchHistoryItemsToRender) {\n        // Slice required amount of history items required to render 10 total preview items\n        searchHistory = searchHistory.slice(-searchHistoryItemsToRender);\n      }\n\n      if (searchHistory.length > 0) {\n        const liHeaderEl = document.createElement('li');\n        liHeaderEl.textContent = 'Search History';\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 search of searchHistory) {\n          const liEl = this.createSearchHistoryListElement(search, position, query);\n          previewEl.appendChild(liEl);\n          position++;\n        }\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    this.appendSearchHistory(input.value);\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    this.appendSearchHistory(query);\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 }\n    });\n    this.dispatchEvent(searchEvent);\n  }\n\n  private onSearchHistoryClick(event: MouseEvent) {\n    if (event.target instanceof HTMLAnchorElement) {\n      type Detail = SearchedEvent['detail'];\n      const searchEvent = new CustomEvent<Detail>('searched', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          event_id: uuidv4(),\n          query: event.target.dataset.searchTerm\n        }\n      });\n      this.dispatchEvent(searchEvent);\n    }\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    this.appendSearchHistory(query);\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 }\n    });\n    this.dispatchEvent(searchEvent);\n  }\n\n  private createSearchHistoryListElement(query: string, position: number, currentQuery: string) {\n    const anchor = document.createElement('a');\n\n    anchor.href = createSuggestedSearchTermUrl(position, query, currentQuery);\n    anchor.textContent = query;\n    anchor.dataset.searchTerm = query;\n    anchor.classList.add('search-preview-link');\n    anchor.ariaLabel = `See all results for \"${query}\"`;\n    anchor.addEventListener('click', this.onSearchHistoryClickBound);\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 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 * This listener prevents propagation of the click event when a user focuses on the search input\n * field. This is to prevent the event from bubbling up to the document click listener, as this\n * logic will close the search preview pane if the click was outside of the preview pane bounds.\n * Although this is the desired behavior, the one exception is the input field which should not\n * close the search preview pane if it is open and inversely, allow the on focus event handler\n * determine if there was a previously active search preview results and search term and show the\n * search preview results if so.\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  }\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\n  private onSearchClosed() {\n    if (this.classList.contains('search-page')) {\n      return;\n    }\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\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'search-button': SearchButton;\n  }\n}\n\nif (!customElements.get('search-button')) {\n  customElements.define('search-button', SearchButton);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the profile button. Redirect to log in page or account page when clicked.\n */\nclass ProfileButton extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * URL to navigate to login page\n     */\n    accountLoginUrl: string;\n\n    /**\n     * URL to navigate to account page\n     */\n    accountUrl: string;\n\n    /**\n     * Whether the customer is logged in. The stylesheet reads this attribute too.\n     */\n    authenticated: 'false' | 'true';\n  };\n\n  private onButtonClickedBound = this.onButtonClicked.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.addEventListener('click', this.onButtonClickedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('click', this.onButtonClickedBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private onButtonClicked() {\n    if (this.dataset.authenticated === 'true') {\n      location.href = this.dataset.accountUrl;\n    } else {\n      location.href = this.dataset.accountLoginUrl;\n    }\n  }\n\n  private render() {\n    const button = this.shadowRoot.querySelector('button');\n\n    if (this.dataset.authenticated === 'true') {\n      button.setAttribute('aria-label', 'Go to your account page');\n    } else {\n      button.setAttribute('aria-label', 'Log in to your account');\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'profile-button': ProfileButton;\n  }\n}\n\nif (!customElements.get('profile-button')) {\n  customElements.define('profile-button', ProfileButton);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the hamburger icon. Opens the main menu drawer when clicked.\n */\nclass MobileMenuButton extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-opened'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Whether the menu is open\n     */\n    opened: 'false' | 'true' | null;\n  };\n\n  private onMobileMenuButtonClickedBound = this.onMobileMenuButtonClicked.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n    this.addEventListener('click', this.onMobileMenuButtonClickedBound);\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('click', this.onMobileMenuButtonClickedBound);\n  }\n\n  public attributeChangedCallback(_name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && oldValue !== newValue) {\n      this.render();\n    }\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    const button = this.shadowRoot.querySelector('button');\n\n    if (this.dataset.opened === 'true') {\n      button?.setAttribute('aria-expanded', 'true');\n      button?.setAttribute('aria-label', 'Close menu');\n    } else {\n      button?.setAttribute('aria-expanded', 'false');\n      button?.setAttribute('aria-label', 'Open menu');\n    }\n  }\n\n  private onMobileMenuButtonClicked(event: MouseEvent) {\n    event.stopPropagation();\n\n    if (this.dataset.opened === 'true') {\n      const menuCloseEvent = new Event('menu-close-requested', { bubbles: true, composed: true });\n      this.dispatchEvent(menuCloseEvent);\n    } else {\n      if (!(event.currentTarget instanceof HTMLElement)) {\n        return;\n      }\n\n      const focusEvent = new Event('focus-stack-push-requested', {\n        bubbles: true,\n        composed: true\n      });\n      event.currentTarget.dispatchEvent(focusEvent);\n\n      type MenuOpenRequestedDetail = WindowEventMap['menu-open-requested']['detail'];\n      const menuOpenEvent = new CustomEvent<MenuOpenRequestedDetail>('menu-open-requested', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          element_id: 'menu-panel-main-mobile'\n        }\n      });\n      this.dispatchEvent(menuOpenEvent);\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'mobile-menu-button': MobileMenuButton;\n  }\n}\n\nif (!customElements.get('mobile-menu-button')) {\n  customElements.define('mobile-menu-button', MobileMenuButton);\n}\n","import { ready } from '../../lib/dom-ready.js';\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    /**\n     * Announces that a menu panel has finished closing. Dispatched from the panel element itself\n     * and bubbles to window, so the panel is identified by the event target rather than a detail\n     * payload.\n     */\n    'menu-closed': Event;\n\n    /**\n     * Announces that a menu panel has finished opening. Dispatched from the panel element itself\n     * and bubbles to window, so the panel is identified by the event target rather than a detail\n     * payload.\n     */\n    'menu-opened': Event;\n  }\n}\n\n/**\n * In the liquid, menu-panels is an id on a basic div element. This is a custom interface that\n * attaches explicit dataset properties to the basic div element so that we have type safety.\n */\ninterface MenuPanelsElement extends HTMLElement {\n  readonly dataset: {\n    /**\n     * The id of the panel that is currently open. Deleted when the menu closes, so it is undefined\n     * whenever no panel is showing.\n     */\n    activePanel: string | undefined;\n  };\n}\n\nfunction onMenuOpenRequested(event: WindowEventMap['menu-open-requested']) {\n  // A request must name the panel to open; there is nothing to act on without one\n  if (!event.detail.element_id) {\n    return;\n  }\n\n  const menuPanelsElement = document.querySelector<MenuPanelsElement>('#menu-panels');\n  if (!menuPanelsElement) {\n    return;\n  }\n\n  const panel = menuPanelsElement.querySelector<HTMLElement>('#' + event.detail.element_id);\n\n  // If the id does not point to something in the dom, ignore the request.\n  if (!panel) {\n    return;\n  }\n\n  const activePanel = menuPanelsElement.dataset.activePanel;\n  const previousPanel = menuPanelsElement.querySelector<HTMLElement>('#' + activePanel);\n  if (previousPanel) {\n    previousPanel.hidden = true;\n    previousPanel.inert = true;\n  }\n\n  menuPanelsElement.dataset.activePanel = event.detail.element_id;\n\n  // close the search box if open\n  dispatchEvent(new Event('search-input-reset'));\n\n  const stickyHeader = document.querySelector('header .sticky-header');\n  stickyHeader.classList.add('menu-shown');\n\n  panel.inert = false;\n  panel.hidden = false;\n\n  const menuButtons = document.querySelectorAll<HTMLElement>('header .nav .menu-button');\n  for (const button of menuButtons) {\n    const controlledPanel = button.getAttribute('aria-controls');\n    if (controlledPanel === event.detail.element_id) {\n      button.setAttribute('aria-expanded', 'true');\n      button.classList.add('active');\n    } else if (controlledPanel) {\n      button.setAttribute('aria-expanded', 'false');\n      button.classList.remove('active');\n    }\n  }\n\n  // only dispatch modal-open-requested if there was no previous panel\n  if (!previousPanel) {\n    removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n    addEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n    const showEvent = new Event('modal-open-requested', { bubbles: true, composed: true });\n    menuPanelsElement.dispatchEvent(showEvent);\n  }\n\n  const firstFocusable = panel.querySelector<HTMLElement>('.menu-button');\n  if (firstFocusable) {\n    firstFocusable.focus({ focusVisible: true });\n  }\n\n  // The special offers carousel mounts while its panel is still hidden, so it can't measure its\n  // slides until now. The refresh is also where it works out which slides are off screen and makes\n  // them inert, so do not drop it.\n  const carousel = panel.querySelector('carousel-element');\n  if (carousel) {\n    carousel.dataset.refresh = 'true';\n  }\n\n  panel.dispatchEvent(new Event('menu-opened', { bubbles: true }));\n}\n\nfunction onMenuCloseRequested(_event: WindowEventMap['menu-close-requested']) {\n  removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n  const menuPanelsElement = document.querySelector<MenuPanelsElement>('#menu-panels');\n  const activePanel = menuPanelsElement?.dataset.activePanel;\n\n  const stickyHeader = document.querySelector('header .sticky-header');\n  stickyHeader.classList.remove('menu-shown');\n\n  const menuButtons = document.querySelectorAll<HTMLElement>('header .nav .menu-button');\n  for (const button of menuButtons) {\n    const controlledPanel = button.getAttribute('aria-controls');\n    if (controlledPanel) {\n      button.setAttribute('aria-expanded', 'false');\n      button.classList.remove('active');\n    }\n  }\n\n  if (activePanel) {\n    const panel = menuPanelsElement?.querySelector<HTMLElement>('#' + activePanel);\n    if (panel) {\n      panel.hidden = true;\n      panel.inert = true;\n    }\n\n    if (menuPanelsElement) {\n      delete menuPanelsElement.dataset.activePanel;\n    }\n\n    if (menuPanelsElement) {\n      const event = new Event('modal-close-requested', { bubbles: true, composed: true });\n      menuPanelsElement.dispatchEvent(event);\n    }\n\n    if (panel) {\n      panel.dispatchEvent(new Event('menu-closed', { bubbles: true }));\n    }\n  }\n}\n\n/**\n * Closes the open menu panel when the user performs a light-dismiss gesture (a click on the\n * backdrop or an escape key press). Rather than tearing the panel down directly, it re-dispatches\n * the header's own menu-close-requested event so every close funnels through the single close path\n * in onMenuCloseRequested. This listener is only attached while a panel is open.\n */\nfunction onModalDismissRequested(_event: WindowEventMap['modal-dismiss-requested']) {\n  dispatchEvent(new Event('menu-close-requested'));\n}\n\nfunction onHeaderClicked(_event: MouseEvent) {\n  // close the search box if open\n  dispatchEvent(new Event('search-input-reset'));\n\n  // close the menu if open\n  dispatchEvent(new Event('menu-close-requested'));\n}\n\nfunction onMenuButtonClicked(event: MouseEvent) {\n  if (!(event.currentTarget instanceof HTMLElement)) {\n    return;\n  }\n\n  const panelId = event.currentTarget.getAttribute('aria-controls');\n  if (!panelId) {\n    return;\n  }\n\n  event.stopPropagation();\n\n  const opened = event.currentTarget.getAttribute('aria-expanded') === 'true';\n  if (opened) {\n    dispatchEvent(new Event('menu-close-requested'));\n  } else {\n    const focusRequested = new Event('focus-stack-push-requested', {\n      bubbles: true,\n      composed: true\n    });\n    event.currentTarget.dispatchEvent(focusRequested);\n\n    requestMenuOpen(panelId);\n  }\n}\n\nfunction mobileBackButtonClicked(_event: MouseEvent) {\n  requestMenuOpen('menu-panel-main-mobile');\n}\n\nfunction requestMenuOpen(elementId: string) {\n  type Detail = WindowEventMap['menu-open-requested']['detail'];\n\n  const event = new CustomEvent<Detail>('menu-open-requested', {\n    detail: {\n      element_id: elementId\n    }\n  });\n\n  dispatchEvent(event);\n}\n\n// Swipe detection variables\n\nlet touchStartX = 0;\nlet touchEndX = 0;\nlet touchStartY = 0;\nlet touchEndY = 0;\nlet isSwiping = false;\n\nfunction onMenuPanelTouchStart(event: TouchEvent) {\n  touchStartX = event.touches[0].clientX;\n  touchStartY = event.touches[0].clientY;\n  isSwiping = false;\n}\n\nfunction onMenuPanelTouchMove(event: TouchEvent) {\n  touchEndX = event.touches[0].clientX;\n  touchEndY = event.touches[0].clientY;\n\n  const deltaX = Math.abs(touchStartX - touchEndX);\n  const deltaY = Math.abs(touchStartY - touchEndY);\n\n  if (deltaX > deltaY) {\n    // Horizontal swipe detected\n    isSwiping = true;\n  } else {\n    // Vertical swipe detected\n    isSwiping = false;\n  }\n}\n\nfunction onMenuPanelTouchEnd(event: TouchEvent) {\n  const dx = Math.abs(touchStartX - touchEndX);\n  const dy = Math.abs(touchStartY - touchEndY);\n\n  const minimumSwipeDistanceThreshold = 100;\n\n  if (event.target instanceof HTMLElement && !isCarouselElement(event.target)) {\n    // Only detect horizontal swipes where horizontal movement is dominant\n    if (dx > dy && dx >= minimumSwipeDistanceThreshold && isSwiping) {\n      if (typeof event.cancelable !== 'boolean' || event.cancelable) {\n        // do not trigger the default \"go back/forward\" action\n        event.preventDefault();\n      }\n\n      const panel = event.target.closest<HTMLElement>('.menu-panel');\n      if (panel) {\n        onHorizontalSwipe(panel);\n      }\n    }\n  }\n\n  isSwiping = false;\n}\n\nfunction isCarouselElement(element: HTMLElement) {\n  // Check if the element is a carousel or a child of a carousel\n  if (element instanceof HTMLElement) {\n    const carousel = element.closest('carousel-element');\n    if (carousel) {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nfunction onHorizontalSwipe(panel: HTMLElement) {\n  if (innerWidth >= 960) {\n    // If not mobile, do nothing\n    return;\n  }\n\n  const isMainMenu = panel.id === 'menu-panel-main-mobile';\n  if (isMainMenu) {\n    // If the main menu is swiped, close it\n    dispatchEvent(new Event('menu-close-requested'));\n  } else {\n    // else go back to the main menu\n    requestMenuOpen('menu-panel-main-mobile');\n  }\n}\n\nfunction onReady() {\n  const headerWrapper = document.querySelector<HTMLElement>('header .wrapper');\n  if (!headerWrapper) {\n    // this is not a mega menu header, probably a minimal header or other\n    return;\n  }\n\n  addEventListener('menu-open-requested', onMenuOpenRequested);\n  addEventListener('menu-close-requested', onMenuCloseRequested);\n\n  const menuPanels = document.querySelector<MenuPanelsElement>('#menu-panels');\n  if (menuPanels) {\n    // the menu panels are initially hidden with an inline style, so we need to remove it to make\n    // them work\n    menuPanels.style.removeProperty('display');\n\n    menuPanels.addEventListener('touchstart', onMenuPanelTouchStart, { passive: true });\n    menuPanels.addEventListener('touchmove', onMenuPanelTouchMove, { passive: true });\n    menuPanels.addEventListener('touchend', onMenuPanelTouchEnd, { passive: false });\n  }\n\n  headerWrapper.addEventListener('click', onHeaderClicked);\n\n  const menuButtons = document.querySelectorAll<HTMLElement>(\n    'header .nav .menu-button, #menu-panels .menu-button');\n  for (const button of menuButtons) {\n    button.addEventListener('click', onMenuButtonClicked);\n  }\n\n  const mobileBackButtons = document.querySelectorAll<HTMLElement>('#menu-panels button.title');\n  for (const button of mobileBackButtons) {\n    button.addEventListener('click', mobileBackButtonClicked);\n  }\n}\n\nready(onReady);\n","import { ready } from '../../lib/dom-ready.js';\n\n/**\n * Shopify's preview bar is the only thing that tells you you are looking at a preview theme, and\n * people close it and then forget. So they report a bug that is already fixed, or they QA the\n * staging theme thinking it is live. We stamp PREVIEW across the header logo as a second signal\n * that sticks around after the preview bar is gone.\n */\nfunction onReady() {\n  if (!Shopify.previewMode) {\n    return;\n  }\n\n  // 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__","tick","event","CustomEvent","detail","timestamp","Date","now","dispatchEvent","AnnouncementBar","HTMLElement","endTimestamp","onCloseClickedBound","this","onCloseClicked","bind","onTimerTickBound","onTimerTick","connectedCallback","initializeShadowRoot","shadowRoot","clockEndTime","parseInt","setTimeout","window","countdown_timer_interval_started","setInterval","render","disconnectedCallback","removeEventListener","closeButton","attachShadow","mode","innerHTML","timer","clockShow","shouldShowClock","hidden","hideBanner","badge","badgeShow","renderClock","currentTimestamp","timeLeft","secondsLeft","Math","round","seconds","minutesLeft","minutes","hoursLeft","hours","days","text","toString","join","computeTimeLeft","setClockSegmentValue","setAttribute","selector","value","stringValue","element","textContent","headerBanner","closest","customElements","get","define","SearchProductPreview","image","src","imageUrl","alt","title","rnds8","Uint8Array","byteToHex","i","push","slice","unsafeStringify","arr","offset","toLowerCase","dist_v4","options","buf","crypto","randomUUID","_ref","_options$random","rnds","random","rng","getRandomValues","length","Error","RangeError","_v4","namedSizePixels","compact","grande","icon","large","medium","original","pico","small","thumb","AssertionError","constructor","message","super","name","async","suggest","input","expression","assert","query","path","Shopify","routes","root","section_id","url","URL","location","href","response","responseText","responseBody","searchParams","set","Number","isInteger","limit","limit_scope","Array","isArray","resource_types","prefix","fields","unavailable_products","fetch","error","suggestError","SuggestError","cause","ok","status","retryAfter","headers","retryAfterSeconds","retry_after","includes","json","response_body","console","log","resources","results","articles","collections","pages","products","queries","JSON","parse","storageKey","isAllowedInMarket","market","append","values","read","trim","replace","localStorage","setItem","stringify","writeUnsafe","debug","write","serialized","getItem","every","item","removeItem","search_history_reset","ShopifySearchInput","observedAttributes","onFormSubmitBound","onFormSubmit","onFocusBound","onFocus","onDocumentClickBound","onDocumentClick","onSearchCloseBound","onSearchClose","onKeyUpBoundDebounced","func","timeout","_len","arguments","args","_key","clearTimeout","debounce","onKeyUp","onKeyDownBound","onKeyDown","onBlurBound","onBlur","onPageShowBound","onPageShow","onProductPreviewClickBound","onProductPreviewClick","onSearchForClickBound","onSearchForClick","onSearchHistoryClickBound","onSearchHistoryClick","onSuggestedQueryClickBound","onSuggestedQueryClick","suggestionActiveIndex","searchInput","onInputClick","attributeChangedCallback","oldValue","newValue","isConnected","iconReset","formEl","disabled","isSearchPage","classList","add","searchTerm","URLSearchParams","search","catch","handleSuggestError","renderPreviewPane","triggerFocus","focus","appendSearchHistory","marketHandle","showSearchPreview","scrollbarWidth","innerWidth","documentElement","clientWidth","style","setProperty","scrollLocked","ariaExpanded","headerEl","hideSearchPreview","backdrop","contains","remove","header","preventDefault","stopPropagation","resetEvent","Event","bubbles","composed","key","anchors","querySelectorAll","markSuggestionActiveAtIndex","_this$shadowRoot$quer","activeAnchor","click","updateSearchButton","searchButton","urlQueryParam","persisted","target","Element","searchFocused","activeElement","searchEvent","event_id","showPreviewDrawer","suggestions","previewEl","position","filteredQueries","filter","q","liHeaderEl","createElement","role","ariaLevel","tabIndex","appendChild","suggestedQuery","liEl","createSearchQuerySuggestionListElement","product","createProductSuggestionListElement","searchHistoryItemsToRender","searchHistory","suggestedTermsArray","map","createSearchHistoryListElement","userQuery","find","styled_text","anchorEl","currentQuery","preview","featured_image","absolute","startsWith","named","size","width","height","crop","quality","build","anchor","productHandle","origin","createSuggestedProductUrl","handle","ariaLabel","index","old","adjustedIndex","from","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","body","parentNode","scrollY","scrollTop","state","scrollPosition","scrollDirection","scrollTolerance","headerBannerVisible","headerBannerHeight","country","menuBar","menuBarHeight","contentPosition","getBoundingClientRect","bottom","passive","CartButton","delegatesFocus","cartCounter","quantity","itemsText","removeAttribute","button","isCartUpdatedEvent","onCartUpdated","cart","item_count","buttons","onCartButtonClick","cartButton","localName","focusEvent","searchResetEvent","menuCloseEvent","cartOpenEvent","source","designMode","floor","cart_lifecycle","queue","SearchButton","onSearchButtonClickedBound","onSearchButtonClicked","onSearchClosedBound","onSearchClosed","searchElement","ProfileButton","onButtonClickedBound","onButtonClicked","authenticated","accountUrl","accountLoginUrl","MobileMenuButton","onMobileMenuButtonClickedBound","onMobileMenuButtonClicked","_name","menuOpenEvent","element_id","onMenuOpenRequested","menuPanelsElement","panel","activePanel","previousPanel","inert","menuButtons","controlledPanel","getAttribute","onModalDismissRequested","showEvent","firstFocusable","focusVisible","carousel","refresh","onMenuCloseRequested","onHeaderClicked","onMenuButtonClicked","panelId","focusRequested","requestMenuOpen","mobileBackButtonClicked","elementId","touchStartX","touchEndX","touchStartY","touchEndY","isSwiping","onMenuPanelTouchStart","touches","clientX","clientY","onMenuPanelTouchMove","deltaX","abs","deltaY","onMenuPanelTouchEnd","dx","dy","isCarouselElement","cancelable","isMainMenu","id","onHorizontalSwipe","menuPanels","removeProperty","mobileBackButtons","previewMode","navigator","webdriver","logo","wash","createElementNS","label"],"sourceRoot":""}