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.