{"version":3,"file":"price-box-v2-element-section.min.js","mappings":"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","sources":["webpack://lange/./lib/cart/digest.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/@integrabeauty/shopify-ajax-api/src/cart.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/discount/helper.ts","webpack://lange/./lib/cart/discount/apply.ts","webpack://lange/./lib/countdown-timer.ts","webpack://lange/./sections/price-box-v2-element/price-box-v2/index.ts","webpack://lange/./lib/dom-ready.ts"],"sourcesContent":["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","/**\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    'INIT_TIMEOUT',\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","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","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 values 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 * Thrown when a single cart read takes longer than its time limit. Callers can check for it to tell\n * a hung read apart from one that failed outright.\n */\nexport class CartFetchTimeoutError extends Error {\n  public declare timeout: number;\n\n  constructor(timeout: number) {\n    super('Timed out fetching cart');\n    this.name = this.constructor.name;\n    this.timeout = timeout;\n  }\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 CartFetchTimeoutError(timeout);\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 discount 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 uses performance.now(), which counts from page load rather than the Unix epoch, and falls\n * 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 * Whether any cart transaction is currently in flight. A transaction stops counting once it\n * completes or times out, so this cannot stay true for longer than the transaction timeout.\n *\n * Interface elements that are created partway through a transaction never saw its started event, so\n * they have to ask this when they connect. The add to cart button relies on it to stay disabled\n * when it is rebuilt during an add. Changing what this reports, for example counting only some\n * kinds of transactions, changes when shoppers can tap add to cart. See issue 7022.\n */\nexport function isActive() {\n  return getSharedState().activeCount > 0;\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 a 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 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 inapplicable situations, such as when the\n  // cart is empty (the discount is applied but if you look at the discount object in the\n  // discount_codes array the applicable property is false). By only validating if requested we\n  // avoid generating 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 is 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 { 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","// We have now more than one element that displays a countdown clock on the page. When the elements\n// use separate `setInterval()` calls to display their time the effect of them not being\n// synchronized can be seen even if the interval is low (50-100ms).\n//\n// This module provides a very simple synchronization method and some shared utilities for timestamp\n// calculations.\n//\n// Clock synchronization is based on a single `setInterval()` call. This starts a timer which emits\n// `timer-tick` events. Each page element can listen to the events to update the displayed time at\n// the very same moment other elements do.\n//\n// The timer has to be initialized with the `startTimer()` function. There is no possibility to stop\n// the timer, as we would have to implement a reference counter which would be prone to problems,\n// i.e. a bug in a simple component could stop all timers on the page. Instead of stopping the timer\n// the components should stop listening to the events.\n\ndeclare global {\n  /**\n   * Sentinel marking that the shared tick interval has been started, so repeated `startTimer()`\n   * calls do not each spin up their own interval.\n   *\n   * This lives on the global scope rather than in a module variable on purpose. Each section is its\n   * own webpack entry and we do not share chunks, so this file is copied into every bundle that\n   * imports it. A module variable would therefore be per-bundle, letting every section on the page\n   * start its own interval and defeating the synchronization. The tick events are already\n   * dispatched globally, so the guard has to be global too.\n   */\n  var countdown_timer_interval_started: boolean | undefined;\n}\n\n/**\n * The remaining time between two timestamps, broken down into whole units plus a human readable\n * summary. Each numeric component is the remainder within its larger unit, so `hours`, `minutes`,\n * and `seconds` never exceed their natural maximums (e.g. `seconds` is always 0-59).\n */\ninterface TimeLeft {\n  /** Whole days remaining. */\n  days?: number;\n\n  /** Whole hours remaining within the current day (0-23). */\n  hours?: number;\n\n  /** Whole minutes remaining within the current hour (0-59). */\n  minutes?: number;\n\n  /** Whole seconds remaining within the current minute (0-59). */\n  seconds?: number;\n\n  /** Human readable summary of the remaining time, e.g. `\"1 day 2 hours 3 minutes 4 seconds\"`. */\n  text?: string;\n}\n\n/**\n * Calculate the remaining time between currentTimestamp and endTimestamp and express it as an\n * object with number of days, hours, minutes, seconds as integers and a human readable `text`\n *\n * Timestamps should be in milliseconds.\n *\n * This is not guaranteed to return an object. It may return null if the current time is past the\n * end time or if the end time is falsy. In other words, a null return value indicates that either\n * there is no time left or it is not possible to compute it.\n */\nexport function computeTimeLeft(currentTimestamp: number, endTimestamp: number) {\n  if (currentTimestamp > endTimestamp || !endTimestamp) {\n    return null;\n  }\n\n  const secondsLeft = Math.round((endTimestamp - currentTimestamp) / 1000);\n  const seconds = secondsLeft % 60;\n\n  const minutesLeft = (secondsLeft - seconds) / 60;\n  const minutes = minutesLeft % 60;\n\n  const hoursLeft = (minutesLeft - minutes) / 60;\n  const hours = hoursLeft % 24;\n\n  const days = (hoursLeft - hours) / 24;\n\n  const text = [\n    days.toString(),\n    days === 1 ? 'day' : 'days',\n    hours.toString(),\n    hours === 1 ? 'hour' : 'hours',\n    minutes.toString(),\n    minutes === 1 ? 'minute' : 'minutes',\n    seconds.toString(),\n    seconds === 1 ? 'second' : 'seconds'\n  ].join(' ');\n\n  const timeLeft: TimeLeft = {\n    days,\n    hours,\n    minutes,\n    seconds,\n    text\n  };\n\n  return timeLeft;\n}\n\n/**\n * Start the shared heartbeat that drives every countdown on the page.\n *\n * Once started, a tick event fires on the global scope once a second carrying the current\n * timestamp. Each countdown section listens for that event and repaints from the same timestamp, so\n * every countdown on the page advances at the exact same instant instead of drifting out of step\n * with each other.\n *\n * Any section that wants a countdown may call this, in any order, as many times as it likes. Only\n * the very first call anywhere on the page actually creates the interval. Every later call finds\n * the guard already set and skips that part. The guard lives on the global scope rather than in a\n * module variable on purpose. This file is copied into each webpack bundle because we do not share\n * chunks, so a module variable would be per bundle and would let every section start its own\n * interval, which would bring back the drift this module exists to prevent. The sentinel\n * declaration near the top of the file explains that reasoning in full.\n *\n * The immediate tick deliberately sits outside the guard so it runs on every call rather than only\n * the first. Some sections paint their starting value themselves when they connect, but others set\n * their displayed time only inside their tick listener. A section of that second kind that connects\n * after the interval is already running would otherwise show an empty clock for up to a second,\n * until the next regular beat arrives. Firing one immediate tick per caller hands each one a value\n * straight away. The extra repaints this causes are cheap because a section only touches the DOM\n * when its displayed value actually changed.\n */\nexport function startTimer() {\n  setTimeout(tick, 0);\n\n  if (!window.countdown_timer_interval_started) {\n    window.countdown_timer_interval_started = true;\n    setInterval(tick, 1000);\n  }\n}\n\n/**\n * Emit a single tick event stamped with the current time.\n *\n * This is the callback for both the one-shot immediate timeout and the recurring interval that\n * startTimer sets up. The order in which those two fire never matters. Each event carries its own\n * timestamp, and every listener recomputes its whole display from whatever timestamp it receives,\n * holding no state from one tick to the next. So a tick that arrives out of order, or an extra tick\n * from a second caller, at worst causes one redundant repaint, which a section ignores when its\n * displayed value has not actually changed. That is also why there is no timer id to capture or\n * clear here. The immediate timeout has already elapsed by the time this runs, and the interval is\n * meant to run for the life of the page.\n */\nfunction tick() {\n  const event = new CustomEvent<TimerTickEvent['detail']>('timer-tick', {\n    detail: {\n      timestamp: Date.now()\n    }\n  });\n  dispatchEvent(event);\n}\n\n/**\n * This event is fired per tick of the countdown timer. It represents one tick. It captures the\n * precise point in time at which the tick occurred.\n *\n * Events have a native timeStamp property, which at first glance suggests this does not need a\n * custom property as a simple Event would suffice. However, event.timeStamp is not an epoch, it is\n * a high resolution timestamp relative to the page load. Listeners typically compare this event's\n * timestamp to Date.now(). In order to do that comparison using the native event.timeStamp, they\n * have to use performance.timeOrigin + event.timeStamp. We have seen the performance global have\n * issues, the comparison involves more boilerplate, and there is a mental tax on using it because\n * of its subtleties and misleading name. Not to mention the fact that most people write \"timestamp\"\n * in all lowercase but some camel case fanatic thought better. Using a custom property actually\n * reduces the complexity despite its reinvention of the wheel.\n *\n * The name \"timestamp\" conventionally refers to an ISO string. However, given that the native APIs\n * have adopted the term (e.g. the DOM High Resolution Time API), we are stuck with the bad name in\n * the browser setting, and must live with it.\n */\ntype TimerTickEvent = CustomEvent<{\n  /**\n   * The precise point in time at which the tick occurred. Measures as milliseconds since epoch.\n   *\n   * Despite the name, this has nothing to do with event.timeStamp. Do not confuse the two.\n   *\n   * Despite the name, this is not an ISO string. It is a number representing milliseconds since\n   * epoch.\n   */\n  timestamp: number;\n}>;\n\ndeclare global {\n  interface WindowEventMap {\n    'timer-tick': TimerTickEvent;\n  }\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport type { Cart } from '@integrabeauty/shopify-ajax-api';\nimport { apply } from '../../../lib/cart/discount/apply.js';\nimport { getDiscountAmounts, getDiscountCodes } from '../../../lib/cart/discount/discount.js';\nimport { read } from '../../../lib/cart/state.js';\nimport { computeTimeLeft, startTimer } from '../../../lib/countdown-timer.js';\nimport { ready, type ReleaseHandle } from '../../../lib/dom-ready.js';\n\n/**\n * The redesigned PDP price box, with the regular price and a promo badge on top and, when the\n * product has a discount code, a pink card under them with the campaign name, the countdown, the\n * price with the code, and an Apply button. The price-box-v2 snippet renders all of its markup\n * server side, so the box lays out on first paint without waiting on this script.\n *\n * This element keeps the prices current after that. The badge and the price with the code follow\n * the selected variant, and the countdown ticks along with the other countdowns on the page. It\n * reads variant prices from the `<product-price>` it contains.\n *\n * When the product has a discount code, clicking anywhere on the box or pressing its Apply button\n * applies the code to the cart. Once the code is taking money off something in the cart, the\n * regular price turns gray and the button says the code was applied. A failed apply is reported the\n * way the cart reports any failed apply, and the box stays as it was.\n *\n * The box follows the cart too, so it shows the code as applied when the shopper arrives with the\n * code already discounting their cart, and goes back to Apply when the code stops discounting it,\n * for example when the shopper removes it in the cart drawer. It reads the discounts from the cart\n * the server rendered into the page, so it does not wait for the cart to load.\n */\nclass PriceBoxV2 extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Set to \"true\" once the discount code is taking money off something in the cart.\n     */\n    codeApplied: string;\n\n    /**\n     * Set to \"true\" while the discount code is being applied.\n     */\n    codeApplying: string;\n\n    /**\n     * The discount code's percent off. Absent when the product has no code.\n     */\n    codePercentage: string;\n\n    /**\n     * The product's discount code, applied to the cart when the box is clicked. Absent when the\n     * product has no code.\n     */\n    discountCode: string;\n\n    /**\n     * Countdown end time as unix epoch seconds. The countdown stays hidden when absent.\n     */\n    endTime: string;\n\n    /**\n     * The product id, used to pick out variant changes for this product.\n     */\n    productId: string;\n\n    /**\n     * The id of the variant whose prices the box shows. Setting it points the box at another\n     * variant.\n     */\n    selectedVariantId: string;\n\n    /**\n     * Countdown start time as unix epoch seconds.\n     */\n    startTime: string;\n  };\n\n  public static get observedAttributes() {\n    return ['data-selected-variant-id'];\n  }\n\n  private onCartUpdatedBound = this.onCartUpdated.bind(this);\n  private onClickBound = this.onClick.bind(this);\n  private onProductVariantOptionChangedBound = this.onProductVariantOptionChanged.bind(this);\n  private onReadyBound = this.onReady.bind(this);\n  private onTimerTickBound = this.onTimerTick.bind(this);\n  private declare releaseReady: ReleaseHandle;\n\n  /**\n   * Whether the shopper clicked to apply the code.\n   */\n  private clickedApply = false;\n\n  public connectedCallback() {\n    this.addEventListener('click', this.onClickBound);\n    addEventListener('product-variant-option-changed', this.onProductVariantOptionChangedBound);\n\n    if (this.dataset.endTime) {\n      addEventListener('timer-tick', this.onTimerTickBound);\n      startTimer();\n    }\n\n    if (this.dataset.discountCode) {\n      this.renderCodeState(read());\n      addEventListener('cart-updated', this.onCartUpdatedBound);\n    }\n\n    this.releaseReady = ready(this.onReadyBound);\n  }\n\n  public disconnectedCallback() {\n    if (this.releaseReady) {\n      this.releaseReady();\n    }\n\n    this.removeEventListener('click', this.onClickBound);\n    removeEventListener('cart-updated', this.onCartUpdatedBound);\n    removeEventListener('product-variant-option-changed', this.onProductVariantOptionChangedBound);\n    removeEventListener('timer-tick', this.onTimerTickBound);\n  }\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    if (this.isConnected && oldValue !== newValue && name === 'data-selected-variant-id') {\n      this.renderSelectedVariant();\n    }\n  }\n\n  /**\n   * Brings the box in line with the cart. While a click is applying the code we leave it alone,\n   * since the click settles the box itself once the apply ends.\n   */\n  private onCartUpdated(event: GlobalEventHandlersEventMap['cart-updated']) {\n    if (this.dataset.codeApplying !== 'true') {\n      this.renderCodeState(event.detail.cart);\n    }\n  }\n\n  private onReady() {\n    if (!this.isConnected) {\n      return;\n    }\n\n    // The server worked out the badge and the price with the code from the variant's base prices.\n    // Offer templates can override those prices inside product-price, so we redo them from the\n    // rendered variant data once it is all parsed.\n    this.renderSelectedVariant();\n  }\n\n  private onClick(event: MouseEvent) {\n    if (!this.dataset.discountCode || this.dataset.codeApplied === 'true' ||\n      this.dataset.codeApplying === 'true') {\n      return;\n    }\n\n    // The price row has its own clickable parts, the \"i\" button and the compare at price, which\n    // open the compare at price modal. A click on one of those is not a click to apply the code. A\n    // click on the Apply button or anywhere else on the box does apply it.\n    const applyButton = this.querySelector('.apply');\n    for (const target of event.composedPath()) {\n      if (target === this || target === applyButton) {\n        break;\n      }\n\n      if (target instanceof HTMLButtonElement) {\n        return;\n      }\n\n      if (target instanceof HTMLElement && target.id === 'compare-at-price' && !target.inert) {\n        return;\n      }\n    }\n\n    this.applyCode().catch(console.warn);\n  }\n\n  private async applyCode() {\n    const code = this.dataset.discountCode.toUpperCase();\n    this.dataset.codeApplying = 'true';\n    this.renderApplyStep('applying');\n\n    try {\n      // An empty cart takes the code too, it just sits there as not yet applicable until something\n      // qualifying is added. That is still what we want here, so we skip the check that would treat\n      // it as a failure.\n      if (!getDiscountCodes(read()).includes(code)) {\n        await apply(this, [code], 'price-box-v2', false);\n      }\n    } finally {\n      delete this.dataset.codeApplying;\n    }\n\n    this.clickedApply = true;\n\n    // A failed apply is reported as an event rather than thrown, so we check the cart itself to see\n    // whether the code made it on.\n    this.renderCodeState(read());\n  }\n\n  /**\n   * Shows the code as applied or not, going by the given cart.\n   */\n  private renderCodeState(cart: Cart) {\n    if (this.isCodeApplied(cart)) {\n      this.dataset.codeApplied = 'true';\n      this.renderApplyStep('applied');\n    } else {\n      delete this.dataset.codeApplied;\n      this.renderApplyStep('ready');\n    }\n  }\n\n  /**\n   * Whether the product's discount code counts as applied in the given cart.\n   *\n   * We go by the cart's discount applications rather than its discount codes, because the cart the\n   * server renders into the page has the applications but not the codes. A code only shows up in\n   * the applications once it is taking money off something.\n   *\n   * After the shopper clicks to apply the code, it counts as applied for as long as it is on the\n   * cart, even before it discounts anything. Otherwise a click with this product not yet in the\n   * cart would put the code on and then show Apply again, as if the click did nothing. The cart has\n   * its discount codes by then, since applying the code loads the cart.\n   */\n  private isCodeApplied(cart: Cart) {\n    const code = this.dataset.discountCode.toUpperCase();\n    if (getDiscountAmounts(cart).has(code)) {\n      return true;\n    }\n\n    return this.clickedApply && getDiscountCodes(cart).includes(code);\n  }\n\n  /**\n   * Keeps the Apply button and the status line after it in step with applying the code. The\n   * stylesheet already switches the button's wording off the box's data attributes, so this only\n   * covers what a screen reader needs. While the code is being applied and once it is on, the\n   * button stays focusable but reads as disabled, and the status line announces the step. The box\n   * gets told about every cart update, so the status line only changes when the step does, which\n   * keeps a screen reader from repeating it.\n   */\n  private renderApplyStep(step: 'applied' | 'applying' | 'ready') {\n    const button = this.querySelector('.apply');\n    if (!button) {\n      return;\n    }\n\n    const status = this.querySelector('.status');\n    if (!status) {\n      return;\n    }\n\n    let message = '';\n    if (step === 'ready') {\n      button.removeAttribute('aria-disabled');\n    } else if (step === 'applying') {\n      button.setAttribute('aria-disabled', 'true');\n      message = `Applying code ${this.dataset.discountCode}`;\n    } else {\n      button.setAttribute('aria-disabled', 'true');\n      message = `Code ${this.dataset.discountCode} applied`;\n    }\n\n    if (status.textContent !== message) {\n      status.textContent = message;\n    }\n  }\n\n  private onProductVariantOptionChanged(event: WindowEventMap['product-variant-option-changed']) {\n    if (event.detail.id.toString() === this.dataset.productId) {\n      this.dataset.selectedVariantId = event.detail.selected_variant_id.toString();\n    }\n  }\n\n  /**\n   * Shows the selected variant's prices across the box. The product price follows a variant change\n   * on its own, so it only needs pointing at the variant when the box learned about it some other\n   * way, like the variant being set before the box was on the page.\n   */\n  private renderSelectedVariant() {\n    const variantId = this.dataset.selectedVariantId;\n    const productPrice = this.querySelector('product-price');\n    const productElement = productPrice?.querySelector('product-element');\n    if (productElement && productElement.dataset.selectedVariantId !== variantId) {\n      productElement.dataset.selectedVariantId = variantId;\n      productPrice.dataset.update = Date.now().toString();\n    }\n\n    this.renderPrices(variantId);\n  }\n\n  private renderPrices(variantId: string) {\n    const variantElement = this.querySelector<HTMLElementTagNameMap['variant-element']>(\n      `product-element > variant-element[data-id=\"${variantId}\"]`);\n    if (!variantElement) {\n      return;\n    }\n\n    if (this.dataset.codePercentage) {\n      this.renderCodePrices(variantElement);\n    } else {\n      this.renderCompareAtSavings(variantElement);\n    }\n  }\n\n  /**\n   * Shows the variant's price with the discount code in the card, and how much the code saves in\n   * the badge. Prices are in cents. We round the price with the code to whole dollars so it shows\n   * no cents.\n   */\n  private renderCodePrices(variantElement: HTMLElementTagNameMap['variant-element']) {\n    const price = parseInt(variantElement.dataset.price, 10);\n    const percentage = parseInt(this.dataset.codePercentage, 10);\n    const codePrice = Math.round(price * (100 - percentage) / 10000) * 100;\n\n    const codePriceElement = this.querySelector<HTMLElementTagNameMap['price-element']>(\n      '.code-price');\n    if (codePriceElement) {\n      codePriceElement.dataset.subunits = codePrice.toString();\n    }\n\n    const savingsElement = this.querySelector<HTMLElementTagNameMap['price-element']>(\n      '.badge .savings');\n    if (savingsElement) {\n      savingsElement.dataset.subunits = (price - codePrice).toString();\n    }\n  }\n\n  /**\n   * Shows the variant's savings against its compare at price as a percent in the badge, or hides\n   * the badge when the variant has no savings.\n   */\n  private renderCompareAtSavings(variantElement: HTMLElementTagNameMap['variant-element']) {\n    const badge = this.querySelector<HTMLElement>('.badge');\n    const savings = this.querySelector('.badge .savings');\n    if (!badge || !savings) {\n      return;\n    }\n\n    const price = parseInt(variantElement.dataset.price, 10);\n    const compareAtPrice = parseInt(variantElement.dataset.compareAtPrice, 10);\n\n    if (compareAtPrice > price) {\n      const percentage = Math.round(((compareAtPrice - price) / compareAtPrice) * 100);\n      savings.textContent = `${percentage}%`;\n      badge.hidden = false;\n    } else {\n      badge.hidden = true;\n    }\n  }\n\n  private onTimerTick(event: WindowEventMap['timer-tick']) {\n    const countdown = this.querySelector<HTMLElement>('.countdown');\n    const days = this.querySelector('.countdown .days');\n    const hours = this.querySelector('.countdown .hours');\n    const minutes = this.querySelector('.countdown .minutes');\n    const seconds = this.querySelector('.countdown .seconds');\n\n    // Safari's translate feature is known to strip empty elements, so we skip the tick rather than\n    // throw when one of these went missing.\n    if (!countdown || !days || !hours || !minutes || !seconds) {\n      return;\n    }\n\n    const endTime = parseInt(this.dataset.endTime, 10) * 1000;\n    const startTime = parseInt(this.dataset.startTime, 10) * 1000;\n    const timeLeft = computeTimeLeft(event.detail.timestamp, endTime);\n\n    if (!timeLeft || startTime > event.detail.timestamp) {\n      countdown.hidden = true;\n      return;\n    }\n\n    days.textContent = timeLeft.days.toString().padStart(2, '0');\n    hours.textContent = timeLeft.hours.toString().padStart(2, '0');\n    minutes.textContent = timeLeft.minutes.toString().padStart(2, '0');\n    seconds.textContent = timeLeft.seconds.toString().padStart(2, '0');\n    countdown.hidden = false;\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'price-box-v2': PriceBoxV2;\n  }\n}\n\nif (!customElements.get('price-box-v2')) {\n  customElements.define('price-box-v2', PriceBoxV2);\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"],"names":["digest","cart","cart_input_type","_cart$attributes","_cart$cart_level_disc","_cart$items","token","attribute_keys","Object","keys","attributes","discount_codes","cart_level_discount_titles","cart_level_discount_applications","map","application","title","items","item","_item$line_level_disc","key","variant_id","quantity","properties","discounts","line_level_discount_allocations","allocation","_allocation$discount_","discount_application","error","digest_failed","decorateError","value","assignmentError","reason","Error","message","console","log","name","tag","prototype","toString","call","report","dispatcher","detail","target","Element","isConnected","window","event","CustomEvent","bubbles","composed","dispatchEvent","dispatchCartUpdateErred","ignoredCodes","includes","code","_detail$transaction","_detail$transaction2","inputs","scenario","transaction","id","reportError","setTimeout","raise","escalateCartError","assert","expression","AssertionError","constructor","super","this","getRequestId","response","headers","get","isResponse","ok","status","text","json","cart_decorateError","resolveUrl","path","URL","location","protocol","host","async","cartFetch","url","Shopify","routes","root","responseBodyText","responseBodyObject","fetch","cache","href","consumeError","JSON","parse","isCart","isObject","Array","isArray","rnds8","Uint8Array","byteToHex","i","push","slice","unsafeStringify","arr","offset","toLowerCase","dist_v4","options","buf","crypto","randomUUID","_ref","_options$random","rnds","random","rng","getRandomValues","length","RangeError","_v4","copy","structuredClone","cloned","trustworthy","stringify","isWellFormed","upper","toUpperCase","test","containsJavascriptWord","getApplicableDiscountCodes","filter","applicable","getDiscountCodes","read","cart_lifecycle","CartFetchTimeoutError","timeout","cartFetchOnceWithTimeout","timer","timeoutPromise","Promise","_resolve","reject","race","clearTimeout","resolveAfterTimeout","milliseconds","resolve","groupByVariant","output","variant","property","merged_properties","image_url","image","price","product_id","product_title","product_type","sku","variant_title","findVariantsAdded","c0map","c1map","increments","findVariantsRemoved","decrements","complement","a","b","now","_performance","performance","debug","Date","getSharedState","cart_transaction","activeCount","count","timeline","shared","Number","isInteger","start","_len","arguments","args","_key","epoch","completed","startMillis","didTimeOut","complete","snapshot","warn","reportConcurrency","append","type","duration","Math","round","undefined","end","_performance3","measure","entry","splice","max","applyDiscountsHelper","newCodes","overrides","postValidate","discount_code","validateCodesBeforeUpdate","oldUppers","newCodesCount","currentCodes","getCurrentCodes","newUppers","retainedOldUppers","some","override","old_code","new_code","discount","Set","join","input","init","body","method","_error$response_body5","_error$response_body6","response_body","description","cartUpdate","updatedCart","limit","attempts","delays","lastError","delay","state_cartFetch","updatedNormalCodes","newCode","upperNewCode","validateCodesAfterUpdate","apply","beforeCart","initial","previous","oldVariants","newVariants","prevCodes","currCodes","discount_codes_added","discount_codes_removed","event_id","is_initial","variants_added","variants_removed","write","discount_overrides","normalNewCodes","before_cart","before_codes","new_codes","tick","timestamp","PriceBoxV2","HTMLElement","observedAttributes","onCartUpdatedBound","onCartUpdated","bind","onClickBound","onClick","onProductVariantOptionChangedBound","onProductVariantOptionChanged","onReadyBound","onReady","onTimerTickBound","onTimerTick","clickedApply","connectedCallback","addEventListener","dataset","endTime","countdown_timer_interval_started","setInterval","discountCode","renderCodeState","releaseReady","callback","document","readyState","removeEventListener","ready","disconnectedCallback","attributeChangedCallback","oldValue","newValue","renderSelectedVariant","codeApplying","codeApplied","applyButton","querySelector","composedPath","HTMLButtonElement","inert","applyCode","catch","renderApplyStep","isCodeApplied","discountAmounts","Map","total_allocated_amount","currentTotal","set","lineItem","amount","getDiscountAmounts","has","step","button","removeAttribute","setAttribute","textContent","productId","selectedVariantId","selected_variant_id","variantId","productPrice","productElement","update","renderPrices","variantElement","codePercentage","renderCodePrices","renderCompareAtSavings","parseInt","percentage","codePrice","codePriceElement","subunits","savingsElement","badge","savings","compareAtPrice","hidden","countdown","days","hours","minutes","seconds","startTime","timeLeft","currentTimestamp","endTimestamp","secondsLeft","minutesLeft","hoursLeft","computeTimeLeft","padStart","customElements","define"],"sourceRoot":""}