{"version":3,"file":"before-after-video-section.min.js","mappings":"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","sources":["webpack://lange/./lib/dom-ready.ts","webpack://lange/./lib/perf.ts","webpack://lange/./lib/growthbook-feature.ts","webpack://lange/./sections/before-after-video/experiment.ts","webpack://lange/./sections/before-after-video/index.ts","webpack://lange/./lib/video.ts"],"sourcesContent":["/**\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","/**\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","import { ready } from '../../lib/dom-ready.js';\nimport { getFeatureValue, featureValueMatches } from '../../lib/growthbook-feature.js';\n\nfunction getBeforeAfterSection(selector: string) {\n  // We used the :has() selector before to find the sections but it causes issues in some browsers\n  // so instead we find the top element and then get the closest section to it\n  const topElement = document.querySelector(selector);\n  if (topElement) {\n    // We are within a dynamically rendered Shopify section so the section does not have a stable\n    // section id we can jump to. Walk up the DOM instead.\n    return topElement.closest('section');\n  }\n}\n\n/**\n * Set up the before/after video experiment by showing or hiding sections based on the feature\n * value. If the feature value is 'video', the standard B/A section is hidden and the video B/A\n * section is shown. Any other value leaves the video B/A section hidden.\n */\nfunction setupExperiment(featureValue: string) {\n  if (featureValueMatches(featureValue, 'video')) {\n    const videoSection = getBeforeAfterSection('.before-after-video-section');\n    const staticSection = getBeforeAfterSection('.before-after-section');\n\n    if (videoSection && staticSection) {\n      staticSection.style.display = 'none';\n      videoSection.style.display = 'block';\n    }\n  }\n}\n\nfunction onReady() {\n  const video = document.querySelector<HTMLElement>('.before-after-video-section');\n\n  // Only set up the experiment if the video section has the correct experiment data attribute. All\n  // other PDPs will render the sections as normal - showing/hiding based on the availability of\n  // content only\n  if (video && video.dataset.experimentId === 'before_after_video') {\n    // The video is a descendant of a dynamically rendered section so it does not have a stable\n    // section id. Walk up the DOM instead.\n    const section = video.closest('section');\n    section.style.display = 'none';\n\n    getFeatureValue('before_after_video', 'static')\n      .then(setupExperiment).catch(console.warn);\n  }\n}\n\nready(onReady);\n","import { ready } from '../../lib/dom-ready.js';\nimport { play } from '../../lib/video.js';\nimport './experiment.js';\n\nfunction onSlideVisible(event: WindowEventMap['carousel-slide-visible']) {\n  const slide = event.detail.slide;\n  if (!slide.closest('.before-after-video-section')) {\n    return;\n  }\n\n  // A carousel in a hidden tab panel can't measure its slides, so every slide in it counts as\n  // visible. Playing those would start videos nobody can see, so we wait until the tab opens.\n  if (!slide.closest('.tabpanel[hidden]')) {\n    playSlideVideo(slide);\n  }\n}\n\nfunction onSlideHidden(event: WindowEventMap['carousel-slide-hidden']) {\n  const slide = event.detail.slide;\n  if (slide.closest('.before-after-video-section')) {\n    const video = slide.querySelector('video');\n    video?.pause();\n  }\n}\n\nfunction playSlideVideo(slide: Element) {\n  // Do not play the slide video while in the theme editor.\n\n  // This module expects that the Shopify global is correctly declared and initialized. We\n  // intentionally are not being defensive about its presence here.\n\n  if (!Shopify.designMode) {\n    const video = slide.querySelector('video');\n    if (video) {\n      // A refusal here is not actionable, so keep a breadcrumb and never warn.\n      play(video).catch(console.debug);\n    }\n  }\n}\n\nfunction onTabClicked(event: MouseEvent) {\n  const selectedTab = event.currentTarget;\n\n  // Prove that selectedTab is both defined and of type Element. This is better than an unsafe hard\n  // cast to Element.\n\n  if (!(selectedTab instanceof Element)) {\n    return;\n  }\n\n  // The element is located within a dynamically rendered section so it does not have a stable id.\n  // Walk up the DOM instead.\n  const section = selectedTab.closest('.before-after-video-section');\n  const tabs = section.querySelectorAll('[role=\"tab\"]');\n  for (const tab of tabs) {\n    tab.setAttribute('aria-selected', tab === selectedTab ? 'true' : 'false');\n  }\n\n  const panelId = selectedTab.getAttribute('aria-controls');\n  const panels = section.querySelectorAll<HTMLElement>('.tabpanel');\n  let targetPanel: HTMLElement = null;\n  for (const panel of panels) {\n    panel.hidden = panel.id !== panelId;\n    if (panel.hidden) {\n      // The carousel does not notice its panel being hidden, so it never reports the slide as\n      // hidden and we pause the panel's videos ourselves.\n      for (const video of panel.querySelectorAll('video')) {\n        video.pause();\n      }\n    } else {\n      targetPanel = panel;\n    }\n  }\n\n  // The carousel mounted while its panel was hidden, so it lays its slides out again now that it\n  // can measure them. The carousel already counted the current slide as visible back then and does\n  // not say so again, so we play its video here. The carousel may not have mounted yet, in which\n  // case the mount plays it.\n  const carousel = targetPanel?.querySelector('carousel-element');\n  if (carousel?.dataset.index) {\n    carousel.dataset.refresh = 'true';\n    const slides = carousel.querySelectorAll('[data-carousel-slide]');\n    const currentSlide = slides[parseInt(carousel.dataset.index, 10)];\n    if (currentSlide) {\n      playSlideVideo(currentSlide);\n    }\n  }\n}\n\nfunction initSection(section: Element) {\n  const tabs = section.querySelectorAll<HTMLElement>('.tabs [role=\"tab\"]');\n  for (const tab of tabs) {\n    tab.addEventListener('click', onTabClicked);\n  }\n}\n\nfunction onReady() {\n  const selector = '.before-after-video-section:not(.initialized)';\n  const sections = document.querySelectorAll(selector);\n  for (const section of sections) {\n    initSection(section);\n    section.classList.add('initialized');\n  }\n}\n\n// These go on now rather than once the document is ready, because the carousel can mount and report\n// its first slide as visible before a ready callback gets to run.\naddEventListener('carousel-slide-visible', onSlideVisible);\naddEventListener('carousel-slide-hidden', onSlideHidden);\n\nready(onReady);\n","/**\n * Try to play a video, and always report the outcome as a promise.\n *\n * Do not inline this function. Playing a video is inconsistent across browsers in several ways, and\n * wrapping the call in an async function is what smooths all of them over at once.\n *\n * Some browsers have no `play` function on the element at all, so the call throws a TypeError. Some\n * have one that returns undefined instead of a promise, which is how `play` worked before the spec\n * gained a return value. Some return a promise that rejects later, which is the modern behavior and\n * is how a browser reports that it refused to autoplay. And some throw synchronously, right out of\n * the call, rather than rejecting.\n *\n * An async function turns all four into the same thing. Whatever happens inside it, the caller gets\n * a promise back, so a synchronous throw arrives as a rejection and a missing or non-promise `play`\n * still produces a promise that resolves. That means one `catch` on the call site covers every\n * failure mode, and the caller never has to check whether it got a promise before attaching a\n * handler.\n *\n * Nothing here is actionable, because a refusal is the browser enforcing its own autoplay policy.\n * Restrictive in-app browsers and low power mode refuse even after a user gesture, and interrupted\n * or unsupported sources reject too. The video simply stays paused, so callers should keep a debug\n * breadcrumb and never warn.\n */\nexport async function play(video: HTMLVideoElement) {\n  return video.play();\n}\n"],"names":["ready","callback","type","document","readyState","timer","setTimeout","clearTimeout","addEventListener","removeEventListener","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","once","growthbook","client_loader","warn","name","start","end","detail","_performance3","measure","feature","timeout","getBeforeAfterSection","selector","topElement","querySelector","closest","setupExperiment","featureValue","expected","toLowerCase","videoSection","staticSection","style","display","playSlideVideo","slide","designMode","video","play","catch","onTabClicked","event","_targetPanel","selectedTab","currentTarget","Element","section","tabs","querySelectorAll","tab","setAttribute","panelId","getAttribute","panels","targetPanel","panel","hidden","id","pause","carousel","dataset","index","refresh","currentSlide","parseInt","initSection","experimentId","then","sections","classList","add"],"sourceRoot":""}