{"version":3,"file":"banner-section.min.js","mappings":"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","sources":["webpack://lange/./sections/banner/banner-element/index.ts","webpack://lange/./lib/perf.ts","webpack://lange/./lib/growthbook-feature.ts","webpack://lange/./lib/dom-ready.ts","webpack://lange/./sections/banner/banner-experiments.ts"],"sourcesContent":["import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders generic Lange full width banner with title, copy, cta.\n */\nclass BannerElement extends HTMLElement implements CustomElement {\n  declare readonly dataset: {\n    /**\n     * Displays banner copy (subtitle)\n     */\n    copy: string;\n\n    /**\n     * Cta button hash, if provided cta href is set to hash value, overrides ctaUrl attribute\n     */\n    ctaHash: string;\n\n    /**\n     * Cta button name\n     */\n    ctaText: string;\n\n    /**\n     * Cta button url redirect\n     */\n    ctaUrl: string;\n\n    /**\n     * Displays additional banner disclaimer text\n     */\n    disclaimer: string;\n\n    /**\n     * Boolean indicating whether the whole banner area is a cta or not\n     */\n    fullBannerIsCta: string;\n\n    /**\n     * Define heading level. Default is <h2>.\n     */\n    headingLevel: 'h1' | 'h2';\n\n    /**\n     * Flag to remove any bottom margin when required for the UX\n     */\n    noBottomMargin: 'false' | 'true';\n\n    /**\n     * Displays promo code on the banner\n     */\n    promoCode: string;\n\n    /**\n     * Banner text color represented as hex excluding the cta and promo code\n     */\n    textColor: string;\n\n    /**\n     * Displays banner title\n     */\n    title: string;\n  };\n\n  private onBannerClickedBound = this.onBannerClicked.bind(this);\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n    this.addEventListener('click', this.onBannerClickedBound);\n    this.render();\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  public disconnectedCallback() {\n    this.removeEventListener('click', this.onBannerClickedBound);\n  }\n\n  private onBannerClicked(_event: MouseEvent) {\n    if (this.dataset.fullBannerIsCta === 'true') {\n      location.href = this.dataset.ctaUrl;\n    }\n  }\n\n  private render() {\n    const disclaimerWrapper = this.shadowRoot.querySelector<HTMLDivElement>('.disclaimer');\n    const disclaimer = disclaimerWrapper.querySelector('p');\n\n    if (this.dataset.disclaimer) {\n      disclaimerWrapper.style.display = 'flex';\n      disclaimer.textContent = this.dataset.disclaimer;\n    } else {\n      disclaimerWrapper.style.display = 'none';\n    }\n\n    if (this.dataset.title) {\n      let titleEl = this.shadowRoot.querySelector('.title');\n      if (!titleEl) {\n        const titleTag = this.dataset.headingLevel === 'h1' ? 'h1' : 'h2';\n        titleEl = document.createElement(titleTag);\n        titleEl.setAttribute('data-click-track', 'Hero CTA Title');\n        titleEl.classList.add('title');\n\n        const contentEl = this.shadowRoot.querySelector('.content');\n        contentEl.prepend(titleEl);\n      }\n\n      titleEl.innerHTML = this.dataset.title;\n    }\n\n    const copyEl = this.shadowRoot.querySelector('.copy');\n    copyEl.innerHTML = this.dataset.copy;\n\n    const ctaAnchor = this.shadowRoot.querySelector<HTMLAnchorElement>('.cta a');\n    if ((this.dataset.ctaUrl || this.dataset.ctaHash) && this.dataset.ctaText) {\n      ctaAnchor.style.display = 'inline-block';\n      ctaAnchor.textContent = this.dataset.ctaText;\n      ctaAnchor.ariaLabel = this.dataset.ctaText || '';\n      ctaAnchor.href = this.dataset.ctaHash ? `#${this.dataset.ctaHash}` : this.dataset.ctaUrl;\n    } else {\n      ctaAnchor.style.display = 'none';\n    }\n\n    // If a CTA URL is provided without button text, we make the whole banner a link.\n    if (this.dataset.ctaUrl && !this.dataset.ctaText || this.dataset.fullBannerIsCta === 'true') {\n      this.ariaLabel = this.dataset.title || this.dataset.ctaText || 'Shop Now';\n      this.role = 'link';\n    } else {\n      this.removeAttribute('aria-label');\n      this.removeAttribute('role');\n    }\n\n    // Only show promo-code section if there is a promo code\n    const promoCodeWrapperEl = this.shadowRoot.querySelector<HTMLDivElement>('.promo-code');\n    if (this.dataset.promoCode) {\n      const promoCodeEl = promoCodeWrapperEl.querySelector('span');\n      promoCodeWrapperEl.style.display = 'inline-flex';\n      promoCodeEl.innerText = this.dataset.promoCode;\n    } else {\n      promoCodeWrapperEl.style.display = 'none';\n    }\n\n    if (this.dataset.textColor) {\n      const copyWrapperEl = this.shadowRoot.querySelector<HTMLDivElement>('.copy');\n      copyWrapperEl.style.color = this.dataset.textColor;\n      disclaimerWrapper.style.color = this.dataset.textColor;\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'banner-element': BannerElement;\n  }\n}\n\nif (!customElements.get('banner-element')) {\n  customElements.define('banner-element', BannerElement);\n}\n","/**\n * Returns a timestamp in milliseconds for measuring durations.\n *\n * This decorates performance.now().\n *\n * This tries to use the native high-resolution epoch, falling back to Date.now().\n *\n * If native throws, this still returns a value, but it logs a message as a side effect.\n *\n * This happens in a try/catch, so it is safe.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/now\n */\nexport function now() {\n  try {\n    if (typeof performance?.now === 'function') {\n      return performance.now();\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n\n  return Date.now();\n}\n\n/**\n * Records a named point on the User Timing timeline in devtools.\n *\n * This decorates performance.mark within a try/catch that suppresses errors.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/mark\n */\nexport function mark(name: string) {\n  try {\n    if (typeof performance?.mark === 'function') {\n      performance.mark(name);\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n}\n\n/**\n * Records a named span between two timestamps on the User Timing timeline in devtools.\n *\n * This decorates performance.measure within a try/catch that suppresses errors.\n *\n * The bounds are explicit timestamps rather than named marks, which avoids the error the marked\n * name form throws when a start mark was never recorded.\n *\n * The detail param is optional and must be structured-cloneable. It should be a plain object. If\n * the value cannot be cloned, then performance.measure throws, which is caught and logged by this\n * wrapper. If specified, the detail shows up in the tooltip in the devtools panel.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Performance/measure\n */\nexport function measure(name: string, start: number, end: number, detail?: unknown) {\n  try {\n    if (typeof performance?.measure === 'function') {\n      performance.measure(name, { start, end, detail });\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n}\n","import { measure, now } from './perf.js';\n\n/**\n * Waits for the runtime's load to reach a terminal state. Resolves true once a client is ready to\n * read, and false once the runtime has failed or the given number of milliseconds passes.\n *\n * The caller does not have to care whether the runtime loaded before or after it, which is the\n * whole point. Arriving late is covered by reading the load state, because the broadcast has\n * already been and gone by then, and arriving early is covered by listening for the broadcast.\n * Between them there is no ordering a caller can lose to.\n *\n * A failure resolves as soon as it is known rather than waiting out the timeout. That matters\n * because two of the failures are decided long before any caller's deadline. The theme editor is\n * known before a single bundle runs, and a missing anonymous id is known the moment the runtime\n * looks for one. Sitting out two or three seconds for an answer that already exists would delay\n * everything downstream of the value for nothing.\n *\n * Anything other than pending is terminal, which is what the check tests for, so a failure reason\n * added later needs no change here.\n *\n * On a preview theme the wait is extended, because the runtime deliberately holds the broadcast\n * open there to give the GrowthBook DevTools extension time to push forced variant overrides in. A\n * caller that gave up partway through that hold would silently drop the forced variant and make the\n * extension unreliable to debug with. How long the hold lasts comes off the runtime global, so the\n * two sides cannot drift apart into a hold that outlives the wait covering it.\n *\n * Neither the preview check nor the hold length can be read from anything the runtime's boot script\n * writes, because that script loads async and can execute after a caller has already asked. Both\n * the Shopify globals and the runtime's inline seed are set in the head instead, so they are always\n * available.\n *\n * The timeout stays as a floor for the one case no broadcast can cover, the boot script never\n * running at all. On that path the listener is left registered rather than torn down, since a\n * broadcast that does eventually arrive resolves a promise that has already settled, which changes\n * nothing.\n */\nfunction whenLoaded(milliseconds: number) {\n  return new Promise<boolean>(resolve => {\n    if (growthbook_runtime.load_state !== 'pending') {\n      resolve(growthbook_runtime.load_state === 'ready');\n      return;\n    }\n\n    const extraMillis = Shopify.previewMode ? growthbook_runtime.preview_hold_ms : 0;\n    const timer = setTimeout(resolve, milliseconds + extraMillis, false);\n\n    addEventListener('growthbook-load-settled', () => {\n      clearTimeout(timer);\n      resolve(growthbook_runtime.load_state === 'ready');\n    }, { once: true });\n  });\n}\n\n/**\n * Fault-tolerant wrapper around GrowthBook's own getFeatureValue SDK method.\n *\n * In GrowthBook the unit is a feature (a feature flag) identified by a feature key. The feature's\n * value for the current user is decided by evaluating its rules. One kind of rule is an experiment\n * rule (a \"feature flag experiment\"): it randomly buckets the user into a variation and returns\n * that variation's value. So the same feature key can resolve to a static flag value or to an A/B\n * test assignment, and this call returns whatever it resolves to. The caller cannot tell which, and\n * does not need to. When the feature is backed by an experiment rule, the returned value is the\n * user's assigned variation.\n *\n * Calling this is not a passive read, and that is the thing to know before adding a call. Bucketing\n * happens during the read, and the SDK reports the exposure to the runtime's tracking callback on\n * the way back out, which is what puts the visitor in the experiment's analytics. So the moment a\n * section calls this, that visitor counts as having seen whatever variation came back.\n *\n * That makes where a call sits matter as much as what it does with the answer. A read the visitor\n * could not possibly have seen still counts, and every such visitor lands in both groups and\n * behaves the same in both, which drags the measured difference between them toward zero. So gate\n * the call on the thing being on the page and reachable, rather than reading up front and deciding\n * later.\n *\n * On top of the raw SDK call this wrapper adds:\n *\n * - Fault tolerance. On a timeout, an error, or an undefined feature it returns the caller's\n *   fallback instead of throwing. It does assume the runtime global is present, which the always-on\n *   layer guarantees, so a page that somehow lacks it rejects rather than falling back.\n * - A shared, pre-initialized client. It reads the single client the cross-section coordination\n *   global exposes, so the SDK is initialized once per page rather than once per call.\n * - Indifference to load order. It waits on the runtime's ready signal rather than on the client\n *   promise existing yet, so a caller whose bundle runs before the runtime's is not penalized for\n *   it.\n * - A bounded wait. It always resolves within the given timeout, or within a global default when no\n *   per-call timeout is passed.\n * - Per-page memoization. The first resolved value for a feature key is cached and returned for\n *   every later call in the same page load. Once the payload has loaded this buys nothing on its\n *   own, because the SDK evaluates from attributes that do not change during a page, so repeated\n *   reads would agree with each other anyway. Where it earns its place is the timeout boundary. A\n *   caller that gave up waiting holds the fallback and the page has already acted on it, the client\n *   can become ready a moment later, and without it the next caller would get the real value and\n *   disagree with what the page is showing.\n *\n * @param featureKey The GrowthBook feature key.\n * @param fallback The value returned when the feature is undefined or when the lookup times out or\n * errors.\n * @param timeoutMs Optional per-call override for the lookup timeout, in milliseconds.\n *\n * @see https://docs.growthbook.io/lib/js#quick-usage GrowthBook JS SDK getFeatureValue\n * @see https://docs.growthbook.io/feature-flag-experiments How A/B tests are delivered via features\n * @see https://docs.growthbook.io/features/basics Feature flag fundamentals (keys, values, rules)\n */\nexport async function getFeatureValue<T extends string = string>(featureKey: string,\n  fallback: NoInfer<T>, timeoutMs?: number) {\n  const resolvedFeatures = growthbook_runtime.resolved_features;\n  if (resolvedFeatures[featureKey]) {\n    return <T>resolvedFeatures[featureKey];\n  }\n\n  let value: T | null = null;\n\n  // The span starts here rather than at function entry, so that it only ever covers a real wait.\n  // The memoized return above answers without waiting on anything, and timing it would put a span\n  // on the timeline for every read in the page. Leaving it outside means a span showing up at all\n  // is a caller that actually waited. Closing the span in a finally is what covers the timeout and\n  // the failure paths, which are the ones most worth a number.\n\n  const startMillis = now();\n  const effectiveTimeoutMs = timeoutMs ?? growthbook_runtime.default_timeout_ms;\n\n  try {\n    const loaded = await whenLoaded(effectiveTimeoutMs);\n\n    // The runtime either failed or ran out of time, and neither is reported here so as to avoid\n    // Sentry noise, so we return instead of throwing. The fallback is cached so that later calls\n    // for the same feature key in the same page load get the same answer, since a feature read in\n    // several places has to agree with itself.\n\n    if (!loaded) {\n      resolvedFeatures[featureKey] = fallback;\n      return fallback;\n    }\n\n    // The signal says the client is ready, so this await is already settled and only defends\n    // against the impossible case of the two disagreeing.\n\n    const growthbook = await growthbook_runtime.client_loader;\n\n    if (!growthbook) {\n      resolvedFeatures[featureKey] = fallback;\n      return fallback;\n    }\n\n    // Passing null here rather than the caller's fallback is deliberate. The SDK applies its own\n    // default only on a strict null check against the evaluated value, so handing it null makes\n    // this a straight pass-through of whatever the feature evaluated to, and every \"nothing usable\"\n    // case then lands on the single type check below.\n    //\n    // The evaluated value is null for an unknown feature key, for a feature with no default value\n    // configured, and for an unmet prerequisite. Passing the fallback here would cover exactly\n    // those and nothing else.\n    //\n    // What it would not cover is a value that is present but not a string. A feature configured\n    // with a number, a boolean, or a JSON value evaluates to that value, the SDK's null check does\n    // not fire, and the value comes back untouched. Undefined slips through for the same reason. So\n    // the type check below is load-bearing either way, and routing everything through it keeps the\n    // decision about what counts as usable in one place.\n\n    value = <T | null>growthbook.getFeatureValue<string | null>(featureKey, null);\n  } catch (error) {\n    console.warn('Error getting feature value from GrowthBook:', error);\n  } finally {\n    measure('growthbook-feature-value', startMillis, now(), {\n      feature: featureKey,\n      timeout: effectiveTimeoutMs\n    });\n  }\n\n  // The value comes from GrowthBook, so its type is an unverified boundary assertion. Coalesce\n  // anything that is not a string (null, or a missing or unexpected value) to the caller's default,\n  // so the resolved value is always a defined string.\n\n  if (typeof value !== 'string') {\n    value = fallback;\n  }\n\n  resolvedFeatures[featureKey] = value;\n\n  return value;\n}\n\n/**\n * Compares a value read through {@link getFeatureValue} against an expected value, ignoring case.\n * Every comparison against a feature value goes through this, even where a plain equality check\n * would do today, so that the casing decision lives in one place and new code copied from an\n * existing call site picks it up.\n *\n * The value is GrowthBook boundary data whose casing is not guaranteed, and a feature has been seen\n * returning an uppercase value. The expected side can be typed by hand too, such as an experiment\n * id typed into a section setting in the theme editor. Neither side's casing is enforced anywhere,\n * so we lowercase both. We leave the value itself untouched in the wrapper, so a caller reading it\n * directly still sees exactly what GrowthBook served.\n *\n * @param value The feature value\n * @param expected The value to compare against\n */\nexport function featureValueMatches(value: string, expected: string) {\n  return value.toLowerCase() === expected.toLowerCase();\n}\n","/**\n * Releases the handle returned from the ready() function.\n *\n * This is idempotent. Additional calls are pointless but harmless.\n */\nexport type ReleaseHandle = () => void;\n\n/**\n * A subset of events that pertain to document ready state.\n */\ntype LoadingEventMap = Pick<DocumentEventMap, 'DOMContentLoaded' | 'load'>;\n\n/**\n * Calls the provided callback when the document is ready. This works both when the document is\n * already ready and when it is not, so callers do not have to care. This exists because it ensures\n * that each caller consistently expresses its dependency on document readiness, and to save a bit\n * of boilerplate.\n *\n * A document is considered ready when it has been parsed. This corresponds to the ready state of\n * 'interactive'. When the document has been fully loaded, the ready state is 'complete'. The\n * callback runs either way, so callers do not have to check which one they are in.\n *\n * Ready means the html is parsed and the DOM is there to query. It does not mean the page's other\n * scripts have run. Deferred scripts and module scripts run after the ready state turns\n * 'interactive' and before DOMContentLoaded fires, so a callback here can run before them. When a\n * caller needs some other script to have run already, this is the wrong tool, and it should wait on\n * whatever that script produces instead.\n *\n * That remedy has a catch of its own when the calling bundle is loaded with async. An async bundle\n * can execute after DOMContentLoaded has already fired, so a plain listener for that event never\n * runs and the work silently never happens. Nothing here can detect that, because by the time this\n * file executes the event is long gone. Catching it needs something registered at parse time,\n * before any bundle loads.\n *\n * There is a second trap right next to that one, and it applies only when waiting for\n * DOMContentLoaded. The ready state stays 'interactive' after DOMContentLoaded has fired, all the\n * way until 'complete', so the ready state cannot tell you whether DOMContentLoaded is still coming\n * or has already gone. Waiting for load is not exposed to this, because the ready state turns\n * 'complete' and load fires in the same task, so seeing 'complete' means load has fired too.\n *\n * When the document is already ready, the callback is invoked via setTimeout. This way, if the\n * callback throws, the error surfaces to the global error handler in a way that is similar to what\n * happens when an event listener throws.\n *\n * We might one day want to move this to an NPM library.\n *\n * Beware of a quirk here. setTimeout yields, and the busier the frontend, the longer it takes to\n * fire. Page load is exactly when the frontend is busiest. The timeout is a floor and not a\n * guarantee, so there is no real bound on how long that yield lasts. What this means is that using\n * this library can introduce more delay than you might expect. Compare code that runs on async load\n * and checks ready state and invokes immediately to code that runs after setTimeout. The former\n * runs in the same tick, the latter runs in \"some later tick\". That later tick can be surprisingly\n * much later. That can cause initial paint issues.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event\n * @see https://html.spec.whatwg.org/multipage/parsing.html#the-end\n */\nexport function ready(callback: (event?: Event) => void,\n  type: keyof LoadingEventMap = 'DOMContentLoaded') {\n  if (document.readyState === 'complete' || (type === 'DOMContentLoaded' &&\n    document.readyState === 'interactive')) {\n    const timer = setTimeout(callback, 0);\n    const handle: ReleaseHandle = () => clearTimeout(timer);\n    return handle;\n  } else {\n    addEventListener(type, callback);\n    const handle: ReleaseHandle = () => removeEventListener(type, callback);\n    return handle;\n  }\n}\n","import { ready } from '../../lib/dom-ready.js';\nimport { getFeatureValue, featureValueMatches } from '../../lib/growthbook-feature.js';\n\ninterface ExperimentElement extends HTMLElement {\n  readonly dataset: {\n    /**\n     * The GrowthBook feature key for this banner, set in the Liquid template. Leave unset to always\n     * show the default banner.\n     */\n    experimentId: string;\n  };\n}\n\n/**\n * Reveals the correct banner for each experiment container on the page based on its GrowthBook\n * experiment. For every container, the feature key is looked up to decide which banner to show:\n * by convention the experiment's test variant is named identically to the key, so a returned\n * variation matching the key reveals the test banner and anything else reveals the default banner.\n * Containers without a feature key always get the default banner.\n */\nasync function setupBannerExperiments() {\n  const containers = document.querySelectorAll<ExperimentElement>('.banner-experiment-container');\n\n  for (const container of containers) {\n    const featureKey = container.dataset.experimentId;\n\n    let showTestBanner = false;\n    if (featureKey) {\n      const featureValue = await getFeatureValue(featureKey, 'default', 2500);\n\n      // for simplicity we require that the test variant name is the same as the feature key\n      showTestBanner = featureValueMatches(featureValue, featureKey);\n    }\n\n    if (showTestBanner) {\n      container.classList.add('show-test');\n    } else {\n      container.classList.add('show-default');\n    }\n  }\n}\n\nfunction onReady() {\n  setupBannerExperiments().catch(console.warn);\n}\n\nready(onReady);\n"],"names":["BannerElement","HTMLElement","onBannerClickedBound","this","onBannerClicked","bind","connectedCallback","initializeShadowRoot","addEventListener","render","shadowRoot","attachShadow","mode","innerHTML","disconnectedCallback","removeEventListener","_event","dataset","fullBannerIsCta","location","href","ctaUrl","disclaimerWrapper","querySelector","disclaimer","style","display","textContent","title","titleEl","titleTag","headingLevel","document","createElement","setAttribute","classList","add","prepend","copy","ctaAnchor","ctaHash","ctaText","ariaLabel","role","removeAttribute","promoCodeWrapperEl","promoCode","promoCodeEl","innerText","textColor","color","now","_performance","performance","error","console","debug","Date","async","getFeatureValue","featureKey","fallback","timeoutMs","resolvedFeatures","growthbook_runtime","resolved_features","value","startMillis","effectiveTimeoutMs","default_timeout_ms","milliseconds","Promise","resolve","load_state","extraMillis","Shopify","previewMode","preview_hold_ms","timer","setTimeout","clearTimeout","once","growthbook","client_loader","warn","name","start","end","detail","_performance3","measure","feature","timeout","featureValueMatches","expected","toLowerCase","customElements","get","define","callback","type","readyState","ready","containers","querySelectorAll","container","experimentId","showTestBanner","setupBannerExperiments","catch"],"sourceRoot":""}