window.codexData = window.codexData || {}; let headerNavigation = false; let headerNavigationFragment = ''; const oldViewName = window.codexData.view.name; function satelliteTrack(scrollViewTrack = false) { // eslint-disable-next-line no-underscore-dangle if (typeof window._satellite !== 'undefined') { // eslint-disable-next-line no-underscore-dangle let copyCodexData = Object.assign({}, codexData); if( scrollViewTrack ) { copyCodexData.content = {}; } const data = window._satellite.track('track', copyCodexData); } } /** * Does the element `el` have at least one of the CSS classnames in `classNames`? * @param {Element} el Element to test for the presence of at least one of the CSS classnames in `classList`. * @param {Array} [classNames=[]] CSS classnames to test against. * @returns {boolean} `false` if `classNames` is empty; otherwise whether or not `el` contains one of `classNames` in its `classList`. */ function elementHasSomeClassName(el, classNames = []) { return classNames.some(className => el.classList.contains(className)); } /** * Handle carousel button click event. * @param {HTMLElement} [slickElement=null] */ function carouselClickEvent(slickElement = null) { if (window.codexData) { let slickIdentify = ''; let slickArrowParentId = ''; let slickArrowParentIdName = ''; let pageName = ''; if (window.location.pathname === '/') { pageName = 'home'; } if (slickElement) { delete codexData.flow; const { flowComplete, flowStep, login, ...restEvents } = codexData.events; codexData.events = restEvents; const slickArrowElementClass = slickElement.className.split(' '); slickArrowParentId = slickElement.closest('section'); if (slickArrowParentId) { const { name = '' } = slickArrowParentId.dataset.analytics ? JSON.parse(slickArrowParentId.dataset.analytics) : {}; slickArrowParentIdName = name != '' ? ':' + name : ''; } if ( slickElement.dataset?.carouselArrow?.toLowerCase() === 'next' || elementHasSomeClassName(slickElement, ['slick-next', 'rotator-next-btn']) ) { slickIdentify = 'next'; } else if ( slickElement.dataset?.carouselArrow?.toLowerCase() === 'prev' || elementHasSomeClassName(slickElement, ['slick-prev', 'rotator-prev-btn']) ) { slickIdentify = 'previous'; } } window.codexData.content = {}; window.codexData.events.interaction = true; window.codexData.events.view = false; pageOrViewName = pageName ? ':' + pageName : ':' + oldViewName; window.codexData.interaction = { name: (`${window.codexData.app.name}${pageOrViewName}${slickArrowParentIdName}:carousel-${slickIdentify}`), }; delete window.codexData.view; satelliteTrack(); } } function getRandomAlphanumericAsciiCharacter() { const CHARACTER_RANGE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const randomIndex = Math.floor(Math.random() * CHARACTER_RANGE.length); return CHARACTER_RANGE[randomIndex]; } function getRandomAlphanumericAsciiId(length) { if (!(length > 0)) { throw new Error('getRandomAlphanumericAsciiId must be called with a non-zero positive length'); } const id = Array(length) .fill('') .map(() => getRandomAlphanumericAsciiCharacter()); return id.join(''); } function createSerializationId(idPrefix, id, timeMs) { return `${idPrefix}${id}${timeMs}`; } /** * @summary Use this function as a hook for successful newsletter signups. * Call this function where you know the signup is successful, and it will take care of the analytics fields. * @param {string} [flowName] Optionally override `flow.name`; otherwise will use default of `${app.name}-newsletter-signup` * @returns {void} */ function handleSuccessMessageAnalytics(flowName = null) { if (window.codexData) { // Get the app name from the codexData object, otherwise use `'corus'` as a default. const namePrefix = window?.codexData?.app?.name ?? 'corus'; // Get the 2-letter app ID from the codexData object, otherwise use `'CE'` as a default. const idPrefix = window?.codexData?.misc?.serializationIdPrefix ?? 'CE'; const oldCodexData = window.codexData; const flowNameText = flowName ? flowName : `${namePrefix}-newsletter-signup`; const newCodexData = { ...oldCodexData, events: { flowComplete: true, flowStep: true, newsletterSignup: true, view: false, // newsletter signup is not a page view }, flow: { name: flowNameText, serializationid: createSerializationId( idPrefix, getRandomAlphanumericAsciiId(4), Date.now(), ), stepName: 'complete', }, }; window.codexData = newCodexData; satelliteTrack(); } } if (window.codexData) { // Trigger analytics for anchor links, buttons, or input or option elements when they have a 'data-analytics' attribute const navOrBtnItems = document.querySelectorAll('a[data-analytics], button[data-analytics], input[data-analytics], option[data-analytics]'); let navOrBtnItemName = ''; navOrBtnItems.forEach((navOrBtnItem) => { const { position = '', name = '', module = '', destination = '' } = JSON.parse(navOrBtnItem.dataset.analytics); // By default first click should expand accordion let expand = true; navOrBtnItem.addEventListener('click', () => { const { href: url = '' } = navOrBtnItem; const [, fragmentUrl] = url.split('#'); if (fragmentUrl) { headerNavigation = true; headerNavigationFragment = fragmentUrl; } window.codexData.events = window.codexData.events || {}; window.codexData.content = {}; window.codexData.events.interaction = true; window.codexData.events.view = false; let pageName = ''; if (window.location.pathname === '/') { pageName = 'home'; } sectionNavOrBtnItem = navOrBtnItem.closest('section'); if (sectionNavOrBtnItem) { const { name = '' } = sectionNavOrBtnItem.dataset.analytics ? JSON.parse(sectionNavOrBtnItem.dataset.analytics) : {}; navOrBtnItemName = name != '' ? ':' + name : ''; } pageOrViewName = pageName ? ':' + pageName : ':' + oldViewName; if (module) { if (module == 'share') { window.codexData.content = {}; window.codexData.events.share = true; window.codexData.share = {type : name}; window.codexData.interaction = { name: (`${window.codexData.app.name}:${pageOrViewName}${navOrBtnItemName}:${module}`).replace(/(\W)\W*/g, '$1'), }; delete window.codexData.view; } if (module == 'faq') { window.codexData.content = {}; let expandOrCollapse = expand ? 'expand' : 'collapse'; if(expandOrCollapse == 'expand') { window.codexData.interaction = { name: (`${window.codexData.app.name}:${pageOrViewName}${navOrBtnItemName}:${module}:${name}:${expandOrCollapse}`).replace(/(\W)\W*/g, '$1'), }; // after expand, next on click event should collapse the accordion expand = false; } else { delete window.codexData.events.interaction; delete window.codexData.interaction; expand = true; return; } delete window.codexData.view; } /** * Set data layer fields for items that direct users to external sites and applications. * Use the `destination` property to indicate where the user is going using a slug. * For example, `apple-podcasts`, `spotify`, etc. */ if (module === 'exit-link' && destination) { window.codexData.content = {}; const interactionName = ( `${window.codexData.app.name}:${pageOrViewName}${navOrBtnItemName}:${module}:${destination}` ).replace(/(\W)\W*/g, '$1'); window.codexData.interaction = { name: interactionName, }; delete window.codexData.view; } } else { const positionName = position ? ':' + position : ':'; window.codexData.content = {}; window.codexData.interaction = { name: (`${window.codexData.app.name}:${pageOrViewName}${navOrBtnItemName}${positionName}:${name}`).replace(/(\W)\W*/g, '$1'), }; delete window.codexData.view; } satelliteTrack(); }); }); //==================================================================================================================// // NEWSLETTER PAGE FORM INITIAL FLOW SERIALIZATION ID // //==================================================================================================================// // If this is the Newsletter Page if (window.codexData.content.type === 'page' && window.codexData.content.title.toLowerCase().includes('newsletter')) { /** @type {NodeListOf} */ const newsletterForms = document.querySelectorAll('form.newsletter-form'); /** * Create the initial newsletter flow serialization ID. */ if (newsletterForms.length > 0) { // Get the 2-letter app ID from the codexData object, otherwise use `'CE'` as a default. const idPrefix = window?.codexData?.misc?.serializationIdPrefix ?? 'CE'; // Ensure codexData.flow is defined. window.codexData.flow = window.codexData.flow || {}; // Set the flow serialization ID, using the user's current client-side time in ms. window.codexData.flow.serializationid = createSerializationId( idPrefix, getRandomAlphanumericAsciiId(4), Date.now() ); } } //==================================================================================================================// // END NEWSLETTER PAGE FORM INITIAL FLOW SERIALIZATION ID // //==================================================================================================================// // Trigger page views on scroll when section has // 'data-analytics' and 'data-analytics-view' attributes (() => { let ticking = false; function triggerAnalytics() { ticking = false; const elements = document.querySelectorAll('section[data-analytics][data-analytics-view=false]'); elements.forEach((element) => { const elementTop = element.getBoundingClientRect().top; const elementBottom = element.getBoundingClientRect().bottom; const windowHeight = window.innerHeight; if (elementTop < windowHeight && windowHeight < elementBottom) { const sectionID = element.getAttribute('id'); if (sectionID && headerNavigation) { if (sectionID === headerNavigationFragment) { headerNavigation = false; } else { return; } } window.codexData = window.codexData || {}; window.codexData.view.type = 'section'; const dataAnalytics = JSON.parse(element.dataset.analytics); window.codexData.view.name = dataAnalytics.name; window.codexData.events.view = true; window.codexData.events.interaction = false; if( typeof(window.overrideCodexDataConfig) === 'function' ) { window.codexData = window.overrideCodexDataConfig(window.codexData); } satelliteTrack(true); element.setAttribute('data-analytics-view', true); } }); } /** * For Slick Slider ( Carousel ) arrow button click ( Next / Previous ) * * The arrow buttons should have one of the following to indicate it is a next / previous button: * - a `data-carousel-arrow` attribute. Direction then depends on attribute value being `next` or `prev`. * - a classlist including `slick-arrow`. Direction then depends on including a `slick-(next/prev)` class. * - a classlist including `rotator-arrow`. Direction then depends on including a `rotator-(next/prev)-btn` class. * * Additionally, the buttons should NOT have a classlist including `fullSchedule-arrow` or `gallery-next`. * * @param {MouseEvent} event */ function handleCarouselClick(event) { const slickArrowElement = event.target; const hasCarouselArrowDatasetOrClassNames = ( slickArrowElement.dataset?.carouselArrow /* data-carousel-arrrow */ || elementHasSomeClassName(slickArrowElement, ['slick-arrow', 'rotator-arrow']) ); if ( hasCarouselArrowDatasetOrClassNames && !slickArrowElement.classList.contains('fullSchedule-arrow') ) { if (!slickArrowElement.classList.contains('gallery-next')) { carouselClickEvent(slickArrowElement, true); } } } document.addEventListener('click', handleCarouselClick); function loadRequest() { // eslint-disable-next-line no-underscore-dangle if (window._satellite !== 'undefined') { ticking = ticking || requestAnimationFrame(triggerAnalytics); } } window.addEventListener('scroll', loadRequest, false); })(); //==================================================================================================================// // LIGHT / DARK MODE THEME TOGGLE // //==================================================================================================================// // Set data layer field for the state of the dark / light mode theme toggle, // otherwise the browser / device theme preference. // Field is of the form `{dark|light}:{system|user}` /** * Get the value of the `user_chosen_theme` cookie and verify it is either `'dark'` or `'light'`. * Returns `undefined` if cookie does not exist or does not contain `'dark'` or `'light'`. * @returns {'dark' | 'light' | undefined} */ const getUserChosenThemeCookieValue = () => { // Adapated from MDN example. const cookieValue = document.cookie .split('; ') .find((row) => row.startsWith('user_chosen_theme=')) ?.split('=')[1]; // Guard against unknown values if (['dark', 'light'].includes(cookieValue)) { return cookieValue; } return undefined; }; // Just in case window.codexData.view = window.codexData.view || {}; const userChosenThemeCookieValue = getUserChosenThemeCookieValue(); if (userChosenThemeCookieValue) { window.codexData.view.appearance = `${userChosenThemeCookieValue}:user`; } else { // Note that `light` could also indicate no preference / use browser or device settings const isBrowserOrSystemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; window.codexData.view.appearance = `${isBrowserOrSystemDarkMode ? 'dark' : 'light'}:system`; } //==================================================================================================================// // END LIGHT / DARK MODE THEME TOGGLE // //==================================================================================================================// } ; (function(){var __webpack_exports__={};try{window.sophi=SOPHIDATA}catch(a){}(function(f,g){window.sophi=window.sophi||{};var c=window.sophi;c.q=c.q||[],c.sendEvent=function(a){c.q.push(a)},c.data=c.data||{},c.settings=c.settings||{};const b=c.settings;let a;b.trackerName=b.trackerName||"sophiTag";try{window.localStorage&&((a=localStorage.getItem(`${b.trackerName}.tagCdn`))&&"string"===typeof a&&a.length>7||(a=void 0))}catch(e){a=void 0}b.loadFrom=a?`${a}sophi.min.js`:b.loadFrom||b.tracker_address,b.legacy=a?`${a}sophi.legacy.min.js`:b.loadFrom||"https://cdn.sophi.io/latest/sophi.legacy.min.js";try{eval('let id = Symbol("id"), a = [...new Set([0,1])].includes(0);')}catch(a){b.loadFrom=b.legacy}finally{if(!window[b.trackerName]){a=document.createElement("script");const e=document.getElementsByTagName("script")[0];a.async=1,a.src=b.loadFrom,e.parentNode.insertBefore(a,e)}}c.sendEvent({type:"page_view"})})()})();;