{"version":3,"file":"cart-drawer-section.min.js","mappings":"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","sources":["webpack://lange/./lib/cart-event-queue.ts","webpack://lange/./lib/dom-ready.ts","webpack://lange/./sections/cart-drawer/cart-actions/index.ts","webpack://lange/./sections/cart-drawer/cart-drawer/index.ts","webpack://lange/../node_modules/@integrabeauty/cookie/src/index.ts","webpack://lange/./sections/cart-drawer/cart-gwp/index.ts","webpack://lange/./sections/cart-drawer/cart/index.ts","webpack://lange/../node_modules/@integrabeauty/shopify-ajax-api/src/cart.ts","webpack://lange/./lib/cart/digest.ts","webpack://lange/./lib/cart/equality.ts","webpack://lange/./lib/decorate-error.ts","webpack://lange/./lib/cart/error.ts","webpack://lange/../node_modules/@integrabeauty/assert/src/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/object.ts","webpack://lange/./lib/cart/discount/discount.ts","webpack://lange/./lib/untrusted-value.ts","webpack://lange/./lib/cart/state.ts","webpack://lange/./lib/perf.ts","webpack://lange/./lib/cart/transaction.ts","webpack://lange/./lib/cart/change.ts","webpack://lange/./lib/cart/remove.ts","webpack://lange/./lib/cart/update.ts","webpack://lange/./sections/cart-drawer/cart-line-item/index.ts","webpack://lange/./sections/cart-drawer/cart-line-items/index.ts","webpack://lange/./sections/cart-drawer/cart-line-items/index.html","webpack://lange/./sections/cart-drawer/cart-messages/index.ts","webpack://lange/./sections/cart-drawer/cart-totals/index.ts","webpack://lange/./sections/cart-drawer/cart-upsell-item/index.ts","webpack://lange/./sections/cart-drawer/cart-upsell-items/index.ts","webpack://lange/../node_modules/@shopify/theme-currency/currency.js","webpack://lange/./sections/cart-drawer/free-shipping-banner/index.ts","webpack://lange/./sections/cart-drawer/promo-goal-progress-v2/index.ts","webpack://lange/./lib/cart/discount/helper.ts","webpack://lange/./sections/cart-drawer/discount-input/index.ts","webpack://lange/./lib/cart/discount/apply.ts","webpack://lange/./lib/cart/discount/remove.ts","webpack://lange/./sections/cart-drawer/promo-goal/index.ts","webpack://lange/./sections/cart-drawer/promo-goal-progress/qualifying-subtotal.ts","webpack://lange/./sections/cart-drawer/promo-goal-progress/index.ts","webpack://lange/./lib/growthbook-feature.ts","webpack://lange/./sections/cart-drawer/shipping-experiment.ts","webpack://lange/./sections/cart-drawer/drawer-control.ts","webpack://lange/./sections/cart-drawer/upsell-modal/index.ts","webpack://lange/./sections/cart-drawer/checkout-started.ts"],"sourcesContent":["// 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","/**\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 type { CustomElement } from '@integrabeauty/custom-elements';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport { ready, type ReleaseHandle } from '../../../lib/dom-ready.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders a \"Continue to Checkout\" button and a \"Continue Shopping\" button that closes the side\n * cart.\n */\nclass CartActions extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Boolean represented as string to indicate if the close button should be hidden\n     */\n    hideCloseButton: string;\n\n    /**\n     * Boolean represented as string to indicate if query parameter to skip shop pay when navigating\n     * to checkout should be set to true.\n     */\n    skipShopPay: 'false' | 'true';\n  };\n\n  private checkoutInitSucceeded = false;\n  private onCheckoutButtonClickedBound = this.onCheckoutButtonClicked.bind(this);\n  private onCartTransactionStartedBound = this.onCartTransactionStarted.bind(this);\n  private onCartTransactionCompletedBound = this.onCartTransactionCompleted.bind(this);\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n  private onReadyBound = this.onReady.bind(this);\n  private onPageShowBound = this.onPageShow.bind(this);\n  private onContinueShoppingButtonClickedBound = this.onContinueShoppingButtonClicked.bind(this);\n\n  private declare releaseReady: ReleaseHandle;\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.releaseReady = ready(this.onReadyBound);\n\n    addEventListener('pageshow', this.onPageShowBound);\n  }\n\n  public disconnectedCallback() {\n    const checkoutButton = this.shadowRoot.querySelector<HTMLElement>('#checkout-button');\n    checkoutButton?.removeEventListener('click', this.onCheckoutButtonClickedBound);\n\n    const continueShopping = this.shadowRoot.querySelector<HTMLButtonElement>('#continue-shopping');\n    continueShopping?.removeEventListener('click', this.onContinueShoppingButtonClickedBound);\n\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\n    removeEventListener('cart-transaction-started', this.onCartTransactionStartedBound);\n    removeEventListener('cart-transaction-completed', this.onCartTransactionCompletedBound);\n    removeEventListener('pageshow', this.onPageShowBound);\n\n    if (this.releaseReady) {\n      this.releaseReady();\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 onReady() {\n    if (!this.isConnected) {\n      return;\n    }\n\n    const checkoutButton = this.shadowRoot.querySelector<HTMLElement>('#checkout-button');\n    checkoutButton.addEventListener('click', this.onCheckoutButtonClickedBound);\n\n    const continueButton = this.shadowRoot.querySelector<HTMLButtonElement>('#continue-shopping');\n    if (continueButton) {\n      if (this.dataset.hideCloseButton === 'true') {\n        continueButton.remove();\n      } else {\n        continueButton.addEventListener('click', this.onContinueShoppingButtonClickedBound);\n      }\n    }\n\n    addEventListener('cart-transaction-started', this.onCartTransactionStartedBound);\n    addEventListener('cart-transaction-completed', this.onCartTransactionCompletedBound);\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n  }\n\n  /**\n   * Handle a click on the continue to checkout button. It turns out that this is a little more\n   * complicated than it seems like it should be at first glance because we have some complicated\n   * race conditions and sad paths to handle.\n   *\n   * One issue is that the navigation sometimes takes a while. The current page remains visible\n   * sometimes right up until a large amount of the next page is loaded. Since the checkout page can\n   * take a terribly long time to load (e.g. 10+ seconds), it gives the user the impression they did\n   * not click on the button at all or that there was some error, and so they naturally click again.\n   * So another issue is that we have to carefully handle multiple clicks.\n   *\n   * Handling multiple clicks is also important because of what happens to the checkout cache during\n   * navigation. Users can edit the checkout on the checkout page. However, we do not keep the cache\n   * up to date on the checkout page. Therefore, the checkout service clears the cached checkout\n   * before redirecting. This causes an error if the user manages to click on the continue to\n   * checkout button more than once, after the cache has been cleared, but before the new page has\n   * loaded. We have to prevent those additional clicks from trying to trigger the redirect again\n   * because it leads to an error where the checkout is not found in the cache.\n   *\n   * Another subtle issue here is that disabling the button does not actually make it so that the\n   * button cannot be clicked or that its click handler is not invoked. So we have to actually check\n   * for whether the button is disabled. This is because a click that lands on a child element of a\n   * disabled button still bubbles up to and invokes this handler in Chrome and Safari (Firefox\n   * stops the bubbling). Form membership is not a factor; the button is a real button element.\n   *\n   * We only need to re-enable the button on error. On successful redirection we can leave the\n   * button state as disabled up until the browser changes the page.\n   *\n   * We do not have to log errors to Sentry here. Error handling and console logging is done by the\n   * checkout service.\n   *\n   * @see https://stackoverflow.com/questions/37109771\n   */\n  private onCheckoutButtonClicked(event: MouseEvent) {\n    const button = event.currentTarget;\n\n    // We need to narrow the type of the button variable from EventTarget to HTMLButtonElement. We\n    // want to do so without hard casting the type. The listener is bound to the element with the id\n    // #checkout-button and we are using currentTarget instead of target. This element is an actual\n    // button in the HTML. We want to later have access to the disabled property, which is not\n    // available on HTMLElement, but is on HTMLButtonElement. The correct expression here is to use\n    // instanceof HTMLButtonElement.\n\n    if (!(button instanceof HTMLButtonElement)) {\n      console.log('ignoring checkout button click because click is not on a button');\n      return;\n    }\n\n    // A disabled button does not receive clicks on itself, but a click on one of its child elements\n    // still bubbles up through the disabled button and reaches this listener in Chrome and Safari.\n    // So we manually enforce the disabled state instead of relying on the browser to prevent\n    // clicks.\n\n    if (button.disabled) {\n      console.log('ignoring checkout button click because button is disabled');\n      return;\n    }\n\n    try {\n      sessionStorage.setItem('checkout_button_clicked_epoch', Date.now().toString());\n    } catch (error) {\n      console.warn(error);\n    }\n\n    const modal = document.querySelector('upsell-modal');\n    if (modal) {\n      // query all upsells that are not hidden\n      const activeUpsells = modal.querySelectorAll<HTMLElement>('[data-product-id]:not([hidden])');\n      if (activeUpsells.length > 0 && !cart_drawer.upsell_modal_displayed) {\n        type Detail = WindowEventMap['cart-close-requested']['detail'];\n        const cartCloseEvent = new CustomEvent<Detail>('cart-close-requested', {\n          bubbles: true,\n          composed: true,\n          detail: {\n            animate: false,\n            source: 'checkout-upsell-handoff'\n          }\n        });\n        this.dispatchEvent(cartCloseEvent);\n\n        const upsellEvent = new Event('upsell-open-requested', { bubbles: true, composed: true });\n        this.dispatchEvent(upsellEvent);\n        cart_drawer.upsell_modal_displayed = true;\n        return;\n      } else {\n        console.log('not showing upsell modal on checkout button click');\n      }\n    } else {\n      console.log('element upsell-modal not found on checkout button click');\n    }\n\n    // Disable the checkout button. This does not actually prevent it from being clicked because the\n    // button is located outside of a form element. However, it does cause future click events to be\n    // handled by the click listener differently. Disabling the button before the redirect is\n    // important because there is sometimes a substantial visual lag that occurs during the redirect\n    // where we have observed visitors try to click the button multiple times while waiting.\n    button.disabled = true;\n\n    // This uses a relative url so as to work on multiple domains\n    const url = new URL(Shopify.routes.root + 'checkout', 'https://langehair.com');\n    if (this.dataset.skipShopPay === 'true') {\n      url.searchParams.set('skip_shop_pay', 'true');\n    }\n\n    location.href = url.toString();\n  }\n\n  /**\n   * We only start listening for this event from within the connected callback so we know the dom is\n   * ready and the backdrop element exists.\n   */\n  private onCartTransactionStarted(_event: Event) {\n    const button = this.shadowRoot.querySelector<HTMLButtonElement>('#checkout-button');\n    button.disabled = true;\n  }\n\n  private onCartTransactionCompleted(_event: WindowEventMap['cart-transaction-completed']) {\n    if (this.checkoutInitSucceeded) {\n      this.endLoading();\n    }\n  }\n\n  /**\n   * Enable the button and hide the spinner.\n   */\n  private endLoading() {\n    const button = this.shadowRoot.querySelector<HTMLButtonElement>('#checkout-button');\n    button.disabled = false;\n  }\n\n  private onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    if (event.detail.is_initial) {\n      this.checkoutInitSucceeded = true;\n    }\n\n    // Only enable the button if we previously observed a successful checkout-initialized event.\n    if (this.checkoutInitSucceeded) {\n      const button = this.shadowRoot.querySelector<HTMLButtonElement>('#checkout-button');\n      button.disabled = false;\n    }\n  }\n\n  private onPageShow(event: PageTransitionEvent) {\n    if (event.persisted) {\n      this.endLoading();\n    }\n  }\n\n  private onContinueShoppingButtonClicked(_event: MouseEvent) {\n    const upsell = document.querySelector('upsell-modal');\n    if (upsell?.dataset.show === 'true') {\n      const event = new Event('upsell-close-requested', { bubbles: true, composed: true });\n      this.dispatchEvent(event);\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          animate: false,\n          source: 'continue-shopping-button'\n        }\n      });\n      this.dispatchEvent(cartOpenEvent);\n    } else {\n      type Detail = WindowEventMap['cart-close-requested']['detail'];\n      const event = new CustomEvent<Detail>('cart-close-requested', {\n        bubbles: true,\n        composed: true,\n        detail: {\n          animate: true,\n          source: 'continue-shopping-button'\n        }\n      });\n      this.dispatchEvent(event);\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cart-actions': CartActions;\n  }\n}\n\nif (!customElements.get('cart-actions')) {\n  customElements.define('cart-actions', CartActions);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * This element is a higher level wrapper to implement the shopping bag drawer. It does not require\n * any knowledge of the underlying cart-element functionality and is only concerned with managing\n * the drawer state.\n *\n * The drawer shows and hides itself off `data-modal-state` rather than `data-show`. For now it sets\n * that attribute itself whenever `data-show` changes. Later the modal manager sets it instead.\n */\nclass CartDrawer extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-modal-state', 'data-show'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * If 'true' the opening and closing of the modal will be animated\n     */\n    animate: 'false' | 'true';\n\n    /**\n     * Whether the cart drawer is shown, as \"open\". Anything else, or no value at all, means closed.\n     */\n    modalState: string;\n\n    /**\n     * Whether the cart drawer is open.\n     *\n     * If the attribute is set to \"true\" the cart drawer is visible, if the attribute is set to\n     * \"false\", not setting or missing the cart drawer will not be visible.\n     */\n    show: 'false' | 'true';\n  };\n\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n  private onCloseClickedBound = this.onCloseClicked.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    const closeButton = this.shadowRoot.querySelector('close-button');\n    closeButton?.addEventListener('click', this.onCloseClickedBound);\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n  }\n\n  public disconnectedCallback() {\n    const closeButton = this.shadowRoot.querySelector('close-button');\n    closeButton?.removeEventListener('click', this.onCloseClickedBound);\n\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\n  }\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && name === 'data-show' && oldValue !== newValue) {\n      this.dataset.modalState = newValue === 'true' ? 'open' : 'closed';\n    }\n\n    if (this.isConnected && name === 'data-modal-state' && 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  /**\n   * If the emitted \"cart-updated\" event is not the initialization of the cart on page load, we must\n   * scroll the cart drawer window to the top to ensure any cart related messaging is brought into\n   * view for the customer.\n   */\n  private onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    if (!event.detail.is_initial) {\n      const modal = this.shadowRoot.querySelector('#panel');\n      modal.scrollTo(0, 0);\n    }\n  }\n\n  private showCartDrawer() {\n    const modal = this.shadowRoot.querySelector<HTMLElement>('#panel');\n    modal.inert = false;\n    modal.scrollTo(0, 0);\n\n    const closeButton = this.shadowRoot.querySelector('close-button');\n\n    // Focus moves into the drawer programmatically as it opens. Without forcing the visible-focus\n    // state, the browser suppresses the focus ring after a mouse-driven open, so the just-focused\n    // close button gives no visual cue of where focus landed. Older browsers that ignore the option\n    // degrade to the prior behavior, an unadorned programmatic focus.\n    closeButton.focus({ focusVisible: true });\n  }\n\n  private hideCartDrawer() {\n    const modal = this.shadowRoot.querySelector<HTMLElement>('#panel');\n    modal.inert = true;\n  }\n\n  private render() {\n    if (this.dataset.modalState === 'open') {\n      this.showCartDrawer();\n    } else {\n      this.hideCartDrawer();\n    }\n  }\n\n  private onCloseClicked(_event: MouseEvent) {\n    // A rapid second activation of the close control can fire this again after the first close has\n    // already begun. We do not de-duplicate here and instead rely on the cart drawer's close\n    // ignoring a request when it is already closed, so the repeat stays a silent no-op rather than\n    // producing a spurious modal close.\n    type Detail = WindowEventMap['cart-close-requested']['detail'];\n    const event = new CustomEvent<Detail>('cart-close-requested', {\n      bubbles: true,\n      composed: true,\n      detail: {\n        animate: true,\n        source: 'close-button'\n      }\n    });\n    this.dispatchEvent(event);\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cart-drawer': CartDrawer;\n  }\n}\n\nif (!customElements.get('cart-drawer')) {\n  customElements.define('cart-drawer', CartDrawer);\n}\n","/**\n * Represents the properties of a cookie.\n */\nexport interface Cookie {\n  /**\n   * Include a leading \".\" to include subdomains.\n   *\n   * @example \".langehair.com\"\n   */\n  domain: string;\n\n  /**\n   * Indicates the maximum lifetime of the cookie as an HTTP-date timestamp. See Date for the\n   * required formatting.\n   *\n   * If unspecified, the cookie becomes a session cookie. A session finishes when the client shuts\n   * down, after which the session cookie is removed.\n   *\n   * Warning: Many web browsers have a session restore feature that will save all tabs and restore\n   * them the next time the browser is used. Session cookies will also be restored, as if the\n   * browser was never closed.\n   *\n   * When an Expires date is set, the deadline is relative to the client the cookie is being set on,\n   * not the server.\n   */\n  expires: Date;\n\n  /**\n   * A <cookie-name> can contain any US-ASCII characters except for: control characters (ASCII\n   * characters `0` up to `31` and ASCII character `127`) or separator characters (space, tab and\n   * the characters: `( ) < > @ , ; : \\ \" / [ ] ? = { })`.\n   */\n  name: string;\n\n  /**\n   * Path is generally \"/\" which is for all pages for a domain. Otherwise the cookie is specific to\n   * the given path (e.g. particular page).\n   */\n  path: string;\n\n  /**\n   * Controls whether or not a cookie is sent with cross-site requests, providing some protection\n   * against cross-site request forgery attacks (CSRF).\n   *\n   * * Strict Means that the browser sends the cookie only for same-site requests, that is, requests\n   *   originating from the same site that set the cookie. If a request originates from a different\n   *   domain or scheme (even with the same domain), no cookies with the SameSite=Strict attribute\n   *   are sent.\n   * * Lax Means that the cookie is not sent on cross-site requests, such as on requests to load\n   *   images or frames, but is sent when a user is navigating to the origin site from an external\n   *   site (for example, when following a link). This is the default behavior if the SameSite\n   *   attribute is not specified.\n   * * None means that the browser sends the cookie with both cross-site and same-site requests. The\n   *   Secure attribute must also be set when setting this value, like so SameSite=None; Secure. If\n   *   Secure is missing an error will be logged\n   *\n   * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie\n   */\n  same_site: 'Lax' | 'None' | 'Strict';\n\n  /**\n   * Indicates that the cookie is sent to the server only when a request is made with the https:\n   * scheme (except on localhost), and therefore, is more resistant to man-in-the-middle attacks.\n   */\n  secure: boolean;\n\n  /**\n   * A <cookie-value> can optionally be wrapped in double quotes and include any US-ASCII character\n   * excluding control characters (ASCII characters 0 up to 31 and ASCII character 127), Whitespace,\n   * double quotes, commas, semicolons, and backslashes.\n   */\n  value: string;\n}\n\n/**\n * Decodes a percent-encoded cookie name or value, falling back to the raw input when decoding is\n * not possible.\n *\n * Cookie storage is shared by every script on a domain, including third party libraries that write\n * raw, improperly-encoded values. A single cookie whose value contains a malformed percent-\n * sequence, such as a literal percent sign that is not followed by two hexadecimal digits, makes\n * decodeURIComponent throw a URIError. Because the whole cookie string is parsed at once, one such\n * cookie would otherwise poison every read on the page rather than just its own entry. Returning\n * the undecoded text keeps the remaining cookies readable, and a value that was never percent-\n * encoded is returned unchanged anyway.\n */\nfunction safeDecode(value: string) {\n  try {\n    return decodeURIComponent(value);\n  } catch (error) {\n    return value;\n  }\n}\n\n/**\n * Parses the value of document.cookie into an an array of cookie objects.\n *\n * **This decodes the cookie name and value. You should not decode the cookie name or value\n * yourself**.\n *\n * Cookies can be set with separate flags. When there are two cookies with the same name but with\n * different flags, the name will appear twice in document.cookie. Therefore, this returns an array\n * of Cookie objects instead of a single object where each name is a property.\n *\n * @throws {Error} if document is undefined, if document.cookie cannot be accessed for some reason\n * such as security restrictions on iframes or browser restrictions, or if document.cookie does not\n * produce a string\n */\nexport function parse() {\n  return document.cookie.split(';').map<Pick<Cookie, 'name' | 'value'>>(parts => {\n    const index = parts.indexOf('=');\n    // decodeURIComponent(undefined) produces strange results in some browsers, such as the string\n    // undefined, so we have to avoid that\n    const encodedValue = parts.slice(index + 1)?.trim();\n    return {\n      name: safeDecode(parts.slice(0, index).trim()),\n      value: typeof encodedValue === 'string' ? safeDecode(encodedValue) : undefined\n    };\n  });\n}\n\n/**\n * Retrieves the value of a particular cookie by name.\n *\n * If a cookie with the given name is not found, then undefined is returned.\n *\n * Internally this calls a private function to parse the cookie and that function accesses\n * document.cookie and for that reason this function can throw. This will almost never happen except\n * when attempting to access cookies cross-context, e.g. accessing parent window cookies from within\n * an iframe where the iframe has certain attributes. Some third party libraries do this naively and\n * without concern for the security constraints imposed by browsers. As a result, you should always\n * consider handling errors when calling this function.\n *\n * Because this can throw, **please implement proper error handling** when calling this function.\n *\n * **This decodes the cookie name and value. You should not decode the cookie name or value\n * yourself**.\n *\n * Cookie values are always strings. If you happen to be storing something like a JSON-like object\n * in the cookie value, then the raw JSON string is returned and is not parsed on your behalf. If\n * you are enclosing your value in double quotes, the leading and trailing double quote characters\n * are included in the returned value.\n *\n * **WARNING**: This is a naive read. When examining the list of cookies, this returns the first one\n * in the list. More than one cookie can have the same name when two or more cookies have the same\n * name but different flags. This is entirely dependent on the order of the names in\n * document.cookie, which is completely unknown and can change at any time and probably varies by\n * browser. Therefore, when there is more than one cookie with the same name, it is possible this\n * returns an unexpected cookie value.\n *\n * @throws {SecurityError}\n * @throws {Error}\n */\nexport function read(name: string) {\n  return readAll(name).shift();\n}\n\nexport function readAll(name: string) {\n  return parse().filter(cookie => cookie.name === name).map(cookie => cookie.value);\n}\n\n/**\n * Given a cookie, generate the string that can be supplied to document.cookie\n *\n * **This encodes the cookie name and cookie. Do not encode these yourself**.\n */\nexport function serialize(cookie: Partial<Cookie>) {\n  if (!cookie.name) {\n    throw new Error('Cookie name is required');\n  }\n\n  const parts: string[] = [];\n\n  parts.push(`${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value || '')}`);\n\n  if (typeof cookie.path === 'string') {\n    parts.push(`Path=${cookie.path}`);\n  }\n\n  if (typeof cookie.domain === 'string') {\n    parts.push(`Domain=${cookie.domain}`);\n  }\n\n  if (cookie.expires && typeof cookie.expires.getTime === 'function') {\n    parts.push(`Expires=${cookie.expires.toUTCString()}`);\n  }\n\n  if (typeof cookie.same_site === 'string') {\n    parts.push(`SameSite=${cookie.same_site}`);\n  }\n\n  if (cookie.secure) {\n    parts.push('Secure');\n  }\n\n  // The delimiter must include a single space after semicolon\n\n  return parts.join('; ');\n}\n\n/**\n * This function can throw because setting document.cookie sometimes throws an error.\n *\n * This function can throw on invalid input, such as when not specifying a required property such as\n * cookie name.\n *\n * **This encodes the cookie name and cookie. Do not encode these yourself**.\n */\nexport function write(cookie: Partial<Cookie>) {\n  document.cookie = serialize(cookie);\n}\n\n/**\n * Deletes a cookie.\n *\n * Of the cookie properties, only the name is required. However, some browsers are extremely strict\n * about how a cookie is deleted. To delete a cookie the exact same other properties must also be\n * provided, other than the cookie's value and expiration date. For example, if the cookie to be\n * deleted is secure, then you must specify the secure property to delete the cookie.\n *\n * If the properties do not match, this completes without error and the cookie that was to be\n * deleted still remains.\n *\n * Deleting a cookie is done by setting its value to an empty string and then assigning the full\n * cookie value to document.cookie.\n *\n * This function may throw an error because deleting a cookie is done by assigning a value to\n * document.cookie which can sometimes throw.\n *\n * The expires property is ignored. This sets expiration date to 1970 to force cookie to become\n * expired.\n *\n * Insecure cookies cannot be removed. These are cookies belonging to the http protocol.\n */\nexport function remove(cookie: Partial<Cookie>) {\n  write({ ...cookie, value: '', expires: new Date(0) });\n}\n","import { read } from '@integrabeauty/cookie';\nimport type { CustomElement } from '@integrabeauty/custom-elements';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\ntype PriceEl = HTMLElementTagNameMap['price-element'];\ntype ProductEl = HTMLElementTagNameMap['product-element'];\ntype VariantEl = HTMLElementTagNameMap['variant-element'];\n\n/**\n * A single selectable reward variant scraped from a `<variant-element>` inside a `<cart-gwp-tier>`.\n * Data is extracted once at parse time and stored as plain values\n */\ninterface Variant {\n  /**\n   * Whether this variant is available to add to cart. Unavailable variants are filtered out before\n   * rendering.\n   */\n  available: boolean;\n\n  /**\n   * Original price before discount, in cents. Zero if there is no compare-at price.\n   */\n  compareAtPrice: number;\n\n  /**\n   * URL of the variant image, or the product's main image as a fallback. Empty string if no image\n   * is available.\n   */\n  imageSrc: string;\n\n  /**\n   * Display title combining the product title and variant title, e.g. \"Shampoo - 8oz\". When the\n   * variant title is \"Default Title\" (Shopify placeholder for single-option products), only the\n   * product title is used.\n   */\n  title: string;\n\n  /**\n   * URL to the specific variant page, e.g. \"/products/shampoo?variant=123\". Used as the href for\n   * the image clickable area.\n   */\n  url: string;\n\n  /**\n   * Shopify variant ID as a string.\n   */\n  variantId: string;\n}\n\n/**\n * A single GWP reward tier parsed from a child {@link CartGwpTier} element.\n */\ninterface GwpTier {\n  /**\n   * Message shown when the threshold has been reached but the reward has not yet been claimed.\n   * Defaults to \"Your free gift is waiting\" if not set in Shopify Admin.\n   */\n  claimMessage: string;\n\n  /**\n   * Cart total in cents at the time of the last state update. Used by the active tier's progress\n   * bar to show current spend vs threshold. Zero until the first cart-updated event is processed.\n   */\n  currentAmountCents: number;\n\n  /**\n   * Tail of the message shown on locked tiers: \"Add $X to {goalText}\". Should be written in\n   * lowercase to continue the sentence, e.g. \"unlock free shipping\". Defaults to \"unlock free\n   * shipping\" or \"unlock a free gift\" based on reward type if not set in Admin.\n   */\n  goalText: string;\n\n  /**\n   * Discount code that activates this reward. Applied to the cart when the customer adds the GWP\n   * variant, and used to detect whether the reward is already present in the cart via line-level\n   * discount allocations.\n   */\n  promoCode: string;\n\n  /**\n   * Identifies the reward type. Known values: 'free_gift', 'free_shipping'.\n   */\n  rewardType: string;\n\n  /**\n   * Current display state of this tier. Recalculated on every cart-updated event before rendering.\n   */\n  state: TierState;\n\n  /**\n   * Minimum cart total required to unlock this reward, in cents.\n   */\n  thresholdCents: number;\n\n  /**\n   * Message shown once the reward has been claimed or the shipping tier unlocked. Defaults to\n   * \"You've unlocked free shipping\" or \"You've unlocked a free gift\" based on reward type.\n   */\n  unlockedMessage: string;\n\n  /**\n   * Available reward variants scraped from the tier's nested `<product-element>` children. Only\n   * direct-child variants are included to avoid picking up variants from bundle components.\n   */\n  variants: Variant[];\n}\n\n/**\n * Represents the current display state of a single reward tier.\n *\n * - `locked` — threshold not reached and not the next tier in line\n * - `active` — the next tier to unlock; progress bar is shown\n * - `unclaimed` — threshold reached, free product available to add to cart\n * - `claimed` — threshold reached and the free product is already in cart\n * - `unlocked_shipping` — threshold reached for a free_shipping reward\n */\ntype TierState = 'active' | 'claimed' | 'locked' | 'unclaimed' | 'unlocked_shipping';\n\n/**\n * This element renders gift-with-purchase reward tiers inside the cart drawer. It reacts to cart-\n * updated events to recalculate tier states and re-render accordingly.\n *\n * Each tier is sourced from a child `<cart-gwp-tier>` element written by Liquid. Tier data is\n * parsed once and cached, since the DOM does not change between cart updates.\n */\nclass CartGwp extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * ISO 4217 currency code for the current market, e.g. \"USD\".\n     */\n    currencyCode: string;\n\n    /**\n     * Currency symbol character for the current market, e.g. \"$\".\n     */\n    currencySymbol: string;\n  };\n\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n\n  /**\n   * Tier data parsed from child cart-gwp-tier elements. Cached after the first parse because the\n   * Liquid-rendered DOM structure does not change during the page session.\n   */\n  private tiers: GwpTier[] | null = null;\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    // Replay any cart-updated events that fired before this element connected, so the initial cart\n    // state is applied immediately without waiting for the next update.\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\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 onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    this.updateTierStates(event.detail.cart);\n    this.render();\n    this.maybeAutoAdd();\n  }\n\n  private updateTierStates(cart: Cart) {\n    const tiers = this.collectTierData();\n    if (tiers.length === 0) {\n      return;\n    }\n\n    const lockedThresholds = tiers\n      .filter(t => cart.total_price < t.thresholdCents)\n      .map(t => t.thresholdCents);\n\n    // The active tier is the locked tier with the lowest threshold (next in line to unlock).\n    const nextThreshold = Math.min(...lockedThresholds);\n\n    for (const tier of tiers) {\n      tier.state = determineTierState(tier, cart, nextThreshold);\n      tier.currentAmountCents = cart.total_price;\n    }\n  }\n\n  /**\n   * Simulates an add-to-cart-button click for every unclaimed free-gift tier that has exactly one\n   * available variant, when the customer is in the GWP auto-add test group. render() must be called\n   * first so the buttons are present in the shadow DOM.\n   */\n  private maybeAutoAdd() {\n    if (readGwpAutoAddCookie() !== 'true') {\n      return;\n    }\n\n    const tiers = this.collectTierData();\n    for (const tier of tiers) {\n      if (tier.state !== 'unclaimed' || tier.rewardType !== 'free_gift') {\n        continue;\n      }\n\n      const available = tier.variants.filter(v => v.available);\n      if (available.length !== 1) {\n        continue;\n      }\n\n      const selector =\n        `.tier add-to-cart-button[data-discount-code=\"${CSS.escape(tier.promoCode)}\"]`;\n      const addToCartButton = this.shadowRoot.querySelector(selector);\n      const button = addToCartButton.shadowRoot.querySelector('button');\n      button.click();\n    }\n  }\n\n  private collectTierData() {\n    if (this.tiers) {\n      return this.tiers;\n    }\n\n    const tiers: GwpTier[] = [];\n\n    const tierElements = this.querySelectorAll('cart-gwp-tier');\n    for (const tierEl of tierElements) {\n      const rewardType = tierEl.dataset.rewardType ?? '';\n      const isShipping = rewardType === 'free_shipping';\n\n      tiers.push({\n        promoCode: tierEl.dataset.promoCode ?? '',\n        thresholdCents: parseInt(tierEl.dataset.thresholdCents ?? '0', 10),\n        rewardType,\n        goalText: tierEl.dataset.goalText ||\n          (isShipping ? 'unlock free shipping' : 'unlock a free gift'),\n        claimMessage: tierEl.dataset.claimMessage || 'Your free gift is waiting',\n        unlockedMessage: tierEl.dataset.unlockedMessage ||\n          (isShipping ? 'You\\'ve unlocked free shipping' : 'You\\'ve unlocked a free gift'),\n        variants: parseVariants(tierEl),\n        state: 'locked',\n        currentAmountCents: 0\n      });\n    }\n\n    tiers.sort((a, b) => a.thresholdCents - b.thresholdCents);\n    this.tiers = tiers;\n    return tiers;\n  }\n\n  private renderTier(tier: GwpTier) {\n    const template = this.shadowRoot.querySelector<HTMLTemplateElement>('#tier');\n    const frag = document.importNode(template.content, true);\n    const tierEl = frag.querySelector('.tier');\n\n    tierEl.classList.add(tier.state);\n\n    const isUnlocked = tier.state === 'unclaimed' ||\n      tier.state === 'claimed' ||\n      tier.state === 'unlocked_shipping';\n    const iconId = isUnlocked ? '#icon-checkmark' : '#icon-locked';\n    const iconTemplate = this.shadowRoot.querySelector<HTMLTemplateElement>(iconId);\n    tierEl.querySelector('.icon').appendChild(document.importNode(iconTemplate.content, true));\n\n    const labelPriceEl = tierEl.querySelector<PriceEl>('.label-price');\n    const labelNameEl = tierEl.querySelector('.label-name');\n    if (tier.state === 'active') {\n      labelPriceEl.dataset.currencyCode = this.dataset.currencyCode;\n      labelPriceEl.dataset.currencySymbol = this.dataset.currencySymbol;\n      labelPriceEl.dataset.subunits = tier.thresholdCents.toString();\n      labelNameEl.textContent = ` — ${getTierLabel(tier.rewardType, tier.state)}`;\n    } else {\n      labelPriceEl.remove();\n      labelNameEl.textContent = getTierLabel(tier.rewardType, tier.state);\n    }\n\n    const amountsEl = tierEl.querySelector('.amounts');\n    if (tier.state === 'active' || isUnlocked) {\n      const currentSubunits = tier.state === 'active' ?\n        Math.min(tier.currentAmountCents, tier.thresholdCents) :\n        tier.thresholdCents;\n\n      const currentEl = amountsEl.querySelector<PriceEl>('.current');\n      currentEl.dataset.currencyCode = this.dataset.currencyCode;\n      currentEl.dataset.currencySymbol = this.dataset.currencySymbol;\n      currentEl.dataset.subunits = currentSubunits.toString();\n\n      const thresholdEl = amountsEl.querySelector<PriceEl>('.threshold');\n      thresholdEl.dataset.currencyCode = this.dataset.currencyCode;\n      thresholdEl.dataset.currencySymbol = this.dataset.currencySymbol;\n      thresholdEl.dataset.subunits = tier.thresholdCents.toString();\n    } else {\n      // Locked tiers with no progress show no amounts.\n      amountsEl.remove();\n    }\n\n    const messagePrefixEl = tierEl.querySelector('.message-prefix');\n    const messagePriceEl = tierEl.querySelector<PriceEl>('.message-price');\n    const messageTextEl = tierEl.querySelector('.message-text');\n\n    if (tier.state === 'locked' || tier.state === 'active') {\n      const remaining = Math.max(0, tier.thresholdCents - tier.currentAmountCents);\n      messagePrefixEl.textContent = 'Add ';\n      messagePriceEl.dataset.currencyCode = this.dataset.currencyCode;\n      messagePriceEl.dataset.currencySymbol = this.dataset.currencySymbol;\n      messagePriceEl.dataset.subunits = remaining.toString();\n      messageTextEl.textContent = ` to ${tier.goalText}`;\n    } else {\n      messagePrefixEl.remove();\n      messagePriceEl.remove();\n      if (tier.state === 'claimed' || tier.state === 'unlocked_shipping') {\n        messageTextEl.textContent = tier.unlockedMessage;\n      } else {\n        messageTextEl.textContent = tier.claimMessage;\n      }\n    }\n\n    const progressEl = tierEl.querySelector('.progress');\n    if (tier.state === 'active') {\n      const percent = tier.thresholdCents > 0 ?\n        Math.min(100, Math.round((tier.currentAmountCents / tier.thresholdCents) * 100)) :\n        100;\n      progressEl.setAttribute('aria-valuenow', percent.toString());\n      progressEl.querySelector<HTMLElement>('.bar').style.width = `${percent}%`;\n    } else {\n      progressEl.remove();\n    }\n\n    const cardsEl = tierEl.querySelector('.cards');\n    if (tier.state === 'unclaimed') {\n      const availableVariants = tier.variants.filter(v => v.available);\n      for (const variant of availableVariants) {\n        cardsEl.appendChild(this.renderVariantCard(variant, tier.promoCode));\n      }\n    } else {\n      cardsEl.remove();\n    }\n\n    return tierEl;\n  }\n\n  private renderVariantCard(variant: Variant, promoCode: string) {\n    const template = this.shadowRoot.querySelector<HTMLTemplateElement>('#variant-card');\n    const frag = document.importNode(template.content, true);\n    const card = frag.querySelector('.card');\n\n    const link = card.querySelector<HTMLAnchorElement>('.image-link');\n    link.href = variant.url;\n\n    const img = link.querySelector('img');\n    if (variant.imageSrc) {\n      img.src = variant.imageSrc;\n      img.alt = variant.title;\n    } else {\n      link.remove();\n    }\n\n    const titleEl = card.querySelector<HTMLSpanElement>('.title');\n    titleEl.textContent = variant.title;\n\n    const compareEl = card.querySelector('.compare');\n    if (variant.compareAtPrice > 0) {\n      const priceEl = compareEl.querySelector('price-element');\n      priceEl.dataset.currencyCode = this.dataset.currencyCode;\n      priceEl.dataset.currencySymbol = this.dataset.currencySymbol;\n      priceEl.dataset.subunits = variant.compareAtPrice.toString();\n    } else {\n      compareEl.remove();\n    }\n\n    // The _is_gwp attribute is a line item property that flags this cart item as a gift-with-\n    // purchase reward. This allows order management and analytics code to identify GWP items\n    // without relying on the discount code alone.\n    const customAttribute = document.createElement('custom-attribute');\n    customAttribute.dataset.key = '_is_gwp';\n    customAttribute.dataset.value = 'true';\n\n    const variantEl = document.createElement('variant-element');\n    variantEl.slot = 'variants';\n    variantEl.dataset.id = variant.variantId;\n    variantEl.dataset.quantity = '1';\n    variantEl.appendChild(customAttribute);\n\n    const addToCartButton = card.querySelector('add-to-cart-button');\n    addToCartButton.dataset.discountCode = promoCode;\n    addToCartButton.appendChild(variantEl);\n\n    return card;\n  }\n\n  private render() {\n    if (!this.tiers?.length) {\n      return;\n    }\n\n    const tiersContainer = this.shadowRoot.querySelector('#tiers');\n    tiersContainer.replaceChildren();\n\n    for (const tier of this.tiers) {\n      tiersContainer.appendChild(this.renderTier(tier));\n    }\n  }\n}\n\n/**\n * Returns the value of the allow-gwp-auto-add cookie, or undefined if absent or unavailable.\n */\nfunction readGwpAutoAddCookie() {\n  let value;\n  try {\n    value = read('allow-gwp-auto-add');\n  } catch (error) {\n    // ignore the errors - fallback to showing the normal selection UI for the customer if cookies\n    // are unavailable\n  }\n\n  return value;\n}\n\n/**\n * Extracts all available variants from a tier element's direct `<product-element>` children. Uses\n * `:scope >` selectors to avoid including variants from nested bundle components\n */\nfunction parseVariants(tierEl: CartGwpTier): Variant[] {\n  const variants: Variant[] = [];\n\n  const productElements = tierEl.querySelectorAll<ProductEl>(':scope > product-element');\n  for (const productEl of productElements) {\n    const selectedId = productEl.dataset.selectedVariantId;\n\n    // Prefer the selected variant; fall back to the first available one.\n    const variantEl =\n      productEl.querySelector<VariantEl>(`:scope > variant-element[data-id=\"${selectedId}\"]`) ??\n      productEl.querySelector<VariantEl>(':scope > variant-element[data-id]');\n\n    if (!variantEl) {\n      continue;\n    }\n\n    const productTitle = productEl.dataset.title ?? '';\n    const variantTitle = variantEl.dataset.title ?? '';\n\n    // \"Default Title\" is Shopify's placeholder for single-option products; omit it from the title.\n    const title = variantTitle && variantTitle !== 'Default Title' ?\n      `${productTitle} - ${variantTitle}` :\n      productTitle;\n\n    variants.push({\n      available: variantEl.dataset.available === 'true',\n      compareAtPrice: parseInt(variantEl.dataset.compareAtPrice ?? '0', 10),\n      imageSrc: variantEl.dataset.imageSrc ?? productEl.dataset.mainImage ?? '',\n      title,\n      url: productEl.dataset.url + '?variant=' + variantEl.dataset.id,\n      variantId: variantEl.dataset.id ?? ''\n    });\n  }\n\n  return variants;\n}\n\n/** Calculates the current display state for a tier based on the cart total and unlock order. */\nfunction determineTierState(tier: GwpTier, cart: Cart, nextThreshold: number) {\n  const totalPrice = cart.total_price;\n\n  if (totalPrice >= tier.thresholdCents) {\n    if (tier.rewardType === 'free_shipping') {\n      return 'unlocked_shipping';\n    }\n\n    if (isGwpClaimedInCart(tier.promoCode, cart)) {\n      return 'claimed';\n    }\n\n    return 'unclaimed';\n  }\n\n  if (tier.thresholdCents === nextThreshold) {\n    return 'active';\n  }\n\n  return 'locked';\n}\n\n/**\n * Returns true if a line item in the cart has a discount allocation whose title matches the given\n * promo code. This is how we detect that the customer has already claimed a GWP reward.\n */\nfunction isGwpClaimedInCart(promoCode: string, cart: Cart) {\n  const code = promoCode.toLowerCase();\n  return cart.items.some(item =>\n    item.line_level_discount_allocations.some(alloc =>\n      alloc.discount_application.title.toLowerCase() === code\n    )\n  );\n}\n\n/**\n * Returns the human-readable label for a tier header derived from its reward type and current\n * state. Labels are intentionally generic (\"Free Gift\", \"Free Shipping\") rather than showing the\n * raw promo code.\n */\nfunction getTierLabel(rewardType: string, state: TierState) {\n  if (rewardType === 'free_shipping') {\n    return 'Free Shipping';\n  }\n\n  if (state === 'claimed') {\n    return 'Free Gift Unlocked';\n  }\n\n  return 'Free Gift';\n}\n\n/** Type guard that narrows a queued event to the cart-updated event type. */\n/**\n * Provides typed access to the data attributes on a `<cart-gwp-tier>` element, which is rendered by\n * Liquid from a GWP metaobject tier entry.\n */\ninterface CartGwpTier extends HTMLElement {\n  readonly dataset: {\n    /**\n     * Message shown when the threshold has been reached but the reward has not been claimed.\n     */\n    claimMessage: string;\n\n    /**\n     * Tail of the \"Add $X to {goalText}\" message shown on locked tiers. Should be written in\n     * lowercase, e.g. \"unlock free shipping\". Leave blank to use the default.\n     */\n    goalText: string;\n\n    /**\n     * Discount code that activates this reward.\n     */\n    promoCode: string;\n\n    /**\n     * Identifies the reward type. Known values: 'free_gift', 'free_shipping'.\n     */\n    rewardType: string;\n\n    /**\n     * Minimum cart total required to unlock this reward, in cents, as a string.\n     */\n    thresholdCents: string;\n\n    /**\n     * Message shown once the reward has been claimed or the shipping tier unlocked.\n     */\n    unlockedMessage: string;\n  };\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cart-gwp': CartGwp;\n    'cart-gwp-tier': CartGwpTier;\n  }\n}\n\nif (!customElements.get('cart-gwp')) {\n  customElements.define('cart-gwp', CartGwp);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport type { Cart, Item } from '@integrabeauty/shopify-ajax-api';\nimport { getEventQueue, isCartUpdatedEvent, isCartUpdateErredEvent } from '../../../lib/cart-event-queue.js';\nimport { ready, type ReleaseHandle } from '../../../lib/dom-ready.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders cart state and provides UI for adding and removing items and starting checkout\n */\nclass CartElement extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * URL for header \"our best sellers\" anchor link\n     */\n    bestSellersUrl: string;\n\n    /**\n     * Country ISO code. Required.\n     *\n     * @example \"US\"\n     */\n    countryCode: string;\n\n    /**\n     * Currency ISO code. Required.\n     *\n     * @example \"US\"\n     */\n    currencyCode: string;\n\n    /**\n     * Currency Symbol. Required.\n     *\n     * @example \"US\"\n     */\n    currencySymbol: string;\n\n    /**\n     * The name of the current Shopify template. Affects whether the side cart is displayed.\n     */\n    templateName: string;\n  };\n\n  private onCartInitRequestedBound = this.onCartInitRequested.bind(this);\n  private onCartTransactionStartedBound = this.onCartTransactionStarted.bind(this);\n  private onCartTransactionCompletedBound = this.onCartTransactionCompleted.bind(this);\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n  private onCartUpdateErredBound = this.onCartUpdateErred.bind(this);\n  private onContinueShoppingButtonClickBound = this.onContinueShoppingButtonClick.bind(this);\n  private onReadyBound = this.onReady.bind(this);\n  private onShowDiscountInputClickedBound = this.onShowDiscountInputClicked.bind(this);\n\n  private cartViewedDispatched = false;\n\n  private declare releaseReady: ReleaseHandle;\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n    this.releaseReady = ready(this.onReadyBound);\n  }\n\n  public disconnectedCallback() {\n    const continueShopping = this.shadowRoot.querySelector<HTMLElement>('#continue-shopping');\n    continueShopping?.removeEventListener('click', this.onContinueShoppingButtonClickBound);\n\n    removeEventListener('cart-init-requested', this.onCartInitRequestedBound);\n    removeEventListener('cart-transaction-started', this.onCartTransactionStartedBound);\n    removeEventListener('cart-transaction-completed', this.onCartTransactionCompletedBound);\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\n    removeEventListener('cart-update-erred', this.onCartUpdateErredBound);\n\n    // Extra paranoia, this should always be defined\n    if (this.releaseReady) {\n      this.releaseReady();\n    }\n\n    const showDiscountInput = this.shadowRoot.querySelector<HTMLElement>(\n      '.show-discount-input button');\n    showDiscountInput?.removeEventListener('click', this.onShowDiscountInputClickedBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n\n      // The template ships with the loading message showing, because usually this element is built\n      // before the cart arrives. It is not always that way around. The cart can have landed\n      // already, and this element can also be built fresh long after the cart settled, and in both\n      // of those the message is wrong the moment it paints. The queue tells us which case we are in\n      // and reading it is cheap, so decide here. Waiting for the replay in onReady is a task later,\n      // and a task is long enough for the browser to paint the message we are about to take back.\n      // Any error in the queue is enough, no need to single out the init failure. Every other code\n      // comes from an operation that needs a working cart, so one of those being here means the\n      // cart arrived and the updated event above already matched.\n      const settled = getEventQueue().some(event => isCartUpdatedEvent(event) ||\n        isCartUpdateErredEvent(event));\n      if (settled) {\n        this.hideLoadingMessage();\n      }\n    }\n  }\n\n  private onReady() {\n    if (!this.isConnected) {\n      return;\n    }\n\n    const continueShopping = this.shadowRoot.querySelector<HTMLElement>('#continue-shopping');\n    continueShopping.addEventListener('click', this.onContinueShoppingButtonClickBound);\n\n    if (this.dataset.bestSellersUrl) {\n      const bestSellersEl = this.shadowRoot.querySelector<HTMLAnchorElement>('a.best-sellers-link');\n      bestSellersEl.href = this.dataset.bestSellersUrl;\n    }\n\n    addEventListener('cart-init-requested', this.onCartInitRequestedBound);\n    addEventListener('cart-transaction-started', this.onCartTransactionStartedBound);\n    addEventListener('cart-transaction-completed', this.onCartTransactionCompletedBound);\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      } else if (isCartUpdateErredEvent(event)) {\n        try {\n          this.onCartUpdateErredBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n    addEventListener('cart-update-erred', this.onCartUpdateErredBound);\n\n    const showDiscountInput = this.shadowRoot.querySelector<HTMLElement>(\n      '.show-discount-input button');\n    showDiscountInput.addEventListener('click', this.onShowDiscountInputClickedBound);\n  }\n\n  private onContinueShoppingButtonClick(_event: MouseEvent) {\n    // if user is on the cart page there is no cart drawer and the \"Continue Shopping\" CTA should\n    // instead route the user to the home page.\n    if (this.dataset.templateName === 'cart') {\n      location.href = location.origin;\n      return;\n    }\n\n    type Detail = WindowEventMap['cart-close-requested']['detail'];\n    const event = new CustomEvent<Detail>('cart-close-requested', {\n      bubbles: true,\n      composed: true,\n      detail: {\n        animate: true,\n        source: 'cart-continue-shopping-button'\n      }\n    });\n    this.dispatchEvent(event);\n  }\n\n  /**\n   * Renders the first cart we receive.\n   *\n   * Watch out, onCartUpdated below does nearly all of this same show and hide work a second time,\n   * for every update after the first one. The two copies use different names for the same things,\n   * so promo here is promoGoalEl down there and installmentsMessage here is\n   * installmentPaymentMessageElement down there, and they look those elements up in different ways\n   * too. Nobody has collapsed the two, so a change to what shows and hides here almost certainly\n   * needs the same change down there.\n   */\n  private onCartUpdatedInitial(event: GlobalEventHandlersEventMap['cart-updated']) {\n    const cart = event.detail.cart;\n\n    if (cart.items.length > 0) {\n      this.shadowRoot.querySelector<HTMLElement>('#cart-contents').hidden = false;\n      this.shadowRoot.querySelector<HTMLElement>('.footer').hidden = false;\n      this.querySelector('cart-line-items')?.setAttribute('role', 'list');\n    } else {\n      const emptyCart = this.shadowRoot.querySelector<HTMLElement>('#empty-cart');\n      emptyCart.hidden = false;\n    }\n\n    // We will load only one of these\n    const promo = this.querySelector<HTMLElement>(\n      'free-shipping-banner, promo-goal-progress, cart-gwp');\n\n    const installmentsMessage = this.querySelector('installment-payment-message');\n\n    if (this.dataset.countryCode !== 'US') {\n      if (installmentsMessage) {\n        installmentsMessage.hidden = true;\n      }\n\n      if (promo) {\n        promo.hidden = true;\n      }\n    } else {\n      if (installmentsMessage) {\n        installmentsMessage.hidden = false;\n      }\n\n      if (cart.items.some(isMBG)) {\n        installmentsMessage?.classList.add('disclaimer');\n      } else {\n        installmentsMessage?.classList.remove('disclaimer');\n      }\n\n      if (installmentsMessage) {\n        installmentsMessage.dataset.estimatedTotal = cart.total_price.toString();\n      }\n\n      if (promo) {\n        promo.hidden = false;\n      }\n    }\n\n    // If the cart has TBYB products in the cart with deferred payments, we need to show the due\n    // today messaging. We determine the total deferred payments in the cart and if this value is\n    // greater than zero we show messaging otherwise we hide it.\n    this.renderDeferredPaymentMessage(cart);\n\n    const estTotal = this.shadowRoot.querySelector<HTMLElement>('#estimated-total');\n    estTotal.dataset.subunits = cart.total_price.toString();\n    estTotal.dataset.currencyCode = this.dataset.currencyCode;\n    estTotal.dataset.currencySymbol = this.dataset.currencySymbol;\n\n    this.hideBackdrop();\n  }\n\n  private onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    const cart = event.detail.cart;\n\n    // Any cart at all means the bag is no longer on its way, so the loading message goes here\n    // rather than in the initial branch below. An update that is not the initial one should not be\n    // able to reach us first, because the two things that emit one, the cross tab sync and the\n    // bfcache reconcile, both wait for the cart to have initialized. That is their invariant to\n    // keep though, held for their own reasons in their own files, and if it ever slipped the\n    // message would sit on top of a rendered cart. Clearing it for every update costs nothing and\n    // does not depend on any of that.\n    this.hideLoadingMessage();\n\n    // A cart arriving is the moment there is something to look at, so this is where the cart page\n    // announces its view. It does nothing in the drawer, which announces its own when it opens.\n    this.dispatchCartViewed();\n\n    const heading = this.shadowRoot.querySelector('h2');\n    const quantity = cart.item_count;\n    if (quantity === 0) {\n      heading.textContent = 'Shopping Bag';\n    } else if (quantity === 1) {\n      heading.textContent = 'Shopping Bag (1 item)';\n    } else {\n      heading.textContent = ['Shopping Bag (', quantity.toString(), ' items)'].join('');\n    }\n\n    if (event.detail.is_initial) {\n      this.onCartUpdatedInitial(event);\n      return;\n    }\n\n    // From here down is the second copy of the show and hide work described on onCartUpdatedInitial\n    // above.\n    const cartContents = this.shadowRoot.querySelector<HTMLElement>('#cart-contents');\n    const emptyCart = this.shadowRoot.querySelector<HTMLElement>('#empty-cart');\n    const footerEl = this.shadowRoot.querySelector<HTMLElement>('.footer');\n    const lineItems = this.querySelector('cart-line-items');\n\n    if (cart.items.length > 0) {\n      if (cartContents) {\n        cartContents.hidden = false;\n      }\n\n      emptyCart.hidden = true;\n      footerEl.hidden = false;\n      lineItems?.setAttribute('role', 'list');\n    } else {\n      emptyCart.hidden = false;\n\n      if (cartContents) {\n        cartContents.hidden = true;\n      }\n\n      footerEl.hidden = true;\n      lineItems?.removeAttribute('role');\n    }\n\n    // we will load only one of these, either the free shipping banner or promo goal progress\n    const promoGoalEl = this.querySelector('free-shipping-banner') ||\n      this.querySelector('promo-goal-progress') ||\n      this.querySelector('cart-gwp');\n\n    const installmentPaymentMessageElement = this.querySelector('installment-payment-message');\n\n    if (this.dataset.countryCode === 'US') {\n      if (installmentPaymentMessageElement) {\n        installmentPaymentMessageElement.hidden = false;\n      }\n\n      if (cart.items.some(isMBG)) {\n        installmentPaymentMessageElement?.classList.add('disclaimer');\n      } else {\n        installmentPaymentMessageElement?.classList.remove('disclaimer');\n      }\n\n      if (installmentPaymentMessageElement) {\n        installmentPaymentMessageElement.dataset.estimatedTotal = cart.total_price.toString();\n      }\n\n      if (cart.attributes?._aux_opt_in_v2 === 'new') {\n        const promoGoalV2El = this.querySelector('promo-goal-progress-v2');\n        if (promoGoalV2El) {\n          promoGoalV2El.dataset.show = 'true';\n        }\n      }\n\n      if (promoGoalEl) {\n        promoGoalEl.hidden = false;\n      }\n    } else {\n      if (installmentPaymentMessageElement) {\n        installmentPaymentMessageElement.hidden = true;\n      }\n\n      if (promoGoalEl) {\n        promoGoalEl.hidden = true;\n      }\n    }\n\n    const estTotal = this.shadowRoot.querySelector<HTMLElement>('#estimated-total');\n    estTotal.dataset.subunits = cart.total_price.toString();\n    estTotal.dataset.currencyCode = this.dataset.currencyCode;\n    estTotal.dataset.currencySymbol = this.dataset.currencySymbol;\n\n    // If the cart has TBYB products in the cart with deferred payments, we need to show the due\n    // today messaging. We determine the total deferred payments in the cart and if this value is\n    // greater than zero we show messaging otherwise we hide it.\n    this.renderDeferredPaymentMessage(cart);\n  }\n\n  /**\n   * When the cart fails to initialize, the lifecycle layer opens the drawer so the shopper sees\n   * what happened, and cart-messages renders the error into it. No cart ever arrives in that case,\n   * so the initial cart-updated we normally clear the loading message on never comes. Leaving it up\n   * would put \"we are loading your bag\" directly under a message saying we could not load it, for\n   * as long as the shopper leaves the drawer open.\n   *\n   * Any error settles this, so there is no need to pick out the init failure. Every other code\n   * comes from an operation that needs a working cart, so by the time one of those can happen the\n   * message is long gone and clearing it again changes nothing.\n   */\n  private onCartUpdateErred(_event: GlobalEventHandlersEventMap['cart-update-erred']) {\n    this.hideLoadingMessage();\n  }\n\n  /**\n   * The shopper asked to load their bag again after init failed. We show the same loading message\n   * as a normal first load while init runs, and it goes away again when init succeeds or fails.\n   */\n  private onCartInitRequested(_event: WindowEventMap['cart-init-requested']) {\n    this.shadowRoot.querySelector<HTMLElement>('#cart-loading').hidden = false;\n  }\n\n  /**\n   * Announces that the shopper is looking at their cart, on the cart page only. The drawer has an\n   * obvious moment for this, the point where it opens, and the cart page has nothing like it. The\n   * cart arrives after the page does, so we go with the first one we receive, which is the earliest\n   * point where there is a cart to look at.\n   *\n   * Only the first. Every update after that is the shopper changing something in a cart they are\n   * already looking at, which is not a new view.\n   *\n   * The flag holding that is per instance and not per page load, so a second instance announces a\n   * second view. Watch out for that if this element ever ends up on the page more than once.\n   */\n  private dispatchCartViewed() {\n    if (this.dataset.templateName !== 'cart') {\n      return;\n    }\n\n    if (this.cartViewedDispatched) {\n      return;\n    }\n\n    this.cartViewedDispatched = true;\n\n    type CartViewedEvent = GlobalEventHandlersEventMap['cart-viewed'];\n    const event = new CustomEvent<CartViewedEvent['detail']>('cart-viewed', {\n      bubbles: true,\n      composed: true,\n      detail: {\n        surface: 'page'\n      }\n    });\n    this.dispatchEvent(event);\n  }\n\n  private hideLoadingMessage() {\n    this.shadowRoot.querySelector<HTMLElement>('#cart-loading').hidden = true;\n  }\n\n  private renderDeferredPaymentMessage(cart: Cart) {\n    let deferredAmount = 0;\n    for (const item of cart.items) {\n      if (item.selling_plan_allocation?.selling_plan?.recurring_deliveries === false &&\n        item.selling_plan_allocation?.selling_plan?.name !== 'Pre-Order') {\n        deferredAmount += item.selling_plan_allocation.price;\n\n        // If a discount was applied to the item, we need to subtract that from the deferred amount\n        // because the TryNow app is not aware of any discounts and therefore the\n        // selling_plan_allocation.price property reflects the price before discount.\n        if (item.line_level_total_discount > 0) {\n          deferredAmount -= item.line_level_total_discount;\n        }\n      }\n    }\n\n    if (deferredAmount > 0) {\n      const dueTodayEl = this.shadowRoot.querySelector<HTMLElement>('#due-today');\n      dueTodayEl.dataset.subunits = (cart.total_price - deferredAmount).toString();\n      dueTodayEl.dataset.currencyCode = this.dataset.currencyCode;\n      dueTodayEl.dataset.currencySymbol = this.dataset.currencySymbol;\n\n      const dueTodayContainer = this.shadowRoot.querySelector<HTMLElement>('#tbyb-due-today');\n      dueTodayContainer.hidden = false;\n    } else {\n      const dueTodayContainer = this.shadowRoot.querySelector<HTMLElement>('#tbyb-due-today');\n      dueTodayContainer.hidden = true;\n    }\n  }\n\n  /**\n   * We only start listening for this event from within the connected callback so we know the DOM is\n   * ready and the backdrop element exists.\n   */\n  private onCartTransactionStarted(_event: WindowEventMap['cart-transaction-started']) {\n    this.showBackdrop();\n  }\n\n  /**\n   * We only start listening for this event from within the connected callback so we know the DOM is\n   * ready and the backdrop element exists.\n   */\n  private onCartTransactionCompleted(_event: WindowEventMap['cart-transaction-completed']) {\n    this.hideBackdrop();\n  }\n\n  /**\n   * The cart has its own loading veil: a translucent overlay with a spinner that covers the cart's\n   * contents while a cart transaction (add, remove, quantity change, discount) is in flight. Its\n   * job is to signal that work is underway and to lightly block the contents while the request is\n   * pending, alongside the disabling of individual controls. This is a different kind of backdrop\n   * than the page layers that dim everything behind a modal or drawer: its scope is only this\n   * element's own content area, and clicking it does not dismiss anything.\n   */\n  private hideBackdrop() {\n    // The loading veil is rendered as part of this element's template, so it lives in the shadow\n    // tree and must be looked up through the shadow root. A light-DOM lookup silently finds nothing\n    // and the toggle below quietly does nothing.\n    const backdrop = this.shadowRoot.querySelector<HTMLElement>('#cart-backdrop');\n    if (backdrop) {\n      backdrop.hidden = true;\n    }\n  }\n\n  private showBackdrop() {\n    // The loading veil is rendered as part of this element's template, so it lives in the shadow\n    // tree and must be looked up through the shadow root. A light-DOM lookup silently finds nothing\n    // and the toggle below quietly does nothing.\n    const backdrop = this.shadowRoot.querySelector<HTMLElement>('#cart-backdrop');\n    if (backdrop) {\n      backdrop.hidden = false;\n    }\n  }\n\n  private onShowDiscountInputClicked(_event: MouseEvent) {\n    const discountInput = this.shadowRoot.querySelector<HTMLSlotElement>(\n      'slot[name=\"discount-input\"]');\n    discountInput.hidden = false;\n\n    // hide the button after clicking it\n    const showDiscountInput = this.shadowRoot.querySelector<HTMLElement>('.show-discount-input');\n    showDiscountInput.hidden = true;\n  }\n}\n\nfunction isMBG(item: Item) {\n  return item.properties?.['_is_mbg'] === 'true';\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cart-element': CartElement;\n  }\n}\n\nif (!customElements.get('cart-element')) {\n  customElements.define('cart-element', CartElement);\n}\n","import type {} from '@integrabeauty/shopify-browser-types';\n\n/**\n * Represents the value of a cart attribute. Ordinarily values are strings. However, Shopify also\n * supports storing nested objects as values.\n */\nexport type AttributeValue = Record<string, string> | string;\n\n/**\n * The type of the value of `Cart['attributes']`\n */\nexport type Attributes = Record<string, AttributeValue>;\n\n/**\n * This is the stored cart, which is what a get cart answers with. A mutation answers with a\n * superset, so see the response body types alongside each mutation for the extra properties those\n * carry.\n *\n * @todo selling plan properties\n * @todo endpoint request body response body types\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart\n */\nexport interface Cart {\n  /**\n   * An object where the key is the attribute name and the value is the attribute value.\n   */\n  attributes: Attributes;\n\n  /**\n   * A discount application represents a type of discount.\n   *\n   * When a discount is applied to one or more line items, it is said to be _allocated_ to the line\n   * items. That is why line level discount allocation objects have a nested application object that\n   * will correlate with one of the applications in this array.\n   *\n   * When a discount is applied to the whole cart, it will only be present in this array because it\n   * is not allocated to particular lines.\n   */\n  cart_level_discount_applications: DiscountApplication[];\n\n  /**\n   * The ISO currency code for the cart.\n   *\n   * @example \"USD\"\n   */\n  currency: string;\n\n  /**\n   * Discount codes that have been applied to the cart.\n   *\n   * Not all codes are applicable. It is possible for a code to be applied but inapplicable.\n   *\n   * This does not provide information about the amount of each discount. Refer to the cart level\n   * discount applications or line level discount allocations for that information.\n   *\n   * This array will include shipping discounts. A shipping discount will be present in the array if\n   * applied but in the inapplicable state while in the cart. The discount will appear as applicable\n   * at checkout automatically.\n   */\n  discount_codes: DiscountCode[];\n\n  /**\n   * Total quantity of items in a cart\n   */\n  item_count: number;\n\n  /**\n   * The cart's line items.\n   *\n   * A cart will always have a defined items array, even if the cart is empty.\n   */\n  items: Item[];\n\n  items_subtotal_price: number;\n\n  /**\n   * An optional textual note that may be applied to the cart. If a cart becomes a checkout, and\n   * then the checkout becomes an order, the cart note will carry over to the order note.\n   *\n   * An order's note, and an order's note attributes, are two completely separate concepts.\n   */\n  note: string;\n\n  original_total_price: number;\n\n  /**\n   * This is true when at least one item in the cart requires shipping.\n   *\n   * This is false when no items in the cart require shipping.\n   *\n   * Shopify supports digital-only carts. These are carts that when purchased do not require the\n   * creation of a fulfillment. For example, a cart that consists only of a digital gift card.\n   */\n  requires_shipping: boolean;\n\n  /**\n   * This property is defined when there are cart mutations that involve the use of liquid-rendered\n   * sections.\n   *\n   * @see https://shopify.dev/docs/api/ajax/reference/cart#request-sections\n   */\n  sections: Record<string, string>;\n\n  /**\n   * This is the publicly visible cart id.\n   *\n   * If a checkout is created from this cart, the checkout's cart token will be equal to this value.\n   *\n   * Never write logic that parses this value. The format can change at any time, and has changed\n   * several times in the past.\n   *\n   * The value may contain url-like values, e.g. \"abc?key=123\"\n   */\n  token: string;\n\n  /**\n   * The total reduction in price in currency subunits (e.g. in cents for USD).\n   *\n   * @example 5010\n   */\n  total_discount: number;\n\n  /**\n   * Total price in currency subunits. For example, if the currency is USD, this is in cents.\n   *\n   * @example 1050\n   */\n  total_price: number;\n\n  /**\n   * The total weight of the cart (in grams?). This is a simple sum of the weight of the items of\n   * the cart (unit weight * quantity).\n   *\n   * @example 123.456\n   */\n  total_weight: number;\n}\n\n/**\n * Represents a discount that has been applied to an item.\n */\nexport interface Discount {\n  amount: number;\n  title: string;\n}\n\n/**\n * Represents a discount application that has been allocated to a line item.\n */\nexport interface DiscountAllocation {\n  /**\n   * The amount of the application that was allocated to a particular line item.\n   *\n   * When a discount application is allocated to several line items, this is a partial value of the\n   * full discount application amount that is allocated to this particular line. When the discount\n   * application is only allocated to a single line, this value will be equal to the total value of\n   * the discount application.\n   *\n   * For example, let's say we have cart with 2 line items and 10 dollar off discount application.\n   * The total value of the discount application is 10 dollars. Let's say the discount is allocated\n   * to both lines. The value of each allocation is 5 dollars. For each line this amount will\n   * contain the value 500. Within the nested application object, the application's total amount\n   * will be 1000. This is because each nested application object is a copy of the one application\n   * that happens to have multiple allocations (the discount is applied to multiple lines).\n   *\n   * The amount is in currency subunits. For example, for USD, this is the value of cents. For\n   * example, a discount of $2.50 is 250.\n   *\n   * @example 1050\n   */\n  amount: number;\n\n  /**\n   * The corresponding discount application that was allocated to a particular line item.\n   */\n  discount_application: DiscountApplication;\n}\n\n/**\n * A discount application represents a discount of some kind. There are automatically applied\n * discount applications, discount code applications, and script applied discount applications.\n */\nexport interface DiscountApplication {\n  allocation_method: string;\n  created_at: string;\n  description: string;\n  key: string;\n  target_selection: string;\n  target_type: string;\n  title: string;\n  total_allocated_amount: number;\n  type: string;\n  value: string;\n  value_type: string;\n}\n\n/**\n * The details of an image resource, such as the hero image for a product.\n */\nexport interface Image {\n  alt: string;\n  aspect_ratio: number;\n  height: number;\n  url: string;\n  width: number;\n}\n\n/**\n * Represents a line item in a cart.\n */\nexport interface Item {\n  /**\n   * The price of the line item after discounts\n   */\n  discounted_price: number;\n\n  /**\n   * Discount codes applied to the line item.\n   *\n   * @deprecated\n   */\n  discounts: Discount[];\n\n  /**\n   * The main image for the product\n   */\n  featured_image: Image;\n\n  /**\n   * The combined price, in the currency's subunit, of all of the items in the line item. This\n   * includes any line-level discounts.\n   *\n   * The value is equal to line_item.final_price multiplied by line_item.quantity. It's output in\n   * the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  final_line_price: number;\n\n  /**\n   * The price of the line item in the currency's subunit. This includes any line-level discounts.\n   *\n   * The value is output in the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  final_price: number;\n\n  /**\n   * Returns true if the product associated with the line item is a gift card. Returns false if not.\n   */\n  gift_card: boolean;\n\n  /**\n   * The weight of the line item in the store's default weight unit.\n   */\n  grams: number;\n\n  handle: string;\n\n  has_components: boolean;\n\n  /**\n   * The ID of the line item. The ID differs depending on the context. The following table outlines\n   * the possible contexts and their associated values:\n   *\n   * Context Value\n   *\n   * * cart.items The ID of the line item's variant. This ID isn't unique, and can be shared by\n   *   multiple items with the same variant.\n   * * checkout.line_items A temporary unique hash generated for the checkout.\n   * * order.line_items A unique integer ID.\n   */\n  id: number;\n\n  image: string;\n\n  /**\n   * Added in conjunction with parent_relationship to support determining whether a line item can be\n   * removed or have its quantity updated when the line item is a child of another line item, e.g.\n   * part of a bundle or value set or promotional gift.\n   */\n  instructions: {\n    can_remove: boolean;\n    can_update_quantity: boolean;\n  };\n\n  key: string;\n\n  /**\n   * The discount allocations that apply to the line item.\n   */\n  line_level_discount_allocations: DiscountAllocation[];\n\n  /**\n   * The total amount of any discounts applied to the line item in the currency's subunit.\n   *\n   * The value is output in the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  line_level_total_discount: number;\n\n  /**\n   * @deprecated https://shopify.dev/docs/api/liquid/objects/line_item\n   */\n  line_price: number;\n\n  /**\n   * The name and value pairs for each option of the variant associated with the line item.\n   *\n   * The array is never empty because variants with no options still have a default option. Because\n   * of this, you should use line_item.product.has_only_default_variant to check whether there's any\n   * information to output.\n   */\n  options_with_values: Option[];\n\n  /**\n   * The combined price of all of the items in a line item in the currency's subunit, before any\n   * discounts have been applied.\n   *\n   * The value is equal to line_item.original_price multiplied by line_item.quantity. It's output in\n   * the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  original_line_price: number;\n\n  /**\n   * The price of the line item in the currency's subunit, before discounts have been applied.\n   *\n   * The value is output in the customer's local (presentment) currency.\n   *\n   * For currencies without subunits, such as JPY and KRW, tenths and hundredths of a unit are\n   * appended. For example, 1000 Japanese yen is output as 100000.\n   */\n  original_price: number;\n\n  /**\n   * The parent relationship of the line item. This is only present if the line item is a child of\n   * another line item. For example, when the line item is part of a bundle or value set or\n   * promotional gift.\n   *\n   * The parent relationship must be established by adding the \"parent_id\" property to the add to\n   * bag request.\n   *\n   * @see https://shopify.dev/docs/apps/build/product-merchandising/nested-cart-lines/create-nested-cart-lines#cart-ajax-api\n   */\n  parent_relationship: {\n    parent_key: string;\n  };\n\n  presentment_price: number;\n\n  /**\n   * @deprecated https://shopify.dev/docs/api/liquid/objects/line_item\n   */\n  price: number;\n\n  product_description: string;\n\n  product_has_only_default_variant: boolean;\n\n  /**\n   * The ID of the line item's product.\n   */\n  product_id: number;\n\n  product_title: string;\n\n  product_type: string;\n\n  properties: Record<string, string>;\n  quantity: number;\n\n  quantity_rule: QuantityRule;\n\n  requires_shipping: boolean;\n\n  /**\n   * The selling plan allocation of the line item.\n   */\n  selling_plan_allocation: SellingPlanAllocation;\n\n  /**\n   * The SKU of the variant in the line item.\n   *\n   * Stock Keeping Unit.\n   */\n  sku: string;\n\n  successfully_fulfilled_quantity: number;\n\n  taxable: boolean;\n\n  /**\n   * The title of the line item. The title is a combination of line_item.product.title and\n   * line_item.variant.title, separated by a hyphen.\n   *\n   * In most contexts, the line item title appears in the customer's preferred language. However, in\n   * the context of an order, the line item title appears in the language that the customer checked\n   * out in. The title can receive an override value from the Cart Transform API. Overrides take\n   * precedence over translations.\n   *\n   * #### Line item title history\n   *\n   * When referencing line item, product, and variant titles in the context of an order, the\n   * following changes might result in a different output than you expect:\n   * * A product or variant being deleted\n   * * A product or variant title being edited\n   *\n   * When line_item.title is referenced for an order, the line item title at the time of the order\n   * is returned. However, when line_item.product.title and line_item.variant.title are referenced,\n   * the current value for each title is returned.\n   */\n  title: string;\n\n  /**\n   * @deprecated https://shopify.dev/docs/api/liquid/objects/line_item\n   */\n  total_discount: number;\n\n  unit_price_measurement: {\n    measured_type: string;\n    quantity_unit: string;\n    quantity_value: string;\n    reference_unit: string;\n    reference_value: number;\n  };\n\n  untranslated_product_title: string;\n\n  untranslated_variant_title: string;\n\n  /**\n   * The relative URL of the variant associated with the line item.\n   */\n  url: string;\n\n  /**\n   * The ID of the line item's variant.\n   */\n  variant_id: number;\n\n  variant_options: string[];\n\n  variant_title: string;\n\n  /**\n   * The vendor of the variant associated with the line item.\n   */\n  vendor: string;\n}\n\nexport interface SellingPlanAllocation {\n  compare_at_price: number;\n\n  per_delivery_price: number;\n\n  /**\n   * @example 900\n   */\n  price: number;\n\n  price_adjustments: SellingPlanAllocationPriceAdjustment[];\n  selling_plan: SellingPlan;\n}\n\nexport interface SellingPlanAllocationPriceAdjustment {\n  /**\n   * @example 1\n   */\n  position: number;\n\n  /**\n   * @example 900\n   */\n  price: number;\n}\n\nexport interface SellingPlan {\n  description: unknown;\n\n  fixed_selling_plan: boolean;\n\n  id: number;\n  name: string;\n\n  options: SellingPlanOption[];\n\n  price_adjustments: SellingPlanPriceAdjustment[];\n\n  recurring_deliveries: boolean;\n}\n\nexport interface SellingPlanOption {\n  name: string;\n  position: number;\n  value: string;\n}\n\nexport interface SellingPlanPriceAdjustment {\n  order_count: unknown;\n  position: number;\n  value: number;\n  value_type: string;\n}\n\nexport interface Option {\n  name: string;\n  value: string;\n}\n\nexport interface QuantityRule {\n  increment: number;\n  max: number;\n  min: number;\n}\n\nexport interface DiscountCode {\n  /**\n   * Whether the discount is currently applicable. It is possible to apply a discount code to a cart\n   * but have it not yet be applicable. When not applicable, there will not be any associated\n   * discount applications or allocations. The moment someone performs an action that makes the\n   * discount code applicable, Shopify will update the property to true.\n   */\n  applicable: boolean;\n\n  /**\n   * The discount code string.\n   *\n   * This is NOT guaranteed to be uppercase. Discount codes entered at checkout are captured in\n   * whatever case was used during input.\n   *\n   * @example \"MONEY1234\"\n   * @example \"Abc123def\"\n   */\n  code: string;\n}\n\n/**\n * Returns the request id header from the ajax cart api response. Capturing this value is important\n * when helping Shopify support investigate issues.\n *\n * This does not error. If there is a problem, this returns null or undefined.\n */\nfunction getRequestId(response: Response) {\n  try {\n    return response.headers.get('X-Request-Id');\n  } catch (error) {\n    // ignore\n  }\n}\n\n/**\n * Returns whether the given value is shaped like a fetch Response.\n *\n * The return type of fetch is a contract, not a verified fact. A third party that overrides the\n * global fetch can resolve it to null, undefined, or some unrelated value, in which case the very\n * next property access throws an opaque error with no indication that a bad override was the cause.\n * This checks structurally for the members this module reads rather than with an instanceof, so a\n * conforming fetch polyfill that returns its own Response class still passes while a missing or\n * malformed value is trapped at the point it is received.\n *\n * This type check does not technically prove that the value is a genuine Response object, only that\n * it has the expected shape. We are intentionally lying with types here, so that we can leverage\n * the type inference to support the subset of use cases this module actually relies on. More over,\n * this lying is ok, as the call to `response = await fetch` is also lying because it overly trusts\n * that fetch returns value of type Response. We are going to keep trying to \"make fetch happen\"\n * regardless.\n *\n * ## War Story\n *\n * As much as it seems like this is an insane and irrationally paranoid check, you would be\n * surprised. For example, in the wild, we observed a third party vendor that hooked global fetch,\n * had an error in their hook, and simultaneously, another vendor's third party polyfill that only\n * defined fetch without also defining Response, leading to even more obscure cases when older\n * browsers were involved. Then, to make matters worse, a fix was implemented leveraging the clean\n * global fetch using an iframe content window hack to work around the inability to cleanly capture\n * the native fetch before it was erroneously hooked, and this broke certain expressions that use\n * instanceof because instanceof has unintuitive behavior when checking instanceof of a value from a\n * different global context, e.g. from an iframe. A web of lies!\n */\nfunction isResponse(value: any): value is Response {\n  if (value === null || typeof value !== 'object') {\n    return false;\n  }\n\n  if (typeof value.ok !== 'boolean') {\n    return false;\n  }\n\n  if (typeof value.status !== 'number') {\n    return false;\n  }\n\n  if (typeof value.text !== 'function') {\n    return false;\n  }\n\n  if (typeof value.json !== 'function') {\n    return false;\n  }\n\n  return true;\n}\n\n/**\n * Attaches a single piece of context onto an error before it is thrown.\n *\n * The target may be a value caught upstream rather than one constructed here, and such a value can\n * carry a non-writable property, such as a DOMException whose code is a read-only accessor, or be\n * frozen outright, in which case assigning to it throws. Decorating an error must never itself\n * fail, so a rejected assignment is swallowed. This context is best effort metadata, so the\n * property is skipped when it cannot be set, leaving the error otherwise intact to be thrown and\n * handled by the caller.\n */\nfunction decorateError(error: any, key: string, value: unknown) {\n  try {\n    error[key] = value;\n  } catch (error) {\n    // The target rejects this property. Skip it rather than letting decoration fail.\n  }\n}\n\n/**\n * Returns an absolute url for the given path.\n *\n * It would be nice to just use a relative url in fetch. However, we see real world errors about\n * requests including credentials. Visitors might have buggy extensions inserting document.baseURI\n * or they are browsing the site in some weird way. To work around this, we build up a full url that\n * omits the credentials.\n *\n * Unfortunately, specifying the base url means we have to also solve the problem of determining it.\n * There is currently a desire to avoid location.origin due to browser support. we do not want to\n * touch the document base uri. Use host over hostname since that includes port.\n */\nfunction resolveUrl(path: string) {\n  return new URL(path, `${location.protocol}//${location.host}`);\n}\n\n/**\n * An item to add to the cart\n */\nexport interface AddItem {\n  /**\n   * Variant id\n   *\n   * Shopify says that it supports variant ids as numbers, but we have witnessed strange errors when\n   * using numbers so we impose an artificial limitation on the type here and require strings.\n   */\n  id: string;\n\n  /**\n   * The id of the line item to add the item as a child of. This is used for adding items as\n   * children of value sets and bundles. If not specified, the item is added as a top level line\n   * item.\n   *\n   * @see https://shopify.dev/docs/apps/build/product-merchandising/nested-cart-lines/create-nested-cart-lines#cart-ajax-api\n   */\n  parent_id?: number | string;\n\n  /**\n   * Line item properties to include.\n   *\n   * If the variant does not already exist in the cart, then the logic is straightforward. This will\n   * result in a new line item with these properties.\n   *\n   * If the variant exists in the cart, then the logic is convoluted. If the specified properties\n   * are identical to the existing properties, then the quantity of the existing line item\n   * increments. If the specified properties are different, then a new line item is created with\n   * these properties, resulting in the same variant being present in multiple line items.\n   */\n  properties?: Attributes;\n\n  /**\n   * The amount of the variant to add.\n   *\n   * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-add-js\n   */\n  quantity: number;\n\n  selling_plan?: number | string;\n}\n\n/**\n * The response body of a cart add.\n *\n * This one is not a cart at all, unlike the response body of a change or an update. It carries only\n * the items the request asked for, so there is no token, no totals, and no view of the rest of the\n * cart. See the notes on the add function for why the body is not worth reading.\n *\n * None of the functions here parse this body, because a caller that wants the resulting state\n * performs its own get cart afterward. So this type exists to describe what the endpoint answers\n * with rather than to annotate a return value.\n */\nexport interface CartAddResponseBody {\n  /**\n   * The line items the add produced, one per item in the request.\n   *\n   * These look like ordinary cart line items and are typed as such, though a few of the rarer line\n   * item properties have not been seen on one, so treat the shape as the usual line item rather\n   * than a guaranteed match.\n   */\n  items: Item[];\n}\n\n/**\n * Adds items to the cart. At least one item should be specified.\n *\n * This does not fetch the response body. The raw response body does not contain the full cart\n * state. The raw response body may not be the latest state of cart items. The raw response body may\n * contain zero-quantity items still present after prior removal.\n *\n * This validates the response status. If the status is not ok this throws an error.\n *\n * This specifies fetch mode no-store so that the call is not unintentionally cached.\n *\n * Adding an item the cart does not necessarily mean a new line item is created. For each item in\n * the input, Shopify makes a server side determination of whether to update the quantity of an\n * existing line item or add a new line item. The factors in that decision are related to how a line\n * item key is determined, which is poorly documented. Basically, if an item has some different\n * properties, e.g. line item properties, or pricing, then the item will always be added as a new\n * line item, even though the variant id is the same as an existing line item.\n *\n * The line item scripts run on every mutation. The scripts will remove any orphaned value set line\n * items implicitly. Keep this in mind when debugging.\n *\n * The content type header must be specified in the request or Shopify interprets the request as a\n * form submission, which we are not doing here, which leads to an error.\n *\n * Shopify's injected front end code (e.g. Trekkie, Monorail) triggers a console warning when it\n * detects that the number of items requested to be added is less than the number that was added.\n * This is not necessarily an error. It can indicate that the line item scripts removed one or more\n * line items during the add mutation.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-add-js\n * @see https://help.shopify.com/en/manual/checkout-settings/script-editor/shopify-scripts\n */\nexport async function cartAdd(items: AddItem[]) {\n  const url = resolveUrl(Shopify.routes.root + 'cart/add.js');\n\n  const init: RequestInit = {\n    body: JSON.stringify({ items }),\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    cache: 'no-store',\n    method: 'POST'\n  };\n\n  let response;\n  try {\n    response = await fetch(url, init);\n  } catch (error) {\n    decorateError(error, 'items', items);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetch did not return a response adding to cart');\n    decorateError(error, 'items', items);\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} adding to cart`);\n    decorateError(error, 'items', items);\n    decorateError(error, 'request_id', getRequestId(response));\n    decorateError(error, 'response_status', response.status);\n\n    try {\n      const responseBody = await response.json();\n      decorateError(error, 'response_body', responseBody);\n    } catch (responseConsumeAndParseBodyError) {\n      // ignore\n    }\n\n    // For certain error responses, Shopify provides a clearer error message in the response body.\n    // On some 422 responses a more specific description is present alongside the generic message,\n    // so prefer the description, fall back to the generic message, and otherwise leave the default\n    // received-status message in place. For example, adding a bundle whose component is unavailable\n    // yields a body like the following, where description is meaningful and message is generic:\n    //\n    // { \"description\": \"The bundle product 'Product Title' cannot be added to the cart.\",\n    // \"message\": \"Cart Error\", \"status\": 422 }\n    //\n    // The status property of the response body mirrors the HTTP status code (empirical).\n\n    let message;\n    if (response.status === 422 && typeof error.response_body?.description === 'string') {\n      message = error.response_body.description;\n    } else if (typeof error.response_body?.message === 'string') {\n      message = error.response_body.message;\n    }\n\n    if (message) {\n      decorateError(error, 'message', message);\n    }\n\n    throw error;\n  }\n\n  return response;\n}\n\n/**\n * Either id or line must be specified.\n */\nexport interface CartChangeInput {\n  /**\n   * Can be either the variant id or the line item key.\n   */\n  id?: number | string;\n\n  /**\n   * A cart can have multiple line items that share the same variant_id. For example, when variants\n   * have different line item properties, or automatic discounts create variants at different\n   * prices. To account for this, you can use the line property when updating the cart.\n   *\n   * The value of line is the 1-based index position of the item in the cart.\n   */\n  line?: number;\n\n  /**\n   * The properties property sets the line item properties. Its value must be an object of key-value\n   * pairs.\n   *\n   * Whenever a POST request includes properties, it overwrites the entire properties object. Any\n   * key-value pairs that were already in the properties object are erased.\n   *\n   * It's not possible to set a line item's properties property to an empty object once a value is\n   * set. If you need to remove all line item properties, then you need to remove the entire line\n   * item.\n   *\n   * You can visually hide line item properties at checkout by creating private properties. This\n   * technique might be an alternative to removing a line item when managing properties.\n   */\n  properties?: Record<string, any>;\n\n  /**\n   * The value of the quantity property represents the new quantity you want for the line item. It\n   * must be an integer.\n   *\n   * You can remove a line item by setting the quantity to 0.\n   *\n   * It is not clear from the documentation, but based on the description of the selling plan\n   * property, it looks like quantity is actually optional and defaults to 1 when not specified.\n   */\n  quantity?: number;\n\n  /**\n   * The selling_plan property sets the line item selling plan. Its value must be one of the\n   * following:\n   *\n   * * The selling plan ID: To set a specific selling plan for the line item.\n   * * null or an empty string: To remove any selling plan from the line item.\n   *\n   * When specifying the selling_plan property, consider the following:\n   * * You can use only the line property for identifying the line item.\n   * * You should always specify the quantity property. If the quantity isn't specified, then it's\n   *   assumed to be 1.\n   */\n  selling_plan?: number;\n}\n\n/**\n * The response body of a cart change.\n *\n * The two extra arrays are not part of the stored cart. Neither one is present in a get cart\n * response, and a cart update answers with a different extra property instead, so these describe\n * what the change did rather than what the cart holds.\n *\n * None of the functions here parse this body, because a caller that wants the resulting state\n * performs its own get cart afterward. So this type exists to describe what the endpoint answers\n * with rather than to annotate a return value.\n */\nexport interface CartChangeResponseBody extends Cart {\n  /**\n   * The line items the change brought into the cart.\n   *\n   * Nothing has been observed to put an entry in here, including a change that raised a quantity,\n   * so whether an entry looks like a removed one is unconfirmed.\n   */\n  items_added: CartChangeItem[];\n\n  /**\n   * The line items the change took out of the cart.\n   *\n   * A removal driven from a cart drawer, which identifies the line by its position rather than by\n   * variant, does report the removed line here. A removal that identifies the line by variant id\n   * has come back empty, so what qualifies is not settled.\n   */\n  items_removed: CartChangeItem[];\n}\n\n/**\n * An entry in one of the two arrays a cart change response carries.\n *\n * This is undocumented and the shape here is what has been observed on a removal. It is close\n * enough to a line item to be mistaken for one and it is not one, so a few differences are worth\n * knowing. The id is not the variant id, the line key arrives under a different property name, and\n * the price is a decimal string rather than an integer in subunits. It also carries no line item\n * properties, no weight, no discount details, and no parent relationship.\n */\nexport interface CartChangeItem {\n  /**\n   * An opaque identifier for the entry, formatted as a uuid.\n   *\n   * This is not the variant id, unlike the id of a cart line item.\n   */\n  id: string;\n\n  image: string;\n\n  presentment_price: number;\n\n  /**\n   * The price as a decimal string, in the presentment currency.\n   *\n   * Note that this is not an integer in currency subunits, which is how a price is expressed\n   * everywhere else in the cart.\n   *\n   * @example \"289.00\"\n   */\n  price: string;\n\n  product_id: number;\n\n  /**\n   * The title of the product, which here includes the variant.\n   *\n   * Note that a cart line item excludes the variant from this property and only combines the two in\n   * its title.\n   *\n   * @example \"TurboStyle - Blush\"\n   */\n  product_title: string;\n\n  product_type: string;\n  quantity: number;\n  sku: string;\n  title: string;\n  untranslated_product_title: string;\n  untranslated_variant_title: string;\n  url: string;\n  variant_id: number;\n  variant_title: string;\n  vendor: string;\n\n  /**\n   * The line item key, which a cart line item exposes as its key instead.\n   *\n   * @example \"47267055599716:fa232aa1a0b5b250164c9843d7a2ee6c\"\n   */\n  view_key: string;\n}\n\n/**\n * Use the /{locale}/cart/change.js endpoint to change the quantity, properties, and selling_plan\n * properties of a cart line item. Only items already in your cart can be changed, and you can\n * change only one line item at a time.\n *\n * The POST data requires either an id or line property to identify the line item to be changed.\n *\n * The value of the id property can be one of the following:\n *\n * * The line item's variant_id\n * * The line item's key\n *\n * A cart can have multiple line items that share the same variant_id. For example, when variants\n * have different line item properties, or automatic discounts create variants at different prices.\n * Because of this, it's recommended to use the line item key to ensure that you're only changing\n * the intended line item.\n *\n * If the item that you're attempting to change isn't already in the cart, then\n * /{locale}/cart/change.js doesn't add it. Instead, it returns a 404 error.\n *\n * The documentation states that the response body is the JSON of the cart. This exposes the\n * response object but does not fetch the response body.\n *\n * ### Removing out of stock items\n * It is unclear but based on a conversation with Shopify support, prefer to use cart/change instead\n * of cart/update to avoid issues with removing an out of stock item from the cart. Using update\n * results in a 422 error but supposedly cart/change does not.\n */\nexport async function cartChange(input: CartChangeInput) {\n  const url = resolveUrl(Shopify.routes.root + 'cart/change.js');\n\n  const init: RequestInit = {\n    body: JSON.stringify(input),\n    cache: 'no-store',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    method: 'POST'\n  };\n\n  let response;\n  try {\n    response = await fetch(url, init);\n  } catch (error) {\n    decorateError(error, 'input', input);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetch did not return a response changing cart');\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} changing cart`);\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'request_id', getRequestId(response));\n\n    try {\n      decorateError(error, 'response_body', await response.json());\n    } catch (consumeError) {\n      // ignore\n    }\n\n    decorateError(error, 'response_status', response.status);\n    decorateError(error, 'response_url', response.url);\n\n    // For certain error responses, Shopify provides a clearer error message in the response body.\n    // On some 422 responses a more specific description is present alongside the generic message,\n    // so prefer the description, fall back to the generic message, and otherwise leave the default\n    // received-status message in place. Shopify's generic message on these is literally \"Cart\n    // Error\", so promoting it over the description would discard the only meaningful text.\n    //\n    // The status property of the response body mirrors the HTTP status code (empirical).\n\n    let message;\n    if (response.status === 422 && typeof error.response_body?.description === 'string') {\n      message = error.response_body.description;\n    } else if (typeof error.response_body?.message === 'string') {\n      message = error.response_body.message;\n    }\n\n    if (message) {\n      decorateError(error, 'message', message);\n    }\n\n    throw error;\n  }\n\n  return response;\n}\n\n/**\n * Use the GET /{locale}/cart.js endpoint to get the cart as JSON.\n *\n * All monetary properties are returned in the customer's presentment currency. To check the\n * customer's presentment currency, you can use the currency field in the response.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#get-locale-cart-js\n */\nexport async function cartFetch() {\n  const url = resolveUrl(Shopify.routes.root + 'cart.js');\n\n  let response;\n  try {\n    response = await fetch(url, { cache: 'no-store' });\n  } catch (error) {\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetching cart did not return a valid response');\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} fetching cart`);\n    decorateError(error, 'request_id', getRequestId(response));\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n\n    try {\n      decorateError(error, 'response_body', await response.json());\n    } catch (consumeError) {\n      // ignore\n    }\n\n    throw error;\n  }\n\n  // Using response.json() obscures errors and prevents us from being able to see what the raw\n  // response body looks like. The usual cases of errant response bodies include:\n  //\n  // - default response bodies for errors, which are using plain text or html\n  // - firewall interceptions that produce html as a response, usually html\n  // - wifi connect interstitial page load, usually html\n  //\n  // So we are intentionally using response.text() and JSON.parse.\n\n  let responseBodyText;\n  try {\n    responseBodyText = await response.text();\n  } catch (error) {\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  let responseBodyObject;\n  try {\n    responseBodyObject = JSON.parse(responseBodyText);\n  } catch (error) {\n    decorateError(error, 'response_body', responseBodyText);\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  if (!isCart(responseBodyObject)) {\n    const error: any = new Error('Unexpected get cart response body');\n    decorateError(error, 'response_body', responseBodyObject);\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  return responseBodyObject;\n}\n\nexport interface CartUpdateInput {\n  /**\n   * New cart attributes. Existing cart attributes are overwritten.\n   */\n  attributes?: Attributes;\n\n  /**\n   * Beta shopify feature. Essentially works like Storefront discount apply mutation.\n   *\n   * Pass an empty string to remove discount(s).\n   */\n  discount?: string;\n\n  /**\n   * Cart note.\n   */\n  note?: string;\n\n  /**\n   * Even though Shopify supports specifying variant ids as numbers, we impose the restriction that\n   * the property names of the updates object must be strings due to some strange looking errors\n   * when specifying numbers.\n   */\n  updates?: Record<string, any>;\n}\n\n/**\n * The response body of a cart update.\n *\n * The changelog is not part of the stored cart. It is absent from a get cart response, and a cart\n * change answers with two differently named arrays instead, so it describes what the update did\n * rather than what the cart holds.\n *\n * None of the functions here parse this body, because a caller that wants the resulting state\n * performs its own get cart afterward. So this type exists to describe what the endpoint answers\n * with rather than to annotate a return value.\n */\nexport interface CartUpdateResponseBody extends Cart {\n  /**\n   * A summary of what the update brought into the cart.\n   *\n   * This is present on every successful update, including one that only writes attributes or a\n   * note, in which case the arrays inside it are empty.\n   */\n  items_changelog: ItemsChangelog;\n}\n\n/**\n * The summary of what an update brought into the cart.\n *\n * This is undocumented, so the shape here is only what has been observed. The added array is the\n * only property seen so far. A removed counterpart would be the obvious sibling but no update\n * response has produced one, not even one that set a quantity to zero, so it is left out rather\n * than guessed at.\n */\nexport interface ItemsChangelog {\n  /**\n   * The variants the update brought into the cart, and the quantity each one landed at.\n   *\n   * Only a variant that was not in the cart beforehand shows up here. Raising or lowering the\n   * quantity of a line that already exists leaves this empty, as does removing one, so this reports\n   * newly created lines rather than every quantity change.\n   */\n  added: ItemsChangelogAddition[];\n}\n\n/**\n * An entry in the added array of an update's changelog.\n *\n * This is not a line item. It carries only the variant and the quantity, so it cannot stand in for\n * the corresponding entry in the cart's items array.\n */\nexport interface ItemsChangelogAddition {\n  /**\n   * The quantity the line item landed at, not the amount by which it changed.\n   */\n  quantity: number;\n\n  /**\n   * Note that this is a number here, unlike elsewhere in this module where a variant id is\n   * restricted to a string on the request side.\n   */\n  variant_id: number;\n}\n\n/**\n * Performs a cart update.\n *\n * This does not return the cart updated state. You will need to do a followup call to fetch the\n * updated state of the cart. One reason this does also return the updated state is that we must\n * perform a second call to fetch the updated state but not all callers are interested in doing so.\n *\n * This does not return the response body of the call. There is no need to fetch the body because\n * the body does not contain useful data. The response body can contain 0 quantity items and other\n * kinds of strange things.\n *\n * This must specify the content type header. Shopify supports both a JSON request body and a form\n * data request body. Shopify defaults to interpreting requests as form submissions. This uses the\n * JSON format so the header is required.\n *\n * Backend scripts may affect the output.\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-update-js\n * @see https://shopify.dev/docs/api/ajax/reference/cart#update-line-item-quantities\n * @see https://shopify.dev/docs/api/ajax/reference/cart#private-cart-attributes\n */\nexport async function cartUpdate(input: CartUpdateInput) {\n  const url = resolveUrl(Shopify.routes.root + 'cart/update.js');\n\n  const init: RequestInit = {\n    body: JSON.stringify(input),\n    cache: 'no-store',\n    headers: {\n      'Content-Type': 'application/json'\n    },\n    method: 'POST'\n  };\n\n  let response;\n  try {\n    response = await fetch(url, init);\n  } catch (error) {\n    decorateError(error, 'input', input);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!isResponse(response)) {\n    const error: any = new Error('fetch did not return a response updating cart');\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'response', response);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error(`Received status ${response.status} updating cart`);\n    decorateError(error, 'request_body', input);\n    decorateError(error, 'request_id', getRequestId(response));\n    decorateError(error, 'response_status', response.status);\n\n    try {\n      const parsedErrorResponseBody = await response.json();\n      decorateError(error, 'response_body', parsedErrorResponseBody);\n    } catch (consumeError) {\n      // ignore\n    }\n\n    // For certain error responses, Shopify provides a clearer error message in the response body.\n    // On some 422 responses a more specific description is present alongside the generic message,\n    // so prefer the description, fall back to the generic message, and otherwise leave the default\n    // received-status message in place. Shopify's generic message on these is literally \"Cart\n    // Error\", so promoting it over the description would discard the only meaningful text.\n    //\n    // The status property of the response body mirrors the HTTP status code (empirical).\n\n    let message;\n    if (response.status === 422 && typeof error.response_body?.description === 'string') {\n      message = error.response_body.description;\n    } else if (typeof error.response_body?.message === 'string') {\n      message = error.response_body.message;\n    }\n\n    if (message) {\n      decorateError(error, 'message', message);\n    }\n\n    throw error;\n  }\n\n  return response;\n}\n\n/**\n * Returns whether the error indicates the add failed because one of the products is out of stock\n * (not available for sale). In this case Shopify provides an error response body that looks like\n * this:\n *\n * ```json\n * {\n *   \"description\": \"All 0 <product title> are in your cart\",\n *   \"message\": \"Cart Error\",\n *   \"status\": 422\n * }\n * ```\n *\n * ```json\n * {\n *   \"description\": \"The product '<title>' is already sold out.\",\n *   \"message\": \"Cart Error\",\n *   \"status\": 422\n * }\n * ```\n *\n * This error can happen both when adding an item or updating the quantity of an item.\n *\n * This assumes error messages are in English.\n */\nexport function isUnavailableError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      // this one is only present in the add error response\n      if (description.startsWith('All 0') && description.includes('in your cart')) {\n        return true;\n      }\n\n      // this string is sometimes present in the update error response, not just the add response\n      if (description.includes('already sold out')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the error indicates the add failed because the shopper already holds the item's\n * per-order purchase limit in their cart. Shopify provides an error response body that looks like\n * this:\n *\n * ```json\n * {\n *   \"description\": \"The maximum quantity of this item is already in your cart.\",\n *   \"message\": \"The maximum quantity of this item is already in your cart.\",\n *   \"status\": 422\n * }\n * ```\n *\n * This assumes error messages are in English.\n */\nexport function isMaxQuantityError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      const normalizedDescription = description.toLowerCase();\n      if (normalizedDescription.includes('maximum quantity of this item is already in your cart')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the error indicates the add failed because the request mixed a bundle product and\n * an add-on in the same `cart/add.js` call, which Shopify does not allow. Shopify provides an error\n * response body that looks like this:\n *\n * ```json\n * {\n *   \"description\": \"Bundle products and add-ons cannot be mixed\",\n *   \"message\": \"Bundle products and add-ons cannot be mixed\",\n *   \"status\": 422\n * }\n * ```\n *\n * This assumes error messages are in English.\n */\nexport function isBundleAddonMixError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      const normalizedDescription = description.toLowerCase();\n      if (normalizedDescription.includes('bundle products and add-ons cannot be mixed')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the error indicates the add failed because the selling plan sent with the item is\n * not one Shopify will accept for that variant. This happens when a rendered page carries a selling\n * plan ID (for example a pre-order deferred-payment plan) that has since become stale: the variant\n * came back in stock, or the plan was edited or removed, after the page was rendered but before the\n * shopper clicked add. Shopify responds with a body that looks like this:\n *\n * ```json\n * {\n *   \"description\": \"Cannot apply selling plan to variant\",\n *   \"message\": \"Cart Error\",\n *   \"status\": 422\n * }\n * ```\n *\n * This assumes error messages are in English.\n */\nexport function isInvalidSellingPlanError(error: any) {\n  if (error instanceof Error && 'response_body' in error && isObject(error.response_body) &&\n    error.response_body.status === 422) {\n    const description = error.response_body.description;\n    if (typeof description === 'string') {\n      if (description.includes('Cannot apply selling plan to variant')) {\n        return true;\n      }\n    }\n  }\n\n  return false;\n}\n\n/**\n * Returns whether the given value looks like a cart object.\n *\n * This is not a complete validation. This is a shallow check that examines whether the value is an\n * object containing an items property that is an array. Any value that meets that criteria\n * satisfies the meaning of a cart.\n */\nexport function isCart(value: any): value is Cart {\n  return isObject(value) && Array.isArray(value.items);\n}\n\nfunction isObject(value: any): value is Record<string, any> {\n  return typeof value === 'object' && value !== null;\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\n\n/**\n * Returns a general object consisting of only those properties from the cart that might be useful\n * during triage. This avoids the lazy capture of the full cart, which is often full of lots of data\n * irrelevant to analysis, and often results in truncation that obscures the values that are very\n * important to analysis.\n *\n * The output type here is intentionally vaguely typed so as to not bake in any future guarantees of\n * property names. This output is not meant for library usage. It is only meant for relaying to\n * other downstream systems that operate on unknown bags of data.\n *\n * This is written defensively to never throw so that it is safe to call from critical paths.\n */\nexport function digest(cart: Partial<Cart>) {\n  if (cart === null) {\n    const output: Record<string, any> = { cart_input_type: 'null' };\n    return output;\n  }\n\n  if (typeof cart !== 'object') {\n    const output: Record<string, any> = { cart_input_type: typeof cart };\n    return output;\n  }\n\n  try {\n    const output: Record<string, any> = {\n      token: cart.token,\n      attribute_keys: Object.keys(cart.attributes ?? {}),\n      discount_codes: cart.discount_codes,\n      cart_level_discount_titles: cart.cart_level_discount_applications?.map(\n        application => application?.title),\n      items: cart.items?.map(item => ({\n        key: item?.key,\n        variant_id: item?.variant_id,\n        quantity: item?.quantity,\n        properties: item?.properties,\n        discounts: item?.line_level_discount_allocations?.map(\n          allocation => allocation?.discount_application?.title)\n      }))\n    };\n    return output;\n  } catch (error) {\n    return { digest_failed: `${error}` };\n  }\n}\n","import { type Cart, isCart } from '@integrabeauty/shopify-ajax-api';\n\n/**\n * Returns whether two carts are equal. This is an approximate check that only considers line items.\n *\n * This does not consider line item properties.\n *\n * This does not consider cart attributes.\n *\n * This does not consider discount applications and allocations.\n *\n * This function exists separate from a full cart equality check because we need a way to consider\n * only certain state changes as meaningful. One of the reasons this is needed is because we have\n * little to no control over third party changes to cart state. There are apps that inject code that\n * does its own cart modifications, e.g. adjusting the cart's attributes on page load, and short of\n * uninstalling them we cannot prevent this. There are even mobile apps that inject content scripts\n * that assume control over the cart state. The decision making to install apps does not always\n * appreciate the complexity of the state comparison case. Given that, we need a way to treat only\n * certain state changes as meaningful, which means being deliberate about which parts of the api\n * contract we honor. This function therefore only considers line items and ignores other state\n * changes because it is intentionally only adhering to one clause of the Shopify cart contract.\n *\n * This is complicated because Shopify's cart mutations generally do not return the updated cart\n * state. They often return junk. The only way to reliably determine the updated cart state is to\n * fetch it separately, which always involves fetching the entire state. Which unfortunately is\n * often the first time that we learn that something else has modified the cart. We have to\n * reconcile the difference between the edits we issued and the actual updated cart state,\n * accounting for unexpected changes. In other words, we are comparing projections of the state\n * rather than the actual state. Since the endpoints do not support projection (e.g. give me only\n * these fields), we do this by selectively comparing only certain cart properties.\n */\nexport function hasSimilarItems(cart1: Cart, cart2: Cart) {\n  // Sanity check the inputs. This should never happen. However, it does allow the caller to be less\n  // careful with guaranteeing null values. Also, we have seen runtime state diverge from statically\n  // typed state in rare cases of bugs and unexpected third party interference, so even though this\n  // is redundant type checking at compile time, it is not redundant at runtime.\n\n  if (!isCart(cart1)) {\n    return false;\n  }\n\n  if (!isCart(cart2)) {\n    return false;\n  }\n\n  // Seeing a new token is unlikely to ever happen and is more of a sanity check. One plausible\n  // situation is cart expiration resulting in cart replacement.\n\n  // We have seen cases where we have partial cart state and the token is missing, so this only does\n  // token comparison when both tokens are known, to still allow for other kinds of cart state\n  // comparison when either is unknown.\n\n  if (cart1.token && cart2.token && cart1.token !== cart2.token) {\n    return false;\n  }\n\n  // The same number of line items should be present in both carts.\n\n  if (cart1.items.length !== cart2.items.length) {\n    return false;\n  }\n\n  // The lines should have the same keys with the same quantities. If any one line does not, break.\n  // Because there are the same number of line items in both carts, finding that every item from\n  // cart1 exists in cart2 implies that every item from cart2 exists in cart1, so we only need to\n  // examine state in one direction.\n\n  // Watch out for careless type inconsistencies, e.g. key as a number on input but key as a string\n  // on output. It is best to only compare output types because those are consistent. The cart api\n  // unfortunately has some Postel's Law like behavior. Avoid comparing a manually constructed cart\n  // to a fetched cart.\n\n  for (const item1 of cart1.items) {\n    let found = false;\n    for (const item2 of cart2.items) {\n      if (item1.key === item2.key && item1.quantity === item2.quantity) {\n        found = true;\n        break;\n      }\n    }\n\n    if (!found) {\n      return false;\n    }\n  }\n\n  return true;\n}\n","/**\n * Copies a single piece of supplementary context onto an error before it is thrown or reported.\n *\n * Use this when decorating a value the calling code did not construct, such as a caught value or an\n * error handed in by another module. An error the caller just built with new is always writable, so\n * plain assignment is fine there. A value from elsewhere may be a foreign object this code does not\n * own. Such a value can carry a non-writable property, such as a DOMException whose code is a read-\n * only accessor, or be frozen outright, and assigning to it then throws. Attaching context must\n * never itself fail, so a rejected assignment is swallowed and the remaining context is still\n * attempted. Guarding each property in isolation means a single read-only slot, the common case,\n * costs only that one property rather than all of them.\n *\n * In practice the value that triggers this is almost always a DOMException that reached the caller\n * from a fetch the browser cut short, such as an abort when an in-flight request is interrupted.\n * The platform defines a DOMException's code, name, and message as read-only attributes, so\n * assigning a code onto one throws under the strict mode that all bundled module code runs in. It\n * surfaces from Safari far more than elsewhere for two reasons. The reported wording, attempted to\n * assign to readonly property, is JavaScriptCore's phrasing for this failure, so the same bug in\n * another engine is worded differently and groups as a separate report, which makes this one look\n * Safari specific even though the read-only rule is not. And iOS Safari abandons in-flight fetches\n * aggressively when a tab is backgrounded or a connection drops, which is when such a DOMException\n * tends to be produced and handed to code that assumed an ordinary writable error.\n *\n * A swallowed failure is logged at a level low enough that it is recorded as a breadcrumb for\n * monitoring rather than escalated into its own captured event. That keeps the lost context visible\n * alongside the eventual report without adding fresh noise of its own. The breadcrumb names the\n * property that could not be attached, the kind of value that rejected it, and why, which is enough\n * to recognize a read-only or frozen caught value after the fact.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Read-only\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMException\n * @see https://webidl.spec.whatwg.org/#dfn-read-only\n */\nexport function decorateError(error: any, key: string, value: unknown) {\n  try {\n    error[key] = value;\n  } catch (assignmentError) {\n    let reason: string;\n    if (assignmentError instanceof Error) {\n      reason = assignmentError.message;\n    } else {\n      reason = `${assignmentError}`;\n    }\n\n    console.log('Failed to assign error property', {\n      key,\n      name: error?.name,\n      tag: Object.prototype.toString.call(error),\n      reason\n    });\n  }\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { decorateError } from '../decorate-error.js';\nimport type { Transaction } from './transaction.js';\n\ntype CartUpdateErredEvent = CustomEvent<{\n  /**\n   * Cart state.\n   *\n   * This may be undefined or null or invalid because almost nothing is guaranteed.\n   */\n  cart?: Cart;\n\n  /**\n   * An error code that describes the context of the error or the type of the error.\n   */\n  code: string;\n\n  /**\n   * The error that occurred. This will almost always be of type `Error`. However, this is set to\n   * the value of whatever error was caught. Values not of type `Error` can be thrown. We are not\n   * checking that the value is an instance of an `Error` first. It is not safe to assume this is of\n   * type `Error` particularly because of code minimization and third party modifications.\n   */\n  error: any;\n\n  /**\n   * A mix of misc properties that various cart transactions will include that help provide more\n   * information about the inputs to the code that led to the error.\n   */\n  inputs?: Record<string, any>;\n\n  /**\n   * If true, this means the event was emitted by a multi-fetch operation where some of the fetches\n   * succeeded. For example, consider the addItems call, which has to do two fetches in its virtual\n   * transaction. This will be true when the fetch to add the item succeeded but the fetch to get\n   * the new cart state after the add failed. This flag exists so that callers can choose to react\n   * to this situation differently, e.g. by retrying just the cart state fetch, or changing what\n   * error message is displayed (a failure to add versus a partially successful add).\n   */\n  partial?: boolean;\n\n  /**\n   * An optional programmatic code or human-readable description of the context in which some cart\n   * update was taken. This is primarily for analytics, but can also be used for ui logic.\n   *\n   * For example:\n   * * distinctly identify two different add to cart buttons\n   * * distinctly identify a particular ui element used to apply a discount\n   */\n  scenario?: string;\n\n  /**\n   * The transaction in which the error occurred.\n   */\n  transaction: Transaction;\n}>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'cart-update-erred': CartUpdateErredEvent;\n  }\n}\n\n/**\n * Broadcasts a cart failure and, when it is a genuine programmer error, surfaces it to the global\n * error handler so that monitoring records it.\n *\n * The cart helpers deliberately catch their own failures so that the code awaiting them is not\n * forced to handle a promise rejection. Every failure is broadcast as an event for the interface to\n * react to. That catch keeps the promise from rejecting, but for an unexpected failure it does not\n * mean the problem was actually dealt with. It is still a real error that would have reached the\n * top level had it never been caught. The escalation step re-throws those so that they do, leaving\n * error monitoring to record whatever uncaught errors arrive with no knowledge of the cart.\n */\nexport function report(dispatcher: EventTarget | null, detail: CartUpdateErredEvent['detail']) {\n  dispatchCartUpdateErred(dispatcher, detail);\n  escalateCartError(detail);\n}\n\n/**\n * Dispatches the failure as an event for the interface to react to.\n *\n * The dispatcher is the target the event is dispatched from. It is required but may be null, and it\n * may be any event target, not necessarily an element or window. The target is used as given, with\n * two exceptions that exist only to keep the event reaching window level listeners: a null target\n * dispatches from window, and so does a detached element.\n *\n * A detached element is swapped for window because of how bubbling works. We want to guarantee the\n * event bubbles up to window so that window level listeners are notified. A detached element only\n * bubbles up to its detached subtree root, which is not window, so dispatching from it directly\n * would not reach those listeners.\n */\nfunction dispatchCartUpdateErred(dispatcher: EventTarget | null,\n  detail: CartUpdateErredEvent['detail']) {\n  // Dispatch the failure from whatever target the caller designates; this forms no opinion about\n  // what kind of target it is. The only special handling exists to keep the event reaching the\n  // global listeners: a detached element has no live ancestor chain to bubble through, so it is\n  // swapped for the global scope, and a null target means the global scope outright. Any other\n  // target, including a connected element, is used as given. Callers should still prefer the\n  // element most directly responsible for the failure, so a connected element carries truthful\n  // provenance and bubbles, crossing shadow boundaries, up to the global listeners.\n\n  let target: EventTarget;\n  if (dispatcher instanceof Element) {\n    if (dispatcher.isConnected) {\n      target = dispatcher;\n    } else {\n      target = window;\n    }\n  } else if (dispatcher) {\n    target = dispatcher;\n  } else {\n    target = window;\n  }\n\n  const event = new CustomEvent<typeof detail>('cart-update-erred', {\n    detail,\n    bubbles: true,\n    composed: true\n  });\n\n  target.dispatchEvent(event);\n}\n\n/**\n * Surfaces a genuine programmer error to the global error handler so that monitoring records it.\n *\n * Some failures are expected outcomes with their own user facing messaging rather than programmer\n * errors: a shopper adding an out of stock item, or mistyping a discount code. Those are never\n * escalated to monitoring. Each such failure is identified by its code, the same classification the\n * cart helpers attach for the interface, so the list here must keep pace with the codes those\n * helpers produce.\n *\n * The throw is deferred to a later task so that it escapes the surrounding promise chain instead of\n * rejecting it, preserving the rejection-free contract of the cart helpers. The error keeps the\n * stack captured when it was first created, so the report still points at the origin of the failure\n * rather than at this deferred throw. However, beware that this does mean that the error event\n * properties will reflect this throw context, not the original error context, which means that the\n * instrumenting code must prioritize the original error properties over the error event properties.\n *\n * When available, the throw is routed through the reportError api, which surfaces the value to the\n * global error handler without the extra task hop. Older engines that lack it fall back to a\n * deferred throw, which reaches the same handler.\n *\n * The context describing the failure is attached to the error so that monitoring which serializes\n * the non-standard properties of an error can include it. A value that is not an error is wrapped\n * so that the context still has somewhere to live.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/reportError\n */\nfunction escalateCartError(detail: CartUpdateErredEvent['detail']) {\n  // These are expected outcomes with their own user facing messaging rather than programmer errors.\n  //\n  // This list of codes to ignore deliberately excludes invalid selling plan codes such as\n  // ADD_ITEMS_INVALID_SELLING_PLAN, ADD_ITEMS_INVALID_BACKORDER_SELLING_PLAN, and\n  // ADD_ITEMS_INVALID_TBYB_SELLING_PLAN. Unlike the ephemeral shopper conditions, a rejected\n  // selling plan is a plan we attached to the item, so it signals that something is misconfigured\n  // rather than a transient outcome. We want these to continue reaching error diagnostics so that\n  // misconfiguration is visible.\n\n  const ignoredCodes = [\n    'APPLY_DISCOUNTS',\n    'ADD_ITEMS_OOS',\n    'ADD_ITEMS_BUNDLE_ADDON_MIX',\n    'ADD_ITEMS_MAX_QUANTITY',\n    'CHANGE_CART_OOS',\n    'UPDATE_CART_OOS'\n  ];\n\n  if (detail && !ignoredCodes.includes(detail.code)) {\n    let error: any;\n\n    if (detail.error instanceof Error) {\n      error = detail.error;\n    } else {\n      error = new Error(`${detail.error}`);\n    }\n\n    decorateError(error, 'code', detail.code);\n    decorateError(error, 'inputs', detail.inputs);\n    decorateError(error, 'scenario', detail.scenario);\n    decorateError(error, 'cart', detail.cart);\n    decorateError(error, 'transaction_id', detail.transaction?.id);\n    decorateError(error, 'transaction_name', detail.transaction?.name);\n\n    if (typeof reportError === 'function') {\n      reportError(error);\n    } else {\n      setTimeout(raise, 0, error);\n    }\n  }\n}\n\nfunction raise(value: unknown) {\n  throw value;\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","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","/**\n * Returns a deep clone of the given value.\n *\n * Prefers structuredClone and falls back to a JSON round trip. The fallback covers both an\n * environment without structuredClone and one whose structuredClone cannot be trusted. On the\n * storefront a compromised browser can leave a monkey-patched structuredClone that returns\n * undefined for a real object rather than cloning it, which otherwise surfaces as confusing\n * property-access failures far from the cause. The values cloned through here are plain data that\n * round trips faithfully through JSON, so the fallback is equivalent rather than a degradation.\n */\nexport function copy<T>(value: T) {\n  if (typeof structuredClone === 'function') {\n    const cloned = structuredClone(value);\n\n    // A healthy structuredClone returns a nullish result only for a nullish input, so a nullish\n    // clone of a non-nullish value means the primitive cannot be trusted. Fall through to the\n    // serialization clone in that case.\n\n    const inputPresent = value !== null && value !== undefined;\n    const cloneMissing = cloned === null || cloned === undefined;\n\n    let trustworthy = true;\n    if (inputPresent && cloneMissing) {\n      trustworthy = false;\n    }\n\n    if (trustworthy) {\n      return cloned;\n    }\n  }\n\n  return <T>JSON.parse(JSON.stringify(value));\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { containsJavascriptWord } from '../../untrusted-value.js';\n\n/**\n * Returns whether a discount code string is well-formed. This does not guarantee the discount code\n * is valid and in the applicable state. That is why this function is not named isValid.\n *\n * This tolerates any kind of input so as to provide a convenient API. The caller is not obligated\n * to check that the input is a string beforehand. This function exists to avoid that boilerplate.\n * However, callers should be providing values that are strings.\n */\nexport function isWellFormed(value: any) {\n  if (typeof value !== 'string') {\n    return false;\n  }\n\n  if (value.length === 0) {\n    return false;\n  }\n\n  // This catches abuse of path parameters\n  if (value.includes('/') || value.includes('\\\\')) {\n    return false;\n  }\n\n  if (value.includes(' ')) {\n    return false;\n  }\n\n  const upper = value.toUpperCase();\n\n  // This catches common js bugs in the calling code\n  if (upper === 'UNDEFINED' || upper === 'NULL') {\n    return false;\n  }\n\n  if (containsJavascriptWord(upper)) {\n    return false;\n  }\n\n  return true;\n}\n\n/**\n * Returns normalized array of discount code strings currently applied to the cart.\n *\n * This filters out codes from the cart's discount_codes array property where the applicable value\n * is not true. That is what distinguishes this function from {@link getDiscountCodes}.\n *\n * This normalizes the codes. Shopify does not guarantee the code strings are uppercase. This will\n * uppercase.\n *\n * Tolerates null/undefined cart. Returns an empty array. This is safe to use without checking\n * whether cart is defined.\n *\n * Tolerates invalid discount_codes property. This supports a rare case where the cart was SSRed\n * correctly, but then not fetched. The SSR state is missing the property. In this case an empty\n * array is returned.\n */\nexport function getApplicableDiscountCodes(cart: Cart) {\n  if (!cart) {\n    return [];\n  }\n\n  if (!Array.isArray(cart.discount_codes)) {\n    return [];\n  }\n\n  return cart.discount_codes\n    .filter(code => code.applicable)\n    .map(code => code.code)\n    .map(code => code.toUpperCase());\n}\n\n/**\n * Returns all codes currently in the cart's discount codes array.\n *\n * This includes applicable as well as inapplicable codes. This inclusion of inapplicable codes is\n * what distinguishes this from the function {@link getApplicableDiscountCodes}.\n *\n * The returned codes are always uppercase. This must uppercase the codes returned because Shopify\n * does not guarantee the codes are upper case. Codes applied at checkout are saved in whatever\n * input case was used. The logic that applies codes while shopping can guarantee the input codes\n * are in uppercase but the logic at checkout cannot because the checkout logic is managed by\n * Shopify.\n *\n * It is possible that the discount_codes array property in the input cart is undefined. Shopify\n * does not support rendering the discount_codes array in liquid. There is a situation where the\n * cart state was server side rendered without discount_codes and then we failed to update the state\n * and therefore failed to set the property dynamically during init. This tolerates that so as to\n * guarantee that an array is always returned.\n */\nexport function getDiscountCodes(cart: Cart) {\n  if (!cart) {\n    return [];\n  }\n\n  if (!Array.isArray(cart.discount_codes)) {\n    return [];\n  }\n\n  return cart.discount_codes.map(code => code.code).map(code => code.toUpperCase());\n}\n\n/**\n * Totals up the discount amounts the cart has allocated and returns them as a map. Values are in\n * cents.\n *\n * The keys are discount application titles in uppercase rather than discount codes. For an ordinary\n * code those are the same string, which is what lets a caller match this map against the cart's\n * discount codes. They are not guaranteed to agree though, because Shopify treats the title as the\n * customer facing name of the discount rather than as the code. So a caller can end up holding a\n * title here that matches none of the codes on the cart.\n *\n * A code missing from this map has not necessarily done nothing. It means the cart allocated no\n * amount to it. A free shipping code behaves that way. It sits among the cart's discount codes as\n * applicable while appearing in neither array read here, because shipping is not priced until\n * checkout.\n *\n * Tolerates a null or undefined cart, and a cart missing any of the arrays this reads. Returns an\n * empty map. This is safe to use without checking whether the cart is defined.\n */\nexport function getDiscountAmounts(cart: Cart) {\n  // Shopify keeps discounts in two separate places, split by scope, so we read both to find all of\n  // them. A discount covering the cart as a whole lands in cart_level_discount_applications. A\n  // discount covering particular products lands on each line item holding one of those products, in\n  // that item's line_level_discount_allocations.\n  //\n  // The two do not overlap, which is what makes it safe to add both into one map without counting a\n  // discount twice. We checked this in real carts from both directions. A cart wide code showed up\n  // only in the cart level array with the line allocations empty, and a product code showed up only\n  // on the lines with the cart level array empty.\n  //\n  // A product discount is allocated separately to every line it touches, so its total is the sum of\n  // those per line amounts. That is what the running total is for, and it is also why the same code\n  // can appear many times while we walk the lines.\n  //\n  // Note which field each loop reads, because the two arrays do not name their amounts the same\n  // way. The line loop reads the allocation's amount, which is the share allocated to that one\n  // line. The application sitting inside that allocation carries a total_allocated_amount too, and\n  // in the carts we looked at it happened to equal the same share, but it describes the application\n  // rather than the line, so we do not read it there.\n  //\n  // We skip anything that is not a discount code. Automatically applied discounts and script\n  // applied discounts land in these same arrays, but they carry no code for a caller to match\n  // against, so they do not belong in a map keyed this way.\n\n  const discountAmounts = new Map<string, number>();\n\n  if (!cart) {\n    return discountAmounts;\n  }\n\n  // The isArray checks in the following code are considered acceptable paranoia. We want this to\n  // work in bad environments with partial carts, which we have surprisingly encountered.\n\n  // Discounts covering the cart as a whole\n\n  if (Array.isArray(cart.cart_level_discount_applications)) {\n    for (const application of cart.cart_level_discount_applications) {\n      if (application.type === 'discount_code') {\n        const code = application.title.toUpperCase();\n        const discount = application.total_allocated_amount;\n        const currentTotal = discountAmounts.get(code) || 0;\n        discountAmounts.set(code, currentTotal + discount);\n      }\n    }\n  }\n\n  // Discounts covering particular products, spread over the lines holding those products\n\n  if (Array.isArray(cart.items)) {\n    for (const lineItem of cart.items) {\n      if (!Array.isArray(lineItem.line_level_discount_allocations)) {\n        continue;\n      }\n\n      for (const allocation of lineItem.line_level_discount_allocations) {\n        if (allocation.discount_application.type === 'discount_code') {\n          const code = allocation.discount_application.title.toUpperCase();\n          const discount = allocation.amount;\n          const currentTotal = discountAmounts.get(code) || 0;\n          discountAmounts.set(code, currentTotal + discount);\n        }\n      }\n    }\n  }\n\n  return discountAmounts;\n}\n","/**\n * Returns whether a string contains the word \"javascript\" anywhere. This is a lightweight heuristic\n * for rejecting obviously-junk externally-sourced values before they enter state, not real XSS\n * defense, which happens more strictly at the actual sinks. The broad match suits values from a\n * constrained alphabet, such as discount codes, where the word never legitimately appears.\n */\nexport function containsJavascriptWord(value: any) {\n  return typeof value === 'string' && /javascript/i.test(value);\n}\n\n/**\n * Returns whether a string contains the \"javascript:\" URI scheme. This is a lightweight heuristic\n * for rejecting the classic scheme-injection vector before a value enters state, not real XSS\n * defense, which happens more strictly at the actual sinks. Unlike {@link containsJavascriptWord}\n * this tolerates the bare word, so a value that is itself an arbitrary external URL, such as a\n * referrer, is not falsely rejected merely for mentioning javascript in a path.\n */\nexport function containsJavascriptScheme(value: any) {\n  return typeof value === 'string' && /javascript:/i.test(value);\n}\n","import { assert } from '@integrabeauty/assert';\nimport { type Cart, cartFetch as cartFetchOnce, isCart } from '@integrabeauty/shopify-ajax-api';\nimport { v4 as uuidv4 } from 'uuid';\nimport { copy } from '../object.js';\nimport { getApplicableDiscountCodes } from './discount/discount.js';\nimport type { Transaction } from './transaction.js';\n\n/**\n * This event is generated when the state of the cart changes. The event includes the old state and\n * the new state and some deltas.\n *\n * Various cart api calls can result in unexpected behavior. For example, adding an item to cart\n * could cause the line key of unrelated cart lines to change. Therefore, this does not generate\n * deltas based on line key. In addition, each time cart state is obtained, there is no guarantee\n * that the state only changed because of the specified change. For example, a visitor interacting\n * with the same cart in multiple tabs may update the state in one tab, but because the deltas are\n * based on the prior state per tab, the next update in one of the tabs would diff against older\n * state. Therefore, listeners should refrain from operating on deltas and always treat each cart\n * update holistically.\n */\ntype CartUpdatedEvent = CustomEvent<{\n  /**\n   * The new cart state.\n   */\n  cart: Cart;\n\n  /**\n   * Discount codes present in the new cart state that are not present in the old cart state.\n   *\n   * This only includes discount codes. This does not include automatically applied discounts or\n   * script applied discounts.\n   */\n  discount_codes_added: string[];\n\n  /**\n   * Discount codes present in the old cart state that are not present in the new cart state.\n   *\n   * This only includes discount codes. This does not include automatically applied discounts or\n   * script applied discounts.\n   */\n  discount_codes_removed: string[];\n\n  /**\n   * A unique event id. This is useful if tracking this event client side and server side and there\n   * is a need to correlate front end and backend events.\n   */\n  event_id: string;\n\n  /**\n   * Whether this event represents a cart initialization.\n   *\n   * The diffs for an initial update are not useful. In this case the transition is from the empty\n   * state. Do not treat an initial update as a non-initial update.\n   */\n  is_initial?: boolean;\n\n  /**\n   * The previous state of the cart.\n   *\n   * This may be null. For example, when initializing the cart.\n   */\n  previous: Cart;\n\n  /**\n   * Certain transactions provide a description of the context in which the transaction occurred.\n   * For example, two add to cart elements might specify two scenarios when adding to cart, which\n   * then generates an update event with different scenarios, which can then be relayed downstream\n   * to analytics to differentiate in reporting.\n   *\n   * This is primarily an analytics label, but be aware that some consumers also branch on specific\n   * values as behavior, for example suppressing UI reactions to updates the shopper did not\n   * initiate in this tab. Certain values are therefore effectively part of behavior, not only\n   * reporting, so renaming one can change how the UI reacts, not just how a change is reported.\n   *\n   * This is intentionally a broad type. This is not meant to be a type safe enumeration. There are\n   * no heavy safeguards here.\n   */\n  scenario?: string;\n\n  /**\n   * Details of the cart service transaction that triggered this update event.\n   */\n  transaction: {\n    /**\n     * Transaction id\n     */\n    id: number;\n\n    /**\n     * Transaction name\n     */\n    name: string;\n  };\n\n  /**\n   * An array of variant units added to cart. This includes both variants added as new line items\n   * and quantity increases of existing line items.\n   *\n   * A product variant in this array does not contain all the properties of a cart item because we\n   * cannot trivially discern which lines changed when a cart updates.\n   */\n  variants_added: ProductVariant[];\n\n  /**\n   * Variants removed from cart. This can be removal of lines or quantity decreases.\n   *\n   * A product variant does not contain all the properties of a cart item because we cannot\n   * trivially discern which lines changed as a result of a state change.\n   */\n  variants_removed: ProductVariant[];\n}>;\n\n/**\n * Represents a product variant. A Shopify product has one or more variants. A Shopify product with\n * no explicitly configured variant still has a default variant.\n *\n * This should not be confused with a canonical product variant that exists in a single line item in\n * a cart object. This is an amalgamation, a composition of the variant as it exists in one or more\n * line items in a cart. This assumes that price and other properties are the same, apart from the\n * special merged_properties property, which combines the line item properties of the lines\n * containing the variant together.\n */\ninterface ProductVariant {\n  /**\n   * Path to variant image\n   *\n   * @example \"//langehair.com/cdn/shop/products/filename.jpg?v=1680740790\"\n   */\n  image_url: string;\n\n  /**\n   * The combined line item properties of the line items containing this variant\n   *\n   * The key of this object is the property name.\n   *\n   * The value for each key in this object are the distinct values that were merged together.\n   */\n  merged_properties: Record<string, string[]>;\n\n  price: number;\n  product_id: number;\n  product_title: string;\n  product_type: string;\n  quantity: number;\n  sku: string;\n\n  /**\n   * Product variant url (relative)\n   *\n   * @example \"/products/salt-sea-texturizing-spray?variant=41345560477796\"\n   */\n  url: string;\n\n  variant_id: number;\n  variant_title: string;\n}\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'cart-updated': CartUpdatedEvent;\n  }\n}\n\n/**\n * Sets the current state of the cart. This is the in memory state that is stored as the value of a\n * global variable, not the server side state.\n *\n * In memory state is PER TAB PER WINDOW/APP. If someone is browsing in multiple tabs concurrently\n * the state may conflict. The cart token corresponds to a server side cart id and is stored in a\n * cookie that is shared among all tabs, so it is possible for multiple tabs to refer to the same\n * cart object.\n *\n * Always use this method to update the state of the global cart variable. Do not update the state\n * of the global cart variable directly.\n *\n * Throws an error if the given cart is not a cart. This also indicates a more insidious problem\n * where there is no longer a guarantee that client side state reflects server side state, so\n * consider some error handling.\n *\n * This saves a copy of the cart so that future updates by reference to the cart object do not\n * modify the global cart state.\n *\n * Emit a cart updated event that contains some of the differences. This emits the updated event\n * even when there are no changes detected.\n *\n * Any update can result in unrelated cart changes. Changing one line's properties could cause line\n * merging. Also, prior state comes from page state, which is per tab, so interactions in multiple\n * tabs can produce strange behavior.\n *\n * The dispatcher is the target the updated event is dispatched from. It is required but may be\n * null, and it may be any event target, not necessarily an element or window. The target is used as\n * given, with two exceptions that exist only to keep the event reaching window level listeners: a\n * null target dispatches from window, and so does a detached element. A detached element only\n * bubbles up to its detached subtree root, which is not window, so dispatching from it directly\n * would not reach those listeners. Callers should prefer the element most directly responsible for\n * the change, so a connected element carries truthful provenance and bubbles, crossing shadow\n * boundaries, up to the global listeners. Genuinely ambient updates with no originating element,\n * such as init or a cross tab sync, pass window (or null).\n */\nexport function write(dispatcher: EventTarget | null, cart: Cart, transaction: Transaction,\n  scenario?: string, initial = false) {\n  const previous = initial ? null : read();\n\n  // Update global state.\n  assert(isCart(cart), 'Cannot write invalid cart');\n  cart_lifecycle.cart = copy(cart);\n\n  // Prepare for diffing. We group by variant because line keys change in unexpected ways.\n  const oldVariants = groupByVariant(previous);\n  const newVariants = groupByVariant(cart);\n\n  // For discount code deltas, we only examine deltas in codes that both exist and are in the\n  // applicable state in the cart's discount_codes array.\n\n  const prevCodes = getApplicableDiscountCodes(previous);\n  const currCodes = getApplicableDiscountCodes(cart);\n\n  // Emit an event with diffs\n\n  type Detail = CartUpdatedEvent['detail'];\n  const event = new CustomEvent<Detail>('cart-updated', {\n    detail: {\n      cart,\n      discount_codes_added: complement(currCodes, prevCodes),\n      discount_codes_removed: complement(prevCodes, currCodes),\n      event_id: uuidv4(),\n      is_initial: initial,\n      previous,\n      scenario,\n      variants_added: findVariantsAdded(oldVariants, newVariants),\n      variants_removed: findVariantsRemoved(oldVariants, newVariants),\n      transaction: {\n        id: transaction.id,\n        name: transaction.name\n      }\n    },\n    bubbles: true,\n    composed: true\n  });\n\n  // Dispatch the update from whatever target the caller designates; this forms no opinion about\n  // what kind of target it is. The only special handling exists to keep the event reaching the\n  // global listeners: a detached element has no live ancestor chain to bubble through, so it is\n  // swapped for the global scope, and a null target means the global scope outright. Any other\n  // target, including a connected element, is used as given, so it carries truthful provenance and\n  // bubbles, crossing shadow boundaries, up to the global listeners.\n\n  let target: EventTarget;\n  if (dispatcher instanceof Element) {\n    if (dispatcher.isConnected) {\n      target = dispatcher;\n    } else {\n      target = window;\n    }\n  } else if (dispatcher) {\n    target = dispatcher;\n  } else {\n    target = window;\n  }\n\n  target.dispatchEvent(event);\n}\n\n/**\n * Returns a copy of the global cart state.\n *\n * Throws an error if the global cart object is not a cart object.\n */\nexport function read() {\n  assert(isCart(cart_lifecycle.cart), 'Global cart state is invalid on read');\n  return copy(cart_lifecycle.cart);\n}\n\ninterface FetchOptions {\n  /**\n   * The total number of requests to make, counting the first one.\n   */\n  attempts?: number;\n\n  /**\n   * How long to wait in milliseconds before each retry, one entry per retry.\n   */\n  delays?: number[];\n\n  /**\n   * How long in milliseconds to wait on a single attempt before counting it as failed. Leaving it\n   * out means each attempt waits as long as the request takes.\n   */\n  timeout?: number;\n}\n\n/**\n * Reads the server side cart state.\n *\n * This wraps the ajax api call of the same name and is what cart code here should reach for\n * instead. The only things it adds are a few attempts and an optional time limit on each one, so it\n * behaves the same otherwise.\n *\n * A cart read is idempotent and it sits in the middle of things the shopper is waiting on, like\n * adding to cart, so a single failed request is worth another go rather than turning into an error\n * the shopper sees. We make up to a few attempts, waiting a little longer before each retry. If\n * every attempt fails then the error from the last one is thrown and the caller decides what that\n * means. Some callers treat it as fatal, some stay quiet about it, so this forms no opinion.\n *\n * Do not use this to react to a mutation you just made unless you are fine with a repeat request,\n * because each attempt sends a fresh one.\n *\n * Callers can pass options to change how many attempts are made, how long to wait between them, and\n * how long a single attempt gets before it counts as failed. Leaving them out gets the defaults\n * below, and no time limit.\n *\n * There is no limit on the total time across all attempts. The caller is responsible for picking\n * options that keep the worst case acceptable, which is every attempt using its full time limit\n * plus every wait between them.\n */\nexport async function cartFetch(options: FetchOptions = {}) {\n  // How many times we are willing to ask the server before giving up. The first attempt counts, so\n  // this is the total number of requests and not the number of retries on top of one.\n  const limit = options.attempts || 3;\n\n  // How long we wait before each retry. Shopify throttles cart reads, so going right back at it\n  // just piles more requests onto something that already told us to slow down. These stay short as\n  // a balance between waiting long enough to avoid another 429 and waiting so long the user\n  // perceives the UI as broken.\n  const delays = options.delays || [100, 250];\n\n  let lastError: Error;\n\n  for (let attempts = 0; attempts < limit; attempts++) {\n    if (attempts > 0) {\n      const delay = delays[attempts - 1];\n      await resolveAfterTimeout(delay);\n    }\n\n    try {\n      const cart = await cartFetchOnceWithTimeout(options.timeout);\n      return cart;\n    } catch (error) {\n      lastError = error;\n    }\n  }\n\n  throw lastError;\n}\n\n/**\n * Reads the cart once, giving up after the given number of milliseconds. Leaving out the timeout\n * means there is no limit.\n *\n * Giving up does not stop the request. It keeps going in the background and whatever it eventually\n * returns is ignored. That is fine because reading the cart changes nothing, but it means this must\n * never be used for a cart write, since a write that times out here can still land later.\n */\nasync function cartFetchOnceWithTimeout(timeout: number) {\n  if (!timeout) {\n    return cartFetchOnce();\n  }\n\n  let timer: ReturnType<typeof setTimeout>;\n  const timeoutPromise = new Promise<never>((_resolve, reject) => {\n    const error = new Error('Timed out fetching cart');\n    timer = setTimeout(reject, timeout, error);\n  });\n\n  try {\n    const cart = await Promise.race([cartFetchOnce(), timeoutPromise]);\n    return cart;\n  } finally {\n    clearTimeout(timer);\n  }\n}\n\n/**\n * Returns a promise which resolves after the given timeout\n */\nfunction resolveAfterTimeout(milliseconds: number) {\n  return new Promise(resolve => {\n    setTimeout(resolve, milliseconds);\n  });\n}\n\ntype ProductVariantMap = Record<string, ProductVariant>;\n\n/**\n * Aggregates cart line items by variant. Returns the aggregated variants as an object where each\n * key of the object is a variant id and each value is the corresponding variant object.\n *\n * This does some complex stuff. One key thing to constantly point out is that a variant can appear\n * in more than one line item in a cart. There are several reasons why. Shopify generally refers to\n * this as a variant split. Variant splits happen for reasons such as having different line item\n * properties, or different prices as a result of allocated discounted applications, or because of\n * more rare reasons like product subscriptions. Because we are grouping by variant id, we have to\n * invent some novel ways of combining the different values of the line items containing the same\n * variant.\n *\n * To combine the line item properties, we invent a new property, merged_properties, that contains\n * the properties of each line item. In each merged properties value, there is an array of values.\n * When appending to the values array, we only append unique values (using exact value comparison).\n *\n * There are many downstream concerns. Here is one example. When relaying an add to cart event to a\n * third party analytics system, we sometimes want the product's image url. This function's output\n * is the input to the variants_added array of the cart updated event emitted as a result of a cart\n * write. So we have to embed the image url here in the variant object so that it can be accessed\n * without having to do some kind of later lookup.\n *\n * @param cart the input cart to aggregate\n */\nfunction groupByVariant(cart: Cart) {\n  const output: ProductVariantMap = {};\n\n  // It is important to return null here, and not the empty object, so that later functions that use\n  // this output can trivially distinguish between transitions from null state and transitions from\n  // empty cart state. The transition from null state happens on init.\n\n  if (!cart) {\n    return null;\n  }\n\n  for (const item of cart.items) {\n    const variant = output[item.variant_id.toString()];\n    if (variant) {\n      // Merge the item's properties into the variant's properties.\n      for (const property in item.properties || {}) {\n        variant.merged_properties[property] = variant.merged_properties[property] || [];\n        const value = item.properties[property];\n        if (!variant.merged_properties[property].includes(value)) {\n          variant.merged_properties[property].push(value);\n        }\n      }\n\n      variant.quantity += item.quantity;\n    } else {\n      // Merge in the initial properties, where each value is a one value array.\n      const properties: Record<string, string[]> = {};\n      for (const key in item.properties || {}) {\n        properties[key] = [item.properties[key]];\n      }\n\n      output[item.variant_id.toString()] = {\n        image_url: item.image,\n        merged_properties: properties,\n        // eslint-disable-next-line @typescript-eslint/no-deprecated\n        price: item.price,\n        product_id: item.product_id,\n        product_title: item.product_title,\n        product_type: item.product_type,\n        quantity: item.quantity,\n        sku: item.sku,\n        url: item.url,\n        variant_id: item.variant_id,\n        variant_title: item.variant_title\n      };\n    }\n  }\n\n  return output;\n}\n\n/**\n * Returns an array of variant data objects that were added as a result of the write mutation.\n *\n * This tolerates nulls\n *\n * @param c0map variants in old cart state\n * @param c1map variants in new cart state\n * @returns variants added array\n */\nfunction findVariantsAdded(c0map: ProductVariantMap, c1map: ProductVariantMap) {\n  // When the cart transitions from the null state, there are no previous variants. This means that\n  // all variants in the new state have been added. Transitioning from null can mean multiple\n  // things. It can mean either that cart state was initialized on page load, or that cart state was\n  // recovered from error. Because of the ambiguity we are not emitting adds for transitions from\n  // the null state.\n\n  if (!c0map) {\n    return [];\n  }\n\n  // Figure out which variants had quantity increases in any of the line items containing those\n  // variants. Any variant that had a quantity increase is referred to here as an increment. In\n  // addition, look for any variants that are present in the new state but not in the old state, in\n  // which case their full quantity is the quantity added.\n\n  const increments: ProductVariantMap = {};\n  for (const key in c1map) {\n    if (c0map[key]) {\n      // In this branch, the variant was present in the old map and is present also in the new map.\n\n      if (c1map[key].quantity > c0map[key].quantity) {\n        increments[key] = copy(c1map[key]);\n        increments[key].quantity = c1map[key].quantity - c0map[key].quantity;\n      } else {\n        // ignore 0 quantity change and negative quantity change (decrements).\n      }\n    } else {\n      // In this branch, the variant is present in the new map but not in the old map.\n\n      increments[key] = copy(c1map[key]);\n    }\n  }\n\n  return Object.keys(increments).map(key => increments[key]);\n}\n\nfunction findVariantsRemoved(c0map: ProductVariantMap, c1map: ProductVariantMap) {\n  if (!c0map) {\n    return [];\n  }\n\n  const decrements: ProductVariantMap = {};\n  for (const key in c0map) {\n    if (c1map[key]) {\n      if (c1map[key].quantity < c0map[key].quantity) {\n        decrements[key] = copy(c1map[key]);\n        decrements[key].quantity = c0map[key].quantity - c1map[key].quantity;\n      } else {\n        // ignore 0 quantity change\n      }\n    } else {\n      // if the old variant is not found in the updated cart, all quantity was removed\n      decrements[key] = copy(c0map[key]);\n    }\n  }\n\n  return Object.keys(decrements).map(key => decrements[key]);\n}\n\n/**\n * Return all values present in array a that are not present in array b.\n *\n * The complement of a set is the set that includes all the elements of the universal set that are\n * not present in the given set. Here, a is the universal set and b is the given set.\n */\nfunction complement(a: string[], b: string[]) {\n  assert(Array.isArray(a), 'complement parameter \"a\" is not an array');\n  assert(Array.isArray(b), 'complement parameter \"b\" is not an array');\n  return a.filter(value => !b.includes(value));\n}\n","/**\n * Returns a timestamp in milliseconds for measuring durations.\n *\n * This decorates performance.now().\n *\n * This tries to use the native high-resolution epoch, falling back to Date.now().\n *\n * If native throws, this still returns a value, but it logs a message as a side effect.\n *\n * This happens in a try/catch, so it is safe.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/now\n */\nexport function now() {\n  try {\n    if (typeof performance?.now === 'function') {\n      return performance.now();\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n\n  return Date.now();\n}\n\n/**\n * Records a named point on the User Timing timeline in devtools.\n *\n * This decorates performance.mark within a try/catch that suppresses errors.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark\n */\nexport function mark(name: string) {\n  try {\n    if (typeof performance?.mark === 'function') {\n      performance.mark(name);\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n}\n\n/**\n * Records a named span between two timestamps on the User Timing timeline in devtools.\n *\n * This decorates performance.measure within a try/catch that suppresses errors.\n *\n * The bounds are explicit timestamps rather than named marks, which avoids the error the marked\n * name form throws when a start mark was never recorded.\n *\n * The detail param is optional and must be structured-cloneable. It should be a plain object. If\n * the value cannot be cloned, then performance.measure throws, which is caught and logged by this\n * wrapper. If specified, the detail shows up in the tooltip in the devtools panel.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure\n */\nexport function measure(name: string, start: number, end: number, detail?: unknown) {\n  try {\n    if (typeof performance?.measure === 'function') {\n      performance.measure(name, { start, end, detail });\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n}\n","import { measure, now } from '../perf.js';\n\ntype CartTransactionStartedEvent = CustomEvent<Transaction>;\ntype CartTransactionCompletedEvent = CustomEvent<Transaction>;\n\n/**\n * The bookkeeping shared by every copy of this module. Each entry bundles its own copy, so the\n * numbers and the timeline have to sit on the window for the copies to agree on them.\n */\ninterface State {\n  /** The number of currently active cart service transactions. */\n  activeCount: number;\n\n  /** Number of cart operations started so far, and the source of transaction ids. */\n  count: number;\n\n  /** A rolling log of recent transaction start and end entries. */\n  timeline: Record<string, any>[];\n}\n\ndeclare global {\n  var cart_transaction: State;\n\n  interface WindowEventMap {\n    'cart-transaction-completed': CartTransactionCompletedEvent;\n    'cart-transaction-started': CartTransactionStartedEvent;\n  }\n}\n\n/**\n * Obtain the shared state, initializing it as needed.\n *\n * We do not assume the global state is reliably present and instead access state via this function\n * every time because we have seen very surprising errors in the wild to which we wish to remain\n * resilient, even at the cost of generating artificial and inaccurate state.\n */\nfunction getSharedState() {\n  window.cart_transaction = window.cart_transaction || { activeCount: 0, count: 0, timeline: [] };\n\n  const shared = window.cart_transaction;\n\n  if (!Number.isInteger(shared.activeCount) || shared.activeCount < 0) {\n    shared.activeCount = 0;\n  }\n\n  if (!Number.isInteger(shared.count)) {\n    shared.count = 0;\n  }\n\n  if (!Array.isArray(shared.timeline)) {\n    shared.timeline = [];\n  }\n\n  return shared;\n}\n\n/**\n * A transaction represents an action carried out by the cart service. For example, an add to cart\n * transaction or a remove from cart transaction.\n *\n * A transaction is a plain record minted by the start function and completed through the handle it\n * carries. The bookkeeping fields live on the record because completion is a module function that\n * operates on the record rather than a method that closes over it.\n */\nexport interface Transaction {\n  /** The arguments to the start of the transaction */\n  args: any[];\n\n  /** Complete this transaction. Idempotent, and reachable only through the handle. */\n  complete?(didTimeOut?: boolean): void;\n\n  /** Whether completion has already run, so a second call is a no-op */\n  completed: boolean;\n\n  /** The transaction start time in milliseconds */\n  epoch: number;\n\n  /** Transaction unique global id */\n  id: number;\n\n  /** Transaction name, e.g. function name */\n  name: string;\n\n  /**\n   * The transaction start time on the monotonic clock, used only to compute duration.\n   *\n   * This is separate from epoch, which stays wall-clock so it still answers when the transaction\n   * happened. The two clocks have different origins, so never mix them in one subtraction.\n   */\n  startMillis: number;\n\n  /** Timeout timer id */\n  timer?: ReturnType<typeof setTimeout>;\n}\n\n/**\n * Start a cart service transaction and return its handle. Use the handle's complete method to end\n * the transaction.\n */\nexport function start(name: string, ...args: any[]) {\n  const shared = getSharedState();\n\n  // Increment the shared transaction id. A simple monotonic counter ensures that no two\n  // transactions have the same id (but see the caveats mentioned in getting shared state).\n\n  shared.count++;\n\n  const transaction: Transaction = {\n    args,\n    epoch: Date.now(),\n    id: shared.count,\n    name,\n    completed: false,\n    startMillis: now()\n  };\n\n  // Expose complete as a \"method\" of the returned object. Callers are expected to complete the\n  // transaction via its own method. This is better than exporting the top level complete function\n  // as a free function where callers could mistakenly complete the wrong transaction.\n\n  transaction.complete = (didTimeOut = false) => complete(transaction, didTimeOut);\n\n  // Increment the number of active transactions. This should be a transition from 0 to 1.\n\n  shared.activeCount++;\n\n  // If as a result of the increment there is now more than one active transaction, report the\n  // problem but continue. The goal is not to error. Many transactions are able to reconcile from\n  // overlapping flights.\n\n  if (shared.activeCount > 1) {\n    reportConcurrency(transaction);\n  }\n\n  // Record the transaction in the active transactions timeline. This is a fresh object instead of\n  // the original transaction so that it snapshots.\n\n  append({ epoch: transaction.epoch, id: transaction.id, name: transaction.name, type: 'start' });\n\n  // Start a timer that ensures the transaction settles eventually. Ensuring this is important\n  // because the upper layers typically lock down the UI during a transaction, and we want to\n  // prevent locking the UI indefinitely.\n\n  transaction.timer = setTimeout(complete, 30 * 1000, transaction, true);\n\n  console.log('Cart transaction started', transaction.name);\n\n  type Detail = CartTransactionStartedEvent['detail'];\n  const event = new CustomEvent<Detail>('cart-transaction-started', { detail: transaction });\n  dispatchEvent(event);\n\n  return transaction;\n}\n\n/**\n * Complete a transaction.\n *\n * Appends the transaction to the transaction history log.\n *\n * Emits a transaction completed event. Various transactions should complete when done to signal the\n * completion to listeners.\n *\n * This runs either manually whenever the transaction completes, or automatically, if the\n * transaction did not complete quickly enough (e.g. because of an error, omission, or a genuinely\n * long operation that timed out).\n *\n * Transactions should be short-lived. Transactions are automatically considered complete after a\n * certain amount of time so as to guarantee completion. Higher layer interfaces rely on transaction\n * state to lock interfaces for periods of time. Locking must be definite.\n *\n * Completion is idempotent. The timeout safety net and the caller's own completion can both fire\n * for one transaction when a tab is suspended long enough for the operation to outlive the timeout,\n * as happens on a backgrounded mobile tab that resumes. Whichever fires first records the outcome\n * and any later call is a no-op, so the active transaction count decrements exactly once per start\n * and cannot drift negative and defeat the interface locks that read it.\n */\nfunction complete(transaction: Transaction, didTimeOut = false) {\n  // A transaction that already timed out lands here when its operation finally finishes, so we log\n  // how long it really took. That tells a stalled request that eventually came back apart from one\n  // that never did.\n\n  if (transaction.completed) {\n    const duration = Math.round(now() - transaction.startMillis);\n    console.log('Cart transaction already completed', transaction.name, didTimeOut, duration);\n    return;\n  }\n\n  transaction.completed = true;\n\n  clearTimeout(transaction.timer);\n  transaction.timer = undefined;\n\n  append({\n    epoch: Date.now(),\n    id: transaction.id,\n    name: transaction.name,\n    timeout: didTimeOut,\n    type: 'end'\n  });\n\n  const shared = getSharedState();\n\n  if (shared.activeCount > 0) {\n    shared.activeCount--;\n  }\n\n  if (didTimeOut) {\n    console.log('Cart transaction timed out', transaction.name);\n  }\n\n  console.log('Cart transaction completed', transaction.name);\n\n  // Capture performance data representing each transaction's duration.\n\n  measure(`cart-${transaction.name}-${transaction.id}`, transaction.startMillis, now(), {\n    id: transaction.id,\n    name: transaction.name,\n    timeout: didTimeOut\n  });\n\n  type Detail = CartTransactionCompletedEvent['detail'];\n  const event = new CustomEvent<Detail>('cart-transaction-completed', { detail: transaction });\n  dispatchEvent(event);\n}\n\n/**\n * Capture a concurrent transaction in way that observers can see it\n */\nfunction reportConcurrency(transaction: Transaction) {\n  const state = getSharedState();\n\n  // Snapshot the state. The reason for this is because the state that is rendered in the devtools\n  // console is the state of the live reference when the value is viewed in the log, which is after\n  // the time it was recorded. Snapshotting saves the value at the time of recording it.\n  //\n  // This is a shallow snapshot. Since the timeline is append only, we are basically just recording\n  // the timeline up to the time of the snapshot, e.g. basically this is just a partial view of an\n  // array that may have more entries by the time it is expanded but those entries are hidden.\n\n  const snapshot = [...state.timeline];\n\n  console.warn('Concurrent transaction started', {\n    id: transaction.id,\n    name: transaction.name,\n    timeline: snapshot\n  });\n}\n\n/**\n * Add a timeline entry.\n *\n * The type of entry here is intentionally general because this has a relaxed opinion on what gets\n * stored.\n */\nfunction append(entry: Record<string, any>) {\n  const state = getSharedState();\n  const timeline = state.timeline;\n\n  timeline.push(entry);\n\n  const capacity = 50;\n\n  // Keep only the most recent entries so the timeline does not grow without bound over a long\n  // session. Removing from the front drops the oldest; the clamp guards against a negative removal\n  // count while the timeline is still under the cap.\n  // TODO: the clamp may be unnecessary, since splice already treats a negative delete count as\n  // zero, so it is likely defensive rather than required. Verify and drop it if so.\n  timeline.splice(0, Math.max(timeline.length - capacity, 0));\n}\n","import { type CartChangeInput, type Cart, cartChange, isUnavailableError } from '@integrabeauty/shopify-ajax-api';\nimport { digest } from './digest.js';\nimport { hasSimilarItems } from './equality.js';\nimport { report } from './error.js';\nimport { cartFetch, read, write } from './state.js';\nimport { start } from './transaction.js';\n\n/**\n * Change the cart's line item quantities, note, selling plan allocation or attributes.\n *\n * @param target the element most directly responsible for the change, used to originate any failure\n * event with truthful provenance instead of at global scope\n * @param input cart change input\n * @param scenario optional description or code describing the context in which this takes place\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-change-js\n */\nexport async function change(target: EventTarget | null, input: CartChangeInput,\n  scenario?: string) {\n  const transaction = start('changeCart', input);\n\n  let previous: Cart;\n  let remoteCart: Cart;\n\n  try {\n    // It is possible that the client side cart state is stale, so we need to grab it in order to\n    // compare it to the remote state.\n    previous = read();\n\n    // Grab the remote state\n    remoteCart = await cartFetch();\n\n    if (!hasSimilarItems(previous, remoteCart)) {\n      // Update local state to reflect the latest remote state.\n      write(target, remoteCart, transaction, scenario);\n\n      // Define a custom error code when throwing this error so that calling code can extract a more\n      // specific error code.\n\n      const error: any = new Error('Cart state out of sync during changeCart');\n      error.error_code = 'CHANGE_ITEMS_DESYNC';\n      throw error;\n    }\n\n    await cartChange(input);\n    const cart = await cartFetch();\n    write(target, cart, transaction, scenario);\n  } catch (error) {\n    report(target, {\n      code: determineErrorCode(error),\n      error,\n      inputs: {\n        input,\n        previous_cart: digest(previous),\n        fetched_cart: digest(remoteCart)\n      },\n      scenario,\n      transaction\n    });\n  } finally {\n    transaction.complete();\n  }\n}\n\nfunction determineErrorCode(error: any) {\n  if (isUnavailableError(error)) {\n    return 'CHANGE_CART_OOS';\n  }\n\n  if ('error_code' in error) {\n    return error.error_code;\n  }\n\n  return 'CHANGE_CART';\n}\n","import { assert } from '@integrabeauty/assert';\nimport { type Cart, cartChange } from '@integrabeauty/shopify-ajax-api';\nimport { digest } from './digest.js';\nimport { hasSimilarItems } from './equality.js';\nimport { report } from './error.js';\nimport { cartFetch, read, write } from './state.js';\nimport { start } from './transaction.js';\n\n/**\n * Removes items from the cart.\n *\n * Each id in the array can be either a line key or a variant id. A cart can have multiple line\n * items that share the same variant id. For example, when variants have different line item\n * properties, or automatic discounts create variants at different prices. Because of this, it is\n * recommended to use the line item key to ensure that you're only changing the intended line item.\n *\n * If you want to also change other cart properties in the same call, use the more general\n * updateCart call instead while specifying lines with quantity 0 to effectively remove. This\n * removeItems call is just a convenient but limited way of doing a cart update.\n *\n * Even though Shopify permits specifying a variant id as a number, we require that it is a string\n * because we have seen strange, undocumented errors when specifying variant ids as numbers, such as\n * some kind of server side Ruby error with parsing numbers.\n *\n * @param target the element most directly responsible for the removal, used to originate any\n * failure event with truthful provenance instead of at global scope\n * @param lineKeys the line keys or variant ids of the items to remove\n * @param scenario optional description or code describing the context in which this takes place\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#update-line-item-quantities\n */\nexport async function remove(target: EventTarget | null, lineKeys: string[], scenario?: string) {\n  const transaction = start('removeItems', lineKeys, scenario);\n  let errorCode = 'REMOVE_ITEMS';\n\n  let previous: Cart;\n  let remoteCart: Cart;\n\n  try {\n    assert(lineKeys.length > 0, 'At least one item must be specified');\n\n    // Even though shopify supports variant ids as change item ids as numbers, this function does\n    // not support using variant id. This function only operates on line keys, which must always be\n    // strings. Even though we have type safety checks, we still want a runtime check, as the\n    // calling code is typically less diligent.\n    assert(lineKeys.every(isString), 'One or more invalid line keys');\n\n    // Ensure the caller did not provide duplicates\n    const uniqueKeySet = new Set(lineKeys);\n    assert(uniqueKeySet.size === lineKeys.length, 'One or more duplicate line keys');\n\n    // cart/update supports removing multiple items at once, but cart/change only supports changing\n    // a single item at once. While it would be ideal to use cart/update, cart/update has an\n    // undocumented bug where attempting to set the quantity of an out of stock line item to 0\n    // results in a 422 error. We reached out to Shopify support, and they stated that we should use\n    // cart/change instead. So now removing multiple items at once gets complicated, because we have\n    // no real transactional safety. In fact, we should probably drop support for removing multiple\n    // items at once and fully migrate the use cases for removing multiple items to other solutions,\n    // e.g. use the Shopify cart transforms for bulk add/remove/update, such as what the Shopify\n    // bundles app does. In the short term we are going to attempt to support bulk removal by\n    // sequentially removing items.\n\n    // Removing multiple items at once, or at least attempting to support the invented abstraction\n    // of an atomic mutation on the cart that is actually a compound mutation, is further\n    // complicated by how cart/change behaves. Another undocumented feature of cart/change is that\n    // changing any one item's quantity to 0 can cause other line items to change their keys. The\n    // keys of other lines change in an undocumented manner. It is not clear if affecting a single\n    // line can change any other line, or only lines that have a later position. I think what is\n    // happening is that the keys of later lines shift when the quantity of an earlier line is\n    // changed to 0. So, the idea is that if we remove lines in reverse order, we avoid the issue\n    // with shifting keys per change.\n\n    // Clarified, removing any one line can impact the keys of all other lines. So we cannot remove\n    // by key in a batch, because each single removal affects the rest.\n\n    // The next problem then is that the calling code is not obligated to provide us line keys in\n    // natural cart order. So we have to find a way to get the natural order. In which case the next\n    // problem is that in order to figure that out we need the current cart state, which is not\n    // provided as input. So we have to get the current cart state and assume it is authoritative.\n\n    previous = read();\n\n    // Verify that the cart state is not stale by fetching the latest server side cart state prior\n    // to mutation.\n\n    remoteCart = await cartFetch();\n\n    if (!hasSimilarItems(previous, remoteCart)) {\n      // Update local state to reflect the latest remote state.\n      write(target, remoteCart, transaction, scenario);\n\n      errorCode = 'REMOVE_ITEMS_DESYNC';\n      throw new Error('Cart state out of sync during removeItems');\n    }\n\n    // Verify that the number of lines to remove is less than or equal to the number of items in the\n    // cart. It is possible that the visitor is looking at a stale cart state and then trying to\n    // remove one or more items from a stale cart state. One thing we can detect is that when the\n    // cart state is stale, there should still always be enough items in the cart to remove. This is\n    // somewhat redundant with a later assertion but it still gives us a more exact error.\n    assert(lineKeys.length <= previous.items.length, 'Too many line keys');\n\n    const lineIndicesToRemove = [];\n    for (let i = 0; i < previous.items.length; i++) {\n      const item = previous.items[i];\n      if (lineKeys.includes(item.key)) {\n        lineIndicesToRemove.push(i + 1);\n      }\n    }\n\n    assert(lineIndicesToRemove.length === lineKeys.length, 'One or more line keys not found');\n\n    // Reorder from last to first, as each removal changes the position of later ones\n    lineIndicesToRemove.reverse();\n\n    // Then perform the removals. We cannot perform the removals concurrently. We have to perform\n    // the removals sequentially as racing them concurrently carries the risk that the removals\n    // occur in the order we did not intend.\n\n    for (const line of lineIndicesToRemove) {\n      await cartChange({ line, quantity: 0 });\n    }\n\n    // Because we intentionally ignore the output of each cart/change, obtain the updated cart state\n    // through yet another request.\n\n    const cart = await cartFetch();\n    write(target, cart, transaction, scenario);\n  } catch (error) {\n    report(target, {\n      code: errorCode,\n      error,\n      inputs: {\n        line_keys: lineKeys,\n        previous_cart: digest(previous),\n        fetched_cart: digest(remoteCart)\n      },\n      scenario,\n      transaction\n    });\n  } finally {\n    transaction.complete();\n  }\n}\n\nfunction isString(value: any): value is string {\n  return typeof value === 'string';\n}\n","import { type CartUpdateInput, type Cart, cartUpdate, isUnavailableError } from '@integrabeauty/shopify-ajax-api';\nimport { digest } from './digest.js';\nimport { hasSimilarItems } from './equality.js';\nimport { report } from './error.js';\nimport { cartFetch, read, write } from './state.js';\nimport { start } from './transaction.js';\n\n/**\n * Update the cart's line item quantities, note, or attributes.\n *\n * @param target the element most directly responsible for the update, used to originate any failure\n * event with truthful provenance instead of at global scope\n * @param input cart update input\n * @param scenario optional description or code describing the context in which this takes place\n *\n * @see https://shopify.dev/docs/api/ajax/reference/cart#post-locale-cart-update-js\n */\nexport async function update(target: EventTarget | null, input: CartUpdateInput,\n  scenario?: string) {\n  const transaction = start('updateCart', input, scenario);\n\n  let previous: Cart;\n  let remoteCart: Cart;\n\n  try {\n    // It is possible that the client side cart state is stale, so we need to grab it in order to\n    // compare it to the remote state.\n    previous = read();\n\n    // Grab the remote state\n    remoteCart = await cartFetch();\n\n    if (!hasSimilarItems(previous, remoteCart)) {\n      // Update local state to reflect the latest remote state.\n      write(target, remoteCart, transaction, scenario);\n\n      // Define a custom error code when throwing this error so that calling code can extract a more\n      // specific error code.\n\n      const error: any = new Error('Cart state out of sync during updateCart');\n      error.error_code = 'UPDATE_ITEMS_DESYNC';\n      throw error;\n    }\n\n    await cartUpdate(input);\n    const cart = await cartFetch();\n    write(target, cart, transaction, scenario);\n  } catch (error) {\n    report(target, {\n      code: determineErrorCode(error),\n      error,\n      inputs: {\n        input,\n        previous_cart: digest(previous),\n        fetched_cart: digest(remoteCart)\n      },\n      scenario,\n      transaction\n    });\n  } finally {\n    transaction.complete();\n  }\n}\n\nfunction determineErrorCode(error: any) {\n  if (isUnavailableError(error)) {\n    return 'UPDATE_CART_OOS';\n  }\n\n  if ('error_code' in error) {\n    return error.error_code;\n  }\n\n  return 'UPDATE_CART';\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport type { CartChangeInput, CartUpdateInput } from '@integrabeauty/shopify-ajax-api';\nimport { change } from '../../../lib/cart/change.js';\nimport { remove } from '../../../lib/cart/remove.js';\nimport { read } from '../../../lib/cart/state.js';\nimport { update } from '../../../lib/cart/update.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders a cart line item, with the ability for the user to adjust quantities, and remove the\n * item.\n */\nclass CartLineItem extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-is-available',\n      'data-max-quantity',\n      'data-line-item-id',\n      'data-line-price-after-discount',\n      'data-line-price',\n      'data-price',\n      'data-image-src',\n      'data-image-alt',\n      'data-title',\n      'data-quantity',\n      'data-variant-id',\n      'data-parent-id',\n      'data-product-handle',\n      'data-product-id',\n      'data-product-type',\n      'data-update',\n      'data-selling-plan-id',\n      'data-is-upsell'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Country ISO code. Required.\n     *\n     * @example \"US\"\n     */\n    countryCode: string;\n\n    /**\n     * Currency ISO Code. Required\n     */\n    currencyCode: string;\n\n    /**\n     * Currency Symbol. Required\n     */\n    currencySymbol: string;\n\n    /**\n     * Image alternate text.\n     */\n    imageAlt: string;\n\n    /**\n     * Image url.\n     */\n    imageSrc: string;\n\n    /**\n     * Whether the item has inventory that is sellable.\n     */\n    isAvailable: string;\n\n    /**\n     * Boolean string indicating this is a parent line item with child items (e.g. GWP). Parent line\n     * items have controls for quantity and deletion, and child line items are visually indented\n     * with no quantity or delete controls because they are bound to the parent line item via the\n     * Shopify API.\n     */\n    isParent: 'false' | 'true';\n    /**\n     * Whether the item is an upsell item that qualifies for special pricing or logic.\n     */\n    isUpsell: string;\n\n    /**\n     * The id of the item in the cart\n     */\n    lineItemId: string;\n\n    /**\n     * The price before discounts. This could be either the MSRP (the compare at price) or it could\n     * be the markdown down (the sales price).\n     *\n     * This is for the entire line, not per unit, so this takes into account quantity.\n     */\n    linePrice: string;\n\n    /**\n     * This is the price after discounts. This is for the entire line, not per unit, so this also\n     * takes into account quantity.\n     */\n    linePriceAfterDiscount: string;\n\n    /**\n     * The maximum quantity of the item that can be added to cart. This controls whether certain\n     * features, such as a UI element for incrementing quantity, are enabled. Some items have a\n     * limitation that they can only be included once in a cart because they are offered at a\n     * reduced price under specific conditions.\n     */\n    maxQuantity: string;\n\n    /**\n     * If present, this line item is a child of another line item (e.g. GWP). The parentId is the\n     * Shopify line item key of the parent (format: \"variantId:propertiesHash\"). Child items have\n     * controls hidden because Shopify API automatically replicates parent actions (quantity\n     * changes, deletions) to all children.\n     */\n    parentId: string;\n    /**\n     * The price of the item (per unit)\n     */\n    price: string;\n\n    /**\n     * The Shopify url slug of the product in the line item.\n     */\n    productHandle: string;\n\n    /**\n     * The Shopify product id\n     */\n    productId: string;\n\n    /**\n     * The Shopify product type\n     *\n     * @example \"hair-care\"\n     */\n    productType: string;\n\n    /**\n     * A url for the PDP of the product.\n     */\n    productUrl: string;\n\n    /**\n     * The number of items\n     */\n    quantity: string;\n\n    /**\n     * String representation of the selling plan. This is used to display the selling plan delivery\n     * frequency in the UI.\n     */\n    sellingPlanFrequency: string;\n\n    /**\n     * If the item has an associated selling plan, e.g. it is a subscription item, then this is the\n     * id of the related selling plan that has all of the specifics of the subscription.\n     */\n    sellingPlanId: string;\n\n    /**\n     * The line item display title, which is typically the product title.\n     */\n    title: string;\n\n    /**\n     * Setting this attribute to a new value triggers a render. This exists so that we can trivially\n     * change several attributes without triggering a render per attribute change, then set this one\n     * attribute to trigger a single render.\n     */\n    update: string;\n\n    /**\n     * The id of the variant represented by the cart line item\n     */\n    variantId: string;\n  };\n\n  private onCartTransactionStartedBound = this.onCartTransactionStarted.bind(this);\n  private onCartTransactionCompletedBound = this.onCartTransactionCompleted.bind(this);\n\n  private onSellingPlanChangedBound = this.onSellingPlanChanged.bind(this);\n  private onUpgradeButtonClickBound = this.onUpgradeButtonClick.bind(this);\n  private onRemoveLineItemClickBound = this.onRemoveLineItemClick.bind(this);\n  private onQuantityChangedBound = this.onQuantityChanged.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    const removeButton = this.shadowRoot.querySelector<HTMLElement>('.remove-line-item');\n    removeButton.addEventListener('click', this.onRemoveLineItemClickBound);\n\n    const quantity = this.shadowRoot.querySelector<HTMLSelectElement>('.quantity');\n    quantity.addEventListener('change', this.onQuantityChangedBound);\n\n    addEventListener('cart-transaction-started', this.onCartTransactionStartedBound);\n    addEventListener('cart-transaction-completed', this.onCartTransactionCompletedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    const removeButton = this.shadowRoot.querySelector<HTMLElement>('.remove-line-item');\n    removeButton?.removeEventListener('click', this.onRemoveLineItemClickBound);\n\n    const quantity = this.shadowRoot.querySelector<HTMLSelectElement>('.quantity');\n    quantity.removeEventListener('change', this.onQuantityChangedBound);\n\n    // These two live in our light DOM and only one of them is ever there, depending on whether the\n    // item is already a subscription. render() puts the listener on whichever one it finds, so we\n    // take it back off here to match.\n    const sellingPlanSelect = this.querySelector<HTMLSelectElement>('.selling-plan-options');\n    sellingPlanSelect?.removeEventListener('change', this.onSellingPlanChangedBound);\n\n    const upgradeButton = this.querySelector<HTMLButtonElement>('.upgrade-to-subscription');\n    upgradeButton?.removeEventListener('click', this.onUpgradeButtonClickBound);\n\n    removeEventListener('cart-transaction-started', this.onCartTransactionStartedBound);\n    removeEventListener('cart-transaction-completed', this.onCartTransactionCompletedBound);\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 onQuantityChanged(event: Event) {\n    console.debug('Cart line quantity changed');\n\n    if (!(event.target instanceof HTMLSelectElement)) {\n      return;\n    }\n\n    const quantity = parseInt(event.target.value, 10);\n\n    const updates = {\n      [this.dataset.lineItemId]: quantity\n    };\n\n    const previous = read();\n\n    // Child quantities are bound to parent via Shopify API, but we explicitly include them here for\n    // optimistic UI updates before the API response completes\n    for (const item of previous.items) {\n      if (item.parent_relationship?.parent_key === this.dataset.lineItemId) {\n        updates[item.key] = quantity;\n      }\n    }\n\n    const input: CartUpdateInput = {\n      updates\n    };\n    const scenario = 'cart-line-item-quantity-changed';\n    const dispatcher = this;\n    update(dispatcher, input, scenario).catch(console.warn);\n  }\n\n  private onRemoveLineItemClick(event: MouseEvent) {\n    console.debug('Cart line remove clicked', { trusted: event.isTrusted });\n\n    if (event.target instanceof HTMLButtonElement && !event.target.disabled) {\n      const lineKeys = [this.dataset.lineItemId];\n      const scenario = 'cart-line-item-remove-click';\n      const dispatcher = this;\n      remove(dispatcher, lineKeys, scenario).catch(console.warn);\n    }\n  }\n\n  private onCartTransactionStarted(_event: WindowEventMap['cart-transaction-started']) {\n    const actionElements = this.shadowRoot.querySelectorAll<HTMLButtonElement | HTMLSelectElement>(\n      '.cart-actions');\n    for (const actionElement of actionElements) {\n      actionElement.disabled = true;\n    }\n\n    const select = this.querySelector<HTMLSelectElement>('.selling-plan-options');\n    if (select) {\n      select.disabled = true;\n    }\n\n    const upgradeButton = this.querySelector<HTMLButtonElement>('.upgrade-to-subscription');\n    if (upgradeButton) {\n      upgradeButton.disabled = true;\n    }\n  }\n\n  private onCartTransactionCompleted(_event: WindowEventMap['cart-transaction-completed']) {\n    const actionElements = this.shadowRoot.querySelectorAll<HTMLButtonElement | HTMLSelectElement>(\n      '.cart-actions');\n    for (const actionElement of actionElements) {\n      actionElement.disabled = false;\n    }\n\n    const select = this.querySelector<HTMLSelectElement>('.selling-plan-options');\n    if (select) {\n      select.disabled = false;\n    }\n\n    const upgradeButton = this.querySelector<HTMLButtonElement>('.upgrade-to-subscription');\n    if (upgradeButton) {\n      upgradeButton.disabled = false;\n    }\n  }\n\n  private render() {\n    const removeButton = this.shadowRoot.querySelector<HTMLElement>('.remove-line-item');\n\n    const lineItemEl = this.shadowRoot.querySelector('.line-item');\n    lineItemEl.id = this.dataset.variantId;\n\n    const primaryImageElement =\n      this.shadowRoot.querySelector<HTMLElementTagNameMap['image-element']>('#image');\n\n    if (!primaryImageElement.dataset.src) {\n      primaryImageElement.dataset.alt = '';\n      primaryImageElement.dataset.src = this.dataset.imageSrc;\n    }\n\n    // Child items (e.g. GWP) use smaller images to create visual hierarchy\n    if (this.dataset.parentId) {\n      primaryImageElement.dataset.width = '90';\n    }\n\n    // Possible 'undefined' to be a string type when passed undefined value via data attribute\n    if (!this.dataset.imageSrc || this.dataset.imageSrc === 'undefined') {\n      console.warn('Product missing image', this.dataset.variantId, this.dataset.title);\n    }\n\n    if (this.dataset.productUrl) {\n      const featuredImageAnchor = this.shadowRoot.querySelector('clickable-area');\n      if (featuredImageAnchor) {\n        featuredImageAnchor.dataset.url = this.dataset.productUrl;\n      }\n    }\n\n    const lineItemTitle = this.shadowRoot.querySelector<HTMLAnchorElement>('#title');\n\n    if (this.dataset.productUrl) {\n      lineItemTitle.href = this.dataset.productUrl;\n    } else {\n      lineItemTitle.title = 'This product does not have a page to view';\n    }\n\n    // Although we can use textContent to assign the title text to the anchor element, we\n    // intentionally use innerHTML to ensure any HTML entities (i.e. &amp;) are decoded.\n    lineItemTitle.innerHTML = this.dataset.title;\n\n    const linePriceAfterDiscountEl =\n      this.shadowRoot.querySelector<HTMLElement>('#line-price-after-discount');\n    linePriceAfterDiscountEl.dataset.currencyCode = this.dataset.currencyCode;\n    linePriceAfterDiscountEl.dataset.currencySymbol = this.dataset.currencySymbol;\n    linePriceAfterDiscountEl.dataset.subunits = this.dataset.linePriceAfterDiscount.toString();\n\n    // element with id line-price is the \"compare at price\"\n    const linePriceEl = this.shadowRoot.querySelector<HTMLElement>('#line-price');\n    linePriceEl.dataset.currencyCode = this.dataset.currencyCode;\n    linePriceEl.dataset.currencySymbol = this.dataset.currencySymbol;\n    linePriceEl.dataset.subunits = this.dataset.linePrice.toString();\n\n    if (parseInt(this.dataset.linePrice, 10) >\n      parseInt(this.dataset.linePriceAfterDiscount, 10)) {\n      this.shadowRoot.querySelector('.price').classList.remove('no-discount');\n      linePriceEl.dataset.ariaLabelPrefix = 'Regular price:';\n      linePriceAfterDiscountEl.dataset.ariaLabelPrefix = 'Sale price:';\n    } else {\n      this.shadowRoot.querySelector('.price').classList.add('no-discount');\n      linePriceEl.dataset.ariaLabelPrefix = '';\n      linePriceAfterDiscountEl.dataset.ariaLabelPrefix = 'Price:';\n    }\n\n    if (this.dataset.isAvailable === 'false') {\n      this.shadowRoot.querySelector<HTMLElement>('#out-of-stock').hidden = false;\n    }\n\n    if (this.dataset.sellingPlanId) {\n      // if this is a subscription item handle changing the selling plan\n      const select = this.querySelector<HTMLSelectElement>('.selling-plan-options');\n      if (select) {\n        select.addEventListener('change', this.onSellingPlanChangedBound);\n      }\n    } else {\n      // if not a subscription but selling plans are available - allow upgrading to a subscription\n      const upgradeButton = this.querySelector<HTMLButtonElement>('.upgrade-to-subscription');\n      if (upgradeButton) {\n        upgradeButton.addEventListener('click', this.onUpgradeButtonClickBound);\n      }\n    }\n\n    // Child items have quantity/delete controls hidden - Shopify API replicates all parent actions\n    // (quantity changes, deletions) to children automatically\n    if (this.dataset.parentId) {\n      const controlsRow = this.shadowRoot.querySelector<HTMLElement>('.line-item-bottom');\n      controlsRow.hidden = true;\n      return;\n    }\n\n    removeButton?.setAttribute('aria-label', ['remove', this.dataset.title, 'from cart'].join(' '));\n\n    this.renderQuantity();\n  }\n\n  /**\n   * When a user clicks the upgrade button, this sets the selling plan of the line item to the one\n   * specified in the button's selling plan id attribute.\n   *\n   * The upgrade button is not part of this element's template. The parent cart-line-items section\n   * creates it, sets data-selling-plan-id (the plan to upgrade to, which is not the same thing as\n   * our own data-selling-plan-id), and slots it into our light DOM. We read that attribute back\n   * here, hence the untyped dataset access. The comment where the parent sets it has the why.\n   *\n   * The parent creates the button as an HTMLButtonElement, and we read the current target rather\n   * than the target, so the instanceof check should always pass.\n   *\n   * Event listeners cannot be async, and promises must have explicit error handling. The underlying\n   * changeCart call emits errors via a side-channel error event instead of throwing. Therefore we\n   * basically ignore the error in the promise chain since it only happens in extreme edge cases.\n   */\n  private onUpgradeButtonClick(event: MouseEvent) {\n    if (event.currentTarget instanceof HTMLButtonElement) {\n      const input: CartChangeInput = {\n        id: this.dataset.lineItemId,\n        selling_plan: parseInt(event.currentTarget.dataset.sellingPlanId, 10)\n      };\n      const scenario = 'cart-line-item-upgrade-click';\n      const dispatcher = this;\n      change(dispatcher, input, scenario).catch(console.warn);\n    }\n  }\n\n  /**\n   * There is no more specific type of event for change events, so Event is the correct type. React\n   * has a virtual ChangeEvent type but that is not applicable here.\n   */\n  private onSellingPlanChanged(event: Event) {\n    // Documenting a previous bug for posterity. This previously was hard casting event.target to\n    // HTMLOptionElement. That is incorrect because change events are always fired on the select\n    // element, not its options. It was obscured because both HTMLSelectElement and\n    // HTMLOptionElement have a value property. Second, this now uses event.currentTarget instead of\n    // event.target. Both happened to be the same select here (the listener is on the select, and\n    // change does not bubble from the options), so this was not a live bug, but currentTarget is\n    // the more robust expression of intent: it is by definition the element the listener is bound\n    // to. Third, we prefer a runtime check over a hard cast precisely because it avoids this class\n    // of bugs.\n\n    if (event.currentTarget instanceof HTMLSelectElement) {\n      const input: CartChangeInput = {\n        id: this.dataset.lineItemId,\n        selling_plan: parseInt(event.currentTarget.value, 10)\n      };\n      const scenario = 'cart-line-item-selling-plan-changed';\n      const dispatcher = this;\n\n      // We intentionally omit error handling here because the underlying cart module will handle.\n      change(dispatcher, input, scenario).catch(console.warn);\n    }\n  }\n\n  private renderQuantity() {\n    const quantityElement = this.shadowRoot.querySelector<HTMLSelectElement>('.quantity');\n\n    if (this.dataset.isUpsell === 'true') {\n      // Only one item of an upsell product can be added, so no need to render the quantity selector\n\n      quantityElement.hidden = true;\n      return;\n    }\n\n    if (this.dataset.isAvailable === 'false') {\n      quantityElement.disabled = true;\n    }\n\n    // Since switching the quantity control to a select element, we should have predefined options\n    // in the HTML. For now, we've agreed to let users add up to 5 items, but this is negotiable.\n    const defaultMaxQuantity = 10;\n    const quantity = parseInt(this.dataset.quantity, 10);\n\n    let maxQuantity = defaultMaxQuantity;\n    if (this.dataset.maxQuantity) {\n      maxQuantity = parseInt(this.dataset.maxQuantity, 10);\n    }\n\n    if (maxQuantity < quantity) {\n      maxQuantity = quantity;\n    }\n\n    if (maxQuantity !== defaultMaxQuantity) {\n      // Currently, we set data-max-quantity to a value of 1 for the cart line item only for upsell\n      // products. That means in most cases, we will be rendering the default options, so this check\n      // prevents updating the DOM if not needed.\n\n      let optionsHtml = '';\n      for (let i = 1; i <= maxQuantity; i++) {\n        optionsHtml += `<option value=\"${i}\">${i}</option>`;\n      }\n\n      quantityElement.innerHTML = optionsHtml;\n    }\n\n    quantityElement.value = this.dataset.quantity;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cart-line-item': CartLineItem;\n  }\n}\n\nif (!customElements.get('cart-line-item')) {\n  customElements.define('cart-line-item', CartLineItem);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport type { Item } from '@integrabeauty/shopify-ajax-api';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\n\ninterface SellingPlanOptions {\n  id: number;\n  name: string;\n}\n\ntype CartUpdatedEvent = GlobalEventHandlersEventMap['cart-updated'];\n\n/**\n * This is a container element for cart-line-item elements. This is responsible for rendering the\n * items whenever cart state changes.\n *\n * This does not do partial renders specific to the type of cart update. This always fully re-\n * renders after an update. Any cart update that is in response to some action (e.g. add item,\n * remove item) can contain any kind of state change. Each action such as add to cart obtains a new\n * cart state where multiple things, things not directly related to the items involved, may have\n * changed. For example, removing a line item can change the key property of other items not\n * removed. In addition, there is no pressing need to partially render because the number of items\n * is generally small whereas the amount of engineering effort to properly partially render is huge.\n *\n * Prior implementations of this logic were horrifically flawed. Please never reintroduce support\n * for, or the concept of, partial rendering.\n *\n * @deprecated this element should not have been implemented\n */\nclass CartLineItems extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Country ISO code. Required.\n     *\n     * @example \"US\"\n     */\n    countryCode: string;\n\n    /**\n     * Currency ISO Code. Required\n     */\n    currencyCode: string;\n\n    /**\n     * Currency Symbol. Required\n     */\n    currencySymbol: string;\n  };\n\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n  }\n\n  public disconnectedCallback() {\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = html;\n    }\n  }\n\n  /**\n   * Any cart update can cause many changes to the cart, including changes from other tabs. We\n   * cannot rely on line key changes because unrelated keys can change per mutation. Refresh all\n   * line items.\n   */\n  private onCartUpdated(event: CartUpdatedEvent) {\n    this.render(event.detail.cart.items);\n  }\n\n  private render(parents: Item[]) {\n    // Pre-compute parent keys to enable single-pass rendering. This avoids nested loops over the\n    // full cart for each item when identifying which items have children.\n    const uniqueParentKeys = new Set();\n    for (const item of parents) {\n      if (item.parent_relationship?.parent_key) {\n        uniqueParentKeys.add(item.parent_relationship.parent_key);\n      }\n    }\n\n    const fragment = document.createDocumentFragment();\n    for (const item of parents) {\n      if (item.parent_relationship?.parent_key) {\n        // Child items (e.g. GWP) are rendered nested within their parent, skip in top-level loop\n        continue;\n      }\n\n      const element = this.createItemElement(item);\n      if (uniqueParentKeys.has(item.key)) {\n        element.dataset.isParent = 'true';\n        this.appendChildItems(element, item.key, parents);\n      }\n\n      fragment.appendChild(element);\n    }\n\n    this.innerHTML = '';\n    this.appendChild(fragment);\n  }\n\n  private appendChildItems(parentElement: HTMLElement, parentKey: string, items: Item[]) {\n    for (const item of items) {\n      if (item.parent_relationship?.parent_key === parentKey) {\n        const childElement = this.createItemElement(item);\n        childElement.classList.add('child-item');\n        parentElement.appendChild(childElement);\n      }\n    }\n  }\n\n  private createItemElement(item: Item) {\n    const lineItemEl = document.createElement('cart-line-item');\n    lineItemEl.setAttribute('role', 'listitem');\n    lineItemEl.dataset.countryCode = this.dataset.countryCode;\n    lineItemEl.dataset.currencyCode = this.dataset.currencyCode;\n    lineItemEl.dataset.currencySymbol = this.dataset.currencySymbol;\n    lineItemEl.dataset.imageSrc = item.image;\n    lineItemEl.dataset.imageAlt = item.title;\n    lineItemEl.dataset.isAvailable = 'true';\n    lineItemEl.dataset.lineItemId = item.key;\n    lineItemEl.dataset.variantId = item.variant_id.toString();\n    lineItemEl.dataset.title = item.title;\n    lineItemEl.dataset.quantity = item.quantity.toString();\n    lineItemEl.dataset.productType = item.product_type;\n    lineItemEl.dataset.productUrl = item.url;\n    lineItemEl.dataset.price = item.final_price.toString();\n    lineItemEl.dataset.linePrice = item.original_line_price.toString();\n    lineItemEl.dataset.linePriceAfterDiscount = item.final_line_price.toString();\n\n    if (item.parent_relationship?.parent_key) {\n      lineItemEl.dataset.parentId = item.parent_relationship.parent_key;\n    }\n\n    if (item.properties._is_upsell === 'true') {\n      lineItemEl.dataset.isUpsell = 'true';\n      lineItemEl.dataset.maxQuantity = '1';\n    }\n\n    if (item.handle) {\n      lineItemEl.dataset.productHandle = item.handle;\n    }\n\n    if (item.product_id) {\n      lineItemEl.dataset.productId = item.product_id.toString();\n    }\n\n    // Child items (e.g. GWP) are free promotional products, so subscription options don't apply\n    if (item.selling_plan_allocation && !item.parent_relationship?.parent_key) {\n      lineItemEl.dataset.sellingPlanId = item.selling_plan_allocation.selling_plan.id.toString();\n      lineItemEl.dataset.sellingPlanFrequency =\n        item.selling_plan_allocation.selling_plan.name.replace('Delivery every ', '');\n      if (item.properties['_selling_plan_options']) {\n        const select = document.createElement('select');\n        select.classList.add('selling-plan-options');\n        select.slot = 'selling-plan-options';\n\n        const options = JSON.parse(item.properties['_selling_plan_options']);\n\n        // If the parsed JSON does not conform to what is expected, we simply do not render and\n        // inject the selling plan options input\n        if (isSellingPlanOptionsArray(options)) {\n          for (const option of options) {\n            const optionEl = document.createElement('option');\n            optionEl.value = option.id.toString();\n            optionEl.textContent = option.name;\n            if (option.id === item.selling_plan_allocation.selling_plan.id) {\n              optionEl.selected = true;\n            }\n\n            select.appendChild(optionEl);\n          }\n\n          lineItemEl.appendChild(select);\n        }\n      }\n    } else if (item.properties['_selling_plan_options'] && !item.parent_relationship?.parent_key) {\n      // If not a subscription but selling plans are available - allow upgrading to a subscription\n      // using the first available selling plan. Child items are excluded since these kind of\n      // interactions are bound to their parent line item.\n      let sellingPlanId: number;\n      try {\n        const sellingPlans = <{ id: number; name: string; }[]>JSON.parse(\n          item.properties['_selling_plan_options']);\n        sellingPlanId = sellingPlans[0].id;\n      } catch (error) {\n        console.warn('Invalid _selling_plan_options', item.properties['_selling_plan_options'],\n          error);\n      }\n\n      if (sellingPlanId) {\n        const upgradeButton = document.createElement('button');\n        upgradeButton.textContent = 'Subscribe & Save';\n        // This is the plan to upgrade to, so it means something different than the line item\n        // element's own data-selling-plan-id, which is the plan the item is already on. It lives on\n        // the button rather than on the element so that offering a choice of plans later is just\n        // more buttons. The line item element reads it back off the button on click.\n        upgradeButton.dataset.sellingPlanId = sellingPlanId.toString();\n        upgradeButton.slot = 'selling-plan-options';\n        upgradeButton.classList.add('upgrade-to-subscription');\n        upgradeButton.setAttribute('data-click-track', 'Cart subscription upgrade');\n        lineItemEl.appendChild(upgradeButton);\n      }\n    }\n\n    const messages = generateMessages(item, this.dataset.currencySymbol);\n\n    // Child items inherit messaging context from parent, suppress duplicates to avoid UI clutter\n    if (messages.length > 0 && !item.parent_relationship?.parent_key) {\n      const ulTemplate = this.shadowRoot.querySelector<HTMLTemplateElement>('#message-list');\n      const ulClone = <HTMLElement>ulTemplate.content.cloneNode(true);\n      const ul = ulClone.querySelector('ul');\n\n      for (const message of messages) {\n        const itemTemplate = this.shadowRoot.querySelector<HTMLTemplateElement>('#message');\n        const itemClone = <HTMLElement>itemTemplate.content.cloneNode(true);\n        const itemEl = itemClone.querySelector('li');\n        itemEl.classList.add(message.type);\n\n        const itemMessage = itemEl.querySelector<HTMLSpanElement>('.message');\n        itemMessage.innerHTML = message.message;\n\n        ul.appendChild(itemEl);\n      }\n\n      lineItemEl.appendChild(ul);\n    }\n\n    lineItemEl.dataset.update = Date.now().toString();\n\n    return lineItemEl;\n  }\n}\n\ninterface LineItemMessage {\n  message: string;\n  type: 'discount' | 'information' | 'warning';\n}\n\n/**\n * Builds the messages to show for a line item.\n */\nfunction generateMessages(item: Item, currencySymbol: string) {\n  const messages: LineItemMessage[] = [];\n\n  // Render the discounts allocated to the line. It is possible for allocations to allocate 0\n  // dollars of a discount application to a line. This is pretty common with discounts such as BXGY\n  // discounts that do not reduce the price of the items used in the BX part of the discount, only\n  // the GY part. The discounts are allocated to the line as $0 off, as the lines participate in the\n  // discount but are not discounted themselves. We only want to render reductions.\n\n  for (const allocation of item.line_level_discount_allocations) {\n    if (allocation.discount_application.title && allocation.amount > 0) {\n      // The allocation amount is in subunits (e.g. cents in USD), so we divide by 100. We use\n      // toFixed to ensure we show 2 significant digits\n      const value = (allocation.amount / 100).toFixed(2);\n\n      messages.push({ type: 'discount',\n        message: `${allocation.discount_application.title} (${currencySymbol}${value})` });\n    } else if (allocation.amount === 0 && /bogo/i.test(allocation.discount_application.title)) {\n      const code = allocation.discount_application.title;\n\n      let message: string;\n      if (item.quantity === 1) {\n        message = `This product qualifies another product for ${code}`;\n      } else {\n        message = `This product qualifies ${item.quantity} other products for ${code}`;\n      }\n\n      messages.push({ type: 'information', message });\n    }\n  }\n\n  if (item.properties._contains_alcohol === 'true') {\n    messages.push({ type: 'information',\n      message: 'This item may only ship to the contiguous U.S and Canada.' });\n  }\n\n  if (item.properties._compatibility === 'soleil') {\n    messages.push({ type: 'warning',\n      message: 'Only compatible with <a href=\"/products/soleil\">Soleil</a>' });\n  }\n\n  if (item.properties._compatibility === 'max-high-volume') {\n    messages.push({ type: 'warning',\n      message: 'TOOL SOLD SEPARATELY. Requires <a href=\"/products/multi-volume-series\">' +\n        'Multi-Volume Series</a> brush dryer handle to work' });\n  }\n\n  if (item.properties._final_sale === 'true') {\n    messages.push({ type: 'warning', message: 'This item is final sale, not eligible for return' });\n  }\n\n  if (item.properties._backorder_message) {\n    messages.push({ type: 'information', message: item.properties._backorder_message });\n  }\n\n  if (item.properties._is_tbyb) {\n    messages.push({ type: 'information', message: item.properties._is_tbyb });\n  }\n\n  return messages;\n}\n\nfunction isSellingPlanOptionsArray(value: any): value is SellingPlanOptions[] {\n  return Array.isArray(value) && value.filter(item => item.id && item.name).length === value.length;\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    // eslint-disable-next-line @typescript-eslint/no-deprecated\n    'cart-line-items': CartLineItems;\n  }\n}\n\nif (!customElements.get('cart-line-items')) {\n  // eslint-disable-next-line @typescript-eslint/no-deprecated\n  customElements.define('cart-line-items', CartLineItems);\n}\n","// Module\nvar code = \"<slot></slot><template id=\\\"message-list\\\"><ul slot=\\\"additional\\\" style=\\\"margin: 8px 0; padding: 0;\\\"></ul></template><template id=\\\"message\\\"><li><span class=\\\"icon information-icon\\\"><svg role=\\\"presentation\\\" width=\\\"24\\\" height=\\\"24\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><path d=\\\"M12 16V12M12 8H12.01M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12Z\\\" stroke=\\\"#1E1E1E\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"/></svg></span><span class=\\\"icon warning-icon\\\"><svg role=\\\"presentation\\\" width=\\\"24\\\" height=\\\"24\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><path d=\\\"M11.9999 9.00022V13.0002M11.9999 17.0002H12.0099M21.7299 18.0002L13.7299 4.00022C13.5555 3.69243 13.3025 3.43641 12.9969 3.25829C12.6912 3.08017 12.3437 2.98633 11.9899 2.98633C11.6361 2.98633 11.2887 3.08017 10.983 3.25829C10.6773 3.43641 10.4244 3.69243 10.2499 4.00022L2.24993 18.0002C2.07361 18.3056 1.98116 18.6521 1.98194 19.0047C1.98272 19.3573 2.07671 19.7035 2.25438 20.008C2.43204 20.3126 2.68708 20.5648 2.99362 20.7391C3.30017 20.9133 3.64734 21.0034 3.99993 21.0002H19.9999C20.3508 20.9999 20.6955 20.9072 20.9992 20.7315C21.303 20.5558 21.5551 20.3033 21.7304 19.9993C21.9057 19.6954 21.998 19.3506 21.9979 18.9997C21.9978 18.6488 21.9054 18.3041 21.7299 18.0002Z\\\" stroke=\\\"#1E1E1E\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"/></svg></span><span class=\\\"icon discount-icon\\\"><svg role=\\\"presentation\\\" width=\\\"24\\\" height=\\\"24\\\" viewBox=\\\"0 0 24 24\\\" fill=\\\"none\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\"><path d=\\\"M7.5 8C7.77614 8 8 7.77614 8 7.5C8 7.22386 7.77614 7 7.5 7C7.22386 7 7 7.22386 7 7.5C7 7.77614 7.22386 8 7.5 8Z\\\" fill=\\\"#1E1E1E\\\"/><path d=\\\"M12.586 2.586C12.211 2.2109 11.7024 2.00011 11.172 2H4C3.46957 2 2.96086 2.21071 2.58579 2.58579C2.21071 2.96086 2 3.46957 2 4V11.172C2.00011 11.7024 2.2109 12.211 2.586 12.586L11.29 21.29C11.7445 21.7416 12.3592 21.9951 13 21.9951C13.6408 21.9951 14.2555 21.7416 14.71 21.29L21.29 14.71C21.7416 14.2555 21.9951 13.6408 21.9951 13C21.9951 12.3592 21.7416 11.7445 21.29 11.29L12.586 2.586Z\\\" stroke=\\\"#1E1E1E\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"/><path d=\\\"M7.5 8C7.77614 8 8 7.77614 8 7.5C8 7.22386 7.77614 7 7.5 7C7.22386 7 7 7.22386 7 7.5C7 7.77614 7.22386 8 7.5 8Z\\\" stroke=\\\"#1E1E1E\\\" stroke-width=\\\"2\\\" stroke-linecap=\\\"round\\\" stroke-linejoin=\\\"round\\\"/></svg></span><span class=\\\"message\\\"></span></li></template>\";\n// Exports\nexport default code;","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { getEventQueue, isCartUpdatedEvent, isCartUpdateErredEvent } from '../../../lib/cart-event-queue.js';\nimport styles from './index.scss';\n\ntype CartUpdatedEvent = GlobalEventHandlersEventMap['cart-updated'];\ntype CartUpdateErredEvent = GlobalEventHandlersEventMap['cart-update-erred'];\ntype ProductVariant = CartUpdatedEvent['detail']['variants_added'][number];\n\n/**\n * Renders cart messages by building and appending alert-element elements.\n */\nclass CartMessages extends HTMLElement implements CustomElement {\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n  private onCartUpdateErredBound = this.onCartUpdateErred.bind(this);\n\n  constructor() {\n    super();\n    this.attachShadow({ mode: 'open' });\n    this.shadowRoot.innerHTML = `<style>${styles}</style>`;\n  }\n\n  public connectedCallback() {\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      } else if (isCartUpdateErredEvent(event)) {\n        try {\n          this.onCartUpdateErredBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n    addEventListener('cart-update-erred', this.onCartUpdateErredBound);\n  }\n\n  public disconnectedCallback() {\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\n    removeEventListener('cart-update-erred', this.onCartUpdateErredBound);\n  }\n\n  private onCartUpdateErred(event: CartUpdateErredEvent) {\n    // Clear any previous messages\n    const alerts = this.shadowRoot.querySelectorAll('alert-element');\n    for (const alert of alerts) {\n      alert.remove();\n    }\n\n    switch (event.detail.code) {\n      // We don't check whether the shopper has cookies turned off, since we don't trust the\n      // browser's answer enough to show an error over it. A shopper with cookies blocked or\n      // unsupported probably ends up here with the generic init error instead.\n      case 'INIT':\n        this.showInitErrorMessage();\n        break;\n\n      case 'ADD_ITEMS':\n        this.showErrorMessage('Oh no! There was a problem adding the items to your bag. ' +\n          'Please try again in a few moments.');\n        break;\n      case 'ADD_ITEMS_OOS':\n        this.showErrorMessage('Oh no! There was a problem adding the item(s) to your bag. ' +\n          'One or more of the products is out of stock.');\n        break;\n      case 'ADD_ITEMS_BUNDLE_ADDON_MIX':\n        this.showErrorMessage('Oh no! There was a problem adding the item(s) to your bag. ' +\n          'A bundle and an add-on cannot be added together. Try adding them one at a time.');\n        break;\n      case 'ADD_ITEMS_MAX_QUANTITY':\n        this.showErrorMessage('Oh no! There was a problem adding the item(s) to your bag. ' +\n          'The maximum quantity of this item is already in your cart.');\n        break;\n      case 'ADD_ITEMS_INVALID_SELLING_PLAN':\n        this.showErrorMessage('Oh no! There was a problem adding the item(s) to your bag. ' +\n          'One or more of the products is not available for subscription.');\n        break;\n      case 'ADD_ITEMS_INVALID_BACKORDER_SELLING_PLAN':\n        this.showErrorMessage('Oh no! There was a problem adding the item(s) to your bag. ' +\n          'The pre-order for one or more of the products is no longer available. ' +\n          'Try refreshing the page.');\n        break;\n      case 'ADD_ITEMS_INVALID_TBYB_SELLING_PLAN':\n        this.showErrorMessage('Oh no! There was a problem adding the item(s) to your bag. ' +\n          'One or more of the products is no longer available to try before you buy. ' +\n          'Try refreshing the page.');\n        break;\n      case 'REMOVE_ITEMS':\n        this.showErrorMessage('Oh no! Something went wrong removing items from your bag! ' +\n          'Try refreshing the page and trying again.');\n        break;\n      case 'REMOVE_ITEMS_DESYNC':\n        this.showErrorMessage('Oh no! Something went wrong removing items from your bag! ' +\n          'Try refreshing the page and trying again.');\n        break;\n      case 'UPDATE_CART':\n      case 'UPDATE_ITEMS_DESYNC':\n      case 'CHANGE_CART':\n      case 'CHANGE_ITEMS_DESYNC':\n        this.showErrorMessage('Oh no! Something went wrong updating items in your bag! ' +\n          'Try refreshing the page and trying again.');\n        break;\n      case 'UPDATE_CART_OOS':\n      case 'CHANGE_CART_OOS':\n        this.showErrorMessage('Oh no! Something went wrong updating items in your bag! ' +\n          'One or more products are out of stock.');\n        break;\n      case 'APPLY_DISCOUNTS':\n        // If there are existing applied discount codes to the cart and the scenario is \"cart-\n        // discount-input\" we do not display a cart level error message since the <discount-input>\n        // custom element has its own messaging logic and will handle these cases.\n        if (Array.isArray(event.detail.inputs?.before_codes) &&\n          event.detail.inputs?.before_codes.length === 0 &&\n          event.detail.scenario !== 'cart-discount-input') {\n          this.showErrorMessage('Oh no! Something went wrong applying your discount code.');\n        }\n\n        break;\n      case 'REMOVE_DISCOUNT':\n        this.showErrorMessage('Oh no! Something went wrong removing your discount code.');\n        break;\n    }\n  }\n\n  private onCartUpdated(event: CartUpdatedEvent) {\n    // We do not show change messages for the transition from a null cart to an initial cart state.\n    // The deltas are not meaningful.\n    //\n    // An init that failed before this one may have left its error showing, such as when this\n    // element replays the queue and finds the failure ahead of the retry that worked. The cart is\n    // here now, so we clear it.\n    if (event.detail.is_initial) {\n      const alerts = this.shadowRoot.querySelectorAll('alert-element');\n      for (const alert of alerts) {\n        alert.remove();\n      }\n\n      return;\n    }\n\n    // Skip announcements for updates the shopper did not initiate in this tab (state synced from\n    // another tab, or a bfcache restore catching up to the server). Announcing a delta they did not\n    // cause here would be wrong. This couples to specific scenario values by design.\n\n    const nonUserInitiatedScenarios = ['cross-tab-sync', 'bfcache-reconcile'];\n    if (nonUserInitiatedScenarios.includes(event.detail.scenario)) {\n      return;\n    }\n\n    // We generally should not be mutating the dom one node at a time and should use some batch\n    // removal technique such as setting the parent element's innerHTML to an empty string. However\n    // there are extremely few messages so this is not a concern.\n\n    // Clear any previous messages\n    const alerts = this.shadowRoot.querySelectorAll('alert-element');\n    for (const alert of alerts) {\n      alert.remove();\n    }\n\n    // Show added items messages\n    if (event.detail.variants_added.length > 0) {\n      this.showItemsAddedMessage(event.detail.variants_added);\n    }\n\n    // Show removed items messages\n    if (event.detail.variants_removed.length > 0) {\n      const titles = event.detail.variants_removed.map(variant => variant.product_title);\n      const itemsString = titles.join(', ');\n      const message = document.createElement('div');\n\n      message.textContent = `You removed ${itemsString} from your shopping bag.`;\n      const alert = document.createElement('alert-element');\n      alert.dataset.alertType = 'success';\n      alert.appendChild(message);\n      this.shadowRoot.appendChild(alert);\n    }\n\n    this.showDiscountCodeAppliedMessages(event);\n    this.showDiscountRemovedMessages(event);\n  }\n\n  private showDiscountCodeAppliedMessages(event: CartUpdatedEvent) {\n    for (const code of event.detail.discount_codes_added) {\n      const message = `Applied discount code ${code}.`;\n      this.showSuccessMessage(message);\n    }\n  }\n\n  private showDiscountRemovedMessages(event: CartUpdatedEvent) {\n    for (const code of event.detail.discount_codes_removed) {\n      const message = `The discount ${code} was removed.`;\n      this.showSuccessMessage(message);\n    }\n  }\n\n  private showItemsAddedMessage(items: ProductVariant[]) {\n    const upsellItem = items.find(item => item.merged_properties._is_upsell);\n    if (upsellItem) {\n      this.showUpsellItemMessage(upsellItem);\n      return;\n    }\n\n    const backorderedItem = items.find(item => item.merged_properties._backorder_message);\n    if (backorderedItem) {\n      this.showBackorderItemMessage(backorderedItem);\n    }\n\n    const addedLineItemsString = items.map(item => item.product_title).join(', ');\n\n    const message = document.createElement('div');\n    message.innerHTML = `This is so great! You added ${addedLineItemsString} to your shopping` +\n      ' bag!';\n\n    const alert = document.createElement('alert-element');\n    alert.dataset.alertType = 'success';\n    alert.appendChild(message);\n    this.shadowRoot.appendChild(alert);\n  }\n\n  private showBackorderItemMessage(item: ProductVariant) {\n    let message = item.merged_properties._backorder_message[0];\n    if (!message) {\n      message =\n        'This item is currently on backorder, estimated ship date is visible on product page';\n    }\n\n    const backorderMessage = document.createElement('div');\n    backorderMessage.textContent = `Important note about: ${item.product_title}. ${message}`;\n\n    const backorderAlert = document.createElement('alert-element');\n    backorderAlert.dataset.alertType = 'warning';\n    backorderAlert.appendChild(backorderMessage);\n\n    this.shadowRoot.appendChild(backorderAlert);\n  }\n\n  private showErrorMessage(messageHtml: string) {\n    const message = document.createElement('div');\n    message.textContent = messageHtml;\n\n    const alert = document.createElement('alert-element');\n    alert.dataset.alertType = 'danger';\n    alert.appendChild(message);\n\n    this.shadowRoot.appendChild(alert);\n  }\n\n  /**\n   * Shows that the cart failed to load, with a button that asks cart init to run again so the\n   * shopper can recover without reloading the page.\n   */\n  private showInitErrorMessage() {\n    const message = document.createElement('div');\n    message.textContent = 'Something went wrong loading your shopping bag.';\n\n    const button = document.createElement('button');\n    button.type = 'button';\n    button.textContent = 'Reload my bag';\n    button.addEventListener('click', onReloadBagClick);\n\n    const alert = document.createElement('alert-element');\n    alert.dataset.alertType = 'danger';\n    alert.append(message, button);\n\n    this.shadowRoot.appendChild(alert);\n  }\n\n  private showSuccessMessage(messageHtml: string) {\n    const message = document.createElement('div');\n    message.textContent = messageHtml;\n\n    const alert = document.createElement('alert-element');\n    alert.dataset.alertType = 'success';\n    alert.appendChild(message);\n\n    this.shadowRoot.appendChild(alert);\n  }\n\n  private showUpsellItemMessage(item: ProductVariant) {\n    const message = document.createElement('div');\n    message.textContent = `${item.product_title} is a special ` +\n      'additional savings item. It can only be purchased together with a regular item and only ' +\n      'once per order.';\n\n    const alert = document.createElement('alert-element');\n    alert.dataset.alertType = 'info';\n    alert.appendChild(message);\n\n    this.shadowRoot.appendChild(alert);\n  }\n}\n\n/**\n * Asks cart init to run again. Init lives in a different bundle, so we ask through an event on\n * window rather than calling it.\n *\n * We take the error away first, and the cart shows its loading message while init runs. If init\n * fails again, the error comes back with a new button, so the shopper can tell it failed.\n */\nfunction onReloadBagClick(event: MouseEvent) {\n  const button = event.currentTarget;\n  if (button instanceof HTMLButtonElement) {\n    button.closest('alert-element')?.remove();\n  }\n\n  const requestEvent = new Event('cart-init-requested');\n  dispatchEvent(requestEvent);\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cart-messages': CartMessages;\n  }\n}\n\nif (!customElements.get('cart-messages')) {\n  customElements.define('cart-messages', CartMessages);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders the cart's totals. Displays the subtotal of all line items, the total discount amount,\n * and the discount percentage (if any).\n */\nclass CartTotals extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-estimated-total'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * ISO currency code.\n     *\n     * @example \"USD\"\n     */\n    currencyCode: string;\n\n    /**\n     * Locale specific currency characters.\n     *\n     * @example \"$\"\n     */\n    currencySymbol: string;\n\n    /**\n     * The price before discounts less the price after discounts.\n     */\n    discountAmount: string;\n\n    /**\n     * The price after discounts divided by the price before discounts, as a percentage.\n     */\n    discountPercent: string;\n\n    /**\n     * The price after discounts but before taxes and shipping, which are unknown until checkout.\n     */\n    estimatedTotal: string;\n\n    /**\n     * The price before discounts.\n     */\n    subtotalAmount: string;\n  };\n\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\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    // Hide discount row if discount is $0 (which is the same as subtotal = estimated total)\n    const discountRow = this.shadowRoot.querySelector<HTMLElement>('#discount-row');\n    if (this.dataset.discountAmount === '0') {\n      discountRow.hidden = true;\n    } else {\n      discountRow.hidden = false;\n    }\n\n    const subtotalEl = this.shadowRoot.querySelector<HTMLElement>('#subtotal');\n    subtotalEl.dataset.currencyCode = this.dataset.currencyCode;\n    subtotalEl.dataset.currencySymbol = this.dataset.currencySymbol;\n    subtotalEl.dataset.subunits = this.dataset.subtotalAmount;\n\n    const discountTotalEl = this.shadowRoot.querySelector<HTMLElement>('#discount');\n    discountTotalEl.dataset.currencyCode = this.dataset.currencyCode;\n    discountTotalEl.dataset.currencySymbol = this.dataset.currencySymbol;\n    discountTotalEl.dataset.subunits = this.dataset.discountAmount;\n\n    this.shadowRoot.querySelector('#discount-percentage').textContent =\n      `(${this.dataset.discountPercent}%)`;\n  }\n\n  private onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    const cart = event.detail.cart;\n    if (cart.items.length === 0) {\n      return;\n    }\n\n    const subtotal = computeSubtotal(cart);\n\n    this.dataset.subtotalAmount = subtotal.toString();\n\n    const applied = this.shadowRoot.querySelector<HTMLElement>('#order-discounts-row');\n    applied.style.display = 'none';\n\n    let orderDiscountsValue = 0;\n    for (const discount of cart.cart_level_discount_applications) {\n      if (discount.total_allocated_amount > 0) {\n        orderDiscountsValue += discount.total_allocated_amount;\n      }\n    }\n\n    if (orderDiscountsValue > 0) {\n      // If a cart level discount is applied, we unset display none to ensure \"Order Discounts\"\n      // heading is visible.\n      applied.style.display = 'flex';\n\n      const orderDiscountsEl = this.shadowRoot.querySelector<HTMLElement>('#order-discount-total');\n      orderDiscountsEl.dataset.currencySymbol = this.dataset.currencySymbol;\n      orderDiscountsEl.dataset.currencyCode = this.dataset.currencyCode;\n      orderDiscountsEl.dataset.subunits = orderDiscountsValue.toString();\n    }\n\n    // We cannot use the original discount amount because that is the difference between the sales\n    // price and the discounted price. The sales price is the implicitly discounted MSRP. We want\n    // the different from the MSRP.\n\n    // The total after discounts, e.g. the estimated total less taxes and shipping that someone will\n    // pay, is the total_price property. Because total_price is floating point, we want to round to\n    // two significant digits.\n\n    const discountAmount = Math.round(100 * (subtotal - cart.total_price)) / 100;\n\n    this.dataset.discountAmount = discountAmount.toString();\n\n    // The discount percent must be manipulated to be based upon our custom subtotal so that it\n    // represents in the reduction of the MSRP, not the sales price. We multiply by 100 so that we\n    // get a nice visual percentage (e.g. a 10% discount). We use floor instead of round so that we\n    // never over-represent the percentage. (e.g. 5.5% off is 5% off, not 6%).\n\n    const discountPercent = Math.floor(100 * discountAmount / subtotal);\n    this.dataset.discountPercent = discountPercent.toString();\n\n    // Set this attribute last because this triggers a render\n    this.dataset.estimatedTotal = cart.total_price.toString();\n  }\n}\n\n/**\n * The subtotal should be the total of the compare at price elements. Because we are working with\n * compare at prices, we cannot use the cart's price before discounts fields. We have to calculate\n * this on our own.\n *\n * There was an implementation choice to compute this in the server side rendered cart state in\n * liquid or to compute this here. It seems better to do it here because JavaScript is much more\n * expressive and the cost of performing the calculation here is not significant and because this\n * clearly documents the logic and is easier to maintain.\n */\nfunction computeSubtotal(cart: Cart) {\n  let subtotal = 0;\n  for (const item of cart.items) {\n    const compareAtPrice = parseInt(item.properties._value_set_compare_price, 10);\n    if (Number.isInteger(compareAtPrice)) {\n      subtotal += compareAtPrice;\n    } else {\n      subtotal += item.original_line_price;\n    }\n  }\n\n  // If doing some math somehow resulted in floating point junk, remove it. This generally does not\n  // happen in browsers that we tested but you never know.\n  subtotal = Math.floor(subtotal);\n\n  return subtotal;\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cart-totals': CartTotals;\n  }\n}\n\nif (!customElements.get('cart-totals')) {\n  customElements.define('cart-totals', CartTotals);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * This element renders an upsell item with the ability for the user to add an upsell to cart.\n */\nclass CartUpsellItem extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-variant-id', 'data-product-tags', 'data-compare-at-price'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Compare at price\n     */\n    compareAtPrice: string;\n\n    /**\n     * Country ISO code\n     */\n    country: string;\n\n    /**\n     * Currency ISO code\n     *\n     * @example \"USD\"\n     */\n    currencyCode: string;\n\n    /**\n     * Currency symbol character\n     *\n     * @example \"$\"\n     */\n    currencySymbol: string;\n\n    /**\n     * Product handle\n     */\n    handle: string;\n\n    /**\n     * Product id\n     */\n    id: string;\n\n    /**\n     * Product image url\n     */\n    image: string;\n\n    /**\n     * Product price\n     */\n    price: string;\n\n    /**\n     * CSV of product tags\n     */\n    productTags: string;\n\n    /**\n     * Product title, used together with the variant title to build the displayed upsell name.\n     */\n    productTitle: string;\n\n    /**\n     * Average product rating\n     */\n    reviewsAverage: string;\n\n    /**\n     * Number of product reviews\n     */\n    reviewsCount: string;\n\n    /**\n     * Variant SKU\n     */\n    sku: string;\n\n    /**\n     * Name of the Shopify template the upsell is being rendered within.\n     */\n    templateName: string;\n\n    /**\n     * Product title.\n     */\n    title: string;\n\n    /**\n     * Product type\n     */\n    type: string;\n\n    /**\n     * Id of the variant added to the cart when the upsell is accepted. Rendering is skipped until\n     * this is set.\n     */\n    variantId: string;\n\n    /**\n     * Variant title, appended to the product title unless it is the default variant title.\n     */\n    variantTitle: string;\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\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  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    if (!this.dataset.variantId) {\n      return;\n    }\n\n    const upsellLink = new URL(`products/${this.dataset.handle}`, location.origin);\n    const url = upsellLink.toString();\n    const title = this.buildTitle();\n\n    this.renderTitle(title, url);\n    this.renderImage(url);\n    this.renderPrices();\n    this.renderAddButton(title);\n    this.renderReviews(url);\n  }\n\n  private buildTitle() {\n    let title = this.dataset.productTitle;\n    if (this.dataset.variantTitle !== 'Default Title') {\n      title += ` ${this.dataset.variantTitle}`;\n    }\n\n    return title;\n  }\n\n  private renderTitle(title: string, url: string) {\n    const anchor = this.shadowRoot.querySelector<HTMLAnchorElement>('.title a');\n    anchor.href = url;\n    anchor.textContent = title;\n  }\n\n  private renderImage(url: string) {\n    const image = this.shadowRoot.querySelector<HTMLElement>('#image');\n    // the image is decorative (described by the adjacent text)\n    image.dataset.alt = '';\n    image.dataset.src = this.dataset.image;\n\n    const clickableArea = this.shadowRoot.querySelector('clickable-area');\n    clickableArea.dataset.url = url;\n  }\n\n  /**\n   * Some upsells carry their price in a product tag instead of on the variant, so we have to work\n   * out which price to actually show. The compare at price comes back empty when there is nothing\n   * to strike through.\n   */\n  private resolvePrices() {\n    let price = this.dataset.price;\n    let compareAtPrice = this.dataset.compareAtPrice;\n    const upsellTagSearch = this.dataset.currencyCode === 'CAD' ? 'UPSELL:CAD' : 'UPSELL';\n\n    // It is important to use optional chaining here because product tags is not guaranteed defined\n    const upsellTag = this.dataset.productTags?.split(' ').find(t => t.includes(upsellTagSearch));\n\n    if (upsellTag) {\n      const upsellPriceArray = upsellTag.split(':');\n      const upsellPriceString = this.dataset.currencyCode === 'CAD' ?\n        upsellPriceArray[2] :\n        upsellPriceArray[1];\n      const upsellPrice = parseFloat(upsellPriceString) * 100;\n\n      // Not all upsell items require their price and compareAtPrice values to be updated before\n      // being presented to the user. To prevent over writing already properly priced products, we\n      // must confirm the extracted upsell price from the products tags is a lower value. If so we\n      // proceed with the updating the compareAtPrice.\n      if (upsellPrice < parseInt(price, 10) && parseInt(price, 10) > parseInt(compareAtPrice, 10)) {\n        compareAtPrice = price;\n      }\n\n      price = upsellPrice.toString();\n    }\n\n    return { price, compareAtPrice };\n  }\n\n  private renderPrices() {\n    const prices = this.resolvePrices();\n\n    const priceEl = this.shadowRoot.querySelector<HTMLElement>('#price');\n    priceEl.dataset.currencyCode = this.dataset.currencyCode;\n    priceEl.dataset.currencySymbol = this.dataset.currencySymbol;\n    priceEl.dataset.subunits = prices.price;\n\n    const compareAtPriceEl = this.shadowRoot.querySelector<HTMLElement>('#compare-at-price');\n    compareAtPriceEl.dataset.currencyCode = this.dataset.currencyCode;\n    compareAtPriceEl.dataset.currencySymbol = this.dataset.currencySymbol;\n\n    if (prices.compareAtPrice) {\n      compareAtPriceEl.dataset.subunits = prices.compareAtPrice;\n      compareAtPriceEl.dataset.hide = 'false';\n      compareAtPriceEl.dataset.ariaLabelPrefix = 'Regular price:';\n      priceEl.dataset.ariaLabelPrefix = 'Sale price:';\n    } else {\n      // hide the compare\n      compareAtPriceEl.dataset.hide = 'true';\n      compareAtPriceEl.dataset.ariaLabelPrefix = '';\n      priceEl.dataset.ariaLabelPrefix = 'Price:';\n    }\n  }\n\n  private renderAddButton(title: string) {\n    const addButton = this.shadowRoot.querySelector('add-to-cart-button');\n    addButton.dataset.country = this.dataset.country;\n    addButton.dataset.variantTitle = title;\n\n    if (this.shadowRoot.querySelector(`variant-element[data-id=\"${this.dataset.variantId}\"]`)) {\n      // do not add\n    } else {\n      const customAttribute = document.createElement('custom-attribute');\n      customAttribute.dataset.key = '_is_upsell';\n      customAttribute.dataset.value = 'true';\n\n      const variant = document.createElement('variant-element');\n      variant.slot = 'variants';\n      variant.dataset.id = this.dataset.variantId;\n      variant.dataset.quantity = '1';\n      variant.appendChild(customAttribute);\n\n      addButton.appendChild(variant);\n    }\n\n    addButton.dataset.update = Date.now().toString();\n  }\n\n  private renderReviews(url: string) {\n    const reviews = this.shadowRoot.querySelector('product-rating-stars');\n    reviews.dataset.average = this.dataset.reviewsAverage;\n    reviews.dataset.count = this.dataset.reviewsCount;\n    reviews.dataset.href = `${url}#reviews-section`;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'cart-upsell-item': CartUpsellItem;\n  }\n}\n\nif (!customElements.get('cart-upsell-item')) {\n  customElements.define('cart-upsell-item', CartUpsellItem);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * The container for the upsell suggestions shown in the cart drawer. The individual suggestions are\n * cart upsell item children.\n *\n * This exists to keep the suggestions relevant as the cart changes. On every cart update it hides\n * any item already in the cart, shows at most four, and collapses itself when none are left to\n * suggest.\n */\nclass CartUpsellItems extends HTMLElement implements CustomElement {\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    // The animation frame ensures the element has actually rendered.\n    requestAnimationFrame(() => {\n      const event = new Event('cart-upsell-items-rendered', { bubbles: true, composed: true });\n      this.dispatchEvent(event);\n    });\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n  }\n\n  public disconnectedCallback() {\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\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 onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    const variantIds = event.detail.cart.items.map(item => item.variant_id);\n    const elements = this.querySelectorAll('cart-upsell-item');\n    let visibleCount = 0;\n\n    for (const element of elements) {\n      const variantId = parseInt(element.dataset.variantId, 10);\n      const show = !variantIds.includes(variantId) && visibleCount < 4;\n\n      if (show) {\n        // if upsellItemElement is not included in the cart unhide it\n        element.style.display = 'flex';\n        visibleCount++;\n      } else {\n        element.style.display = 'none';\n      }\n    }\n\n    // Hide upsell items section if it is empty\n    if (visibleCount === 0) {\n      this.style.display = 'none';\n    } else {\n      this.style.display = 'block';\n    }\n  }\n}\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    /**\n     * Dispatched by the upsell items element whenever the upsell items element is rendered.\n     */\n    'cart-upsell-items-rendered': Event;\n  }\n\n  interface HTMLElementTagNameMap {\n    'cart-upsell-items': CartUpsellItems;\n  }\n}\n\nif (!customElements.get('cart-upsell-items')) {\n  customElements.define('cart-upsell-items', CartUpsellItems);\n}\n","/**\n * Currency Helpers\n * -----------------------------------------------------------------------------\n * A collection of useful functions that help with currency formatting\n *\n * Current contents\n * - formatMoney - Takes an amount in cents and returns it as a formatted dollar value.\n *\n */\n\nconst moneyFormat = '${{amount}}';\n\n/**\n * Format money values based on your shop currency settings\n * @param  {Number|string} cents - value in cents or dollar amount e.g. 300 cents\n * or 3.00 dollars\n * @param  {String} format - shop money_format setting\n * @return {String} value - formatted value\n */\nexport function formatMoney(cents, format) {\n  if (typeof cents === 'string') {\n    cents = cents.replace('.', '');\n  }\n  let value = '';\n  const placeholderRegex = /\\{\\{\\s*(\\w+)\\s*\\}\\}/;\n  const formatString = format || moneyFormat;\n\n  function formatWithDelimiters(\n    number,\n    precision = 2,\n    thousands = ',',\n    decimal = '.'\n  ) {\n    if (isNaN(number) || number == null) {\n      return 0;\n    }\n\n    number = (number / 100.0).toFixed(precision);\n\n    const parts = number.split('.');\n    const dollarsAmount = parts[0].replace(\n      /(\\d)(?=(\\d\\d\\d)+(?!\\d))/g,\n      `$1${thousands}`\n    );\n    const centsAmount = parts[1] ? decimal + parts[1] : '';\n\n    return dollarsAmount + centsAmount;\n  }\n\n  switch (formatString.match(placeholderRegex)[1]) {\n    case 'amount':\n      value = formatWithDelimiters(cents, 2);\n      break;\n    case 'amount_no_decimals':\n      value = formatWithDelimiters(cents, 0);\n      break;\n    case 'amount_with_comma_separator':\n      value = formatWithDelimiters(cents, 2, '.', ',');\n      break;\n    case 'amount_no_decimals_with_comma_separator':\n      value = formatWithDelimiters(cents, 0, '.', ',');\n      break;\n  }\n\n  return formatString.replace(placeholderRegex, value);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { formatMoney } from '@shopify/theme-currency';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * This element is responsible for showing a free shipping message. It reacts to a cart updated\n * event to calculate if the cart qualifies for free shipping.\n *\n * The free shipping is applicable when the estimated cart total reaches a certain threshold or when\n * there is a subscription item in the cart.\n */\nclass FreeShippingBanner extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Text to show when free shipping is unlocked because of a subscription item in the cart\n     */\n    subscriptionText: string;\n\n    /**\n     * Minimum cart value for free shipping (in cents).\n     */\n    threshold: string;\n  };\n\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n\n  /**\n   * Estimated customer cart value, exact value is calculated on checkout shipping step (in cents).\n   */\n  private estimatedTotal = 0;\n\n  /**\n   * Whether there is a subscription item in the cart, which unlocks free shipping regardless of the\n   * cart value.\n   */\n  private subscriptionsInCart = false;\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\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 onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    const cart = event.detail.cart;\n    this.estimatedTotal = cart.total_price;\n\n    let subscriptionsInCart = false;\n    for (const item of cart.items) {\n      if (item?.selling_plan_allocation?.selling_plan?.recurring_deliveries) {\n        subscriptionsInCart = true;\n        break;\n      }\n    }\n\n    this.subscriptionsInCart = subscriptionsInCart;\n\n    this.render();\n  }\n\n  private render() {\n    const span = this.shadowRoot.querySelector('span');\n    const progressContainer = this.shadowRoot.querySelector('.progress-container');\n    const progressBar = this.shadowRoot.querySelector<HTMLElement>('.progress-bar');\n\n    span.textContent = this.getMessageText();\n\n    let percentage: number;\n    if (this.subscriptionsInCart) {\n      percentage = 100;\n    } else {\n      const threshold = parseInt(this.dataset.threshold, 10);\n      percentage = Math.min(100, (this.estimatedTotal / threshold) * 100);\n    }\n\n    percentage = Math.round(percentage);\n    if (isNaN(percentage)) {\n      percentage = 0;\n    }\n\n    progressContainer.setAttribute('aria-valuenow', percentage.toString());\n    progressBar.style.width = percentage + '%';\n  }\n\n  private getMessageText() {\n    if (this.subscriptionsInCart) {\n      return this.dataset.subscriptionText ||\n        'Free shipping unlocked with subscription item!';\n    }\n\n    const threshold = parseInt(this.dataset.threshold, 10);\n    const difference = threshold - this.estimatedTotal;\n\n    if (difference > 0) {\n      // eslint-disable-next-line no-template-curly-in-string\n      return 'Add ' + formatMoney(difference, '${{amount}}') +\n        ' to unlock Free Contiguous US Shipping!';\n    } else {\n      return 'Congratulations! You\\'ve unlocked Free Contiguous US Shipping!';\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'free-shipping-banner': FreeShippingBanner;\n  }\n}\n\nif (!customElements.get('free-shipping-banner')) {\n  customElements.define('free-shipping-banner', FreeShippingBanner);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { formatMoney } from '@shopify/theme-currency';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * This element is responsible for showing multi-tiered promotion progress in the users shopping\n * cart. It reacts to a cart updated event to calculate if the cart qualifies for any of the\n * promotional goals.\n *\n * Currently, the element is being used exclusively to test free shipping, which is both applicable\n * when the estimated cart total reaches a certain threshold or when there is a subscription item in\n * the cart.\n *\n * This is the second version of the promo goals progress, which is a replacement for the original.\n * The original promo goals progress element is still available as a separate custom element, but it\n * is not currently in use.\n *\n * @todo This element currently assumes that the only promo-goal is free shipping. If we want to\n * support multiple promo-goals, we will need to update the logic to not make this assumption.\n */\nclass PromoGoalProgressV2 extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-update'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Boolean flag to determine whether the promo goals progress bar should be shown.\n     */\n    show: 'false' | 'true';\n\n    /**\n     * Text to show when free shipping is unlocked because of a subscription item in the cart\n     */\n    subscriptionText: string;\n\n    /**\n     * Minimum cart value for free shipping (in cents).\n     */\n    threshold: string;\n\n    /**\n     * Mechanism to update the promo goals progress bar after threshold updates are processed.\n     */\n    update: string;\n  };\n\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n\n  /**\n   * Estimated customer cart value, exact value is calculated on checkout shipping step (in cents).\n   */\n  private estimatedTotal = 0;\n\n  /**\n   * Whether there is a subscription item in the cart, which unlocks free shipping regardless of the\n   * cart value.\n   */\n  private subscriptionsInCart = false;\n\n  private promoGoals!: PromoGoal[];\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\n  }\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (oldValue === newValue) {\n      return;\n    }\n\n    if (this.isConnected) {\n      this.ingestPromoGoals();\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 onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    const cart = event.detail.cart;\n    this.estimatedTotal = cart.total_price;\n\n    let subscriptionsInCart = false;\n    for (const item of cart.items) {\n      if (item?.selling_plan_allocation?.selling_plan?.recurring_deliveries) {\n        subscriptionsInCart = true;\n        break;\n      }\n    }\n\n    this.subscriptionsInCart = subscriptionsInCart;\n\n    this.render();\n  }\n\n  private ingestPromoGoals() {\n    const goals = this.querySelectorAll('promo-goal-v2');\n\n    this.promoGoals = [];\n    for (const goal of goals) {\n      this.promoGoals.push({\n        name: goal.dataset.offerText,\n        threshold: parseInt(goal.dataset.thresholdCents, 10)\n      });\n    }\n  }\n\n  private render() {\n    if (!this.promoGoals) {\n      this.ingestPromoGoals();\n    }\n\n    const span = this.shadowRoot.querySelector('span');\n    const progressContainer = this.shadowRoot.querySelector('.progress-container');\n    const progressBar = this.shadowRoot.querySelector<HTMLElement>('.progress-bar');\n\n    span.textContent = this.getMessageText();\n\n    let percentage: number;\n    if (this.subscriptionsInCart) {\n      percentage = 100;\n    } else {\n      const threshold = parseInt(this.dataset.threshold, 10);\n      percentage = Math.min(100, (this.estimatedTotal / threshold) * 100);\n    }\n\n    percentage = Math.round(percentage);\n    if (isNaN(percentage)) {\n      percentage = 0;\n    }\n\n    progressContainer.setAttribute('aria-valuenow', percentage.toString());\n    progressBar.style.width = percentage + '%';\n\n    const goalNamesContainer = this.shadowRoot.querySelector('.goal-names');\n    goalNamesContainer.innerHTML = '';\n\n    // Loop through promo goals to add goal names to the progress bar\n    let goalIndex = 0;\n    for (const goal of this.promoGoals) {\n      goalIndex++;\n      const nameDiv = document.createElement('div');\n      nameDiv.classList.add('name');\n\n      const spanDiv = document.createElement('span');\n      spanDiv.textContent = goal.name;\n      nameDiv.appendChild(spanDiv);\n\n      if (this.estimatedTotal >= goal.threshold) {\n        nameDiv.classList.add('achieved');\n      }\n\n      if (this.promoGoals.length === goalIndex) {\n        nameDiv.classList.add('last');\n      } else {\n        const threshold = parseInt(this.dataset.threshold, 10);\n        const percentage = Math.min(100, (goal.threshold / threshold) * 100);\n        nameDiv.style.left = `${percentage}%`;\n      }\n\n      goalNamesContainer.appendChild(nameDiv);\n    }\n\n    // Loop through promo goals to add threshold markers to the progress bar\n    const thresholdsContainer = this.shadowRoot.querySelector('.goal-thresholds');\n    thresholdsContainer.innerHTML = '';\n    goalIndex = 0;\n    for (const goal of this.promoGoals) {\n      goalIndex++;\n      const thresholdElement = document.createElement('div');\n      thresholdElement.classList.add('threshold');\n      // eslint-disable-next-line no-template-curly-in-string\n      thresholdElement.textContent = formatMoney(goal.threshold, '${{amount_no_decimals}}');\n\n      if (this.promoGoals.length === goalIndex) {\n        thresholdElement.classList.add('last');\n      } else {\n        const threshold = parseInt(this.dataset.threshold, 10);\n        const percentage = Math.min(100, (goal.threshold / threshold) * 100);\n        thresholdElement.style.left = `${percentage}%`;\n      }\n\n      thresholdsContainer.appendChild(thresholdElement);\n    }\n  }\n\n  private getMessageText() {\n    if (this.subscriptionsInCart) {\n      return this.dataset.subscriptionText ||\n        'Free shipping unlocked with subscription item!';\n    }\n\n    const threshold = parseInt(this.dataset.threshold, 10);\n    const difference = threshold - this.estimatedTotal;\n\n    if (difference > 0) {\n      // eslint-disable-next-line no-template-curly-in-string\n      return 'You\\'re ' + formatMoney(difference, '${{amount}}') +\n        ' away from Free Shipping!';\n    } else {\n      return 'You\\'ve Earned Free Contiguous US Shipping!';\n    }\n  }\n}\n\ninterface PromoGoal {\n  name: string;\n  threshold: number;\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'promo-goal-progress-v2': PromoGoalProgressV2;\n  }\n}\n\nif (!customElements.get('promo-goal-progress-v2')) {\n  customElements.define('promo-goal-progress-v2', PromoGoalProgressV2);\n}\n","import { type Cart, cartUpdate } from '@integrabeauty/shopify-ajax-api';\nimport { cartFetch } from '../state.js';\nimport { getApplicableDiscountCodes, getDiscountCodes, isWellFormed } from './discount.js';\n\ntype DiscountOverride = typeof cart_lifecycle.discount_overrides[number];\n\n/**\n * Updates a cart's discount codes. This uses a specially enabled feature on the lange Shopify shop\n * that enables the use of the cart update ajax api to modify the cart's discounts. The special\n * feature enables the use of a discount property in the update call.\n *\n * New codes are partially validated before the http request is sent. If any new code is invalid,\n * this throws an error immediately.\n *\n * This will send an update request even if the discount codes have not changed. This does not check\n * whether sending the request is pointless. One of the reasons why this check is not performed is\n * because sometimes callers want to call this function even when it seems pointless because of the\n * risk that client side state is out of sync with server side state. That kind of thing can happen\n * for example when browsing the site in multiple tabs, because client side state is largely managed\n * per tab, so one tab may naively update discounts without awareness of changes made in other tabs.\n * While we attempt to keep state synchronized across tabs, there is no guarantee.\n *\n * The special discount property of the update request body has no documentation. We figured out\n * that it works similar to the checkout links discount url parameter. Multiple codes can be\n * specified as a comma separated value. This also means that codes should not contain commas. We\n * also figured out that it overwrites all discounts. This does not just add new discounts. That is\n * why this requires both the set of existing codes and the new codes.\n *\n * @param cart the state of the cart prior to update\n * @param newCodes the codes that should be applied to the cart in addition to the existing codes\n * @param overrides discount overrides, this is expected to always be defined even if there are no\n * overrides\n * @param postValidate whether to check if the new codes were actually applied after the update\n *\n * @see https://help.shopify.com/en/manual/discounts/managing-discount-codes\n */\nexport async function applyDiscountsHelper(cart: Cart, newCodes: string[],\n  overrides: DiscountOverride[], postValidate = true) {\n  validateCodesBeforeUpdate(newCodes);\n\n  const oldUppers = getCurrentCodes(cart, newCodes.length);\n  const newUppers = newCodes.map(code => code.toUpperCase());\n\n  const retainedOldUppers = oldUppers.filter(code =>\n    !overrides.some(override =>\n      override.old_code === code && newUppers.includes(override.new_code)));\n\n  const mergedUppers = new Set<string>([...retainedOldUppers, ...newUppers]);\n  const mergedCodes = [...mergedUppers];\n\n  const discount = mergedCodes.join(',');\n  await cartUpdate({ discount });\n  const updatedCart = await cartFetch();\n\n  // Only validate if post validation requested. Validation is performed by default but there are\n  // some use cases where discounts are applied even in applicable situations, such as when the cart\n  // is empty (the discount is applied but if you look at the discount object in the discount_codes\n  // array the applicable property is false). By only validating if requested we avoid generating\n  // routine validation errors.\n\n  if (postValidate) {\n    validateCodesAfterUpdate(updatedCart, newCodes);\n  }\n\n  return updatedCart;\n}\n\n/**\n * Obtain the existing discount codes. Generally, we want to retain all codes, including currently\n * inapplicable codes, as those codes may become applicable later and have been deemed applied even\n * though they have no effect. However, retaining all codes leads to the possible accumulation of\n * several inapplicable codes. This leads into a poorly documented issue. Once there are about 5 or\n * 6 codes applied, in either the applicable or inapplicable state, future applications fail without\n * regard to code applicability.\n *\n * Therefore, this needs to take into account the total number of discount codes that will be sent\n * as a part of a cart update. This is why the new codes array is also an input to this function\n * that has only to do with old codes.\n *\n * This is a hacky rushed fix that needs to be revisited. Expired codes stick around. Typo codes\n * stick around. Once we reach more than 5 codes (in any state), all future applications, even of\n * valid codes, incorrectly fail to apply. So in this case, we have to filter down the list. The\n * quickest thing we can do is just drop codes that are applied but in the inapplicable state. We\n * prefer not to do this because we want to support visitors who apply a code, it becomes applied\n * but inapplicable, then add a product, and it becomes applicable.\n *\n * This hacky fix is not perfect because it is not deduplicating, this is an inaccurate and quick\n * fix for \"several\" issues, not all of the issues stemming from bad codes sticking around.\n *\n * There is a limitation on the number of discount codes that can be applied to the same order,\n * including codes in the inapplicable state. According to Shopify, \"Customers can use a maximum of\n * five product or order discount codes and one shipping discount code on the same order.\"\n *\n * @see https://help.shopify.com/en/manual/discounts/combining-discounts/discount-combinations#limitations\n */\nfunction getCurrentCodes(cart: Cart, newCodesCount: number) {\n  const currentCodes = Array.isArray(cart.discount_codes) ? cart.discount_codes : [];\n\n  // We test against what would be the total number of applied codes in determining which codes\n  // should be included in the output. If adding the new codes would exceed the threshold, then we\n  // try our best to trim the number of currently applied codes down to below the threshold. Since\n  // we cannot tell which discount codes are shipping discount codes, we use the threshold of 5\n  // instead of 6.\n\n  // There is currently no reliable additional way of choosing which discount codes to remove. There\n  // is no clear documentation on the order of the codes in the array. So we have to choose an\n  // arbitrary method of ejecting some codes. For now, this basically just gets rid of any codes\n  // that are inapplicable. That will most likely remove typos and expired codes. Most visitors will\n  // not discern the missing codes.\n\n  // This does not guarantee that the output is reduced to below the threshold. If there are 5\n  // applicable codes, then no codes will be removed, and the 6th application can still fail.\n\n  // This is not trying to get down to just below the threshold. This does a whole sweep. We might\n  // want to change this to remove inapplicable codes up until the threshold is no longer exceeded.\n\n  // Drop all inapplicable codes by keeping around only those older codes that are applicable. Try\n  // to retain inapplicable shipping codes.\n\n  if (currentCodes.length + newCodesCount > 5) {\n    return currentCodes\n      .filter(code => code.applicable || /ship/i.test(code.code))\n      .map(code => code.code)\n      .map(code => code.toUpperCase());\n  }\n\n  return getDiscountCodes(cart);\n}\n\nfunction validateCodesBeforeUpdate(newCodes: string[]) {\n  if (newCodes.length < 1) {\n    throw new Error('At least one discount code must be specified');\n  }\n\n  // If any one of the input codes is invalid, reject with a specific error message that mentions\n  // the code. It is important to be explicit here because this error may be visible to users.\n\n  for (const code of newCodes) {\n    if (!isWellFormed(code)) {\n      const error: any = new Error(`Invalid discount code ${code}`);\n      error.discount_code = code;\n      error.discount_codes = newCodes;\n      throw error;\n    }\n  }\n}\n\n/**\n * When validating whether the codes applied, we only validate whether new codes applied. We do not\n * validate whether existing codes applied, because it possible that applying a new code caused a\n * previous code to be removed, because the new code replaces the existing code, for example,\n * because the new code offers a better discount.\n *\n * If a code was applied but in the inapplicable state, then we treat the add as a failure, even\n * though the code persists in the discount_codes array. The rationale is complex. Basically, we\n * must address the UX concern that we must provide feedback that an invalid code was applied, so we\n * must trigger an error. However, some codes, such as shipping codes, can be early applied. Some\n * codes, such as shipping codes, can apply but have no cart level applications or line level\n * allocations, but still be applied, and still be valid, even though the code is not actually\n * affecting the estimated total. We do not want to treat that as an error.\n *\n * For a shipping discount that is applicable, even though it has no effect, it will appear in the\n * discount_codes array with applicable true.\n *\n * For a fictional discount that is inapplicable, it will appear in the discount_codes array with\n * applicable false.\n */\nfunction validateCodesAfterUpdate(cart: Cart, newCodes: string[]) {\n  const updatedNormalCodes = getApplicableDiscountCodes(cart);\n\n  // Downstream analytics sometimes does a breakdown on the error message. Input new codes are in\n  // mixed cases, leading to nearly duplicate messages. To make it easier on analysis, we report on\n  // the normalized discount code, not the actual characters entered by visitors.\n\n  for (const newCode of newCodes) {\n    const upperNewCode = newCode.toUpperCase();\n    if (!updatedNormalCodes.includes(upperNewCode)) {\n      const error: any = new Error(`The discount code ${upperNewCode} was not applied`);\n      error.discount_code = upperNewCode;\n      error.discount_codes = newCodes;\n      throw error;\n    }\n  }\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { apply } from '../../../lib/cart/discount/apply.js';\nimport { getApplicableDiscountCodes, getDiscountAmounts, getDiscountCodes } from '../../../lib/cart/discount/discount.js';\nimport { remove } from '../../../lib/cart/discount/remove.js';\nimport { read } from '../../../lib/cart/state.js';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders a text field for someone to type in a discount code and apply it to the cart. This also\n * renders the apply button and some error messages the currently applied code(s) and a way of\n * removing discounts.\n */\nclass DiscountInput extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Currency code\n     */\n    currencyCode: string;\n\n    /**\n     * Currency Symbol (e.g., $)\n     */\n    currencySymbol: string;\n  };\n\n  /**\n   * The most recent cart state. Both the codes we show and the amounts we show beside them are\n   * derived from this when rendering, so the two always describe the same cart.\n   */\n  private cart: Cart = null;\n\n  /**\n   * Number of active transactions. This helps guarding against applying / removing multiple codes\n   * at the same time\n   */\n  private activeTransactions = 0;\n\n  /**\n   * Last error message. If not set, the error box won't be displayed\n   */\n  private errorMessage: string | null = null;\n\n  private onCartTransactionStartedBound = this.onCartTransactionStarted.bind(this);\n  private onCartTransactionEndedBound = this.onCartTransactionEnded.bind(this);\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n  private onCartUpdateErredBound = this.onCartUpdateErred.bind(this);\n  private onDiscountFormSubmitBound = this.onDiscountFormSubmit.bind(this);\n  private onDiscountInputBound = this.onDiscountInput.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    const form = this.shadowRoot.querySelector<HTMLFormElement>('#discount-form');\n    form.addEventListener('submit', this.onDiscountFormSubmitBound);\n\n    const inputElement = this.shadowRoot.querySelector<HTMLInputElement>('#input');\n    inputElement.addEventListener('input', this.onDiscountInputBound);\n\n    // we might have missed some events while disconnected, so we reset the transactions lock\n    this.activeTransactions = 0;\n    // and the error message\n    this.errorMessage = null;\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n    addEventListener('cart-transaction-completed', this.onCartTransactionEndedBound);\n    addEventListener('cart-transaction-started', this.onCartTransactionStartedBound);\n\n    // This element applies discounts and dispatches its own failures from itself, so we listen here\n    // rather than at global scope. That way it only receives the failures it caused, without having\n    // to match on a scenario token to recognize them.\n\n    this.addEventListener('cart-update-erred', this.onCartUpdateErredBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    const form = this.shadowRoot.querySelector<HTMLFormElement>('#discount-form');\n    form?.removeEventListener('submit', this.onDiscountFormSubmitBound);\n\n    const inputElement = this.shadowRoot.querySelector<HTMLInputElement>('#input');\n    inputElement?.removeEventListener('input', this.onDiscountInputBound);\n\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\n    removeEventListener('cart-transaction-completed', this.onCartTransactionEndedBound);\n    removeEventListener('cart-transaction-started', this.onCartTransactionStartedBound);\n\n    this.removeEventListener('cart-update-erred', this.onCartUpdateErredBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    // The cart describes its discounts in two separate places and we need both of them here. The\n    // discount_codes array is the list of codes sitting on the cart, along with whether each one\n    // actually took, and it carries no money. The discount applications carry the money, but they\n    // are labeled with a title rather than a code. So we build the list from the codes, because a\n    // code is also what the remove button has to send back to Shopify, and then we look up each\n    // amount by matching a title against a code.\n\n    const codes = getApplicableDiscountCodes(this.cart);\n    const discountAmounts = getDiscountAmounts(this.cart);\n\n    // An unmatched amount is one whose title matches none of the codes we are about to display.\n    // That happens when the application's title is not a code string, or when the cart holds an\n    // allocation for a code it does not mark applicable, since we only display applicable ones.\n    // Nothing downstream needs this, so we say it and carry on.\n    //\n    // We log rather than warn because it is routine rather than a fault, and we log rather than\n    // debug because log is the tier that becomes a Sentry breadcrumb. It is rare and it describes\n    // real cart state, so it is worth carrying along if some unrelated error is reported later. The\n    // mismatch in the other direction, a code we show with no amount to put beside it, stays at\n    // debug below because it fires for every applicable code with no amount, which is routine for a\n    // free shipping code.\n\n    const unmatched = Array.from(discountAmounts.keys()).filter(title => !codes.includes(title));\n\n    if (unmatched.length > 0) {\n      console.log('Found unmatched discount applications:', unmatched.join(', '));\n    }\n\n    const appliedDiscountsEl = this.shadowRoot.querySelector('.applied-discounts');\n    appliedDiscountsEl.innerHTML = '';\n\n    const appliedDiscountTemplate = this.shadowRoot.querySelector<HTMLTemplateElement>(\n      '#applied-discount-template');\n\n    for (const code of codes) {\n      const appliedDiscountFragment = this.createAppliedDiscount(appliedDiscountTemplate, code,\n        discountAmounts.get(code));\n      appliedDiscountsEl.appendChild(appliedDiscountFragment);\n    }\n\n    const input = this.shadowRoot.querySelector<HTMLInputElement>('#input');\n    input.disabled = this.activeTransactions > 0;\n\n    const submitButton = this.shadowRoot.querySelector<HTMLButtonElement>('#submit-button');\n    submitButton.disabled = input.value.length < 3 || this.activeTransactions > 0;\n\n    const alert = this.shadowRoot.querySelector('alert-element');\n    if (this.errorMessage) {\n      alert.dataset.alertType = 'danger';\n      alert.innerHTML = '';\n      const span = document.createElement('span');\n      span.textContent = this.errorMessage;\n      alert.appendChild(span);\n      alert.hidden = false;\n    } else {\n      alert.hidden = true;\n    }\n  }\n\n  /**\n   * Builds one row for the list of applied discounts, showing the code, the amount coming off the\n   * cart for it, and a button to remove it. The amount is optional because a code can be applied\n   * without the cart allocating anything to it, and the row hides its amount in that case.\n   *\n   * The caller passes the template in because a row is built per code and the template is the same\n   * one every time, so looking it up here would repeat that work for every row.\n   */\n  private createAppliedDiscount(template: HTMLTemplateElement, code: string, amount: number) {\n    const appliedDiscountFragment = <DocumentFragment>template.content.cloneNode(true);\n\n    const discountInfo = appliedDiscountFragment.querySelector<HTMLDivElement>('.discount-info');\n    discountInfo.dataset.code = code;\n\n    const codeEl = discountInfo.querySelector('.discount-code');\n    codeEl.textContent = code;\n\n    if (Number.isInteger(amount)) {\n      // if the amount is found, we display it\n      const amountEl = discountInfo.querySelector('price-element');\n      amountEl.dataset.subunits = amount.toString();\n      amountEl.dataset.currencyCode = this.dataset.currencyCode;\n      amountEl.dataset.currencySymbol = this.dataset.currencySymbol;\n    } else {\n      console.debug('Trying to display discount application, but no amount found', code);\n\n      // if the amount is not found, we still display the code, but we hide the amount\n      for (const infoElement of discountInfo.querySelectorAll<HTMLElement>('.amount-info')) {\n        infoElement.hidden = true;\n      }\n    }\n\n    const cta = appliedDiscountFragment.querySelector('button');\n    cta.dataset.code = code;\n    cta.setAttribute('aria-label', 'Remove discount code ' + code);\n    cta.addEventListener('click', onRemoveDiscountClicked);\n    if (this.activeTransactions > 0) {\n      cta.disabled = true;\n    } else {\n      cta.disabled = false;\n    }\n\n    return appliedDiscountFragment;\n  }\n\n  private onCartTransactionStarted(_event: WindowEventMap['cart-transaction-started']) {\n    this.activeTransactions += 1;\n    this.render();\n  }\n\n  private onCartTransactionEnded(_event: WindowEventMap['cart-transaction-completed']) {\n    // there is a completed event without a matching started event on page load - ignore it\n    if (this.activeTransactions > 0) {\n      this.activeTransactions -= 1;\n    }\n\n    this.render();\n  }\n\n  /**\n   * Whenever someone is typing in the input, we have to consider enabling or disabling the apply\n   * button.\n   */\n  private onDiscountInput(_event: Event) {\n    this.render();\n  }\n\n  private onDiscountFormSubmit(event: SubmitEvent) {\n    event.preventDefault();\n\n    const input = this.shadowRoot.querySelector<HTMLInputElement>('#input');\n\n    const newCodes = [input.value];\n\n    // Both of these carry logic here, not just analytics, unlike most other cart mutations.\n    // Dispatching the failure from this element is what lets this element's own listener catch it\n    // and render the message beside the input. The scenario lets the global listener that renders\n    // errors at the top of the cart drawer recognize this case and suppress its own message,\n    // deferring to the messaging shown beside the input.\n\n    const scenario = 'cart-discount-input';\n    const dispatcher = this;\n\n    // The wrapper in finally is important because it keeps render bound to this instance.\n\n    apply(dispatcher, newCodes, scenario)\n      .then(() => input.value = '')\n      .catch(console.warn)\n      .finally(() => this.render());\n  }\n\n  private onCartUpdateErred(event: GlobalEventHandlersEventMap['cart-update-erred']) {\n    // Because this listener is attached to the element itself rather than to global scope, it only\n    // receives failures that reach this element. Discount failures from other sources, such as\n    // application on page load or as part of adding to the cart, originate elsewhere and never\n    // arrive here; those are surfaced by a separate global listener that renders errors at the top\n    // of the cart drawer.\n\n    // Handle only failures dispatched at this element, the ones it caused, and not any that merely\n    // bubble up through it from a descendant. By convention this element dispatches its own\n    // discount failures from itself, so this holds, but asserting it makes that a local guarantee\n    // rather than something inherited from how errors happen to be dispatched elsewhere.\n\n    if (event.target !== this) {\n      return;\n    }\n\n    // Narrow to discount application failures so that any other kind of failure this element might\n    // dispatch is not mistaken for one.\n\n    if (event.detail.code !== 'APPLY_DISCOUNTS') {\n      return;\n    }\n\n    // Try to get the existing codes\n\n    let existingCodes: string[];\n    try {\n      if (isFullyDefinedStringArray(event.detail.inputs?.before_codes) &&\n        event.detail.inputs.before_codes.length > 0) {\n        existingCodes = event.detail.inputs.before_codes.map(before => before.toUpperCase());\n      } else {\n        const cart = read();\n        // this ignores whether a discount code is currently applicable as we are only using this to\n        // decide what error message to show\n        existingCodes = getDiscountCodes(cart);\n      }\n    } catch (error) {\n      console.warn(error);\n    }\n\n    // If there are already some codes, then one of the reasons a discount does not apply is because\n    // the existing codes are combinable with the new codes and the existing codes provide a better\n    // discount. We try to provide a better error message in this case.\n    if (existingCodes?.length > 0) {\n      this.errorMessage = 'Oops. It looks like this discount could not be applied. But don\\'t ' +\n        'worry, you\\'re already getting the best deal!';\n    } else {\n      this.errorMessage = 'There was a problem applying the discount.' +\n        ' Please try again in a few moments';\n    }\n\n    this.render();\n  }\n\n  private onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    // update the cart we render from, both on initial page load and when the cart state changes\n\n    this.cart = event.detail.cart;\n\n    // On any update, we have a successful update. If there was a prior update error, there may be\n    // still an error message. Hide that error message.\n    this.errorMessage = null;\n    // trigger a render\n    this.render();\n  }\n}\n\nfunction onRemoveDiscountClicked(event: MouseEvent) {\n  // Use the current target rather than the target or the bound receiver. The current target is by\n  // definition the element this listener is attached to, the remove button, regardless of which\n  // inner element actually received the click and without depending on how this function is\n  // invoked. That makes it the reliable choice both for reading the code and as the failure origin.\n\n  const target = event.currentTarget;\n\n  if (target instanceof HTMLButtonElement && !target.disabled) {\n    target.disabled = true;\n\n    const code = target.dataset.code;\n    const scenario = 'cart-discount-input-remove-click';\n\n    // The dispatcher is the clicked button, the element most directly responsible for the removal.\n    // This is a standalone listener bound to the button, so the button is what is in scope; the\n    // surrounding input element would have to be reached by climbing out of its shadow tree. It\n    // also makes no observable difference to listeners outside that tree, since the failure event\n    // composes out of it and is seen as originating from the surrounding element either way.\n\n    const dispatcher = target;\n\n    remove(dispatcher, code, scenario).catch(console.warn);\n  }\n}\n\nfunction isFullyDefinedStringArray(value: any): value is string[] {\n  return Array.isArray(value) && value.every(element => typeof element === 'string');\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'discount-input': DiscountInput;\n  }\n}\n\nif (!customElements.get('discount-input')) {\n  customElements.define('discount-input', DiscountInput);\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { digest } from '../digest.js';\nimport { report } from '../error.js';\nimport { read, write } from '../state.js';\nimport { start } from '../transaction.js';\nimport { getApplicableDiscountCodes } from './discount.js';\nimport { applyDiscountsHelper } from './helper.js';\n\n/**\n * Applies discount codes to the cart.\n *\n * Do not include existing codes in new codes. Only include new codes in new codes.\n *\n * This does not check whether client state is desynced because it does not matter.\n *\n * @param target the element most directly responsible for the apply, used to originate any failure\n * event with truthful provenance instead of at global scope\n * @param newCodes the new discount codes to apply, excluding existing codes\n * @param scenario optional description or code describing the context in which this takes place\n * @param postValidate whether a new code that ends up applied but not applicable counts as a\n * failure, which it always does for an empty cart\n */\nexport async function apply(target: EventTarget | null, newCodes: string[], scenario?: string,\n  postValidate = true) {\n  const transaction = start('applyDiscountCodes', newCodes, scenario);\n\n  let beforeCart: Cart;\n\n  // TODO: this is cheating by relying on a global declared in the cart lifecycle section, which is\n  // a different section. we need a different way to load overrides, such as in their own section.\n\n  try {\n    beforeCart = read();\n    const afterCart = await applyDiscountsHelper(beforeCart, newCodes,\n      cart_lifecycle.discount_overrides, postValidate);\n    write(target, afterCart, transaction, scenario);\n  } catch (error) {\n    // In this catch clause we must be extra paranoid not to introduce additional errors so we can\n    // make very few assumptions about state.\n\n    // Downstream analytics looks at codes that failed to apply. Downstream tools do simple string\n    // aggregation. If we use raw codes, which can be in mixed case, we end up with one bucket for\n    // each variation of character case. To make things easier we emit normalized values.\n    const normalNewCodes = Array.isArray(newCodes) ? newCodes.map(code => code?.toUpperCase()) : [];\n\n    report(target, {\n      code: 'APPLY_DISCOUNTS',\n      error,\n      inputs: {\n        before_cart: digest(beforeCart),\n        before_codes: getApplicableDiscountCodes(beforeCart),\n        new_codes: normalNewCodes\n      },\n      scenario,\n      transaction\n    });\n  } finally {\n    transaction.complete();\n  }\n}\n","import { cartUpdate, type Cart } from '@integrabeauty/shopify-ajax-api';\nimport { digest } from '../digest.js';\nimport { report } from '../error.js';\nimport { cartFetch, read, write } from '../state.js';\nimport { start } from '../transaction.js';\nimport { getDiscountCodes } from './discount.js';\n\n/**\n * Remove a specific discount code from the cart.\n *\n * This leverages a special flag configured in the lange store that enables the discount property to\n * be specified in the cart update request body. This special feature is not documented.\n *\n * Shopify does not provide an API for removing a specific code. We figured out that the discount\n * property accepts a comma separated values list of discounts to apply. Specifying the empty string\n * means remove all. In order to remove a specific discount, we specify a csv of all codes other\n * than the specific code to remove, which effectively removes that discount code. If omitting the\n * discount code results in an empty csv string, then effectively all codes are removed.\n *\n * @param target the element most directly responsible for the removal, used to originate any\n * failure event with truthful provenance instead of at global scope\n * @param code the discount code to remove\n * @param scenario optional description or code describing the context in which this takes place\n */\nexport async function remove(target: EventTarget | null, code: string, scenario?: string) {\n  const transaction = start('removeDiscount', code, scenario);\n\n  let previousCart: Cart;\n\n  try {\n    // In order to remove the discount codes, we need to determine the new set of codes to apply. We\n    // have to preserve any existing codes. The trick to removal is that we basically just filter\n    // out the code to be removed from the list of existing codes, then apply the resulting set of\n    // codes.\n\n    previousCart = read();\n\n    // When obtaining previous codes, we ignore whether the code is applicable. We want to retain\n    // inapplicable codes in the update.\n\n    const previousCodes = getDiscountCodes(previousCart);\n\n    const newCodes = previousCodes.filter(previousCode => previousCode !== code.toUpperCase());\n\n    // The code being removed may be the only applied code, in which case newCodes is an empty\n    // array. When newCodes is an empty array, the join returns an empty string. When discount is an\n    // empty string, cartUpdate is effectively removing all discount codes.\n\n    const discount = newCodes.join(',');\n    await cartUpdate({ discount });\n    const cart = await cartFetch();\n    write(target, cart, transaction, scenario);\n  } catch (error) {\n    report(target, {\n      code: 'REMOVE_DISCOUNT',\n      error,\n      inputs: {\n        code,\n        previous_cart: digest(previousCart)\n      },\n      scenario,\n      transaction\n    });\n  } finally {\n    transaction.complete();\n  }\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders a promo goal. Used in slot of promo-goal-progress.\n */\nclass PromoGoal extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-currency-code',\n      'data-currency-symbol',\n      'data-is-locked',\n      'data-offer-text',\n      'data-threshold-cents'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * The ISO code of the currency\n     *\n     * @example USD\n     */\n    currencyCode: string;\n\n    /**\n     * Currency symbol\n     *\n     * @example $\n     */\n    currencySymbol: string;\n\n    /**\n     * The state of the goal\n     */\n    isLocked: 'false' | 'true';\n\n    /**\n     * Text that describes the promotional goal\n     */\n    offerText: string;\n\n    /**\n     * The amount of money the customer needs to spend to unlock the promotional goal in cents\n     */\n    thresholdCents: string;\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.render();\n  }\n\n  public attributeChangedCallback(_name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && newValue !== oldValue) {\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    if (!this.dataset.thresholdCents ||\n      !this.dataset.currencySymbol ||\n      !this.dataset.currencyCode ||\n      !this.dataset.offerText) {\n      return;\n    }\n\n    const thresholdAmount = parseInt(this.dataset.thresholdCents || '0', 10);\n\n    const label = this.shadowRoot.querySelector('.label');\n\n    // if the promo goal does not have a spend threshold, then it is instantly applicable. in this\n    // case we just need to render 'get y' not 'spend x, get y'\n    label.innerHTML = thresholdAmount === 0 ?\n      `Get ${this.dataset.offerText}!` :\n      `Spend <price-element\n      data-currency-symbol=\"${this.dataset.currencySymbol}\"\n      data-currency-code=\"${this.dataset.currencyCode}\"\n      data-subunits=\"${this.dataset.thresholdCents}\">\n      </price-element>, get ${this.dataset.offerText}!`;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'promo-goal': PromoGoal;\n  }\n}\n\nif (!customElements.get('promo-goal')) {\n  customElements.define('promo-goal', PromoGoal);\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\n\n/**\n * Computes how much of the cart counts toward the next promo goal threshold.\n *\n * We want the total after discounts, except that we want to exclude the promotional discount\n * itself. As a result, we cannot work with the original total price or the price after discounts.\n * So we have to create a custom total of discounts and subtract this from the original total price\n * to get a custom price after discounts.\n *\n * This is a front end mirror of the qualifying amount computed server side by the backend tiered\n * discount functions. If those functions change what counts toward a tier, this must change with\n * them or the progress bar will mislead.\n */\nexport function computeQualifyingSubtotal(cart: Cart) {\n  const originalTotalPrice = cart.original_total_price;\n\n  // This total amount of discounts, regardless of the type of discount\n  const totalDiscountPrice = calculateTotalDiscount(cart);\n\n  // This is the total amount discounted by promo goal discounts (e.g. BMSM)\n  const totalPromoGoalDiscountPrice = calculateTotalPromoGoalDiscount(cart);\n\n  const totalPriceOfExcludedItems = calculateTotalPriceOfPromoExcludedItems(cart);\n\n  const priceAfterDiscounts = originalTotalPrice - totalDiscountPrice;\n  const priceAfterDiscountsExceptPromoGoalDiscounts = priceAfterDiscounts +\n    totalPromoGoalDiscountPrice - totalPriceOfExcludedItems;\n\n  return priceAfterDiscountsExceptPromoGoalDiscounts;\n}\n\nfunction calculateTotalDiscount(cart: Cart) {\n  let total = 0;\n  for (const application of cart.cart_level_discount_applications) {\n    total += application.total_allocated_amount;\n  }\n\n  for (const item of cart.items) {\n    for (const allocation of item.line_level_discount_allocations) {\n      total += allocation.amount;\n    }\n  }\n\n  return total;\n}\n\nfunction calculateTotalPriceOfPromoExcludedItems(cart: Cart) {\n  let total = 0;\n\n  // There are also $0 items to exclude, but since they are 0, we just ignore them since they do not\n  // change the total.\n\n  for (const item of cart.items) {\n    if (item.gift_card) {\n      total += item.final_line_price;\n    }\n  }\n\n  return total;\n}\n\nfunction calculateTotalPromoGoalDiscount(cart: Cart) {\n  let total = 0;\n\n  for (const application of cart.cart_level_discount_applications) {\n    if (isPromoNamedDiscount(application.title)) {\n      total += application.total_allocated_amount;\n    }\n  }\n\n  for (const item of cart.items) {\n    for (const allocation of item.line_level_discount_allocations) {\n      if (isPromoNamedDiscount(allocation.discount_application.title)) {\n        total += allocation.discount_application.total_allocated_amount;\n      }\n    }\n  }\n\n  return total;\n}\n\nfunction isPromoNamedDiscount(value: string) {\n  if (typeof value !== 'string') {\n    return false;\n  }\n\n  const upper = value.toUpperCase();\n\n  if (upper.startsWith('BUY MORE SAVE MORE')) {\n    return true;\n  }\n\n  // This is a special $20 off that is allocated over line items. This is also a type of promo goal.\n  if (upper.includes('$20 DISCOUNT APPLIED')) {\n    return true;\n  }\n\n  if (/SPEND \\$[0-9]+ GET [0-9]+% OFF/.test(upper)) {\n    return true;\n  }\n\n  return false;\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\nimport styles from './index.scss';\nimport { computeQualifyingSubtotal } from './qualifying-subtotal.js';\n\n/**\n * Takes promo-goal web elements as inputs in its slot and renders a progress bar.\n */\nclass PromoGoalProgress extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Active currency ISO code, used when rendering the amounts in the progress message.\n     *\n     * @example \"USD\"\n     */\n    currencyCode: string;\n\n    /**\n     * Currency symbol character, used when rendering the amounts in the progress message.\n     *\n     * @example \"$\"\n     */\n    currencySymbol: string;\n  };\n\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n  private declare cart: Cart;\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\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 onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    this.cart = event.detail.cart;\n    this.render();\n  }\n\n  private render() {\n    if (!this.cart) {\n      return;\n    }\n\n    const qualifyingSubtotal = computeQualifyingSubtotal(this.cart);\n\n    const paragraph = this.shadowRoot.querySelector('p');\n\n    const promoGoals = this.collectPromoGoalsData();\n\n    if (promoGoals.length === 0) {\n      paragraph.hidden = true;\n      return;\n    }\n\n    let eligibleThreshold = 0;\n    const eligiblePromoGoals = [];\n\n    // The goals array is sorted from lowest threshold to highest\n\n    for (const promoGoal of promoGoals) {\n      // When the cart subtotal (estimated total) is greater than or equal to the threshold, then\n      // the promo goal has already been unlocked and can be skipped.\n      if (qualifyingSubtotal >= promoGoal.thresholdCents) {\n        promoGoal.element.dataset.isLocked = 'false';\n        continue;\n      }\n\n      promoGoal.element.dataset.isLocked = 'true';\n\n      // If there are no eligible promo goals, add them now\n      if (eligiblePromoGoals.length === 0) {\n        eligiblePromoGoals.push(promoGoal);\n        // also capture any other promo goals with identical threshold\n        eligibleThreshold = promoGoal.thresholdCents;\n        continue;\n      }\n\n      if (eligibleThreshold === promoGoal.thresholdCents) {\n        eligiblePromoGoals.push(promoGoal);\n        continue;\n      }\n    }\n\n    if (eligiblePromoGoals.length === 0) {\n      paragraph.hidden = true;\n    }\n\n    const thresholdAmount = eligiblePromoGoals.length > 0 ?\n      eligiblePromoGoals[0].thresholdCents :\n      promoGoals[promoGoals.length - 1].thresholdCents;\n\n    const amountToGoal = thresholdAmount - qualifyingSubtotal;\n    const offerText = eligiblePromoGoals.map(i => i.offerText).join(' and ');\n\n    paragraph.innerHTML = `Add <price-element\n      data-currency-code=\"${this.dataset.currencyCode}\"\n      data-currency-symbol=\"${this.dataset.currencySymbol}\"\n      data-subunits=\"${amountToGoal}\">\n      </price-element> to unlock ${offerText}!`;\n\n    const percentSpentToGoal = Math.min(100, Math.round(\n      (qualifyingSubtotal / thresholdAmount) * 100));\n    const progressEl = this.shadowRoot.querySelector('#progress');\n    progressEl.setAttribute('style', `width: ${percentSpentToGoal}%;`);\n\n    type PriceElement = HTMLElementTagNameMap['price-element'];\n    const spentEl = this.shadowRoot.querySelector<PriceElement>('#spent');\n    spentEl.dataset.currencyCode = this.dataset.currencyCode;\n    spentEl.dataset.currencySymbol = this.dataset.currencySymbol;\n\n    if (qualifyingSubtotal >= promoGoals[promoGoals.length - 1].thresholdCents) {\n      spentEl.dataset.subunits = '';\n    } else {\n      spentEl.dataset.subunits = qualifyingSubtotal.toString();\n    }\n\n    const goalEl = this.shadowRoot.querySelector<PriceElement>('#goal');\n    goalEl.dataset.currencyCode = this.dataset.currencyCode;\n    goalEl.dataset.currencySymbol = this.dataset.currencySymbol;\n    goalEl.dataset.subunits = thresholdAmount.toString();\n\n    if (eligiblePromoGoals.length > 0) {\n      paragraph.hidden = false;\n    }\n\n    const progressBar = progressEl.parentElement;\n    progressBar.setAttribute('aria-valuenow', percentSpentToGoal.toString());\n    if (percentSpentToGoal < 100) {\n      progressBar.setAttribute('aria-label', [\n        this.dataset.currencySymbol,\n        (qualifyingSubtotal / 100).toFixed(2),\n        ' of ',\n        this.dataset.currencySymbol,\n        // threshold amounts will usually be rounded to a full dollar\n        (thresholdAmount / 100).toString(),\n        ' goal'\n      ].join(''));\n    } else {\n      progressBar.setAttribute('aria-label', [\n        this.dataset.currencySymbol,\n        (thresholdAmount / 100).toString(),\n        ' goal reached'\n      ].join(''));\n    }\n  }\n\n  private collectPromoGoalsData() {\n    const elements = this.querySelectorAll('promo-goal');\n\n    const goals: PromoGoal[] = [];\n    for (const element of elements) {\n      const goal = <PromoGoal>{};\n      goal.currencyCode = element.dataset.currencyCode;\n      goal.currencySymbol = element.dataset.currencySymbol;\n      goal.isLocked = element.dataset.isLocked === 'true';\n      goal.offerText = element.dataset.offerText;\n      goal.thresholdCents = parseInt(element.dataset.thresholdCents, 10);\n      goal.element = element;\n      goals.push(goal);\n    }\n\n    return goals.sort(compareThresholds);\n  }\n}\n\nfunction compareThresholds(a: PromoGoal, b: PromoGoal) {\n  if (a.thresholdCents === b.thresholdCents) {\n    return 0;\n  }\n\n  return a.thresholdCents < b.thresholdCents ? -1 : 1;\n}\n\ninterface PromoGoal {\n  currencyCode: string;\n  currencySymbol: string;\n  element: HTMLElement;\n  isLocked: boolean;\n  offerText: string;\n  thresholdCents: number;\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'promo-goal-progress': PromoGoalProgress;\n  }\n}\n\nif (!customElements.get('promo-goal-progress')) {\n  customElements.define('promo-goal-progress', PromoGoalProgress);\n}\n","import { measure, now } from './perf.js';\n\n/**\n * Waits for the runtime's load to reach a terminal state. Resolves true once a client is ready to\n * read, and false once the runtime has failed or the given number of milliseconds passes.\n *\n * The caller does not have to care whether the runtime loaded before or after it, which is the\n * whole point. Arriving late is covered by reading the load state, because the broadcast has\n * already been and gone by then, and arriving early is covered by listening for the broadcast.\n * Between them there is no ordering a caller can lose to.\n *\n * A failure resolves as soon as it is known rather than waiting out the timeout. That matters\n * because two of the failures are decided long before any caller's deadline. The theme editor is\n * known before a single bundle runs, and a missing anonymous id is known the moment the runtime\n * looks for one. Sitting out two or three seconds for an answer that already exists would delay\n * everything downstream of the value for nothing.\n *\n * Anything other than pending is terminal, which is what the check tests for, so a failure reason\n * added later needs no change here.\n *\n * On a preview theme the wait is extended, because the runtime deliberately holds the broadcast\n * open there to give the GrowthBook DevTools extension time to push forced variant overrides in. A\n * caller that gave up partway through that hold would silently drop the forced variant and make the\n * extension unreliable to debug with. How long the hold lasts comes off the runtime global, so the\n * two sides cannot drift apart into a hold that outlives the wait covering it.\n *\n * Neither the preview check nor the hold length can be read from anything the runtime's boot script\n * writes, because that script loads async and can execute after a caller has already asked. Both\n * the Shopify globals and the runtime's inline seed are set in the head instead, so they are always\n * available.\n *\n * The timeout stays as a floor for the one case no broadcast can cover, the boot script never\n * running at all. On that path the listener is left registered rather than torn down, since a\n * broadcast that does eventually arrive resolves a promise that has already settled, which changes\n * nothing.\n */\nfunction whenLoaded(milliseconds: number) {\n  return new Promise<boolean>(resolve => {\n    if (growthbook_runtime.load_state !== 'pending') {\n      resolve(growthbook_runtime.load_state === 'ready');\n      return;\n    }\n\n    const extraMillis = Shopify.previewMode ? growthbook_runtime.preview_hold_ms : 0;\n    const timer = setTimeout(resolve, milliseconds + extraMillis, false);\n\n    addEventListener('growthbook-load-settled', () => {\n      clearTimeout(timer);\n      resolve(growthbook_runtime.load_state === 'ready');\n    }, { once: true });\n  });\n}\n\n/**\n * Fault-tolerant wrapper around GrowthBook's own getFeatureValue SDK method.\n *\n * In GrowthBook the unit is a feature (a feature flag) identified by a feature key. The feature's\n * value for the current user is decided by evaluating its rules. One kind of rule is an experiment\n * rule (a \"feature flag experiment\"): it randomly buckets the user into a variation and returns\n * that variation's value. So the same feature key can resolve to a static flag value or to an A/B\n * test assignment, and this call returns whatever it resolves to. The caller cannot tell which, and\n * does not need to. When the feature is backed by an experiment rule, the returned value is the\n * user's assigned variation.\n *\n * Calling this is not a passive read, and that is the thing to know before adding a call. Bucketing\n * happens during the read, and the SDK reports the exposure to the runtime's tracking callback on\n * the way back out, which is what puts the visitor in the experiment's analytics. So the moment a\n * section calls this, that visitor counts as having seen whatever variation came back.\n *\n * That makes where a call sits matter as much as what it does with the answer. A read the visitor\n * could not possibly have seen still counts, and every such visitor lands in both groups and\n * behaves the same in both, which drags the measured difference between them toward zero. So gate\n * the call on the thing being on the page and reachable, rather than reading up front and deciding\n * later.\n *\n * On top of the raw SDK call this wrapper adds:\n *\n * - Fault tolerance. On a timeout, an error, or an undefined feature it returns the caller's\n *   fallback instead of throwing. It does assume the runtime global is present, which the always-on\n *   layer guarantees, so a page that somehow lacks it rejects rather than falling back.\n * - A shared, pre-initialized client. It reads the single client the cross-section coordination\n *   global exposes, so the SDK is initialized once per page rather than once per call.\n * - Indifference to load order. It waits on the runtime's ready signal rather than on the client\n *   promise existing yet, so a caller whose bundle runs before the runtime's is not penalized for\n *   it.\n * - A bounded wait. It always resolves within the given timeout, or within a global default when no\n *   per-call timeout is passed.\n * - Per-page memoization. The first resolved value for a feature key is cached and returned for\n *   every later call in the same page load. Once the payload has loaded this buys nothing on its\n *   own, because the SDK evaluates from attributes that do not change during a page, so repeated\n *   reads would agree with each other anyway. Where it earns its place is the timeout boundary. A\n *   caller that gave up waiting holds the fallback and the page has already acted on it, the client\n *   can become ready a moment later, and without it the next caller would get the real value and\n *   disagree with what the page is showing.\n *\n * @param featureKey The GrowthBook feature key.\n * @param fallback The value returned when the feature is undefined or when the lookup times out or\n * errors.\n * @param timeoutMs Optional per-call override for the lookup timeout, in milliseconds.\n *\n * @see https://docs.growthbook.io/lib/js#quick-usage GrowthBook JS SDK getFeatureValue\n * @see https://docs.growthbook.io/feature-flag-experiments How A/B tests are delivered via features\n * @see https://docs.growthbook.io/features/basics Feature flag fundamentals (keys, values, rules)\n */\nexport async function getFeatureValue<T extends string = string>(featureKey: string,\n  fallback: NoInfer<T>, timeoutMs?: number) {\n  const resolvedFeatures = growthbook_runtime.resolved_features;\n  if (resolvedFeatures[featureKey]) {\n    return <T>resolvedFeatures[featureKey];\n  }\n\n  let value: T | null = null;\n\n  // The span starts here rather than at function entry, so that it only ever covers a real wait.\n  // The memoized return above answers without waiting on anything, and timing it would put a span\n  // on the timeline for every read in the page. Leaving it outside means a span showing up at all\n  // is a caller that actually waited. Closing the span in a finally is what covers the timeout and\n  // the failure paths, which are the ones most worth a number.\n\n  const startMillis = now();\n  const effectiveTimeoutMs = timeoutMs ?? growthbook_runtime.default_timeout_ms;\n\n  try {\n    const loaded = await whenLoaded(effectiveTimeoutMs);\n\n    // The runtime either failed or ran out of time, and neither is reported here so as to avoid\n    // Sentry noise, so we return instead of throwing. The fallback is cached so that later calls\n    // for the same feature key in the same page load get the same answer, since a feature read in\n    // several places has to agree with itself.\n\n    if (!loaded) {\n      resolvedFeatures[featureKey] = fallback;\n      return fallback;\n    }\n\n    // The signal says the client is ready, so this await is already settled and only defends\n    // against the impossible case of the two disagreeing.\n\n    const growthbook = await growthbook_runtime.client_loader;\n\n    if (!growthbook) {\n      resolvedFeatures[featureKey] = fallback;\n      return fallback;\n    }\n\n    // Passing null here rather than the caller's fallback is deliberate. The SDK applies its own\n    // default only on a strict null check against the evaluated value, so handing it null makes\n    // this a straight pass-through of whatever the feature evaluated to, and every \"nothing usable\"\n    // case then lands on the single type check below.\n    //\n    // The evaluated value is null for an unknown feature key, for a feature with no default value\n    // configured, and for an unmet prerequisite. Passing the fallback here would cover exactly\n    // those and nothing else.\n    //\n    // What it would not cover is a value that is present but not a string. A feature configured\n    // with a number, a boolean, or a JSON value evaluates to that value, the SDK's null check does\n    // not fire, and the value comes back untouched. Undefined slips through for the same reason. So\n    // the type check below is load-bearing either way, and routing everything through it keeps the\n    // decision about what counts as usable in one place.\n\n    value = <T | null>growthbook.getFeatureValue<string | null>(featureKey, null);\n  } catch (error) {\n    console.warn('Error getting feature value from GrowthBook:', error);\n  } finally {\n    measure('growthbook-feature-value', startMillis, now(), {\n      feature: featureKey,\n      timeout: effectiveTimeoutMs\n    });\n  }\n\n  // The value comes from GrowthBook, so its type is an unverified boundary assertion. Coalesce\n  // anything that is not a string (null, or a missing or unexpected value) to the caller's default,\n  // so the resolved value is always a defined string.\n\n  if (typeof value !== 'string') {\n    value = fallback;\n  }\n\n  resolvedFeatures[featureKey] = value;\n\n  return value;\n}\n\n/**\n * Compares a value read through {@link getFeatureValue} against an expected value, ignoring case.\n * Every comparison against a feature value goes through this, even where a plain equality check\n * would do today, so that the casing decision lives in one place and new code copied from an\n * existing call site picks it up.\n *\n * The value is GrowthBook boundary data whose casing is not guaranteed, and a feature has been seen\n * returning an uppercase value. The expected side can be typed by hand too, such as an experiment\n * id typed into a section setting in the theme editor. Neither side's casing is enforced anywhere,\n * so we lowercase both. We leave the value itself untouched in the wrapper, so a caller reading it\n * directly still sees exactly what GrowthBook served.\n *\n * @param value The feature value\n * @param expected The value to compare against\n */\nexport function featureValueMatches(value: string, expected: string) {\n  return value.toLowerCase() === expected.toLowerCase();\n}\n","import { cartUpdate } from '@integrabeauty/shopify-ajax-api';\nimport { getEventQueue, isCartUpdatedEvent } from '../../lib/cart-event-queue.js';\nimport { ready } from '../../lib/dom-ready.js';\nimport { getFeatureValue, featureValueMatches } from '../../lib/growthbook-feature.js';\n\n/**\n * We are intentionally not using a transaction to lock down the UI here or rewrite the global page\n * cart state. We do not think it is needed here. Beware that this relies on other code that checks\n * for this attribute to not rely on the cart state from events, unless it so happens to be based on\n * updated state after a later fetch. So this is a point in time where local cart state in the cart\n * lifecycle goes stale.\n *\n * Shopify's update cart endpoint only mutates the specified attributes. We do not need to gather\n * all attributes first in order to ensure they are preserved. We are just writing the attributes we\n * own and leaving all of the other attributes unaffected. This function is written with the\n * assumption that there is point to the update.\n *\n * This should not be called when the attribute already exists. The caller should be checking first.\n */\nfunction onGetFeatureValue(promoProgress: HTMLElementTagNameMap['promo-goal-progress-v2'],\n  group: string, cartTotal: number) {\n  // We have just found out which group this shopper is in, and this is the only point in this page\n  // view where we know it. The attribute write below does not come back around to us as a cart\n  // updated event, so nothing else is going to show the bar. If we leave it to the attribute, the\n  // shopper sees no bar at all until their next page load fetches the cart from Shopify and tells\n  // us the group all over again.\n\n  // Only group \"2\" sees the free shipping progress bar when the cart total is at least $75\n  if (featureValueMatches(group, '2') && cartTotal >= 7500) {\n    promoProgress.dataset.show = 'true';\n  }\n\n  // Only group \"1\" sees the free shipping progress bar unconditionally\n  if (featureValueMatches(group, '1')) {\n    promoProgress.dataset.show = 'true';\n  }\n\n  // We clear the attribute from the first shipping experiment at the same time. Shoppers who were\n  // in that experiment still carry it on their cart, and Shopify copies every cart attribute onto\n  // the order's note attributes at checkout, so it keeps turning up in reporting long after that\n  // experiment ended. Setting a value to null is how we remove a key.\n  return cartUpdate({\n    attributes: {\n      _aux_opt_in: null,\n      _aux_opt_in_v2: null,\n      _aux_opt_in_v3: group\n    }\n  });\n}\n\nfunction onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n  const promoProgress = document.querySelector<HTMLElementTagNameMap['promo-goal-progress-v2']>(\n    'cart-element promo-goal-progress-v2');\n\n  // Since the feature gate determining whether to render the promo goals progress bar is server\n  // side, we need to check if the promo goals progress bar exists to determine if the experiment is\n  // active. If the promo goals progress bar does not exist, we can exit early.\n  if (!promoProgress) {\n    return;\n  }\n\n  // Once a shopper's group is written on their cart, every later cart update comes back through\n  // here, so this is what shows the bar on all of their page loads after the one where we picked\n  // the group. We stop instead of falling through, because reading the feature again would report\n  // another exposure to GrowthBook for a shopper we already bucketed.\n\n  // If the shopper is in the \"2\" group they are shown the $89 Free Shipping progress bar when the\n  // cart total price is at least $75\n  if (event.detail.cart.attributes?._aux_opt_in_v3 === '2') {\n    // Since cart total price changes in both directions, we must also hide the progress bar when\n    // the total falls below the threshold.\n    if (event.detail.cart.total_price >= 7500) {\n      promoProgress.dataset.show = 'true';\n    } else {\n      promoProgress.dataset.show = 'false';\n    }\n\n    return;\n  }\n\n  // If the shopper is in the \"1\" group they are shown the $89 Free Shipping progress bar\n  // unconditionally\n  if (event.detail.cart.attributes?._aux_opt_in_v3 === '1') {\n    promoProgress.dataset.show = 'true';\n    return;\n  }\n\n  // Shoppers bucketed into \"old\" group get no progress bar, so there is nothing to render. We still\n  // have to stop here so that we do not fall through and read the feature a second time.\n  if (event.detail.cart.attributes?._aux_opt_in_v3 === '0') {\n    return;\n  }\n\n  getFeatureValue('shipping-suppression-experiment', '0')\n    .then(value => onGetFeatureValue(promoProgress, value, event.detail.cart.total_price))\n    .catch(console.warn);\n}\n\nfunction onReady() {\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 { v4 as uuidv4 } from 'uuid';\nimport { getEventQueue, isCartUpdateErredEvent } from '../../lib/cart-event-queue.js';\nimport { now } from '../../lib/perf.js';\n\n// Drives showing and hiding the cart drawer. It owns the open and close requests the rest of the\n// page dispatches, the light dismiss gesture the modal manager broadcasts, the decision to open on\n// a failed cart init, and the state attributes on the drawer element that everything else reads.\n// This lives beside the section index rather than inside it because the index is only there to pull\n// the section's pieces together, and it lives outside the drawer element because the requests come\n// from all over the page rather than from the element itself.\n\n/**\n * One open or close of the drawer, kept so the next change can be compared against it.\n */\ninterface DrawerChange {\n  direction: 'close' | 'open';\n  source: string;\n  time: number;\n}\n\n/**\n * The most recent open or close of the drawer on this page, or undefined before the first one.\n */\nlet lastChange: DrawerChange;\n\n/**\n * Whether we already warned about a fast flip on this page. We only warn once per page load.\n */\nlet fastFlipWarned = false;\n\n/**\n * Opens the cart drawer. Emits a cart-drawer-opened event and a cart-viewed event.\n *\n * The cart-drawer-opened event is primarily only intended for downstream analytics, not managing UI\n * state. Avoid implementing any ui management logic that uses this event.\n *\n * Callers ask to open without knowing whether the drawer is already open, the same way they do when\n * closing, so this mirrors close and owns that check itself. The source is only for diagnostics.\n */\nfunction open(animate: boolean, source: string) {\n  const cartDrawer = document.querySelector('cart-drawer');\n\n  // Not finding the drawer is normal and not a problem. The cart page renders this section without\n  // the cart-drawer wrapper around it, so there is no drawer to open there.\n  if (!cartDrawer) {\n    return;\n  }\n\n  // The drawer marks itself open with this flag while it is shown, so an open request that arrives\n  // while it is already open is redundant and the rest of this would re-open an open drawer.\n  if (cartDrawer.dataset.show === 'true') {\n    console.log('Drawer already open, ignoring open request from', source);\n    return;\n  }\n\n  cartDrawer.dataset.animate = animate ? 'true' : 'false';\n  cartDrawer.dataset.show = 'true';\n\n  console.log('Cart opened', source);\n\n  checkFastFlip('open', source);\n\n  const showEvent = new Event('modal-open-requested', { bubbles: true, composed: true });\n  cartDrawer.dispatchEvent(showEvent);\n\n  addEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n  type CartDrawerOpenedEvent = GlobalEventHandlersEventMap['cart-drawer-opened'];\n  const openedEvent = new CustomEvent<CartDrawerOpenedEvent['detail']>('cart-drawer-opened', {\n    bubbles: true,\n    composed: true,\n    detail: {\n      event_id: uuidv4()\n    }\n  });\n  cartDrawer.dispatchEvent(openedEvent);\n\n  type CartViewedEvent = GlobalEventHandlersEventMap['cart-viewed'];\n  const viewedEvent = new CustomEvent<CartViewedEvent['detail']>('cart-viewed', {\n    bubbles: true,\n    composed: true,\n    detail: {\n      surface: 'drawer'\n    }\n  });\n  cartDrawer.dispatchEvent(viewedEvent);\n}\n\n/**\n * Closes the cart drawer.\n *\n * This bails out unless the drawer is actually open, which makes closing idempotent at the source.\n * Callers ask to close without knowing whether the drawer is open. The installment message closes\n * the cart preemptively before opening its own modal even though on a product page the cart is\n * almost never open, and the drawer's own close control can be activated twice in quick succession\n * before the first close finishes. Without this guard each of those still announces a close to the\n * modal manager, which then sees a close for a modal it does not have recorded as active and logs a\n * mismatch warning. Mirroring the early return that the open path already uses keeps a redundant\n * close from reaching the manager at all, and it also avoids broadcasting a closed event for a\n * close that never happened. Production diagnostics confirmed this unguarded close was the source\n * of those warnings and that the page was already fully torn down in every case, so returning here\n * strands nothing.\n *\n * The source is only for diagnostics, the same as on open.\n */\nfunction close(animate: boolean, source: string) {\n  // Drop the dismiss listener unconditionally, ahead of the early returns below. It is registered\n  // on the window while the drawer is open, so it outlives the drawer element: if the section is\n  // rebuilt and replaced while open, the replacement comes back without the open flag, and the\n  // guards below would then skip cleanup and leak the listener. Removal is idempotent and has no\n  // observable effect when nothing is registered, unlike the event broadcasts further down, which\n  // must stay behind the guard so a redundant close stays silent.\n  removeEventListener('modal-dismiss-requested', onModalDismissRequested);\n\n  const cartDrawer = document.querySelector('cart-drawer');\n\n  // Not finding the drawer is normal and not a problem. The cart page renders this section without\n  // the cart-drawer wrapper around it, so there is no drawer to close there.\n  if (!cartDrawer) {\n    return;\n  }\n\n  // The drawer marks itself open with this flag while it is shown, so anything other than open\n  // means there is nothing to close and the rest of this would be a redundant close.\n  if (cartDrawer.dataset.show !== 'true') {\n    console.log('Not closing cart because the drawer is not open, close request from', source);\n    return;\n  }\n\n  cartDrawer.dataset.animate = animate ? 'true' : 'false';\n  cartDrawer.dataset.show = 'false';\n\n  console.log('Cart closed', source);\n\n  checkFastFlip('close', source);\n\n  const closeEvent = new Event('modal-close-requested', { bubbles: true, composed: true });\n  cartDrawer.dispatchEvent(closeEvent);\n\n  const closedEvent = new Event('cart-drawer-closed', { bubbles: true, composed: true });\n  cartDrawer.dispatchEvent(closedEvent);\n}\n\n/**\n * Warns when the drawer opens and closes, or closes and opens, faster than a shopper would do it\n * without clicking anything.\n *\n * We have had a report of the drawer flashing on and off by itself during page load that we could\n * not reproduce. The open and close log lines, with their sources, already become breadcrumbs, but\n * breadcrumbs only get sent along with an event. So this raises one warning, which carries the\n * recent drawer history with it, and then stops checking for the rest of the page.\n */\nfunction checkFastFlip(direction: DrawerChange['direction'], source: string) {\n  if (fastFlipWarned) {\n    return;\n  }\n\n  const previous = lastChange;\n  const time = now();\n  lastChange = { direction, source, time };\n\n  if (!previous) {\n    return;\n  }\n\n  // The most milliseconds between two changes that we still count as a fast flip. Changes further\n  // apart than this are slow enough for a shopper to have made on purpose.\n  const maxGap = 1000;\n\n  const gap = time - previous.time;\n  if (gap > maxGap) {\n    return;\n  }\n\n  // A shopper can open the drawer and close it again quickly on purpose, and the upsell modal hands\n  // off to and from the drawer on a click too. These are the sources that come from a shopper\n  // clicking or pressing a key, so a fast flip made only of these is expected. This list has to be\n  // kept up to date by hand as sources get added.\n  const shopperSources = [\n    'add-to-cart-button',\n    'cart-continue-shopping-button',\n    'checkout-upsell-handoff',\n    'close-button',\n    'continue-shopping-button',\n    'header-cart-button-click',\n    'modal-dismiss',\n    'upsell-modal-close-click'\n  ];\n\n  if (shopperSources.includes(previous.source) && shopperSources.includes(source)) {\n    return;\n  }\n\n  fastFlipWarned = true;\n\n  const error: any = new Error('Cart drawer changed state too quickly');\n  error.gap = Math.round(gap);\n  error.previousDirection = previous.direction;\n  error.previousSource = previous.source;\n  error.currentDirection = direction;\n  error.currentSource = source;\n  error.visibilityState = document.visibilityState;\n  console.warn(error);\n}\n\nfunction onCartOpenRequested(event: WindowEventMap['cart-open-requested']) {\n  const animate = event.detail?.animate ?? true;\n  const source = event.detail?.source;\n  open(animate, source);\n}\n\nfunction onCartCloseRequested(event: WindowEventMap['cart-close-requested']) {\n  const animate = event.detail?.animate ?? true;\n  const source = event.detail?.source;\n  close(animate, source);\n}\n\n/**\n * Closes the cart drawer when the user performs a light-dismiss gesture (a click on the backdrop or\n * an escape key press) while it is the open modal. The modal manager broadcasts the request; we\n * close with animation since this is a user-initiated dismissal.\n */\nfunction onModalDismissRequested(_event: WindowEventMap['modal-dismiss-requested']) {\n  console.log('Closing cart on modal-dismiss-requested');\n  close(true, 'modal-dismiss');\n}\n\n/**\n * Opens the drawer when cart init fails, so the shopper sees that it failed. The message itself is\n * rendered separately by cart-messages in response to the same error, so all we do here is make it\n * visible. We have no other way to show the shopper that message. Only an init failure opens the\n * drawer. The other cart errors happen while the drawer is already in use, so they are left to\n * their own handling.\n *\n * We keep listening after the first failure rather than unregistering, because cart init can happen\n * more than once during the life of the page.\n */\nfunction onCartUpdateErred(event: GlobalEventHandlersEventMap['cart-update-erred']) {\n  if (event.detail.code === 'INIT') {\n    open(true, 'cart-lifecycle-error');\n  }\n}\n\nfunction main() {\n  // Init can fail before this bundle runs, because this bundle is deferred and the lifecycle\n  // snippet starts init while the page is still parsing. A listener we register now would never\n  // hear about that failure, and the drawer would stay shut over an error message already rendered\n  // inside it. So we replay the buffered cart events for a failure we missed, then listen for the\n  // ones still to come.\n\n  const cartUpdateErrorEvents = getEventQueue().filter(isCartUpdateErredEvent);\n\n  if (cartUpdateErrorEvents.length > 0) {\n    console.log('Replaying %d cart error events in drawer-control', cartUpdateErrorEvents.length);\n  }\n\n  for (const event of cartUpdateErrorEvents) {\n    onCartUpdateErred(event);\n  }\n\n  addEventListener('cart-update-erred', onCartUpdateErred);\n  addEventListener('cart-close-requested', onCartCloseRequested);\n  addEventListener('cart-open-requested', onCartOpenRequested);\n}\n\nmain();\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { getEventQueue, isCartUpdatedEvent } from '../../../lib/cart-event-queue.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * This custom element wraps and displays a pre-checkout upsell drawer when a user clicks \"Checkout\"\n * from the cart drawer view.\n *\n * The modal shows and hides itself off `data-modal-state` rather than `data-show`. For now it sets\n * that attribute itself whenever `data-show` changes. Later the modal manager sets it instead.\n */\nclass UpsellModal extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-modal-state',\n      'data-show'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * If 'true' the opening and closing of the modal will be animated\n     */\n    animate: 'false' | 'true';\n\n    /**\n     * Currency ISO code. Required.\n     *\n     * @example \"US\"\n     */\n    currencyCode: string;\n\n    /**\n     * Currency Symbol. Required.\n     *\n     * @example \"US\"\n     */\n    currencySymbol: string;\n\n    /**\n     * Whether the modal is shown, as \"open\". Anything else, or no value at all, means closed.\n     */\n    modalState: string;\n\n    /**\n     * Controls the visibility of the modal\n     */\n    show: 'false' | 'true';\n\n    /**\n     * Subtitle copy to display in pre-checkout upsell modal\n     */\n    subtitle: string;\n\n    /**\n     * Title copy to display in pre-checkout upsell modal\n     */\n    title: string;\n\n  };\n\n  private declare cartVariantIds: number[];\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n  private onCloseClickedBound = this.onCloseClicked.bind(this);\n  private onCollapseAnimationEndedBound = this.onCollapseAnimationEnded.bind(this);\n  private onExpandAnimationEndedBound = this.onExpandAnimationEnded.bind(this);\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && name === 'data-show' && newValue !== oldValue) {\n      this.dataset.modalState = newValue === 'true' ? 'open' : 'closed';\n    }\n\n    if (this.isConnected && name === 'data-modal-state' && newValue !== oldValue) {\n      if (newValue === 'open') {\n        this.openModal();\n      } else if (oldValue === 'open') {\n        this.closeModal();\n      }\n    }\n  }\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    const closeButton = this.shadowRoot.querySelector('close-button');\n    closeButton.addEventListener('click', this.onCloseClickedBound);\n\n    for (const event of getEventQueue()) {\n      if (isCartUpdatedEvent(event)) {\n        try {\n          this.onCartUpdatedBound(event);\n        } catch (error) {\n          console.warn(error);\n        }\n      }\n    }\n\n    addEventListener('cart-updated', this.onCartUpdatedBound);\n  }\n\n  public disconnectedCallback() {\n    const closeButton = this.shadowRoot.querySelector('close-button');\n    closeButton?.removeEventListener('click', this.onCloseClickedBound);\n\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open', delegatesFocus: true });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private openModal() {\n    const modal = this.shadowRoot.querySelector<HTMLElement>('div[role=\"dialog\"]');\n    modal.inert = false;\n\n    const animate = this.dataset.animate ?? 'true';\n\n    // If we are reopening while the slide out is still running, the close listener is still\n    // attached, and it would hide the dialog the moment the slide in finishes. So we drop it here.\n    modal.removeEventListener('animationend', this.onCollapseAnimationEndedBound);\n\n    if (animate === 'true') {\n      // before animation focus cannot be set because the element has `visibility: hidden`, we need\n      // to wait till the animation end\n      modal.addEventListener('animationend', this.onExpandAnimationEndedBound);\n    }\n\n    modal.hidden = false;\n    modal.classList.remove('collapsed');\n    modal.classList.add('expanded');\n\n    if (animate === 'false') {\n      this.initializeFocus();\n    }\n\n    const event = new Event('modal-open-requested', { bubbles: true, composed: true });\n    modal.dispatchEvent(event);\n  }\n\n  private closeModal() {\n    const modal = this.shadowRoot.querySelector<HTMLElement>('div[role=\"dialog\"]');\n\n    // The panel stays on screen while it slides out, so for those couple hundred milliseconds it is\n    // still clickable and still in the tab order. We mark it inert first so nobody can reach a\n    // panel that is on its way out. Opening clears this again.\n    modal.inert = true;\n\n    const animate = this.dataset.animate ?? 'true';\n\n    // If we are closing while the slide in is still running, the open listener is still attached,\n    // and it would move focus into a dialog we are busy closing. So we drop it here.\n    modal.removeEventListener('animationend', this.onExpandAnimationEndedBound);\n\n    if (animate === 'true') {\n      // Hiding the dialog sets display to none, and the browser cannot animate an element that is\n      // not displayed. So we leave it showing and wait for the slide out to finish before we hide\n      // it, the same way opening waits for the slide in before it moves focus.\n      modal.addEventListener('animationend', this.onCollapseAnimationEndedBound);\n    }\n\n    modal.classList.remove('expanded');\n    modal.classList.add('collapsed');\n\n    if (animate === 'false') {\n      modal.hidden = true;\n    }\n\n    const event = new Event('modal-close-requested', { bubbles: true, composed: true });\n    modal.dispatchEvent(event);\n  }\n\n  private render() {\n    const titleEl = this.shadowRoot.querySelector('.header h2');\n    titleEl.textContent = this.dataset.title;\n\n    const subtitleEl = this.shadowRoot.querySelector('.header p');\n    subtitleEl.textContent = this.dataset.subtitle;\n\n    if (this.cartVariantIds?.length < 1) {\n      return;\n    }\n\n    const upsellItems = this.querySelectorAll<HTMLElement>('li[data-product-id]');\n    for (const upsellItem of upsellItems) {\n      const variantId = parseInt(upsellItem.dataset.variantId, 10);\n      // When the variant is already in the cart, hidden becomes true and the item is not shown.\n      // When it is not in the cart, hidden becomes false and the item is shown. We only upsell\n      // something the shopper does not already have.\n      upsellItem.hidden = this.cartVariantIds.includes(variantId);\n    }\n  }\n\n  private onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    this.cartVariantIds = event.detail.cart.items.map(item => item.variant_id);\n\n    this.render();\n  }\n\n  private onCollapseAnimationEnded(event: AnimationEvent) {\n    if (event.currentTarget instanceof HTMLElement) {\n      event.currentTarget.removeEventListener('animationend', this.onCollapseAnimationEndedBound);\n      event.currentTarget.hidden = true;\n    }\n  }\n\n  private onExpandAnimationEnded(event: AnimationEvent) {\n    if (event.currentTarget instanceof HTMLElement) {\n      event.currentTarget.removeEventListener('animationend', this.onExpandAnimationEndedBound);\n    }\n\n    this.initializeFocus();\n  }\n\n  private initializeFocus() {\n    const closeButton = this.shadowRoot.querySelector('close-button');\n    closeButton.focus({ focusVisible: true });\n  }\n\n  private onCloseClicked(_event: MouseEvent) {\n    const event = new Event('upsell-close-requested', { bubbles: true, composed: true });\n    this.dispatchEvent(event);\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        animate: false,\n        source: 'upsell-modal-close-click'\n      }\n    });\n    this.dispatchEvent(cartOpenEvent);\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'upsell-modal': UpsellModal;\n  }\n}\n\nif (!customElements.get('upsell-modal')) {\n  customElements.define('upsell-modal', UpsellModal);\n}\n","import type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { v4 as uuidv4 } from 'uuid';\nimport { read } from '../../lib/cart/state.js';\n\n/**\n * Reports the start of checkout when the visitor clicks the checkout button.\n *\n * Checkout can occur on other websites such as Shopify's Shop Pay checkout. We do not have a\n * trivial way of recording the start of checkout when a checkout page loads. Therefore we have to\n * try to fire the event before navigating to checkout.\n *\n * This watches for the click from out here rather than being called by the cart actions element, so\n * that the element's own click handling stays concerned only with navigating to checkout. It is a\n * best effort report, and observing in parallel means it cannot delay or prevent the navigation.\n * The button lives inside a shadow root, so the click is identified from the composed path rather\n * than from the target, which is retargeted to the host by the time it reaches us. The report is\n * then dispatched from the button itself, since that is the element responsible for it. Bubbling\n * out of the shadow root is what carries it to the window level consumers that read it.\n *\n * The event is dispatched via setTimeout. Non-native events are evaluated synchronously by\n * listeners. Dispatching via setTimeout ensures that no long-running listener introduces extra\n * delay before the redirect occurs. It is vital that the redirect occurs quickly. There is already\n * a concerning lag that happens when navigating to checkout that we do not want to increase.\n */\nfunction onClick(event: MouseEvent) {\n  const targets = event.composedPath();\n  const target = targets.find(target => target instanceof HTMLButtonElement &&\n    target.id === 'checkout-button');\n  if (target) {\n    setTimeout(emit, 0, target);\n  }\n}\n\nfunction emit(target: EventTarget) {\n  const cart = read();\n\n  const startedEvent = new CustomEvent<CheckoutStartedEvent['detail']>('checkout-started', {\n    bubbles: true,\n    cancelable: false,\n    composed: true,\n    detail: {\n      cart,\n      event_id: uuidv4()\n    }\n  });\n\n  target.dispatchEvent(startedEvent);\n}\n\n/**\n * Announces that the visitor is on their way to checkout.\n */\ntype CheckoutStartedEvent = CustomEvent<{\n  /**\n   * Snapshot of the cart taken at the moment of the click.\n   */\n  cart: Cart;\n\n  /**\n   * Identifies this occurrence so downstream pixels can deduplicate it.\n   */\n  event_id: string;\n}>;\n\ndeclare global {\n  interface GlobalEventHandlersEventMap {\n    'checkout-started': CheckoutStartedEvent;\n  }\n}\n\ndocument.addEventListener('click', onClick);\n"],"names":["getEventQueue","cart_lifecycle","queue","isCartUpdatedEvent","value","Event","type","isCartUpdateErredEvent","ready","callback","document","readyState","timer","setTimeout","clearTimeout","addEventListener","removeEventListener","CartActions","HTMLElement","checkoutInitSucceeded","onCheckoutButtonClickedBound","this","onCheckoutButtonClicked","bind","onCartTransactionStartedBound","onCartTransactionStarted","onCartTransactionCompletedBound","onCartTransactionCompleted","onCartUpdatedBound","onCartUpdated","onReadyBound","onReady","onPageShowBound","onPageShow","onContinueShoppingButtonClickedBound","onContinueShoppingButtonClicked","connectedCallback","initializeShadowRoot","releaseReady","disconnectedCallback","checkoutButton","shadowRoot","querySelector","continueShopping","attachShadow","mode","innerHTML","isConnected","continueButton","dataset","hideCloseButton","remove","event","error","console","warn","button","currentTarget","HTMLButtonElement","log","disabled","sessionStorage","setItem","Date","now","toString","modal","querySelectorAll","length","cart_drawer","upsell_modal_displayed","cartCloseEvent","CustomEvent","bubbles","composed","detail","animate","source","dispatchEvent","upsellEvent","url","URL","Shopify","routes","root","skipShopPay","searchParams","set","location","href","_event","endLoading","is_initial","persisted","upsell","show","cartOpenEvent","customElements","get","define","CartDrawer","observedAttributes","onCloseClickedBound","onCloseClicked","closeButton","attributeChangedCallback","name","oldValue","newValue","modalState","render","scrollTo","showCartDrawer","inert","focus","focusVisible","hideCartDrawer","safeDecode","decodeURIComponent","read","cookie","split","map","parts","_parts$slice","index","indexOf","encodedValue","slice","trim","undefined","filter","readAll","shift","CartGwp","tiers","updateTierStates","cart","maybeAutoAdd","collectTierData","lockedThresholds","t","total_price","thresholdCents","nextThreshold","Math","min","tier","state","determineTierState","currentAmountCents","readGwpAutoAddCookie","rewardType","variants","v","available","selector","CSS","escape","promoCode","click","tierElements","tierEl","_tierEl$dataset$rewar","_tierEl$dataset$promo","_tierEl$dataset$thres","isShipping","push","parseInt","goalText","claimMessage","unlockedMessage","parseVariants","sort","a","b","renderTier","template","importNode","content","classList","add","isUnlocked","iconId","iconTemplate","appendChild","labelPriceEl","labelNameEl","currencyCode","currencySymbol","subunits","textContent","getTierLabel","amountsEl","currentSubunits","currentEl","thresholdEl","messagePrefixEl","messagePriceEl","messageTextEl","remaining","max","progressEl","percent","round","setAttribute","style","width","cardsEl","availableVariants","variant","renderVariantCard","card","link","img","imageSrc","src","alt","title","compareEl","compareAtPrice","priceEl","customAttribute","createElement","key","variantEl","slot","id","variantId","quantity","addToCartButton","discountCode","_this$tiers","tiersContainer","replaceChildren","productElements","productEl","_productEl$querySelec","_productEl$dataset$ti","_variantEl$dataset$ti","_variantEl$dataset$co","_ref","_variantEl$dataset$im","_variantEl$dataset$id","selectedId","selectedVariantId","productTitle","variantTitle","mainImage","code","toLowerCase","items","some","item","line_level_discount_allocations","alloc","discount_application","isGwpClaimedInCart","CartElement","onCartInitRequestedBound","onCartInitRequested","onCartUpdateErredBound","onCartUpdateErred","onContinueShoppingButtonClickBound","onContinueShoppingButtonClick","onShowDiscountInputClickedBound","onShowDiscountInputClicked","cartViewedDispatched","showDiscountInput","hideLoadingMessage","bestSellersUrl","templateName","origin","onCartUpdatedInitial","_this$querySelector","hidden","promo","installmentsMessage","countryCode","isMBG","estimatedTotal","renderDeferredPaymentMessage","estTotal","hideBackdrop","dispatchCartViewed","heading","item_count","join","cartContents","emptyCart","footerEl","lineItems","removeAttribute","promoGoalEl","installmentPaymentMessageElement","_cart$attributes","attributes","_aux_opt_in_v2","promoGoalV2El","surface","deferredAmount","_item$selling_plan_al","_item$selling_plan_al2","selling_plan_allocation","selling_plan","recurring_deliveries","price","line_level_total_discount","dueTodayEl","showBackdrop","backdrop","_item$properties","properties","getRequestId","response","headers","isResponse","ok","status","text","json","decorateError","resolveUrl","path","protocol","host","async","cartChange","input","init","body","JSON","stringify","cache","method","fetch","Error","_error$response_body3","_error$response_body4","consumeError","message","response_body","description","cartFetch","responseBodyText","responseBodyObject","parse","isCart","cartUpdate","_error$response_body5","_error$response_body6","isUnavailableError","isObject","startsWith","includes","Array","isArray","digest","cart_input_type","_cart$cart_level_disc","_cart$items","token","attribute_keys","Object","keys","discount_codes","cart_level_discount_titles","cart_level_discount_applications","application","_item$line_level_disc","variant_id","discounts","allocation","_allocation$discount_","digest_failed","hasSimilarItems","cart1","cart2","item1","found","item2","decorate_error_decorateError","assignmentError","reason","tag","prototype","call","report","dispatcher","target","Element","window","dispatchCartUpdateErred","ignoredCodes","_detail$transaction","_detail$transaction2","inputs","scenario","transaction","reportError","raise","escalateCartError","assert","expression","AssertionError","constructor","super","rnds8","Uint8Array","byteToHex","i","unsafeStringify","arr","offset","dist_v4","options","buf","crypto","randomUUID","_options$random","rnds","random","rng","getRandomValues","RangeError","_v4","copy","structuredClone","cloned","trustworthy","isWellFormed","upper","toUpperCase","test","containsJavascriptWord","getApplicableDiscountCodes","applicable","getDiscountCodes","state_write","initial","previous","state_read","oldVariants","groupByVariant","newVariants","prevCodes","currCodes","discount_codes_added","complement","discount_codes_removed","event_id","variants_added","findVariantsAdded","variants_removed","findVariantsRemoved","state_cartFetch","limit","attempts","delays","lastError","delay","resolveAfterTimeout","cartFetchOnceWithTimeout","timeout","timeoutPromise","Promise","_resolve","reject","race","milliseconds","resolve","output","property","merged_properties","image_url","image","product_id","product_title","product_type","sku","variant_title","c0map","c1map","increments","decrements","_performance","performance","debug","measure","start","end","_performance3","getSharedState","cart_transaction","activeCount","count","timeline","shared","Number","isInteger","_len","arguments","args","_key","epoch","completed","startMillis","didTimeOut","complete","snapshot","reportConcurrency","append","duration","entry","splice","change","remoteCart","error_code","determineErrorCode","previous_cart","fetched_cart","isString","update_determineErrorCode","CartLineItem","onSellingPlanChangedBound","onSellingPlanChanged","onUpgradeButtonClickBound","onUpgradeButtonClick","onRemoveLineItemClickBound","onRemoveLineItemClick","onQuantityChangedBound","onQuantityChanged","removeButton","sellingPlanSelect","upgradeButton","_name","HTMLSelectElement","updates","lineItemId","_item$parent_relation","parent_relationship","parent_key","update","catch","trusted","isTrusted","lineKeys","errorCode","every","Set","size","lineIndicesToRemove","reverse","line","line_keys","remove_remove","actionElements","actionElement","select","primaryImageElement","parentId","productUrl","featuredImageAnchor","lineItemTitle","linePriceAfterDiscountEl","linePriceAfterDiscount","linePriceEl","linePrice","ariaLabelPrefix","isAvailable","sellingPlanId","renderQuantity","quantityElement","isUpsell","maxQuantity","optionsHtml","CartLineItems","parents","uniqueParentKeys","fragment","createDocumentFragment","_item$parent_relation2","element","createItemElement","has","isParent","appendChildItems","parentElement","parentKey","_item$parent_relation3","childElement","_item$parent_relation4","_item$parent_relation5","_item$parent_relation6","_item$parent_relation7","lineItemEl","imageAlt","productType","final_price","original_line_price","final_line_price","_is_upsell","handle","productHandle","productId","sellingPlanFrequency","replace","option","optionEl","selected","messages","amount","toFixed","_contains_alcohol","_compatibility","_final_sale","_backorder_message","_is_tbyb","generateMessages","ul","cloneNode","itemEl","CartMessages","_event$detail$inputs","_event$detail$inputs2","alerts","alert","showInitErrorMessage","showErrorMessage","before_codes","showItemsAddedMessage","itemsString","alertType","showDiscountCodeAppliedMessages","showDiscountRemovedMessages","showSuccessMessage","upsellItem","find","showUpsellItemMessage","backorderedItem","showBackorderItemMessage","addedLineItemsString","backorderMessage","backorderAlert","messageHtml","onReloadBagClick","_button$closest","closest","requestEvent","CartTotals","discountRow","discountAmount","subtotalEl","subtotalAmount","discountTotalEl","discountPercent","subtotal","_value_set_compare_price","floor","computeSubtotal","applied","display","orderDiscountsValue","discount","total_allocated_amount","orderDiscountsEl","CartUpsellItem","buildTitle","renderTitle","renderImage","renderPrices","renderAddButton","renderReviews","anchor","resolvePrices","_this$dataset$product","upsellTagSearch","upsellTag","productTags","upsellPriceArray","upsellPriceString","upsellPrice","parseFloat","prices","compareAtPriceEl","hide","addButton","country","reviews","average","reviewsAverage","reviewsCount","CartUpsellItems","requestAnimationFrame","variantIds","elements","visibleCount","formatMoney","cents","format","placeholderRegex","formatString","formatWithDelimiters","number","precision","thousands","decimal","isNaN","match","FreeShippingBanner","subscriptionsInCart","span","progressContainer","progressBar","percentage","getMessageText","threshold","subscriptionText","difference","PromoGoalProgressV2","promoGoals","ingestPromoGoals","goals","goal","offerText","goalNamesContainer","goalIndex","nameDiv","spanDiv","left","thresholdsContainer","thresholdElement","applyDiscountsHelper","newCodes","overrides","postValidate","discount_code","validateCodesBeforeUpdate","oldUppers","newCodesCount","currentCodes","getCurrentCodes","newUppers","retainedOldUppers","override","old_code","new_code","updatedCart","updatedNormalCodes","newCode","upperNewCode","validateCodesAfterUpdate","DiscountInput","activeTransactions","errorMessage","onCartTransactionEndedBound","onCartTransactionEnded","onDiscountFormSubmitBound","onDiscountFormSubmit","onDiscountInputBound","onDiscountInput","form","inputElement","codes","discountAmounts","Map","currentTotal","lineItem","getDiscountAmounts","unmatched","from","appliedDiscountsEl","appliedDiscountTemplate","appliedDiscountFragment","createAppliedDiscount","discountInfo","amountEl","infoElement","cta","onRemoveDiscountClicked","preventDefault","beforeCart","discount_overrides","normalNewCodes","before_cart","new_codes","apply","then","finally","_existingCodes","existingCodes","before","previousCart","previousCode","discount_remove_remove","PromoGoal","thresholdAmount","computeQualifyingSubtotal","originalTotalPrice","original_total_price","totalDiscountPrice","total","calculateTotalDiscount","totalPromoGoalDiscountPrice","isPromoNamedDiscount","calculateTotalPromoGoalDiscount","totalPriceOfExcludedItems","gift_card","calculateTotalPriceOfPromoExcludedItems","PromoGoalProgress","qualifyingSubtotal","paragraph","collectPromoGoalsData","eligibleThreshold","eligiblePromoGoals","promoGoal","isLocked","amountToGoal","percentSpentToGoal","spentEl","goalEl","compareThresholds","getFeatureValue","featureKey","fallback","timeoutMs","resolvedFeatures","growthbook_runtime","resolved_features","effectiveTimeoutMs","default_timeout_ms","load_state","extraMillis","previewMode","preview_hold_ms","once","growthbook","client_loader","feature","featureValueMatches","expected","_event$detail$cart$at","_event$detail$cart$at2","_event$detail$cart$at3","promoProgress","_aux_opt_in_v3","group","cartTotal","_aux_opt_in","onGetFeatureValue","lastChange","UpsellModal","onCollapseAnimationEndedBound","onCollapseAnimationEnded","onExpandAnimationEndedBound","onExpandAnimationEnded","openModal","closeModal","delegatesFocus","_this$dataset$animate","initializeFocus","_this$dataset$animate2","_this$cartVariantIds","subtitle","cartVariantIds","upsellItems","emit","startedEvent","cancelable","composedPath","fastFlipWarned","drawer_control_open","cartDrawer","checkFastFlip","showEvent","onModalDismissRequested","openedEvent","viewedEvent","drawer_control_close","closeEvent","closedEvent","direction","time","gap","shopperSources","previousDirection","previousSource","currentDirection","currentSource","visibilityState","onCartOpenRequested","_event$detail$animate","_event$detail","_event$detail2","onCartCloseRequested","_event$detail$animate2","_event$detail3","_event$detail4","cartUpdateErrorEvents","main"],"sourceRoot":""}