PageSourceSearch

https://ardene.com/cdn/shop/t/233/assets/snippet.braze.js?v=151675626790986278851790195638

js ardene.com collected 2026-09-24 18:22:35 UTC 76,849 bytes, 2 lines download raw bytes

1(function(){"use strict";var __webpack_modules__={
1"./src/constants/events.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   EVENTS: () => (/* binding */ EVENTS)\n/* harmony export */ });\n/**\n * Defines custom event names for various e-commerce interactions and Shopify\n * theme editor events.\n *\n * @constant\n */\nconst EVENTS = {\n  // E-commerce events\n  CART_ADD: 'cart-add',\n  PRODUCT_ADDED_TO_CART: 'product-added-to-cart',\n  PRODUCT_REMOVED_FROM_CART: 'product-removed-from-cart',\n  CART_REMOVE: 'cart-remove',\n  CART_UPDATE: 'cart-update',\n  CART_VIEW: 'cart-view',\n  SEARCH_INIT: 'search-init',\n  SEARCH: 'search',\n  FILTER_APPLY: 'filter-apply',\n  FILTER_REMOVE: 'filter-remove',\n  FILTER_RESET: 'filter-reset',\n  FILTER_SORT: 'filter-sort',\n  FILTER_UPDATE: 'filter-update',\n  VARIANT_CHANGE: 'variant-change',\n  PRODUCT_CHANGE: 'product-change',\n  SELECT_ITEM: 'select-item',\n  ADD_TO_WISHLIST: 'add-to-wishlist',\n  REMOVE_FROM_WISHLIST: 'remove-from-wishlist',\n  OPEN_MODAL: 'open-modal',\n  CLOSE_MODAL: 'close-modal',\n  PRODUCT_VIEWED: 'product-viewed',\n  PRODUCT_GRID_READY: 'product-grid-ready',\n  PRODUCT_GRID_VIEWED: 'product-grid-viewed',\n  CART_VIEWED: 'cart-viewed',\n  BEGIN_CHECKOUT: 'begin-checkout',\n  SWYM_COLLECTIONS_LOADED: 'swym:collections-loaded',\n\n  // Shopify theme editor events\n  SHOPIFY_INSPECTOR_ACTIVATE: 'shopify:inspector:activate',\n  SHOPIFY_INSPECTOR_DEACTIVATE: 'shopify:inspector:deactivate',\n  SHOPIFY_SECTION_LOAD: 'shopify:section:load',\n  SHOPIFY_SECTION_UNLOAD: 'shopify:section:unload',\n  SHOPIFY_SECTION_SELECT: 'shopify:section:select',\n  SHOPIFY_SECTION_DESELECT: 'shopify:section:deselect',\n  SHOPIFY_SECTION_REORDER: 'shopify:section:reorder',\n  SHOPIFY_BLOCK_SELECT: 'shopify:block:select',\n  SHOPIFY_BLOCK_DESELECT: 'shopify:block:deselect',\n};\n\n\n//# sourceURL=webpack://ardene-theme/./src/constants/events.js?\n}")},
1"./src/sections-api-service.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   \"default\": () => (/* binding */ SectionsAPIService)\n/* harmony export */ });\nconst SESSION_CACHE_PREFIX = 'sections_cache:';\nconst SESSION_CACHE_TTL = 30 * 60 * 1000;\n\nclass SectionsAPIService {\n  static cache = {};\n  static requests = {};\n\n  /**\n   * Fetches a section, ensuring only one request is made per unique URL at a time.\n   *\n   * @async\n   * @param {string | URL} url\n   * @param {string} sectionId\n   * @param {{ cache?: boolean; sessionCache?: boolean; method?: string; headers?: Record<string, string>; signal?: AbortSignal }} config\n   * @returns {Promise<string>}\n   */\n  static async request(url, sectionId, config) {\n    const _url = new URL(url);\n    _url.searchParams.set('section_id', sectionId);\n    const key = _url.toString();\n\n    // Return from in-memory cache if available\n    if (config?.cache !== false && this.cache[key]) return this.cache[key];\n\n    // Check sessionStorage cache (survives page navigation)\n    if (config?.sessionCache) {\n      const sessionData = this.getSessionCache(key);\n      if (sessionData) {\n        this.cache[key] = sessionData;\n        return sessionData;\n      }\n    }\n\n    // If a request is already in progress, return the existing promise\n    if (this.requests[key]) return this.requests[key];\n\n    // Create and store the fetch promise\n    this.requests[key] = (async () => {\n      try {\n        const res = await fetch(_url, {\n          method: config?.method || 'GET',\n          headers: config?.headers,\n          signal: config?.signal,\n        });\n\n        if (!res.ok) {\n          throw new Error(`Section request failed: ${res.status} ${res.statusText}`);\n        }\n\n        const text = await res.text();\n        this.cache[key] = text;\n\n        if (config?.sessionCache) {\n          this.setSessionCache(key, text);\n        }\n\n        return text;\n      } finally {\n        delete this.requests[key];\n      }\n    })();\n\n    return this.requests[key];\n  }\n\n  static getSessionCache(key) {\n    try {\n      const raw = sessionStorage.getItem(SESSION_CACHE_PREFIX + key);\n      if (!raw) return null;\n      const { data, ts } = JSON.parse(raw);\n      if (Date.now() - ts > SESSION_CACHE_TTL) {\n        sessionStorage.removeItem(SESSION_CACHE_PREFIX + key);\n        return null;\n      }\n      return data;\n    } catch {\n      return null;\n    }\n  }\n\n  static setSessionCache(key, data) {\n    try {\n      sessionStorage.setItem(\n        SESSION_CACHE_PREFIX + key,\n        JSON.stringify({ data, ts: Date.now() }),\n      );\n    } catch {\n      this.cleanExpiredSessionCache();\n      try {\n        sessionStorage.setItem(\n          SESSION_CACHE_PREFIX + key,\n          JSON.stringify({ data, ts: Date.now() }),\n        );\n      } catch {\n        // Storage still full after cleanup\n      }\n    }\n  }\n\n  static cleanExpiredSessionCache() {\n    try {\n      const now = Date.now();\n      const keysToRemove = [];\n      for (let i = 0; i < sessionStorage.length; i++) {\n        const storageKey = sessionStorage.key(i);\n        if (storageKey?.startsWith(SESSION_CACHE_PREFIX)) {\n          try {\n            const { ts } = JSON.parse(sessionStorage.getItem(storageKey));\n            if (now - ts > SESSION_CACHE_TTL) {\n              keysToRemove.push(storageKey);\n            }\n          } catch {\n            keysToRemove.push(storageKey);\n          }\n        }\n      }\n      keysToRemove.forEach((k) => sessionStorage.removeItem(k));\n    } catch {\n      // Fail silently\n    }\n  }\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/sections-api-service.js?\n}")},
1"./src/snippets/braze/banner-products.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   BANNER_PRODUCTS_ATTRIBUTE: () => (/* binding */ BANNER_PRODUCTS_ATTRIBUTE),\n/* harmony export */   CAROUSEL_MOUNT_EVENT: () => (/* binding */ CAROUSEL_MOUNT_EVENT),\n/* harmony export */   productHandlesFromBannerHtml: () => (/* binding */ productHandlesFromBannerHtml),\n/* harmony export */   renderBannerProductCarousel: () => (/* binding */ renderBannerProductCarousel)\n/* harmony export */ });\n/* harmony import */ var _util_urlParser_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../util/urlParser.js */ \"./src/util/urlParser.js\");\n/* harmony import */ var _product_card_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./product-card.js */ \"./src/snippets/braze/product-card.js\");\n\n\n\n/**\n * Product carousels built from a Braze banner's body.\n *\n * A recommendation banner is authored in Braze as an HTML block listing\n * product URLs, one per paragraph. Rendered as Braze ships it, that is a\n * column of bare links. A banner placement can instead opt in to reading those\n * URLs as product handles and rendering the storefront's own product cards,\n * which is what `data-braze-banner-products` on the container asks for.\n *\n * Only the handle is taken from the banner. The card itself comes from the\n * `product-card` section, so price, badges, swatches and locale are the\n * storefront's and cannot go stale against whatever the campaign was authored\n * with \u2014 the same contract `braze/product-card.js` holds for c
1ontent cards.\n */\n\n/** Opt-in flag set by blocks/braze-banner.liquid. */\nconst BANNER_PRODUCTS_ATTRIBUTE = 'data-braze-banner-products';\n\n/** Asks snippet.braze-content-cards.js to mount Swiper over a new scaffold. */\nconst CAROUSEL_MOUNT_EVENT = 'braze:carousel-mount-requested';\n\n/** The arrows blocks/braze-banner.liquid authors for a product placement. */\nconst NAV_TEMPLATE_SELECTOR = 'template[data-braze-banner-nav]';\n\n/**\n * Absolute product URLs in banner copy. Stops at whitespace and at the\n * characters that would end an attribute or a tag, so a URL sitting in text\n * (`<p>https://\u2026</p>`) and one in an `href` both match.\n */\nconst PRODUCT_URL_PATTERN = /https?:\\/\\/[^\\s\"'<>]+\\/products\\/[^\\s\"'<>]+/gi;\n\n/** Punctuation that follows a URL in prose rather than belonging to it. */\nconst TRAILING_PUNCTUATION = /[.,;:!?)\\]}]+$/;\n\n/**\n * The product handles a banner names, in the order it names them.\n *\n * Deduplicated: a campaign that lists the same product twice should not render\n * it twice. Locale prefixes are handled by the shared parser, so `/en-ca/\n * products/x` and `/products/x` both yield `x`.\n *\n * @param {string} html \u2014 the banner's raw HTML\n * @returns {string[]}\n */\nfunction productHandlesFromBannerHtml(html) {\n  if (typeof html !== 'string' || html === '') return [];\n\n  const seen = new Set();\n  const handles = [];\n\n  for (const match of html.match(PRODUCT_URL_PATTERN) ?? []) {\n    const handle = (0,_util_urlParser_js__WEBPACK_IMPORTED_MODULE_0__.extractProductHandle)(match.replace(TRAILING_PUNCTUATION, ''));\n    if (!handle || seen.has(handle)) continue;\n\n    seen.add(handle);\n    handles.push(handle);\n  }\n\n  return handles;\n}\n\n/**\n * @param {string} className\n * @param {Record<string, string>} [attributes]\n * @returns {HTMLDivElement}\n */\nfunction div(className, attributes = {}) {\n  const el = document.createElement('div');\n  el.className = className;\n  Object.entries(attributes).forEach(([name, value]) => el.setAttribute(name, value));\n\n  return el;\n}\n\n/**\n * The arrows authored for one placement, cloned ready to mount.\n *\n * They come from Liquid rather than from here because their labels need the\n * `t` filter, which a bundle cannot call, and the carets are the theme's own\n * icon snippets. Cloning per render is what keeps them working across\n * re-renders: each render replaces the container's children, so the arrows\n * mounted last time go with the carousel they were mounted into.\n *\n * Returns `null` for a placement that authored none, which is what a banner\n * rendered by an older template looks like \u2014 the carousel still renders, just\n * without arrows.\n *\n * @param {string} [placement]\n * @returns {DocumentFragment | null}\n */\nfunction cloneNavigation(placement) {\n  if (!placement) return null;\n\n  const template = Array.from(document.querySelectorAll(NAV_TEMPLATE_SELECTOR)).find(\n    (node) => node.dataset.brazeBannerNav === placement,\n  );\n\n  return template ? template.content.cloneNode(true) : null;\n}\n\n/**\n * Builds the carousel scaffold the content-cards section uses.\n *\n * The shape is deliberately identical, because that is what buys the shared\n * look: `snippet.braze-content-cards.css` styles it and\n * `snippet.braze-content-cards.js` mounts Swiper over it. The mount carries\n * `data-braze-carousel-mount` rather than `data-braze-content-cards`, because\n * the latter is the content-card feed selector and `braze/cards.js` empties\n * every element matching it on each render.\n *\n * @param {DocumentFragment | null} [navigation]\n * @returns {{ wrapper: HTMLElement, mount: HTMLElement }}\n */\nfunction buildScaffold(navigation) {\n  const wrapper = div('braze-content-cards__wrapper braze-content-cards__wrapper--carousel');\n\n  // The header row is how the section puts its arrows top-right, and\n  // snippet.braze-content-cards.js looks them up from the wrapper. Here it\n  // holds the arrows alone, there being no heading to share the row with.\n  if (navigation) {\n    const header = div('braze-content-cards__header braze-content-cards__header--nav-only');\n    header.appendChild(navigation);\n    wrapper.appendChild(header);\n  }\n\n  const slider = div('braze-content-cards__slider braze-content-cards--nav-top-right');\n  const scaffold = div('braze-content-cards__carousel swiper', {\n    'data-swiper': '',\n    'data-empty': 'true',\n  });\n  const mount = div('braze-content-cards swiper-wrapper', {\n    'data-braze-carousel-mount': '',\n    'data-layout': 'carousel',\n  });\n\n  scaffold.appendChild(mount);\n  slider.appendChild(scaffold);\n  wrapper.appendChild(slider);\n\n  return { wrapper, mount };\n}\n\n/**\n * Replaces a banner container's contents with a carousel of product cards.\n *\n * Cards are appended synchronously as placeholders and fill themselves from\n * the storefront, so the carousel can be mou
1nted immediately rather than\n * waiting on a round trip per product. A handle the storefront has no product\n * for removes its own card.\n *\n * @param {HTMLElement} container \u2014 the `[data-braze-banner]` element\n * @param {string[]} handles\n * @param {{ onCardClick?: function(): void, placement?: string }} [options]\n * @returns {boolean} whether a carousel was rendered\n */\nfunction renderBannerProductCarousel(container, handles, options = {}) {\n  if (!container || handles.length === 0) return false;\n\n  const { onCardClick, placement } = options;\n  const { wrapper, mount } = buildScaffold(cloneNavigation(placement));\n\n  handles.forEach((handle) => {\n    mount.appendChild((0,_product_card_js__WEBPACK_IMPORTED_MODULE_1__.buildProductCardFromHandle)(handle, { onClick: onCardClick }));\n  });\n\n  container.replaceChildren(wrapper);\n\n  // A dedicated event, not braze:content-cards-updated: the BrazeCarousel\n  // element listens for that one and re-renders from `detail.cards`, so\n  // borrowing it would blank every carousel on the page.\n  document.dispatchEvent(new CustomEvent(CAROUSEL_MOUNT_EVENT));\n\n  return true;\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/banner-products.js?\n}")},
1"./src/snippets/braze/banners.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   initBanners: () => (/* binding */ initBanners)\n/* harmony export */ });\n/* harmony import */ var _banner_products_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./banner-products.js */ \"./src/snippets/braze/banner-products.js\");\n/**\n * Braze Banners rendering for `[data-braze-banner]` placements.\n *\n * The Banners API arrived in a later Web SDK release than the theme previously\n * bundled, and the app embed's SDK version is Braze's choice rather than ours.\n * Every entry point is therefore feature-detected: if the embed ships an older\n * build, banner placements degrade to empty containers instead of throwing.\n */\n\n\n\nconst BANNER_SELECTOR = '[data-braze-banner]';\n\nconst REQUIRED_METHODS = [\n  'subscribeToBannersUpdates',\n  'requestBannersRefresh',\n  'getBanner',\n  'insertBanner',\n];\n\n/**\n * @param {object} sdk\n * @returns {boolean}\n */\nfunction supportsBanners(sdk) {\n  return REQUIRED_METHODS.every(method => typeof sdk[method] === 'function');\n}\n\n/**\n * The banner's HTML.\n *\n * `banner.html` is the documented property, but the Banners API is newer than\n * the SDK build the app embed happens to ship and this theme cannot pin that\n * version. When the property is absent the banner is inserted first and its\n * rendered markup read back, which costs a render the caller then replaces but\n * never leaves a placement blank.\n *\n * @param {object} banner\n * @param {HTMLElement} container\n * @param {object} sdk\n * @returns {string}\n */\nfunction bannerHtml(banner, container, sdk) {\n  if (typeof banner.html === 'string' && banner.html !== '') return banner.html;\n\n  try {\n    sdk.insertBanner(banner, container);\n    return container.innerHTML;\n  } catch (error) {\n    console.error('[Braze] Could not read the banner body for product URLs:', error);\n    return '';\n  }\n}\n\n/**\n * Inserts one placement's banner into its container.\n * @param {object} sdk\n * @param {string} placement\n */\nfunction renderPlacement(sdk, placement) {\n  const banner = sdk.getBanner(placement);\n  if (!banner) return;\n\n  const container = document.getElementById(`${placement}-banner`);\n  if (!container) return;\n\n  // A control banner has no body to read product URLs out of, and must be\n  // inserted untouched so Braze logs the holdout impression.\n  if (!banner.isControl && container.hasAttribute(_banner_products_js__WEBPACK_IMPORTED_MODULE_0__.BANNER_PRODUCTS_ATTRIBUTE)) {\n    const handles = (0,_banner_products_js__WEBPACK_IMPORTED_MODULE_0__.productHandlesFromBannerHtml)(bannerHtml(banner, container, sdk));\n\n    if ((0,_banner_products_js__WEBPACK_IMPORTED_MODULE_0__.renderBannerProductCarousel)(container, handles, {\n      placement,\n      onCardClick: () => sdk.logBannerClick?.(banner),\n    })) {\n      return;\n    }\n    // No product URLs in the body: fall through and render the banner as\n    // authored, rather than leaving the placement empty.\n  }\n\n  sdk.insertBanner(banner, container);\n\n  // Control-group banners must be inserted so Braze logs the impression, but\n  // must not be visible.\n  if (banner.isControl) {\n    container.style.display = 'none';\n  }\n}\n\n/**\n * Subscribes to banner updates for every placement declared on the page.\n * @param {object} sdk \u2014 the resolved `window.braze`\n */\nfunction initBanners(sdk) {\n  const placements = Array.from(document.querySelectorAll(BANNER_SELECTOR))\n    .map(el => el.dataset.brazeBanner)\n    .filter(Boolean);\n\n  if (placements.length === 0) return;\n\n  if (!supportsBanners(sdk)) {\n    console.error(\n      '[Braze] The app embed SDK does not expose the Banners API; ' +\n      `${placements.length} banner placement(s) will not render.`\n    );\n    return;\n  }\n\n  sdk.subscribeToBannersUpdates(() => {\n    // Runs inside the SDK's subscription callback: one failing placement must\n    // not stop the others from rendering.\n    placements.forEac
1h(placement => {\n      try {\n        renderPlacement(sdk, placement);\n      } catch (error) {\n        console.error(`[Braze] Could not render the \"${placement}\" banner:`, error);\n      }\n    });\n  });\n\n  sdk.requestBannersRefresh(placements);\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/banners.js?\n}")},
1"./src/snippets/braze/card-layout.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   desktopColumnCount: () => (/* binding */ desktopColumnCount),\n/* harmony export */   desktopTrackCount: () => (/* binding */ desktopTrackCount)\n/* harmony export */ });\n/**\n * Desktop column count for a Braze content-card feed.\n *\n * Cards never exceed a 4-up slot: 1\u20134 cards sit in a four-column grid.\n * Additional cards share equally up to the section max (schema 4\u20136).\n *\n * @param {number} cardCount\n * @param {number} [maxColumns=5]\n * @returns {number}\n */\nfunction desktopColumnCount(cardCount, maxColumns = 5) {\n  const parsed = Number(maxColumns);\n  const max = Math.min(6, Math.max(4, Number.isFinite(parsed) && parsed > 0 ? parsed : 5));\n  const count = Number(cardCount) || 0;\n  return Math.max(4, Math.min(count, max));\n}\n\n/**\n * Number of grid tracks to lay down, which is what centres a sparse feed.\n *\n * The column count above fixes the card *width* at a four-up slot. Laying down\n * a full row of tracks regardless of card count is what pins two cards to the\n * left with two empty tracks beside them, because `1fr` tracks leave no free\n * space for `justify-content` to distribute. One track per card, sized to the\n * four-up width, gives it something to centre.\n *\n * Capped at the column count so a feed longer than one row still wraps \u2014\n * eight explicit tracks would overflow rather than flowing onto a second row.\n * Never zero, which `repeat()` rejects.\n *\n * Resolved here rather than in CSS because `repeat()` takes a literal integer,\n * so `min()` inside it does not parse.\n *\n * @param {number} cardCount\n * @param {number} [maxColumns=5]\n * @returns {number}\n */\nfunction desktopTrackCount(cardCount, maxColumns = 5) {\n  const count = Number(cardCount) || 0;\n  return Math.max(1, Math.min(count, desktopColumnCount(count, maxColumns)));\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/card-layout.js?\n}")},
1"./src/snippets/braze/card-type.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   CARD_TYPE: () => (/* binding */ CARD_TYPE),\n/* harmony export */   getCardType: () => (/* binding */ getCardType),\n/* harmony export */   getContentCardType: () => (/* binding */ getContentCardType)\n/* harmony export */ });\n/* harmony import */ var _product_handle_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./product-handle.js */ \"./src/snippets/braze/product-handle.js\");\n/**\n * Content card type resolution.\n *\n * `card.constructor.name` is NOT usable here. The app embed serves a minified\n * SDK bundle, so class names are mangled and every card would fall through to\n * the image-only branch. `instanceof` compares constructor identity and is\n * unaffected by minification, so it is the only safe check.\n *\n * The embed is not contractually obliged to expose the card classes on its\n * global, so a duck-typed fallback keeps rendering correct either way.\n */\n\n\n\nconst CARD_TYPE = {\n  PRODUCT: 'product',\n  CLASSIC: 'classic',\n  CAPTIONED_IMAGE: 'captioned-image',\n  IMAGE_ONLY: 'image-only',\n};\n\n/**\n * Duck-types a card by the fields each Braze card type actually carries.\n * Classic cards have a description; captioned images have a title but no\n * description; image-only cards have neither.\n * @param {object} card\n * @returns {string}\n */\nfunction inferCardType(card) {\n  if (card?.description) return CARD_TYPE.CLASSIC;\n  if (card?.title) return CARD_TYPE.CAPTIONED_IMAGE;\n  return CARD_TYPE.IMAGE_ONLY;\n}\n\n/**\n * Resolves a content card's type against the SDK's card classes, falling back\n * to duck-typing when the SDK does not expose them.\n *\n * A product handle is checked first and wins over every SDK class: naming a\n * product is an explicit instruction from the campaign, and the card's own\n * image and copy are irrelevant once the storefront renders the product\n * itself. Any Braze card type can carry the key-value pair.\n *\n * @param {object} card\n * @param {object|null} sdk \u2014 the resolved `window.braze`\n * @returns {string} one of CARD_TYPE\n */\nfunction getCardType(card, sdk) {\n  if ((0,_product_handle_js__WEBPACK_IMPORTED_MODULE_0__.hasProductHandle)(card)) return CARD_TYPE.PRODUCT;\n\n  return getContentCardType(card, sdk);\n}\n\n/**\n * The card's own visual type, ignoring any product it names.\n *\n * This is what a product card falls back to when the storefront has no product\n * for the handle: the card still has an image and copy of its own, and showing\n * those beats showing nothing.\n *\n * @param {object} card\n * @param {object|null} sdk \u2014 the resolved `window.braze`\n * @returns {string} one of CARD_TYPE, never CARD_TYPE.PRODUCT\n */\nfunction getContentCardType(card, sdk) {\n  if (sdk?.ClassicCard && card instanceof sdk.ClassicCard) return CARD_TYPE.CLASSIC;\n  if (sdk?.CaptionedImage && card instanceof sdk.CaptionedImage) return CARD_TYPE.CAPTIONED_IMAGE;\n  if (sdk?.ImageOnly && card instanceof sdk.ImageOnly) return CARD_TYPE.IMAGE_ONLY;\n\n  return inferCardType(card);\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/card-type.js?\n}")},
1"./src/snippets/braze/cards.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   createCardRenderer: () => (/* binding */ createCardRenderer)\n/* harmony export */ });\n/* harmony import */ var _card_type_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./card-type.js */ \"./src/snippets/braze/card-type.js\");\n/* harmony import */ var _card_layout_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./card-layout.js */ \"./src/snippets/braze/card-layout.js\");\n/* harmony import */ var _safe_url_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./safe-url.js */ \"./src/snippets/braze/safe-url.js\");\n/* harmony import */ var _product_card_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./product-card.js */ \"./src/snippets/braze/product-card.js\");\n\n\n\n\n\n/**\n * Content Card feed rendering for `[data-braze-content-cards]` containers.\n *\n * Card DOM is built here; the inbox and carousel render the same card pool\n * with their own markup (see snippet.braze-inbox.js / snippet.braze-carousel.js).\n */\n\nconst FEED_SELECTOR = '[data-braze-content-cards]';\n\n/**\n * Builds an <img> element for a content card.\n * @param {string} imageUrl\n * @param {string} [altText]\n * @returns {HTMLImageElement}\n */\nfunction buildImg(imageUrl, altText) {\n  const img = document.createElement('img');\n  if ((0,_safe_url_js__WEBPACK_IMPORTED_MODULE_2__.isSafeUrl)(imageUrl)) {\n    img.src = imageUrl;\n  }\n  img.alt = altText || '';\n  img.className = 'braze-card__image';\n  img.loading = 'lazy';\n  return img;\n}\n\n/**\n * Builds the title/description/link body shared by classic and captioned cards.\n * @param {object} card\n * @param {object} sdk\n * @param {boolean} withDescription\n * @returns {HTMLElement}\n */\nfunction buildCardBody(card, sdk, withDescription) {\n  const body = document.createElement('div');\n  body.className = 'braze-card__body';\n\n  if (card.title) {\n    const h3 = document.createElement('h3');\n    h3.className = 'braze-card__title';\n    h3.textContent = card.title;\n    body.appendChild(h3);\n  }\n\n  if (withDescription && card.description) {\n    const p = document.createElement('p');\n    p.className = 'braze-card__description';\n    p.textContent = card.description;\n    body.appendChild(p);\n  }\n\n  if ((0,_safe_url_js__WEBPACK_IMPORTED_MODULE_2__.isSafeUrl)(card.url)) {\n    const a = document.createElement('a');\n    a.href = card.url;\n    a.className = 'braze-card__link';\n    a.textContent = card.linkText || card.url;\n    a.addEventListener('click', () => sdk.logContentCardClick(card));\n    body.appendChild(a);\n  }\n\n  return body;\n}\n\n/**\n * Wraps an image in a clickable link when the card has a safe URL.\n *\n * @param {HTMLImageElement} img\n * @param {object} card\n * @param {object} sdk\n * @returns {HTMLElement}\n */\nfunction wrapImageLink(img, card, sdk) {\n  if (!(0,_safe_url_js__WEBPACK_IMPORTED_MODULE_2__.isSafeUrl)(card.url)) return img;\n\n  const a = document.createElement('a');\n  a.href = card.url;\n  a.className = 'braze-card__image-link';\n  a.addEventListener('click', () => sdk.logContentCardClick(card));\n  a.appendChild(img);\n  return a;\n}\n\n/**\n * Builds a card with an optional image above a text body.\n * @param {object} card\n * @param {object} sdk\n * @param {string} variantClass\n * @param {boolean} withDescription\n * @returns {HTMLElement}\n */\nfunction buildCardWithBody(card, sdk, variantClass, withDescription) {\n  const article = document.createElement('article');\n  article.className = `braze-card ${variantClass}`;\n\n  if (card.imageUrl) {\n    const img = buildImg(card.imageUrl, card.title);\n    article.appendChild(wrapImageLink(img, card, sdk));\n  }\n\n  article.appendChild(buildCardBody(card, sdk, withDescription));\n\n  return article;\n}\n\n/**\n * Builds an image-only card. The entire card is the link; no dismiss button.\n * @param {object} card\n * @param {object} sdk\n * @returns {HTMLElement}\n */\nfunction buildImageOnlyCard(card, sdk) {\n  const linkable = (0,_safe_url_js__WEBPACK_IMPORTED_MODULE_2__.isSafeUrl)(card.url);\n  const wrapper = document.createElement(linkable ? 'a' : 'div');\n  wrapper.className = 'braze-card braze-card--image-only';\n\n  if (linkable) {\n    wrapper.href = card.url;\n    wrapper.addEventListener('click', () => sdk.logContentCardClick(card));\n  }\n\n  if (card.imageUrl) {\n    wrapper.appendChild(buildImg(card.imageUrl));\n  }\n\n  return wrapper;\n}\n\n/**\n * Builds a card from its own content \u2014 image, copy, link \u2014 whatever its type.\n * @param {object} card\n * @param {object} sdk\n * @returns {HTMLElement}\n */\nfunction buildContentCardEl(card, sdk) {\n  const type = (0,_card_type_js__WEBPACK_IMPORTED_MODULE_0__.getContentCardType)(card, sdk);\n\n  if (type === _card_type_js__WEBPACK_IMPORTED_MODULE_0__.CARD_TYPE.CLASSIC) {\n    return buildCardWithBody(card, sdk, 'braze-card--classic', true);\n  }\n  if (type === _card_type_js__WEBPACK_IMPORTED_MODULE_0__.CARD_TYPE.CAPTIONED_IMAGE) {\n    return buildCardWithBody(card, sdk, 'braze-card--captioned-image', false);\n  }\n\n  return buildImageOnlyCard(card, sdk);\n}\n\n/**\n * Swaps a product placeholder for the card's own content, in place, so the cell\n * keeps its position in the grid.\n *\n * A recommendation card names a product the storefront may not have \u2014 a handle\n * can be stale, or the catalogue feed can disagree with the storefront \u2014 and\n * the card still carries the image and title Braze rendered it with. Showing\n * those beats showing a hole.\n *\n * @param {HTMLElement} placeholder\n * @param {object} card\n * @param {object} sdk\n * @param {IntersectionObserver} impressionObserver\n */\nfunction replaceWithContentCard(placeholder, card, sdk, impressionObserver) {\n  const el = buildContentCardEl(card, sdk);\n  el.dataset.brazeCardId = card.id;\n\n  placeholder.replaceWith(el);\n  // The observer was watching the placeholder, which is now detached and can\n  // never intersect, so the impression has to be re-armed on the new element.\n  impressionObserver.observe(el);\n}\n\n/**\n * Builds the DOM element for any card type, then observes it for impressions.\n * @param {object} card\n * @param {object} sdk\n * @param {IntersectionObserver} impressionObserver\n * @returns {HTMLElement}\n */\nfunction buildCardEl(card, sdk, impressionObserver) {\n  const type = (0,_card_type_js__WEBPACK_IMPORTED_MODULE_0__.getCardType)(card, sdk);\n\n  const el =\n    type === _card_type_js__WEBPACK_IMPORTED_MODULE_0__.CARD_TYPE.PRODUCT\n      ? (0,_product_card_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"])(card, sdk, {\n          onUnavailable: placeholder =>\n            replaceWithContentCard(placeholder, card, sdk, impressionObserver),\n        })\n      : buildContentCardEl(card, sdk);\n\n  el.dataset.brazeCardId = card.id;\n  impressionObserver.observe(el);\n  return el;\n}\n\n/**\n * Filters cards by the extras key/value pair declared on the container via\n * data-braze-extras-key / data-braze-extras-value. Unfiltered when no key.\n * @param {object[]} cards\n * @param {HTMLElement} container\n * @returns {object[]}\n */\nfunction filterByExtras(cards, container) {\n  const key = container.dataset.brazeExtrasKey;\n  const value = container.dataset.brazeExtrasValue;\n  if (!key) return cards;\n\n  return cards.filter(card => (card.extras ?? {})[key] === value);\n}\n\n/**\n * Builds an IntersectionObserver that logs one impression per card, once.\n *\n * @param {object} sdk\n * @param {object[]} visibleCards\n * @param {Set<string>} seenCardIds \u2014 shared across containers\n * @returns {IntersectionObserver}\n */\nfunction createImpressionObserver(sdk, visibleCards, seenCardIds) {\n  const observer = new IntersectionObserver((entries) =>
1 {\n    entries.forEach(entry => {\n      if (!entry.isIntersecting) return;\n\n      const cardId = entry.target.dataset.brazeCardId;\n      if (!cardId || seenCardIds.has(cardId)) return;\n\n      const card = visibleCards.find(c => c.id === cardId);\n      if (card) {\n        sdk.logContentCardImpressions([card]);\n        seenCardIds.add(cardId);\n      }\n\n      observer.unobserve(entry.target);\n    });\n  }, { threshold: 0.5 });\n\n  return observer;\n}\n\n/**\n * Renders one container's filtered subset of the card pool.\n * @param {object[]} cards \u2014 the full pool from the SDK\n * @param {HTMLElement} container\n * @param {object} sdk\n * @param {Set<string>} seenCardIds \u2014 shared across containers\n */\nfunction renderContainerCards(cards, container, sdk, seenCardIds) {\n  container.innerHTML = '';\n\n  const filtered = filterByExtras(cards, container);\n  const visibleCards = filtered.filter(c => !c.dismissed && !c.isControl);\n  const controlCards = filtered.filter(c => c.isControl);\n\n  // Control-group cards are never rendered but must still log an impression so\n  // Braze can measure the holdout.\n  if (controlCards.length) {\n    sdk.logContentCardImpressions(controlCards);\n  }\n\n  if (visibleCards.length === 0) {\n    container.setAttribute('hidden', '');\n    return;\n  }\n\n  container.removeAttribute('hidden');\n\n  const maxDesktop = Number(container.dataset.gridColumnsDesktopMax) || 5;\n  container.style.setProperty(\n    '--braze-grid-columns-desktop',\n    String((0,_card_layout_js__WEBPACK_IMPORTED_MODULE_1__.desktopColumnCount)(visibleCards.length, maxDesktop)),\n  );\n  // Column count sizes the cards; track count centres them when the feed is\n  // too short to fill a row.\n  container.style.setProperty(\n    '--braze-grid-tracks',\n    String((0,_card_layout_js__WEBPACK_IMPORTED_MODULE_1__.desktopTrackCount)(visibleCards.length, maxDesktop)),\n  );\n\n  const impressionObserver = createImpressionObserver(sdk, visibleCards, seenCardIds);\n\n  visibleCards.forEach(card => {\n    container.appendChild(buildCardEl(card, sdk, impressionObserver));\n  });\n}\n\n/**\n * Creates a renderer bound to one SDK instance.\n *\n * `seenCardIds` is shared across all containers so a card appearing in two\n * placements only logs one impression per page session.\n *\n * @param {object} sdk \u2014 the resolved `window.braze`\n * @returns {{ renderContentCards: function(object[]): void }}\n */\nfunction createCardRenderer(sdk) {\n  const seenCardIds = new Set();\n\n  /**\n   * Distributes the card pool across every feed container on the page, then\n   * notifies downstream snippets (inbox, carousel) that fresh cards arrived.\n   * @param {object[]} cards\n   */\n  function renderContentCards(cards) {\n    // Runs inside the SDK's content-cards subscription: one bad container must\n    // not abort the others or skip the event the inbox and carousel listen for.\n    document.querySelectorAll(FEED_SELECTOR).forEach(container => {\n      try {\n        renderContainerCards(cards, container, sdk, seenCardIds);\n      } catch (error) {\n        console.error('[Braze] Could not render a content-card container:', error);\n      }\n    });\n\n    document.dispatchEvent(\n      new CustomEvent('braze:content-cards-updated', { detail: { cards } })\n    );\n  }\n\n  return { renderContentCards };\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/cards.js?\n}")},
1"./src/snippets/braze/identify.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   identifyUser: () => (/* binding */ identifyUser)\n/* harmony export */ });\n/**\n * User identification and locale attributes.\n *\n * The external ID is `sha256(email.trim().toLowerCase())`. Two other places\n * derive the same digest \u2014 `assets/braze-events.js` (for analytics payloads)\n * and, historically, the custom pixel \u2014 so the normalization here must not\n * drift or identified users will fork into separate profiles.\n *\n * The theme never calls `openSession()`. The Braze Shopify Integration app\n * embed owns the session lifecycle.\n */\n\n/** Matches a pre-computed SHA-256 hex digest. */\nconst SHA256_HEX = /^[a-f0-9]{64}$/;\n\n/**\n * Hashes a string with SHA-256 and returns a lowercase hex digest.\n * @param {string} input\n * @returns {Promise<string>}\n */\nasync function sha256Hex(input) {\n  const normalized = String(input).trim().toLowerCase();\n  const data = new TextEncoder().encode(normalized);\n  const hashBuffer = await crypto.subtle.digest('SHA-256', data);\n  return Array.from(new Uint8Array(hashBuffer))\n    .map(b => b.toString(16).padStart(2, '0'))\n    .join('');\n}\n\n/**\n * Validates a value as an already-hashed external ID.\n *\n * This is the only guard between a URL parameter and `changeUser()`, so it\n * must stay strict \u2014 an unvalidated value would let an arbitrary string claim\n * a Braze profile.\n *\n * @param {unknown} value\n * @returns {string|null} the normalized digest, or null if not one\n */\nfunction normalizePrehashedEmail(value) {\n  if (typeof value !== 'string') return null;\n  const normalized = value.trim().toLowerCase();\n  return SHA256_HEX.test(normalized) ? normalized : null;\n}\n\n/**\n * Reads the `eid` external ID from the URL. Set by email campaign links so a\n * recipient is recognized before they log in.\n * @returns {string|null}\n */\nfunction readEidFromUrl() {\n  try {\n    const eid = new URLSearchParams(window.location.search).get('eid');\n    return normalizePrehashedEmail(eid);\n  } catch (error) {\n    console.error('[Braze] Could not read the eid parameter:', error);\n    return null;\n  }\n}\n\n/**\n * Writes the store's language and country onto the current profile.\n *\n * The built-in `language` / `country` fields plus `store_*` custom attributes\n * come from Liquid (`request.locale.iso_code`, `localization.country.iso_code`),\n * which is authoritative. The `shopify_*` custom attributes mirror the naming\n * the retired custom pixel wrote, so existing Braze segments keep resolving.\n *\n * @param {object} sdk\n * @param {{locale?: string, country?: string}} config\n */\nfunction applyLocaleAttributes(sdk, config) {\n  const user = sdk.getUser();\n  if (!user) return;\n\n  const { locale, country } = config;\n\n  if (locale) {\n    user.setLanguage(locale);\n    user.setCustomUserAttribute('store_language', locale);\n    user.setCustomUserAttribute('shopify_language', locale);\n  }\n\n  if (country) {\n    user.setCountry(country);\n    user.setCustomUserAttribute('store_country', country);\n    user.setCustomUserAttribute('shopify_country', country);\n  }\n\n  if (locale && country) {\n    user.setCustomUserAttribute('shopify_locale', `${locale}-${country}`.toLowerCase());\n  }\n}\n\n/**\n * Flushes queued attribute writes immediately when the theme opts in.\n *\n * `openSession()` does not reliably flush queued attribute changes when a\n * session is already active, and the SDK has no page-unload beacon. On a\n * market switch (a full reload) the language/country writes would otherwise\n * sit in the batch queue and be discarded before transmission.\n *\n * @param {object} sdk\n * @param {{immediateFlush?: boolean}} config\n */\nfunction flushIfRequested(sdk, config) {\n  if (config.immediateFlush === false) return;\n  sdk.requestImmediateDataFlush();\n}\n\n/**\n * Resolves the visitor's external ID and applies it.\n *\n * Precedence matches the retired pixel: an `eid` from an email campaign wins\n * over the logged-in customer's email, because a recipient may open a campaign\n * link while logged into a different account.\n *\n * @param {object} sdk\n * @returns {Promise<boolean>} whether the visitor was identified\n */\nasync function applyExternalId(sdk) {\n  const eid = readEidFromUrl();\n\n  if (eid) {\n    sdk.changeUser(eid);\n    sdk.getUser()?.setCustomUserAttribute('last_eid', eid);\n    sdk.logCustomEvent('user_identified', { source: 'eid' });\n    return true;\n  }\n\n  if (window.ardeneCustomerEmail) {\n    sdk.changeUser(await sha256Hex(window.ardeneCustomerEmail));\n    return true;\n  }\n\n  return false;\n}\n\n/**\n * Identifies the visitor and stamps locale attributes.\n *\n * Anonymous visitors are not identified but still get locale attributes \u2014 the\n * integration initializes \"upon site visit\", so an anonymous profile exists.\n *\n * The two stages are wrapped separately on purpose: identification can fail on\n * its own (`crypto.subtle` is unavailable over plain HTTP and in older\n * WebViews) and the locale attributes must still be written exactly once\n * either way.\n *\n * @param {object} sdk \u2014 the resolved `window.braze`\n * @param {object} config \u2014 window.BRAZE_CONFIG\n */\nasync function identifyUser(sdk, config) {\n  try {\n    await applyExternalId(sdk);\n  } catch (error) {\n    console.error('[Braze] Identification failed; continuing anonymously:', error);\n  }\n\n  try {\n    applyLocaleAttributes(sdk, config);\n    flushIfRequested(sdk, config);\n    sdk.requestContentCardsRefresh();\n  } catch (error) {\n    console.error('[Braze] Could not apply locale attributes:', error);\n  }\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/identify.js?\n}")},
1"./src/snippets/braze/in-app-messages.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   subscribeToInAppMessages: () => (/* binding */ subscribeToInAppMessages)\n/* harmony export */ });\n/**\n * In-app message handling, plus the compensation layer for the initialization\n * options the theme can no longer pass.\n *\n * The Braze Shopify Integration owns `initialize()`, and it exposes no UI for\n * custom initialization options. Three options the theme used to set\n * deliberately therefore revert to Braze's defaults, and all three defaults\n * are wrong for this storefront:\n *\n *   inAppMessageZIndex                   1499  \u2192  9001/9011/9021  (CSS, see snippet.braze.css)\n *   requireExplicitInAppMessageDismissal true  \u2192  false           (guardDismissal below)\n *   openInAppMessagesInNewTab            true  \u2192  false           (forceNewTabLinks below)\n *\n * A fourth, `allowUserSuppliedJavascript`, has no client-side equivalent \u2014\n * there is no post-initialization setter and no per-message override. If the\n * embed initializes with the default, JavaScript inside HTML in-app messages\n * will not execute. See BRAZE_SDK_POC.md \xA78.\n */\n\n/**\n * Overlay nodes we have installed the dismissal guard for.\n * A WeakSet keyed on the node avoids depending on Braze's CSS class names,\n * which are not a stable contract.\n * @type {WeakSet<Element>}\n */\nconst guardedOverlays = new WeakSet();\n\nlet dismissalGuardInstalled = false;\n\n/**\n * Installs a single document-level capture listener that swallows clicks\n * landing directly on a Braze overlay.\n *\n * Capture at `document` runs before any listener attached to the overlay\n * itself, which is what lets it preempt Braze's own click-outside dismissal.\n * Registering on the overlay would be too late \u2014 Braze registers first.\n */\nfunction installDismissalGuard() {\n  if (dismissalGuardInstalled) return;\n  dismissalGuardInstalled = true;\n\n  document.addEventListener(\n    'click',\n    (event) => {\n      // Only a click on the overlay itself is a click-outside. Anything inside\n      // the message has a descendant as its target and must pass through.\n      if (!guardedOverlays.has(event.target)) return;\n\n      event.preventDefault();\n      event.stopPropagation();\n    },\n    true\n  );\n}\n\n/**\n * Reproduces `requireExplicitInAppMessageDismissal: true` for one message.\n *\n * The overlay is the rendered message's parent. If the SDK's DOM shape ever\n * changes so that it is not, this degrades to Braze's default (click-outside\n * dismisses) rather than breaking the message.\n *\n * @param {Element|null|undefined} messageNode \u2014 node handed back by showInAppMessage\n */\nfunction guardDismissal(messageNode) {\n  const overlay = messageNode?.parentElement;\n  if (!overlay) return;\n\n  installDismissalGuard();\n  guardedOverlays.add(overlay);\n}\n\n/**\n * Reproduces `openInAppMessagesInNewTab: true` at the DOM level.\n *\n * Applied in addition to the SDK-level `openTarget` hints below, because the\n * embed's SDK version is Braze's choice and the enum may not be exposed.\n * Rewriting the anchors is version-independent.\n *\n * @param {Element|null|undefined} messageNode\n */\nfunction forceNewTabLinks(messageNode) {\n  if (!messageNode?.querySelectorAll) return;\n\n  messageNode.querySelectorAll('a[href]').forEach((anchor) => {\n    anchor.target = '_blank';\n    const rel = new Set((anchor.rel || '').split(/\\s+/).filter(Boolean));\n    rel.add('noopener');\n    anchor.rel = [...rel].join(' ');\n  });\n}\n\n/**\n * Sets the SDK-level open target on a message and its buttons, when the SDK\n * exposes the enum.\n * @param {object} message\n * @param {object} sdk\n */\nfunction applyOpenTarget(message, sdk) {\n  const blank = sdk.InAppMessage?.OpenTarget?.BLANK;\n  if (blank === undefined) return;\n\n  // These are SDK-owned instances that Braze reads back after we return, so\n  // they must be mutated in place \u2014 a copy would not reach the renderer.\n  message.openTarget = blank;
1\n  (message.buttons ?? []).forEach((button) => {\n    button.openTarget = blank;\n  });\n}\n\n/**\n * Shows a message and applies every post-render compensation to it.\n * @param {object} message\n * @param {object} sdk\n */\nfunction showWithCompensations(message, sdk) {\n  applyOpenTarget(message, sdk);\n\n  sdk.showInAppMessage(message, undefined, (messageNode) => {\n    // Older SDK builds invoke the callback with no argument; fall back to the\n    // message's own element reference if the SDK exposes one.\n    const node = messageNode ?? message.element ?? null;\n    guardDismissal(node);\n    forceNewTabLinks(node);\n  });\n}\n\n/**\n * Subscribes to in-app messages and renders them with the compensation layer.\n * @param {object} sdk \u2014 the resolved `window.braze`\n */\nfunction subscribeToInAppMessages(sdk) {\n  sdk.subscribeToInAppMessage((message) => {\n    // Control-group messages must still be \"shown\" so Braze logs the\n    // server-side impression, but they render no UI \u2014 so no compensation.\n    if (message.isControl) {\n      sdk.showInAppMessage(message);\n      return;\n    }\n\n    // Optional `show_on` extra set in the Braze dashboard restricts a message\n    // to one path, e.g. { \"show_on\": \"collections/all\" }. Unset shows\n    // everywhere.\n    const showOn = (message.extras ?? {})['show_on'];\n    if (showOn && window.location.pathname !== `/${showOn}`) return;\n\n    showWithCompensations(message, sdk);\n  });\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/in-app-messages.js?\n}")},
1"./src/snippets/braze/product-card.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   buildProductCardFromHandle: () => (/* binding */ buildProductCardFromHandle),\n/* harmony export */   \"default\": () => (/* binding */ buildProductCard)\n/* harmony export */ });\n/* harmony import */ var _sections_api_service_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../sections-api-service.js */ \"./src/sections-api-service.js\");\n/* harmony import */ var _constants_events_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../../constants/events.js */ \"./src/constants/events.js\");\n/* harmony import */ var _product_handle_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./product-handle.js */ \"./src/snippets/braze/product-handle.js\");\n\n\n\n\n/**\n * Product cards for content cards that name a product.\n *\n * Only the handle is read from the card \u2014 never a title, image or price it may\n * also carry. The storefront renders the product through its own `product-card`\n * section, so market pricing, badges, swatches and locale stay correct and\n * cannot go stale against whatever the campaign was authored with.\n */\n\nconst PRODUCT_CARD_SECTION = 'product-card';\nconst LOADING_CLASS = 'braze-card--loading';\n\n/** Module URLs already re-created from a fetched card. */\nconst activatedScripts = new Set();\n\n/**\n * Root-relative product URL for the active market, so the section renders in\n * the shopper's locale and currency.\n *\n * @param {string} handle\n * @returns {string}\n */\nfunction productUrl(handle) {\n  const root = window.Shopify?.routes?.root || window.routes?.root_url || '/';\n  const normalizedRoot = root.replace(/\\/?$/, '/');\n\n  return new URL(\n    `${normalizedRoot}products/${encodeURIComponent(handle)}`,\n    window.location.origin,\n  ).toString();\n}\n\n/**\n * The section answers 200 even for a handle that resolves to no product: the\n * card comes back as a shell whose product-form carries `data-product=\"null\"`\n * and a Liquid error where the product should be. Appending that would show an\n * empty tile, so it counts as unavailable.\n *\n * @param {HTMLElement} el\n * @returns {boolean}\n */\nfunction isUnresolvedProduct(el) {\n  return Boolean(el.querySelector?.('product-form[data-product=\"null\"]'));\n}\n\n/**\n * @param {string} html - Section markup from our own storefront\n * @returns {HTMLElement | null}\n */\nfunction parseCard(html) {\n  const holder = document.createElement('div');\n  holder.innerHTML = String(html).trim();\n\n  return holder.firstElementChild;\n}\n\n/**\n * Scripts inserted through innerHTML never execute, so the module tags the\n * card snippet ships with \u2014 price, media gallery, colour swatches \u2014 arrive\n * inert and the card would render as static markup with a Liquid-rendered\n * price. Re-creating each tag is what makes the browser run it and upgrade the\n * card's custom elements. A module URL evaluates once per document, so the\n * second card pays nothing.\n *\n * @param {HTMLElement} root\n */\nfunction activateScripts(root) {\n  root.querySelectorAll('script[src]').forEach((inert) => {\n    const { src } = inert;\n    inert.remove();\n\n    if (!src || activatedScripts.has(src)) return;\n    activatedScripts.add(src);\n\n    const script = document.createElement('script');\n    script.type = inert.type || 'module';\n    script.src = src;\n    document.head.appendChild(script);\n  });\n}\n\n/**\n * Fetches the product card and fills the placeholder, or drops the placeholder\n * when the product cannot be rendered.\n *\n * @param {HTMLElement} wrapper\n * @param {string} handle\n * @param {function(HTMLElement): void} onUnavailable \u2014 receives the placeholder\n */\nasync function fillProductCard(wrapper, handle, onUnavailable) {\n  try {\n    const html = await _sections_api_service_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].request(productUrl(handle), PRODUCT_CARD_SECTION, {\n      cache: true,\n    });\n\n    // A fresh card feed may have re-rendered the container while this was in\n    // flight; `innerHTML = ''` detaches the placeholder, so there is nothing\n    // left to fill and the new render owns the cell.\n    if (!wrapper.isConnected) return;\n\n    const productCard = parseCard(html);\n    if (!productCard) throw new Error(`Empty product-card section for \"${handle}\"`);\n    if (isUnresolvedProduct(productCard)) {\n      throw new Error(`No product on this storefront for \"${handle}\"`);\n    }\n\n    wrapper.classList.remove(LOADING_CLASS);\n    wrapper.appendChild(productCard);\n    activateScripts(wrapper);
1\n\n    // Swym binds its wishlist buttons on this event, exactly as the\n    // recommendation rails announce their own AJAX-rendered cards.\n    document.dispatchEvent(\n      new CustomEvent(_constants_events_js__WEBPACK_IMPORTED_MODULE_1__.EVENTS.SWYM_COLLECTIONS_LOADED, {\n        detail: { appendedElements: [wrapper] },\n      }),\n    );\n  } catch (error) {\n    // Not an error state for the shopper: the card still has its own image and\n    // copy, and the caller swaps those in. Logged because a storefront that\n    // cannot resolve its own recommendation handles is worth knowing about.\n    console.warn(\n      `[Braze] No product for \"${handle}\" \u2014 falling back to the card's own content.`,\n      error,\n    );\n\n    onUnavailable(wrapper);\n  }\n}\n\n/**\n * Builds the placeholder for a product handle and starts filling it.\n *\n * The element is returned synchronously so the feed keeps its one-element-per-\n * card contract and the impression observer has something to watch; the\n * storefront fetch lands into it afterwards.\n *\n * @param {string} handle\n * @param {{ onUnavailable?: function(HTMLElement): void, onClick?: function(): void }} [options]\n * @returns {HTMLElement}\n */\nfunction buildProductCardFromHandle(handle, options = {}) {\n  // Default to dropping the cell, so the module stands on its own; the feed\n  // passes a callback that swaps in the card's own content instead.\n  const { onUnavailable = (placeholder) => placeholder.remove(), onClick } = options;\n\n  const wrapper = document.createElement('div');\n  wrapper.className = `braze-card braze-card--product ${LOADING_CLASS}`;\n\n  // The whole cell is the click target: the product card brings its own links,\n  // so there is no single anchor of ours to bind.\n  if (onClick) wrapper.addEventListener('click', onClick, { once: true });\n\n  fillProductCard(wrapper, handle, onUnavailable);\n\n  return wrapper;\n}\n\nfunction buildProductCard(card, sdk, options = {}) {\n  return buildProductCardFromHandle((0,_product_handle_js__WEBPACK_IMPORTED_MODULE_2__.productHandleFromCard)(card), {\n    ...options,\n    onClick: () => sdk.logContentCardClick(card),\n  });\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/product-card.js?\n}")},
1"./src/snippets/braze/product-handle.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   HANDLE_EXTRA_KEYS: () => (/* binding */ HANDLE_EXTRA_KEYS),\n/* harmony export */   hasProductHandle: () => (/* binding */ hasProductHandle),\n/* harmony export */   productHandleFromCard: () => (/* binding */ productHandleFromCard)\n/* harmony export */ });\n/* harmony import */ var _util_urlParser_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../../util/urlParser.js */ \"./src/util/urlParser.js\");\n\n\n/**\n * Product handle resolution for content cards.\n *\n * A card names a product either through a key-value pair or, failing that,\n * through its click URL. The URL is what the Braze AI recommendation campaigns\n * actually carry \u2014 those cards are generated from the catalogue and their only\n * extras entry is `placement` \u2014 so reading it is the difference between the\n * feature working and never firing at all.\n *\n * The cost of reading the URL: a promo card that happens to link to a product\n * page renders as that product's card rather than its own artwork. If a\n * campaign ever needs that, give the card a key-value pair \u2014 extras win over\n * the URL \u2014 or scope the behaviour per section.\n */\n\n/** Key-value pairs read for a product handle, in priority order. */\nconst HANDLE_EXTRA_KEYS = ['product_handle', 'product_handles'];\n\n/**\n * Handles are a single URL path segment. A value carrying whitespace or URL\n * punctuation is a mis-configured card rather than a handle, and would only\n * produce a doomed section request.\n *\n * @param {unknown} handle\n * @returns {boolean}\n */\nconst isUsableHandle = (handle) =>\n  typeof handle === 'string' && handle.length > 0 && !/[\\s/?#]/.test(handle);\n\n/**\n * The handle in a card's click URL, or '' when it does not link to a product.\n *\n * Locale prefixes are stripped by the shared parser (`/en-ca/products/x` and\n * `/products/x` both yield `x`), and the segment is decoded because Braze\n * authors these URLs with raw characters that `URL` percent-encodes.\n *\n * @param {string} url\n * @returns {string}\n */\nfunction handleFromUrl(url) {\n  if (typeof url !== 'string' || !url.trim()) return '';\n\n  const handle = (0,_util_urlParser_js__WEBPACK_IMPORTED_MODULE_0__.extractProductHandle)(url);\n  if (!handle) return '';\n\n  try {\n    return decodeURIComponent(handle);\n  } catch {\n    return handle;\n  }\n}\n\n/**\n * The first usable handle a card names, or '' when it names none.\n *\n * One grid cell renders one product, so a key holding a comma-separated list\n * contributes its first entry and nothing else \u2014 a list of handles belongs to\n * the recommendation rails, which have somewhere to put them.\n *\n * @param {object} card\n * @param {string[]} [handleKeys]\n * @returns {string}\n */\nfunction productHandleFromCard(card, handleKeys = HANDLE_EXTRA_KEYS) {\n  const extras = card?.extras || {};\n\n  const fromExtras = handleKeys\n    .flatMap((key) => (typeof extras[key] === 'string' ? extras[key].split(',') : []))\n    .map((handle) => handle.trim())\n    .find(isUsableHandle);\n\n  if (fromExtras) return fromExtras;\n\n  const fromUrl = handleFromUrl(card?.url);\n\n  return isUsableHandle(fromUrl) ? fromUrl : '';\n}\n\n/**\n * The flag that decides how a card renders: a card naming a product renders as\n * a storefront product card, a card naming none renders from its own content.\n *\n * @param {object} card\n * @param {string[]} [handleKeys]\n * @returns {boolean}\n */\nfunction hasProductHandle(card, handleKeys) {\n  return productHandleFromCard(card, handleKeys) !== '';\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/product-handle.js?\n}")},
1"./src/snippets/braze/ready.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   flushNow: () => (/* binding */ flushNow),\n/* harmony export */   getSdk: () => (/* binding */ getSdk),\n/* harmony export */   run: () => (/* binding */ run),\n/* harmony export */   start: () => (/* binding */ start),\n/* harmony export */   whenReady: () => (/* binding */ whenReady)\n/* harmony export */ });\n/**\n * Braze SDK readiness gateway.\n *\n * The Braze Shopify Integration app embed owns the SDK lifecycle \u2014 it calls\n * `initialize()` and `openSession()` itself. The theme must never do either;\n * it only consumes the instance the embed publishes as `window.braze`.\n *\n * The embed injects through `{{ content_for_header }}`, which sits *after*\n * this snippet's script tag in `layout/theme.liquid`, and it loads its bundle\n * asynchronously. So `window.braze` is not guaranteed to exist \u2014 let alone be\n * initialized \u2014 when this module evaluates. Everything therefore goes through\n * `run()`, which invokes immediately once the SDK is ready and queues until\n * then.\n *\n * This module owns the only readiness state in the theme. Other bundles\n * (carousel, inbox, analytics) reach the SDK through `window.BRAZE_SDK`\n * rather than importing this file, because webpack inlines a separate copy of\n * every shared module into each entry \u2014 two copies here would mean two\n * independent queues.\n */\n\nconst POLL_INTERVAL_MS = 250;\nconst MAX_ATTEMPTS = 40; // ~10s ceiling, then give up quietly.\n\n/** @type {object|null} The resolved, initialized SDK \u2014 null until ready. */\nlet sdk = null;\n\n/** @type {Array<function(object): void>} Calls made before the SDK was ready. */\nlet pending = [];\n\n/** @type {Array<function(object): void>} Callbacks for whenReady(). */\nlet readyWaiters = [];\n\n/** True once the poll ceiling was reached without the SDK appearing. */\nlet gaveUp = false;\n\n/**\n * Returns the SDK only once it is genuinely initialized.\n *\n * Presence is not readiness: `getUser()` returns undefined until the embed has\n * called `initialize()`, and calling anything before that point is a silent\n * no-op. Testing `getUser()` is the documented readiness signal.\n *\n * @returns {object|null}\n */\nfunction resolveSdk() {\n  const candidate = window.braze;\n  if (!candidate || typeof candidate.getUser !== 'function') return null;\n\n  try {\n    return candidate.getUser() ? candidate : null;\n  } catch {\n    // getUser() throws on some pre-initialization states.\n    return null;\n  }\n}\n\n/**\n * Invokes one queued call, isolating its failure from the rest of the queue.\n * @param {function(object): void} fn\n */\nfunction invoke(fn) {\n  try {\n    fn(sdk);\n  } catch (error) {\n    console.error('[Braze] SDK call failed:', error);\n  }\n}\n\n/** Drains the pending queue in the order calls were made. */\nfunction flush() {\n  const queued = pending;\n  pending = [];\n  queued.forEach(invoke);\n\n  const waiters = readyWaiters;\n  readyWaiters = [];\n  waiters.forEach(invoke);\n}\n\n/**\n * Polls for the SDK until it resolves or the attempt ceiling is reached.\n * Discards the queue on timeout so it cannot leak for the page's lifetime.\n */\nfunction startPolling() {\n  let attempts = 0;\n\n  const timer = setInterval(() => {\n    attempts += 1;\n    sdk = resolveSdk();\n\n    if (sdk) {\n      clearInterval(timer);\n      flush();\n      return;\n    }\n\n    if (attempts >= MAX_ATTEMPTS) {\n      clearInterval(timer);\n      gaveUp = true;\n      pending = [];\n      readyWaiters = [];\n      console.error(\n        '[Braze] SDK never became ready \u2014 is the Braze app embed enabled on this theme?'\n      );\n    }\n  }, POLL_INTERVAL_MS);\n}\n\n/**\n * Runs `fn` with the SDK, immediately if ready, otherwise when it becomes\n * ready. Use for every side-effecting SDK call.\n * @param {function(object): void} fn\n */\nfunction run(fn) {\n  // The embed may still have arrived after the poll gave up.\n  if (!sdk && gaveUp) sdk = resolveSdk();\n\n  if (sdk) {\n    invoke(fn);\n    return;\n  }\n\n  // Queueing after we have stopped polling would leak for the page's lifetime.\n  if (gaveUp) return;\n\n  pending = [...pending, fn];\n}\n\n/**\n * Like {@link run}, but for one-time setup that must happen exactly once the\n * SDK is ready (subscriptions, identification, the compensation layer).\n * @param {function(object): void} fn\n */\nfunction whenReady(fn) {\n  if (!sdk && gaveUp) sdk = resolveSdk();\n\n  if (sdk) {\n    invoke(fn);\n    return;\n  }\n\n  if (gaveUp) return;\n\n  readyWaiters = [...readyWaiters, fn];\n}\n\n/**\n * Returns the SDK if it is ready, otherwise null. Use for calls that need a\n * return value and cannot be queued.\n * @returns {object|null}\n */\nfunction getSdk() {\n  return sdk;\n}\n\n/**\n * Drains the queue and forces the SDK to transmit. Call before navigating.\n *\n * There are two independent ways an event can be lost on unload, and this\n * closes both:\n *\n *   1. It is still in `pending`, because the poll interval has not fired yet.\n *      Draining hands it to the SDK.\n *   2. The SDK has accepted it but batches its network requests, so it may\n *      still be unsent. `requestImmediateDataFlush()` puts it on the wire.\n *\n * Step 2 is the one that bites `begin_checkout` in `assets/analytics.js`: it\n * fires and then immediately navigates to checkout, so the batch window never\n * elapses. Every other tracked event fires mid-browse, where the SDK's own\n * periodic flush catches it.\n *\n * Unlike the attribute flush in `identify.js`, this is deliberately *not*\n * gated on `BRAZE_CONFIG.immediateFlush`. That setting trades request volume\n * against delivery latency; here the alternative to flushing is silent data\n * loss, and it costs at most one request per checkout click.\n */\nfunction flushNow() {\n  if (!sdk) {\n    sdk = resolveSdk();\n    if (sdk) flush();\n  }\n\n  if (!sdk) return;
1\n\n  try {\n    // The app embed chooses the SDK version, so the method is not guaranteed.\n    sdk.requestImmediateDataFlush?.();\n  } catch (error) {\n    // A failed flush must never block the navigation that triggered it.\n    console.error('[Braze] Immediate data flush failed:', error);\n  }\n}\n\n/** Begins resolving the SDK. Safe to call once, at module init. */\nfunction start() {\n  sdk = resolveSdk();\n  if (sdk) {\n    flush();\n    return;\n  }\n  startPolling();\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/ready.js?\n}")},
1"./src/snippets/braze/safe-url.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   isSafeUrl: () => (/* binding */ isSafeUrl)\n/* harmony export */ });\n/**\n * URL scheme validation for Braze-authored content.\n *\n * Card and banner URLs are authored in the Braze dashboard, not by the theme.\n * Assigning one straight to `element.href` lets a `javascript:` URL execute in\n * the storefront origin the moment a shopper clicks the card. `data:` and\n * `vbscript:` are the same class of problem.\n *\n * Braze's own `allowUserSuppliedJavascript` initialization flag exists to stop\n * exactly this, which is the clearest signal that dashboard-authored content is\n * not to be trusted with a raw href.\n */\n\n/** Absolute http(s) \u2014 the only remote schemes a card may link to. */\nconst ABSOLUTE_HTTP = /^https?:\\/\\//i;\n\n/**\n * Returns true when a URL is safe to assign to an href or src.\n *\n * Accepts absolute http(s) and root-relative paths. Protocol-relative URLs\n * (`//host`) are rejected: they are absolute links to another origin wearing a\n * relative disguise.\n *\n * @param {unknown} url\n * @returns {boolean}\n */\nfunction isSafeUrl(url) {\n  if (typeof url !== 'string') return false;\n\n  const trimmed = url.trim();\n  if (!trimmed) return false;\n\n  if (trimmed.startsWith('//')) return false;\n  if (trimmed.startsWith('/')) return true;\n\n  return ABSOLUTE_HTTP.test(trimmed);\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/braze/safe-url.js?\n}")},
1"./src/snippets/snippet.braze.css":function(__unused_webpack_module,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n// extracted by mini-css-extract-plugin\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/snippet.braze.css?\n}")},
1"./src/snippets/snippet.braze.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval('{__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _snippet_braze_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./snippet.braze.css */ "./src/snippets/snippet.braze.css");\n/* harmony import */ var _braze_ready_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./braze/ready.js */ "./src/snippets/braze/ready.js");\n/* harmony import */ var _braze_cards_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./braze/cards.js */ "./src/snippets/braze/cards.js");\n/* harmony import */ var _braze_in_app_messages_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./braze/in-app-messages.js */ "./src/snippets/braze/in-app-messages.js");\n/* harmony import */ var _braze_banners_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./braze/banners.js */ "./src/snippets/braze/banners.js");\n/* harmony import */ var _braze_identify_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./braze/identify.js */ "./src/snippets/braze/identify.js");\n\n\n\n\n\n\n\n/**\n * Braze theme integration.\n *\n * The Braze Shopify Integration app embed is the single SDK instance on the\n * storefront: it calls `initialize()` and `openSession()` and owns the API key.\n * This snippet only *consumes* `window.braze`. It must never initialize or\n * open a session \u2014 doing so is what produced duplicate anonymous profiles and\n * inflated MAU. See BRAZE_SDK_POC.md.\n *\n * Because the embed injects through `{{ content_for_header }}` \u2014 after this\n * script tag in layout/theme.liquid \u2014 every SDK call goes through the\n * readiness gateway in ./braze/ready.js rather than touching `window.braze`\n * directly.\n */\n\nconst config = window.BRAZE_CONFIG ?? {};\n\n/**\n * SDK facade for other bundles.\n *\n * Webpack inlines a separate copy of every shared module into each entry, so\n * the inbox, carousel and analytics bundles cannot import the readiness\n * gateway without getting their own independent queue. They go through this\n * global instead, which keeps readiness state in exactly one place.\n */\nwindow.BRAZE_SDK = {\n  // Fire-and-forget calls \u2014 queued until the SDK is ready.\n  logContentCardImpressions: cards => (0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.run)(sdk => sdk.logContentCardImpressions(cards)),\n  logContentCardClick: card => (0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.run)(sdk => sdk.logContentCardClick(card)),\n  logCardDismissal: card => (0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.run)(sdk => sdk.logCardDismissal(card)),\n  requestContentCardsRefresh: () => (0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.run)(sdk => sdk.requestContentCardsRefresh()),\n  logCustomEvent: (name, payload) => (0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.run)(sdk => sdk.logCustomEvent(name, payload)),\n\n  // Calls that need a return value cannot be queued \u2014 they report null until\n  // the SDK is ready, and callers re-render on braze:content-cards-updated.\n  getCachedContentCards: () => (0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.getSdk)()?.getCachedContentCards() ?? null,\n  getLanguage: () => (0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.getSdk)()?.getUser()?.getLanguage() ?? null,\n\n  // Card type resolution needs the SDK\'s card classes for an instanceof check.\n  getSdk: _braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.getSdk,\n\n  /**\n   * Drains the queue and forces the SDK to transmit. Callers that trigger\n   * navigation must call this first (see begin_checkout in\n   * assets/braze-events.js): a queued event is lost silently on unload, and\n   * so is one the SDK has accepted but not yet sent from its request batch.\n   */\n  flushBeforeNavigation: _braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.flushNow,\n};\n\n(0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.whenReady)((sdk) => {\n  // `enableLogging` is an initialization option the integration does not\n  // expose, but toggleLogging() is a post-initialization function, so this one\n  // is fully recoverable.\n  if (config.enableLogging && typeof sdk.toggleLogging === \'function\') {\n    sdk.toggleLogging();\n  }\n\n  (0,_braze_in_app_messages_js__WEBPACK_IMPORTED_MODULE_3__.subscribeToInAppMessages)(sdk);\n\n  const { renderContentCards } = (0,_braze_cards_js__WEBPACK_IMPORTED_MODULE_2__.createCardRenderer)(sdk);\n\n  // Render cards already cached from a previous session immediately, before\n  // the network response arrives.\n  const cached = sdk.getCachedContentCards();\n  if (cached?.cards?.length) {\n    renderContentCards(cached.cards);\n  }\n\n  // Re-render whenever the SDK delivers fresh cards (fires on session start).\n  sdk.subscribeToContentCardsUpdates(updates => renderContentCards(updates.cards));\n\n  (0,_braze_banners_js__WEBPACK_IMPORTED_MODULE_4__.initBanners)(sdk);\n\n  void (0,_braze_identify_js__WEBPACK_IMPORTED_MODULE_5__.identifyUser)(sdk, config);\n});\n\n// Refresh content cards whenever the minicart drawer opens so the\n// `braze-banner` block the minicart offers renders against fresh cards.\nconst minicartEl = document.getElementById(\'minicart\');\nif (minicartEl) {\n  minicartEl.addEventListener(\'drawer:opened\', () => {\n    (0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.run)(sdk => sdk.requestContentCardsRefresh());\n  });\n}\n\n(0,_braze_ready_js__WEBPACK_IMPORTED_MODULE_1__.start)();\n\n\n//# sourceURL=webpack://ardene-theme/./src/snippets/snippet.braze.js?\n}')},
1"./src/util/urlParser.js":function(__unused_webpack___webpack_module__,__webpack_exports__,__webpack_require__){eval("{__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */   buildLocalizedPathname: () => (/* binding */ buildLocalizedPathname),\n/* harmony export */   extractProductHandle: () => (/* binding */ extractProductHandle),\n/* harmony export */   getCurrentCollectionUrl: () => (/* binding */ getCurrentCollectionUrl),\n/* harmony export */   parseLocalizedPathname: () => (/* binding */ parseLocalizedPathname)\n/* harmony export */ });\n/**\n * Utility functions for parsing URLs with localization support\n */\n\n/**\n * Parses a URL pathname and extracts language prefix and path segments\n * @param {string} pathname - The URL pathname to parse\n * @returns {Object} Object containing language prefix and path segments\n */\nfunction parseLocalizedPathname(pathname = '') {\n  const segments = pathname.split('/').filter(Boolean);\n\n  // Check if first segment is a language/locale code (e.g. en, fr, en-ca, en-us, fr-ca)\n  const hasLanguagePrefix =\n    segments.length > 0 && /^[a-z]{2}(-[a-z]{2})?$/i.test(segments[0]);\n\n  return {\n    languagePrefix: hasLanguagePrefix ? segments[0] : '',\n    pathSegments: hasLanguagePrefix ? segments.slice(1) : segments,\n    hasLanguagePrefix,\n  };\n}\n\n/**\n * Reconstructs a URL pathname with optional language prefix\n * @param {string} languagePrefix - The language prefix (e.g., 'fr')\n * @param {string[]} pathSegments - Array of path segments\n * @returns {string} The reconstructed pathname\n */\nfunction buildLocalizedPathname(languagePrefix = '', pathSegments = []) {\n  return [languagePrefix, ...pathSegments].filter(Boolean).join('/');\n}\n\n/**\n * Extracts product handle from a product URL with localization support\n * @param {string} productUrl - The product URL\n * @returns {string|null} The product handle or null if not found\n */\nfunction extractProductHandle(productUrl) {\n  try {\n    const url = new URL(productUrl, window.location.origin);\n    const { pathSegments } = parseLocalizedPathname(url.pathname);\n\n    const productsIndex = pathSegments.findIndex((segment) => segment === 'products');\n    if (productsIndex !== -1 && pathSegments[productsIndex + 1]) {\n      return pathSegments[productsIndex + 1];\n    }\n  } catch (error) {\n    console.warn('Error extracting product handle:', error);\n  }\n\n  return null;\n}\n\n/**\n * Gets the current collection URL from the window location with localization support\n * @returns {string|null} The collection URL or null if not a collection page\n */\nfunction getCurrentCollectionUrl() {\n  const { languagePrefix, pathSegments } = parseLocalizedPathname(window.location.pathname);\n\n  const collectionsIndex = pathSegments.findIndex((segment) => segment === 'collections');\n  if (collectionsIndex !== -1 && pathSegments[collectionsIndex + 1]) {\n    return buildLocalizedPathname(languagePrefix, [\n      'collections',\n      pathSegments[collectionsIndex + 1],\n    ]);\n  }\n\n  return null;\n}\n\n\n//# sourceURL=webpack://ardene-theme/./src/util/urlParser.js?\n}")}
1},__webpack_module_cache__={};function __webpack_require__(moduleId){var cachedModule=__webpack_module_cache__[moduleId];if(cachedModule!==void 0)return cachedModule.exports;var module=__webpack_module_cache__[moduleId]={exports:{}};return __webpack_modules__[moduleId](module,module.exports,__webpack_require__),module.exports}(function(){__webpack_require__.d=function(exports,definition){for(var key in definition)__webpack_require__.o(definition,key)&&!__webpack_require__.o(exports,key)&&Object.defineProperty(exports,key,{enumerable:!0,get:definition[key]})}})(),(function(){__webpack_require__.o=function(obj,prop){return Object.prototype.hasOwnProperty.call(obj,prop)}})(),(function(){__webpack_require__.r=function(exports){typeof Symbol!="undefined"&&Symbol.toStringTag&&Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(exports,"__esModule",{value:!0})}})(),__webpack_require__("./src/snippets/snippet.braze.js");var __webpack_exports__=__webpack_require__("./src/snippets/snippet.braze.css")})();
2//# sourceMappingURL=/cdn/shop/t/233/assets/snippet.braze.js.map?v=151675626790986278851790195638

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.