{"company":{"model":{"version":3,"companyId":"626fe4757826497d85d5678a","imageViewedEventsEnabled":false,"analyticsEnabled":true,"matomoTrackerEnabled":false,"matomoTrackerFullEnabled":false,"ecommerceTrackerEnabled":false,"zsiteVanityUrlsEnabled":false,"heatmapsEnabled":false,"isUsingUuids":false,"isImageCroppingEnabled":true,"enableCNCReflowPreventionOutput":false,"enableImageLQIP":false,"textWidgetEnabled":false,"enhancedTextWidgetEnabled":true,"dynamicProductWidgetEnabled":false,"cloudinaryDefaultImageProvider":true,"adaTabOrderEnabled":true,"experiencePreRenderEnabled":false,"userPermissionsEnabled":false,"salesforceRecommendationsEnabled":false,"combinedGroupsSpeed":true,"imageErrorSampleRate":0,"imageCompressionQuality":85,"matomoTrackingUrl":"http://","shareWithAllByDefault":true,"cloudinaryImageAndDprOverride":"q_auto","state":"Active"}},"viewer":{"experiences":{"C3_e5e2f4b1-598d-802d-8003-41e0c805b8a4_e5e2f4b1-598d-802d-8003-41e0c805b8a5":{"currentSceneNumber":1,"transitionDirection":0,"model":{"companyId":"626fe4757826497d85d5678a","createdAt":1696955830262,"modifiedAt":1696955830262,"autoPlayDelay":0,"type":"Experience","language":"en-US","id":"C3_e5e2f4b1-598d-802d-8003-41e0c805b8a4_e5e2f4b1-598d-802d-8003-41e0c805b8a5","name":"Variant 1","width":5001,"height":1,"transition":"none","scenes":[{"id":"a0bd4374-c808-802a-8003-41e0c98cbdf2","sceneIndex":0,"sceneNumber":"1","widgets":[],"widgetTabOrder":[],"removedWidgetTabOrder":[],"customClasses":[null]}],"threshold":0,"backgroundScene":{"sceneNumber":"background","widgets":[],"widgetTabOrder":[],"removedWidgetTabOrder":[]},"overlayScene":{"sceneNumber":"overlay","widgets":[],"widgetTabOrder":[],"removedWidgetTabOrder":[]},"backdrop":{"type":"color","fit":"Experience","color":"#FFFFFF","image":null},"behaviors":[{"steps":[{"id":"","steps":[],"data":{"loadType":"executeJs","details":{"jsSnippet":"\n(function() {\n    window.FASTR_FRONTEND.experiences[_experienceId].tamperLibrary = {\n        setImageUrl: async function setImage(integrationResults, widget, args) {\n\n    debug.breakpoint();\n\n    const { source } = args;\n\n    const sourceData = integrationResults.get(source);\n\n    if (!sourceData) {\n        debug.error(`No data found for source ${source}`);\n    }\n\n    // Replace image-widget in SSR body\n    let widgetEl = domRef.querySelector(`div[id$='${widget.uuid}'] img`);\n\n    if (widgetEl) {\n        widgetEl.removeAttribute('srcset');\n        widgetEl.style.setProperty('object-fit', 'contain');\n        widgetEl.src = sourceData;\n\n        // Replace in the model\n        if (widget['src']) {\n            widget.src = sourceData;\n            widget.changeSource = \"behavior\";\n        }\n    } else {\n\n        // Replace image in svg SSR body\n        widgetEl = domRef.querySelector(`div[id$='${widget.uuid}'] svg`);\n\n        if (widgetEl) {\n            let imageEl = widgetEl.querySelector('image');\n            imageEl.setAttribute('href', sourceData);\n            imageEl.setAttribute('preserveAspectRatio', 'xMidYMid meet'); // This will ensure proportional scaling of the image\n\n            // Replace in the model\n            widget.svgContent = widgetEl.innerHTML;\n        }\n    }\n}\n,\n        setHref: async function setOnclick(integrationResults, widget, args) {\n\n    debug.breakpoint();\n\n    const { source } = args;\n\n    // Replace in the model\n    widget[\"action\"] = widget[\"action\"] || [];\n    widget[\"action\"].type = \"link\";\n    widget[\"action\"].url = integrationResults.get(source);\n}\n,\n        setSceneTarget: async function setOnclick(integrationResults, widget, args) {\n\n    debug.breakpoint();\n\n    const { source } = args;\n\n    // Replace in the model\n\n    // TODO: This sets the scene click target\n}\n,\n        setText: async function setText(integrationResults, widget, args) {\n\n    const replaceInTextNodes = function(node, searchText, replacementText, modifyOriginal = false) {\n        let tempNode;\n        if (typeof node === 'string') {\n            // We're working with the widget model\n            let tempDiv = document.createElement('div');\n            tempDiv.innerHTML = node;\n            tempNode = tempDiv;\n        } else {\n            // We're working with an actual DOM node\n            tempNode = modifyOriginal ? node : node.cloneNode(true);\n        }\n    \n        let nodeList = [];\n        let walker = document.createTreeWalker(tempNode, NodeFilter.SHOW_TEXT);\n        while (walker.nextNode()) nodeList.push(walker.currentNode);\n    \n        for (let textNode of nodeList) {\n            textNode.nodeValue = textNode.nodeValue.replaceAll(searchText, replacementText);\n        }\n    \n        return (typeof node === 'string') ? tempNode.innerHTML : tempNode;\n    }\n\n    debug.breakpoint();\n\n    const { source, placeholder } = args;\n\n    const widgetEl = domRef.querySelector(`div[id$='${widget.uuid}']`);\n\n    const sourceValue = integrationResults.get(source);\n\n    if (!sourceValue) {\n        debug.log(`No match found in integration results for ${source} for widget ${widget.uuid}`);\n        return;\n    }\n\n    if (placeholder) {\n        // Replace in the model\n        if (widget[\"text\"]) {\n            widget[\"text\"] = replaceInTextNodes(widget[\"text\"], placeholder, sourceValue);\n        }\n\n        // Replace in SSR body\n        replaceInTextNodes(widgetEl, placeholder, sourceValue, true);\n        \n    } else {\n        // Replace in the model\n        if (widget[\"text\"]) {\n            widget[\"text\"] = replaceInTextNodes(widget[\"text\"], widgetEl.textContent.trim(), sourceValue);\n        }\n\n        // Replace in SSR body\n        replaceInTextNodes(widgetEl, widgetEl.textContent.trim(), sourceValue, true);\n    }\n\n    widget.text = widgetEl.innerHTML;\n},\n        hideGroup: \nasync function hideGroup(_, __, args) {\n\n    debug.breakpoint();\n\n    const { placeholder } = args;\n\n    const widgets = placeholder.split(',');\n\n    for (const widgetUuid of widgets) {\n        const { widget } = findWidgetByUuid(_model, widgetUuid);\n        this.hideWidget(_, widget);\n    }\n    \n}\n,\n        hideWidget: \nasync function hideWidget(_, widget) {\n\n    debug.breakpoint();\n\n    const widgetUuuid = widget.uuid;\n    // Replace in SSR body\n    const widgetEl = domRef.querySelector(`div[id$='${widget.uuid}']`);\n    widgetEl.style.opacity = 0\n    widgetEl.style.display = 'none';\n\n    // Replace in the model\n    widget['opacity'] = \"0\";\n    \n}\n,\n        setAltText: \nasync function setAltText(integrationResults, widget, args) {\n\n    debug.breakpoint();\n\n    const { source } = args;\n\n    const txt = integrationResults.get(source);\n\n    // Replace in SSR body\n    const widgetEl = domRef.querySelector(`div[id$='${widget.uuid}']`);\n    widgetEl.setAttribute('aria-label', txt);\n\n    // If this is an image widget, also set the alt text\n    const img = widgetEl.querySelector('img');\n    if (img) {\n        img.setAttribute('alt', txt);\n    }\n\n    // Replace in the model\n    widget['alt'] = txt;\n    \n}\n,\n    };\n    window.FASTR_FRONTEND.experiences[_experienceId].conditionFunction = async function evaluateConditions(integrationResults, conditions) {\n    debug.breakpoint();\n\n    try {\n        for (let i = 0; i < conditions.length; i++) {\n            const condition = conditions[i];\n            const source = integrationResults.get(condition.lefthandPrefix);\n\n            let comparison;\n            if (condition.righthandType === \"static\") {\n                comparison = condition.righthandValue;\n            } else {\n                comparison = integrationResults.get(condition.righthandPrefix);\n            }\n\n            let comparisonNumber = Number(comparison);\n\n            // If comparison is wrapped in quotes, use it as the value\n            if (!isNaN(comparisonNumber)) {\n                comparison = comparisonNumber;\n            } else if ((comparison.startsWith(`\"`) && comparison.endsWith(`\"`)) || (comparison.startsWith(`'`) && comparison.endsWith(`'`))) {\n                comparison = comparison.slice(1, -1);\n\n                // Try converting to a number, if not possible use it as a string\n                const comparisonNumber = Number(comparison);\n                if (!isNaN(comparisonNumber)) {\n                    comparison = comparisonNumber;\n                }\n            }\n\n            const comparator = condition.comparator;\n\n            const comparators = {\n                \"<\": (a, b) => a < b,\n                \"<=\": (a, b) => a <= b,\n                \"==\": (a, b) => a == b,\n                \">=\": (a, b) => a >= b,\n                \">\": (a, b) => a > b,\n                \"!=\": (a, b) => a != b,\n                equals: (a, b) => a === b,\n                includes: (a, b) => a.includes(b),\n                doesNotContain: (a, b) => !a.includes(b),\n                startsWith: (a, b) => a.startsWith(b),\n                endsWith: (a, b) => a.endsWith(b),\n                is: (a, b) => a == b,\n                isNot: (a, b) => a != b,\n            };\n\n            const comparisonFunction = comparators[comparator];\n            if (!comparisonFunction) throw new Error(`Invalid comparator: ${comparator}`);\n\n            debug.log(`Comparing: ${source} ${comparator} ${comparison}`);\n\n            const result = comparisonFunction(source, comparison);\n\n            debug.log(`The result of the comparison is ${result}`);\n\n            if (!result) return false;\n        }\n\n        return true;\n    } catch (error) {\n        console.log(`Failed to evaluate conditions: ${error}`);\n    }\n\n    return false;\n}\n;\n\n    modelModificationConfigs.forEach((config) => {\n        try {\n            if (config.gatherInputs) {\n                config.gatherInputs = eval(\"(\" + config.gatherInputs + \")()\");\n            }\n            config.integrations.forEach((integration) => {\n                integration.processInputs = eval(\"(\" + integration.processInputs + \")()\");\n                integration.processFunction = eval(\"(\" + integrations[integration.processFunction] + \")()\");\n                integration.processOutputs = eval(\"(\" + integration.processOutputs + \")()\");\n            });\n        } catch (e) {\n            debug.error(\"Error setting up integration functions\");\n            debug.error(e);\n        }\n\n        window.FASTR_FRONTEND.experiences[_experienceId].registerModelTamperFunction(config);\n    });\n\n})();\n","jsSnippetMetaData":"\nconst _experienceId = \"BEHAVIOR_EXPERIENCEID_PLACEHOLDER\";\n\nconst lastUnderscoreIndex = _experienceId.lastIndexOf(\"_\");\n\nconst experienceTypeId = _experienceId.substring(0, lastUnderscoreIndex);\nconst experienceOrdinal = _experienceId.substring(lastUnderscoreIndex + 1);\n\nlet _model = undefined;\n\nwindow.ZMAGS_API = window.ZMAGS_API || { experiences: {} };\nwindow.ZMAGS_API.experiences[_experienceId] = window.ZMAGS_API.experiences[_experienceId] || {};\n\ntry {\n    window.FASTR_FRONTEND = window.FASTR_FRONTEND || {};\n    window.FASTR_FRONTEND.experiences = window.FASTR_FRONTEND.experiences || {};\n    window.FASTR_FRONTEND.experiences[_experienceId] = window.FASTR_FRONTEND.experiences[_experienceId] || {};\n\n    // Pull properties from ZMAGS_API to FASTR_FRONTEND.\n    // NOTE: this sets us up to deprecate ZMAGS_API from viewer in the future\n    for (const prop in window.ZMAGS_API.experiences[_experienceId]) {\n        if (!window.FASTR_FRONTEND.experiences[_experienceId].hasOwnProperty(prop)) {\n            window.FASTR_FRONTEND.experiences[_experienceId][prop] = window.ZMAGS_API.experiences[_experienceId][prop];\n        }\n    }\n} catch (error) {\n    console.error(error);\n}\n\nconst domRef = document.querySelector(`div[data-experience='${experienceTypeId}'][data-ordinal='${experienceOrdinal}']`);\n\nconst getFASTR_FRONTEND_DEBUG = () => {\n    try {\n        return JSON.parse(localStorage.getItem(\"FASTR_FRONTEND_DEBUG\"));\n    } catch (error) {\n        console.warn(\"Error parsing FASTR_FRONTEND_DEBUG from localStorage:\", error);\n        return {};\n    }\n};\n\nconst FASTR_FRONTEND_DEBUG = getFASTR_FRONTEND_DEBUG();\n\nconst debug = {\n    output: (type, message, ...optionalParams) => {\n        if (FASTR_FRONTEND_DEBUG && FASTR_FRONTEND_DEBUG.trace) {\n            console[type](`[FASTR_FRONTEND_DEBUG] ${message}`, ...optionalParams);\n        }\n    },\n    log: (message, ...optionalParams) => {\n        debug.output(\"log\", message, ...optionalParams);\n    },\n    error: (message, ...optionalParams) => {\n        debug.output(\"error\", message, ...optionalParams);\n    },\n    breakpoint: () => {\n        if (FASTR_FRONTEND_DEBUG && FASTR_FRONTEND_DEBUG.break) {\n            debugger;\n        }\n    },\n};\n\nwindow.FASTR_FRONTEND.experiences[_experienceId].dumpState = () => {\n    const experienceIdentifier = _experienceId.split(\"_\").slice(0, -1).join(\"_\");\n    const model = window[_experienceId + \"_STATE\"].viewer.experiences[experienceIdentifier].model;\n    const modelSizeInKB = JSON.stringify(model).length / 1024;\n    console.log(`Model size for experience ${_experienceId}: ${modelSizeInKB.toFixed(2)} KB`);\n};\n\n\nconst modelModificationConfigs = [];\n\nconst backgroundIntegrations = [\n   \"\\t\\twindow.ZMAGS_ANALYTICS_API = window.ZMAGS_ANALYTICS_API || {};\\n\\t\\tZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE = {};\\n\\t\\tZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER = null;\\n\\t\\tZMAGS_ANALYTICS_API.PLUS_ICONS = [\\\"\\\"]\\n\\t\\tZMAGS_ANALYTICS_API.MUTATION_OBSERVER = null;\\n\\t\\tZMAGS_ANALYTICS_API.EXPERIENCE_TYPES = ['']\\n\\t\\tZMAGS_ANALYTICS_API.EVENTS_FIRED = ZMAGS_ANALYTICS_API?.EVENTS_FIRED || {\\n\\t\\t\\t\\\"click_events\\\": []\\n\\t\\t};\\n\\n\\t\\tZMAGS_ANALYTICS_API.getExperienceName = function (contextWindow, experienceId) {\\n\\t\\t\\tif (!ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[experienceId]) {\\n\\t\\t\\t\\tconst expname = contextWindow?.[`${experienceId}_1_STATE`]?.viewer?.experiences?.[experienceId]?.model?.name || \\\"\\\";\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[experienceId] = expname;\\n\\t\\t\\t}\\n\\t\\t\\treturn ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[experienceId];\\n\\t\\t}\\n\\n\\t\\tZMAGS_ANALYTICS_API.customEvent = function (name, args) {\\n\\t\\t\\ttop.window.dataLayer = top.window.dataLayer || [];\\n\\t\\t\\tlet data = { event: name };\\n\\t\\t\\tfor (let key in args) {\\n\\t\\t\\t\\tif (args.hasOwnProperty(key)) {\\n\\t\\t\\t\\t\\tdata[key] = args[key];\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t\\ttop.window.dataLayer.push(data);\\n\\t\\t}\\n\\n\\t\\tZMAGS_ANALYTICS_API.trackLink = function (link, experienceName, clickPlus = false) {\\n\\t\\t\\tconst url = link.href;\\n\\t\\t\\tconst scene = link.closest(\\\"[class^='scene-']\\\")?.className || \\\"\\\";\\n\\t\\t\\tconst event = {\\n\\t\\t\\t\\t\\\"fastr_exp\\\": experienceName,\\n\\t\\t\\t\\t\\\"fastr_exp_type\\\": ZMAGS_ANALYTICS_API.EXPERIENCE_TYPES.filter(name => experienceName.toLocaleUpperCase().includes(name))[0] || \\\"experience\\\",\\n\\t\\t\\t\\t\\\"fastr_int\\\": clickPlus ? \\\"link_click\\\" : \\\"click\\\",\\n\\t\\t\\t\\t\\\"fastr_scene\\\": scene,\\n\\t\\t\\t\\t\\\"fastr_url\\\": url\\n\\t\\t\\t}\\n\\t\\t\\tif (!document.hidden) {\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.customEvent(\\\"fastr\\\", event);\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.EVENTS_FIRED['click_events'].push(event)\\n\\t\\t\\t}\\n\\t\\t};\\n\\n\\t\\tZMAGS_ANALYTICS_API.trackSceneChange = function (viewerContainer, experienceId, experienceName) {\\n\\t\\t\\tconst activeScene = viewerContainer.querySelector('[class^=\\\"scene-\\\"][style*=\\\"visibility: inherit\\\"][style*=\\\"opacity:\\\"]:not(.scene-background, .scene-overlay), [class^=\\\"scene-\\\"][style*=\\\"visibility: visible\\\"]:not(.scene-background, .scene-overlay),  [class^=\\\"scene-\\\"][style*=\\\"visibility:visible\\\"]:not(.scene-background, .scene-overlay)')?.className || \\\"\\\";\\n\\t\\t\\tconst ordinal = viewerContainer?.dataset?.ordinal || 1\\n\\t\\t\\tconsole.log(`experienceId: `, experienceId, activeScene)\\n\\t\\t\\tif (!ZMAGS_ANALYTICS_API.EVENTS_FIRED[`${experienceId}_${ordinal}_scene_${activeScene}`] && !document.hidden) {\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.customEvent(\\\"zmags\\\", {\\n\\t\\t\\t\\t\\t\\\"fastr_exp\\\": experienceName,\\n\\t\\t\\t\\t\\t\\\"fastr_exp_type\\\": ZMAGS_ANALYTICS_API.EXPERIENCE_TYPES.filter(name => experienceName.toLocaleUpperCase().includes(name))[0] || \\\"experience\\\",\\n\\t\\t\\t\\t\\t\\\"fastr_int\\\": \\\"view\\\",\\n\\t\\t\\t\\t\\t\\\"fastr_scene\\\": activeScene\\n\\t\\t\\t\\t});\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.EVENTS_FIRED[`${experienceId}_${ordinal}_scene_${activeScene}`] = true\\n\\t\\t\\t}\\n\\t\\t}\\n\\n\\t\\tZMAGS_ANALYTICS_API.trackExperienceSceneView = function (viewerContainer, experienceId) {\\n\\t\\t\\tconst observerConfig = {\\n\\t\\t\\t\\tattributes: true,\\n\\t\\t\\t\\tchildList: true,\\n\\t\\t\\t};\\n\\t\\t\\tconst sceneContaniers = Array.from(viewerContainer.querySelectorAll('.scene-background') || []).map(ele => {\\n\\t\\t\\t\\tif (!ele.parentNode.dataset.analyticsInitiated) {\\n\\t\\t\\t\\t\\tele.parentNode.dataset.experienceId = experienceId\\n\\t\\t\\t\\t\\tele.parentNode.dataset.analyticsInitiated = true\\n\\t\\t\\t\\t\\treturn ele.parentNode\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\treturn null\\n\\t\\t\\t}).filter(ele => ele !== null)\\n\\t\\t\\tsceneContaniers.forEach((container) => {\\n\\t\\t\\t\\tconst experienceId = container.dataset.experienceId;\\n\\t\\t\\t\\tconst experienceName = ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[`${experienceId}`]\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackSceneChange(container, experienceId, experienceName)\\n\\t\\t\\t\\tcontainer.addEventListener(\\\"click\\\", function (event) {\\n\\t\\t\\t\\t\\tconst clickPlus = ZMAGS_ANALYTICS_API.PLUS_ICONS.filter(img => (event.target.src || \\\"\\\")?.toLowerCase()?.includes(img)).length > 0\\n\\t\\t\\t\\t\\tconst anchorTag = event.target?.closest('a') || {}\\n\\t\\t\\t\\t\\tif (anchorTag.tagName === \\\"A\\\") {\\n\\t\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackLink(anchorTag, `${experienceId} - ${experienceName}`, clickPlus)\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t})\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.MUTATION_OBSERVER.observe(container, observerConfig)\\n\\t\\t\\t})\\n\\t\\t}\\n\\n\\t\\tZMAGS_ANALYTICS_API.setupTracking = function (context, contextWindow) {\\n\\t\\t\\tconst viewerContainers = context.querySelectorAll('[class^=\\\"zmags-viewer-container\\\"][data-zmags-initiated]:not([data-analytics-initiated])');\\n\\n\\t\\t\\tfor (let viewerContainer of viewerContainers) {\\n\\t\\t\\t\\tconst experienceId = viewerContainer.dataset.experience;\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.getExperienceName(contextWindow, experienceId);\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER.observe(viewerContainer)\\n\\t\\t\\t\\tviewerContainer.dataset.analyticsInitiated = true\\n\\t\\t\\t}\\n\\t\\t\\tconst iframes = Array.from(context.querySelectorAll(\\\"iframe:not([data-analytics-error])\\\"))\\n\\t\\t\\tiframes.forEach(function (iframe) {\\n\\t\\t\\t\\ttry {\\n\\t\\t\\t\\t\\tconst iframeDoc = iframe.contentDocument || iframe.contentWindow.document;\\n\\t\\t\\t\\t\\tconst iframeWindow = iframe.contentWindow;\\n\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.setupTracking(iframeDoc, iframeWindow);\\n\\t\\t\\t\\t} catch (e) {\\n\\t\\t\\t\\t\\tiframe.dataset.analyticsError = true\\n\\t\\t\\t\\t\\tconsole.log(\\\"Failed to access iframe, probably because it is from a different origin.\\\");\\n\\t\\t\\t\\t}\\n\\t\\t\\t});\\n\\t\\t}\\n\\n\\t\\tfunction isElementInViewport(element) {\\n\\t\\t\\tconst sceneContainer = element.querySelectorAll('[class=\\\"scene-background\\\"]')[0] || element\\n\\t\\t\\tconst rect = sceneContainer.getBoundingClientRect();\\n\\t\\t\\tconst viewportHeight = window.innerHeight || document.documentElement.clientHeight;\\n\\t\\t\\treturn ((rect.top >= 0 && rect.top <= viewportHeight) || (rect.bottom >= 0 && rect.bottom <= viewportHeight));\\n\\t\\t}\\n\\n\\t\\tconst mutationObserver = ZMAGS_ANALYTICS_API.MUTATION_OBSERVER || new MutationObserver((mutationsList, observer) => {\\n\\t\\t\\tconst mutation = mutationsList[0]\\n\\t\\t\\tif (!mutation) return;\\n\\t\\t\\tconst viewerContainer = mutation.target\\n\\t\\t\\tconst experienceId = viewerContainer.dataset.experienceId;\\n\\t\\t\\tconst experienceName = ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[`${experienceId}`]\\n\\t\\t\\tif (isElementInViewport(viewerContainer)) {\\n\\t\\t\\t\\tsetTimeout(() => {\\n\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackSceneChange(viewerContainer, experienceId, experienceName)\\n\\t\\t\\t\\t}, 500)\\n\\t\\t\\t}\\n\\t\\t});\\n\\n\\t\\tif (!ZMAGS_ANALYTICS_API.MUTATION_OBSERVER) ZMAGS_ANALYTICS_API.MUTATION_OBSERVER = mutationObserver;\\n\\n\\t\\tfunction handleIntersection(entries) {\\n\\t\\t\\tentries.forEach(entry => {\\n\\t\\t\\t\\tif (entry.isIntersecting) {\\n\\t\\t\\t\\t\\tconst viewerContainer = entry.target\\n\\t\\t\\t\\t\\tconst experienceId = viewerContainer.dataset.experience;\\n\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackSceneChange(viewerContainer, experienceId, ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[`${experienceId}`])\\n\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackExperienceSceneView(entry.target, experienceId)\\n\\t\\t\\t\\t}\\n\\t\\t\\t});\\n\\t\\t}\\n\\n\\t\\tconst options = {\\n\\t\\t\\troot: null,\\n\\t\\t\\trootMargin: '0px',\\n\\t\\t\\tthreshold: 0\\n\\t\\t};\\n\\n\\t\\tconst observer = ZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER || new IntersectionObserver(handleIntersection, options);\\n\\n\\t\\tif (!ZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER) ZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER = observer\\n\\n\\n\\t\\tsetInterval(function () {\\n\\t\\t\\tZMAGS_ANALYTICS_API.setupTracking(document, window);\\n\\t\\t}, 500);\"\n];\n\nconst functionIntegrations = {};\n\nconst integrations = {};\n\nwindow.FASTR_FRONTEND = window.FASTR_FRONTEND || {};\nwindow.FASTR_FRONTEND.experiences = window.FASTR_FRONTEND.experiences || {};\n\nwindow.FASTR_FRONTEND.experiences[_experienceId] = {\n    initialTimestamp: performance.now(),\n    memoCache: new Map(),\n    pendingCache: new Map(),\n    performanceCounters: {\n        memoizedFunctions: 0,\n        cacheHits: 0,\n        cacheMisses: 0,\n        callDurations: [],\n        keyMapping: {},\n    },\n    hashFnv32a: function (str, seed = 0x811c9dc5) {\n        let h = seed;\n        for (let i = 0; i < str.length; i++) {\n            h ^= str.charCodeAt(i);\n            h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);\n        }\n        return h >>> 0;\n    },\n    memoize: function (fn) {\n        this.performanceCounters.memoizedFunctions++;\n        debug.log(`Memoized function created. Total: ${this.performanceCounters.memoizedFunctions}`);\n\n        return async (...args) => {\n            const fnString = fn.toString();\n            const key = JSON.stringify({ fnString, args });\n            const hashedKey = this.hashFnv32a(key);\n            const start = performance.now();\n\n            this.performanceCounters.keyMapping[hashedKey] = key;\n\n            if (this.memoCache.has(hashedKey)) {\n                this.performanceCounters.cacheHits++;\n                const end = performance.now();\n                debug.log(`Cache hit for key ${hashedKey}. Total hits: ${this.performanceCounters.cacheHits}`);\n                this.performanceCounters.callDurations.push({\n                    key: hashedKey,\n                    type: \"hit\",\n                    duration: end - start,\n                    start,\n                });\n                return this.memoCache.get(hashedKey);\n            }\n\n            if (this.pendingCache.has(hashedKey)) {\n                this.performanceCounters.waitingCalls++;\n                const result = await this.pendingCache.get(hashedKey);\n                this.performanceCounters.cacheHits++;\n                debug.log(`Pending cache hit for key ${hashedKey}. Total hits: ${this.performanceCounters.cacheHits}`);\n                const end = performance.now();\n                this.performanceCounters.callDurations.push({\n                    key: hashedKey,\n                    type: \"awaiting cache\",\n                    duration: end - start,\n                    start,\n                });\n                return result;\n            }\n\n            this.performanceCounters.cacheMisses++;\n            debug.log(`Cache miss for key ${hashedKey}. Total misses: ${this.performanceCounters.cacheMisses}`);\n            const promise = fn(...args);\n            this.pendingCache.set(hashedKey, promise);\n            const result = await promise;\n            this.memoCache.set(hashedKey, result);\n            this.pendingCache.delete(hashedKey);\n\n            const end = performance.now();\n            this.performanceCounters.totalDuration += end - start;\n            debug.log(`Memoized function ${fn.name} took ${end - start} milliseconds to execute. Total duration: ${this.performanceCounters.totalDuration}`);\n            this.performanceCounters.callDurations.push({\n                key: hashedKey,\n                type: \"miss\",\n                duration: end - start,\n                start,\n            });\n            return result;\n        };\n    },\n    runPerformanceTrace: function () {\n        const { callDurations, keyMapping } = this.performanceCounters;\n\n        const sortedDurations = callDurations.sort((a, b) => a.start - b.start);\n\n        sortedDurations.forEach(({ key, type, duration, start }) => {\n            const { fnString, args } = JSON.parse(keyMapping[key]);\n\n            let strippedFnString = fnString.replace(/async\\s+function\\s*\\(\\s*input\\s*\\)\\s*\\{\\s*/, \"\").replace(/\\n/g, \"\");\n\n            const shortenedFnString = strippedFnString.length > 90 ? strippedFnString.substring(0, 87) + \"...\" : strippedFnString;\n            const argsString = JSON.stringify(args).replace(/\\n/g, \"\");\n            const shortenedArgsString = argsString.length > 90 ? argsString.substring(0, 87) + \"...\" : argsString;\n\n            const fullKey = `Body: [${shortenedFnString}] Args: [${shortenedArgsString}]`;\n\n            const elapsedTime = start - this.initialTimestamp;\n            const minutes = Math.floor(elapsedTime / 60000);\n            const seconds = ((elapsedTime % 60000) / 1000).toFixed(3);\n            console.log(`[${minutes}:${seconds}] [${type}] [${key}] ${fullKey}: ${Math.round(duration)}ms`);\n        });\n    },\n    processGivenInputs: async function (inputFn) {\n        const memoizedProcessInputs = this.memoize(inputFn);\n        debug.log(\"Gathering inputs with :\", inputFn);\n        const generatedInput = await memoizedProcessInputs();\n        return generatedInput;\n    },\n    processIntegrationConfig: async function (inputJSON, processInputsFn, processFunctionFn, processOutputsFn) {\n        const memoizedProcessInputs = this.memoize(processInputsFn);\n        const memoizedProcessFunction = this.memoize(processFunctionFn);\n        const memoizedProcessOutputs = this.memoize(processOutputsFn);\n\n        debug.log(\"Processing inputs with inputJSON:\", inputJSON);\n        const processedInput = await memoizedProcessInputs(inputJSON);\n\n        debug.log(\"Executing process function with processedInput:\", processedInput);\n        const output = await memoizedProcessFunction(processedInput);\n\n        debug.log(\"Processing outputs with output:\", output);\n        const outputJSON = await memoizedProcessOutputs(output);\n\n        debug.log(\"Returning outputJSON:\", outputJSON);\n        return outputJSON;\n    },\n    runBackgroundIntegrations: async function () {\n        debug.breakpoint();\n\n        for (const integration of backgroundIntegrations) {\n            const body = integration;\n            try {\n                const func = new Function(\"model\", body);\n                await func(_model);\n            } catch (error) {\n                debug.error(\"Failed to run background integration\");\n                debug.error(JSON.stringify(body));\n                debug.error(error);\n            }\n        }\n    },\n    customActionHandler: async function (integrationId, args) {\n        debug.breakpoint();\n\n        const fn = functionIntegrations[integrationId];\n\n        const wrappedFn =\n`(async function customAction(inputs, model) {\n    ${fn}\n})(inputs, model);`;\n\n        // Flatten our inputs down to a simple object\n        const inputs = args ? Object.fromEntries(args.map(({name, value}) => [name, value])) : {};\n\n        try {\n            const func = new Function(\"inputs\", \"model\", wrappedFn);\n            await func(inputs, _model);\n        } catch (error) {\n            debug.error(\"Failed to run custom action integration\");\n            debug.error(JSON.stringify(fn));\n            debug.error(error);\n        }\n    },\n};\n\nwindow.FASTR_FRONTEND.experiences[_experienceId].tamperFunctions = window.FASTR_FRONTEND.experiences[_experienceId].tamperFunctions || [];\n\n// Called once _model is guaranteed to be set\nwindow.FASTR_FRONTEND.experiences[_experienceId].experienceInit = async () => {\n    if (window.FASTR_FRONTEND.experiences[_experienceId].experienceInitCalled) return;\n\n    window.FASTR_FRONTEND.experiences[_experienceId].experienceInitCalled = true;\n\n    window.FASTR_FRONTEND.experiences[_experienceId].runBackgroundIntegrations();\n\n    window.FASTR_FRONTEND.experiences[_experienceId].getCurrentScene = () => {\n        // If we're in pre-init, return scene 1\n        return window[_experienceId + \"_STATE\"] ? window[_experienceId + \"_STATE\"].viewer.experiences[experienceTypeId].currentSceneNumber : 1;\n    };\n\n    window.FASTR_FRONTEND.experiences[_experienceId].setCurrentScene = (sceneNumber) => {\n        window[_experienceId + \"_STATE\"].viewer.experiences[experienceTypeId].currentSceneNumber = sceneNumber;\n    };\n\n    window.FASTR_FRONTEND.experiences[_experienceId].reassertModel = () => {\n        window.ZMAGS_API.experiences[_experienceId].updateStateCallback(async () => {\n            return modelTamperPipeline(_model);\n        });\n    };\n\n    // Walk the model and find any custom actions or overlays that need to be updated\n    function updateWidgetActions(widgets) {\n        for (let widget of widgets) {\n            if (widget.action) {\n                if (widget.action.type === \"js\" && widget.action.code.startsWith(\"window.FASTR_FRONTEND.experiences[_experienceId].customActionHandler\")) {\n                    widget.action.code = widget.action.code.replace(\"_experienceId\", `\"${_experienceId}\"`);\n                } else if (widget.action.type === \"toggleOverlay\") {\n                    debugger;\n                    const widgetDomElement = domRef.querySelector(`div[id$='${widget.uuid}']`);\n\n                    let { parent, index } = findWidgetByUuid(_model, widget.uuid);\n\n                    const overlayResults = findWidgetByUuid(_model, widget.action.target);\n\n                    const overlayTarget = overlayResults.widget;\n\n                    function traverseAndUpdateWidgets(widgetList, originalWidget, parent, index) {\n                        for (const widget of widgetList) {\n                            // If widget type is composite, traverse its group\n                            if (widget.type === \"composite\" && widget.group) {\n                                traverseAndUpdateWidgets(widget.group, originalWidget, parent, index);\n                            } else {\n                                // Adjust the top and left properties of each widget\n                                widget.top += originalWidget.top;\n                                widget.left += originalWidget.left;\n\n                                // Add the widget to the parent\n                                parent.children.splice(index, 0, widget);\n                                index++;\n                            }\n                        }\n                    }\n\n                    function traverseAndAssignHandlers(widgetList, handler) {\n                        const observer = new MutationObserver((mutationsList) => {\n                            for (let mutation of mutationsList) {\n                                // Check the addedNodes property\n                                if (mutation.addedNodes) {\n                                    mutation.addedNodes.forEach((node) => {\n                                        if (node.id) {\n                                            widgetList.forEach((widget) => {\n                                                if (node.id.endsWith(widget.uuid)) {\n                                                    node.onmouseleave = handler;\n                                                }\n                                            });\n                                        }\n                                    });\n                                }\n                            }\n                        });\n\n                        // Start observing the document with the configured parameters\n                        observer.observe(domRef, { childList: true, subtree: true });\n\n                        for (const widget of widgetList) {\n                            // If widget type is composite, traverse its group\n                            if (widget.type === \"composite\" && widget.group) {\n                                traverseAndAssignHandlers(widget.group, handler);\n                            }\n                        }\n                    }\n\n                    if (widgetDomElement) {\n                        if (widget.action.triggerType === \"onHover\") {\n                            const targetLeave = () => {\n                                // Retrieve the original widget from the saved data\n                                const originalWidget = JSON.parse(widgetDomElement.dataset.originalWidget);\n\n                                // Remove overlay widgets from the parent\n                                if (parent && index !== null) {\n                                    parent.children.splice(index - overlayTarget.widgets.length, overlayTarget.widgets.length);\n                                }\n\n                                // Restore the original widget to its original position\n                                if (parent) {\n                                    parent.children.splice(index - overlayTarget.widgets.length, 0, originalWidget);\n                                }\n\n                                // Adjust the top and left properties of each child in overlayTarget's widgets array\n                                if (overlayTarget && overlayTarget.widgets) {\n                                    overlayTarget.widgets.forEach((overlayWidget) => {\n                                        overlayWidget.top += originalWidget.top;\n                                        overlayWidget.left += originalWidget.left;\n                                    });\n                                }\n\n                                // Reassert\n                                window.FASTR_FRONTEND.experiences[_experienceId].reassertModel();\n                            };\n\n                            widgetDomElement.onmouseenter = () => {\n                                // Keep a copy of the original widget\n                                const originalWidget = { ...widget };\n\n                                // Remove the widget from its parent\n                                if (parent && index !== null) {\n                                    parent.children.splice(index, 1);\n                                }\n\n                                // Swap all the elements of the widgets array in overlayTarget\n                                // as children of the original widget's parent\n                                if (parent && overlayTarget && overlayTarget.widgets) {\n                                    traverseAndUpdateWidgets(overlayTarget.widgets, originalWidget, parent, index);\n                                }\n\n                                // Save the original widget somewhere so we can restore it later\n                                widgetDomElement.dataset.originalWidget = JSON.stringify(originalWidget);\n\n                                // Reassert\n                                window.FASTR_FRONTEND.experiences[_experienceId].reassertModel();\n\n                                // Loop over widgets again to assign onmouseleave event handlers\n                                if (overlayTarget && overlayTarget.widgets) {\n                                    traverseAndAssignHandlers(overlayTarget.widgets, targetLeave);\n                                }\n                            };\n                        }\n                    }\n                }\n            }\n\n            if (widget.type === \"composite\") {\n                updateWidgetActions(widget.group);\n            }\n        }\n    }\n\n    // Start by iterating over scenes and calling the function on each scene's widgets\n    _model.scenes.forEach((scene) => updateWidgetActions(scene.widgets));\n};\n\nconst modelTamperPipeline = async (model) => {\n    if (model.id !== experienceId) return model;\n\n    // Set global _model for this experience\n    _model = model;\n\n    debug.breakpoint();\n\n    await window.FASTR_FRONTEND.experiences[_experienceId].experienceInit();\n\n    debug.log(\"Running model tamper pipeline for experience:\", experienceId);\n    for (const tamperFunction of window.FASTR_FRONTEND.experiences[_experienceId].tamperFunctions) {\n        model = await tamperFunction(model);\n    }\n\n    return model;\n};\n\n// If the init.js hasn't loaded the experience state yet, set this callback which will be run before the initial render\nconst modelCallbacks = window.FASTR_FRONTEND.experiences[_experienceId].modelCallbacks || window.ZMAGS_API.experiences[_experienceId].modelCallbacks || [];\nmodelCallbacks.push(modelTamperPipeline);\n\n// For compat, assert that state object on both FF and ZMAGS_API\nwindow.FASTR_FRONTEND.experiences[_experienceId].modelCallbacks = modelCallbacks;\nwindow.ZMAGS_API.experiences[_experienceId].modelCallbacks = modelCallbacks;\n\n// If the init.js has loaded the experience state, update the model on the window for use in rendering\nif (window[_experienceId + \"_STATE\"]) {\n    window[_experienceId + \"_STATE\"].viewer.experiences[experienceTypeId].model = await modelTamperPipeline(window[_experienceId + \"_STATE\"].viewer.experiences[experienceTypeId].model);\n    debug.log(\"Experience state loaded, model updated for experience:\", experienceId);\n}\n\nconst updateStateCallback = window.FASTR_FRONTEND.experiences[_experienceId].updateStateCallback || window.ZMAGS_API.experiences[_experienceId].updateStateCallback;\n\n// For compat, assert that state update fn on both FF and ZMAGS_API\nwindow.FASTR_FRONTEND.experiences[_experienceId].updateStateCallback = updateStateCallback;\nwindow.ZMAGS_API.experiences[_experienceId].updateStateCallback = updateStateCallback;\n\nwindow.FASTR_FRONTEND.experiences[_experienceId].registerModelTamperFunction = async (modelModificationConfig) => {\n    debug.log(`Registering model tamper function for experience: ${experienceId} and widget ${modelModificationConfig.widgetUuid}`);\n\n    const tamperFunctionWrapper = async (model) => {\n        try {\n            const { widget } = findWidgetByUuid(model, modelModificationConfig.widgetUuid);\n            if (!widget) return model;\n\n            const visibleScene = window.FASTR_FRONTEND.experiences[_experienceId].getCurrentScene();\n            if (visibleScene !== modelModificationConfig.sceneId) return model;\n\n            debug.log(\"Running integrations for experience:\", experienceId, \"and widget:\", modelModificationConfig.widgetUuid);\n\n            let integrationResult = {\n                get: function (path) {\n                    const parts = path.match(/\"(.*?)\"|[\\w\\s]+|\\d+/g);\n\n                    return parts.reduce((obj, key) => {\n                        if (isNaN(key)) key = key.replace(/\"/g, \"\");\n                        else key = Number(key);\n\n                        return obj[key];\n                    }, this);\n                },\n            };\n\n            const initialInput = await window.FASTR_FRONTEND.experiences[_experienceId].processGivenInputs(modelModificationConfig.gatherInputs);\n            for (const integration of modelModificationConfig.integrations) {\n                const inputJSON = { ...integrationResult, ...integration.input, ...initialInput };\n                const processedInput = await window.FASTR_FRONTEND.experiences[_experienceId].processIntegrationConfig(\n                    inputJSON,\n                    integration.processInputs,\n                    integration.processFunction,\n                    integration.processOutputs\n                );\n                integrationResult = { ...integrationResult, ...processedInput };\n            }\n\n            const tamperDescriptor = modelModificationConfig.tamperFunction;\n            debug.log(\"Calling tamper function \" + tamperDescriptor.type + \" for experience:\", experienceId, \"and widget:\", modelModificationConfig.widgetUuid);\n\n            if (!tamperDescriptor) {\n                debug.log(\"Ignoring undefined tamper function!\");\n                return model;\n            }\n\n            if (tamperDescriptor.conditions) {\n                debug.log(\"Calling condition function for experience:\", experienceId, \"and widget:\", modelModificationConfig.widgetUuid);\n\n                const conditionsPassed = await window.FASTR_FRONTEND.experiences[_experienceId].conditionFunction(integrationResult, modelModificationConfig.tamperFunction.conditions);\n                if (!conditionsPassed) return model;\n            }\n\n            await window.FASTR_FRONTEND.experiences[_experienceId].tamperLibrary[tamperDescriptor.type](integrationResult, widget, tamperDescriptor);\n        } catch (e) {\n            debug.error(\"Failed to process tamper function\");\n            debug.error(JSON.stringify(modelModificationConfig));\n            debug.error(e);\n        }\n        return model;\n    };\n\n    window.FASTR_FRONTEND.experiences[_experienceId].tamperFunctions.push(tamperFunctionWrapper);\n};\n\nfunction findWidgetByUuid(item, widgetUuid, parent = null) {\n    if (item.uuid === widgetUuid || item.id === widgetUuid) {\n        return { widget: item, parent, index: parent ? parent.children.indexOf(item) : null };\n    } else if (item.svgContent && item.svgContent.includes(widgetUuid)) {\n        return { widget: item, parent, index: parent ? parent.children.indexOf(item) : null };\n    }\n\n    const children = item.widgets || item.scenes || item.group;\n    if (children) {\n        item.children = children;\n        for (const child of children) {\n            const result = findWidgetByUuid(child, widgetUuid, item);\n            if (result) return result;\n        }\n    }\n\n    return null;\n}\n\nfunction findSceneByWidgetUuid(widgetUuid) {\n    for (let scene of _model.scenes) {\n        if (sceneContainsWidget(scene, widgetUuid)) {\n            return scene;\n        }\n    }\n    return null;\n}\n\nfunction sceneContainsWidget(scene, widgetUuid) {\n    const children = scene.widgets || scene.group;\n    if (children) {\n        for (const child of children) {\n            if (child.uuid === widgetUuid) {\n                return true;\n            }\n\n            if (child.type === \"composite\" && sceneContainsWidget(child, widgetUuid)) {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n// Function to run when a mutation is observed\nasync function handleVisibilityChange(mutationsList, observer) {\n    for (let mutation of mutationsList) {\n        if (mutation.type === \"attributes\" && mutation.attributeName === \"style\") {\n            const visibility = getComputedStyle(mutation.target).visibility;\n            if (visibility === \"visible\") {\n                const sceneNumber = parseInt(mutation.target.className.split(\"-\")[1]);\n\n                if (window.FASTR_FRONTEND.experiences[_experienceId].getCurrentScene() !== sceneNumber) {\n                    console.log(`Scene ${sceneNumber} is now visible.`);\n                    window.FASTR_FRONTEND.experiences[_experienceId].setCurrentScene(sceneNumber);\n                    window.FASTR_FRONTEND.experiences[_experienceId].reassertModel();\n                }\n            }\n        }\n    }\n}\n\n// Create an observer instance linked to the callback function\nconst observer = new MutationObserver(handleVisibilityChange);\n\n// Options for the observer (which mutations to observe)\nconst config = { attributes: true, attributeFilter: [\"style\"] };\n\n// Find scene divs and start observing them\nconst sceneDivs = Array.from(domRef.querySelectorAll('div[class^=\"scene-\"]')).filter((el) => {\n    const name = el.className.split(\"-\")[1];\n    return !isNaN(parseInt(name));\n});\nsceneDivs.forEach((sceneDiv) => observer.observe(sceneDiv, config));\n\n"}},"stateKey":"newTrigger"}]}]}}},"breakpointGroups":[],"queuedAnalyticsEvents":[{"companyId":"626fe4757826497d85d5678a","experienceId":"C3_e5e2f4b1-598d-802d-8003-41e0c805b8a4_e5e2f4b1-598d-802d-8003-41e0c805b8a5","experienceName":"Variant 1"}],"queuedGroupEvents":[],"initialLoad":false,"experienceFit":"width","isBehaviorExecuting":false,"activeExperienceId":"C3_e5e2f4b1-598d-802d-8003-41e0c805b8a4_e5e2f4b1-598d-802d-8003-41e0c805b8a5","activeExperience":{"currentSceneNumber":1,"transitionDirection":0,"model":{"companyId":"626fe4757826497d85d5678a","createdAt":1696955830262,"modifiedAt":1696955830262,"autoPlayDelay":0,"type":"Experience","language":"en-US","id":"C3_e5e2f4b1-598d-802d-8003-41e0c805b8a4_e5e2f4b1-598d-802d-8003-41e0c805b8a5","name":"Variant 1","width":5001,"height":1,"transition":"none","scenes":[{"id":"a0bd4374-c808-802a-8003-41e0c98cbdf2","sceneIndex":0,"sceneNumber":"1","widgets":[],"widgetTabOrder":[],"removedWidgetTabOrder":[],"customClasses":[null]}],"threshold":0,"backgroundScene":{"sceneNumber":"background","widgets":[],"widgetTabOrder":[],"removedWidgetTabOrder":[]},"overlayScene":{"sceneNumber":"overlay","widgets":[],"widgetTabOrder":[],"removedWidgetTabOrder":[]},"backdrop":{"type":"color","fit":"Experience","color":"#FFFFFF","image":null},"behaviors":[{"steps":[{"id":"","steps":[],"data":{"loadType":"executeJs","details":{"jsSnippet":"\n(function() {\n    window.FASTR_FRONTEND.experiences[_experienceId].tamperLibrary = {\n        setImageUrl: async function setImage(integrationResults, widget, args) {\n\n    debug.breakpoint();\n\n    const { source } = args;\n\n    const sourceData = integrationResults.get(source);\n\n    if (!sourceData) {\n        debug.error(`No data found for source ${source}`);\n    }\n\n    // Replace image-widget in SSR body\n    let widgetEl = domRef.querySelector(`div[id$='${widget.uuid}'] img`);\n\n    if (widgetEl) {\n        widgetEl.removeAttribute('srcset');\n        widgetEl.style.setProperty('object-fit', 'contain');\n        widgetEl.src = sourceData;\n\n        // Replace in the model\n        if (widget['src']) {\n            widget.src = sourceData;\n            widget.changeSource = \"behavior\";\n        }\n    } else {\n\n        // Replace image in svg SSR body\n        widgetEl = domRef.querySelector(`div[id$='${widget.uuid}'] svg`);\n\n        if (widgetEl) {\n            let imageEl = widgetEl.querySelector('image');\n            imageEl.setAttribute('href', sourceData);\n            imageEl.setAttribute('preserveAspectRatio', 'xMidYMid meet'); // This will ensure proportional scaling of the image\n\n            // Replace in the model\n            widget.svgContent = widgetEl.innerHTML;\n        }\n    }\n}\n,\n        setHref: async function setOnclick(integrationResults, widget, args) {\n\n    debug.breakpoint();\n\n    const { source } = args;\n\n    // Replace in the model\n    widget[\"action\"] = widget[\"action\"] || [];\n    widget[\"action\"].type = \"link\";\n    widget[\"action\"].url = integrationResults.get(source);\n}\n,\n        setSceneTarget: async function setOnclick(integrationResults, widget, args) {\n\n    debug.breakpoint();\n\n    const { source } = args;\n\n    // Replace in the model\n\n    // TODO: This sets the scene click target\n}\n,\n        setText: async function setText(integrationResults, widget, args) {\n\n    const replaceInTextNodes = function(node, searchText, replacementText, modifyOriginal = false) {\n        let tempNode;\n        if (typeof node === 'string') {\n            // We're working with the widget model\n            let tempDiv = document.createElement('div');\n            tempDiv.innerHTML = node;\n            tempNode = tempDiv;\n        } else {\n            // We're working with an actual DOM node\n            tempNode = modifyOriginal ? node : node.cloneNode(true);\n        }\n    \n        let nodeList = [];\n        let walker = document.createTreeWalker(tempNode, NodeFilter.SHOW_TEXT);\n        while (walker.nextNode()) nodeList.push(walker.currentNode);\n    \n        for (let textNode of nodeList) {\n            textNode.nodeValue = textNode.nodeValue.replaceAll(searchText, replacementText);\n        }\n    \n        return (typeof node === 'string') ? tempNode.innerHTML : tempNode;\n    }\n\n    debug.breakpoint();\n\n    const { source, placeholder } = args;\n\n    const widgetEl = domRef.querySelector(`div[id$='${widget.uuid}']`);\n\n    const sourceValue = integrationResults.get(source);\n\n    if (!sourceValue) {\n        debug.log(`No match found in integration results for ${source} for widget ${widget.uuid}`);\n        return;\n    }\n\n    if (placeholder) {\n        // Replace in the model\n        if (widget[\"text\"]) {\n            widget[\"text\"] = replaceInTextNodes(widget[\"text\"], placeholder, sourceValue);\n        }\n\n        // Replace in SSR body\n        replaceInTextNodes(widgetEl, placeholder, sourceValue, true);\n        \n    } else {\n        // Replace in the model\n        if (widget[\"text\"]) {\n            widget[\"text\"] = replaceInTextNodes(widget[\"text\"], widgetEl.textContent.trim(), sourceValue);\n        }\n\n        // Replace in SSR body\n        replaceInTextNodes(widgetEl, widgetEl.textContent.trim(), sourceValue, true);\n    }\n\n    widget.text = widgetEl.innerHTML;\n},\n        hideGroup: \nasync function hideGroup(_, __, args) {\n\n    debug.breakpoint();\n\n    const { placeholder } = args;\n\n    const widgets = placeholder.split(',');\n\n    for (const widgetUuid of widgets) {\n        const { widget } = findWidgetByUuid(_model, widgetUuid);\n        this.hideWidget(_, widget);\n    }\n    \n}\n,\n        hideWidget: \nasync function hideWidget(_, widget) {\n\n    debug.breakpoint();\n\n    const widgetUuuid = widget.uuid;\n    // Replace in SSR body\n    const widgetEl = domRef.querySelector(`div[id$='${widget.uuid}']`);\n    widgetEl.style.opacity = 0\n    widgetEl.style.display = 'none';\n\n    // Replace in the model\n    widget['opacity'] = \"0\";\n    \n}\n,\n        setAltText: \nasync function setAltText(integrationResults, widget, args) {\n\n    debug.breakpoint();\n\n    const { source } = args;\n\n    const txt = integrationResults.get(source);\n\n    // Replace in SSR body\n    const widgetEl = domRef.querySelector(`div[id$='${widget.uuid}']`);\n    widgetEl.setAttribute('aria-label', txt);\n\n    // If this is an image widget, also set the alt text\n    const img = widgetEl.querySelector('img');\n    if (img) {\n        img.setAttribute('alt', txt);\n    }\n\n    // Replace in the model\n    widget['alt'] = txt;\n    \n}\n,\n    };\n    window.FASTR_FRONTEND.experiences[_experienceId].conditionFunction = async function evaluateConditions(integrationResults, conditions) {\n    debug.breakpoint();\n\n    try {\n        for (let i = 0; i < conditions.length; i++) {\n            const condition = conditions[i];\n            const source = integrationResults.get(condition.lefthandPrefix);\n\n            let comparison;\n            if (condition.righthandType === \"static\") {\n                comparison = condition.righthandValue;\n            } else {\n                comparison = integrationResults.get(condition.righthandPrefix);\n            }\n\n            let comparisonNumber = Number(comparison);\n\n            // If comparison is wrapped in quotes, use it as the value\n            if (!isNaN(comparisonNumber)) {\n                comparison = comparisonNumber;\n            } else if ((comparison.startsWith(`\"`) && comparison.endsWith(`\"`)) || (comparison.startsWith(`'`) && comparison.endsWith(`'`))) {\n                comparison = comparison.slice(1, -1);\n\n                // Try converting to a number, if not possible use it as a string\n                const comparisonNumber = Number(comparison);\n                if (!isNaN(comparisonNumber)) {\n                    comparison = comparisonNumber;\n                }\n            }\n\n            const comparator = condition.comparator;\n\n            const comparators = {\n                \"<\": (a, b) => a < b,\n                \"<=\": (a, b) => a <= b,\n                \"==\": (a, b) => a == b,\n                \">=\": (a, b) => a >= b,\n                \">\": (a, b) => a > b,\n                \"!=\": (a, b) => a != b,\n                equals: (a, b) => a === b,\n                includes: (a, b) => a.includes(b),\n                doesNotContain: (a, b) => !a.includes(b),\n                startsWith: (a, b) => a.startsWith(b),\n                endsWith: (a, b) => a.endsWith(b),\n                is: (a, b) => a == b,\n                isNot: (a, b) => a != b,\n            };\n\n            const comparisonFunction = comparators[comparator];\n            if (!comparisonFunction) throw new Error(`Invalid comparator: ${comparator}`);\n\n            debug.log(`Comparing: ${source} ${comparator} ${comparison}`);\n\n            const result = comparisonFunction(source, comparison);\n\n            debug.log(`The result of the comparison is ${result}`);\n\n            if (!result) return false;\n        }\n\n        return true;\n    } catch (error) {\n        console.log(`Failed to evaluate conditions: ${error}`);\n    }\n\n    return false;\n}\n;\n\n    modelModificationConfigs.forEach((config) => {\n        try {\n            if (config.gatherInputs) {\n                config.gatherInputs = eval(\"(\" + config.gatherInputs + \")()\");\n            }\n            config.integrations.forEach((integration) => {\n                integration.processInputs = eval(\"(\" + integration.processInputs + \")()\");\n                integration.processFunction = eval(\"(\" + integrations[integration.processFunction] + \")()\");\n                integration.processOutputs = eval(\"(\" + integration.processOutputs + \")()\");\n            });\n        } catch (e) {\n            debug.error(\"Error setting up integration functions\");\n            debug.error(e);\n        }\n\n        window.FASTR_FRONTEND.experiences[_experienceId].registerModelTamperFunction(config);\n    });\n\n})();\n","jsSnippetMetaData":"\nconst _experienceId = \"BEHAVIOR_EXPERIENCEID_PLACEHOLDER\";\n\nconst lastUnderscoreIndex = _experienceId.lastIndexOf(\"_\");\n\nconst experienceTypeId = _experienceId.substring(0, lastUnderscoreIndex);\nconst experienceOrdinal = _experienceId.substring(lastUnderscoreIndex + 1);\n\nlet _model = undefined;\n\nwindow.ZMAGS_API = window.ZMAGS_API || { experiences: {} };\nwindow.ZMAGS_API.experiences[_experienceId] = window.ZMAGS_API.experiences[_experienceId] || {};\n\ntry {\n    window.FASTR_FRONTEND = window.FASTR_FRONTEND || {};\n    window.FASTR_FRONTEND.experiences = window.FASTR_FRONTEND.experiences || {};\n    window.FASTR_FRONTEND.experiences[_experienceId] = window.FASTR_FRONTEND.experiences[_experienceId] || {};\n\n    // Pull properties from ZMAGS_API to FASTR_FRONTEND.\n    // NOTE: this sets us up to deprecate ZMAGS_API from viewer in the future\n    for (const prop in window.ZMAGS_API.experiences[_experienceId]) {\n        if (!window.FASTR_FRONTEND.experiences[_experienceId].hasOwnProperty(prop)) {\n            window.FASTR_FRONTEND.experiences[_experienceId][prop] = window.ZMAGS_API.experiences[_experienceId][prop];\n        }\n    }\n} catch (error) {\n    console.error(error);\n}\n\nconst domRef = document.querySelector(`div[data-experience='${experienceTypeId}'][data-ordinal='${experienceOrdinal}']`);\n\nconst getFASTR_FRONTEND_DEBUG = () => {\n    try {\n        return JSON.parse(localStorage.getItem(\"FASTR_FRONTEND_DEBUG\"));\n    } catch (error) {\n        console.warn(\"Error parsing FASTR_FRONTEND_DEBUG from localStorage:\", error);\n        return {};\n    }\n};\n\nconst FASTR_FRONTEND_DEBUG = getFASTR_FRONTEND_DEBUG();\n\nconst debug = {\n    output: (type, message, ...optionalParams) => {\n        if (FASTR_FRONTEND_DEBUG && FASTR_FRONTEND_DEBUG.trace) {\n            console[type](`[FASTR_FRONTEND_DEBUG] ${message}`, ...optionalParams);\n        }\n    },\n    log: (message, ...optionalParams) => {\n        debug.output(\"log\", message, ...optionalParams);\n    },\n    error: (message, ...optionalParams) => {\n        debug.output(\"error\", message, ...optionalParams);\n    },\n    breakpoint: () => {\n        if (FASTR_FRONTEND_DEBUG && FASTR_FRONTEND_DEBUG.break) {\n            debugger;\n        }\n    },\n};\n\nwindow.FASTR_FRONTEND.experiences[_experienceId].dumpState = () => {\n    const experienceIdentifier = _experienceId.split(\"_\").slice(0, -1).join(\"_\");\n    const model = window[_experienceId + \"_STATE\"].viewer.experiences[experienceIdentifier].model;\n    const modelSizeInKB = JSON.stringify(model).length / 1024;\n    console.log(`Model size for experience ${_experienceId}: ${modelSizeInKB.toFixed(2)} KB`);\n};\n\n\nconst modelModificationConfigs = [];\n\nconst backgroundIntegrations = [\n   \"\\t\\twindow.ZMAGS_ANALYTICS_API = window.ZMAGS_ANALYTICS_API || {};\\n\\t\\tZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE = {};\\n\\t\\tZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER = null;\\n\\t\\tZMAGS_ANALYTICS_API.PLUS_ICONS = [\\\"\\\"]\\n\\t\\tZMAGS_ANALYTICS_API.MUTATION_OBSERVER = null;\\n\\t\\tZMAGS_ANALYTICS_API.EXPERIENCE_TYPES = ['']\\n\\t\\tZMAGS_ANALYTICS_API.EVENTS_FIRED = ZMAGS_ANALYTICS_API?.EVENTS_FIRED || {\\n\\t\\t\\t\\\"click_events\\\": []\\n\\t\\t};\\n\\n\\t\\tZMAGS_ANALYTICS_API.getExperienceName = function (contextWindow, experienceId) {\\n\\t\\t\\tif (!ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[experienceId]) {\\n\\t\\t\\t\\tconst expname = contextWindow?.[`${experienceId}_1_STATE`]?.viewer?.experiences?.[experienceId]?.model?.name || \\\"\\\";\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[experienceId] = expname;\\n\\t\\t\\t}\\n\\t\\t\\treturn ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[experienceId];\\n\\t\\t}\\n\\n\\t\\tZMAGS_ANALYTICS_API.customEvent = function (name, args) {\\n\\t\\t\\ttop.window.dataLayer = top.window.dataLayer || [];\\n\\t\\t\\tlet data = { event: name };\\n\\t\\t\\tfor (let key in args) {\\n\\t\\t\\t\\tif (args.hasOwnProperty(key)) {\\n\\t\\t\\t\\t\\tdata[key] = args[key];\\n\\t\\t\\t\\t}\\n\\t\\t\\t}\\n\\t\\t\\ttop.window.dataLayer.push(data);\\n\\t\\t}\\n\\n\\t\\tZMAGS_ANALYTICS_API.trackLink = function (link, experienceName, clickPlus = false) {\\n\\t\\t\\tconst url = link.href;\\n\\t\\t\\tconst scene = link.closest(\\\"[class^='scene-']\\\")?.className || \\\"\\\";\\n\\t\\t\\tconst event = {\\n\\t\\t\\t\\t\\\"fastr_exp\\\": experienceName,\\n\\t\\t\\t\\t\\\"fastr_exp_type\\\": ZMAGS_ANALYTICS_API.EXPERIENCE_TYPES.filter(name => experienceName.toLocaleUpperCase().includes(name))[0] || \\\"experience\\\",\\n\\t\\t\\t\\t\\\"fastr_int\\\": clickPlus ? \\\"link_click\\\" : \\\"click\\\",\\n\\t\\t\\t\\t\\\"fastr_scene\\\": scene,\\n\\t\\t\\t\\t\\\"fastr_url\\\": url\\n\\t\\t\\t}\\n\\t\\t\\tif (!document.hidden) {\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.customEvent(\\\"fastr\\\", event);\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.EVENTS_FIRED['click_events'].push(event)\\n\\t\\t\\t}\\n\\t\\t};\\n\\n\\t\\tZMAGS_ANALYTICS_API.trackSceneChange = function (viewerContainer, experienceId, experienceName) {\\n\\t\\t\\tconst activeScene = viewerContainer.querySelector('[class^=\\\"scene-\\\"][style*=\\\"visibility: inherit\\\"][style*=\\\"opacity:\\\"]:not(.scene-background, .scene-overlay), [class^=\\\"scene-\\\"][style*=\\\"visibility: visible\\\"]:not(.scene-background, .scene-overlay),  [class^=\\\"scene-\\\"][style*=\\\"visibility:visible\\\"]:not(.scene-background, .scene-overlay)')?.className || \\\"\\\";\\n\\t\\t\\tconst ordinal = viewerContainer?.dataset?.ordinal || 1\\n\\t\\t\\tconsole.log(`experienceId: `, experienceId, activeScene)\\n\\t\\t\\tif (!ZMAGS_ANALYTICS_API.EVENTS_FIRED[`${experienceId}_${ordinal}_scene_${activeScene}`] && !document.hidden) {\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.customEvent(\\\"zmags\\\", {\\n\\t\\t\\t\\t\\t\\\"fastr_exp\\\": experienceName,\\n\\t\\t\\t\\t\\t\\\"fastr_exp_type\\\": ZMAGS_ANALYTICS_API.EXPERIENCE_TYPES.filter(name => experienceName.toLocaleUpperCase().includes(name))[0] || \\\"experience\\\",\\n\\t\\t\\t\\t\\t\\\"fastr_int\\\": \\\"view\\\",\\n\\t\\t\\t\\t\\t\\\"fastr_scene\\\": activeScene\\n\\t\\t\\t\\t});\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.EVENTS_FIRED[`${experienceId}_${ordinal}_scene_${activeScene}`] = true\\n\\t\\t\\t}\\n\\t\\t}\\n\\n\\t\\tZMAGS_ANALYTICS_API.trackExperienceSceneView = function (viewerContainer, experienceId) {\\n\\t\\t\\tconst observerConfig = {\\n\\t\\t\\t\\tattributes: true,\\n\\t\\t\\t\\tchildList: true,\\n\\t\\t\\t};\\n\\t\\t\\tconst sceneContaniers = Array.from(viewerContainer.querySelectorAll('.scene-background') || []).map(ele => {\\n\\t\\t\\t\\tif (!ele.parentNode.dataset.analyticsInitiated) {\\n\\t\\t\\t\\t\\tele.parentNode.dataset.experienceId = experienceId\\n\\t\\t\\t\\t\\tele.parentNode.dataset.analyticsInitiated = true\\n\\t\\t\\t\\t\\treturn ele.parentNode\\n\\t\\t\\t\\t}\\n\\t\\t\\t\\treturn null\\n\\t\\t\\t}).filter(ele => ele !== null)\\n\\t\\t\\tsceneContaniers.forEach((container) => {\\n\\t\\t\\t\\tconst experienceId = container.dataset.experienceId;\\n\\t\\t\\t\\tconst experienceName = ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[`${experienceId}`]\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackSceneChange(container, experienceId, experienceName)\\n\\t\\t\\t\\tcontainer.addEventListener(\\\"click\\\", function (event) {\\n\\t\\t\\t\\t\\tconst clickPlus = ZMAGS_ANALYTICS_API.PLUS_ICONS.filter(img => (event.target.src || \\\"\\\")?.toLowerCase()?.includes(img)).length > 0\\n\\t\\t\\t\\t\\tconst anchorTag = event.target?.closest('a') || {}\\n\\t\\t\\t\\t\\tif (anchorTag.tagName === \\\"A\\\") {\\n\\t\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackLink(anchorTag, `${experienceId} - ${experienceName}`, clickPlus)\\n\\t\\t\\t\\t\\t}\\n\\t\\t\\t\\t})\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.MUTATION_OBSERVER.observe(container, observerConfig)\\n\\t\\t\\t})\\n\\t\\t}\\n\\n\\t\\tZMAGS_ANALYTICS_API.setupTracking = function (context, contextWindow) {\\n\\t\\t\\tconst viewerContainers = context.querySelectorAll('[class^=\\\"zmags-viewer-container\\\"][data-zmags-initiated]:not([data-analytics-initiated])');\\n\\n\\t\\t\\tfor (let viewerContainer of viewerContainers) {\\n\\t\\t\\t\\tconst experienceId = viewerContainer.dataset.experience;\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.getExperienceName(contextWindow, experienceId);\\n\\t\\t\\t\\tZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER.observe(viewerContainer)\\n\\t\\t\\t\\tviewerContainer.dataset.analyticsInitiated = true\\n\\t\\t\\t}\\n\\t\\t\\tconst iframes = Array.from(context.querySelectorAll(\\\"iframe:not([data-analytics-error])\\\"))\\n\\t\\t\\tiframes.forEach(function (iframe) {\\n\\t\\t\\t\\ttry {\\n\\t\\t\\t\\t\\tconst iframeDoc = iframe.contentDocument || iframe.contentWindow.document;\\n\\t\\t\\t\\t\\tconst iframeWindow = iframe.contentWindow;\\n\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.setupTracking(iframeDoc, iframeWindow);\\n\\t\\t\\t\\t} catch (e) {\\n\\t\\t\\t\\t\\tiframe.dataset.analyticsError = true\\n\\t\\t\\t\\t\\tconsole.log(\\\"Failed to access iframe, probably because it is from a different origin.\\\");\\n\\t\\t\\t\\t}\\n\\t\\t\\t});\\n\\t\\t}\\n\\n\\t\\tfunction isElementInViewport(element) {\\n\\t\\t\\tconst sceneContainer = element.querySelectorAll('[class=\\\"scene-background\\\"]')[0] || element\\n\\t\\t\\tconst rect = sceneContainer.getBoundingClientRect();\\n\\t\\t\\tconst viewportHeight = window.innerHeight || document.documentElement.clientHeight;\\n\\t\\t\\treturn ((rect.top >= 0 && rect.top <= viewportHeight) || (rect.bottom >= 0 && rect.bottom <= viewportHeight));\\n\\t\\t}\\n\\n\\t\\tconst mutationObserver = ZMAGS_ANALYTICS_API.MUTATION_OBSERVER || new MutationObserver((mutationsList, observer) => {\\n\\t\\t\\tconst mutation = mutationsList[0]\\n\\t\\t\\tif (!mutation) return;\\n\\t\\t\\tconst viewerContainer = mutation.target\\n\\t\\t\\tconst experienceId = viewerContainer.dataset.experienceId;\\n\\t\\t\\tconst experienceName = ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[`${experienceId}`]\\n\\t\\t\\tif (isElementInViewport(viewerContainer)) {\\n\\t\\t\\t\\tsetTimeout(() => {\\n\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackSceneChange(viewerContainer, experienceId, experienceName)\\n\\t\\t\\t\\t}, 500)\\n\\t\\t\\t}\\n\\t\\t});\\n\\n\\t\\tif (!ZMAGS_ANALYTICS_API.MUTATION_OBSERVER) ZMAGS_ANALYTICS_API.MUTATION_OBSERVER = mutationObserver;\\n\\n\\t\\tfunction handleIntersection(entries) {\\n\\t\\t\\tentries.forEach(entry => {\\n\\t\\t\\t\\tif (entry.isIntersecting) {\\n\\t\\t\\t\\t\\tconst viewerContainer = entry.target\\n\\t\\t\\t\\t\\tconst experienceId = viewerContainer.dataset.experience;\\n\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackSceneChange(viewerContainer, experienceId, ZMAGS_ANALYTICS_API.EXPERIENCE_NAME_CACHE[`${experienceId}`])\\n\\t\\t\\t\\t\\tZMAGS_ANALYTICS_API.trackExperienceSceneView(entry.target, experienceId)\\n\\t\\t\\t\\t}\\n\\t\\t\\t});\\n\\t\\t}\\n\\n\\t\\tconst options = {\\n\\t\\t\\troot: null,\\n\\t\\t\\trootMargin: '0px',\\n\\t\\t\\tthreshold: 0\\n\\t\\t};\\n\\n\\t\\tconst observer = ZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER || new IntersectionObserver(handleIntersection, options);\\n\\n\\t\\tif (!ZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER) ZMAGS_ANALYTICS_API.INTERSECTION_OBSERVER = observer\\n\\n\\n\\t\\tsetInterval(function () {\\n\\t\\t\\tZMAGS_ANALYTICS_API.setupTracking(document, window);\\n\\t\\t}, 500);\"\n];\n\nconst functionIntegrations = {};\n\nconst integrations = {};\n\nwindow.FASTR_FRONTEND = window.FASTR_FRONTEND || {};\nwindow.FASTR_FRONTEND.experiences = window.FASTR_FRONTEND.experiences || {};\n\nwindow.FASTR_FRONTEND.experiences[_experienceId] = {\n    initialTimestamp: performance.now(),\n    memoCache: new Map(),\n    pendingCache: new Map(),\n    performanceCounters: {\n        memoizedFunctions: 0,\n        cacheHits: 0,\n        cacheMisses: 0,\n        callDurations: [],\n        keyMapping: {},\n    },\n    hashFnv32a: function (str, seed = 0x811c9dc5) {\n        let h = seed;\n        for (let i = 0; i < str.length; i++) {\n            h ^= str.charCodeAt(i);\n            h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);\n        }\n        return h >>> 0;\n    },\n    memoize: function (fn) {\n        this.performanceCounters.memoizedFunctions++;\n        debug.log(`Memoized function created. Total: ${this.performanceCounters.memoizedFunctions}`);\n\n        return async (...args) => {\n            const fnString = fn.toString();\n            const key = JSON.stringify({ fnString, args });\n            const hashedKey = this.hashFnv32a(key);\n            const start = performance.now();\n\n            this.performanceCounters.keyMapping[hashedKey] = key;\n\n            if (this.memoCache.has(hashedKey)) {\n                this.performanceCounters.cacheHits++;\n                const end = performance.now();\n                debug.log(`Cache hit for key ${hashedKey}. Total hits: ${this.performanceCounters.cacheHits}`);\n                this.performanceCounters.callDurations.push({\n                    key: hashedKey,\n                    type: \"hit\",\n                    duration: end - start,\n                    start,\n                });\n                return this.memoCache.get(hashedKey);\n            }\n\n            if (this.pendingCache.has(hashedKey)) {\n                this.performanceCounters.waitingCalls++;\n                const result = await this.pendingCache.get(hashedKey);\n                this.performanceCounters.cacheHits++;\n                debug.log(`Pending cache hit for key ${hashedKey}. Total hits: ${this.performanceCounters.cacheHits}`);\n                const end = performance.now();\n                this.performanceCounters.callDurations.push({\n                    key: hashedKey,\n                    type: \"awaiting cache\",\n                    duration: end - start,\n                    start,\n                });\n                return result;\n            }\n\n            this.performanceCounters.cacheMisses++;\n            debug.log(`Cache miss for key ${hashedKey}. Total misses: ${this.performanceCounters.cacheMisses}`);\n            const promise = fn(...args);\n            this.pendingCache.set(hashedKey, promise);\n            const result = await promise;\n            this.memoCache.set(hashedKey, result);\n            this.pendingCache.delete(hashedKey);\n\n            const end = performance.now();\n            this.performanceCounters.totalDuration += end - start;\n            debug.log(`Memoized function ${fn.name} took ${end - start} milliseconds to execute. Total duration: ${this.performanceCounters.totalDuration}`);\n            this.performanceCounters.callDurations.push({\n                key: hashedKey,\n                type: \"miss\",\n                duration: end - start,\n                start,\n            });\n            return result;\n        };\n    },\n    runPerformanceTrace: function () {\n        const { callDurations, keyMapping } = this.performanceCounters;\n\n        const sortedDurations = callDurations.sort((a, b) => a.start - b.start);\n\n        sortedDurations.forEach(({ key, type, duration, start }) => {\n            const { fnString, args } = JSON.parse(keyMapping[key]);\n\n            let strippedFnString = fnString.replace(/async\\s+function\\s*\\(\\s*input\\s*\\)\\s*\\{\\s*/, \"\").replace(/\\n/g, \"\");\n\n            const shortenedFnString = strippedFnString.length > 90 ? strippedFnString.substring(0, 87) + \"...\" : strippedFnString;\n            const argsString = JSON.stringify(args).replace(/\\n/g, \"\");\n            const shortenedArgsString = argsString.length > 90 ? argsString.substring(0, 87) + \"...\" : argsString;\n\n            const fullKey = `Body: [${shortenedFnString}] Args: [${shortenedArgsString}]`;\n\n            const elapsedTime = start - this.initialTimestamp;\n            const minutes = Math.floor(elapsedTime / 60000);\n            const seconds = ((elapsedTime % 60000) / 1000).toFixed(3);\n            console.log(`[${minutes}:${seconds}] [${type}] [${key}] ${fullKey}: ${Math.round(duration)}ms`);\n        });\n    },\n    processGivenInputs: async function (inputFn) {\n        const memoizedProcessInputs = this.memoize(inputFn);\n        debug.log(\"Gathering inputs with :\", inputFn);\n        const generatedInput = await memoizedProcessInputs();\n        return generatedInput;\n    },\n    processIntegrationConfig: async function (inputJSON, processInputsFn, processFunctionFn, processOutputsFn) {\n        const memoizedProcessInputs = this.memoize(processInputsFn);\n        const memoizedProcessFunction = this.memoize(processFunctionFn);\n        const memoizedProcessOutputs = this.memoize(processOutputsFn);\n\n        debug.log(\"Processing inputs with inputJSON:\", inputJSON);\n        const processedInput = await memoizedProcessInputs(inputJSON);\n\n        debug.log(\"Executing process function with processedInput:\", processedInput);\n        const output = await memoizedProcessFunction(processedInput);\n\n        debug.log(\"Processing outputs with output:\", output);\n        const outputJSON = await memoizedProcessOutputs(output);\n\n        debug.log(\"Returning outputJSON:\", outputJSON);\n        return outputJSON;\n    },\n    runBackgroundIntegrations: async function () {\n        debug.breakpoint();\n\n        for (const integration of backgroundIntegrations) {\n            const body = integration;\n            try {\n                const func = new Function(\"model\", body);\n                await func(_model);\n            } catch (error) {\n                debug.error(\"Failed to run background integration\");\n                debug.error(JSON.stringify(body));\n                debug.error(error);\n            }\n        }\n    },\n    customActionHandler: async function (integrationId, args) {\n        debug.breakpoint();\n\n        const fn = functionIntegrations[integrationId];\n\n        const wrappedFn =\n`(async function customAction(inputs, model) {\n    ${fn}\n})(inputs, model);`;\n\n        // Flatten our inputs down to a simple object\n        const inputs = args ? Object.fromEntries(args.map(({name, value}) => [name, value])) : {};\n\n        try {\n            const func = new Function(\"inputs\", \"model\", wrappedFn);\n            await func(inputs, _model);\n        } catch (error) {\n            debug.error(\"Failed to run custom action integration\");\n            debug.error(JSON.stringify(fn));\n            debug.error(error);\n        }\n    },\n};\n\nwindow.FASTR_FRONTEND.experiences[_experienceId].tamperFunctions = window.FASTR_FRONTEND.experiences[_experienceId].tamperFunctions || [];\n\n// Called once _model is guaranteed to be set\nwindow.FASTR_FRONTEND.experiences[_experienceId].experienceInit = async () => {\n    if (window.FASTR_FRONTEND.experiences[_experienceId].experienceInitCalled) return;\n\n    window.FASTR_FRONTEND.experiences[_experienceId].experienceInitCalled = true;\n\n    window.FASTR_FRONTEND.experiences[_experienceId].runBackgroundIntegrations();\n\n    window.FASTR_FRONTEND.experiences[_experienceId].getCurrentScene = () => {\n        // If we're in pre-init, return scene 1\n        return window[_experienceId + \"_STATE\"] ? window[_experienceId + \"_STATE\"].viewer.experiences[experienceTypeId].currentSceneNumber : 1;\n    };\n\n    window.FASTR_FRONTEND.experiences[_experienceId].setCurrentScene = (sceneNumber) => {\n        window[_experienceId + \"_STATE\"].viewer.experiences[experienceTypeId].currentSceneNumber = sceneNumber;\n    };\n\n    window.FASTR_FRONTEND.experiences[_experienceId].reassertModel = () => {\n        window.ZMAGS_API.experiences[_experienceId].updateStateCallback(async () => {\n            return modelTamperPipeline(_model);\n        });\n    };\n\n    // Walk the model and find any custom actions or overlays that need to be updated\n    function updateWidgetActions(widgets) {\n        for (let widget of widgets) {\n            if (widget.action) {\n                if (widget.action.type === \"js\" && widget.action.code.startsWith(\"window.FASTR_FRONTEND.experiences[_experienceId].customActionHandler\")) {\n                    widget.action.code = widget.action.code.replace(\"_experienceId\", `\"${_experienceId}\"`);\n                } else if (widget.action.type === \"toggleOverlay\") {\n                    debugger;\n                    const widgetDomElement = domRef.querySelector(`div[id$='${widget.uuid}']`);\n\n                    let { parent, index } = findWidgetByUuid(_model, widget.uuid);\n\n                    const overlayResults = findWidgetByUuid(_model, widget.action.target);\n\n                    const overlayTarget = overlayResults.widget;\n\n                    function traverseAndUpdateWidgets(widgetList, originalWidget, parent, index) {\n                        for (const widget of widgetList) {\n                            // If widget type is composite, traverse its group\n                            if (widget.type === \"composite\" && widget.group) {\n                                traverseAndUpdateWidgets(widget.group, originalWidget, parent, index);\n                            } else {\n                                // Adjust the top and left properties of each widget\n                                widget.top += originalWidget.top;\n                                widget.left += originalWidget.left;\n\n                                // Add the widget to the parent\n                                parent.children.splice(index, 0, widget);\n                                index++;\n                            }\n                        }\n                    }\n\n                    function traverseAndAssignHandlers(widgetList, handler) {\n                        const observer = new MutationObserver((mutationsList) => {\n                            for (let mutation of mutationsList) {\n                                // Check the addedNodes property\n                                if (mutation.addedNodes) {\n                                    mutation.addedNodes.forEach((node) => {\n                                        if (node.id) {\n                                            widgetList.forEach((widget) => {\n                                                if (node.id.endsWith(widget.uuid)) {\n                                                    node.onmouseleave = handler;\n                                                }\n                                            });\n                                        }\n                                    });\n                                }\n                            }\n                        });\n\n                        // Start observing the document with the configured parameters\n                        observer.observe(domRef, { childList: true, subtree: true });\n\n                        for (const widget of widgetList) {\n                            // If widget type is composite, traverse its group\n                            if (widget.type === \"composite\" && widget.group) {\n                                traverseAndAssignHandlers(widget.group, handler);\n                            }\n                        }\n                    }\n\n                    if (widgetDomElement) {\n                        if (widget.action.triggerType === \"onHover\") {\n                            const targetLeave = () => {\n                                // Retrieve the original widget from the saved data\n                                const originalWidget = JSON.parse(widgetDomElement.dataset.originalWidget);\n\n                                // Remove overlay widgets from the parent\n                                if (parent && index !== null) {\n                                    parent.children.splice(index - overlayTarget.widgets.length, overlayTarget.widgets.length);\n                                }\n\n                                // Restore the original widget to its original position\n                                if (parent) {\n                                    parent.children.splice(index - overlayTarget.widgets.length, 0, originalWidget);\n                                }\n\n                                // Adjust the top and left properties of each child in overlayTarget's widgets array\n                                if (overlayTarget && overlayTarget.widgets) {\n                                    overlayTarget.widgets.forEach((overlayWidget) => {\n                                        overlayWidget.top += originalWidget.top;\n                                        overlayWidget.left += originalWidget.left;\n                                    });\n                                }\n\n                                // Reassert\n                                window.FASTR_FRONTEND.experiences[_experienceId].reassertModel();\n                            };\n\n                            widgetDomElement.onmouseenter = () => {\n                                // Keep a copy of the original widget\n                                const originalWidget = { ...widget };\n\n                                // Remove the widget from its parent\n                                if (parent && index !== null) {\n                                    parent.children.splice(index, 1);\n                                }\n\n                                // Swap all the elements of the widgets array in overlayTarget\n                                // as children of the original widget's parent\n                                if (parent && overlayTarget && overlayTarget.widgets) {\n                                    traverseAndUpdateWidgets(overlayTarget.widgets, originalWidget, parent, index);\n                                }\n\n                                // Save the original widget somewhere so we can restore it later\n                                widgetDomElement.dataset.originalWidget = JSON.stringify(originalWidget);\n\n                                // Reassert\n                                window.FASTR_FRONTEND.experiences[_experienceId].reassertModel();\n\n                                // Loop over widgets again to assign onmouseleave event handlers\n                                if (overlayTarget && overlayTarget.widgets) {\n                                    traverseAndAssignHandlers(overlayTarget.widgets, targetLeave);\n                                }\n                            };\n                        }\n                    }\n                }\n            }\n\n            if (widget.type === \"composite\") {\n                updateWidgetActions(widget.group);\n            }\n        }\n    }\n\n    // Start by iterating over scenes and calling the function on each scene's widgets\n    _model.scenes.forEach((scene) => updateWidgetActions(scene.widgets));\n};\n\nconst modelTamperPipeline = async (model) => {\n    if (model.id !== experienceId) return model;\n\n    // Set global _model for this experience\n    _model = model;\n\n    debug.breakpoint();\n\n    await window.FASTR_FRONTEND.experiences[_experienceId].experienceInit();\n\n    debug.log(\"Running model tamper pipeline for experience:\", experienceId);\n    for (const tamperFunction of window.FASTR_FRONTEND.experiences[_experienceId].tamperFunctions) {\n        model = await tamperFunction(model);\n    }\n\n    return model;\n};\n\n// If the init.js hasn't loaded the experience state yet, set this callback which will be run before the initial render\nconst modelCallbacks = window.FASTR_FRONTEND.experiences[_experienceId].modelCallbacks || window.ZMAGS_API.experiences[_experienceId].modelCallbacks || [];\nmodelCallbacks.push(modelTamperPipeline);\n\n// For compat, assert that state object on both FF and ZMAGS_API\nwindow.FASTR_FRONTEND.experiences[_experienceId].modelCallbacks = modelCallbacks;\nwindow.ZMAGS_API.experiences[_experienceId].modelCallbacks = modelCallbacks;\n\n// If the init.js has loaded the experience state, update the model on the window for use in rendering\nif (window[_experienceId + \"_STATE\"]) {\n    window[_experienceId + \"_STATE\"].viewer.experiences[experienceTypeId].model = await modelTamperPipeline(window[_experienceId + \"_STATE\"].viewer.experiences[experienceTypeId].model);\n    debug.log(\"Experience state loaded, model updated for experience:\", experienceId);\n}\n\nconst updateStateCallback = window.FASTR_FRONTEND.experiences[_experienceId].updateStateCallback || window.ZMAGS_API.experiences[_experienceId].updateStateCallback;\n\n// For compat, assert that state update fn on both FF and ZMAGS_API\nwindow.FASTR_FRONTEND.experiences[_experienceId].updateStateCallback = updateStateCallback;\nwindow.ZMAGS_API.experiences[_experienceId].updateStateCallback = updateStateCallback;\n\nwindow.FASTR_FRONTEND.experiences[_experienceId].registerModelTamperFunction = async (modelModificationConfig) => {\n    debug.log(`Registering model tamper function for experience: ${experienceId} and widget ${modelModificationConfig.widgetUuid}`);\n\n    const tamperFunctionWrapper = async (model) => {\n        try {\n            const { widget } = findWidgetByUuid(model, modelModificationConfig.widgetUuid);\n            if (!widget) return model;\n\n            const visibleScene = window.FASTR_FRONTEND.experiences[_experienceId].getCurrentScene();\n            if (visibleScene !== modelModificationConfig.sceneId) return model;\n\n            debug.log(\"Running integrations for experience:\", experienceId, \"and widget:\", modelModificationConfig.widgetUuid);\n\n            let integrationResult = {\n                get: function (path) {\n                    const parts = path.match(/\"(.*?)\"|[\\w\\s]+|\\d+/g);\n\n                    return parts.reduce((obj, key) => {\n                        if (isNaN(key)) key = key.replace(/\"/g, \"\");\n                        else key = Number(key);\n\n                        return obj[key];\n                    }, this);\n                },\n            };\n\n            const initialInput = await window.FASTR_FRONTEND.experiences[_experienceId].processGivenInputs(modelModificationConfig.gatherInputs);\n            for (const integration of modelModificationConfig.integrations) {\n                const inputJSON = { ...integrationResult, ...integration.input, ...initialInput };\n                const processedInput = await window.FASTR_FRONTEND.experiences[_experienceId].processIntegrationConfig(\n                    inputJSON,\n                    integration.processInputs,\n                    integration.processFunction,\n                    integration.processOutputs\n                );\n                integrationResult = { ...integrationResult, ...processedInput };\n            }\n\n            const tamperDescriptor = modelModificationConfig.tamperFunction;\n            debug.log(\"Calling tamper function \" + tamperDescriptor.type + \" for experience:\", experienceId, \"and widget:\", modelModificationConfig.widgetUuid);\n\n            if (!tamperDescriptor) {\n                debug.log(\"Ignoring undefined tamper function!\");\n                return model;\n            }\n\n            if (tamperDescriptor.conditions) {\n                debug.log(\"Calling condition function for experience:\", experienceId, \"and widget:\", modelModificationConfig.widgetUuid);\n\n                const conditionsPassed = await window.FASTR_FRONTEND.experiences[_experienceId].conditionFunction(integrationResult, modelModificationConfig.tamperFunction.conditions);\n                if (!conditionsPassed) return model;\n            }\n\n            await window.FASTR_FRONTEND.experiences[_experienceId].tamperLibrary[tamperDescriptor.type](integrationResult, widget, tamperDescriptor);\n        } catch (e) {\n            debug.error(\"Failed to process tamper function\");\n            debug.error(JSON.stringify(modelModificationConfig));\n            debug.error(e);\n        }\n        return model;\n    };\n\n    window.FASTR_FRONTEND.experiences[_experienceId].tamperFunctions.push(tamperFunctionWrapper);\n};\n\nfunction findWidgetByUuid(item, widgetUuid, parent = null) {\n    if (item.uuid === widgetUuid || item.id === widgetUuid) {\n        return { widget: item, parent, index: parent ? parent.children.indexOf(item) : null };\n    } else if (item.svgContent && item.svgContent.includes(widgetUuid)) {\n        return { widget: item, parent, index: parent ? parent.children.indexOf(item) : null };\n    }\n\n    const children = item.widgets || item.scenes || item.group;\n    if (children) {\n        item.children = children;\n        for (const child of children) {\n            const result = findWidgetByUuid(child, widgetUuid, item);\n            if (result) return result;\n        }\n    }\n\n    return null;\n}\n\nfunction findSceneByWidgetUuid(widgetUuid) {\n    for (let scene of _model.scenes) {\n        if (sceneContainsWidget(scene, widgetUuid)) {\n            return scene;\n        }\n    }\n    return null;\n}\n\nfunction sceneContainsWidget(scene, widgetUuid) {\n    const children = scene.widgets || scene.group;\n    if (children) {\n        for (const child of children) {\n            if (child.uuid === widgetUuid) {\n                return true;\n            }\n\n            if (child.type === \"composite\" && sceneContainsWidget(child, widgetUuid)) {\n                return true;\n            }\n        }\n    }\n\n    return false;\n}\n\n// Function to run when a mutation is observed\nasync function handleVisibilityChange(mutationsList, observer) {\n    for (let mutation of mutationsList) {\n        if (mutation.type === \"attributes\" && mutation.attributeName === \"style\") {\n            const visibility = getComputedStyle(mutation.target).visibility;\n            if (visibility === \"visible\") {\n                const sceneNumber = parseInt(mutation.target.className.split(\"-\")[1]);\n\n                if (window.FASTR_FRONTEND.experiences[_experienceId].getCurrentScene() !== sceneNumber) {\n                    console.log(`Scene ${sceneNumber} is now visible.`);\n                    window.FASTR_FRONTEND.experiences[_experienceId].setCurrentScene(sceneNumber);\n                    window.FASTR_FRONTEND.experiences[_experienceId].reassertModel();\n                }\n            }\n        }\n    }\n}\n\n// Create an observer instance linked to the callback function\nconst observer = new MutationObserver(handleVisibilityChange);\n\n// Options for the observer (which mutations to observe)\nconst config = { attributes: true, attributeFilter: [\"style\"] };\n\n// Find scene divs and start observing them\nconst sceneDivs = Array.from(domRef.querySelectorAll('div[class^=\"scene-\"]')).filter((el) => {\n    const name = el.className.split(\"-\")[1];\n    return !isNaN(parseInt(name));\n});\nsceneDivs.forEach((sceneDiv) => observer.observe(sceneDiv, config));\n\n"}},"stateKey":"newTrigger"}]}]}}},"page":{"viewportHeight":1,"viewportWidth":5001,"containerHeight":1,"containerWidth":5001}}