{"version":3,"file":"routine-element-section.min.js","mappings":"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","sources":["webpack://lange/./sections/routine/routine-element/index.ts","webpack://lange/./lib/shopify/image-url.ts","webpack://lange/./sections/routine/routine-step/index.ts"],"sourcesContent":["import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\ntype VariantElement = HTMLElementTagNameMap['variant-element'];\ntype ProductElement = HTMLElementTagNameMap['product-element'];\ntype AddToCartElement = HTMLElementTagNameMap['add-to-cart'];\ntype ProductPriceElement = HTMLElementTagNameMap['product-price'];\ntype RoutineDiscountStep = HTMLElementTagNameMap['routine-discount-step'];\n\n/**\n * DiscountStep represents a tier in a step-based discount structure. When the number of items\n * selected in the routine reaches `stepThreshold`, the paired `discountCode` and\n * `discountPercentage` become active. Multiple steps together form a ladder (e.g. 3 items -> 15%\n * off, 5 items -> 25% off).\n */\ninterface DiscountStep {\n  /**\n   * The discount code that is applied when the threshold is met\n   */\n  discountCode: string;\n\n  /**\n   * The percentage discount that is applied when the threshold is met (e.g., 20 for 20%)\n   */\n  discountPercentage: number;\n\n  /**\n   * Number of items that must be selected in the routine for the discount to apply\n   */\n  stepThreshold: number;\n}\n\n/**\n * Orchestrates a personalized (based on the hair quiz results) hair routine displayed on a bundle\n * product detail page.\n *\n * Hosts one or more `<routine-step>` children and re-aggregates all selected variants across those\n * steps whenever a step's selection changes. Two pieces of UI are driven from that aggregated\n * state:\n *\n * **Price box** — the total price (sum of all selected variant prices) is written back to the\n * slotted `product-price` / `installment-payment-message` components by mutating the first\n * `variant-element` inside them and triggering their own re-render via `data-update`.\n *\n * **Add-to-cart** — the first selected variant across all steps becomes the main cart item inside\n * the slotted `add-to-cart`; every subsequent variant is appended as an add-on (`data-is-\n * addon=\"true\"`).\n *\n * Re-renders on every `routine-step-changed` event that bubbles up from a child `<routine-step>`.\n *\n * ## Named slots\n * - `steps` — one `<routine-step>` per routine step\n * - `product-card` — bundle product thumbnail + title shown in the sticky bar at the bottom of\n *   the viewport\n * - `price` — expects `<product-price>` and `<installment-payment-message>`\n * - `add-to-cart` — expects `<add-to-cart>`\n * - `price-sticky` — same as `price`, shown in the sticky bar\n * - `add-to-cart-sticky` — same as `add-to-cart`, shown in the sticky bar\n */\nclass RoutineElement extends HTMLElement implements CustomElement {\n  private variants: VariantElement[] = [];\n\n  /**\n   * The discount steps data is gathered from the slotted `routine-discount-step` elements, it is\n   * guaranteed to be sorted by the `stepThreshold` in ascending order\n   */\n  private discountSteps: DiscountStep[] = [];\n\n  private onRoutineStepChangedBound = this.onRoutineStepChanged.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.addEventListener('routine-step-changed', this.onRoutineStepChangedBound);\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('routine-step-changed', this.onRoutineStepChangedBound);\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private onRoutineStepChanged(_event: Event) {\n    if (this.isConnected) {\n      this.gatherData();\n      this.render();\n    }\n  }\n\n  private gatherVariantData() {\n    const variants = [];\n    const routineSteps = this.querySelectorAll('routine-step[slot=\"steps\"]');\n    for (const step of routineSteps) {\n      const productElements = step.querySelectorAll('product-element');\n      for (const productElement of productElements) {\n        const variantElement = productElement.querySelector<VariantElement>(\n          ':scope > variant-element[data-selected=\"true\"]');\n        if (variantElement) {\n          variants.push(variantElement);\n        }\n      }\n    }\n\n    this.variants = variants;\n  }\n\n  /**\n   * Gathers the discount steps data from the slotted `routine-discount-step` elements, sorts them\n   * by their threshold in ascending order, and stores them in the `discountSteps` property\n   */\n  private gatherDiscountData() {\n    const discountStepElements = this.querySelectorAll<RoutineDiscountStep>('[slot=\"discount\"]');\n    const discountSteps: DiscountStep[] = [];\n    for (const discountStep of discountStepElements) {\n      const discountCode = discountStep.dataset.discountCode;\n      const discountPercentage = parseFloat(discountStep.dataset.discountPercentage || '0');\n      const stepThreshold = parseInt(discountStep.dataset.threshold || '0', 10);\n      if (discountCode && !isNaN(discountPercentage) && !isNaN(stepThreshold)) {\n        discountSteps.push({ discountCode, discountPercentage, stepThreshold });\n      }\n    }\n\n    // make sure the discount steps are sorted by their threshold in ascending order\n    discountSteps.sort(compareDiscountSteps);\n    this.discountSteps = discountSteps;\n  }\n\n  private gatherData() {\n    this.gatherVariantData();\n    this.gatherDiscountData();\n  }\n\n  private getCurrentDiscountStep() {\n    // find the highest discount step for which the threshold is met\n    let currentDiscountStep: DiscountStep;\n    for (const discountStep of this.discountSteps) {\n      if (this.variants.length >= discountStep.stepThreshold) {\n        if (!currentDiscountStep ||\n          discountStep.discountPercentage > currentDiscountStep.discountPercentage) {\n          currentDiscountStep = discountStep;\n        }\n      }\n    }\n\n    return currentDiscountStep;\n  }\n\n  private getNextDiscountStep() {\n    // find the lowest discount step for which the threshold is not met\n    let nextDiscountStep: DiscountStep;\n    for (const discountStep of this.discountSteps) {\n      if (this.variants.length < discountStep.stepThreshold) {\n        nextDiscountStep = discountStep;\n        break;\n      }\n    }\n\n    return nextDiscountStep;\n  }\n\n  private render() {\n    this.renderPriceBoxes();\n    this.renderAddToCartElements();\n  }\n\n  private renderPriceBoxes() {\n    let totalPrice = 0;\n    for (const variant of this.variants) {\n      const variantPrice = parseInt(variant.dataset.price, 10);\n      if (!isNaN(variantPrice)) {\n        totalPrice += variantPrice;\n      }\n    }\n\n    const totalCompareAtPrice = totalPrice;\n\n    const currentDiscountStep = this.getCurrentDiscountStep();\n    if (currentDiscountStep) {\n      const discountPercentage = currentDiscountStep.discountPercentage / 100;\n      const discountAmount = Math.round(totalPrice * discountPercentage);\n      totalPrice = totalPrice - discountAmount;\n    }\n\n    const productPrices = this.querySelectorAll('product-price');\n    for (const productPrice of productPrices) {\n      renderPriceBox(productPrice, totalPrice, totalCompareAtPrice);\n    }\n\n    const emptyMessages = this.shadowRoot.querySelectorAll<HTMLElement>('.price .empty-message');\n    for (const emptyMessage of emptyMessages) {\n      if (this.variants.length === 0) {\n        emptyMessage.hidden = false;\n      } else {\n        emptyMessage.hidden = true;\n      }\n    }\n\n    const priceSlots = this.shadowRoot.querySelectorAll<HTMLElement>('.price slot');\n    for (const priceSlot of priceSlots) {\n      if (this.variants.length === 0) {\n        priceSlot.hidden = true;\n      } else {\n        priceSlot.hidden = false;\n      }\n    }\n\n    const installmentPaymentMessage = this.querySelector(\n      'installment-payment-message[slot=\"price\"]');\n    if (installmentPaymentMessage) {\n      installmentPaymentMessage.setAttribute('data-estimated-total', totalPrice.toString());\n    }\n\n    const priceContainers = this.shadowRoot.querySelectorAll<HTMLElement>('.price');\n    for (const priceContainer of priceContainers) {\n      priceContainer.hidden = false;\n    }\n\n    const promoMessages = this.shadowRoot.querySelectorAll<HTMLElement>('.promo-message');\n    const nextDiscountStep = this.getNextDiscountStep();\n    for (const promoMessage of promoMessages) {\n      if (nextDiscountStep && this.variants.length > 0) {\n        const percentage = nextDiscountStep.discountPercentage.toString();\n        const threshold = nextDiscountStep.stepThreshold;\n\n        const message = `Save up to ${percentage}% when you bundle ${threshold} items`;\n        promoMessage.textContent = message;\n        promoMessage.hidden = false;\n      } else {\n        promoMessage.hidden = true;\n      }\n    }\n  }\n\n  private renderAddToCartElement(addToCart: AddToCartElement) {\n    // set the products and variants\n    const productElement = addToCart.querySelector('product-element');\n\n    // clear the main variant\n    productElement.innerHTML = '';\n    // clear the add-ons (and the main product)\n    addToCart.innerHTML = '';\n    // append the main product again\n    addToCart.appendChild(productElement);\n\n    // we split the variants into the main one (the first) and the add-ons\n    const [mainVariant, ...addonVariants] = this.variants;\n    if (!mainVariant) {\n      return;\n    }\n\n    // AddToCart uses product and variant data from a slotted product element; we need to clone the\n    // variant nodes from the routine steps and append them to that product element with some custom\n    // attributes\n    const mainVariantClone = <VariantElement>mainVariant.cloneNode(true);\n    // This attribute is used for analytical purposes to identify orders coming from the routine\n    // bundle product page\n    setCustomAttribute(mainVariantClone, '_frizz_quiz_bundle', 'true');\n    productElement.appendChild(mainVariantClone);\n\n    for (const variant of addonVariants) {\n      const addon = <VariantElement>variant.cloneNode(true);\n      // same as above, identify routine bundle items for analytical purposes\n      setCustomAttribute(addon, '_frizz_quiz_bundle', 'true');\n      // AddToCart expects a single product but it allows \"addons\"; we set an add-on flag on all\n      // subsequent variants\n      addon.dataset.isAddon = 'true';\n      addToCart.appendChild(addon);\n    }\n\n    // set the discount code for the current discount step, if any\n    const discountStep = this.getCurrentDiscountStep();\n    if (discountStep) {\n      addToCart.dataset.discountCode = discountStep.discountCode;\n    } else {\n      addToCart.removeAttribute('data-discount-code');\n    }\n\n    // trigger re-render of the add-to-cart\n    addToCart.dataset.update = Date.now().toString();\n  }\n\n  private renderAddToCartElements() {\n    const addToCartElements = this.querySelectorAll('add-to-cart');\n    for (const addToCart of addToCartElements) {\n      this.renderAddToCartElement(addToCart);\n    }\n\n    const addToCartContainers = this.shadowRoot.querySelectorAll<HTMLElement>('.add-to-cart');\n    for (const addToCartContainer of addToCartContainers) {\n      addToCartContainer.hidden = false;\n    }\n\n    // hide the buttons if no product/variants are selected\n    const addToCartSlots = this.shadowRoot.querySelectorAll<HTMLElement>('.add-to-cart slot');\n    for (const addToCartSlot of addToCartSlots) {\n      if (this.variants.length === 0) {\n        addToCartSlot.hidden = true;\n      } else {\n        addToCartSlot.hidden = false;\n      }\n    }\n  }\n}\n\n/**\n * Modify the product price element data attributes\n */\nfunction renderPriceBox(productPrice: ProductPriceElement, price: number, compareAtPrice: number) {\n  const productElement = productPrice.querySelector<ProductElement>(':scope > product-element');\n  const firstVariant = productElement?.querySelector('variant-element');\n\n  // We can end up here before the browser has finished putting the price box's children on the\n  // page, and then there is nothing to write the price to, so we skip this box.\n  if (!firstVariant) {\n    return;\n  }\n\n  firstVariant.dataset.price = price.toString();\n\n  if (compareAtPrice > price) {\n    firstVariant.dataset.compareAtPrice = compareAtPrice.toString();\n    productPrice.dataset.discountPercentage = 'true';\n  } else {\n    firstVariant.dataset.compareAtPrice = '';\n    productPrice.dataset.discountPercentage = 'false';\n  }\n\n  // trigger re-render of the product price\n  productPrice.dataset.update = Date.now().toString();\n}\n\n/**\n * Compares two discount steps based on their threshold\n */\nfunction compareDiscountSteps(a: DiscountStep, b: DiscountStep) {\n  return a.stepThreshold - b.stepThreshold;\n}\n\n/**\n * Sets a custom attribute on a variant element. They are used for the analytical purposes\n */\nfunction setCustomAttribute(variant: VariantElement, key: string, value: string) {\n  type CustomAttribute = HTMLElementTagNameMap['custom-attribute'];\n  let customAttribute = variant.querySelector<CustomAttribute>(\n    `custom-attribute[data-key=\"${key}\"]`);\n  if (!customAttribute) {\n    customAttribute = document.createElement('custom-attribute');\n    customAttribute.dataset.key = key;\n    variant.appendChild(customAttribute);\n  }\n\n  customAttribute.dataset.value = value;\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'routine-element': RoutineElement;\n  }\n}\n\nif (!customElements.get('routine-element')) {\n  customElements.define('routine-element', RoutineElement);\n}\n","// Shopify serves resized images two ways. The old way splices the size into the filename, so\n// `foo.jpg` becomes `foo_120x120.jpg`, and that is what the deprecated `img_url` Liquid filter\n// emits. It is also what the retired slate fork alongside this file does. The current way, what the\n// `image_url` filter emits and what Hydrogen's image loader builds from JS, is query params on the\n// original URL. Both come back byte identical, so this is the same images through the shape Shopify\n// still maintains.\n\ntype Crop = 'bottom' | 'center' | 'left' | 'right' | 'top';\n\ntype Size = 'compact' | 'grande' | 'icon' | 'large' | 'medium' | 'original' | 'pico' | 'small' |\n  'thumb';\n\n// Shopify's named sizes are all square, and these are the pixel values its docs give for each one.\n// There is no entry for master because master means the original image at whatever size it happens\n// to be, and asking for no dimensions at all already gets you that.\nconst namedSizePixels: Record<Size, number> = {\n  compact: 160,\n  grande: 600,\n  icon: 32,\n  large: 480,\n  medium: 240,\n  original: 1024,\n  pico: 16,\n  small: 100,\n  thumb: 50\n};\n\nexport interface ImageUrlOptions {\n  /**\n   * Which part of the image to keep when the requested dimensions do not match its aspect ratio.\n   */\n  crop?: Crop;\n\n  /**\n   * Requested height in pixels.\n   */\n  height?: number;\n\n  /**\n   * Encoding quality from 10 to 90. Leave it out to let Shopify pick one for the output format.\n   */\n  quality?: number;\n\n  /**\n   * One of Shopify's named sizes, used only when neither a width nor a height is given.\n   */\n  size?: Size;\n\n  /**\n   * Requested width in pixels.\n   */\n  width?: number;\n}\n\n/**\n * Builds a URL for a Shopify CDN image at a particular size.\n *\n * Pass a width, a height, or both. You can pass one of Shopify's named sizes instead, and with none\n * of the three you get the original image back. Shopify tops out at 5760 in either direction, and\n * asking for something bigger than the original just hands back the original rather than failing.\n *\n * The returned URL is protocol relative so that nothing gets forced onto `http`.\n *\n * There is no format option. Shopify's own only accepts `jpg` and `pjpg`, and it negotiates webp\n * and avif off the `Accept` header anyway.\n *\n * @see https://shopify.dev/docs/api/liquid/filters/image_url\n * @example build('//langehair.com/cdn/shop/files/Foo.jpg?v=1765908254', { width: 120 })\n */\nexport function build(src: string, options: ImageUrlOptions) {\n  // Real image URLs on the site are protocol relative, and URL needs a scheme to parse anything. So\n  // we bolt one on for parsing and take it back off at the end.\n  const absolute = src.startsWith('//') ? `https:${src}` : src;\n  const url = new URL(absolute);\n\n  // A named size is only there for callers that have no dimensions of their own, so an explicit\n  // width or height wins over it. The named sizes are square, so the one number covers both.\n  const named = options.size && !options.width && !options.height ?\n    namedSizePixels[options.size] :\n    undefined;\n  const width = options.width || named;\n  const height = options.height || named;\n\n  if (width) {\n    url.searchParams.set('width', `${width}`);\n  }\n\n  if (height) {\n    url.searchParams.set('height', `${height}`);\n  }\n\n  if (options.crop) {\n    url.searchParams.set('crop', options.crop);\n  }\n\n  if (options.quality) {\n    url.searchParams.set('quality', `${options.quality}`);\n  }\n\n  return url.toString().replace(/^https?:/, '');\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport { build } from '../../../lib/shopify/image-url.js';\nimport html from './index.html';\nimport styles from './index.scss';\n\ntype VariantElement = HTMLElementTagNameMap['variant-element'];\ntype ProductElement = HTMLElementTagNameMap['product-element'];\n\n/**\n * CustomEvent emitted by `<routine-step>` whenever the product is changed (product toggled on/off\n * or variant changed by the user or automatically).\n */\ntype RoutineStepChangedEvent = CustomEvent<{\n  /**\n   * The product ID of the product that changed in the routine step. Empty if the step change was\n   * not related to a specific product\n   */\n  productId?: string;\n}>;\n\n/**\n * A single product extracted from a child product-element\n **/\ninterface StepProduct {\n  id: string;\n\n  /**\n   * Product image URL\n   **/\n  imageUrl: string;\n\n  /**\n   * Product subtitle, e.g. \"Shampoo\"\n   **/\n  subtitle: string;\n\n  /**\n   * Display title (metafields title preferred over Shopify title)\n   **/\n  title: string;\n\n  /**\n   * Product detail page URL\n   */\n  url: string;\n\n  /**\n   * Product variants, e.g. different colors or sizes\n   **/\n  variants: {\n    /**\n     * Whether the variant is available or out of stock\n     */\n    available: boolean;\n\n    /**\n     * Variant price in cents\n     **/\n    compareAtPrice?: number;\n\n    /**\n     * URL of the variant's image - may be empty\n     **/\n    imageSrc: string;\n\n    /**\n     * Variant price in cents\n     **/\n    price: number;\n\n    /**\n     * Whether this variant is currently selected in the routine step\n     **/\n    selected: boolean;\n\n    /**\n     * Variant SKU, used to identify the variant in the add-to-cart payload\n     **/\n    sku: string;\n\n    /**\n     * Variant title\n     **/\n    title: string;\n  }[];\n}\n\n/**\n * A single collapsible step within a `<routine-element>` routine.\n *\n * Renders an accordion header (label + description) and a list of products. Each product row has a\n * checkbox to include or exclude the product from the routine and, when the product has more than\n * one variant, a dropdown to choose between them.\n *\n * Product data is read from child `<product-element slot=\"products\">` elements. The element manages\n * `data-selected` on their nested `<variant-element>` nodes to track the active selection; if no\n * variant is selected on connect, the first available one is selected automatically.\n *\n * Whenever the user checks/unchecks a product or changes its variant, a `routine-step-changed`\n * CustomEvent (composed, bubbles) is dispatched so the parent `<routine-element>` can re-aggregate\n * the total price and add-to-cart payload.\n *\n * Expand/collapse state is controlled by the `data-open` attribute and toggled by clicking the\n * header.\n */\nclass RoutineStep extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-open'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Currency code\n     */\n    currencyCode: string;\n\n    /**\n     * Currency Symbol (e.g., $)\n     */\n    currencySymbol: string;\n\n    /**\n     * Step description text\n     **/\n    description: string;\n\n    /**\n     * Step label, e.g. \"Care\"\n     **/\n    label: string;\n\n    /**\n     * Whether the step body is visible\n     **/\n    open: 'false' | 'true';\n\n    /**\n     * Comma-separated list of product IDs that will be initially selected in the routine step\n     */\n    selectedProductIds: string;\n  };\n\n  private products: StepProduct[] = [];\n  private onHeaderClickedBound = this.onHeaderClicked.bind(this);\n  private onProductToggledBound = this.onProductToggled.bind(this);\n  private onVariantChangedBound = this.onVariantChanged.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    const header = this.shadowRoot.querySelector<HTMLButtonElement>('.header');\n    header.addEventListener('click', this.onHeaderClickedBound);\n\n    if (!this.dataset.open) {\n      this.dataset.open = 'true';\n    }\n\n    this.initializeData();\n    this.gatherData();\n    this.render();\n  }\n\n  public disconnectedCallback() {\n    const header = this.shadowRoot.querySelector<HTMLButtonElement>('.header');\n    header?.removeEventListener('click', this.onHeaderClickedBound);\n  }\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && oldValue !== newValue && name === 'data-open') {\n      this.renderHeader();\n    }\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  /**\n   * Ensures that a variant is selected for each product. If no variant is selected, the first\n   * available one will be selected. This is necessary because the routine step relies on the\n   * presence of a selected variant to determine the product image, price and add-to-cart payload,\n   * so we want to guarantee that there is always a selected variant for each product.\n   */\n  private initializeData() {\n    const preselectedProductIds = (this.dataset.selectedProductIds || '').split(',');\n    const productElements = this.querySelectorAll<ProductElement>(':scope > [slot=\"products\"]');\n    for (const productElement of productElements) {\n      const productId = productElement.dataset.id;\n      if (productId && preselectedProductIds.includes(productId)) {\n        // calling selectVariant with just the product element will select the first available\n        // variant if none is selected\n        selectVariant(productElement);\n      }\n    }\n\n    this.emitChangedEvent();\n  }\n\n  private emitChangedEvent(productId?: string) {\n    type Detail = WindowEventMap['routine-step-changed']['detail'];\n    const detail = <Detail>{};\n    if (productId) {\n      detail.productId = productId;\n    }\n\n    const routineStepChangedEvent = new CustomEvent<Detail>('routine-step-changed', {\n      detail,\n      bubbles: true,\n      composed: true\n    });\n    this.dispatchEvent(routineStepChangedEvent);\n  }\n\n  private gatherData() {\n    const products = [];\n    const productElements = this.querySelectorAll<ProductElement>(':scope > [slot=\"products\"]');\n    for (const productElement of productElements) {\n      const product = <StepProduct>{\n        id: productElement.dataset.id ?? '',\n        imageUrl: productElement.dataset.mainImage ?? '',\n        title: productElement.dataset.metafieldsTitle ?? productElement.dataset.title ?? '',\n        subtitle: productElement.dataset.metafieldsSubtitle ?? '',\n        url: productElement.dataset.url ?? '',\n        variants: []\n      };\n\n      const variantElements = productElement.querySelectorAll<HTMLElement>(\n        ':scope > variant-element');\n      type StepVariant = StepProduct['variants'][number];\n      for (const variantElement of variantElements) {\n        const variant: StepVariant = {\n          available: variantElement.dataset.available === 'true',\n          imageSrc: variantElement.dataset.imageSrc ?? '',\n          price: parseInt(variantElement.dataset.price ?? '0', 10),\n          selected: variantElement.dataset.selected === 'true',\n          sku: variantElement.dataset.sku ?? '',\n          title: variantElement.dataset.title ?? ''\n        };\n\n        if (variantElement.dataset.compareAtPrice) {\n          const compareAtPrice = parseInt(variantElement.dataset.compareAtPrice, 10);\n          if (!isNaN(compareAtPrice)) {\n            variant.compareAtPrice = compareAtPrice;\n          }\n        }\n\n        product.variants.push(variant);\n\n        if (variant.selected && variant.imageSrc) {\n          // if the selected variant has an image, use it instead of the product image\n          product.imageUrl = variant.imageSrc;\n        }\n      }\n\n      products.push(product);\n    }\n\n    this.products = products;\n  }\n\n  private render() {\n    this.renderHeader();\n    this.renderProducts();\n  }\n\n  private renderHeader() {\n    const title = this.shadowRoot.querySelector('.header .title');\n    title.textContent = this.dataset.label;\n\n    const description = this.shadowRoot.querySelector('.description');\n    description.textContent = this.dataset.description;\n\n    const isOpen = this.dataset.open !== 'false';\n    const header = this.shadowRoot.querySelector<HTMLButtonElement>('.header');\n    const body = this.shadowRoot.querySelector<HTMLElement>('.body');\n\n    header.setAttribute('aria-expanded', isOpen ? 'true' : 'false');\n    body.inert = !isOpen;\n  }\n\n  /**\n   * Generate a DOM node for a single product in the routine step based on the product data\n   */\n  private generateListItem(product: StepProduct) {\n    const template = this.shadowRoot.querySelector('template');\n    const item = document.importNode(template.content, true);\n\n    if (product.imageUrl) {\n      const productImage = item.querySelector('img');\n\n      // the thumbnail renders at 60x60, so asking Shopify for the full size image wastes a lot of\n      // bytes. we request 2x that for retina screens, the same doubling image-element does.\n      productImage.src = build(product.imageUrl, { height: 120, width: 120 });\n    }\n\n    const productTitle = item.querySelector('.title');\n    productTitle.textContent = product.title;\n\n    const productSubtitle = item.querySelector('.subtitle');\n    productSubtitle.textContent = product.subtitle;\n\n    const variants = product.variants;\n    const availableVariants = variants.filter(filterAvailable);\n    const selectedVariants = variants.filter(filterSelected);\n\n    const productCardElement = item.querySelector<HTMLElement>('.product');\n    const checkbox = item.querySelector<HTMLInputElement>('.checkbox');\n    if (availableVariants.length === 0) {\n      checkbox.disabled = true;\n      productCardElement.classList.add('out-of-stock');\n    } else {\n      productCardElement.addEventListener('click', onProductClicked);\n    }\n\n    if (selectedVariants.length > 0) {\n      checkbox.setAttribute('checked', '');\n    }\n\n    checkbox.dataset.productId = product.id;\n    checkbox.addEventListener('change', this.onProductToggledBound);\n\n    const variantsContainer = item.querySelector<HTMLElement>('.variants');\n\n    const selectorId = 'variant-selector-' + product.id;\n    const label = variantsContainer.querySelector('label');\n    label.setAttribute('for', selectorId);\n\n    const select = variantsContainer.querySelector('select');\n    select.id = selectorId;\n\n    for (const variant of product.variants) {\n      const option = document.createElement('option');\n      option.value = variant.sku;\n      option.textContent = variant.title;\n      if (!variant.available) {\n        option.disabled = true;\n      }\n\n      if (variant.selected) {\n        option.selected = true;\n      }\n\n      select.appendChild(option);\n    }\n\n    select.dataset.productId = product.id;\n    select.disabled = !checkbox.checked;\n\n    select.addEventListener('change', this.onVariantChangedBound);\n\n    if (product.variants.length > 1) {\n      variantsContainer.hidden = false;\n    }\n\n    // show the price of the selected (or first) variant\n    const productPrice = item.querySelector('product-price');\n    productPrice.dataset.currencyCode = this.dataset.currencyCode;\n    productPrice.dataset.currencySymbol = this.dataset.currencySymbol;\n    const productElement = document.createElement('product-element');\n    const variantElement = document.createElement('variant-element');\n    productElement.appendChild(variantElement);\n    productPrice.appendChild(productElement);\n\n    let selectedVariant = product.variants.find(filterSelected);\n    if (!selectedVariant && product.variants.length > 0) {\n      selectedVariant = product.variants[0];\n    }\n\n    if (selectedVariant) {\n      variantElement.dataset.price = selectedVariant.price.toString();\n      const compareAtPrice = selectedVariant.compareAtPrice;\n      if (compareAtPrice && compareAtPrice > selectedVariant.price) {\n        variantElement.dataset.compareAtPrice = compareAtPrice.toString();\n        productPrice.dataset.discountPercentage = 'true';\n      }\n    }\n\n    productPrice.dataset.update = Date.now().toString();\n\n    const learnMoreLink = item.querySelector<HTMLAnchorElement>('.learn-more');\n    learnMoreLink.href = product.url;\n\n    return item;\n  }\n\n  private renderProducts() {\n    const list = this.shadowRoot.querySelector('.products');\n    if (!list) {\n      return;\n    }\n\n    // clear all the products\n    list.innerHTML = '';\n\n    for (const product of this.products) {\n      const item = this.generateListItem(product);\n      list.appendChild(item);\n    }\n  }\n\n  private onHeaderClicked(_event: MouseEvent) {\n    this.dataset.open = this.dataset.open === 'false' ? 'true' : 'false';\n  }\n\n  private onProductToggled(event: Event) {\n    if (!(event.currentTarget instanceof HTMLInputElement)) {\n      return;\n    }\n\n    const productId = event.currentTarget.dataset.productId;\n    const productElement = this.querySelector<ProductElement>(\n      `product-element[slot=\"products\"][data-id=\"${productId}\"]`);\n\n    const productCard = event.currentTarget.closest('.product');\n    const variantSelector = productCard.querySelector<HTMLSelectElement>('.variants select');\n    const variantSku = variantSelector.value;\n\n    if (event.currentTarget.checked) {\n      variantSelector.disabled = false;\n      selectVariant(productElement, variantSku);\n    } else {\n      variantSelector.disabled = true;\n      deselectAllVariants(productElement);\n    }\n\n    this.emitChangedEvent(productId);\n  }\n\n  private onVariantChanged(event: Event) {\n    if (!(event.currentTarget instanceof HTMLSelectElement)) {\n      return;\n    }\n\n    const variantSku = event.currentTarget.value;\n\n    const productId = event.currentTarget.dataset.productId;\n    const productElement = this.querySelector<ProductElement>(\n      `product-element[slot=\"products\"][data-id=\"${productId}\"]`);\n\n    selectVariant(productElement, variantSku);\n    this.gatherData();\n    this.renderProducts();\n\n    type Detail = WindowEventMap['routine-step-changed']['detail'];\n    const routineStepChangedEvent = new CustomEvent<Detail>('routine-step-changed', {\n      detail: {\n        productId: productId\n      },\n      bubbles: true,\n      composed: true\n    });\n    this.dispatchEvent(routineStepChangedEvent);\n  }\n}\n\n/**\n * Toggle the product checkbox when the user clicks anywhere on the product card, delegating to the\n * checkbox's own change handler. Clicks directly on the checkbox or variant selector are ignored\n * here because those elements handle the interaction themselves.\n *\n * The current target is the product card the listener sits on, which is where the checkbox is\n * found. The target is the descendant the click actually landed on, which decides whether to ignore\n * it.\n */\nfunction onProductClicked(event: MouseEvent) {\n  if (!(event.currentTarget instanceof HTMLElement)) {\n    return;\n  }\n\n  // ignore clicks on the \"Learn More\" link, which is a child of the product card. We don't want to\n  // toggle the checkbox when the user clicks on that link, because it navigates to the product\n  // detail page. It is the only anchor in the card, so any anchor target is that link.\n  if (event.target instanceof HTMLAnchorElement) {\n    return;\n  }\n\n  const targetNodeName = event.target instanceof Element ? event.target.nodeName : null;\n\n  // do not react to clicks on the variant selector or the checkbox\n  if (targetNodeName && !['INPUT', 'SELECT'].includes(targetNodeName)) {\n    const checkbox = event.currentTarget.querySelector<HTMLInputElement>('.checkbox');\n    checkbox.click();\n  }\n}\n\n/**\n * Clear the selected variant for the given product\n */\nfunction deselectAllVariants(product: ProductElement) {\n  const variantElements = product.querySelectorAll<VariantElement>(':scope > variant-element');\n  for (const variantElement of variantElements) {\n    variantElement.dataset.selected = 'false';\n  }\n}\n\n/**\n * Modify the DOM of the product element based on the selected variant SKU. If the SKU is not\n * provided, the first available variant will be selected.\n */\nfunction selectVariant(product: ProductElement, variantSku?: string) {\n  deselectAllVariants(product);\n\n  let variantToSelect: VariantElement;\n  if (variantSku) {\n    variantToSelect = product.querySelector<VariantElement>(\n      `:scope > variant-element[data-sku=\"${variantSku}\"][data-available=\"true\"]`);\n  } else {\n    variantToSelect = product.querySelector<VariantElement>(\n      ':scope > variant-element[data-available=\"true\"]');\n  }\n\n  if (variantToSelect) {\n    variantToSelect.dataset.selected = 'true';\n  }\n}\n\n/**\n * Return true if the variant is available for purchase, false if it's out of stock\n */\nfunction filterAvailable(variant: StepProduct['variants'][number]) {\n  return variant.available;\n}\n\n/**\n * Return true if the variant is currently selected, false otherwise\n */\nfunction filterSelected(variant: StepProduct['variants'][number]) {\n  return variant.selected;\n}\n\ndeclare global {\n  interface WindowEventMap {\n    'routine-step-changed': RoutineStepChangedEvent;\n  }\n\n  interface HTMLElementTagNameMap {\n    'routine-step': RoutineStep;\n  }\n}\n\nif (!customElements.get('routine-step')) {\n  customElements.define('routine-step', RoutineStep);\n}\n"],"names":["RoutineElement","HTMLElement","variants","discountSteps","onRoutineStepChangedBound","this","onRoutineStepChanged","bind","connectedCallback","initializeShadowRoot","addEventListener","disconnectedCallback","removeEventListener","shadowRoot","attachShadow","mode","innerHTML","_event","isConnected","gatherData","render","gatherVariantData","routineSteps","querySelectorAll","step","productElements","productElement","variantElement","querySelector","push","gatherDiscountData","discountStepElements","discountStep","discountCode","dataset","discountPercentage","parseFloat","stepThreshold","parseInt","threshold","isNaN","sort","compareDiscountSteps","getCurrentDiscountStep","currentDiscountStep","length","getNextDiscountStep","nextDiscountStep","renderPriceBoxes","renderAddToCartElements","totalPrice","variant","variantPrice","price","totalCompareAtPrice","Math","round","productPrices","productPrice","renderPriceBox","emptyMessages","emptyMessage","hidden","priceSlots","priceSlot","installmentPaymentMessage","setAttribute","toString","priceContainers","priceContainer","promoMessages","promoMessage","message","textContent","renderAddToCartElement","addToCart","appendChild","mainVariant","addonVariants","mainVariantClone","cloneNode","setCustomAttribute","addon","isAddon","removeAttribute","update","Date","now","addToCartElements","addToCartContainers","addToCartContainer","addToCartSlots","addToCartSlot","compareAtPrice","firstVariant","a","b","key","value","customAttribute","document","createElement","customElements","get","define","namedSizePixels","compact","grande","icon","large","medium","original","pico","small","thumb","RoutineStep","observedAttributes","products","onHeaderClickedBound","onHeaderClicked","onProductToggledBound","onProductToggled","onVariantChangedBound","onVariantChanged","open","initializeData","header","attributeChangedCallback","name","oldValue","newValue","renderHeader","preselectedProductIds","selectedProductIds","split","productId","id","includes","selectVariant","emitChangedEvent","detail","routineStepChangedEvent","CustomEvent","bubbles","composed","dispatchEvent","_productElement$datas","_productElement$datas2","_ref","_productElement$datas3","_productElement$datas4","_productElement$datas5","product","imageUrl","mainImage","title","metafieldsTitle","subtitle","metafieldsSubtitle","url","variantElements","_variantElement$datas","_variantElement$datas2","_variantElement$datas3","_variantElement$datas4","available","imageSrc","selected","sku","renderProducts","label","description","isOpen","body","inert","generateListItem","template","item","importNode","content","src","options","absolute","startsWith","URL","named","size","width","height","undefined","searchParams","set","crop","quality","replace","build","availableVariants","filter","filterAvailable","selectedVariants","filterSelected","productCardElement","checkbox","disabled","classList","add","onProductClicked","variantsContainer","selectorId","select","option","checked","currencyCode","currencySymbol","selectedVariant","find","href","list","event","currentTarget","HTMLInputElement","variantSelector","closest","variantSku","deselectAllVariants","HTMLSelectElement","target","HTMLAnchorElement","targetNodeName","Element","nodeName","click","variantToSelect"],"sourceRoot":""}