1class Player { 2 static instances = []; 3 static intersectionObserver = null; 4 static visibilityListenerRegistered = false; 5 #interval = null; 6 #mediaName = ""; 7 #playerLoadTimeout = window.Granite?.author ? 2000 : 15000; // 2s for sites authors 8 // or 15 seconds timeout for Wistia to load based on 9 #wistiaCanPlay = false; 10 #youtubeVideoId = null; 11 #youTubeFallbackInstance = null; 12 #fallbackActivated = false; 13 14 constructor(mediaID, element, button) { 15 this.mediaID = mediaID; 16 this.button = button; 17 this.wistiaPlayer = element; 18 this.#mediaName = element.getAttribute("data-media-name"); 19 this.#youtubeVideoId = element.getAttribute("data-youtube-videoid"); 20 Player.instances.push(this); 21 this.init(); 22 this.#startWistiaLoadMonitor(); 23 } 24 25 init() { 26 this.playerPlayerListener(); 27 this.endPlayerListener(); 28 } 29 30 // ------------------------------------------------------------------------- 31 // Wistia load monitor 32 // ------------------------------------------------------------------------- 33 34 // Listens for can-play to confirm the media is playable and cancels the 35 // fallback timeout. If can-play never fires within #playerLoadTimeout, 36 // the fallback is triggered. 37 // 38 // VideoPlayer starts a silent muted probe (via _startProbe()) so that 39 // can-play fires without requiring a user gesture. Banner mode does not 40 // need this â Wistia autoplays and loads naturally. 41 #startWistiaLoadMonitor() { 42 let timeoutHandle = null; 43 44 const onWistiaReady = (eventName) => { 45 if (this.#wistiaCanPlay) return; 46 this.#wistiaCanPlay = true; 47 console.debug(eventName + ' FIRED for: ', this.mediaID); 48 clearTimeout(timeoutHandle); 49 this._onWistiaConfirmed(); 50 }; 51 52 this.wistiaPlayer.addEventListener('can-play', () => onWistiaReady('can-play')); 53 this.wistiaPlayer.addEventListener('api-ready', () => onWistiaReady('api-ready')); 54 this.wistiaPlayer.addEventListener('loaded-media-data', () => onWistiaReady('loaded-media-data')); 55 this.wistiaPlayer.addEventListener('loaded-data', () => onWistiaReady('loaded-data')); 56 this.wistiaPlayer.addEventListener('loaded-metadata', () => onWistiaReady('loaded-metadata')); 57 58 timeoutHandle = setTimeout(() => { 59 if (!this.#wistiaCanPlay) { 60 this.#triggerFallback('Wistia player load timeout - can-play event not received'); 61 } 62 }, this.#playerLoadTimeout); 63 } 64 65 // ------------------------------------------------------------------------- 66 // Subclass hooks â override in VideoPlayer / VideoBanner as needed 67 // ------------------------------------------------------------------------- 68 69 // Returns true while a silent probe is in progress. 70 // Used to suppress analytics and side-effects that must not fire during the probe. 71 // Override in VideoPlayer to return this.#probePending. 72 _isProbing() { return false; } 73 74 // Returns true for subclasses that should be paused when another player on the 75 // page starts playing. Override to return true in VideoPlayer. 76 // Default false â banner mode should not be interrupted by a VideoPlayer starting. 77 _shouldPauseOnOtherPlay() { return false; } 78 79 // Returns true for subclasses that should be paused when the browser tab is hidden. 80 // Override to return true in VideoPlayer. 81 // Default false â banner mode has its own visibility handling. 82 _shouldPauseOnVisibilityHide() { return false; } 83 84 // Called when can-play fires, confirming Wistia loaded successfully. 85 // Override in VideoPlayer to restore state after the silent probe. 86 _onWistiaConfirmed() {} 87 88 // Initiates a silent muted probe: mutes and calls play() so that can-play 89 // fires on page load without a user gesture. Only called by VideoPlayer after 90 // customElements.whenDefined('wistia-player') resolves. 91 _startProbe() { 92 console.debug("Muting the video and playing it"); 93 this.wistiaPlayer.classList.remove('player-hidden'); 94 this.wistiaPlayer.muted = true; 95 this.wistiaPlayer.play(); 96 } 97 98 // ------------------------------------------------------------------------- 99 // Fallback 100 // ------------------------------------------------------------------------- 101 102 #triggerFallback(reason) { 103 if (this.#fallbackActivated) return; 104 105 // In WCM edit mode: surface the error to the author instead of activating 106 // the YouTube fallback. Authors need to know the media ID is wrong. 107 if (window.Granite?.author) { 108 this.#showAuthorError(); 109 } 110 111 112 // The following code is intentionally disabled to prevent the YouTube fallback from activating in PROD live. 113 // It can be re-enabled in the future. 114 /* 115 this.#fallbackActivated = true; 116 if (!this.#youtubeVideoId) return; 117 const analyticsDiv = document.getElementById('analytics-detail
117s'); 118 if (analyticsDiv && analyticsDiv.getAttribute('data-wistia-youtube-fallback-enabled') === 'false') return; 119 this.activateFallback(); 120 */ 121 } 122 123 #showAuthorError() { 124 const wrapper = this.wistiaPlayer.closest('.wistia-player-wrapper') || this.wistiaPlayer.parentNode; 125 126 const title = document.createElement('div'); 127 title.className = 'wistia-author-error__title'; 128 title.textContent = 'Video player error'; 129 130 const message = document.createElement('div'); 131 message.className = 'wistia-author-error__message'; 132 message.textContent = 'The player could not be loaded. Please verify the Media ID is correct.'; 133 134 const mediaId = document.createElement('div'); 135 mediaId.className = 'wistia-author-error__media-id'; 136 mediaId.textContent = 'Media ID: ' + this.mediaID; 137 138 const overlay = document.createElement('div'); 139 overlay.className = 'wistia-author-error'; 140 overlay.appendChild(title); 141 overlay.appendChild(message); 142 overlay.appendChild(mediaId); 143 144 wrapper.appendChild(overlay); 145 } 146 147 // Override in subclasses to implement mode-specific fallback behaviour. 148 activateFallback() { 149 } 150 151 // ------------------------------------------------------------------------- 152 // Accessors 153 // ------------------------------------------------------------------------- 154 155 isFallbackActive() { return this.#fallbackActivated; } 156 getYoutubeVideoId() { return this.#youtubeVideoId; } 157 getMediaName() { return this.#mediaName; } 158 setFallbackInstance(instance) { this.#youTubeFallbackInstance = instance; } 159 getFallbackInstance() { return this.#youTubeFallbackInstance; } 160 161 // ------------------------------------------------------------------------- 162 // Playback 163 // ------------------------------------------------------------------------- 164 165 play() { 166 if (this.isFallbackActive() && this.getFallbackInstance()) { 167 this.getFallbackInstance().play(); 168 } else { 169 this.wistiaPlayer.play(); 170 } 171 } 172 173 pause() { 174 if (this.isFallbackActive() && this.getFallbackInstance()) { 175 this.getFallbackInstance().pause(); 176 } else { 177 this.wistiaPlayer.pause(); 178 } 179 } 180 181 // ------------------------------------------------------------------------- 182 // Event listeners 183 // ------------------------------------------------------------------------- 184 185 playerPlayerListener() { 186 this.wistiaPlayer.addEventListener('play', () => { 187 // Suppress during the silent probe â the play event fires when startProbe() 188 // calls play(), but analytics and pause-others must not trigger at that point. 189 if (this._isProbing()) return; 190 191 this.trackProgress(); 192 Player.instances.forEach(player => { 193 if (player !== this && player._shouldPauseOnOtherPlay()) { 194 player.pause(); 195 } 196 }); 197 }); 198 } 199 200 endPlayerListener() { 201 this.wistiaPlayer.addEventListener('ended', () => { 202 // Suppress during the silent probe â a rapid playâpause from the probe 203 // can fire ended on some browsers, which would incorrectly show the banner 204 // and send a mediaComplete analytics event. 205 if (this._isProbing()) return; 206 207 $(this.button).closest('.wistia-banner').removeClass('banner-hidden'); 208 $(this.wistiaPlayer).addClass('player-hidden'); 209 this.sendAdobeEvent(this.#mediaName, 'mediaComplete'); 210 }); 211 } 212 213 // ------------------------------------------------------------------------- 214 // Analytics 215 // ------------------------------------------------------------------------- 216 217 trackProgress() { 218 const milestones = [0, 25, 50, 75]; 219 const reached = { 0: false, 25: false, 50: false, 75: false }; 220 if (this.#interval !== null) return; 221 this.#interval = setInterval(() => { 222 const currentTime = this.wistiaPlayer.currentTime; 223 const duration = this.wistiaPlayer.duration; 224 if (!duration) return; 225 const percent = (currentTime / duration) * 100;
226 milestones.forEach(milestone => { 227 if (!reached[milestone] && percent >= milestone) { 228 reached[milestone] = true; 229 if (milestone === 0) { 230 this.sendAdobeEvent(this.#mediaName, 'mediaPlay'); 231 } else { 232 switch (milestone) { 233 case 25: this.sendAdobeEvent(this.#mediaName, 'media25PercentPlayed'); break; 234 case 50: this.sendAdobeEvent(this.#mediaName, 'media50PercentPlayed'); break; 235 case 75: this.sendAdobeEvent(this.#mediaName, 'media75PercentPlayed'); break; 236 } 237 } 238 } 239 }); 240 if (percent > 98) { 241 clearInterval(this.#interval); 242 this.#interval = null; 243 $(this.button).closest('.wistia-banner').removeClass('banner-hidden'); 244 } 245 }, 500); 246 } 247 248 sendAdobeEvent(mediaNm, mediaEvent) { 249 window.dataLayer = window.dataLayer || []; 250 window.dataLayer.push({ 251 'event': mediaEvent, 252 'media': { 253 'mediaName': mediaNm, 254 'mediaType': 'video' 255 } 256 }); 257 } 258 259 // ------------------------------------------------------------------------- 260 // Lifecycle 261 // ------------------------------------------------------------------------- 262 263 // Clears the analytics interval and removes this instance from the shared 264 // instances array. Call when the component is removed from the page. 265 destroy() { 266 if (this.#interval !== null) { 267 clearInterval(this.#interval); 268 this.#interval = null; 269 } 270 const index = Player.instances.indexOf(this); 271 if (index !== -1) Player.instances.splice(index, 1); 272 } 273 274 // ------------------------------------------------------------------------- 275 // Static â IntersectionObserver + visibility 276 // ------------------------------------------------------------------------- 277 278 static registerWithObserver(player) { 279 if (!Player.intersectionObserver) { 280 Player.intersectionObserver = new IntersectionObserver((entries) => { 281 entries.forEach(entry => { 282 if (!entry.isIntersecting) { 283 const currentPlayer = Player.instances.find(p => p.wistiaPlayer === entry.target); 284 if (currentPlayer && currentPlayer._shouldPauseOnVisibilityHide() && currentPlayer.wistiaPlayer.state === 'playing') { 285 currentPlayer.pause(); 286 } 287 } 288 }); 289 }, { threshold: 0.1 }); 290 } 291 Player.intersectionObserver.observe(player.wistiaPlayer); 292 293 if (!Player.visibilityListenerRegistered) { 294 document.addEventListener('visibilitychange', Player.handleVisibilityChange); 295 Player.visibilityListenerRegistered = true; 296 } 297 } 298 299 static handleVisibilityChange() { 300 if (document.hidden) { 301 Player.instances.forEach(player => { 302 if ( 303 player._shouldPauseOnVisibilityHide() && 304 player.wistiaPlayer.getAttribute('data-outOfViewBehavior') === 'pause' && 305 player.wistiaPlayer.state === 'playing' 306 ) { 307 player.pause(); 308 } 309 }); 310 } 311 } 312} 313 314class VideoPlayer extends Player { 315 // true while the silent probe is running (between _startProbe() and _onWistiaConfirmed()). 316 // Guards analytics and pause-others logic in Player so they don't fire during the probe. 317 // Also used by playButtonClickListener to detect a click before can-play fires. 318 #probePending = false; 319 320 constructor(mediaID, element, button) { 321 super(mediaID, element, button); 322 // #probePending is safe to write here â private fields initialize after super() returns. 323 // Cannot do this in init() because Player's constructor calls init() before VideoPlayer's 324 // private fields exist, causing a "Cannot write private member" error. 325 // 326 // Gate on customElements.whenDefined() â a standard browser Promise that resolves as soon 327 // as the Wistia script registers the wistia-player element class, at which point play() 328 // exists on every instance. If Wistia's script fails to load entirely, whenDefined never 329 // resolves, the probe never starts, and the timeout fires as the backstop. 330 this.#probePending = true; 331 customElements.whenDefined('wistia-player').then(() => { 332 console.debug("Probing video playback started"); 333 this._startProbe(); 334 }); 335 } 336 337 init() { 338 super.init(); 339 this.playButtonClickListener(); 340 341 if (this.wistiaPlayer.getAttribute('data-outOfViewBehavior') === 'pause') { 342 Player.registerWithObserver(this); 343 } 344 345 // Attach transcript show/hide functionality only if the button exists 346 this.showHideTranscript(); 347 348 if (!hasOneTrustTargetingConsent()) { 349 this.wistiaPlayer.setAttribute('do-not-track', 'true'); 350 } 351 } 352 353 // ------------------------------------------------------------------------- 354 // Subclass hook overrides 355 // ------------------------------------------------------------------------- 356 357 _isProbing() { return this.#probePending; } 358 359 _shouldPauseOnOtherPlay() { return true; } 360 361 _shouldPauseOnVisibilityHide() { return true; } 362 363 // Called when can-play fires. Restores the player to its pre-probe state: 364 // pause and unmute so it sits ready for the user's first click. 365 // If the user already clicked during the probe (#probePending cleared by the 366 // click handler), skip the pause/unmute â the player is already playing audibly. 367 _onWistiaConfirmed() { 368 if (!this.#probePending) { 369 console.debug('[VideoPlayer] probe: user already clicked â skipping restore'); 370 return; 371 } 372 this.#probePending = false;
373 this.wistiaPlayer.pause(); 374 this.wistiaPlayer.muted = false; 375 } 376 377 // ------------------------------------------------------------------------- 378 // Fallback 379 // ------------------------------------------------------------------------- 380 381 activateFallback() { 382 const youtubeVideoId = this.getYoutubeVideoId(); 383 if (!youtubeVideoId) { 384 console.error('[VideoPlayer] No YouTube video ID for fallback'); 385 logClientError("ERROR", "No YouTube video ID available for fallback", "VideoPlayer.js"); 386 return; 387 } 388 389 console.debug('[VideoPlayer] Activating YouTube fallback for:', youtubeVideoId); 390 391 // Get the banner element (wistia-banner) 392 const bannerElement = $(this.button).closest('.wistia-player-wrapper').find('.wistia-banner')[0]; 393 394 // Create the fallback player 395 const fallbackInstance = WistiaFallback.createVideoPlayerFallback({ 396 youtubeVideoId: youtubeVideoId, 397 wistiaElement: this.wistiaPlayer, 398 bannerElement: bannerElement, 399 button: this.button, 400 mediaName: this.getMediaName(), 401 sendAdobeEvent: this.sendAdobeEvent.bind(this), 402 onPlay: () => { console.debug('[VideoPlayer] YouTube fallback playing'); }, 403 onPause: () => { console.debug('[VideoPlayer] YouTube fallback paused'); }, 404 onEnd: () => { 405 console.debug('[VideoPlayer] YouTube fallback ended'); 406 $(bannerElement).removeClass('banner-hidden'); 407 } 408 }); 409 410 this.setFallbackInstance(fallbackInstance); 411 } 412 413 // ------------------------------------------------------------------------- 414 // Playback 415 // ------------------------------------------------------------------------- 416 417 play() { 418 if (this.isFallbackActive() && this.getFallbackInstance()) { 419 this.getFallbackInstance().play(); 420 } else { 421 this.wistiaPlayer.play(); 422 } 423 } 424 425 // ------------------------------------------------------------------------- 426 // Event listeners 427 // ------------------------------------------------------------------------- 428 429 playButtonClickListener() { 430 this.button.click(function() { 431 $(this.button).closest('.wistia-banner').addClass('banner-hidden'); 432 $(this.wistiaPlayer).removeClass('player-hidden'); 433 434 if (this.#probePending) { 435 // Probe is still running â player is already playing but muted. 436 // Mark probe done and unmute so audio starts immediately. 437 // _onWistiaConfirmed will see #probePending=false and skip the pause. 438 console.debug('[VideoPlayer] click during probe â unmuting in-progress probe'); 439 this.#probePending = false; 440 this.wistiaPlayer.muted = false; 441 } else { 442 this.play(); 443 } 444 }.bind(this)); 445 } 446 447 // ------------------------------------------------------------------------- 448 // Transcript 449 // ------------------------------------------------------------------------- 450 451 showHideTranscript() { 452 const isInsideTimelineSlide = $(this.button).closest('.cmp-hrztimelineslide').length > 0; 453 const wistiaPlayerContainer = $(this.button).closest('.main-wistia-container'); 454 if (isInsideTimelineSlide) { 455 this.handleTranscriptModal(wistiaPlayerContainer); 456 } else { 457 this.handleTranscript(wistiaPlayerContainer); 458 } 459 } 460 461 handleTranscriptModal(wistiaPlayerContainer) { 462 const slideTranscriptButton = wistiaPlayerContainer.find('.wistia-details-container .transcriptShowHide'); 463 if(slideTranscriptButton.length > 0){ 464 465 //update button styling 466 slideTranscriptButton.removeClass('transcriptShowHide'); 467 slideTranscriptButton.addClass('wistia-transcript-modal-button'); 468 469 //remove inline transcript container 470 wistiaPlayerContainer.find('.wistia-transcript-container').remove(); 471
472 this.attachTranscriptModalButtonClick(slideTranscriptButton); 473 474 const closePreferences = wistiaPlayerContainer.find('.video-transcript-modal--closeButton'); 475 this.attachTranscriptModalCloseClickListener(closePreferences); 476 } 477 } 478 479 handleTranscript(wistiaPlayerContainer) { 480 const transcriptButton = wistiaPlayerContainer.find('.wistia-details-container .transcriptShowHide'); 481 wistiaPlayerContainer.find('.video-transcript-modal').remove(); 482 if (transcriptButton.length > 0) { 483 transcriptButton.click(function() { 484 const transcriptContent = wistiaPlayerContainer.find('.wistia-transcript-container'); 485 const showLabel = transcriptButton.attr('data-showTranscript-label'); 486 const hideLabel = transcriptButton.attr('data-hideTranscript-label'); 487 transcriptContent.toggleClass('wistia-transcript-open'); 488 if (transcriptContent.hasClass('wistia-transcript-open')) { 489 transcriptButton.find('.player-transcript-label').text(hideLabel); 490 transcriptContent.attr('aria-hidden', 'false'); 491 transcriptContent.css('display', 'block'); 492 } else { 493 transcriptButton.find('.player-transcript-label').text(showLabel); 494 transcriptContent.attr('aria-hidden', 'true'); 495 transcriptContent.css('display', 'none'); 496 } 497 }); 498 } 499 } 500 501 attachTranscriptModalButtonClick(slideTranscriptButton) { 502 slideTranscriptButton.click(() => { 503 const modal = document.querySelector(`#wistia-modal-video-${this.mediaID}`); 504 if (modal) { 505 this.#handleEscapeModal(`#wistia-modal-video-${this.mediaID}`); 506 this.#sendButtonAdobeAnalytics( 507 $(document).find('title')[0].text.split('|')[0].trim(), 508 'Open the video transcript modal', 509 'button' 510 ); 511 setTimeout(() => { modal.showModal(); document.querySelector('body').style.overflow = 'hidden'; }, 180); 512 setTimeout(() => { $(modal).fadeIn(700); }, 250); 513 } else { 514 console.warn(`Modal with ID #wistia-modal-video-${this.mediaID} not found.`); 515 } 516 }); 517 } 518 519 #handleEscapeModal(targetModalSelector) { 520 const targetModal = document.querySelector(targetModalSelector); 521 522 // Unbind previous handlers for this namespace 523 $(document).off('keydown.wistiaTranscriptModal'); 524 $(targetModalSelector).off('click.wistiaTranscriptModal'); 525 $(document).on('keydown.wistiaTranscriptModal', function(event) { 526 if (event.key === 'Escape' && targetModal.hasAttribute('open')) { 527 this.#closeModal(targetModal); 528 } 529 }.bind(this)); 530 531 $(targetModalSelector).on('click.wistiaTranscriptModal',function (event) { 532 if (event.target.nodeName === 'DIALOG') { 533 this.#closeModal(targetModal); 534 } 535 }.bind(this)); 536 } 537 538 #closeModal(modal) { 539 $(modal).fadeOut(300); 540 setTimeout(() => { 541 modal.close(); 542 document.querySelector('body').style.overflow = ''; 543 }, 350); 544 } 545 546 attachTranscriptModalCloseClickListener(closePreferences) { 547 closePreferences.click(function() { 548 const modal = $(this.button).closest('.main-wistia-container').find('.video-transcript-modal')[0]; 549 if (modal) this.#closeModal(modal); 550 this.#sendButtonAdobeAnalytics( 551 $(document).find('title')[0].text.split('|')[0].trim(), 552 'Close the video transcript modal', 553 'button' 554 ); 555 }.bind(this)); 556 } 557 558 #sendButtonAdobeAnalytics(location, name, type) { 559 window.dataLayer = window.dataLayer || []; 560 window.dataLayer.push({ 561 "event": "callToAction", //e111 562 "callToAction": { 563 "location": location, //v103
564 "ctaName": name, //v104 565 "ctaType": type //v111 566 } 567 }); 568 } 569} 570class VideoBanner extends Player { 571 constructor(mediaID, element, button) { 572 super(mediaID, element, button); 573 } 574 575 init(){ 576 this.wistiaPlayer.addEventListener('api-ready', () => { 577 this.play(); 578 }); 579 this.overlayClickListener(); 580 this.endPlayerListener(); 581 } 582 583 play () { 584 this.wistiaPlayer.play(); 585 } 586 587 pause () { 588 this.wistiaPlayer.pause(); 589 } 590 591 endPlayerListener () { 592 //attach end event 593 this.wistiaPlayer.addEventListener("ended", () => { 594 this.play(); 595 }); 596 } 597 598 activateFallback() { 599 const youtubeVideoId = this.getYoutubeVideoId(); 600 if (!youtubeVideoId) { 601 console.error('[VideoBanner] No YouTube video ID for fallback'); 602 logClientError("ERROR", "No YouTube video ID available for banner fallback", "VideoBanner.js"); 603 return; 604 } 605 606 console.debug('[VideoBanner] Activating YouTube fallback for:', youtubeVideoId); 607 608 const wrapper = $(this.wistiaPlayer).closest('.wistia-player-wrapper'); 609 const overlayElement = wrapper.find('.player-overlay')[0]; 610 611 WistiaFallback.createVideoBannerFallback({ 612 youtubeVideoId: youtubeVideoId, 613 wistiaElement: this.wistiaPlayer, 614 overlayElement: overlayElement, 615 button: this.button, 616 mediaName: this.getMediaName() 617 }); 618 } 619 620 overlayClickListener () { 621 this.button.closest(".player-overlay").click(function(e) { 622 if (this.wistiaPlayer.state === 'playing' && this.button.find("span").hasClass("pause-icon")) { 623 this.button.find("span").removeClass("pause-icon"); 624 this.button.find("span").addClass("play-icon"); 625 this.button.attr('aria-label', 'Play video'); 626 this.button.attr('title', 'Play video'); 627 this.pause(); 628 } else if(this.wistiaPlayer.state === 'paused' && this.button.find("span").hasClass("play-icon")){ 629 this.button.find("span").removeClass("play-icon"); 630 this.button.find("span").addClass("pause-icon"); 631 this.button.attr('aria-label', 'Pause video'); 632 this.button.attr('title', 'Pause video'); 633 this.play(); 634 } 635 }.bind(this)); 636 } 637} 638window.WistiaPlayerFactory = { 639 createPlayer(mediaID, element, button) { 640 if ($(element).data('type') === 'videoBanner') { 641 return new VideoBanner(mediaID, element, button); 642 } else { 643 return new VideoPlayer(mediaID, element, button); 644 } 645 }, 646 647 // function being used by the editor to reinitialize the players in the page after an editable update, 648 // to make sure that the players are correctly initialized in the new DOM structure 649 reinitialize() { 650 Player.instances 651 .filter(p => !document.body.contains(p.wistiaPlayer)) 652 .forEach(p => p.destroy()); 653 654 $('wistia-player[media-id]').not('[data-type="audioPlayer"]').each(function() { 655 const element = this; 656 const alreadyInitialized = Player.instances.some(p => p.wistiaPlayer === element); 657 if (!alreadyInitialized) { 658 const mediaID = $(element).attr('media-id'); 659 const button = $(element).closest('.wistia-player-wrapper').find('button.play-pause-btn'); 660 WistiaPlayerFactory.createPlayer(mediaID, element, button); 661 } 662 }); 663 } 664}; 665 666$(document).ready(function(){ 667 $('wistia-player[media-id]').not('[data-type="audioPlayer"]').each(function() { 668 const mediaID = $(this).attr('media-id'); 669 const button = $(this).closest('.wistia-player-wrapper').find('button.play-pause-btn'); 670 WistiaPlayerFactory.createPlayer(mediaID, this, button); 671 }); 672}); 673 674/** 675 * Wistia Audio Player 676 * 677 * Three-part UI: 678 * 1. Inline trigger â "Podify AI / Listen as a podcast" with play button 679 * 2. Bottom sticky player â shows on play with title, expand/collapse, play/pause, progress bar 680 * 3. Full-screen expanded panel â title, date, transcript (sticky player overlays at bottom) 681 * 682 * Uses Wistia <wistia-player> web component for audio playback. 683 */ 684 685const MILESTONE_EVENTS = { 686 0: 'mediaPlay', 687 25: 'media25PercentPlayed', 688 50: 'media50PercentPlayed', 689 75: 'media75PercentPlayed' 690}; 691 692// Below this percentage the playhead counts as "at the start", so returning here 693// after real progress is treated as a replay rather than mid-track scrubbing. 694const RESTART_THRESHOLD_PERCENT = 1; 695 696const ICONS = { 697 expand: '/etc.clientlibs/corporatesite/clientlibs/clientlib-site/resources/icons/button-icons/expand_White.svg', 698 collapse: '/etc.clientlibs/corporatesite/clientlibs/clientlib-site/resources/icons/button-icons/collapse_White.svg', 699 speed1x: '/etc.clientlibs/corporatesite/clientlibs/clientlib-site/resources/icons/button-icons/1x_White.svg', 700 speed2x: '/etc.clientlibs/corporatesite/clientlibs/clientlib-site/resources/icons/button-icons/2x_White.svg' 701}; 702 703class PodifyPlayer { 704 constructor(trigger) {
705 this._initialised = false; 706 this._trigger = trigger; 707 this._wistiaPlayer = trigger.find('wistia-player.wistia-audio-player-element')[0]; 708 709 if (!this._wistiaPlayer) return; 710 711 this._mediaName = this._wistiaPlayer.getAttribute('data-media-name') || ''; 712 this._stickyPlayer = trigger.nextAll('.podify-sticky-player').first(); 713 this._expandedPanel = trigger.nextAll('.podify-expanded').first(); 714 715 if (!this._stickyPlayer.length || !this._expandedPanel.length) return; 716 717 this._isPlaying = false; 718 this._durationsSet = false; 719 this._progressInterval = null; 720 this._playbackRate = 1; 721 this._resetMilestones(); 722 723 this._bindElements(); 724 this._bindEvents(); 725 this._initialised = true; 726 } 727 728 // ------------------------------------------------------------------------- 729 // Init 730 // ------------------------------------------------------------------------- 731 732 _bindElements() { 733 const trigger = this._trigger; 734 const sticky = this._stickyPlayer; 735 const expanded = this._expandedPanel; 736 737 this._body = $('body'); 738 739 this._triggerPlayBtn = trigger.find('.podify-trigger__play-btn'); 740 this._triggerReadTime = trigger.find('.podify-trigger__read-time'); 741 742 this._stickyPlayBtn = sticky.find('.podify-sticky-player__play-btn:not(.podify-sticky-player__play-btn--mobile)'); 743 this._stickyPlayIcons = sticky.find('.podify-sticky-player__play-icon'); 744 this._stickyPauseIcons = sticky.find('.podify-sticky-player__pause-icon'); 745 this._mobilePlayBtn = sticky.find('.podify-sticky-player__play-btn--mobile'); 746 this._expandBtn = sticky.find('.podify-sticky-player__expand-btn'); 747 this._expandBtnImg = this._expandBtn.find('img'); 748 this._collapseBtn = sticky.find('.podify-sticky-player__collapse-btn'); 749 this._closeBtn = sticky.find('.podify-sticky-player__close-btn'); 750 this._rewindBtn = sticky.find('.podify-sticky-player__rewind-btn'); 751 this._forwardBtn = sticky.find('.podify-sticky-player__forward-btn'); 752 this._speedBtn = sticky.find('.podify-sticky-player__speed-btn'); 753 this._shareBtn = sticky.find('.podify-sticky-player__share-btn'); 754 this._stickyCurrentTime = sticky.find('.podify-sticky-player__current-time'); 755 this._stickyDuration = sticky.find('.podify-sticky-player__duration'); 756 this._stickySeekbar = sticky.find('.podify-sticky-player__seekbar'); 757 this._stickyProgressFilled = sticky.find('.podify-sticky-player__progress-filled'); 758 759 this._backBtn = expanded.find('.podify-expanded__back-btn'); 760 this._transcriptWrapper = expanded.find('.podify-expanded__transcript-wrapper')[0]; 761 this._transcriptEl = expanded.find('wistia-transcript')[0]; 762 } 763 764 _bindEvents() { 765 const player = this._wistiaPlayer; 766 767 // Playback buttons â arrow functions preserve `this` as the class instance 768 this._triggerPlayBtn.on('click', () => this.togglePlayback()); 769 this._stickyPlayBtn.on('click', () => this.togglePlayback()); 770 this._mobilePlayBtn.on('click', (e) => { 771 e.stopPropagation(); 772 this.togglePlayback(); 773 }); 774 775 // Panel controls 776 this._expandBtn.on('click', () => { 777 if (this._expandedPanel.hasClass('podify-expanded--visible')) { 778 this._hideExpandedPanel(); 779 } else { 780 this._showExpandedPanel(); 781 } 782 }); 783 this._collapseBtn.on('click', () => this._hideExpandedPanel()); 784 this._backBtn.on('click', () => this._hideExpandedPanel()); 785 this._closeBtn.on('click', () => this._onClose()); 786 787 // Transport controls 788 this._rewindBtn.on('click', () => { 789 player.currentTime = Math.max(0, player.currentTime - 15); 790 this._updateProgress(); 791 }); 792 this._forwardBtn.on('click', () => { 793 player.currentTime = Math.min(player.duration || 0, player.currentTime + 15); 794 this._updateProgress(); 795 }); 796 this._speedBtn.on('click', () =>
796 this._onSpeedToggle()); 797 this._shareBtn.on('click', () => this._onShare()); 798 799 // Seekbar 800 this._stickySeekbar.on('input', (e) => this._onSeek(e)); 801 802 // Transcript auto-scroll 803 this._bindTranscriptScroll(); 804 805 // Wistia player events â duration detection 806 customElements.whenDefined('wistia-player').then(() => { 807 this._setDurations(); 808 ['can-play', 'api-ready', 'loaded-media-data', 'loaded-data', 'loaded-metadata'].forEach((evt) => { 809 player.addEventListener(evt, () => this._setDurations()); 810 }); 811 }); 812 813 player.addEventListener('play', () => { 814 this._isPlaying = true; 815 this._setPlayingState(); 816 this._showStickyPlayer(); 817 if (!this._progressInterval) { 818 this._progressInterval = setInterval(() => this._updateProgress(), 250); 819 } 820 }); 821 822 player.addEventListener('pause', () => { 823 this._stopPlayback(); 824 }); 825 826 player.addEventListener('ended', () => { 827 this._stopPlayback(); 828 this._sendAdobeEvent('mediaComplete'); 829 this._resetProgress(); 830 }); 831 } 832 833 // ------------------------------------------------------------------------- 834 // Transcript scroll 835 // ------------------------------------------------------------------------- 836 837 // register a MutationObserver to resolve the delay of the transcript 838 // shown on screen being delayed compared to the spoken audio. 839 // This will help to keep the transcript in sync with the audio. 840 _bindTranscriptScroll() { 841 if (!this._transcriptEl || !this._transcriptWrapper) return; 842 843 const attach = () => { 844 if (!this._transcriptEl.shadowRoot) return; 845 this._transcriptObserver = new MutationObserver(() => this._scrollToCue()); 846 this._transcriptObserver.observe(this._transcriptEl.shadowRoot, { 847 subtree: true, 848 attributeFilter: ['part'] 849 }); 850 }; 851 852 if (this._transcriptEl.shadowRoot) { 853 attach(); 854 } else { 855 customElements.whenDefined('wistia-transcript').then(attach); 856 } 857 } 858 859 _scrollToCue() { 860 const cue = this._transcriptEl.shadowRoot.querySelector('[part~="current-cue"]'); 861 if (!cue || !this._transcriptWrapper) return; 862 863 const wrapperRect = this._transcriptWrapper.getBoundingClientRect(); 864 const cueRect = cue.getBoundingClientRect(); 865 const targetScrollTop = this._transcriptWrapper.scrollTop + (cueRect.top - wrapperRect.top) - (wrapperRect.height / 2) + (cueRect.height / 2); 866 867 this._transcriptWrapper.scrollTo({ top: targetScrollTop, behavior: 'smooth' }); 868 } 869 870 // ------------------------------------------------------------------------- 871 // Playback 872 // ------------------------------------------------------------------------- 873 874 _stopPlayback() { 875 this._isPlaying = false; 876 this._setPausedState(); 877 clearInterval(this._progressInterval); 878 this._progressInterval = null; 879 } 880 881 togglePlayback() { 882 if (!this._initialised) return; 883 if (this._isPlaying) { 884 this._wistiaPlayer.pause(); 885 } else { 886 this._wistiaPlayer.play(); 887 } 888 } 889 890 _showStickyPlayer() { 891 this._stickyPlayer.addClass('podify-sticky-player--visible'); 892 this._stickyPlayer.attr('aria-hidden', 'false'); 893 } 894 895 _onClose() { 896 this._wistiaPlayer.pause(); 897 if (this._expandedPanel.hasClass('podify-expanded--visible')) { 898 this._hideExpandedPanel(); 899 } 900 this._stickyPlayer.removeClass('podify-sticky-player--visible'); 901 this._stickyPlayer.attr('aria-hidden', 'true'); 902 } 903 904 // ------------------------------------------------------------------------- 905 // Panel 906 // ------------------------------------------------------------------------- 907 908 _showExpandedPanel() { 909 this._expandedPanel.addClass('podify-expanded--visible'); 910 this._expandedPanel.attr('aria-hidden', 'false'); 911 this._body.css('overflow', 'hidden'); 912 this._expandBtn.hide(); 913 this._collapseBtn.show(); 914 this._expandBtnImg.attr('src', ICONS.collapse); 915 this._expandBtn.attr({ 'aria-label': 'Collapse player', 'title': 'Collapse player', 'aria-expanded': 'true' }); 916 } 917 918 _hideExpandedPanel() { 919 this._expandedPanel.removeClass('podify-expanded--visible'); 920 this._expandedPanel.attr('aria-hidden', 'true'); 921 this._body.css('overflow', ''); 922 this._collapseBtn.hide(); 923 this._expandBtn.show(); 924 this._expandBtnImg.attr('src', ICONS.expand); 925 this._expandBtn.attr({ 'aria-label': 'Expand player', 'title': 'Expand player', 'aria-expanded': 'false' }); 926 } 927 928 // ------------------------------------------------------------------------- 929 // Progress 930 // ------------------------------------------------------------------------- 931 932 _resetProgress() { 933 this._stickyProgressFilled.css('width', '0%'); 934 this._stickySeekbar.val(0); 935 this._stickyCurrentTime.text('0:00'); 936 this._resetMilestones(); 937 } 938 939 _resetMilestones() { 940 this._milestoneReached = {};
941 Object.keys(MILESTONE_EVENTS).forEach((key) => { 942 this._milestoneReached[key] = false; 943 }); 944 this._progressedBeyondStart = false; 945 } 946 947 // Only a return to the start *after* real progress counts as a replay, so 948 // pausing and resuming near the start cannot re-fire the milestones. 949 _maybeHandleRestart(percent) { 950 if (percent >= RESTART_THRESHOLD_PERCENT) { 951 this._progressedBeyondStart = true; 952 } else if (this._progressedBeyondStart) { 953 this._resetMilestones(); 954 } 955 } 956 957 _updateProgress() { 958 const currentTime = this._wistiaPlayer.currentTime; 959 const duration = this._wistiaPlayer.duration; 960 if (!duration) return; 961 962 const percent = (currentTime / duration) * 100; 963 964 this._stickyProgressFilled.css('width', percent + '%'); 965 this._stickySeekbar.val(percent); 966 this._stickyCurrentTime.text(PodifyPlayer._formatTime(currentTime)); 967 this._maybeHandleRestart(percent); 968 this._trackMilestones(percent); 969 } 970 971 _onSeek(e) { 972 const duration = this._wistiaPlayer.duration; 973 if (!duration) return; 974 const seekPercent = parseFloat($(e.target).val()); 975 const seekTime = (seekPercent / 100) * duration; 976 this._wistiaPlayer.currentTime = seekTime; 977 this._stickyProgressFilled.css('width', seekPercent + '%'); 978 this._stickyCurrentTime.text(PodifyPlayer._formatTime(seekTime)); 979 this._maybeHandleRestart(seekPercent); 980 } 981 982 // ------------------------------------------------------------------------- 983 // UI state 984 // ------------------------------------------------------------------------- 985 986 _setPlayingState() { 987 this._triggerPlayBtn.find('.podify-trigger__play-icon').hide(); 988 this._triggerPlayBtn.find('.podify-trigger__pause-icon').show(); 989 this._triggerPlayBtn.attr('aria-label', 'Pause podcast audio').attr('title', 'Pause podcast audio'); 990 991 this._stickyPlayIcons.hide(); 992 this._stickyPauseIcons.show(); 993 this._stickyPlayBtn.attr('aria-label', 'Pause audio').attr('title', 'Pause audio'); 994 this._mobilePlayBtn.attr('aria-label', 'Pause audio').attr('title', 'Pause audio'); 995 } 996 997 _setPausedState() { 998 this._triggerPlayBtn.find('.podify-trigger__play-icon').show(); 999 this._triggerPlayBtn.find('.podify-trigger__pause-icon').hide(); 1000 this._triggerPlayBtn.attr('aria-label', 'Play podcast audio').attr('title', 'Play podcast audio'); 1001 1002 this._stickyPlayIcons.show(); 1003 this._stickyPauseIcons.hide(); 1004 this._stickyPlayBtn.attr('aria-label', 'Play audio').attr('title', 'Play audio'); 1005 this._mobilePlayBtn.attr('aria-label', 'Play audio').attr('title', 'Play audio'); 1006 } 1007 1008 // ------------------------------------------------------------------------- 1009 // Speed / Share 1010 // ------------------------------------------------------------------------- 1011 1012 _onSpeedToggle() { 1013 this._playbackRate = this._playbackRate === 1 ? 2 : 1; 1014 this._wistiaPlayer.playbackRate = this._playbackRate; 1015 const speedImg = this._speedBtn.find('img'); 1016 if (this._playbackRate === 2) { 1017 speedImg.attr('src', ICONS.speed2x); 1018 speedImg.attr('alt', '2x'); 1019 } else { 1020 speedImg.attr('src', ICONS.speed1x); 1021 speedImg.attr('alt', '1x'); 1022 } 1023 } 1024 1025 _onShare() { 1026 if (navigator.share) { 1027 navigator.share({ title: document.title, url: window.location.href }).catch(() => {}); 1028 } else if (navigator.clipboard) { 1029 navigator.clipboard.writeText(window.location.href).catch(() => {}); 1030 } 1031 } 1032 1033 // ------------------------------------------------------------------------- 1034 // Analytics 1035 // ------------------------------------------------------------------------- 1036 1037 _trackMilestones(percent) { 1038 Object.keys(MILESTONE_EVENTS).forEach((key) => { 1039 const milestone = Number(key); 1040 if (!this._milestoneReached[milestone] && percent >= milestone) { 1041 this._milestoneReached[milestone] = true; 1042 this._sendAdobeEvent(MILESTONE_EVENTS[key]); 1043 } 1044 }); 1045 } 1046 1047 _sendAdobeEvent(mediaEvent) { 1048 window.dataLayer = window.dataLayer || []; 1049 window.dataLayer.push({ 1050 'event': mediaEvent, 1051 'media': { 1052 'mediaName': this._mediaName, 1053 'mediaType': 'audio' 1054 } 1055 }); 1056 } 1057 1058 // ------------------------------------------------------------------------- 1059 // Utility 1060 // ------------------------------------------------------------------------- 1061 1062 _setDurations() { 1063 if (this._durationsSet || !this._wistiaPlayer.duration) return; 1064 this._durationsSet = true; 1065 this._stickyDuration.text(PodifyPlayer._formatTime(this._wistiaPlayer.duration)); 1066 this._triggerReadTime.text(PodifyPlayer._formatTriggerTime(this._wistiaPlayer.duration)); 1067 } 1068 1069 static _formatTime(seconds) { 1070 if (!seconds || isNaN(seconds)) return '0:00'; 1071 const mins = Math.floor(seconds / 60); 1072 const secs = Math.floor(seconds % 60); 1073 return mins + ':' + (secs < 10 ? '0' : '') + secs; 1074 } 1075 1076 static _formatTriggerTime(seconds) { 1077 if (!seconds || isNaN(seconds)) return '0 minutes 0 seconds'; 1078 const mins = Math.floor(seconds / 60); 1079 const secs = Math.floor(seconds % 60); 1080 const parts = []; 1081 if (mins > 0) parts.push(mins + (mins === 1 ? ' minute' : ' minutes')); 1082 if (secs > 0) parts.push(secs + (secs === 1 ? ' second' : ' seconds')); 1083 return parts.join(' '); 1084 } 1085} 1086 1087$(document).ready(function () { 1088 $('.podify-trigger').each(function () { 1089 new PodifyPlayer($(this)); 1090 }); 1091}); 1092 1093(() => { 1094 $(document).ready(function () { 1095 'use strict'; 1096 const videoTranscriptButton = $('.video-transcript'); 1097 const closePreferences = $('.video-transcript-modal__buttonContainer--close'); 1098 const bodyElement = document.querySelector("body"); 1099 1100 //need to extract this function as a global function so it can be reused without creating code duplication 1101 const handleClickAndEscapeModal = function handleEscapeModal(targetModalSelector) { 1102 const targetModal = document.querySelector(targetModalSelector); 1103 1104 // Unbind previous handlers for this namespace
1105 $(document).off('keydown.videoTranscriptModal'); 1106 $(targetModalSelector).off('click.videoTranscriptModal'); 1107 1108 $(document).on('keydown.videoTranscriptModal', function (event) { 1109 if (event.key === "Escape" && targetModal.hasAttribute("open")) { 1110 closeModal(targetModal); 1111 } 1112 }); 1113 1114 $(targetModalSelector).on('click.videoTranscriptModal',function (event) { 1115 if (event.target.nodeName === 'DIALOG') { 1116 closeModal(targetModal); 1117 } 1118 }); 1119 }; 1120 1121 videoTranscriptButton.click(function () { 1122 let currentVideoID = $(this).data('video-modal-id'); 1123 const modal = document.querySelector(`#modal-video-${currentVideoID}`); 1124 1125 if (modal) { 1126 handleClickAndEscapeModal(`#modal-video-${currentVideoID}`); 1127 1128 1129 setTimeout(function () { 1130 modal.showModal(); 1131 bodyElement.style.overflow = 'hidden'; 1132 sendAdobeAnalytics($(document).find("title")[0].text.split("|")[0].trim(), "Open the video" + 1133 " transcript modal", "button"); 1134 }, 180); 1135 setTimeout(function () { 1136 $(modal).fadeIn(700); 1137 }, 250); 1138 } else { 1139 console.warn(`Modal with ID #modal-video-${currentVideoID} not found.`); 1140 } 1141 }); 1142 1143 function closeModal(modal) { 1144 $(modal).fadeOut(300); 1145 1146 setTimeout(() => { 1147 modal.close(); 1148 bodyElement.style.overflow = ''; 1149 }, 350); 1150 } 1151 1152 closePreferences.click(function () { 1153 const modal = $(this).closest('.video-transcript-modal')[0]; 1154 if (modal) { 1155 closeModal(modal); 1156 } 1157 1158 sendAdobeAnalytics($(document).find("title")[0].text.split("|")[0].trim(), "Close the video transcript modal", "button"); 1159 }); 1160 1161 const sendAdobeAnalytics = function (location, name, type) { 1162 window.dataLayer = window.dataLayer || []; 1163 window.dataLayer.push({ 1164 "event": "callToAction", //e111 1165 "callToAction": { 1166 "location": location, //v103 1167 "ctaName": name, //v104 1168 "ctaType": type //v111 1169 } 1170 }); 1171 } 1172 }); 1173})(); 1174 1175 1176 1177 1178$(document).ready(function(){ 1179 $(document).on("scroll", stickyStroll); 1180 var linkItem = $('.fixed-item .link-item'); 1181 if ( linkItem.length ) { 1182 linkItem.first().addClass('active'); 1183 } 1184}); 1185 1186function stickyStroll(event){ 1187 if($('.timelinecontainer').offset()){ 1188 stickyStrollTimeline(event); 1189 }else{ 1190 1191 let stickyRow = $('.sticky-row'); 1192 let scrollPosition = $(document).scrollTop(); 1193 1194 stickyRow.each(function(){ 1195 var currentElement = $(this); 1196 var currentElePosition = currentElement.position().top-70 ; 1197 if(currentElePosition <= scrollPosition && (currentElePosition + currentElement.height()) > scrollPosition){ 1198 currentElement.addClass("fixed"); 1199 } else { 1200 currentElement.removeClass("fixed"); 1201 } 1202 }); 1203 } 1204 1205} 1206 1207function stickyStrollTimeline(event){ 1208 1209 let stickyRow = $('.sticky-row'); 1210 let scrollPosition = $(document).scrollTop(); 1211 let timeLinePosition = $('.timelinecontainer').offset().top; 1212 if(scrollPosition >= timeLinePosition){ 1213 stickyRow.each(function(){ 1214 let currentElement = $(this); 1215 let currentElePosition = currentElement.offset().top-200; 1216 if(currentElePosition <= scrollPosition && (currentElePosition + currentElement.height()) > scrollPosition){ 1217 currentElement.addClass("fixed"); 1218 } else { 1219 currentElement.removeClass("fixed"); 1220 } 1221 }); 1222 } else { 1223 stickyRow.each(function(){ 1224 let currentElement = $(this); 1225 currentElement.removeClass("fixed"); 1226 }); 1227 } 1228} 1229(() => { 1230 $(document).ready(function () { 1231 'use strict'; 1232 1233 let currentEventId = null; 1234 const vanguardEffectCTA = $('.htl-tve-button'); 1235 const closePreferences = $('.cmp-vanguard-effect-modal__buttonContainer--close'); 1236 const bodyElement = document.querySelector("body"); 1237 1238 //need to extract this function as a global function so it can be reused without creating code duplication 1239 const handleClickAndEscapeModal = function handleEscapeModal(targetModalSelector) { 1240 const targetModal = document.querySelector(targetModalSelector); 1241 1242 $(document).keydown(function (event) { 1243 if (event.key === "Escape" && targetModal.hasAttribute("open")) { 1244 closeModal(targetModal); 1245 } 1246 }); 1247 1248 $(targetModalSelector).click(function (event) { 1249 if (event.target.nodeName === 'DIALOG') { 1250 closeModal(targetModal); 1251 } 1252 }); 1253 }; 1254 1255 vanguardEffectCTA.click(function () { 1256 currentEventId = $(this).attr('data-event-id'); 1257 const modal = document.querySelector(`#modal-event-${currentEventId}`); 1258 if (modal) { 1259 handleClickAndEscapeModal(`#modal-event-${currentEventId}`); 1260 //$(modal).fadeIn(700); 1261 1262 setTimeout(function () { 1263 modal.showModal(); 1264 bodyElement.style.overflow = 'hidden'; 1265 sendAnalytics('Open the Vanguard Effect modal', modal); 1266 }, 680); 1267 } else { 1268 console.warn(`Modal with ID #modal-event-${currentEventId} not found.`); 1269 } 1270 }); 1271 1272 function closeModal(modal) { 1273 $(modal).fadeOut(300); 1274 setTimeout(() => { 1275 modal.close(); 1276 //Remove jQuery fadeOut display none 1277 $(modal).css('display', ''); 1278 bodyElement.style.overflow = ''; 1279 sendAnalytics('Close the Vanguard Effect modal', modal); 1280 }, 350); 1281 } 1282 1283 closePreferences.click(function () { 1284 const modal = $(this).closest('.cmp-vanguard-effect-modal')[0]; 1285 if (modal) { 1286 closeModal(modal); 1287 } 1288 }); 1289 1290 function sendAnalytics(ctaName, modal){ 1291 let analyticsHeadline = modal.getAttribute('data-headline-analytics'); 1292 window.dataLayer = window.dataLayer || []; 1293 window.dataLayer.push({ 1294 "event": "callToAction", //e111 1295 "callToAction": { 1296 "location": document.title.split("|")[0].replace("â", "'").trim() + " " + anal
1296yticsHeadline, //v103 1297 "ctaName": ctaName, //v104 1298 "ctaType": 'button' //v111 1299 } 1300 }); 1301 } 1302 }); 1303})(); 1304 1305 1306 1307 1308(function () { 1309 "use strict"; 1310 var topicVisualPositioning = {}; 1311 if(properties["topicVisualSingleStart"] != undefined || properties["topicVisualSingleStart"] != null) { 1312 if(properties["topicVisualSingleStart"] == 'true' && properties["topicVisualSpaceEvenly"] == 'false'){ 1313 topicVisualPositioning.imageStyleElement = "col-12 col-md-6 order-md-1 col-lg-5 order-lg-1"; 1314 topicVisualPositioning.textStyleElement = "col-10 offset-1 col-md-9 order-md-1 offset-md-2 col-lg-7 offset-lg-1 order-lg-2 cmp-topicVisualSingle__textContainerOnRight"; 1315 topicVisualPositioning.colLGsixFlexUpdate = "flex: 0 0 44.666666%"; 1316 topicVisualPositioning.topicTitleLGMarginBottom = "margin-bottom: 20%"; 1317 //added new 1318 topicVisualPositioning.textContainer = "col-12 col-md-6 order-md-1 col-lg-7 order-lg-1"; 1319 }else if(properties["topicVisualSingleStart"] == 'false' && properties["topicVisualSpaceEvenly"] == 'false'){ 1320 topicVisualPositioning.imageStyleElement = "col-12 col-md-6 order-md-2 col-lg-5 order-lg-2"; 1321 topicVisualPositioning.textStyleElement = "col-10 offset-1 col-md-9 offset-md-1 order-md-1 col-lg-7 order-lg-1 cmp-topicVisualSingle__textContainerOnLeft"; 1322 topicVisualPositioning.colLGsixFlexUpdate = ""; 1323 topicVisualPositioning.topicTitleLGMarginBottom = "margin-bottom: 20%;"; 1324 //added new 1325 topicVisualPositioning.textContainer = "col-12 col-md-6 order-md-1 col-lg-7 order-lg-1"; 1326 }else if(properties["topicVisualSingleStart"] == 'true' && properties["topicVisualSpaceEvenly"] == 'true'){ 1327 topicVisualPositioning.imageStyleElement = "col-12 col-md-6 order-md-1 col-lg-6 order-lg-1"; 1328 topicVisualPositioning.textStyleElement = "col-10 offset-1 col-md-9 offset-md-1 order-md-2 col-lg-7 offset-lg-1 order-lg-2 cmp-topicVisualSingle__textContainerOnRightEvenly"; 1329 topicVisualPositioning.colLGsixFlexUpdate = ""; 1330 topicVisualPositioning.topicTitleLGMarginBottom = ""; 1331 //added new 1332 topicVisualPositioning.textContainer = "col-12 col-md-6 order-md-2 col-lg-6 order-lg-2"; 1333 }else if(properties["topicVisualSingleStart"] == 'false' && properties["topicVisualSpaceEvenly"] == 'true'){ 1334 topicVisualPositioning.imageStyleElement = "col-12 col-md-6 order-md-2 col-lg-6 order-lg-2"; 1335 topicVisualPositioning.textStyleElement = "col-10 offset-1 col-md-9 offset-md-1 order-md-1 col-lg-7 offset-lg-1 order-lg-1 cmp-topicVisualSingle__textContainerOnLeftEvenly"; 1336 topicVisualPositioning.colLGsixFlexUpdate = ""; 1337 topicVisualPositioning.topicTitleLGMarginBottom = ""; 1338 //added new 1339 topicVisualPositioning.textContainer = "col-12 col-md-6 col-lg-6 order-lg-1"; 1340 } 1341 } 1342 return topicVisualPositioning; 1343 }); 1344 1345$(document).ready(function () { 1346 const pageTitle = document.querySelector('.vg-container .cmp-topicSectionOverview'); 1347 1348 if( pageTitle != null && pageTitle != undefined){ 1349 document.querySelector('.cmp-topicSectionOverview').style.paddingTop = "2.5%"; 1350 } 1351}); 1352$(document).ready(function () { 1353 const pageTitle = document.querySelector('.cmp-topicPageHeader .cmp-topicPageHeader__pageTitle'); 1354 1355 if( pageTitle != null && pageTitle != undefined){ 1356 document.querySelector('.cmp-topicPageOverview').classList.add("cmp-topicPageOverview__headline-padding"); 1357 } 1358}); 1359(function () { 1360 1361 "use strict"; 1362 var designStyles = {}; 1363 1364 let noPageTitle = resource.properties["topicHeaderPageTitle"]; 1365 1366 if (noPageTitle) { 1367 designStyles.pageTitle = "padding-top : 0" 1368 } else if (!noPageTitle) { 1369 designStyles.pageTitle = "padding-top : 36"; 1370 } 1371 1372 return designStyles; 1373 1374}); 1375(function () { 1376 1377 "use strict"; 1378 var designStyles = {}; 1379 1380 let onLight = (resource.properties["bgColor"] == '#FFFFFF' || resource.properties["bgColor"] == '#E6E6E6') ? true : false;
1381 let noPageTitle = resource.properties["topicHeaderPageTitle"]; 1382 1383 if (onLight) { 1384 designStyles.textColor = '#040505'; 1385 1386 } else if (!onLight) { 1387 designStyles.textColor = '#FFFFFF'; 1388 } 1389 1390 if (noPageTitle) { 1391 designStyles.pageTitle = "padding-top : 0" 1392 } else if (!noPageTitle) { 1393 designStyles.pageTitle = "padding-top : 36"; 1394 } 1395 1396 return designStyles; 1397 1398}); 1399$(document).ready(function() { 1400 var loadMoreIncrement = 10; 1401 var corpArticleFltrElements = []; 1402 var totalArticles = 0; 1403 var filteredArticles = 0; 1404 const filterChipContainer = $('.cmp-teaserFilter__filterChips'); 1405 1406 // Configuration constants 1407 const FILTER_CONFIG = { 1408 MAX_FILTERS: 50, 1409 MAX_PARAM_LENGTH: 1000, 1410 MAX_SEGMENT_LENGTH: 100 1411 }; 1412 1413 function getSelectedFilters() { 1414 const topicFilters = $('input[name="topicFilter"]:checked').map(function() { 1415 return $(this).val(); 1416 }).get(); 1417 const mediaFilters = $('input[name="mediaFilter"]:checked').map(function() { 1418 return $(this).val(); 1419 }).get(); 1420 1421 return topicFilters.concat(mediaFilters); 1422 } 1423 1424 function updateURLWithFilters() { 1425 const selectedFilters = getSelectedFilters(); 1426 const url = new URL(window.location); 1427 1428 if (selectedFilters.length > 0) { 1429 // Limit to reasonable number of filters to prevent abuse 1430 const safeFilters = selectedFilters 1431 .slice(0, FILTER_CONFIG.MAX_FILTERS) 1432 .map(function(filter) { return encodeURIComponent(filter); }); 1433 url.searchParams.set('filters', safeFilters.join(',')); 1434 } else { 1435 url.searchParams.delete('filters'); 1436 } 1437 1438 // Persist sort selection 1439 var activeSort = $('#teaserFilter-sort-select').val(); 1440 if (activeSort && activeSort !== 'Newest') { 1441 url.searchParams.set('sort', activeSort); 1442 } else { 1443 url.searchParams.delete('sort'); 1444 } 1445 1446 // Update URL without reloading the page 1447 window.history.replaceState({}, '', url); 1448 } 1449 1450 function loadSortFromURL(urlParams) { 1451 var sortParam = urlParams.get('sort'); 1452 if (!sortParam) return; 1453 1454 var $select = $('#teaserFilter-sort-select'); 1455 if ($select.find('option[value="' + sortParam + '"]').length === 0) return; 1456 1457 $select.val(sortParam); 1458 performSort(sortParam); 1459 } 1460 1461 function loadFiltersFromURL(urlParams) { 1462 try { 1463 const filtersParam = urlParams.get('filters'); 1464 if (!filtersParam) return; 1465 1466 // Validate maximum length to prevent memory exhaustion attacks 1467 if (filtersParam.length > FILTER_CONFIG.MAX_PARAM_LENGTH) { 1468 console.warn(`Filters parameter exceeds maximum length of ${FILTER_CONFIG.MAX_PARAM_LENGTH} characters and will be ignored`); 1469 return; 1470 } 1471 1472 // Early validation: check for excessive comma-separated values 1473 const commaCount = (filtersParam.match(/,/g) || []).length; 1474 if (commaCount > FILTER_CONFIG.MAX_FILTERS) { 1475 console.warn('Filters parameter contains too many values and will be ignored'); 1476 return; 1477 } 1478 1479 // Parse and validate filters 1480 const filters = filtersParam 1481 .split(',') 1482 .map(function(segment) { 1483 try { 1484 return decodeURIComponent(segment); 1485 } catch (e) { 1486 console.warn('Invalid URL encoding in filter:', segment); 1487 return null; 1488 } 1489 }) 1490 .filter(Boolean) 1491 .filter(function(segment) { 1492 return segment.length <= FILTER_CONFIG.MAX_SEGMENT_LENGTH; 1493 }) 1494 .slice(0, FILTER_CONFIG.MAX_FILTERS); 1495 1496 if (filters.length === 0) return; 1497 1498 // Build checkbox lookup map (O(m) - one-time cost) 1499 const checkboxMap = new Map(); 1500 $('input[name="topicFilter"]').each(function() { 1501 checkboxMap.set(this.value, this); 1502 }); 1503 $('input[name="mediaFilter"]').each(function() { 1504 checkboxMap.set(this.value, this); 1505 }); 1506 1507 // Apply filters using map lookup (O(n) - linear)
1508 filters.forEach(function(filter) { 1509 const checkbox = checkboxMap.get(filter); 1510 if (checkbox) { 1511 checkbox.checked = true; 1512 } 1513 }); 1514 1515 checkAndDisplay(); 1516 displayFilterChips(); 1517 1518 } catch (error) { 1519 console.error('Failed to load filters from URL:', error); 1520 logClientError("WARN", "Failed to load filters from URL: " + (error && error.message ? error.message : String(error)), "teaser-filter.js"); 1521 // Clean up invalid URL params 1522 const url = new URL(window.location); 1523 url.searchParams.delete('filters'); 1524 window.history.replaceState({}, '', url); 1525 } 1526 } 1527 1528 function clearCheckboxes(){ 1529 let topicFilter = document.getElementsByName("topicFilter"); 1530 let mediaFilter = document.getElementsByName("mediaFilter"); 1531 topicFilter.forEach(element => { 1532 element.checked = false; 1533 }); 1534 mediaFilter.forEach(element => { 1535 element.checked = false; 1536 }); 1537 } 1538 1539 function clearChips() { 1540 filterChipContainer.empty(); 1541 displayArticleCounts("topicFilter"); 1542 displayArticleCounts("mediaFilter"); 1543 } 1544 1545 $(".reset-all-filters").click(function(event) { 1546 $(this).hide(); 1547 event.preventDefault(); 1548 clearCheckboxes(); 1549 clearChips(); 1550 displayAllArticles(); 1551 updateURLWithFilters(); 1552 displayNoArticlesMessage(); 1553 displayArticleCounts("topicFilter"); 1554 displayArticleCounts("mediaFilter"); 1555 }); 1556 1557 1558 displayAllArticles(); 1559 1560 filterChipContainer.on('click', '.clear-all-chip-filter', function() { 1561 clearCheckboxes(); 1562 $('.reset-all-filters').hide(); 1563 clearChips(); 1564 displayAllArticles(); 1565 updateURLWithFilters(); 1566 displayNoArticlesMessage(); 1567 displayArticleCounts("topicFilter"); 1568 displayArticleCounts("mediaFilter"); 1569 }); 1570 1571 // Initialize on page load 1572 1573 function initializeFilters() { 1574 totalArticles = $(".corparticle-filter-list").length; 1575 $("#totalArticles").html(totalArticles); 1576 loadFiltersFromURL(new URL(window.location).searchParams); 1577 1578 if (getSelectedFilters().length === 0) { 1579 1580 displayAllArticles(); 1581 } 1582 } 1583 1584 initializeFilters(); 1585 displayArticleCounts("topicFilter"); 1586 displayArticleCounts("mediaFilter"); 1587 1588 $('input[name="topicFilter"]').change( function() { 1589 checkAndDisplay(); 1590 displayFilterChips(); 1591 updateURLWithFilters(); 1592 displayNoArticlesMessage(); 1593 displayArticleCounts("topicFilter"); 1594 displayArticleCounts("mediaFilter"); 1595 }); 1596 1597 $('input[name="mediaFilter"]').change( function() { 1598 checkAndDisplay(); 1599 displayFilterChips(); 1600 updateURLWithFilters(); 1601 displayNoArticlesMessage(); 1602 displayArticleCounts("topicFilter"); 1603 displayArticleCounts("mediaFilter"); 1604 }); 1605 1606 filterChipContainer.on('click', '.cmp-teaserFilter__filterChipClose', function() { 1607 1608 let filterToRemove = $(this).parent().text(); 1609 // revert filter display name to original value using helper 1610 filterToRemove = revertFilterToOriginal(filterToRemove); 1611 $('input[name="topicFilter"][value="' + filterToRemove + '"]').prop('checked', false); 1612 $('input[name="mediaFilter"][value="' + filterToRemove + '"]').prop('checked', false); 1613 $(this).parent().remove(); 1614 1615 if ( filterChipContainer.children().length <= 1 ) { 1616 filterChipContainer.empty(); 1617 } 1618 1619 checkAndDisplay(); 1620 updateURLWithFilters(); 1621 displayArticleCounts("topicFilter"); 1622 displayArticleCounts("mediaFilter"); 1623 displayNoArticlesMessage(); 1624 }); 1625 1626 function formatFilters(filterType) { 1627 let filterElements = []; 1628 let filterChecked = $('input[name="' + filterType + '"]:checked'); 1629 1630 if ( filterChecked.length > 0 ) { 1631 filterChecked.each( function() { 1632 let selectedFilter = $(this).val(); 1633 // remove extra characters for display 1634 if (selectedFilter.includes("___")) { 1635 selectedFilter = selectedFilter.replace(/___/g, " & "); 1636 } if (selectedFilter.includes("_")) { 1637 selectedFilter = selectedFilter.replace(/_/g, " "); 1638 } if (selectedFilter === "Durable") { 1639 selectedFilter = "About Vanguard"; 1640 } 1641 filterElements.push(selectedFilter); 1642 }); 1643 } 1644 1645 return filterElements; 1646 } 1647 1648 function displayFilterChips() { 1649 let filterElements = formatFilters("topicFilter").concat(formatFilters("mediaFilter")); 1650 filterChipContainer.empty(); 1651 1652 // add chips for selected filters to page
1653 filterElements.forEach(function(filter) { 1654 let filterChipDiv = $('<div class="cmp-teaserFilter__filterChip"></div>'); 1655 let filterChipClose = $('<button type="button" class="cmp-teaserFilter__filterChipClose" aria-label="Remove filter: ' + filter + '"></button>'); 1656 filterChipDiv.text(filter); 1657 filterChipDiv.append(filterChipClose); 1658 filterChipContainer.append(filterChipDiv); 1659 }); 1660 1661 // add clear all option to page if there are selected filters 1662 if ( filterElements.length > 0 ) { 1663 filterChipContainer.show(); 1664 let clearAllDiv = $('<button type="button" aria-label="Clear all filters" class="clear-all-chip-filter"></button>'); 1665 clearAllDiv.text("Clear all"); 1666 filterChipContainer.append(clearAllDiv); 1667 } 1668 } 1669 1670 // Helper to revert a filter display name back to its original internal value 1671 function revertFilterToOriginal(displayName) { 1672 if (displayName === "About Vanguard") { 1673 return "Durable"; 1674 } 1675 // Apply the same sequence of replacements as previously done inline 1676 let original = displayName.replace(/ & /g, "___"); 1677 original = original.replace(/ /g, "_"); 1678 return original; 1679 } 1680 1681 function filterArticles(filterType) { 1682 let filteredArticles = []; 1683 1684 $('input[name="' + filterType + '"]:checked').each( function() { 1685 let selectedArticleType = $(this).val(); 1686 let classList = $("."+selectedArticleType).map(function() { 1687 return $(this).attr('class'); 1688 }).get(); 1689 filteredArticles = Array.from(new Set(classList.concat([...filteredArticles]))); 1690 }); 1691 1692 return filteredArticles; 1693 } 1694 1695 function checkAndDisplay() { 1696 corpArticleFltrElements = []; 1697 let topicFilterChecked = $('input[name="topicFilter"]:checked').length > 0; 1698 let mediaFilterChecked = $('input[name="mediaFilter"]:checked').length > 0; 1699 1700 if (topicFilterChecked && mediaFilterChecked) { 1701 $(".reset-all-filters").show(); 1702 let filteredByTopic = filterArticles("topicFilter"); 1703 let filteredByMedia = filterArticles("mediaFilter"); 1704 corpArticleFltrElements = filteredByTopic.filter(value => filteredByMedia.includes(value)); 1705 $('.corparticle-filter-list').hide(); 1706 1707 } else if (topicFilterChecked && !mediaFilterChecked) { 1708 $(".reset-all-filters").show(); 1709 corpArticleFltrElements = filterArticles("topicFilter"); 1710 $('.corparticle-filter-list').hide(); 1711 1712 } else if (mediaFilterChecked && !topicFilterChecked) { 1713 $(".reset-all-filters").show(); 1714 corpArticleFltrElements = filterArticles("mediaFilter"); 1715 $('.corparticle-filter-list').hide(); 1716 1717 } else { 1718 $(".reset-all-filters").hide(); 1719 displayAllArticles(); 1720 } 1721 1722 checkForLoadMore(); 1723 1724 } 1725 1726 function displayAllArticles() { 1727 corpArticleFltrElements = $(".corparticle-filter-list").map(function() { 1728 return $(this).attr('class'); 1729 }).get(); 1730 totalArticles = corpArticleFltrElements.length; 1731 $("#totalArticles").html(totalArticles); 1732 checkForLoadMore(); 1733 } 1734 1735 function displayArticleCounts(filterType) { 1736 let topicFilterChecked = $('input[name="topicFilter"]:checked').length > 0; 1737 let mediaFilterChecked = $('input[name="mediaFilter"]:checked').length > 0; 1738 let filteredByTopic = filterArticles("topicFilter"); 1739 let filteredByMedia = filterArticles("mediaFilter"); 1740 let articleFilterCounts = {}; 1741 1742 $('input[name="' + filterType + '"]').each( function() { 1743 let filter = $(this).val(); 1744 let count = 0; 1745 1746 if (!topicFilterChecked && !mediaFilterChecked) { 1747 count = $("."+filter).length; 1748 articleFilterCounts[filter] = count; 1749 grayZeroCountFilterItem($(this), count); 1750 $(this).parent().find(".cmp-teaserFilter__articleCount").html( "(" + count + ")" ); 1751 1752 } else if (topicFilterChecked && mediaFilterChecked) { 1753 if ( $(this).is(':checked') ) { 1754 let filteredArticles = filteredByTopic.filter(value => filteredByMedia.includes(value)); 1755 count = $("."+filter).filter(function() { 1756 return filteredArticles.includes($(this).attr('class')); 1757 }).length; 1758 } else if (filterType === 'mediaFilter') { 1759 count = $("."+filter).filter(function() { 1760 return filteredByTopic.includes($(this).attr('class')); 1761 }).length; 1762 } else if (filterType === 'topicFilter') { 1763 count = $("." + filter).filter(function () { 1764 return filteredByMedia.includes($(this).attr('class')); 1765 }).length; 1766 } 1767 articleFilterCounts[filter] = count; 1768 grayZeroCountFilterItem($(this), count); 1769 $(this).parent().find(".cmp-teaserFilter__articleCount").html( "(" + count + ")" ); 1770 1771 } else if (topicFilterChecked && !mediaFilterChecked) { 1772 if ( $(this).is(':checked') || filterType === 'topicFilter') { 1773 count = $("."+filter).length; 1774 articleFilterCounts[filter] = count; 1775 grayZeroCountFilterItem($(this), count); 1776 $(this).parent().find(".cmp-teaserFilter__articleCount").html( "(" + count + ")" ); 1777 1778 } else if (filterType === 'mediaFilter') { 1779 count = $("."+filter).filter(function() { 1780 return filteredByTopic.includes($(this).attr('class')); 1781 }).length; 1782 articleFilterCounts[filter] = count; 1783 grayZeroCountFilterItem($(this), count); 1784 $(this).parent().find(".cmp-teaserFilter__articleCount").html( "(" + count + ")" ); 1785 } 1786 1787 } else if (mediaFilterChecked && !topicFilterChecked) { 1788 if ( $(this).is(':checked') || filterType === 'mediaFilter') { 1789 count = $("." + filter).length; 1790 articleFilterCounts[filter] = count; 1791 grayZeroCountFilterItem($(this), count); 1792 $(this).parent().find(".cmp-teaserFilter__articleCount").html("(" + count + ")"); 1793 1794 } else if (filterType === 'topicFilter') { 1795 count = $("."+filter).filter(function() { 1796 return filteredByMedia.includes($(this).attr('class')); 1797 }).length; 1798 articleFilterCounts[filter] = count; 1799 grayZeroCountFilterItem($(this), count); 1800 $(this).parent().find(".cmp-teaserFilter__articleCount").html( "(" + count + ")" ); 1801 } 1802 } 1803 }); 1804 } 1805 1806 function grayZeroCountFilterItem (filter, count) { 1807 if (count === 0) { 1808 filter.parent().addClass("zero-article-filter"); 1809 filter.prop('disabled', !filter.prop('checked')); 1810 } else { 1811 filter.parent().removeClass("zero-article-filter"); 1812 filter.prop('disabled', false); 1813 } 1814 } 1815 1816 function displayNoArticlesMessage() { 1817 if (corpArticleFltrElements.length === 0) { 1818 $(".cmp-teaserFilter__noResults").show(); 1819 } else { 1820 $(".cmp-teaserFilter__noResults").hide(); 1821 } 1822 } 1823 1824 function checkForLoadMore() { 1825 $("#filteredArticles").html(corpArticleFltrElements.length); 1826 $.each(corpArticleFltrElements, function(index, value) { 1827 if ( index < loadMoreIncrement ) { 1828 $('.'+value.replace(/ /g, '.')).show(); 1829 } else { 1830 $('.'+value.replace(/ /g, '.')).hide(); 1831 } 1832 }); 1833 1834 if ( corpArticleFltrElements.length <= loadMoreIncrement ) { 1835 $(".cmp-filterList__buttonCtaContainer").hide(); 1836 } else { 1837 $(".cmp-filterList__buttonCtaContainer").show(); 1838 } 1839 $(".cmp-filterList__buttonCtaContainer").data("visibleItems" , 10); 1840 1841 } 1842 1843 $(".cmp-filterList__buttonCtaContainer").click(function(){
1844 loadMoreArticles(); 1845 }); 1846 1847 function loadMoreArticles(){ 1848 let visibleItems = $(".cmp-filterList__buttonCtaContainer").data("visibleItems"); 1849 let noItemsToBeShown = (visibleItems + loadMoreIncrement) ; 1850 $.each(corpArticleFltrElements, function(index, value) { 1851 if ( index < noItemsToBeShown ) { 1852 $('.'+value.replace(/ /g, '.')).show(); 1853 } else { 1854 $('.'+value.replace(/ /g, '.')).hide(); 1855 } 1856 }); 1857 1858 $(".cmp-filterList__buttonCtaContainer").data("visibleItems" , noItemsToBeShown); 1859 if ( corpArticleFltrElements.length <= noItemsToBeShown ) { 1860 $(".cmp-filterList__buttonCtaContainer").hide(); 1861 } 1862 1863 } 1864 1865 1866 function sortDescendingWithClassSelector(firstEle, secondEle) { 1867 var date1 = new Date($('.'+firstEle.replace(/ /g, '.')).find(".article-publish-date").text()); 1868 var date2 = new Date($('.'+secondEle.replace(/ /g, '.')).find(".article-publish-date").text()); 1869 1870 return (isNaN(date1) || (isNaN(date1) && isNaN(date2)) || (!isNaN(date2) && date1 < date2)) ? 1 : -1; 1871 }; 1872 1873 1874 function sortAscendingWithClassSelector(firstEle, secondEle) { 1875 var date1 = new Date($('.'+firstEle.replace(/ /g, '.')).find(".article-publish-date").text()); 1876 var date2 = new Date($('.'+secondEle.replace(/ /g, '.')).find(".article-publish-date").text()); 1877 1878 return (isNaN(date1) || (isNaN(date1) && isNaN(date2)) || (!isNaN(date2) && date1 < date2)) ? -1 : 1; 1879 } 1880 1881 function sortDescending(firstEle, secondEle) { 1882 var date1 = new Date($(firstEle).find(".article-publish-date").text()); 1883 var date2 = new Date($(secondEle).find(".article-publish-date").text()); 1884 1885 return (isNaN(date1) || (isNaN(date1) && isNaN(date2)) || (!isNaN(date2) && date1 < date2)) ? 1 : -1; 1886 }; 1887 1888 1889 function sortAscending(firstEle, secondEle) { 1890 var date1 = new Date($(firstEle).find(".article-publish-date").text()); 1891 var date2 = new Date($(secondEle).find(".article-publish-date").text()); 1892 1893 return (isNaN(date1) || (isNaN(date1) && isNaN(date2)) || (!isNaN(date2) && date1 < date2)) ? -1 : 1; 1894 } 1895 1896 function performSort(value) { 1897 if (value === "Newest") { 1898 corpArticleFltrElements.sort(sortDescendingWithClassSelector); 1899 $('#corpfilterarticles .corparticle-filter-list').sort(sortDescending).appendTo('#corpfilterarticles'); 1900 } else { 1901 corpArticleFltrElements.sort(sortAscendingWithClassSelector); 1902 $('#corpfilterarticles .corparticle-filter-list').sort(sortAscending).appendTo('#corpfilterarticles'); 1903 } 1904 checkForLoadMore(); 1905 } 1906 1907 // Sort dropdown: handle selection 1908 $("#teaserFilter-sort-select").on('change', function () { 1909 var value = $(this).val(); 1910 performSort(value); 1911 updateURLWithFilters(); 1912 }); 1913 1914 $(window).bind('resize load', function() { 1915 if ($(this).width() > 1022) { 1916 $('.filter-accordion').removeClass('collapsed'); 1917 $('.collapse').addClass('show'); 1918 } else { 1919 $('.filter-accordion').addClass('collapsed'); 1920 $('.collapse').removeClass('show'); 1921 } 1922 1923 }); 1924 1925 // Mobile filter modal 1926 var $component = $('.cmp-teaserFilter'); 1927 var $mobileModal = $component.find('.cmp-teaserFilter__mobileModal'); 1928 var $modalBody = $mobileModal.find('.cmp-teaserFilter__mobileModal-body'); 1929 var $filterColumn = $component.find('.cmp-teaserFilter__filterColumn'); 1930 var $dateSort = $component.find('.cmp-teaserFilter__dateSort'); 1931 1932 var $triggerBtn = $component.find('.cmp-teaserFilter__mobileFilterBtn'); 1933 var $mainContent = $component.find('.cmp-teaserFilter__container'); 1934 1935 //Direct Accessibility Focus to Pop Up Teaser Filter Modal (when on mobile/small screen) 1936 function trapFocus(e) { 1937 var focusableEls = $mobileModal.find('button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])').filter(':visible'); 1938 var firstEl = focusableEls.first(); 1939 var lastEl = focusableEls.last(); 1940 1941 if (e.key === 'Tab') { 1942 if (!$mobileModal[0].contains(document.activeElement)) { 1943 e.preventDefault();
1944 firstEl.focus(); 1945 } else if (e.shiftKey) { 1946 if (document.activeElement === firstEl[0]) { 1947 e.preventDefault(); 1948 lastEl.focus(); 1949 } 1950 } else { 1951 if (document.activeElement === lastEl[0]) { 1952 e.preventDefault(); 1953 firstEl.focus(); 1954 } 1955 } 1956 } 1957 1958 if (e.key === 'Escape') { 1959 $mobileModal.find('.cmp-teaserFilter__mobileModal-close').trigger('click'); 1960 } 1961 } 1962 1963 $triggerBtn.on('click', function() { 1964 $modalBody.append($filterColumn.find('.cmp-teaserFilter__filterAccordion')); 1965 $modalBody.append($dateSort); 1966 $mobileModal.removeAttr('hidden'); 1967 $mainContent.attr('aria-hidden', 'true'); 1968 $('body').css('overflow', 'hidden'); 1969 $mobileModal.find('.cmp-teaserFilter__mobileModal-close').focus(); 1970 $(document).on('keydown.mobileModal', trapFocus); 1971 }); 1972 1973 $mobileModal.on('click', '.cmp-teaserFilter__mobileModal-close', function() { 1974 $filterColumn.append($modalBody.find('.cmp-teaserFilter__filterAccordion')); 1975 $component.find('.cmp-teaserFilter__filterDateContainer').append($modalBody.find('.cmp-teaserFilter__dateSort')); 1976 $mobileModal.attr('hidden', ''); 1977 $mainContent.removeAttr('aria-hidden'); 1978 $('body').css('overflow', ''); 1979 $(document).off('keydown.mobileModal'); 1980 $triggerBtn.focus(); 1981 }); 1982}); 1983 1984jQuery(document).ready(function($){ 1985 var k = 1; 1986 $('.table').each(function(){ 1987 $(this).attr('id','tableIndex'+k); 1988 var tabledata = $(this).attr('id'); 1989 var i = 1; 1990 $('#'+tabledata+' '+' .table-data tr:first-child td').each(function(){ 1991 $(this).addClass('row-count'+i); 1992 i++; 1993 }); 1994 k++; 1995 setTimeout(addScroll, 1000); 1996 function addScroll(){ 1997 if( $('#'+tabledata+ ' .left-column-padding .row-count5').length === 1 ){ 1998 $('#'+tabledata+' '+'.table-container').addClass('activate-scroll'); 1999 $('#'+tabledata+' '+' .scroll-btns').addClass('activate-buttons'); 2000 } 2001 if( $('#'+tabledata+ ' .no-left-padding .row-count7').length === 1 ){ 2002 $('#'+tabledata+' '+'.table-container').addClass('activate-scroll'); 2003 $('#'+tabledata+' '+' .scroll-btns').addClass('activate-buttons'); 2004 } 2005 } 2006 2007 2008 var colTotalCount = $('#'+tabledata+ ' table th').length; 2009 var colCount = colTotalCount - 1; 2010 var currentColumn = 1; 2011 2012 2013 const columnWidth = 100/colTotalCount; 2014 2015 if( $('#'+tabledata+ ' table').attr('data-firstcolumnwidth') == 'on'){ 2016 $('#'+tabledata+ ' table tr td:first-child').attr('width', columnWidth+10+'%'); 2017 const restColumnsWidth = 90/colTotalCount; 2018 $('#'+tabledata+ ' table tr td:not(:first-child)').attr('width', restColumnsWidth+'%'); 2019 let oldWidthValue = $('#'+tabledata+ ' table tr td:first-child').width(); 2020 let updatedWidthValue = oldWidthValue + 50; 2021 $('#'+tabledata+ ' table tr td:first-child').css('cssText', 'min-width:'+ updatedWidthValue+'px;'); 2022 $('#'+tabledata+ ' .left-scroll-shadow').css('cssText', 'left:'+ updatedWidthValue+'px;'); 2023 2024 }else{ 2025 $('#'+tabledata+ ' table tr td').attr('width', columnWidth+'%'); 2026 } 2027 2028 2029 2030 $('#'+tabledata+ ' table tr td:first-child').each(function(){ 2031 let rowSpanCount = $(this).attr('rowspan'); 2032 if($(this).attr('rowspan') > 1){ 2033 $(this).addClass('extended-row'); 2034 $('#'+tabledata+ ' table tr').slice('-'+rowSpanCount).remove(); 2035 } 2036 }); 2037 2038 if(colTotalCount > 6){ 2039 $('#'+tabledata+' '+' .scroll-btns').addClass('activate-buttons'); 2040 $('#'+tabledata+' '+' .table-container').addClass('activate-scroll'); 2041 } 2042 if (window.matchMedia('(max-width: 767px)').matches) { 2043 $('#'+tabledata+' '+'.table-container').addClass('activate-scroll'); 2044 } 2045 else if (window.matchMedia('(max-width: 1023px)').matches && colTotalCount > 4 ) { 2046 $('#'+tabledata+' '+' .scroll-btns').addClass('activate-buttons'); 2047 $('#'+tabledata+' '+'.table-container').addClass('activate-scroll'); 2048 2049 } 2050 2051 const tableContainer = $('#'+tabledata+' '+' .table-container'); 2052 var containerWidth = tableContainer.width(); 2053 var lastVal; 2054 2055 2056 tableContainer.scroll(function () { 2057 var scrollPercentage=100*this.scrollLeft/this.scrollWidth/(1-this.clientWidth/this.scrollWidth); 2058 let scrollValue = scrollPercentage.toFixed(2); 2059 2060 if(scrollValue < 1){ 2061 $('#'+tabledata+ ' .left-button').addClass('disable-btn'); 2062 $('#'+tabledata+ ' .right-button').removeClass('disable-btn'); 2063 }else if(scrollValue > 1){ 2064 $('#'+tabledata+ ' .left-button').removeClass('disable-btn'); 2065 } 2066 if(scrollValue > 99){ 2067 $('#'+tabledata+ ' .right-button').addClass('disable-btn'); 2068 }else if(scrollValue < 99){ 2069 $('#'+tabledata+ ' .right-button').removeClass('disable-btn'); 2070 } 2071 2072 var scrollLeftValue = tableContainer[0].scrollLeft; 2073 var scrollFullValue = tableContainer[0].scrollWidth; 2074 2075 var scrollCount = scrollLeftValue;
2076 var columnSize = scrollCount/130; 2077 if(scrollCount >= 20){ 2078 $('#'+tabledata+ ' .table-column').addClass('activate-left-shadow'); 2079 }else{ 2080 $('#'+tabledata+ ' .table-column').removeClass('activate-left-shadow'); 2081 } 2082 2083 2084 var roundingValue = Math.ceil(columnSize); 2085 var tabVersionValue = roundingValue + 2; 2086 if($('#'+tabledata+ ' .table-column').hasClass('left-column-padding')){ 2087 var desktopversionValue = roundingValue + 3; 2088 } else{ 2089 var desktopversionValue = roundingValue + 4; 2090 } 2091 if (window.matchMedia('(max-width: 1023px)').matches && roundingValue > 4) { 2092 $('#'+tabledata+' '+' .scroll-btns').addClass('activate-buttons'); 2093 } 2094 }); 2095 2096 2097 $('#'+tabledata+ ' .right-button').click(function(event) { 2098 event.preventDefault(); 2099 $('#'+tabledata+ ' .table-container').animate({ 2100 scrollLeft: "+=" + 130 + "px" 2101 }, "slow"); 2102 }); 2103 2104 $('#'+tabledata+ ' .left-button').click(function(event) { 2105 event.preventDefault(); 2106 $('#'+tabledata+ ' .table-container').animate({ 2107 scrollLeft: "-=" + 130 + "px" 2108 }, "slow"); 2109 }); 2110 2111 }); 2112 2113 2114 $('table .cmp-text').each(function(){ 2115 if($(this).attr('data-backgroun-dcolor')){ 2116 let bgcolor = $(this).css('background-color'); 2117 $(this).closest('th').css('background', bgcolor); 2118 $(this).closest('td').css('cssText', 'background-color:'+ bgcolor+'!important;'); 2119 } 2120 }); 2121 2122 $('.table-data').each(function(){ 2123 $(this).attr('id','tableId'+k); 2124 var tableId = $(this).attr('id'); 2125 var tableBorderWidth = $(this).attr('data-linewidth'); 2126 var tableBorderColor = $(this).attr('data-linecolor'); 2127 2128 2129 if($(this).attr('data-frametype') == 'lines'){ 2130 $(this).addClass('table-frame'); 2131 $(this).find('td').css('border', 'solid'); 2132 $(this).find('td').css('border-width', tableBorderWidth); 2133 $(this).find('th').css('border-color', tableBorderColor); 2134 $(this).find('td').css('border-color', tableBorderColor); 2135 $(this).find('td').css('padding', columnGap); 2136 $(this).find('th').css('cssText', 'border: solid '+ tableBorderColor +' '+ tableBorderWidth+'px'); 2137 } 2138 2139 if($(this).attr('data-verticallines') == 'off'){ 2140 $(this).addClass('no-vertical-borders'); 2141 2142 } 2143 if($(this).attr('data-horizontallines') == 'off'){ 2144 $(this).addClass('no-horizontal-borders'); 2145 2146 2147 } 2148 }); 2149 2150 2151}); 2152(function (window) { 2153 window.subscriptionAnalytics = { 2154 2155 SUBSCRIBER_STATUS_TO_SUBPAGE_ID: { 2156 confirmed: "subscription-dashboard-preferences", 2157 subscriptionActivated: "subscription-dashboard-subscribed", 2158 newSubscriptionManaged: "subscription-dashboard-subscribed-preferences-saved", 2159 alreadySubscribed: "subscription-dashboard-already-subscribed-preferences", 2160 oldSubscriptionManaged: "subscription-dashboard-already-subscribed-preferences-saved" 2161 }, 2162 2163 SUBSCRIBER_STATUS_TO_CTA_LOCATION: { 2164 verifyEmailSent: "verify email", 2165 confirmed: "preferences", 2166 subscriptionActivated: "subscribed", 2167 newSubscriptionManaged: "subscribed preferences saved", 2168 alreadySubscribed: "already subscribed preferences", 2169 oldSubscriptionManaged: "already subscribed preferences saved" 2170 }, 2171 2172 SUBSCRIBER_STATUS_TO_PAGE_LOAD_DATA: { 2173 verifyEmailSent: { 2174 pageName: 'us:en:corp:subscription-dashboard-verify-email', 2175 pageTitle: 'verify email' 2176 }, 2177 subscriptionActivated: { 2178 pageName: 'us:en:corp:subscription-dashboard-subscribed', 2179 pageTitle: 'subscribed' 2180 }, 2181 newSubscriptionManaged: { 2182 pageName: 'us:en:corp:subscription-dashboard-subscribed-preferences-saved', 2183 pageTitle: 'subscribed preferences saved' 2184 }, 2185 alreadySubscribed: { 2186 pageName: 'us:en:corp:subscription-dashboard-already-subscribed-preferences', 2187 pageTitle: 'already subscribed preferences' 2188 }, 2189 oldSubscriptionManaged: { 2190 pageName: 'us:en:corp:subscription-dashboard-already-subscribed-preferences-saved', 2191 pageTitle: 'already subscribed preferences saved' 2192 } 2193 }, 2194 2195 getSubPageIDBySubscriberStatus() {
2196 const subscriberStatus = this.getSubscriberStatus(); 2197 return this.SUBSCRIBER_STATUS_TO_SUBPAGE_ID[subscriberStatus] || ""; 2198 }, 2199 2200 getCallToActionLocationBySubscriberStatus() { 2201 const subscriberStatus = this.getSubscriberStatus(); 2202 let location = this.SUBSCRIBER_STATUS_TO_CTA_LOCATION[subscriberStatus] || ""; 2203 location += " | subscription dashboard"; 2204 return location; 2205 }, 2206 2207 getSubscriberStatus() { 2208 try { 2209 if (typeof window !== "undefined" && window.localStorage) { 2210 return window.localStorage.getItem("subscriberStatus") || ""; 2211 } 2212 } catch (e) { 2213 logClientError("WARN", "localStorage access failed: " + (e && e.message ? e.message : String(e)), "subscriptionAnalytics.js"); 2214 } 2215 return ""; 2216 }, 2217 2218 getPageLoadDataBySubscriberStatus() { 2219 const subscriberStatus = this.getSubscriberStatus(); 2220 return this.SUBSCRIBER_STATUS_TO_PAGE_LOAD_DATA[subscriberStatus] || ""; 2221 }, 2222 2223 pushToDataLayer(payload) { 2224 window.dataLayer = window.dataLayer || []; 2225 window.dataLayer.push(payload); 2226 }, 2227 2228 pushSubPageViewedEventOnSignUpTabClick() { 2229 this.pushToDataLayer({ 2230 event: "subPageViewed", //e166 2231 subPage: { 2232 "subPageID": "subscription-dashboard:sign up", //v105 2233 "spClicked": "true" 2234 } 2235 }); 2236 }, 2237 2238 pushSubPageViewedEventOnAlreadySubscribedTabClick() { 2239 this.pushToDataLayer({ 2240 event: "subPageViewed", //e166 2241 subPage: { 2242 "subPageID": "subscription-dashboard:already subscribed", //v105 2243 "spClicked": "true" 2244 } 2245 }); 2246 }, 2247 2248 pushSubPageViewedEventOnSubscriptionPreferencesTabClick() { 2249 let subPageID = this.getSubPageIDBySubscriberStatus(); 2250 subPageID += ":subscription preferences"; 2251 this.pushToDataLayer({ 2252 event: "subPageViewed", //e166 2253 subPage: { 2254 "subPageID": subPageID, //v105 2255 "spClicked": "true" 2256 } 2257 }); 2258 }, 2259 2260 pushSubPageViewedEventOnPersonalInformationTabClick() { 2261 let subPageID = this.getSubPageIDBySubscriberStatus(); 2262 subPageID += ":personal information"; 2263 this.pushToDataLayer({ 2264 event: "subPageViewed", //e166 2265 subPage: { 2266 "subPageID": subPageID, //v105 2267 "spClicked": "true" 2268 } 2269 }); 2270 }, 2271 2272 pushCallToActionEventOnViewPrivacyPolicy() { 2273 this.pushToDataLayer({ 2274 event: "callToAction", //e111 2275 callToAction: { 2276 "location": "subscription-dashboard", //v103 2277 "ctaName": "privacy policy", //v104 2278 "ctaType": "text link" //v111 2279 } 2280 }); 2281 }, 2282 2283 pushCallToActionEventOnResendEmail() { 2284 let location = this.getCallToActionLocationBySubscriberStatus(); 2285 this.pushToDataLayer({ 2286 event: "callToAction", //e111 2287 callToAction: { 2288 "location": location, //v103 2289 "ctaName": "click here to resend", //v104 2290 "ctaType": "text link" //v111 2291 } 2292 }); 2293 }, 2294 2295 pushCallToActionEventOnSelectAllCheckboxClick() { 2296 let location = this.getCallToActionLocationBySubscriberStatus(); 2297 this.pushToDataLayer({ 2298 event: "callToAction", //e111 2299 callToAction: { 2300 "location": location, //v103 2301 "ctaName": "select all", //v104 2302 "ctaType": "checkbox" //v111 2303 } 2304 }); 2305 }, 2306 2307 pushCallToActionEventOnUnsubscribeFromAllLinkClick() { 2308 let location = this.getCallToActionLocationBySubscriberStatus(); 2309 this.pushToDataLayer({ 2310 event: "callToAction", //e111 2311 callToAction: { 2312 "location": location, //v103 2313 "ctaName": "unsubscribe from all", //v104 2314 "ctaType": "text link" //v111 2315 } 2316 }); 2317 }, 2318 2319 pushCallToActionEventOnUnsubscribeFromAllDismissButtonClick() { 2320 let location = this.getCallToActionLocationBySubscriberStatus(); 2321 this.pushToDataLayer({ 2322 event: "callToAction", //e111 2323 callToAction: { 2324 "location": location, //v103 2325 "ctaName": "confirm leave without saving", //v104 2326 "ctaType": "primary button" //v111 2327 } 2328 }); 2329 }, 2330 2331 pushCallToActionEventOnAlreadySubscribedNextButtonClick() { 2332 this.pushToDataLayer({ 2333 event: "callToAction", //e111 2334 callToAction: { 2335 "location": "subscription-dashboard", //v103 2336 "ctaName": "next", //v104 2337 "ctaType": "primary button" //v111 2338 } 2339 }); 2340 }, 2341 2342 pushCallToActionEventOnPersonalInformationEdit() { 2343 this.pushToDataLayer({ 2344 event: "callToAction", //e111 2345 callToAction: { 2346 "location": "Dashboard - Personal Details", //v103 2347 "ctaName": "edit", //v104 2348 "ctaType": "text link" //v111 2349 } 2350 }); 2351 }, 2352 2353 pushCallToActionEventOnPersonalInformationSave() { 2354 this.pushToDataLayer({ 2355 event: "callToAction", //e111 2356 callToAction: { 2357 "location": "Dashboard - Personal Details", //v103 2358 "ctaName": "save", //v104 2359 "ctaType": "primary button" //v111 2360 } 2361 }); 2362 }, 2363 2364 pushCallToActionEventOnPersonalInformationCancel() { 2365 this.pushToDataLayer({ 2366 event: "callToAction", //e111 2367 callToAction: { 2368 "location": "Dashboard - Personal Details", //v103 2369 "ctaName": "cancel", //v104 2370 "ctaType": "text link" //v111 2371 } 2372 }); 2373 }, 2374 2375 pushSubscriptionInitiatedEvent() { 2376 this.pushToDataLayer({ 2377 event: "subscriptionInitiated", //e94 2378 subscription: { 2379 "subscriptionName": ['DASHBOARD'], //list1 2380 } 2381 }); 2382 }, 2383 2384 pushPageLoadedEventBySubscriberStatus() { 2385 let pageData = this.getPageLoadDataBySubscriberStatus(); 2386 let pageName = pageData.pageName; 2387 let pageTitle = pageData.pageTitle + ' | subscription dashboard'; 2388 this.pushToDataLayer({ 2389 event: "pageLoaded", 2390 page: { 2391 "pageTitle": pageTitle, //c30 2392 "pageName": pageName, //c18, v18 2393 "siteSection1": "global", //c38 2394 "siteSection2": "subscription-dashboard", //c39 2395 "siteIdentifier": "corp", 2396 "is404": "false", 2397 "DLVersion": "1.0.0", //c42, v42 2398 } 2399 }); 2400 }, 2401 2402 pushSubscriptionActivatedEvent(subscriptionNames) { 2403 this.pushToDataLayer({ 2404 event: "subscriptionActivated", //e95 2405 subscription: { 2406 subscriptionName: subscriptionNames, //list1 2407 subscriberID: "", //v61 2408 subscriptionType: "dashboard" //v242 2409 } 2410 }); 2411 }, 2412 2413 pushSubscriptionManagedEvent(subscriptionNames) { 2414 this.pushToDataLayer({ 2415 event: "subscriptionManaged", //e96 2416 subscription: { 2417 subscriptionName: subscriptionNames, //list1 2418 subscriberID: "", //v61 2419 subscriptionType: "dashboard" //v242 2420 } 2421 }); 2422 }, 2423 2424 pushSubscriptionUnsubscribedEvent(subscriptionNames) { 2425 this.pushToDataLayer({ 2426 event: "subscriptionUnsubscribed", //e97 2427 subscription: { 2428 subscriptionName: subscriptionNames, //list1 2429 subscriberID: "" //v61 2430 } 2431 }); 2432 }, 2433 2434 pushPageLoadCompletedEvent() { 2435 this.pushToDataLayer({ 2436 event: "pageLoadCompleted" 2437 }); 2438 } 2439 }; 2440}) (window); 2441 2442/* The Subscription Dashboard is a single-page application (SPA). Since user flow does not change the URL, 2443 we keep track of the current state in the "subscriberStatus" value in localStorage for analytics tracking purposes. 2444 2445 "subscriberStatus" values: 2446 verifyEmailSent : New subscriber submitted email and industry; verification email sent 2447 confirmed : New subscriber clicked on verify link in email; subscriptions not yet activated 2448 subscriptionActivated : New subscriber activated subscriptions (clicked "Subscribe") 2449 newSubscriptionManaged : New subscriber managed preferences (clicked "Save preferences") 2450 alreadySubscribed : Existing subscriber logged in to manage preferences 2451 oldSubscriptionManaged : Existing subscriber managed preferences 2452 unsubscribedAll : Existing subscriber unsubscribed from all 2453*/ 2454 2455(() => { 2456 2457 $(document).ready(function () { 2458 2459 const selectAllCheckbox = $('.checking-select-all-or-none'); 2460 const checkboxes = $('.checking-select'); 2461 const unsubscribe = $('#label-unsubscribe'); 2462 const tabSignUp = $('#loginSignTab'); 2463 const tabAlreadySubscribed = $('#loginAlreadyTab'); 2464 const navPrefsMain = $('#tabPreferences'); 2465 const navPersMain = $('#tabPersonalInfo'); 2466 const personalContainer = $('.personal-info-container'); 2467 const returnUserLoginContainer = $('#subDashReturnLoginContainer') 2468 const emailPrefsLogin = $('#emailPrefsLogin'); 2469 const emailPrefsMain = $('#emailPrefsMain'); 2470 const dashboardConfirmationContainer = $('.dashboard-confirmation-container'); 2471 const plainPersonalInfo = $('.dashboard-plain-personal-info'); 2472 const dashboardEditButton = $('#dashboardEditButton'); 2473 const dashboardCancelButton = $('#dashboard-cancel-button'); 2474 const inputPersonalInfo = $('#input-personal-info'); 2475 const saveButton = $('.dashboard-submit-button'); 2476 const unsubscribeAllDismissButton = $('#unsubscribe-all-dismiss-button'); 2477 const unsubscribeAllConfirmationButton = $('#unsubscribe-all-confirmation-button'); 2478 const personalSaveButton = $('#dashboard-save-button'); 2479 const dashboardLoginPage = $('#dashboardLoginPage'); 2480 const dashboardConfirmSubscriptionPage = $('#dashboardConfirmSubscriptionPage'); 2481 const dashboardMainPage = $('#dashboardMainPage'); 2482 const alreadySubButton = $('#alreadySub-done-btn'); 2483 const doneButton = $('#done-btn'); 2484 const dashboardPrivacyPolicy = $('.privacy-policy'); 2485 const updateSuccessMessage = $('#subDashSuccessConfirmationMessage'); 2486 const dashboardEditMyPreferences = $("#dashboardEditMyPreferences"); 2487 const hideSubscriptionDashboardModal = $('.hide-subscriptionDashboard-modal'); 2488 const confirmBannerJq = $('#confirmBanner'); 2489 const confirmBannerClsBtn = $('#confirmBannerCloseBtn'); 2490 2491 const emailResendSuccessTitle = "Your email was resent to "; 2492 const emailResendSuccessDesc = "Be sure to check your spam folder and the email address provided."; 2493 const emailResendErrorTitle = "An error occurred while sending email to "; 2494 const emailResendErrorDesc = "Please try again."; 2495 const emailResendAlreadySubscribedTitle = "Your email have already been verified"; 2496 const emailResendAlreadySubscribedDesc = "You can manage your preferences in the subscriber dashboard at any time."; 2497 const oneLastStepTitle = "One last step: Tell us which insights and perspectives you'd like to receive."; 2498 const oneLastStepDesc = "Personalize your Insights email by selecting the topics, authors, and news you want to follow."; 2499 const thankYouMessageTitle = "Thank you! You are now subscribed."; 2500 const thankYouMessageDesc = "You can manage your preferences in the subscriber dashboard at any time."; 2501 const successSVGUrl = 'url("/etc.clientlibs/corporatesite/clientlibs/clientlib-site/resources/icons/button-icons/Success.svg")'; 2502 const alertSVGUrl = 'url("/etc.clientlibs/corporatesite/clientlibs/clientlib-site/resources/icons/button-icons/Alert.svg")'; 2503 2504 let isNewUser; 2505 let returningUserSubHistory={}; 2506 let ignoreDirtyForm = false;
2507 2508 // clear subscriber state on initial load of the Subscription Dashboard 2509 window.utils.removeSubscriberStatus(); 2510 2511 // new email signup validation 2512 jQuery('#subDashNewUserEmail').on('focusout', () => { 2513 const isValidEmailCheckForConfMail = validateEmail('subDashNewUserEmail', 'subDashNewUserEmailError'); 2514 if (isValidEmailCheckForConfMail) { 2515 $('#subDashNewUserConfirmMailMsg').show() 2516 } else { 2517 $('#subDashNewUserConfirmMailMsg').hide() 2518 } 2519 }); 2520 2521 // new user industry validation 2522 jQuery('#subDashNewUserIndustry').on('change', () => { 2523 validateIndustry('subDashNewUserIndustry', 'subDashNewUserIndustryError'); 2524 jQuery('#subDashNewUserIndustry').blur(); 2525 jQuery('#done-btn').focus(); 2526 }); 2527 2528 // returning user email validation 2529 jQuery('#subDashReturnUserEmail').on('focusout', () => { 2530 const noUserErrorSpan = $('#subDashReturnUserNoUser')[0]; 2531 hideErrorMessage(noUserErrorSpan) 2532 validateEmail('subDashReturnUserEmail', 'subDashReturnUserEmailError'); 2533 $('#alreadySub-done-btn').focus(); 2534 }); 2535 2536 // update personal email validation 2537 jQuery('#subDashUpdateEmailInput').on('focusout', () => { 2538 validateEmail('subDashUpdateEmailInput', 'subDashUpdateEmailInputError'); 2539 }); 2540 2541 // resend verification email link event listener 2542 $("#confEmailResendLink").on('click', () => { 2543 2544 $.ajax({ 2545 url: "/api/corporatesite/subscription", 2546 type: "POST", 2547 contentType: 'application/json', 2548 dataType: "JSON", 2549 data: JSON.stringify({ 2550 currentEmail: $('#subDashNewUserEmail').val(), 2551 industryId: $("#subDashNewUserIndustry").val(), 2552 active: true 2553 }), 2554 success: function (response) { 2555 if (!isExistingSubscriber(response)) { 2556 displayConfirmBannerForResendEmailSuccess(); 2557 subscriptionAnalytics.pushCallToActionEventOnResendEmail(); 2558 } else { 2559 displayConfirmBannerForResendEmailAlreadySubscribed(); 2560 } 2561 }, 2562 error: function () { 2563 displayConfirmBannerForResendEmailError(); 2564 logClientError("ERROR", "Resend verification email failed", "subscriptionDashboard.js"); 2565 }, 2566 }); 2567 }); 2568 2569 // handle unsaved changes 2570 if ($('.cmp-subscriptionDashboard').length !== 0) { 2571 $('#subDashNewUserEmail,#subDashReturnUserEmail,#subDashNewUserIndustry').on('input', function () { 2572 addEventListener('beforeunload', handleLeaveBeforeSaving, {capture: true}); 2573 }); 2574 } 2575 2576 function checkboxValidation() { 2577 return getUnselectedCheckboxes() !== checkboxes.length; 2578 } 2579 2580 function checkSubscriptionDiff(oldHistory, currentHistory) { 2581 const subscribedTopicIds = $(currentHistory.subscribedTopicIds).not(oldHistory.subscribedTopicIds).get(); 2582 const unsubscribedTopicIds = $(oldHistory.subscribedTopicIds).not(currentHistory.subscribedTopicIds).get(); 2583 const subscribedAuthorIds = $(currentHistory.subscribedAuthorIds).not(oldHistory.subscribedAuthorIds).get(); 2584 const unsubscribedAuthorIds = $(oldHistory.subscribedAuthorIds).not(currentHistory.subscribedAuthorIds).get(); 2585 2586 return { 2587 subscribedTopicIds, 2588 unsubscribedTopicIds, 2589 subscribedAuthorIds, 2590 unsubscribedAuthorIds, 2591 } 2592 } 2593 2594 function showConfirmSubscriptionMessage() { 2595 dashboardLoginPage.removeClass('dashboardContainer vanguard-dashboard-login d-flex justify-content-center'); 2596 dashboardLoginPage.addClass('d-none'); 2597 2598 dashboardConfirmSubscriptionPage.removeClass('d-none'); 2599 $('#emailConfValue').html($('#subDashNewUserEmail').val()); 2600 dashboardConfirmSubscriptionPage.addClass('dashboardContainer vanguard-dashboard-login'); 2601 } 2602 2603 function transitionToForm() { 2604 dashboardLoginPage.removeClass('dashboardContainer vanguard-dashboard-login d-flex justify-content-center'); 2605 dashboardLoginPage.addClass('d-none'); 2606 2607 dashboardMainPage.removeClass('d-none'); 2608 dashboardMainPage.addClass('dashboardContainer d-flex justify-content-center'); 2609 } 2610 2611 function displaySuccessMessage(message) { 2612 updateSuccessMessage.removeClass('d-none'); 2613 updateSuccessMessage.addClass('dashboard-confirmation-container d-flex'); 2614 updateSuccessMessage.children().first().text(message); 2615 $(window).scrollTop(0); 2616 } 2617 2618 function hideSuccessMessage() { 2619 updateSuccessMessage.removeClass('dashboard-confirmation-container d-flex'); 2620 updateSuccessMessage.addClass('d-none'); 2621 } 2622 2623 function gatherCheckboxData() { 2624 let authorCheckbox = []; 2625 let topicCheckbox = []; 2626 2627 $("input:checkbox[name=topicCheckbox]:checked").each(function () { 2628 topicCheckbox.push($(this).attr('id')); 2629 }); 2630 2631 $("input:checkbox[name='authorCheckbox']:checked").each(function () { 2632 authorCheckbox.push($(this).attr('id')); 2633 }); 2634 2635 return { 2636 authorCheckbox, 2637 topicCheckbox 2638 } 2639 } 2640 2641 function handleLeaveBeforeSaving(event) { 2642 if (ignoreDirtyForm) { 2643 return; 2644 } 2645 2646 event.preventDefault(); 2647 2648 const {authorCheckbox, topicCheckbox} = gatherCheckboxData(); 2649 const subscriptionChanges = checkSubscriptionDiff(returningUserSubHistory, { 2650 subscribedAuthorIds: authorCheckbox, 2651 subscribedTopicIds: topicCheckbox 2652 }) 2653 2654 // iterate through diff to see if any changes are present 2655 const unsavedSubscriptions = Object.values(subscriptionChanges).map((topicSubs) => Boolean(topicSubs.length)).filter(val => val); 2656 let unsavedPersonalDetails = false;
2657 2658 if (!isNewUser) { 2659 const updateEmailInput = $('#subDashUpdateEmailInput').val(); 2660 const updateIndustryInput = $('#subDashUpdateIndustrySelect').val(); 2661 2662 const hasUnsavedEmail = updateEmailInput !== returningUserSubHistory.currentEmail; 2663 const hasUnsavedIndustry = updateIndustryInput !== returningUserSubHistory.industryId; 2664 2665 unsavedPersonalDetails = hasUnsavedEmail || hasUnsavedIndustry; 2666 } 2667 2668 if (unsavedSubscriptions.length || unsavedPersonalDetails) { 2669 // modern browsers no longer show custom messages, nor can we use a custom modal 2670 const browserMessage = 'There are unsaved changes. Are you sure you want to leave?' 2671 // some browsers require that event.returnValue be set to show dialogue 2672 event.returnValue = browserMessage; 2673 return browserMessage; 2674 } 2675 return; 2676 } 2677 2678 function populatePersonalData(responseData) { 2679 const currentIndustry = $(`option[value=${responseData.industryId}]`).first().text(); 2680 2681 $('#subDashCurrentEmailPlaintext').text(responseData.currentEmail); 2682 $('#subDashCurrentIndustryPlaintext').text(currentIndustry); 2683 $('#subDashUpdateEmailInput').prop('value', responseData.currentEmail); 2684 $('#subDashUpdateIndustrySelect').prop('value', responseData.industryId); 2685 } 2686 2687 function populateExistingData(responseData) { 2688 returningUserSubHistory = responseData; 2689 2690 if (returningUserSubHistory.subscribedTopicIds) { 2691 $("input:checkbox[name=topicCheckbox]").each(function () { 2692 if (returningUserSubHistory.subscribedTopicIds.find((id) => id === $(this).attr('id'))) { 2693 $(this).prop('checked', true); 2694 } 2695 }); 2696 } 2697 2698 if (returningUserSubHistory.subscribedAuthorIds) { 2699 $("input:checkbox[name=authorCheckbox]").each(function () { 2700 if (returningUserSubHistory.subscribedAuthorIds.find((id) => id === $(this).attr('id'))) { 2701 $(this).prop('checked', true); 2702 } 2703 }); 2704 } 2705 2706 updateSelectAllCheckBox(); 2707 populatePersonalData(responseData); 2708 } 2709 2710 function getUnselectedCheckboxes() { 2711 let notSelected = 0; 2712 for (let i = 0; i < checkboxes.length; i++) { 2713 const checkbox = checkboxes[i]; 2714 2715 if (!checkbox.checked) { 2716 notSelected++; 2717 } 2718 } 2719 return notSelected; 2720 } 2721 2722 // Info banner close action 2723 confirmBannerClsBtn.on('click', () => { 2724 confirmBannerJq.addClass('d-none') 2725 confirmBannerJq.removeClass('confirm-info-banner') 2726 }); 2727 2728 function displayConfirmBanner(title, description, messageType) { 2729 confirmBannerJq.removeClass('d-none') 2730 confirmBannerJq.addClass('confirm-info-banner') 2731 $('#ConfirmInfoBannerHeader').html(title) 2732 $('#ConfirmInfoBannerMessage').html(description) 2733 if (messageType === "Success") { 2734 $(".confirm-info-banner__message-icon").css('content', successSVGUrl) 2735 $(".confirm-info-banner").css({ 'background-color': '#E9F6E9', 'border-bottom': '1px solid #1D9F22' }); 2736 $("#dashboardConfirmSubscriptionPage").css('padding-top', '5%') 2737 } else { 2738 $(".confirm-info-banner__message-icon").css('content', alertSVGUrl) 2739 $(".confirm-info-banner").css({ 'background-color': '#FFF9E5', 'border-bottom': '1px solid #CC7A00' }) 2740 } 2741 } 2742 2743 function displayConfirmBannerForResendEmailSuccess() { 2744 let title = emailResendSuccessTitle + $('#subDashNewUserEmail').val(); 2745 displayConfirmBanner(title, emailResendSuccessDesc, "Success") 2746 } 2747 2748 function displayConfirmBannerForResendEmailError() { 2749 let title = emailResendErrorTitle + $('#subDashNewUserEmail').val(); 2750 displayConfirmBanner(title, emailResendErrorDesc, "Error") 2751 } 2752 2753 function displayConfirmBannerForResendEmailAlreadySubscribed() {
2754 displayConfirmBanner(emailResendAlreadySubscribedTitle, emailResendAlreadySubscribedDesc, "Success") 2755 } 2756 2757 function displayConfirmBannerOneLastStepMessage() { 2758 displayConfirmBanner(oneLastStepTitle, oneLastStepDesc, "Alert") 2759 } 2760 2761 function displayConfirmBannerThankYouMessage() { 2762 displayConfirmBanner(thankYouMessageTitle, thankYouMessageDesc, "Success"); 2763 $(window).scrollTop(0); 2764 } 2765 2766 function displaySignUpTab() { 2767 returnUserLoginContainer.removeClass('d-block'); 2768 returnUserLoginContainer.addClass('d-none'); 2769 emailPrefsLogin.removeClass('d-none'); 2770 emailPrefsLogin.addClass('d-block'); 2771 tabSignUp.prop('class', 'dashboard-active-tab'); 2772 tabAlreadySubscribed.prop('class', 'navigation-tab'); 2773 } 2774 2775 function displayAlreadySubscribedTab() { 2776 emailPrefsLogin.removeClass('d-block'); 2777 emailPrefsLogin.addClass('d-none'); 2778 returnUserLoginContainer.removeClass('d-none'); 2779 returnUserLoginContainer.addClass('d-block'); 2780 tabSignUp.prop('class', 'navigation-tab'); 2781 tabAlreadySubscribed.prop('class', 'dashboard-active-tab'); 2782 } 2783 2784 function displaySubscriptionPreferences() { 2785 hideSuccessMessage(); 2786 personalContainer.removeClass('d-block'); 2787 personalContainer.addClass('d-none'); 2788 emailPrefsMain.removeClass('d-none'); 2789 emailPrefsMain.addClass('d-block'); 2790 navPrefsMain.prop('class', 'dashboard-active-tab'); 2791 navPersMain.prop('class', 'navigation-tab'); 2792 } 2793 2794 function displayPersonalInformation() { 2795 hideSuccessMessage(); 2796 emailPrefsMain.removeClass('d-block'); 2797 emailPrefsMain.addClass('d-none'); 2798 personalContainer.removeClass('d-none'); 2799 personalContainer.addClass('d-block'); 2800 navPrefsMain.prop('class', 'navigation-tab'); 2801 navPersMain.prop('class', 'dashboard-active-tab'); 2802 } 2803 2804 /********** Sign up - Next button **********/ 2805 2806 doneButton.click(() => { 2807 $('.dashboard-submit-button').focus(); 2808 const isValidEmail = validateEmail('subDashNewUserEmail', 'subDashNewUserEmailError'); 2809 const isValidIndustry = validateIndustry('subDashNewUserIndustry', 'subDashNewUserIndustryError'); 2810 if (!isValidEmail) { 2811 $('#subDashNewUserEmail').focus(); 2812 } else if (!isValidIndustry) { 2813 $('#subDashNewUserIndustry').focus(); 2814 } 2815 if (isValidEmail && isValidIndustry) { 2816 isNewUser = true; 2817 navPersMain.addClass('d-none'); 2818 2819 $.ajax({ 2820 url: "/api/corporatesite/subscription", 2821 type: "POST", 2822 contentType: 'application/json', 2823 dataType: "JSON", 2824 data: JSON.stringify({ 2825 currentEmail: $('#subDashNewUserEmail').val(), 2826 industryId: $("#subDashNewUserIndustry").val(), 2827 active: true 2828 }), 2829 success: function (response) { 2830 if (isExistingSubscriber(response)) { 2831 $('.modal-subscriptionDashboard').addClass('show-modal'); 2832 $(document.body).append('<div class="modal-backdrop show"></div>'); 2833 $(document.body).addClass('modal-open'); 2834 returningUserSubHistory = response; 2835 2836 } else { 2837 subscriptionAnalytics.pushSubscriptionInitiatedEvent(); 2838 2839 showConfirmSubscriptionMessage(); 2840 2841 window.utils.setSubscriberStatus('verifyEmailSent'); 2842 subscriptionAnalytics.pushPageLoadedEventBySubscriberStatus(); 2843 subscriptionAnalytics.pushPageLoadCompletedEvent(); 2844 } 2845 }, 2846 error: function () { 2847 const verifySubscriberErrorSpan = $('#subDashVerifySubscriberError')[0]; 2848 displayErrorMessage(verifySubscriberErrorSpan); 2849 jQuery('#subDashNewUserEmail').addClass( "errorMessageShow" ); 2850 logClientError("ERROR", "Verify subscriber failed", "subscriptionDashboard.js"); 2851 }, 2852 }); 2853 } 2854 }); 2855 2856 dashboardEditMyPreferences.click(() => { 2857 loadFormForExistingUser(); 2858 }); 2859 2860 function isExistingSubscriber(response) { 2861 return (response.currentEmail != null); 2862 } 2863 2864 function loadFormForExistingUser() { 2865 isNewUser = false;
2866 navPersMain.removeClass('d-none'); 2867 2868 window.utils.setSubscriberStatus('alreadySubscribed'); 2869 subscriptionAnalytics.pushPageLoadedEventBySubscriberStatus(); 2870 subscriptionAnalytics.pushPageLoadCompletedEvent(); 2871 2872 populateExistingData(returningUserSubHistory); 2873 jQuery('.modal-subscriptionDashboard').removeClass('show-modal'); 2874 jQuery('.modal-backdrop').remove(); 2875 jQuery('body').removeClass('modal-open'); 2876 saveButton.text('Save preferences'); 2877 unsubscribe.css('display', 'block'); 2878 transitionToForm(); 2879 } 2880 2881 hideSubscriptionDashboardModal.click(() => { 2882 jQuery('.modal-subscriptionDashboard').removeClass('show-modal'); 2883 jQuery('.modal-backdrop').remove(); 2884 jQuery('body').removeClass('modal-open'); 2885 }); 2886 2887 /********** Already Subscribed - Next button **********/ 2888 2889 alreadySubButton.click(() => { 2890 //Use to track the conversion of user 2891 if (validateEmail('subDashReturnUserEmail', 'subDashReturnUserEmailError')) { 2892 isNewUser = false; 2893 const noUserErrorSpan = $('#subDashReturnUserNoUser')[0]; 2894 hideErrorMessage(noUserErrorSpan); 2895 subscriptionAnalytics.pushCallToActionEventOnAlreadySubscribedNextButtonClick(); 2896 2897 $.ajax({ 2898 url: "/api/corporatesite/subscription", 2899 type: "POST", 2900 contentType: 'application/json', 2901 dataType: "JSON", 2902 data: JSON.stringify({ 2903 currentEmail: $('#subDashReturnUserEmail').val() 2904 }), 2905 success: function (response) { 2906 populateExistingData(response); 2907 saveButton.text('Save preferences'); 2908 unsubscribe.css('display', 'block'); 2909 transitionToForm(); 2910 window.utils.deleteCookie("subscriberStatus"); 2911 window.utils.deleteCookie("subscriberEmail"); 2912 window.utils.deleteCookie("subscriberIndustry"); 2913 2914 window.utils.setSubscriberStatus('alreadySubscribed'); 2915 subscriptionAnalytics.pushPageLoadedEventBySubscriberStatus(); 2916 subscriptionAnalytics.pushPageLoadCompletedEvent(); 2917 }, 2918 error: function () { 2919 displayErrorMessage(noUserErrorSpan); 2920 jQuery('#subDashReturnUserEmail').addClass("errorMessageShow"); 2921 logClientError("ERROR", "Already-subscribed lookup failed", "subscriptionDashboard.js"); 2922 }, 2923 }); 2924 } 2925 }); 2926 2927 let subscriberStatus = window.utils.getCookieValue("subscriberStatus"); 2928 2929 if (subscriberStatus === null || subscriberStatus === undefined){ 2930 dashboardLoginPage.removeClass('d-none'); 2931 dashboardLoginPage.addClass('d-flex'); 2932 } 2933 2934 if ( subscriberStatus === 'confirmed' ) { 2935 returningUserSubHistory.currentEmail = window.utils.getCookieValue("subscriberEmail"); 2936 returningUserSubHistory.industryId = window.utils.getCookieValue("subscriberIndustry"); 2937 populatePersonalData(returningUserSubHistory); 2938 displayConfirmBannerOneLastStepMessage(); 2939 window.utils.setSubscriberStatus('confirmed'); 2940 2941 //select all the checkboxes by default 2942 checkboxes.prop('checked', true); 2943 updateSelectAllCheckBox(); 2944 transitionToForm(); 2945 } else if ( subscriberStatus === 'subscribed' ){ 2946 transitionToForm(); 2947 $('#subDashReturnUserEmail').val(window.utils.getCookieValue("subscriberEmail")); 2948 alreadySubButton.click(); 2949 } 2950 2951 /********** Subscription preferences - Save button **********/ 2952 2953 saveButton.click(() => { 2954 2955 const topicCheckbox = []; 2956 const authorCheckbox = []; 2957 2958 $("input:checkbox[name=topicCheckbox]:checked").each(function () { 2959 topicCheckbox.push($(this).attr('id')); 2960 }); 2961 2962 $("input:checkbox[name='authorCheckbox']:checked").each(function () { 2963 authorCheckbox.push($(this).attr('id')); 2964 }); 2965 2966 const { 2967 subscribedTopicIds, 2968 unsubscribedTopicIds, 2969 subscribedAuthorIds, 2970 unsubscribedAuthorIds, 2971 } = checkSubscriptionDiff(returningUserSubHistory, { 2972 subscribedTopicIds: topicCheckbox, 2973 subscribedAuthorIds: authorCheckbox 2974 }) 2975 2976 const postParams = { 2977 currentEmail: returningUserSubHistory.currentEmail, 2978 subscribedTopicIds, 2979 unsubscribedTopicIds, 2980 subscribedAuthorIds, 2981 unsubscribedAuthorIds, 2982 }; 2983 2984 $.ajax({ 2985 url: "/api/corporatesite/subscription", 2986 type: "POST", 2987 contentType: 'application/json', 2988 dataType: "JSON", 2989 data: JSON.stringify(postParams), 2990 success: function (response) { 2991 populateExistingData(response); 2992 let subscriberStatus = window.utils.getCookieValue("subscriberStatus"); 2993 if ( subscriberStatus && subscriberStatus === 'confirmed') { 2994 displayConfirmBannerThankYouMessage(); 2995 saveButton.text('Save preferences'); 2996 window.utils.setSubscriberStatus('subscriptionActivated'); 2997 2998 subscriptionAnalytics.pushSubscriptionActivatedEvent([...topicCheckbox, ...authorCheckbox]); 2999 subscriptionAnalytics.pushPageLoadedEventBySubscriberStatus(); 3000 subscriptionAnalytics.pushPageLoadCompletedEvent(); 3001 3002 window.utils.deleteCookie("subscriberStatus"); 3003 window.utils.deleteCookie("subscriberEmail"); 3004 window.utils.deleteCookie("subscriberIndustry"); 3005 3006 } else { 3007 $("#confirmBanner").hide(); 3008 displaySuccessMessage('Your subscription preferences have been saved.'); 3009 3010 let subscriberStatus = window.utils.getSubscriberStatus(); 3011 if (subscriberStatus === 'subscriptionActivated') { 3012 window.utils.setSubscriberStatus('newSubscriptionManaged'); 3013 } else if (subscriberStatus === 'alreadySubscribed') { 3014 window.utils.setSubscriberStatus('oldSubscriptionManaged'); 3015 } 3016 3017 subscriptionAnalytics.pushSubscriptionManagedEvent([...subscribedTopicIds, ...subscribedAuthorIds]); 3018 subscriptionAnalytics.pushPageLoadedEventBySubscriberStatus(); 3019 subscriptionAnalytics.pushPageLoadCompletedEvent(); 3020 } 3021 }, 3022 error: function (failureResponse) { 3023 logClientError("ERROR", "Subscription save failed: status=" + (failureResponse && failureResponse.status), "subscriptionDashboard.js"); 3024 }, 3025 }); 3026 }); 3027 3028 /********** Call to actions **********/ 3029 3030 dashboardPrivacyPolicy.click(() => { 3031 subscriptionAnalytics.pushCallToActionEventOnViewPrivacyPolicy(); 3032 }); 3033 3034 /********** Personal information - Save button **********/ 3035 3036 personalSaveButton.click((e) => { 3037 e.preventDefault(); 3038 3039 const currentEmail = returningUserSubHistory.currentEmail; 3040 const newEmail = $('#subDashUpdateEmailInput').val(); 3041 const industryId = $('#subDashUpdateIndustrySelect').val(); 3042 3043 returningUserSubHistory.currentEmail = newEmail; 3044 returningUserSubHistory.industryId = industryId; 3045 3046 const postParams = { 3047 currentEmail, 3048 newEmail, 3049 industryId 3050 } 3051 3052 subscriptionAnalytics.pushCallToActionEventOnPersonalInformationSave(); 3053 3054 $.ajax({ 3055 url: "/api/corporatesite/subscription", 3056 type: "POST", 3057 contentType: 'application/json', 3058 dataType: "JSON", 3059 data: JSON.stringify(postParams), 3060 success: function (response) { 3061 populatePersonalData(response); 3062 displaySuccessMessage('Your personal details have been saved.'); 3063 inputPersonalInfo.removeClass('col-lg-6 col-sm-12 col-md-10 d-block'); 3064 inputPersonalInfo.addClass('col-lg-6 col-sm-12 col-md-10 d-none'); 3065 plainPersonalInfo.removeClass('d-done'); 3066 plainPersonalInfo.addClass('d-block'); 3067 }, 3068 error: function () { 3069 logClientError("ERROR", "Personal details save failed", "subscriptionDashboard.js"); 3070 }, 3071 }); 3072 }); 3073 3074 unsubscribeAllDismissButton.click(() => { 3075 subscriptionAnalytics.pushCallToActionEventOnUnsubscribeFromAllDismissButtonClick(); 3076 $('#unsubscribeModal').modal('hide'); 3077 $(window).scrollTop(0); 3078 dashboardConfirmationContainer.removeClass('d-none'); 3079 dashboardConfirmationContainer.addClass('dashboard-confirmation-container d-flex'); 3080 }); 3081 3082 /********** Unsubscribe from all link **********/ 3083 3084 unsubscribeAllConfirmationButton.click(() => { 3085 3086 const { topicCheckbox, authorCheckbox } = gatherCheckboxData(); 3087 subscriptionAnalytics.pushSubscriptionUnsubscribedEvent([...topicCheckbox, ...authorCheckbox]); 3088 window.utils.setSubscriberStatus('unsubscribedAll'); 3089 3090 ignoreDirtyForm = true; 3091 let currentSubscribeEmail; 3092 3093 if ($('#subDashReturnUserEmail').val() == '') { 3094 currentSubscribeEmail = $('#subDashNewUserEmail').val() 3095 } else { 3096 currentSubscribeEmail = $('#subDashReturnUserEmail').val() 3097 } 3098 const postParams = { 3099 currentEmail: currentSubscribeEmail, 3100 active: false, 3101 }; 3102 3103 $.ajax({ 3104 url: "/api/corporatesite/subscription", 3105 type: "POST", 3106 contentType: 'application/json', 3107 dataType: "JSON", 3108 data: JSON.stringify(postParams), 3109 success: function () { 3110 window.location.assign(`${origin}/content/corporatesite/us/en/corp/subscription-dashboard/unsubscribed.html`); 3111 }, 3112 error: function () { 3113 logClientError("ERROR", "Unsubscribe request failed", "subscriptionDashboard.js"); 3114 window.location.assign(`${origin}/content/corporatesite/us/en/corp/subscription-dashboard/unsubscribed.html`); 3115 }, 3116 }); 3117 3118 }); 3119 3120 function updateSelectAllCheckBox() { 3121 3122 let unselectedCheckBoxes = getUnselectedCheckboxes(); 3123 3124 // all checkboxes are checked 3125 if (unselectedCheckBoxes == 0) { 3126 selectAllCheckbox.prop('checked', true); 3127 selectAllCheckbox.addClass('checking-select-all-or-none'); 3128 selectAllCheckbox.removeClass('checking-select-partial'); 3129 $('input[name="authorCheckbox"]').blur(); 3130 $('input[name="topicCheckbox"]').blur(); 3131 return; 3132 } 3133 3134 // no checkboxes are checked 3135 if (getUnselectedCheckboxes() == checkboxes.length) { 3136 selectAllCheckbox.prop('checked', false); 3137 selectAllCheckbox.removeClass('checking-select-partial'); 3138 selectAllCheckbox.addClass('checking-select-all-or-none'); 3139 $('input[name="authorCheckbox"]').blur(); 3140 $('input[name="topicCheckbox"]').blur(); 3141 return; 3142 } 3143 3144 // some checkboxes are checked 3145 selectAllCheckbox.prop('checked', false); 3146 selectAllCheckbox.removeClass('checking-select-all-or-none'); 3147 selectAllCheckbox.addClass('checking-select-partial'); 3148 $('input[name="authorCheckbox"]').blur(); 3149 $('input[name="topicCheckbox"]').blur(); 3150 } 3151 3152 selectAllCheckbox.click(() => { 3153 // user is checking the select all checkbox 3154 if (selectAllCheckbox.prop('checked')) { 3155 checkboxes.prop('checked', true); 3156 jQuery('.dashboard-submit-button').focus(); 3157 } else { 3158 // otherwise, user is unchecking the select all checkbox 3159 checkboxes.prop('checked', false); 3160 } 3161 updateSelectAllCheckBox(); 3162 subscriptionAnalytics.pushCallToActionEventOnSelectAllCheckboxClick(); 3163 }); 3164 3165 checkboxes.click(() => { 3166 updateSelectAllCheckBox(); 3167 }); 3168 3169 unsubscribe.click(() => { 3170 $('#unsubscribeAllModal').modal({ 3171 show: true 3172 }); 3173 subscriptionAnalytics.pushCallToActionEventOnUnsubscribeFromAllLinkClick(); 3174 }); 3175 3176 tabSignUp.click(() => { 3177 subscriptionAnalytics.pushSubPageViewedEventOnSignUpTabClick(); 3178 displaySignUpTab(); 3179 }); 3180 3181 tabAlreadySubscribed.click(() => { 3182 subscriptionAnalytics.pushSubPageViewedEventOnAlreadySubscribedTabClick(); 3183 displayAlreadySubscribedTab(); 3184 }); 3185 3186 navPrefsMain.click(() => { 3187 subscriptionAnalytics.pushSubPageViewedEventOnSubscriptionPreferencesTabClick(); 3188 displaySubscriptionPreferences(); 3189 }); 3190 3191 navPersMain.click(() => { 3192 subscriptionAnalytics.pushSubPageViewedEventOnPersonalInformationTabClick(); 3193 displayPersonalInformation(); 3194 }); 3195 3196 dashboardEditButton.click(() => { 3197 subscriptionAnalytics.pushCallToActionEventOnPersonalInformationEdit(); 3198 plainPersonalInfo.removeClass('d-block'); 3199 plainPersonalInfo.addClass('d-none'); 3200 inputPersonalInfo.removeClass('d-none'); 3201 inputPersonalInfo.addClass('col-lg-6 col-sm-12 col-md-10 d-block'); 3202 }); 3203 3204 dashboardCancelButton.click(() => { 3205 subscriptionAnalytics.pushCallToActionEventOnPersonalInformationCancel(); 3206 3207 // revert any unsaved changes 3208 populatePersonalData(returningUserSubHistory); 3209 hideErrorMessage($('#subDashUpdateEmailInputError')[0]) 3210 hideErrorMessage($('#subDashUpdateIndustrySelectError')[0]) 3211 plainPersonalInfo.removeClass('d-none'); 3212 plainPersonalInfo.addClass('d-block'); 3213 inputPersonalInfo.removeClass('col-lg-6 col-sm-12 col-md-10 d-block'); 3214 inputPersonalInfo.addClass('d-none'); 3215 }); 3216 }) 3217 3218 3219 $(window).scroll(function () { 3220 if ($('.dashboard-submit-button').length > 0) { 3221 var top_of_element = $(".dashboard-submit-button").offset().top; 3222 var bottom_of_element = $(".dashboard-submit-button").offset().top + $(".dashboard-submit-button").outerHeight(); 3223 var bottom_of_screen = $(window).scrollTop() + $(window).innerHeight(); 3224 var top_of_screen = $(window).scrollTop(); 3225 3226 if ((bottom_of_screen > top_of_element) && (top_of_screen < bottom_of_element)) { 3227 $('.dashboard-submit-button').focus(); 3228 } 3229 } 3230 }); 3231 3232})(); 3233$(document).ready(function(){ 3234 $('.cmp-socialShare-list-item a, .cmp-socialShare-list-item button').on('click', function(){ 3235 //Use to track the sharing of content 3236 window.dataLayer = window.dataLayer || []; 3237 window.dataLayer.push({ 3238 "event" : "contentShared", //e13 3239 "medium": { 3240 "shareType":$(this).attr('data-shareType') , //v73 3241 "linkName": $(this).attr('data-pageUrl') //v72 3242 } 3243 }); 3244 3245 if($(this).attr('data-socialShareURL') != u
3245ndefined){ 3246 window.open($(this).attr('data-socialShareURL'), '_blank'); 3247 } 3248 }); 3249}); 3250 3251(function() { 3252 'use strict'; 3253 3254 $(".relatedLinkReferenceClass").on("click", function() { 3255 let targetURL = $(this).attr('href'); 3256 if ( targetURL && !targetURL.endsWith(".pdf") ) { 3257 window.dataLayer = window.dataLayer || []; 3258 let relatedLinksId = $(this).attr("id"); 3259 relatedLinksId = relatedLinksId.substring(0, relatedLinksId.length-1) +" "+ (parseInt(relatedLinksId.substring((relatedLinksId.length-1)))+1) 3260 let relatedLinksName = $(this).html().split("<"); 3261 let cleanRelatedLinksName = relatedLinksName[0]; 3262 3263 3264 window.dataLayer.push({ 3265 "event": "callToAction", //e111 3266 "callToAction": { 3267 "location": $(document).find("title")[0].text, //v103 3268 "ctaName": relatedLinksId +"-"+ cleanRelatedLinksName, //v104 3269 "ctaType": "Text Link" //v111 3270 } 3271 }); 3272 } 3273 3274 }); 3275 3276})(); 3277(function () { 3278 3279 "use strict"; 3280 var fontColors = {}; 3281 3282 let onLight = (resource.properties["bgColor"] == '#FFFFFF' || resource.properties["bgColor"] == '#E6E6E6' 3283 || resource.properties["bgColor"] == '#DEF5F0' || resource.properties["bgColor"] == '#FFEDED' 3284 || resource.properties["bgColor"] == '#FFF7DE' || resource.properties["bgColor"] == '#FFAD00' 3285 || resource.properties["bgColor"] == '#F9F9F9') ? true : false; 3286 3287 let redColorSelected = resource.properties["fontColor"] == '#C20029' ? true : false; 3288 3289 if( onLight && !redColorSelected ){ 3290 fontColors.quoteAndHR = '#040505'; 3291 fontColors.sourceAndTitle = '#232323'; 3292 3293 } else if( onLight && redColorSelected ){ 3294 fontColors.quoteAndHR = '#C20029'; 3295 fontColors.sourceAndTitle = '#232323'; 3296 } 3297 3298 if( !onLight ){ 3299 fontColors.quoteAndHR = '#FFFFFF'; 3300 fontColors.sourceAndTitle = '#FFFFFF'; 3301 } 3302 3303 return fontColors; 3304 3305}); 3306// bind an event handler to the "click" event for each person container 3307 3308$('.cmp-peoplenoimages__person-container--enabledlink').on('click', function() { 3309 const link = $(this).find('.cmp-peoplenoimages__name'); 3310 const url = link.attr('href'); 3311 if (url) { 3312 window.open(url, '_self'); 3313 } 3314}); 3315// bind an event handler to the "click" event for each person container 3316 3317$('.cmp-people__person-container--enabledlink').on('click', function() { 3318 const link = $(this).find('.cmp-people__name'); 3319 const url = link.attr('href'); 3320 if (url) { 3321 window.open(url, '_self'); 3322 } 3323}); 3324(function() { 3325 'use strict'; 3326 3327 const ASSET_CLASS_EQUITIES = ['Global Equity', 'US Equity', 'International Equity']; 3328 const ASSET_CLASS_FIXED_INCOME = 'Fixed Income'; 3329 const ASSET_CLASS_OTHER = 'Other'; 3330 3331 $(document).ready(function() { 3332 3333 let firstNameField = $('#firstNameField'), 3334 lastNameField = $('#lastNameField'), 3335 titleField = $('#titleField'), 3336 emailField = $('#emailField'), 3337 phoneField = $('#phoneField'), 3338 firmNameField = $('#firmNameField'), 3339 firmAddressField = $('#firmAddressField'), 3340 firmAddressField2 = $('#firmAddressField2'), 3341 firmCityField = $('#firmCityField'), 3342 firmStateField = $('#firmStateField'), 3343 firmZipField = $('#firmZipField'), 3344 firmWebsiteField = $('#firmWebsiteField'), 3345 firmAssetsField = $('#firmAssetsField'), 3346 firmMinorityWomenRadio = $('input:radio[name=firmMinorityWomenRadio]'), 3347 firmMinorityWomenRadioYes = firmMinorityWomenRadio[0], 3348 firmMinorityWomenRadioNo = firmMinorityWomenRadio[1], 3349 strategyNameField = $('#strategyNameField'), 3350 assetClassDropdown = $('#assetClassDropdown'), 3351 equityCapitalizationDropdown = $('#equityCapitalizationDropdown'), 3352 equityCapitalizationDiv = $('.cmp-managerPortal__equity-capitalization'), 3353 equityStyleDropdown = $('#equityStyleDropdown'), 3354 equityStyleDiv = $('.cmp-managerPortal__equity-style'), 3355 fixedIncomeStyleDropdown = $('#fixedIncomeStyleDropdown'), 3356 fixedIncomeStyleDiv = $('.cmp-managerPortal__fixed-income-style'), 3357 fixedIncomeDurationDropdown = $('#fixedIncomeDurationDropdown'), 3358 fixedIncomeDurationDiv = $('.cmp-managerPortal__fixed-income-duration'), 3359 otherDescribeField = $('#otherDescribeField'), 3360 otherDescribeDiv = $('.cmp-managerPortal__other-describe'), 3361 marketStrategyField = $('#marketStrategyField'), 3362 submitButton = $('#submitButton'),
3363 submitErrorMessage = $('#submitErrorMessage')[0], 3364 confirmationModal = $('#confirmationModal'), 3365 confirmationModalButton = $('#confirmationModalButton'); 3366 3367 firstNameField.on('focusout', function() { 3368 validateName('firstNameField', 'firstNameFieldErrorMessage'); 3369 }); 3370 3371 lastNameField.on('focusout', function() { 3372 validateName('lastNameField', 'lastNameFieldErrorMessage'); 3373 }); 3374 3375 titleField.on('focusout', function() { 3376 validateRequiredField('titleField', 'titleFieldErrorMessage'); 3377 }); 3378 3379 emailField.on('focusout', function() { 3380 validateEmail('emailField', 'emailFieldErrorMessage'); 3381 }); 3382 3383 phoneField.on('focusout', function() { 3384 validateOptionalPhone('phoneField', 'phoneFieldErrorMessage'); 3385 }); 3386 3387 phoneField.on('input', function() { 3388 this.value = formatPhoneInput(this.value); 3389 }); 3390 3391 firmNameField.on('focusout', function() { 3392 validateRequiredField('firmNameField', 'firmNameFieldErrorMessage'); 3393 }); 3394 3395 firmAddressField.on('focusout', function() { 3396 validateRequiredField('firmAddressField', 'firmAddressFieldErrorMessage'); 3397 }); 3398 3399 firmCityField.on('focusout', function() { 3400 validateRequiredField('firmCityField', 'firmCityFieldErrorMessage'); 3401 }); 3402 3403 firmStateField.on('focusout', function() { 3404 validateRequiredState('firmStateField', 'firmStateFieldErrorMessage'); 3405 }); 3406 3407 firmStateField.on('input', function() { 3408 this.value = formatAlphabeticInput(this.value).toUpperCase(); 3409 }); 3410 3411 firmZipField.on('focusout', function() { 3412 validateRequiredZipCode('firmZipField', 'firmZipFieldErrorMessage'); 3413 }); 3414 3415 firmZipField.on('input', function() { 3416 this.value = formatNumericInput(this.value); 3417 }); 3418 3419 firmAssetsField.on('input', function() { 3420 this.value = formatDollarAmountInput(this.value); 3421 }); 3422 3423 strategyNameField.on('focusout', function() { 3424 validateRequiredField('strategyNameField', 'strategyNameFieldErrorMessage'); 3425 }); 3426 3427 assetClassDropdown.on('change', function() { 3428 validateRequiredDropdown('assetClassDropdown', 'assetClassDropdownErrorMessage'); 3429 3430 if (ASSET_CLASS_EQUITIES.some(str => this.value.includes(str))) { 3431 equityCapitalizationDiv.show(); 3432 equityStyleDiv.show(); 3433 } else { 3434 equityCapitalizationDiv.hide(); 3435 equityStyleDiv.hide(); 3436 } 3437 3438 if (this.value.includes(ASSET_CLASS_FIXED_INCOME)) { 3439 fixedIncomeStyleDiv.show(); 3440 fixedIncomeDurationDiv.show(); 3441 } else { 3442 fixedIncomeStyleDiv.hide(); 3443 fixedIncomeDurationDiv.hide(); 3444 } 3445 3446 if (this.value.includes(ASSET_CLASS_OTHER)) { 3447 otherDescribeDiv.show(); 3448 } else { 3449 otherDescribeDiv.hide(); 3450 } 3451 }); 3452 3453 equityCapitalizationDropdown.on('change', function() { 3454 validateRequiredDropdown('equityCapitalizationDropdown', 'equityCapitalizationDropdownErrorMessage'); 3455 }); 3456 3457 equityStyleDropdown.on('change', function() { 3458 validateRequiredDropdown('equityStyleDropdown', 'equityStyleDropdownErrorMessage'); 3459 }); 3460 3461 fixedIncomeStyleDropdown.on('change', function() { 3462 validateRequiredDropdown('fixedIncomeStyleDropdown', 'fixedIncomeStyleDropdownErrorMessage'); 3463 }); 3464 3465 fixedIncomeDurationDropdown.on('change', function() { 3466 validateRequiredDropdown('fixedIncomeDurationDropdown', 'fixedIncomeDurationDropdownErrorMessage'); 3467 }); 3468 3469 otherDescribeField.on('focusout', function() { 3470 validateRequiredField('otherDescribeField', 'otherDescribeFieldErrorMessage'); 3471 }); 3472 3473 marketStrategyField.on('focusout', function() { 3474 validateRequiredField('marketStrategyField', 'marketStrategyFieldErrorMessage'); 3475 }); 3476 3477 submitButton.on('click', function() { 3478 if (validateManagerPortalForm()) { 3479 pushEventCTA(); 3480 callManagerPortalService(); 3481 } else { 3482 $('.errorMessageShow').first().focus(); 3483 } 3484 }); 3485 3486 confirmationModalButton.on('click', function() { 3487 confirmationModal.modal('hide'); 3488 resetForm(); 3489 }); 3490 3491 confirmationModal.on('hide.bs.modal', function() { 3492 resetForm(); 3493 }); 3494 3495 function validateManagerPortalForm() { 3496 const isValidFirstName = validateRequiredField('firstNameField', 'firstNameFieldErrorMessage'); 3497 const isValidLastName = validateRequiredField('lastNameField', 'lastNameFieldErrorMessage'); 3498 const isValidTitle = validateRequiredField('titleField', 'titleFieldErrorMessage'); 3499 const isValidEmail = validateEmail('emailField', 'emailFieldErrorMessage'); 3500 const isValidPhone = validateOptionalPhone('phoneField', 'phoneFieldErrorMessage'); 3501 const isValidFirmName = validateRequiredField('firmNameField', 'firmNameFieldErrorMessage'); 3502 const isValidFirmAddress = validateRequiredField('firmAddressField', 'firmAddressFieldErrorMessage'); 3503 const isValidFirmCity = validateRequiredField('firmCityField', 'firmCityFieldErrorMessage'); 3504 const isValidFirmState = validateRequiredState('firmStateField', 'firmStateFieldErrorMessage'); 3505 const isValidFirmZip = validateRequiredZipCode('firmZipField', 'firmZipFieldErrorMessage'); 3506 const isValidStrategyName = validateRequiredField('strategyNameField', 'strategyNameFieldErrorMessage'); 3507 const isValidAssetClass = validateRequiredDropdown('assetClassDropdown', 'assetClassDropdownErrorMessage'); 3508 const isValidEquityCapitalization = validateRequiredDropdown('equityCapitalizationDropdown', 'equityCapitalizationDropdownErrorMessage'); 3509 const isValidEquityStyle = validateRequiredDropdown('equityStyleDropdown', 'equityStyleDropdownErrorMessage'); 3510 const isValidFixedIncomeStyle = validateRequiredDropdown('fixedIncomeStyleDropdown', 'fixedIncomeStyleDropdownErrorMessage'); 3511 const isValidFixedIncomeDuration = validateRequiredDropdown('fixedIncomeDurationDropdown', 'fixedIncomeDurationDropdownErrorMessage'); 3512 const isValidOtherDescribe = validateRequiredField('otherDescribeField', 'otherDescribeFieldErrorMessage'); 3513 const isValidMarketStrategy = validateRequiredField('marketStrategyField', 'marketStrategyFieldErrorMessage'); 3514 3515 if (isValidFirstName && isValidLastName && isValidTitle && isValidEmail && isValidPhone && 3516 isValidFirmName && isValidFirmAddress && isValidFirmCity && isValidFirmState && isValidFirmZip && 3517 isValidStrategyName && isValidAssetClass && isValidMarketStrategy) { 3518 3519 if (ASSET_CLASS_EQUITIES.some(str => assetClassDropdown.val().includes(str))) { 3520 if (isValidEquityCapitalization && isValidEquityStyle) { 3521 return true; 3522 } 3523 } else if (assetClassDropdown.val().includes(ASSET_CLASS_FIXED_INCOME)) { 3524 if (isValidFixedIncomeStyle && isValidFixedIncomeDuration) { 3525 return true; 3526 } 3527 } else if (assetClassDropdown.val().includes(ASSET_CLASS_OTHER)) { 3528 if (isValidOtherDescribe) { 3529 return true; 3530 } 3531 } else { 3532 return true; 3533 } 3534 } 3535 return false;
3536 } 3537 3538 function callManagerPortalService() { 3539 hideErrorMessage(submitErrorMessage); 3540 3541 $.ajax({ 3542 url: "/api/corporatesite/managerportalservice", 3543 type: "POST", 3544 contentType: 'application/json', 3545 dataType: "JSON", 3546 data: JSON.stringify(getFormJsonData()), 3547 success: function (response) { 3548 confirmationModal.modal('show'); 3549 }, 3550 error: function () { 3551 displayErrorMessage(submitErrorMessage); 3552 logClientError("ERROR", "Manager portal form submission failed", "managerPortalForm.js"); 3553 } 3554 }); 3555 3556 } 3557 3558 function getFormJsonData() { 3559 return { 3560 'PRGMgrPortal': { 3561 'Firm': { 3562 'FirmName': firmNameField.val(), 3563 'Website': firmWebsiteField.val(), 3564 'Address1': firmAddressField.val(), 3565 'Address2': firmAddressField2.val(), 3566 'City': firmCityField.val(), 3567 'State': firmStateField.val(), 3568 'ZipCode': firmZipField.val(), 3569 'AUM': firmAssetsField.val(), 3570 'Minority-Women-flag': getSelectedRadioValue(firmMinorityWomenRadio) 3571 }, 3572 'Contact': { 3573 'FirstName': firstNameField.val(), 3574 'LastName': lastNameField.val(), 3575 'Title': titleField.val(), 3576 'Phone': phoneField.val(), 3577 'Email': emailField.val() 3578 }, 3579 'Strategy': { 3580 'StrategyName': strategyNameField.val(), 3581 'AssetClass': getDropdownValue(assetClassDropdown), 3582 'EquityFocus': getDropdownValue(equityCapitalizationDropdown), 3583 'StyleFocus': getDropdownValue(equityStyleDropdown), 3584 'FixedIncStyle': getDropdownValue(fixedIncomeStyleDropdown), 3585 'FixedIncDur': getDropdownValue(fixedIncomeDurationDropdown), 3586 'StratDesc': marketStrategyField.val(), 3587 'AssetDesc': otherDescribeField.val() 3588 } 3589 } 3590 }; 3591 } 3592 3593 function getSelectedRadioValue(radio) { 3594 return radio[0].checked ? radio[0].value : radio[1].value; 3595 } 3596 3597 function getDropdownValue(dropdown) { 3598 return dropdown.val() !== 'Choose' ? dropdown.val() : ''; 3599 } 3600 3601 function resetForm() { 3602 clearAllFormFields(); 3603 hideAssetClassDetailsFields(); 3604 window.scrollTo(0, 0); 3605 } 3606 3607 function clearAllFormFields() { 3608 firstNameField.val(''); 3609 lastNameField.val(''); 3610 titleField.val(''); 3611 emailField.val(''); 3612 phoneField.val(''); 3613 firmNameField.val(''); 3614 firmAddressField.val(''); 3615 firmAddressField2.val(''); 3616 firmCityField.val(''); 3617 firmStateField.val(''); 3618 firmZipField.val(''); 3619 firmWebsiteField.val(''); 3620 firmAssetsField.val(''); 3621 firmMinorityWomenRadioYes.checked = false; 3622 firmMinorityWomenRadioNo.checked = true; 3623 strategyNameField.val(''); 3624 assetClassDropdown.val('Choose'); 3625 equityCapitalizationDropdown.val('Choose'); 3626 equityStyleDropdown.val('Choose'); 3627 fixedIncomeStyleDropdown.val('Choose'); 3628 fixedIncomeDurationDropdown.val('Choose'); 3629 otherDescribeField.val(''); 3630 marketStrategyField.val(''); 3631 } 3632 3633 function hideAssetClassDetailsFields() { 3634 equityCapitalizationDiv.hide(); 3635 equityStyleDiv.hide(); 3636 fixedIncomeStyleDiv.hide(); 3637 fixedIncomeDurationDiv.hide(); 3638 otherDescribeDiv.hide(); 3639 } 3640 3641 function pushEventCTA() { 3642 window.dataLayer = window.dataLayer || []; 3643 window.dataLayer.push({ 3644 "event": "callToAction", //e111 3645 "callToAction": { 3646 "location": submitButton.data('ctalocation'), //v103 3647 "ctaName": "submit", //v104 3648 "ctaType": "primary button" //v111 3649 } 3650 }); 3651 } 3652 }); 3653})() 3654$(document).ready(function () { 3655 3656 //applying the aria-hidden attribute to all the elements that are not visible on the screen 3657 document.querySelectorAll('.legalnote .readmore .legal-note-text').forEach((note) => { 3658 let count = 0;
3659 note.children.forEach((child) => { 3660 if (count > 0) { 3661 child.setAttribute('aria-hidden', 'true'); 3662 } 3663 count++; 3664 }); 3665 }); 3666 3667 $(".readmore-link").on("keypress", function(event) { 3668 if ( event.which === 13 || event.which === 32) { 3669 $(event.target).click(); 3670 } 3671 }); 3672 3673 $(".readmore-link").click(function (e) { 3674 let $el, $currentLegalNote, $up; 3675 let totalHeight = 0; 3676 let childrenOfNotesVisible = false; 3677 let isExpanded = $(e.target).hasClass("expand"); 3678 3679 $el = $(this); 3680 $currentLegalNote = $el.parent(); 3681 $up = $currentLegalNote.closest(".readmore"); 3682 3683 let $notes = $currentLegalNote.find(".legal-note-text"); 3684 let observed = $notes[0]; 3685 3686 $up.toggleClass('active'); 3687 3688 3689 let observer = createResizeObserver($notes, isExpanded); 3690 3691 /*checking if the .readmore div has the expand class and if it doesn't add it otherwise remove it*/ 3692 if (!isExpanded) { 3693 $(e.target).parent(".readmore").addClass("expand"); 3694 $(e.target).addClass("expand"); 3695 $(e.target).html("Read less"); 3696 $currentLegalNote.find(".readmore-gradient").css('opacity', '0'); 3697 3698 childrenOfNotesVisible = true; 3699 childElementsOfNotesVisible($notes, childrenOfNotesVisible); 3700 3701 observer.observe(observed); 3702 window.scroll($(e.target).closest(".legal-container-border").parent().offset()); 3703 } else { 3704 $(e.target).parent(".readmore").removeClass("expand"); 3705 $(e.target).removeClass("expand"); 3706 $(e.target).html("Read more"); 3707 $currentLegalNote.find(".readmore-gradient").css('opacity', '1'); 3708 3709 childElementsOfNotesVisible($notes, childrenOfNotesVisible); 3710 3711 observer.observe(observed); 3712 window.scroll($(e.target).closest(".legal-container-border").parent().offset()); 3713 } 3714 }); 3715 3716 function createResizeObserver($notes, isExpanded) { 3717 //setting up the ResizeObserver 3718 let resizeObserver = new ResizeObserver(() => { 3719 let totalHeightNotes = 0; 3720 //getting the height of the .legal-note-text children 3721 $notes.children().each(function () { 3722 totalHeightNotes = totalHeightNotes + $(this).outerHeight(true); 3723 }); 3724 totalHeight = totalHeightNotes; 3725 if (!isExpanded) { 3726 $notes.css("height", totalHeight); 3727 } else { 3728 $notes.css("height", 50); 3729 } 3730 }); 3731 return resizeObserver; 3732 } 3733 3734 function childElementsOfNotesVisible($notes, childrenOfNotesVisible) { 3735 let count = 0; 3736 $notes.children().each(function () { 3737 if (!childrenOfNotesVisible && count > 0) { 3738 $(this).attr('aria-hidden', 'true'); 3739 } else { 3740 $(this).removeAttr('aria-hidden'); 3741 } 3742 count++; 3743 }); 3744 } 3745}); 3746$(document).ready(function () { 3747 let pictureArray = document.querySelectorAll('.image-container'); 3748 if (pictureArray.length > 0) { 3749 let imageLazyLoading = document.querySelectorAll('.image_loading_lazy'); 3750 let checkForGif = document.querySelectorAll('.image_gif_true'); 3751 if(checkForGif.length > 0 || imageLazyLoading.length > 0 ){ 3752 pictureArray.forEach((pictureEntry) => { 3753 const options = { 3754 root: null, 3755 threshold: 0.75 3756 }; 3757 3758 /*handle load of animation by using the IntersectionObserver */ 3759 let gifImage = pictureEntry.querySelector('.image_gif_true'); 3760 let gifLoaded = pictureEntry.querySelector('.image_gif_true.image-loaded'); 3761 if ( gifImage && !gifLoaded ) { 3762 const observer = new IntersectionObserver((entries , entryObserver) => { 3763 entries.forEach(entry => { 3764 if ( entry.isIntersecting ) { 3765 let defaultImage = pictureEntry.querySelector('.image-container__picture-Image'); 3766 gifImage.classList.add('image-loaded'); 3767 defaultImage.setAttribute('src', defaultImage.getAttribute('data-image')); 3768 defaultImage.setAttribute('alt', defaultImage.getAttribute('data-gif-image-alternate')); 3769 pictureEntry.querySelectorAll('.image-container__picture-Source').forEach((source) => { 3770 source.setAttribute('srcset', defaultImage.getAttribute('data-image')); 3771 }); 3772 entryObserver.disconnect(); 3773 } 3774 }); 3775 }, options); 3776 observer.observe(pictureEntry); 3777 } 3778 }); 3779 } 3780 } 3781 3782}); 3783(() => { 3784 $(document).ready(function () { 3785 'use strict'; 3786 3787 document.addEventListener("click", function (event) { 3788 const link = event.target.closest(".cmp-hrztimelineslide .aemvc-v2-videoplayer .aemvc-v2-video__accessible .c11n-link"); 3789 const bodyElement = document.querySelector("body"); 3790 3791 if (link) { 3792 // Disable default View Transcript functionality 3793 event.stopImmediatePropagation(); 3794 event.preventDefault(); 3795 3796 // Get ID of the closest slide component 3797 let slideComponent = link.closest('.cmp-hrztimelineslide'); 3798 let currentEventId = slideComponent ? slideComponent.id : `slide-${new Date().getTime()}`; 3799 3800 // Get transcript text from the video player component 3801 let transcriptText = "Transcript not available"; 3802 3803 if (slideComponent) { 3804 const videoPlayer = slideComponent.querySelector("c11n-aem-el-videoplayer-v2"); 3805 3806 if (videoPlayer) { 3807 transcriptText = videoPlayer.getAttribute("transcripttext") || "Transcript not available"; 3808 } else { 3809 console.warn(`No video player found inside slide ${currentEventId}`); 3810 } 3811 } 3812 3813 // Check if modal exists, otherwise inject it 3814 let modal = document.querySelector(`#modal-event-${currentEventId}`); 3815 if (!modal) { 3816 modal = document.createElement("dialog"); 3817 modal.id = `modal-event-${currentEventId}`; 3818 modal.classList.add("cmp-hrztimelineslide-transcript-modal"); 3819 modal.innerHTML = ` 3820 <div class="cmp-hrztimelineslide-transcript-modal__container"> 3821 <div class="cmp-hrztimelineslide-transcript-modal__btnContainer--close"> 3822 <button class="cmp-hrztimelineslide-transcript-modal--closeBtn" 3823 title="Close the transcript modal" 3824 aria-label="Close the transcript modal">
3825 <span class="cmp-hrztimelineslide-transcript-modal--closeBtnIcon" aria-hidden="true"></span> 3826 </button> 3827 </div> 3828 <div class="cmp-hrztimelineslide-transcript-modal__contentContainer"> 3829 <div class="cmp-hrztimelineslide-transcript-modal__copy"> 3830 ${transcriptText} 3831 </div> 3832 </div> 3833 </div> 3834 `; 3835 document.body.appendChild(modal); 3836 } else { 3837 // Update transcript content if modal already exists 3838 let transcriptContainer = modal.querySelector("cmp-hrztimelineslide-transcript-modal__copy"); 3839 if (transcriptContainer) { 3840 transcriptContainer.innerHTML = transcriptText; 3841 } 3842 } 3843 3844 modal.style.opacity = "0"; 3845 modal.style.display = "block"; 3846 3847 setTimeout(() => { 3848 modal.showModal(); 3849 modal.style.opacity = "1"; 3850 }, 50); 3851 3852 bodyElement.style.overflow = 'hidden'; 3853 handleClickAndEscapeModal(`#modal-event-${currentEventId}`); 3854 3855 } 3856 }, true); 3857 3858 //need to extract this function as a global function so it can be reused without creating code duplication 3859 const handleClickAndEscapeModal = function handleEscapeModal(targetModalSelector) { 3860 const targetModal = document.querySelector(targetModalSelector); 3861 3862 if (!targetModal) return; 3863 3864 $(document).keydown(function (event) { 3865 if (event.key === "Escape" && targetModal.hasAttribute("open")) { 3866 closeModal(targetModal); 3867 } 3868 }); 3869 3870 $(targetModal).click(function (event) { 3871 if (event.target.nodeName === 'DIALOG') { 3872 closeModal(targetModal); 3873 } 3874 }); 3875 }; 3876 3877 function closeModal(modal) { 3878 modal.style.opacity = "0"; 3879 setTimeout(() => { 3880 modal.close(); 3881 modal.style.display = 'none'; 3882 modal.removeAttribute("open"); 3883 document.body.style.overflow = 'visible'; 3884 }, 700); 3885 } 3886 3887 $(document).on("click", ".cmp-hrztimelineslide-transcript-modal--closeBtn", function () { 3888 const modal = $(this).closest("dialog")[0]; 3889 3890 if(modal) { 3891 closeModal(modal); 3892 } 3893 }); 3894 }); 3895})(); 3896 3897 3898let skipRecordSubPageViewed = false; 3899 3900(function () { 3901 "use strict"; 3902 3903 // set up timeline slide navigation 3904 const htlSlideList = $('.cmp-hrztimelineslide'); 3905 const firstTimelineSlide = $('.htl-content > div.aem-Grid > .horizontalTimelineSlide').not('.vanguardEffectSlide').first(); 3906 let earliestSlideYear = $('[data-vg-htl-year]:first').data('vg-htl-year'); 3907 let latestSlideYear = $('[data-vg-htl-year]:last').data('vg-htl-year'); 3908 3909 let currentYear = earliestSlideYear;
3910 let currentSlideIndex = 0; 3911 3912 //console.log('num of slides: ' + htlSlideList.length); 3913 3914 // Position timeline bar in correct spot 3915 updateTimelineBarPosition(); 3916 $(window).on('resize', updateTimelineBarPosition); 3917 3918 // loop through years adding markers 3919 htlSlideList.each(function() { 3920 let markerYear = parseInt($(this).attr('data-vg-htl-year')); 3921 if (!Number.isNaN(markerYear) && $('#htl-marker-' + markerYear).length === 0) { 3922 let marker = $('<div/>').addClass("htl-progress-marker").attr('id', 'htl-marker-' + markerYear).css('marginLeft', ((markerYear - earliestSlideYear) * 2).toString() + '%'); 3923 $('.htl-progress').append(marker); 3924 } 3925 }); 3926 3927 function newSlide() { 3928 if ($('.htl-progress-container').css('display') != 'none' && $('.htl-progress-indicator').length) { 3929 $('.htl-progress-indicator').css('margin-left', ((parseInt(currentYear) - earliestSlideYear) * 2).toString() + '%'); 3930 $('.htl-progress').attr('aria-valuenow', currentYear.toString()); 3931 } 3932 3933 $('.htl-tve-button').attr('data-event-id', currentYear.toString()); 3934 $('.htl-year').html(currentYear.toString()); 3935 } 3936 3937 $('#htl-controls_previous_button').on('click', function() { 3938 // add analytics call 3939 recordAnalytics(document.title.split("|")[0], 'Previous Year', 'custom button'); 3940 }); 3941 3942 $('#htl-controls_previous_button').on('keydown', function(e) { 3943 if (e.keyCode === 32 || e.keyCode === 13) { //hit SPACE or ENTER 3944 e.preventDefault(); 3945 $('#htl-controls_previous').trigger('click'); 3946 } 3947 }); 3948 3949 $('#htl-controls_next_button').on('click', function() { 3950 // add analytics call 3951 recordAnalytics(document.title.split("|")[0], 'Next Year', 'custom button'); 3952 }); 3953 $('#htl-controls_next_button').on('keydown', function(e) { 3954 if (e.keyCode === 32 || e.keyCode === 13) { //hit SPACE or ENTER 3955 e.preventDefault(); 3956 $('#htl-controls_next').trigger('click'); 3957 } 3958 }); 3959 $('.htl-next-slide-link').on('keydown', function(e) { 3960 if (e.keyCode === 32 || e.keyCode === 13) { //hit SPACE or ENTER 3961 e.preventDefault(); 3962 $('#htl-controls_next').trigger('click'); 3963 } 3964 }); 3965 3966 $('#htl-controls_beginning_button').on('click', function() { 3967 goToStart(); 3968 }); 3969 $('#htl-controls_beginning_button').on('keydown', function(e) { 3970 if (e.keyCode === 32 || e.keyCode === 13) { //hit SPACE or ENTER 3971 e.preventDefault(); 3972 $('#htl-controls_beginning_button').trigger('click'); 3973 } 3974 }); 3975 3976 $(document).on('keydown', function(e) { 3977 if (e.key === 'ArrowRight' || e.keyCode === 39) { 3978 e.preventDefault(); 3979 $('#htl-controls_next').trigger('click'); 3980 } else if (e.key === 'ArrowLeft' || e.keyCode === 37) { 3981 e.preventDefault(); 3982 $('#htl-controls_previous').trigger('click'); 3983 } 3984 }); 3985 3986 function goToStart() { 3987 skipRecordSubPageViewed = true; 3988 3989 // scroll to start of timeline or start of page if scroll container not found 3990 let scrollContainer = document.querySelector('.cmp-horizontaltimeline__container'); 3991 let beginningOffset = scrollContainer ? scrollContainer.offsetTop : 0; 3992 3993 window.scrollTo({ 3994 top: beginningOffset, 3995 behavior: 'smooth' 3996 }); 3997 3998 // add analytics call 3999 recordAnalytics(document.title.split("|")[0], 'Back to start', 'custom button'); 4000 4001 // force current slide to start 4002 currentYear = earliestSlideYear; 4003 newSlide(); 4004 4005 // Wait until the scroll has finished 4006 setTimeout(function() { 4007 skipRecordSubPageViewed = false; 4008 }, 2000); 4009 } 4010 4011 $('#htl-controls_end_button').on('click', function() { 4012 goToEnd(); 4013 }); 4014 $('#htl-controls_end_button').on('keydown', function(e) { 4015 if (e.keyCode === 32 || e.keyCode === 13) { //hit SPACE or ENTER 4016 e.preventDefault();
4017 $('#htl-controls_end_button').trigger('click'); 4018 } 4019 }); 4020 4021 function goToEnd() { 4022 skipRecordSubPageViewed = true; 4023 4024 // scroll to final slide or end of page if scroll container not found 4025 let scrollContainer = document.querySelector('.cmp-horizontaltimeline__container'); 4026 let endOffset = scrollContainer ? scrollContainer.getBoundingClientRect().bottom + window.scrollY - window.innerHeight : document.documentElement.scrollHeight; 4027 4028 window.scrollTo({ 4029 top: endOffset, 4030 behavior: 'smooth' 4031 }); 4032 4033 // add analytics call 4034 recordAnalytics(document.title.split("|")[0], 'Forward to end', 'custom button'); 4035 4036 // force current slide to end 4037 currentYear = latestSlideYear; 4038 newSlide(); 4039 4040 // Wait until the scroll has finished 4041 setTimeout(function() { 4042 skipRecordSubPageViewed = false; 4043 }, 2000); 4044 } 4045 4046 4047 // hide the slides toolbar 4048 let slideObserver2 = new IntersectionObserver( 4049 function (entries) { 4050 entries.forEach(entry => { 4051 if (entry.isIntersecting) { 4052 if ($('.htl-footer').hasClass('show')) { 4053 $('.htl-footer').removeClass('show'); 4054 } 4055 } 4056 }); 4057 }, 4058 { root: document.querySelector('.cmp-horizontaltimeline'), threshold: 0.5 } 4059 ); 4060 const noHTLToolbar = document.querySelectorAll('.horizontalTimelineSlide'); 4061 if (noHTLToolbar && noHTLToolbar.length > 1) { 4062 slideObserver2.observe(noHTLToolbar[0]); 4063 slideObserver2.observe(noHTLToolbar[noHTLToolbar.length - 1]); 4064 } 4065 4066 // show the slides toolbar, change the year, and move the indicator as the slide changes 4067 let slideObserverYear = new IntersectionObserver( 4068 function (entries) { 4069 entries.forEach(entry => { 4070 if (entry.isIntersecting) { 4071 //console.log('isIntersecting:' + entry.target.id); 4072 if (!$('.htl-footer').hasClass('show') && !$(entry.target).hasClass('htl-exit')) { 4073 $('.htl-footer').addClass('show'); 4074 } 4075 const newYear = $(entry.target).data('vg-htl-year'); 4076 if (newYear) { 4077 currentYear = newYear; 4078 currentSlideIndex = htlSlideList.index(htlSlideList.filter("[data-vg-htl-year='" + currentYear + "']")); 4079 4080 newSlide(); 4081 let headline = entry.target.getAttribute("data-vg-htl-analytics-headline") ? "_" + entry.target.getAttribute("data-vg-htl-analytics-headline").trim() : ""; 4082 recordSubPageViewed(currentYear.toString() + headline, "true"); 4083 } else { 4084 //send event for slides that do not have the data-vg-htl-year attribute like the Vanguard effect ones 4085 let headline = entry.target.getAttribute("data-vg-htl-analytics-headline") ? "Slide_" + entry.target.getAttribute("data-vg-htl-analytics-headline").trim() : null; 4086 if(headline){ 4087 recordSubPageViewed(headline, "true"); 4088 } 4089 } 4090 } 4091 }); 4092 }, 4093 { root: null, threshold: 0.25 } 4094 ); 4095 document.querySelectorAll('*[data-vg-slide]:not(.htl-intro)').forEach(item => { 4096 slideObserverYear.observe(item); 4097 }); 4098 4099 $('.htl-intro-cta').on('click', function(e) { 4100 recordAnalytics(document.title.split("|")[0], 'Start exploring', 'custom button'); 4101 }); 4102 $('.htl-intro-cta').keydown(function(e) { 4103 if (e.key === " " || e.key === 'Enter' || e.keyCode === 32 || e.keyCode === 13) { //hit SPACE or ENTER 4104 $(this).trigger('click'); 4105 } 4106 }); 4107 4108 // Repositioning timeline nav bar based on mobile and desktop 4109 function updateTimelineBarPosition() { 4110 const timelineBar = $('#htl-footer'); 4111 if(window.matchMedia('(max-width: 991px), (min-width:992px) and (max-width:1199px) and (orientation: portrait)').matches) { 4112 if (!$(timelineBar).parent().hasClass('aem-grid')) { 4113 timelineBar.insertBefore(firstTimelineSlide); 4114 } 4115 } else { 4116 if (!$(timelineBar).parent().hasClass('horizontalTimeline')){ 4117 timelineBar.insertBefore($('.horizontalTimeline >
4117 div.cmp-horizontaltimeline')); 4118 } 4119 } 4120 } 4121 4122 function recordAnalytics(location, name, type) { 4123 // add analytics call 4124 window.dataLayer = window.dataLayer || []; 4125 window.dataLayer.push({ 4126 "event": "callToAction", //e111 4127 "callToAction": { 4128 "location": location.replace("â", "'").trim(), //v103 4129 "ctaName": name, //v104 4130 "ctaType": type //v111 4131 } 4132 }); 4133 } 4134 4135 function recordSubPageViewed(subPageID, clicked) { 4136 if (skipRecordSubPageViewed) { 4137 //skipRecordSubPageViewed = false; 4138 return; 4139 } 4140 //Used to signify that a subPage was activated/viewed 4141 window.dataLayer = window.dataLayer || []; 4142 window.dataLayer.push({ 4143 "event": "subPageViewed",//e166 4144 "subPage": { 4145 "subPageID": subPageID, //v105 4146 "spClicked": clicked 4147 } 4148 }); 4149 } 4150 4151 4152 $(document).ready(function () { 4153 // prevents browser from automatically restoring the scroll position when navigating back 4154 if ('scrollRestoration' in history) { 4155 history.scrollRestoration = 'manual'; 4156 } 4157 4158 let maskSelectors = [{ 4159 mask: "#slantMask", 4160 container: '.cmp-hrztimelineslide .cmp-hrztimelineslide__background[data-vg-slide-mask="slant"]', 4161 transformTemplate: (containerHeight) => `scale(${containerHeight / 1080})` 4162 4163 }, { 4164 mask: "#endSlideMask", 4165 container: '.htl-exit-mask-layer', 4166 transformTemplate: (containerHeight) => { 4167 //aspect ration of the part we want to hide 4168 let offset = containerHeight * .89; 4169 return `translateX(-${offset}px) scale(${containerHeight / 1080})` 4170 } 4171 }]; 4172 4173 let ticking = false; 4174 function scaleSlantSvg() { 4175 //Read before write 4176 let maskUpdates = maskSelectors.map((maskSelector) => { 4177 const container = document.querySelector(maskSelector.container); 4178 const clipPath = document.querySelector(maskSelector.mask); 4179 4180 if (!container || !clipPath) return; 4181 4182 return { clipPath: clipPath.querySelector('path:only-child') || clipPath, transform: maskSelector.transformTemplate(container.clientHeight) }; 4183 }); 4184 4185 //Update in one frame 4186 ticking = true; 4187 requestAnimationFrame(() => { 4188 maskUpdates.forEach((update) => { 4189 if (update) { 4190 update.clipPath.style.transform = update.transform; 4191 } 4192 }); 4193 ticking = false; 4194 }); 4195 } 4196 4197 function initialize() { 4198 maskSelectors.forEach(maskSelector => { 4199 document 4200 .querySelectorAll(maskSelector.container) 4201 .forEach((e) => e.style.clipPath = `url(${maskSelector.mask})`); 4202 }); 4203 } 4204 4205 window.addEventListener('resize', scaleSlantSvg); 4206 4207 initialize(); 4208 scaleSlantSvg(); 4209 }); 4210 4211 //Handle tab/focus events to scroll to element 4212 $(document).ready(() => { 4213 const scrollContainer = document.querySelector('.cmp-horizontaltimeline__container'); 4214 const carousel = document.querySelector('.htl-content'); 4215 let isWindowFocus = false; 4216 let windowFocusTimeout; 4217 4218 window.addEventListener('focus', () => { 4219 isWindowFocus = true; 4220 clearTimeout(windowFocusTimeout); 4221 windowFocusTimeout = setTimeout(() => isWindowFocus = false, 0); 4222 }); 4223 4224 //On all focused elements in the timeline 4225 $(scrollContainer).on('focus', '*', (e) => { 4226 if (window.innerWidth < 992 || isWindowFocus) return; 4227 e.stopPropagation(); 4228 e.preventDefault(); 4229 4230 //Fix Safari early focus event 4231 setTimeout(() => focusElementInTimeline(e.target), 0); 4232 }); 4233 4234 function focusElementInTimeline(t) { 4235 if (!isElementInViewport(t)) { 4236 const relativeLeft = getPositionRelativeToContainer(t, scrollContainer) + (t.offsetWidth / 2) - (window.innerWidth / 2); 4237 const percentWithinCarousel = relativeLeft / (carou
4237sel.scrollWidth - window.innerWidth); 4238 const scrollHeight = percentWithinCarousel * (scrollContainer.offsetHeight - window.innerHeight) 4239 const scrollTop = scrollHeight + scrollContainer.offsetTop; 4240 window.scrollTo({ 4241 top: scrollTop, 4242 left: 0 4243 }); 4244 } 4245 } 4246 4247 function getPositionRelativeToContainer(current) { 4248 let totalOffset = 0; 4249 4250 while (!!current && current !== scrollContainer && current !== document) { 4251 totalOffset += current.offsetLeft; 4252 current = current.offsetParent; 4253 } 4254 4255 return totalOffset; 4256 } 4257 4258 function isElementInViewport(t) { 4259 const rect = t.getBoundingClientRect(); 4260 return ( 4261 // rect.top >= 0 && 4262 rect.left >= 0 && 4263 //Ignore bottom since this is horizontal scrolling at 100vh 4264 // rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && 4265 rect.right <= (window.innerWidth || document.documentElement.clientWidth) 4266 ) 4267 } 4268 }); 4269 4270 //Handle initial mobile animation 4271 $(document).ready(() => { 4272 //Prevent user from arriving at different height 4273 // if (window.scrollY > 0) { 4274 // window.scrollY = window.scrollY + $('.htl-intro-video').outerHeight(); 4275 // } 4276 4277 setTimeout(() => { 4278 $('.htl-intro').addClass('animate-slide-up') 4279 }, 3000); 4280 }) 4281})(); 4282$(document).ready(function(){ 4283 var secondaryDownloadButton = $('.button-secondary-download'); 4284 secondaryDownloadButton.text().trim().length > 5 ? secondaryDownloadButton.removeClass('button-padding-41').addClass('button-padding-26'):secondaryDownloadButton.removeClass('button-padding-26').addClass('button-padding-41'); 4285 4286 $(".btn-secondary-download-container a").on('click' , function(e){ 4287 e.preventDefault(); 4288 window.open($(this).data('ctapath'), '_blank'); 4289 }); 4290}); 4291$(document).ready(function(){ 4292var secondaryButton = $('.button-secondary'); 4293secondaryButton.text().trim().length > 7 ? secondaryButton.removeClass('button-padding-41').addClass('button-padding-26'):secondaryButton.removeClass('button-padding-26').addClass('button-padding-41'); 4294 4295}); 4296$(document).ready(function(){ 4297 var primaryDownloadButton = $('.button-primary-download'); 4298 primaryDownloadButton.text().trim().length > 5 ? primaryDownloadButton.removeClass('button-padding-41').addClass('button-padding-26'):primaryDownloadButton.removeClass('button-padding-26').addClass('button-padding-41'); 4299 4300 $(".btn-primary-download-container a").on('click' , function(e){ 4301 e.preventDefault(); 4302 window.open($(this).data('ctapath'), '_blank'); 4303 }); 4304}); 4305$(document).ready(function(){ 4306var primaryButton = $('.button-primary'); 4307primaryButton.text().trim().length > 7 ? primaryButton.removeClass('button-padding-41').addClass('button-padding-26'):primaryButton.removeClass('button-padding-26').addClass('button-padding-41'); 4308 4309}); 4310$(document).ready(function() { 4311 4312 let isSafari = navigator.vendor.match(/apple/i) && 4313 !navigator.userAgent.match(/crios/i) && 4314 !navigator.userAgent.match(/fxios/i) && 4315 !navigator.userAgent.match(/Opera|OPT\//); 4316 4317 if (isSafari) { 4318 $('.btn-primary-container .cta-link-event').each(function() { 4319 let current = $(this); 4320 setTargetAttribute(current); 4321 }); 4322 $('.cta-link-arrow-container .cta-link-event').each(function() { 4323 let current = $(this); 4324 setTargetAttribute(current); 4325 }); 4326 } 4327 4328 function setTargetAttribute(current){ 4329 let ctaPath = current.data('ctapath'); 4330 if (ctaPath) { 4331 if (ctaPath.indexOf('/content/corporatesite/') === -1) { 4332 current.attr('target','_blank').attr('rel','external'); 4333 } 4334 } 4335 } 4336 4337 $('.cta-link-event').on('click', function(e) { 4338 e.preventDefault(); 4339 let ctaPath = $(this).data('ctapath'); 4340 if (ctaPath) { 4341 let isPdfContent = ctaPath.endsWith('.pdf'); 4342 let isCorpSiteContent = ctaPath.indexOf('/content/corporatesite/') !== -1; 4343 let ctaName = $(this).data('ctaname'); 4344 4345 if (ctaName === "Subscribe") { 4346 ctaName = "Subscribe CTA Banner" 4347 } 4348 4349 if (!isPdfContent) { 4350 window.dataLayer = window.dataLayer || []; 4351 window.dataLayer.push({ 4352 "event": "callToAction", //e111 4353 "callToAction": { 4354 "location": $(this).data('ctalocation'), //v103 4355 "ctaName": ctaName, //v104 4356 "ctaType": $(this).data('ctatype') //v111 4357 } 4358 }); 4359 } 4360 if (isCorpSiteContent) { 4361 window.open(ctaPath, '_self'); 4362 } else { 4363 window.open(ctaPath, '_blank'); 4364 } 4365 } 4366 }); 4367 4368}); 4369$(document).ready(function () { 4370 var contentListPrimaryDownloadButton = $('.button-contentListPrimary-download-container'); 4371 contentListPrimaryDownloadButton.text().trim().length > 5 ? contentListPrimaryDownloadButton.removeClass('button-padding-43').addClass('button-padding-38') : contentListPrimaryDownloadButton.removeClass('button-padding-38').addClass('button-padding-43'); 4372 4373 $(".btn-contentListPrimary-download-container a").on('click', function (e) { 4374 e.preventDefault(); 4375 let filePath = $(this).data('ctapath'); 4376 let linkTarget = $(this).data('linktarget').toString(); 4377 let linkTargetDefault = '_self' == linkTarget? '_self': '_blank'; 4378 window.open(filePath, linkTargetDefault); 4379 }); 4380}); 4381$(document).ready(function(){ 4382var primaryButton = $('.contentList-button-primary'); 4383primaryButton.text().trim().length > 7 ? primaryButton.removeClass('button-padding-43').addClass('button-padding-38') : primaryButton.removeClass('button-padding-38').addClass('button-padding-43'); 4384 4385}); 4386$(document).ready(function() { 4387 let alertButton = $('.button-urgent,.button-non-urgent'); 4388 alertButton.text().trim().length > 7 ? alertButton.removeClass('button-padding-41').addClass('button-padding-26') : alertButton.removeClass('button-padding-26').addClass('button-padding-41'); 4389}); 4390(() => { 4391 4392})(); 4393 4394$(document).ready(function() { 4395 if ($('.cmp-collapsibledrawer__container--topic').length > 0) { 4396 $('.cmp-collapsibledrawer__container--topic').find(".ffacts").removeClass().addClass( "col-12 col-md-6 col-lg-4 ff-padding"); 4397 } 4398 if ($('.cmp-fastfacts').length > 0) { 4399 $('.cmp-fastfacts').find(".number-dark-grey.fast-facts-line.fast-facts-margin-top").addClass( "fast-facts-line-number-font"); 4400 $('.cmp-fastfacts').find(".h3-heavy.fast-facts-line.fast-facts-margin-top").addClass( "fast-facts-line-text-font"); 4401 4402 if ( $('.cmp-fastfacts').parent().closest('div').parent().closest('div').parent().closest('div').attr('class').indexOf('root responsivegrid') >= 0 ) { 4403 $('.cmp-fastfacts').addClass( "fast-facts-margin"); 4404 $('.ffrow').addClass( "ff-width-87"); 4405 $('.fastfactsTitleRow').addClass( "ff-width-87"); 4406 $('.fastfacts-container').addClass( "ff-container-margin"); 4407 } 4408 } 4409 $('.ffrow').each(function() { 4410 if ($(this).children().length == 2) { 4411 $(this).addClass('justify-content-center'); 4412 $(this).prev('.fastfactsTitleRow').addClass('justify-content-center'); 4413 } else if ($(this).children().length == 1) { 4414 $(this).removeClass('justify-content-center'); 4415 $(this).prev('.fastfactsTitleRow').children('.ffactsheader').removeClass().addClass('ffactsheader col-12 col-md-5 ff-padding offset-md-2'); 4416 $(this).children('.ffacts').addClass('offset-md-2'); 4417 } 4418 }); 4419 4420}); 4421(function () { 4422 "use strict"; 4423 4424 // dynamically adjust iframe height to ensure chart isn't cutoff on mobile, 4425 // doesn't have scrollbars, and doesn't have too much whitespace underneath on desktop 4426 4427 window.addEventListener('message', function(event) { 4428 if (typeof event.data['datawrapper-height'] !== 'undefined') { 4429 let iframes = document.querySelectorAll('iframe'); 4430 for (let chartId in event.data['datawrapper-height']) { 4431 for (let i = 0; i < iframes.length; i++) { 4432 if (iframes[i].contentWindow === event.source) { 4433 let iframe = iframes[i]; 4434 iframe.style.height = event.data['datawrapper-height'][chartId] + 'px'; 4435 adjustContainerHeight(iframe); 4436 } 4437 } 4438 } 4439 } 4440 }); 4441 4442 // adjust height on window resize, if side-by-side chart exists 4443 window.addEventListener('resize', function() { 4444 let iframes = document.querySelectorAll('iframe[id^="datawrapper-chart"]');
4445 iframes.forEach(function(iframe) { 4446 adjustContainerHeight(iframe); 4447 }); 4448 }); 4449 4450 // adjust container height, if there is a side-by-side chart, so both charts are same height 4451 function adjustContainerHeight(iframe) { 4452 let sideBySideChart = getSideBySideChart(iframe); 4453 if (sideBySideChart) { 4454 let iframeHeight = parseInt(iframe.style.height, 10); 4455 let sideBySideHeight = parseInt(window.getComputedStyle(sideBySideChart).height, 10); 4456 let container = iframe.closest('.cmp-dwcharts__container'); 4457 if (container) { 4458 container.style.height = Math.max(iframeHeight, sideBySideHeight) + 'px'; 4459 } 4460 } 4461 } 4462 4463 // returns side-by-side chart, if it exists 4464 function getSideBySideChart(iframe) { 4465 if (!iframe) return; 4466 let parent = iframe.closest('.cmp-container'); 4467 if (parent) { 4468 let grandParent = parent.parentElement?.closest('.cmp-container:not([id="' + parent.id + '"])'); 4469 if (grandParent) { 4470 let otherChart = grandParent.querySelector('[id^="datawrapper-chart"]:not([id="' + iframe.id + '"])'); 4471 if (otherChart) { 4472 return otherChart; 4473 } 4474 } 4475 } 4476 return; 4477 } 4478 4479})(); 4480 4481$(document).ready(function() { 4482 setDeepLinksPrettyTitles(); 4483}); 4484 4485const titles = { 4486 "amazonMusic": "Amazon Music", 4487 "applePodcasts": "Apple Podcasts", 4488 "iHeart": "iHeart", 4489 "spotify": "Spotify", 4490 "tuneIn": "TuneIn", 4491 "youtube": "YouTube", 4492 "reddit": "Reddit" 4493} 4494 4495function setDeepLinksPrettyTitles() { 4496 let deepLinks = document.getElementsByClassName("cmp-deepLinks__link"); 4497 Array.from(deepLinks).forEach(function (deepLink) { 4498 let icon = deepLink.dataset.icon; 4499 let prettyIconName = titles[icon]; 4500 deepLink.title = "Open " + prettyIconName + " in a new tab"; 4501 }); 4502} 4503"use strict"; 4504 4505const contentListArray = document.querySelectorAll(".cmp-contentList"); 4506 4507let contentListListElements = []; 4508let contentListFilteredList = []; 4509let contentListUnfilteredList = []; 4510let contentListSelectOptionsList = []; 4511let contentListInvestmentStewYearSelectOptionsList = []; 4512 4513let contentListInvestmentStewTopicSelected = false; 4514let contentListInvestmentStewYearSelected = false; 4515 4516let contentListFilterSelectors = {}; 4517 4518let contentListPressroomFiltering = null; 4519let contentListInvestmentStewFiltering = null; 4520let contentListElement = null; 4521let contentListLoadMore = null; 4522 4523let contentListCurrentItems = 0; 4524let contentListShowingElements = 10; 4525let contentListNextToShow = 10; 4526 4527let contentListWCMMode = ""; 4528 4529const clearOptions = function clearOptions(element) { 4530 let length = element.options.length; 4531 for (let i = length; i > 0; i--) { 4532 element.remove(i); 4533 } 4534}; 4535 4536const allResults = function allResults() { 4537 contentListUnfilteredList.forEach((element) => { 4538 if (contentListPressroomFiltering) { 4539 if (element.querySelector('.cmp-contentListEntry__date')) { 4540 element.querySelector('.cmp-contentListEntry').style.display = "block"; 4541 contentListElement.querySelector('.cmp-contentList__resultsEntries').appendChild(element); 4542 } 4543 } else if (element.querySelector('.cmp-contentListEntry__contentType')) { 4544 element.querySelector('.cmp-contentListEntry').style.display = "block"; 4545 contentListElement.querySelector('.cmp-contentList__resultsEntries').appendChild(element); 4546 } 4547 }); 4548}; 4549 4550const addOptionsToSelect = function addOptionsToSelect(classOfOption, classOfSelect, array) { 4551 array.forEach((optionElement) => { 4552 let option = document.createElement("option"); 4553 option.setAttribute("value", optionElement); 4554 let optionDisplayText = document.createTextNode(optionElement); 4555 option.appendChild(optionDisplayText); 4556 option.classList.add(classOfOption); 4557 contentListElement.querySelector(classOfSelect).appendChild(option); 4558 }); 4559}; 4560 4561const hideElements = function hideElements(listToHideElements) { 4562 if (contentListWCMMode === 'false') { 4563 let count = 1;
4564 listToHideElements.forEach(function (entry) { 4565 if (count <= contentListShowingElements) { 4566 entry.querySelector('.cmp-contentListEntry').style.display = "block"; 4567 } else { 4568 entry.querySelector('.cmp-contentListEntry').style.display = "none"; 4569 } 4570 count++; 4571 }); 4572 } 4573}; 4574 4575const displayNumberOfResults = function displayNumberOfResults(listOfElementsPresent) { 4576 if (contentListElement.querySelector('.cmp-contentList__results') != null) { 4577 if (listOfElementsPresent.length === 1) { 4578 contentListElement.querySelector('.cmp-contentList__results').textContent = listOfElementsPresent.length + " result"; 4579 } else if (listOfElementsPresent.length > 1) { 4580 contentListElement.querySelector('.cmp-contentList__results').textContent = listOfElementsPresent.length + " results"; 4581 } else if (listOfElementsPresent.length === 0) { 4582 contentListElement.querySelector('.cmp-contentList__results').textContent = "No results found."; 4583 } 4584 } 4585}; 4586 4587const createFilterDropdown = function createFilterDropdown(array) { 4588 array.forEach((element) => { 4589 if (contentListPressroomFiltering != null) { 4590 if (element.querySelector('.cmp-contentListEntry__date') && 4591 !contentListSelectOptionsList.includes(new Date(element.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getFullYear())) { 4592 contentListSelectOptionsList.push(new Date(element.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getFullYear()); 4593 contentListSelectOptionsList.sort((oldest, newest) => { 4594 return newest - oldest; 4595 }); 4596 } 4597 } else if (contentListInvestmentStewFiltering != null) { 4598 if (element.querySelector('.cmp-contentListEntry__contentType') && 4599 !contentListSelectOptionsList.includes(element.querySelector('.cmp-contentListEntry__contentType').getAttribute('data-result-contentType'))) { 4600 contentListSelectOptionsList.push(element.querySelector('.cmp-contentListEntry__contentType').getAttribute('data-result-contentType')); 4601 contentListSelectOptionsList.sort(); 4602 } 4603 if (element.querySelector('.cmp-contentListEntry__date') && 4604 !contentListInvestmentStewYearSelectOptionsList.includes(new Date(element.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getFullYear())) { 4605 contentListInvestmentStewYearSelectOptionsList.push(new Date(element.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getFullYear()); 4606 contentListInvestmentStewYearSelectOptionsList.sort((oldest, newest) => { 4607 return newest - oldest; 4608 }); 4609 } 4610 } else if (element.querySelector('.cmp-contentListEntry__contentType') && 4611 !contentListSelectOptionsList.includes(element.querySelector('.cmp-contentListEntry__contentType').getAttribute('data-result-contentType'))) { 4612 contentListSelectOptionsList.push(element.querySelector('.cmp-contentListEntry__contentType').getAttribute('data-result-contentType')); 4613 contentListSelectOptionsList.sort(); 4614 } 4615 }); 4616}; 4617 4618/*******************************************************************************************************************************/ 4619/************************************************ LOAD MORE ********************************************************************/ 4620/*******************************************************************************************************************************/ 4621const loadMoreResultsHandler = function loadMoreResultsHandler(arrayReferenced) { 4622 let articleToBeFocused = contentListCurrentItems; 4623 for (let i = contentListCurrentItems; i < contentListCurrentItems + contentListNextToShow; i++) { 4624 if (arrayReferenced[i] != null) { 4625 arrayReferenced[i].querySelector('.cmp-contentListEntry').style.display = 'block'; 4626 } 4627 } 4628 contentListCurrentItems += contentListNextToShow; 4629 if (contentListArray.length === 1 && contentListCurrentItems >= arrayReferenced.length) { 4630 contentListLoadMore.style.display = 'none'; 4631 } 4632 if (arrayReferenced[articleToBeFocused].querySelector('.cmp-contentListEntry__headlineLink') != null) { 4633 arrayReferenced[articleToBeFocused].querySelector('.cmp-contentListEntry__headlineLink').focus(); 4634 } 4635}; 4636 4637const showLoadMoreButton = function showLoadMoreButton(listToCheckForLoadMore) { 4638 if (listToCheckForLoadMore.length > contentListShowingElements) { 4639 contentListElement.querySelector('.cmp-contentList__loadM
4639oreButtonContainer').style.display = 'block'; 4640 } else { 4641 contentListElement.querySelector('.cmp-contentList__loadMoreButtonContainer').style.display = 'none'; 4642 } 4643}; 4644 4645const loadMoreResultsOnClick = function loadMoreResultsOnClick() { 4646 4647 if (contentListLoadMore != null) { 4648 contentListLoadMore.addEventListener('click', (event) => { 4649 loadMoreResults(); 4650 }); 4651 contentListLoadMore.addEventListener('keypress', function (e) { 4652 if (e.key === 'Enter') { 4653 loadMoreResults(); 4654 } 4655 }); 4656 } 4657}; 4658 4659const loadMoreResults = function loadMoreResults() { 4660 4661 let noOfContentLists = contentListArray.length; 4662 let selectedLoadmore; 4663 if (noOfContentLists > 1) { 4664 let currentContentList = $(event.target).closest(".cmp-contentList")[0]; 4665 selectedLoadmore = currentContentList.querySelectorAll(".cmp-contentList__loadMoreButton"); 4666 let selectedListItems = currentContentList.querySelectorAll('.cmp-contentList__resultsEntries .contentListEntry'); 4667 let selectedVisibleItemsSize = selectedLoadmore[0].getAttribute('data-contentlistcurrentitems'); 4668 if (selectedVisibleItemsSize) { 4669 contentListCurrentItems = parseInt(selectedVisibleItemsSize); 4670 } else { 4671 contentListCurrentItems = 10; 4672 } 4673 contentListFilteredList = selectedListItems; 4674 } 4675 4676 pushEventCTA(); 4677 if (contentListFilteredList.length > 0) { 4678 loadMoreResultsHandler(contentListFilteredList); 4679 } else { 4680 loadMoreResultsHandler(contentListUnfilteredList); 4681 } 4682 4683 if (noOfContentLists > 1) { 4684 selectedLoadmore[0].setAttribute('data-contentlistcurrentitems', contentListCurrentItems); 4685 if (contentListCurrentItems >= contentListFilteredList.length) { 4686 $(event.target).closest(".cmp-contentList__loadMoreButtonContainer")[0].style.display = 'none'; 4687 } 4688 } 4689} 4690 4691const pushEventCTA = function pushEventCTA() { 4692 window.dataLayer = window.dataLayer || []; 4693 window.dataLayer.push({ 4694 "event": "callToAction", //e111 4695 "callToAction": { 4696 "location": contentListLoadMore.getAttribute('data-ctalocation'), //v103 4697 "ctaName": "Load more", //v104 4698 "ctaType": "primary button" //v111 4699 } 4700 }); 4701} 4702/*******************************************************************************************************************************/ 4703/**************************************** FILTER RESULTS ***********************************************************************/ 4704/*******************************************************************************************************************************/ 4705const resetResultsCount = function resetResultsCount(array) { 4706 contentListCurrentItems = contentListShowingElements; 4707 hideElements(array); 4708 displayNumberOfResults(array); 4709 showLoadMoreButton(array); 4710 setTimeout(() => { 4711 contentListElement.querySelector('.cmp-contentList__resultsEntries').style.height = contentListElement.querySelector('.cmp-contentList__resultsEntries').outerHeight + "px"; 4712 contentListElement.querySelector('.cmp-contentList__resultsEntries').classList.toggle('slideUp'); 4713 }, 500); 4714}; 4715 4716const filteredResults = function filteredResult(arrayToFilter, filterSelected) { 4717 contentListFilteredList = []; 4718 contentListElement.querySelector('.cmp-contentList__resultsEntries').classList.toggle('slideUp'); 4719 while (contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild) { 4720 contentListElement.querySelector('.cmp-contentList__resultsEntries').removeChild(contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild); 4721 } 4722 arrayToFilter.forEach((element) => { 4723 if (contentListPressroomFiltering && 4724 parseInt(filterSelected) === new Date(element.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getFullYear()) { 4725 contentListElement.querySelector('.cmp-contentList__resultsEntries').appendChild(element); 4726 contentListFilteredList.push(element); 4727 } else if (element.querySelector('.cmp-contentListEntry__contentType') && 4728 filterSelected === element.querySelector('.cmp-contentListEntry__contentType').getAttribute('data-result-contentType')) { 4729 contentListElement.querySelector('.cmp-contentList__resultsEntries').appendChild(element); 4730 contentListFilteredList.push(element); 4731 } 4732 }); 4733 resetResultsCount(contentListFilteredList); 4734}; 4735 4736const filterResults = function filterResults(e) { 4737 if (e === "allFiltering") { 4738 contentListFilteredList = []; 4739 contentListElement.querySelector('.cmp-contentList__resultsEntries').classList.toggle('slideUp'); 4740 while (contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild) { 4741 contentListElement.querySelector('.cmp-contentList__resultsEntries').removeChild(contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild); 4742 } 4743 contentListUnfilteredList = contentListListElements; 4744 sessionStorage.setItem("previousResults", contentListUnfilteredList); 4745 allResults(); 4746 resetResultsCount(contentListUnfilteredList); 4747 } else { 4748 filteredResults(contentListUnfilteredList, e); 4749 } 4750}; 4751 4752/*******************************************************************************************************************************/ 4753/*********************************************** FILTERS ***********************************************************************/ 4754/*******************************************************************************************************************************/ 4755const filterFunctionality = function filterFunctionality() { 4756 if (contentListElement.querySelector('.cmp-contentList__filterOptions') != null) { 4757 contentListElement.querySelector('.cmp-contentList__filterOptions').addEventListener('change', (e) => { 4758 filterResults(e.target.value); 4759 }); 4760 } 4761}; 4762 4763const filterPressroomFunctionality = function filterPressroomFunctionality() { 4764 if (contentListElement.querySelector('.cmp-contentList__filterPressroomOptions') != null) { 4765 contentListElement.querySelector('.cmp-contentList__filterPressroomOptions').addEventListener('change', (e) => { 4766 filterResults(e.target.value); 4767 }); 4768 } 4769}; 4770 4771 4772const filterResultsInvestmentStew = function filterResultsInvestmentStew() { 4773 contentListFilteredList = []; 4774 contentListElement.querySelector('.cmp-contentList__resultsEntries').classList.toggle('slideUp'); 4775 while (contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild) { 4776 contentListElement.querySelector('.cmp-contentList__resultsEntries').removeChild(contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild); 4777 } 4778 contentListFilteredList = contentListUnfilteredList.filter(element => { 4779 if (contentListInvestmentStewTopicSelected && !contentListInvestmentStewYearSelected) { 4780 return contentListFilterSelectors.topicSelected === element.querySelector('.cmp-contentListEntry__contentType').getAttribute('data-result-contentType'); 4781 } else if (contentListInvestmentStewTopicSelected && contentListInvestmentStewYearSelected) { 4782 return contentListFilterSelectors.topicSelected === element.querySelector('.cmp-contentListEntry__contentType').getAttribute('data-result-contentType') && 4783 contentListFilterSelectors.yearSelected === new Date(element.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getFullYear(); 4784 } else if (!contentListInvestmentStewTopicSelected && contentListInvestmentStewYearSelected) { 4785 return contentListFilterSelectors.yearSelected === new Date(element.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getFullYear(); 4786 } else if (!contentListInvestmentStewTopicSelected && !contentListInvestmentStewYearSelected) { 4787 return true; 4788 } 4789 }); 4790 contentListFilteredList.forEach(element => { 4791 contentListElement.querySelector('.cmp-contentList__resultsEntries').appendChild(element); 4792 }); 4793 contentListCurrentItems = contentListShowingElements; 4794 hideElements(contentListFilteredList); 4795 displayNumberOfResults(contentListFilteredList); 4796 showLoadMoreButton(contentListFilteredList); 4797 setTimeout(() => { 4798 contentListElement.querySelector('.cmp-contentList__resultsEntries').style.height = contentListElement.querySelector('.cmp-contentList__resultsEntries').outerHeight + "px"; 4799 contentListElement.querySelector('.cmp-contentList__resultsEntries').classList.toggle('slideUp'); 4800 }, 500); 4801}; 4802 4803const filterInvestmentStewTopicFunctionality = function filterInvestmentStewTopicFunctionality() { 4804 if (contentListElement.querySelector('.cmp-contentList__filterInvestmentStewTopicOptions ') != null) { 4805 contentListElement.querySelector('.cmp-contentList__filterInvestmentStewTopicOptions ').addEventListener('change', (e) => { 4806 contentListFilterSelectors.topicSelected = e.target.value; 4807 if (!contentListInvestmentStewYearSelected) { 4808 contentListFilterSelectors.firstSelected = "topic-" + e.target.value; 4809 } 4810 contentListInvestmentStewTopicSelected = true; 4811 if (e.target.value === "allFiltering") { 4812 delete contentListFilterSelectors['topicSelected']; 4813 contentListInvestmentStewTopicSelected = false;
4814 } 4815 filterResultsInvestmentStew(); 4816 }); 4817 } 4818}; 4819 4820const filterInvestmentStewYearFunctionality = function filterInvestmentStewYearFunctionality() { 4821 if (contentListElement.querySelector('.cmp-contentList__filterInvestmentStewYearOptions') != null) { 4822 contentListElement.querySelector('.cmp-contentList__filterInvestmentStewYearOptions').addEventListener('change', (e) => { 4823 contentListFilterSelectors.yearSelected = parseInt(e.target.value); 4824 if (!contentListInvestmentStewTopicSelected) { 4825 contentListFilterSelectors.firstSelected = "year-" + parseInt(e.target.value); 4826 } 4827 contentListInvestmentStewYearSelected = true; 4828 if (e.target.value === "allFiltering") { 4829 delete contentListFilterSelectors['yearSelected']; 4830 contentListInvestmentStewYearSelected = false; 4831 } 4832 filterResultsInvestmentStew(); 4833 }); 4834 } 4835}; 4836/*******************************************************************************************************************************/ 4837/************************************************ SORT *************************************************************************/ 4838/*******************************************************************************************************************************/ 4839const sortContent = function sortContent(arrayToSort, sortOrder) { 4840 contentListCurrentItems = contentListShowingElements; 4841 contentListElement.querySelector('.cmp-contentList__resultsEntries').classList.toggle('slideUp'); 4842 while (contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild) { 4843 contentListElement.querySelector('.cmp-contentList__resultsEntries').removeChild(contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild); 4844 } 4845 if (sortOrder === "newest") { 4846 arrayToSort.sort(function (a, b) { 4847 let dateFirst = new Date(a.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getTime(); 4848 let dateSecond = new Date(b.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getTime(); 4849 return dateSecond - dateFirst; 4850 }); 4851 } else if (sortOrder === "oldest") { 4852 arrayToSort.sort(function (a, b) { 4853 let dateFirst = new Date(a.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getTime(); 4854 let dateSecond = new Date(b.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getTime(); 4855 return dateFirst - dateSecond; 4856 }); 4857 } 4858 arrayToSort.forEach((element) => { 4859 element.querySelector('.cmp-contentListEntry').style.display = "block"; 4860 contentListElement.querySelector('.cmp-contentList__resultsEntries').appendChild(element); 4861 }); 4862 hideElements(arrayToSort); 4863 showLoadMoreButton(arrayToSort); 4864 setTimeout(() => { 4865 contentListElement.querySelector('.cmp-contentList__resultsEntries').style.height = contentListElement.querySelector('.cmp-contentList__resultsEntries').outerHeight + +"px"; 4866 contentListElement.querySelector('.cmp-contentList__resultsEntries').classList.toggle('slideUp'); 4867 }, 500); 4868}; 4869 4870const sortFunctionality = function sortFunctionality() { 4871 if (contentListElement.querySelector('.cmp-contentList__sortOptions') != null) { 4872 contentListElement.querySelector('.cmp-contentList__sortOptions').addEventListener('change', (e) => { 4873 let updatedOrder; 4874 if (contentListFilteredList.length > 0) { 4875 updatedOrder = contentListFilteredList; 4876 sortContent(updatedOrder, e.target.value); 4877 } else { 4878 updatedOrder = contentListUnfilteredList; 4879 sortContent(updatedOrder, e.target.value); 4880 } 4881 }); 4882 } 4883}; 4884 4885if (contentListArray != null) { 4886 contentListArray.forEach((contentListEntry) => { 4887 //initialize variables 4888 contentListElement = contentListEntry; 4889 contentListListElements = [...contentListElement.querySelectorAll('.cmp-contentList__resultsEntries .contentListEntry')]; 4890 contentListUnfilteredList = contentListListElements; 4891 contentListPressroomFiltering = contentListElement.querySelector('.cmp-contentList__filteringPressroomContainer'); 4892 contentListInvestmentStewFiltering = contentListElement.querySelector('.cmp-contentList__filteringSortingInvestmentStewContainer'); 4893 4894 // Get configured items per page (default to 10 for backward compatibility) 4895 let resultsEntryElement = contentListElement.querySelector('.cmp-contentList__resultsEntries'); 4896 if (resultsEntryElement != null) { 4897 let itemsPerPage = resultsEntryElement.getAttribute('data-items-per-page'); 4898 contentListShowingElements = itemsPerPage ? parseInt(itemsPerPage) : 10; 4899 contentListNextToShow = contentListShowingElements; 4900 } 4901 4902 // order the results by the newest date 4903 contentListUnfilteredList.sort(function (a, b) { 4904 let dateFirst = new Date(a.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getTime(); 4905 let dateSecond = new Date(b.querySelector('.cmp-contentListEntry__date').getAttribute('data-result-date')).getTime(); 4906 return dateSecond - dateFirst; 4907 }); 4908 allResults(); 4909 //create the filtering by topic dropdown and sorting it alphabetically 4910 createFilterDropdown(contentListUnfilteredList); 4911 let resultsEntry = contentListElement.querySelector('.cmp-contentList__resultsEntries'); 4912 if (resultsEntry != null) { 4913 contentListWCMMode = resultsEntry.getAttribute('data-wcmMode-edit'); 4914 contentListLoadMore = contentListElement.querySelector('.cmp-contentList__loadM
4914oreButtonContainer'); 4915 contentListCurrentItems = contentListShowingElements; 4916 loadMoreResultsOnClick(); 4917 //hide all the elements except the first 10 4918 hideElements(contentListUnfilteredList); 4919 displayNumberOfResults(contentListUnfilteredList); 4920 showLoadMoreButton(contentListUnfilteredList); 4921 if (contentListElement.querySelector('.cmp-contentList__filteringSortingContainer') != null) { 4922 addOptionsToSelect('cmp-contentList__filterOptions-option', '.cmp-contentList__filterOptions', contentListSelectOptionsList); 4923 contentListElement.querySelector('.cmp-contentList__filterOptionsContainer').classList.remove('cmp-contentList__hideElements'); 4924 contentListElement.querySelector('.cmp-contentList__sortOptionsContainer').classList.remove('cmp-contentList__hideElements'); 4925 } 4926 //Pressroom 4927 if (contentListElement.querySelector('.cmp-contentList__filteringPressroomContainer') != null) { 4928 addOptionsToSelect('cmp-contentList__filterPressroomOptions-option', '.cmp-contentList__filterPressroomOptions', contentListSelectOptionsList); 4929 contentListElement.querySelector('.cmp-contentList__filterPressroomOptionsContainer').classList.remove('cmp-contentList__hideElements'); 4930 } 4931 //Investment stewardship filtering 4932 if (contentListElement.querySelector('.cmp-contentList__filteringSortingInvestmentStewContainer') != null) { 4933 addOptionsToSelect('.cmp-contentList__filterInvestmentStewTopicOptions-option', '.cmp-contentList__filterInvestmentStewTopicOptions', contentListSelectOptionsList); 4934 addOptionsToSelect('.cmp-contentList__filterInvestmentStewYearOptions-option', '.cmp-contentList__filterInvestmentStewYearOptions', contentListInvestmentStewYearSelectOptionsList); 4935 contentListElement.querySelector('.cmp-contentList__filterOptionsYearContainer').classList.remove('cmp-contentList__hideElements'); 4936 contentListElement.querySelector('.cmp-contentList__filterOptionsTopicContainer').classList.remove('cmp-contentList__hideElements'); 4937 } 4938 if (contentListElement.querySelector('.cmp-contentList__resultsContainer') != null) { 4939 contentListElement.querySelector('.cmp-contentList__resultsContainer').classList.remove('cmp-contentList__hideElements'); 4940 } 4941 contentListElement.querySelector('.cmp-contentList__resultsEntries').classList.remove('cmp-contentList__hideElements'); 4942 contentListElement.querySelector('.cmp-contentList__loadMoreButtonContainer').classList.remove('cmp-contentList__hideElements'); 4943 /*********************************************** FILTER *********************************************/ 4944 filterFunctionality(); 4945 /*********************************************** FILTER PRESSROOM*********************************************/ 4946 filterPressroomFunctionality(); 4947 /*********************************************** FILTER Investment Stew***************************************/ 4948 filterInvestmentStewTopicFunctionality(); 4949 filterInvestmentStewYearFunctionality(); 4950 /*************************************** SORT **********************************************************/ 4951 sortFunctionality(); 4952 } 4953 }); 4954} 4955 4956const reapplySelector = function reapplyFiltering(selector) { 4957 if (document.querySelector(selector).value != 'allFiltering' && document.querySelector(selector) != null) { 4958 contentListFilteredList = [...contentListElement.querySelectorAll('.cmp-contentList__resultsEntries .contentListEntry')]; 4959 while (contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild) { 4960 contentListElement.querySelector('.cmp-contentList__resultsEntries').removeChild(contentListElement.querySelector('.cmp-contentList__resultsEntries').firstChild); 4961 } 4962 filteredResults(contentListFilteredList, document.querySelector(selector).value); 4963 } 4964} 4965 4966const reapplyInvestmentStewardshipFilters = function reapplyInvestStewFilter() { 4967 contentListUnfilteredList = [...contentListElement.querySelectorAll('.cmp-contentList__resultsEntries .contentListEntry')]; 4968 if (document.querySelector('.cmp-contentList__filterInvestmentStewTopicOptions').value != 'allFiltering' && document.querySelector('.cmp-contentList__filterInvestmentStewYearOptions').value == 'allFiltering') { 4969 contentListInvestmentStewTopicSelected = true; 4970 contentListFilterSelectors.topicSelected = document.querySelector('.cmp-contentList__filterInvestmentStewTopicOptions').value; 4971 filterResultsInvestmentStew(); 4972 } 4973 if (document.querySelector('.cmp-contentList__filterInvestmentStewYearOptions').value != 'allFiltering' && document.querySelector('.cmp-contentList__filterInvestmentStewTopicOptions').value == 'allFiltering') { 4974 contentListInvestmentStewYearSelected = true; 4975 contentListFilterSelectors.yearSelected = parseInt(document.querySelector('.cmp-contentList__filterInvestmentStewYearOptions').value); 4976 filterResultsInvestmentStew(); 4977 } 4978 4979 if (document.querySelector('.cmp-contentList__filterInvestmentStewTopicOptions').value != 'allFiltering' && 4980 document.querySelector('.cmp-contentList__filterInvestmentStewYearOptions').value != 'allFiltering') { 4981 contentListInvestmentStewTopicSelected = true; 4982 contentListFilterSelectors.topicSelected = document.querySelector('.cmp-contentList__filterInvestmentStewTopicOptions').value; 4983 contentListInvestmentStewYearSelected = true; 4984 contentListFilterSelectors.yearSelected = parseInt(document.querySelector('.cmp-contentList__filterInvestmentStewYearOptions').value); 4985 filterResultsInvestmentStew(); 4986 } 4987} 4988 4989$(document).ready(() => { 4990 if (document.querySelector('.cmp-contentList__filterOptions') != null) { 4991 reapplySelector('.cmp-contentList__filterOptions'); 4992 } 4993 if (document.querySelector('.cmp-contentList__filterPressroomOptions') != null) { 4994 reapplySelector('.cmp-contentList__filterPressroomOptions'); 4995 } 4996 4997 if (document.querySelector('.cmp-contentList__filterInvestmentStewTopicOptions') != null && document.querySelector('.cmp-contentList__filterInvestmentStewYearOptions') != null) { 4998 reapplyInvestmentStewardshipFilters(); 4999 } 5000}); 5001 5002 5003 5004(function () { 5005 "use strict"; 5006 5007 var showHideElements = {}; 5008 5009 showHideElements.contactsPosition = resource.properties["contactModuleSectionTitle"] == null ? "col-sm-12 col-md-5 offset-md-6" : "col-sm-12 col-md-5 offset-md-1"; 5010 5011 if(resource.properties["stackContacts"] == 'true') { 5012 showHideElements.stackContacts = "col-12 col-sm-6 col-md-12 col-lg-6 contacts-two-col-padding"; 5013 }else if(resource.properties["stackContacts"] == 'false') { 5014 showHideElements.stackContacts = "col-12 col-sm-12"; 5015 } 5016 return showHideElements; 5017}); 5018(function() { 5019 'use strict'; 5020 5021 const PANEL_EXPANDED_CLASS = 'cmp-collapsibledrawer__panel--expanded'; 5022 const PANEL_HIDDEN_CLASS = 'cmp-collapsibledrawer__panel--hidden'; 5023 const PANEL_ICON_EXPANDED_CLASS = 'cmp-collapsibledrawer__panel-icon--expanded'; 5024 const PANEL_ICON_HOVER_CLASS = 'cmp-collapsibledrawer__panel-icon--hover'; 5025 5026 $(document).ready(function() { 5027 if ($('.cmp-collapsibledrawer').length > 0) { 5028 if ($('.cmp-collapsibledrawer').data('wcm-mode') != 'EDIT') { 5029 initializePanels(); 5030 scrollToPanel(getUrlParameter('drawer')); 5031 } 5032 } 5033 }); 5034 5035 $('.cmp-collapsibledrawer__button').hover( 5036 function handlerIn() { 5037 if (isPanelExpanded($(this))) { 5038 $(this).find('.cmp-collapsibledrawer__panel-icon').removeClass(PANEL_ICON_EXPANDED_CLASS); 5039 $(this).find('.cmp-collapsibledrawer__panel-icon').addClass(PANEL_ICON_HOVER_CLASS); 5040 } else { 5041 addPanelNumberHover($(this)); 5042 } 5043 }, 5044 function handlerOut() { 5045 $(this).find('.cmp-collapsibledrawer__panel-icon').removeClass(PANEL_ICON_HOVER_CLASS); 5046 if (isPanelExpanded($(this))) { 5047 $(this).find('.cmp-collapsibledrawer__panel-icon').addClass(PANEL_ICON_EXPANDED_CLASS); 5048 } else { 5049 $(this).find('.cmp-collapsibledrawer__panel-icon').removeClass(PANEL_ICON_EXPANDED_CLASS); 5050 removePanelNumberHover($(this)); 5051 } 5052 } 5053 ); 5054 5055 $('.cmp-collapsibledrawer__panel').on('transitionend', function() { 5056 if (isPanelCollapsed($(this))) { 5057 $(this).parent().prev().find('.cmp-collapsibledrawer__panel-icon').removeClass(PANEL_ICON_EXPANDED_CLASS); 5058 } else { 5059 $(this).parent().prev().find('.cmp-collapsibledrawer__panel-icon').addClass(PANEL_ICON_EXPANDED_CLASS); 5060 } 5061 }); 5062 5063 $('.cmp-collapsibledrawer__button').click(function(clickEvent) { 5064 if (isPanelExpanded($(this))) { 5065 collapsePanel($(this)); 5066 } else { 5067 scrollToButton($(this)); 5068 expandPanel($(this)); 5069 } 5070 }); 5071 5072 function initializePanels() { 5073 // set height of each expanded panel for transition to work when collapsing drawer for first time 5074 document.querySelectorAll('.cmp-collapsibledrawer__panel--expanded').forEach((element) => { 5075 element.style.height = element.querySelector('.aem-Grid').offsetHeight + 'px'; 5076 let observed = element.querySelector('.aem-Grid'); 5077 let resizeObserver = new ResizeObserver(() => { 5078 element.style.height = observed.offsetHeight + 'px'; 5079 }); 5080 resizeObserver.observe(observed); 5081 5082 }); 5083 5084 document.querySelectorAll('.cmp-collapsibledrawer__panel--hidden').forEach((element) => { 5085 element.hidden = true; 5086 }); 5087 5088 } 5089 5090 function subpageViewedEventonPageLoad(document) { 5091 let panelTitleOnPageLoad = document.find('.cmp-collapsibledrawer__panel-title').html(); 5092 window.dataLayer = window.dataLayer || []; 5093 window.dataLayer.push({ 5094 "event": "subPageViewed", //e166 5095 "subPage": { 5096 "subPageID": panelTitleOnPageLoad, //v105 5097 "spClicked": "false" 5098 } 5099 }); 5100 } 5101 5102 function addPanelNumberHover(collapsibleDrawerButton) { 5103 let panelNumber = collapsibleDrawerButton.find('.cmp-collapsibledrawer__panel-number'); 5104 if (panelNumber.length > 0) { 5105 panelNumber.attr('class', panelNumber.attr('class') + '--hover'); 5106 } 5107 } 5108 5109 function removePanelNumberHover(collapsibleDrawerButton) { 5110 let panelNumber = collapsibleDrawerButton.find('.cmp-collapsibledrawer__panel-number'); 5111 if (panelNumber.length > 0) { 5112 panelNumber.attr('class', panelNumber.attr('class').replace('--hover', '')); 5113 } 5114 } 5115 5116 function addPanelNumberExpanded(collapsibleDrawerButton) { 5117 let panelNumber = collapsibleDrawerButton.find('.cmp-collapsibledrawer__panel-number'); 5118 if (panelNumber.length > 0) { 5119 panelNumber.attr('class', panelNumber.attr('class').replace('--hover', '') + '--expanded'); 5120 } 5121 } 5122 5123 function removePanelNumberExpanded(collapsibleDrawerButton) { 5124 let panelNumber = collapsibleDrawerButton.find('.cmp-collapsibledrawer__panel-number'); 5125 if (panelNumber.length > 0) { 5126 panelNumber.attr('class', panelNumber.attr('class').replace('--expanded', '')); 5127 } 5128 } 5129 5130 function isPanelExpanded(collapsibleDrawerButton) { 5131 return collapsibleDrawerButton.parent().parent().next().css('height') != '0px'; 5132 } 5133 5134 function isPanelCollapsed(collapsibleDrawerPanel) { 5135 return collapsibleDrawerPanel.css('height') == '0px'; 5136 } 5137 5138 function collapsePanel(collapsibleDrawerButton) { 5139 let panel = collapsibleDrawerButton.parent().parent().next().children(); 5140 panel.css('height', 0 + 'px'); 5141 panel.removeClass(PANEL_EXPANDED_CLASS); 5142 panel.addClass(PANEL_HIDDEN_CLASS); 5143 collapsibleDrawerButton.find('.cmp-collapsibledrawer__panel-icon').removeClass(PANEL_ICON_EXPANDED_CLASS); 5144 removePanelNumberExpanded(collapsibleDrawerButton); 5145 setTimeout(() => { 5146 panel[0].hidden = true; 5147 }, 800); 5148 } 5149 5150 function expandPanel(collapsibleDrawerButton) { 5151 let panel = collapsibleDrawerButton.parent().parent().next().children(); 5152 panel[0].hidden = false;
5153 let observed = panel[0].querySelector('.aem-Grid'); 5154 let resizeObserver = new ResizeObserver(() => { 5155 panel[0].style.height = observed.offsetHeight + 'px'; 5156 }); 5157 resizeObserver.observe(observed); 5158 panel.addClass(PANEL_EXPANDED_CLASS); 5159 panel.removeClass(PANEL_HIDDEN_CLASS); 5160 collapsibleDrawerButton.find('.cmp-collapsibledrawer__panel-icon').addClass(PANEL_ICON_EXPANDED_CLASS); 5161 addPanelNumberExpanded(collapsibleDrawerButton); 5162 subpageViewedLog(collapsibleDrawerButton); 5163 } 5164 5165 function subpageViewedLog(collapsibleDrawerButton) { 5166 let panelTitleOnExpand = collapsibleDrawerButton.find('.cmp-collapsibledrawer__panel-title').html(); 5167 window.dataLayer = window.dataLayer || []; 5168 window.dataLayer.push({ 5169 "event": "subPageViewed", //e112 5170 "subPage": { 5171 "subPageID": panelTitleOnExpand, //v105 5172 "spClicked": "true" 5173 } 5174 }); 5175 } 5176 5177 function scrollToButton(collapsibleDrawerButton) { 5178 let scrollToPosition = collapsibleDrawerButton.prev().offset().top; 5179 $('html,body').animate({ 5180 scrollTop: scrollToPosition 5181 }, 500); 5182 } 5183 5184 function scrollToPanel(panelNumber) { 5185 if (panelNumber != null) { 5186 let panelNumberCount = 1; 5187 $('.cmp-collapsibledrawer__button').each(function() { 5188 if (isVisible($(this))) { 5189 if (panelNumberCount == panelNumber) { 5190 scrollToButton($(this)); 5191 if (!isPanelExpanded($(this))) { 5192 expandPanel($(this)); 5193 } 5194 } 5195 panelNumberCount++; 5196 } 5197 }); 5198 } 5199 } 5200 5201 function getUrlParameter(parameter) { 5202 let urlParams = window.location.search; 5203 if (urlParams != null) { 5204 let paramsList = urlParams.substring(1).split('&'); 5205 for (let i = 0; i < paramsList.length; i++) { 5206 let param = paramsList[i].split('='); 5207 if (param[0] === parameter) { 5208 return param[1]; 5209 } 5210 } 5211 } 5212 } 5213 5214 function isVisible(element) { 5215 return element.css('height') !== '0px'; 5216 } 5217})() 5218const PANEL_NUMBER_EXPANDED_CLASS = 'cmp-accordion__panel-number--expanded'; 5219const PANEL_ICON_EXPANDED_CLASS = 'cmp-accordion__panel-icon--expanded'; 5220const PANEL_ICON_HOVER_CLASS = 'cmp-accordion__panel-icon--hover'; 5221 5222$('.cmp-accordion__button').click(function(clickEvent) { 5223 if (isPanelExpanded($(this))) { 5224 collapseAllPanels(); 5225 } else { 5226 scrollToButton($(this), clickEvent); 5227 collapseAllPanels(); 5228 expandPanel($(this)); 5229 } 5230}); 5231 5232$('.cmp-accordion__button').hover( 5233 function handlerIn() { 5234 if (isPanelExpanded($(this))) { 5235 $(this).find('.cmp-accordion__panel-icon').removeClass(PANEL_ICON_EXPANDED_CLASS); 5236 $(this).find('.cmp-accordion__panel-icon').addClass(PANEL_ICON_HOVER_CLASS); 5237 } 5238 }, 5239 function handlerOut() { 5240 $(this).find('.cmp-accordion__panel-icon').removeClass(PANEL_ICON_HOVER_CLASS); 5241 if (isPanelExpanded($(this))) { 5242 $(this).find('.cmp-accordion__panel-icon').addClass(PANEL_ICON_EXPANDED_CLASS); 5243 } else { 5244 $(this).find('.cmp-accordion__panel-icon').removeClass(PANEL_ICON_EXPANDED_CLASS); 5245 } 5246 } 5247); 5248 5249$('.cmp-accordion__panel').on('transitionend', function() { 5250 if (isPanelCollapsed($(this))) { 5251 $(this).prev().find('.cmp-accordion__panel-icon').removeClass(PANEL_ICON_EXPANDED_CLASS); 5252 } else { 5253 $(this).prev().find('.cmp-accordion__panel-icon').addClass(PANEL_ICON_EXPANDED_CLASS); 5254 } 5255}); 5256 5257function isPanelExpanded(accordionButton) { 5258 return accordionButton.parent().next().css('height') != '0px'; 5259} 5260 5261function isPanelCollapsed(accordionPanel) { 5262 return accordionPanel.css('height') == '0px'; 5263} 5264 5265function isAnyPanelExpanded() { 5266 return $('.cmp-accordion__panel').filter( 5267 function () {
5268 return (this.style.height != '0px') ? this : null; 5269 }).length > 0; 5270} 5271 5272function collapseAllPanels() { 5273 $('.cmp-accordion__panel').attr('style','height: 0px'); 5274 $('.cmp-accordion__panel-number').removeClass(PANEL_NUMBER_EXPANDED_CLASS); 5275 $('.cmp-accordion__panel-icon').removeClass(PANEL_ICON_EXPANDED_CLASS); 5276} 5277 5278function expandPanel(accordionButton) { 5279 let panelHeight = accordionButton.parent().next()[0].scrollHeight + 'px'; 5280 accordionButton.parent().next().css('height', panelHeight); 5281 accordionButton.find('.cmp-accordion__panel-number').addClass(PANEL_NUMBER_EXPANDED_CLASS); 5282 accordionButton.find('.cmp-accordion__panel-icon').addClass(PANEL_ICON_EXPANDED_CLASS); 5283} 5284 5285function scrollToButton(accordionButton, clickEvent) { 5286 let scrollToPosition = accordionButton.prev().offset().top; 5287 if (isAnyPanelExpanded()) { 5288 let clickPositionY = clickEvent.pageY; 5289 let expandedPanelPositionY = $('.cmp-accordion__panel--expanded')[0].offsetTop; 5290 if (clickPositionY > expandedPanelPositionY) { 5291 let openPanelHeight = $('.cmp-accordion__panel--expanded')[0].scrollHeight; 5292 scrollToPosition = scrollToPosition - openPanelHeight; 5293 } 5294 } 5295 $('html,body').animate({ 5296 scrollTop: scrollToPosition 5297 }, 500); 5298} 5299(function () { 5300 'use strict'; 5301 5302 let chartsMap = new Map(); 5303 5304 $(document).ready(function() { 5305 renderCharts(); 5306 }) 5307 5308 function renderCharts() { 5309 $("[id^='chart-").not('[id$="legend"]').each(function(index) { 5310 let chart; 5311 let chartId = $(this).attr('id'); 5312 let legendId = $(this).attr('id') + '-legend'; 5313 let chartConfig = $(this).data('chartjson'); 5314 let isLazyLoadEnabled = $(this).data('enable-lazy-load'); 5315 let isDynamicDateAxisRangeEnabled = $(this).data('enable-dynamic-date-axis-range'); 5316 5317 if (chart) { 5318 chart.dispose(); 5319 } 5320 if (chartConfig != null) { 5321 am4core.addLicense("CH274465790"); 5322 am4core.useTheme(am4themes_animated); 5323 5324 if (isLazyLoadEnabled) { 5325 am4core.options.queue = true; 5326 am4core.options.onlyShowOnViewport = true; 5327 } else { 5328 am4core.options.queue = false; 5329 am4core.options.onlyShowOnViewport = false; 5330 } 5331 5332 chart = am4core.createFromConfig(JSON.parse(JSON.stringify(chartConfig)), chartId); 5333 5334 if (isDynamicDateAxisRangeEnabled) { 5335 addDynamicDateAxisRange(chart); 5336 } 5337 if (chart.legend != null) { 5338 configureLegend(chart, legendId); 5339 } 5340 chartsMap.set(chartId, chart); 5341 } 5342 }); 5343 } 5344 5345 function addDynamicDateAxisRange(chart) { 5346 let dateAxis = chart.xAxes.values[0]; 5347 5348 let minRange = dateAxis.axisRanges.create(); 5349 minRange.maxPosition = 1; 5350 minRange.minPosition = 0; 5351 minRange.label.horizontalCenter = "left" 5352 minRange.label.paddingLeft = 0; 5353 5354 let maxRange = dateAxis.axisRanges.create(); 5355 maxRange.maxPosition = 1; 5356 maxRange.minPosition = 0; 5357 maxRange.label.horizontalCenter = "right" 5358 maxRange.label.paddingRight = 0; 5359 5360 dateAxis.events.on("inited",updateAxisRanges); 5361 dateAxis.events.on("startendchanged", updateAxisRanges); 5362 5363 function updateAxisRanges() { 5364 let labelFormat = "MMM yyyy"; 5365 5366 minRange.value = dateAxis.min + dateAxis.start * (dateAxis.max - dateAxis.min); 5367 minRange.label.text = dateAxis.dateFormatter.format(minRange.value, labelFormat); 5368 5369 maxRange.value = dateAxis.min + dateAxis.end * (dateAxis.max - dateAxis.min - 1000); 5370 maxRange.label.text = dateAxis.dateFormatter.format(maxRange.value, labelFormat); 5371 } 5372 } 5373 5374 function addLegendCheckmarks(chart) { 5375 let marker = chart.legend.markers.template; 5376 let checkbox = marker.createChild(am4core.Image); 5377 checkbox.width = marker.width - 6; 5378 checkbox.height = marker.height - 6; 5379 checkbox.dx = 3; 5380 checkbox.dy = 3; 5381 checkbox.href = "/etc.clientlibs/corporatesite/clientlibs/clientlib-site/resources/icons/button-icons/check.svg"; 5382 5383 let checkboxActiveState = checkbox.states.create("active"); 5384 checkboxActiveState.properties.opacity = 0; 5385 } 5386 5387 function configureLegend(chart, legendId) {
5388 addLegendCheckmarks(chart); 5389 5390 let legendContainer = am4core.create(legendId, am4core.Container); 5391 legendContainer.width = am4core.percent(100); 5392 legendContainer.height = am4core.percent(100); 5393 chart.legend.parent = legendContainer; 5394 5395 chart.events.on("datavalidated", resizeLegends); 5396 chart.events.on("maxsizechanged", resizeLegends); 5397 chart.legend.events.on("datavalidated", resizeLegends); 5398 chart.legend.events.on("maxsizechanged", resizeLegends); 5399 5400 function resizeLegends(event) { 5401 chartsMap.forEach((chart, chartId) => { 5402 let newHeight = chart.legend.contentHeight + 'px'; 5403 $('#' + chartId + '-legend').css('height', newHeight); 5404 }); 5405 } 5406 } 5407})(); 5408const carousel = $('.cmp-corpCarousel-tile__carouselContainer'); 5409const settings = { 5410 slidesToShow: 4, 5411 centerMode: true, 5412 centerPadding: 30, 5413 draggable: false, 5414 responsive: [ 5415 { 5416 breakpoint: 1023, 5417 settings: { 5418 centerMode: true, 5419 centerPadding: 20, 5420 slidesToShow: 3, 5421 } 5422 }, 5423 { 5424 breakpoint: 600, 5425 settings: "unslick" 5426 } 5427 ] 5428} 5429const sl = carousel.slick(settings); 5430 5431function loadCarousel() { 5432 carousel.not('.slick-initialized').slick(settings); 5433} 5434 5435$(document).ready(function() { 5436 loadCarousel(); 5437}); 5438 5439// Reinitialize slick after unslick breakpoint 5440$(window).on('resize', function() { 5441 if( $(window).width() > 600 && !sl.hasClass('slick-initialized')) { 5442 loadCarousel(); 5443 } 5444}); 5445(function() { 5446 'use strict'; 5447 5448 const PLAY_ICON = 'cmp-callOut__playIcon'; 5449 const PAUSE_ICON = 'cmp-callOut__pauseIcon'; 5450 5451 $('.cmp-callOut__playButton').click(function() { 5452 let callOutImage = $(this).parent().siblings('.cmp-callOut-img'); 5453 let staticImg = callOutImage.data('static-img-src'); 5454 let animatedGif = callOutImage.data('animated-gif-src'); 5455 $(this).toggleClass(PAUSE_ICON + ' ' + PLAY_ICON); 5456 if (callOutImage.attr('src') === staticImg) { 5457 callOutImage.attr('src', animatedGif); 5458 $(this).attr('aria-pressed', 'true'); 5459 } else { 5460 callOutImage.attr('src', staticImg); 5461 $(this).attr('aria-pressed', 'false'); 5462 } 5463 }); 5464 5465 $('.cmp-callOut-img').click(function() { 5466 let staticImg = $(this).data('static-img-src'); 5467 let animatedGif = $(this).data('animated-gif-src'); 5468 let playButton = $(this).siblings().children(); 5469 playButton.toggleClass(PAUSE_ICON + ' ' + PLAY_ICON); 5470 if ($(this).attr('src') === staticImg) { 5471 $(this).attr('src', animatedGif); 5472 playButton.attr('aria-pressed', 'true'); 5473 } else { 5474 $(this).attr('src', staticImg); 5475 playButton.attr('aria-pressed', 'false'); 5476 } 5477 }); 5478 5479 $('[data-animated-gif-src]').mouseenter(function() { 5480 $(this).css('cursor','pointer'); 5481 $(this).siblings().children().css('box-shadow','0 0 0 2px #FFFFFF, 0 0 0 4px #0074C9, 0 0 0 6px #FFFFFF'); 5482 }); 5483 5484 $('[data-animated-gif-src]').mouseleave(function() { 5485 $(this).siblings().children().css('box-shadow','none'); 5486 }); 5487})(); 5488 5489 5490(function () { 5491 "use strict"; 5492 5493 var showHideElements = {}; 5494 5495 showHideElements.showLogo = resource.properties["showVanguardLogo"] == 'true' ? "display: block" : "display: none"; 5496 showHideElements.showCTA = resource.properties["showCTA"] == 'true' ? "display: block" : "display: none"; 5497 5498 if(resource.properties["showVanguardLogo"] == 'true') { 5499 showHideElements.showLogo = "display: block" ; 5500 showHideElements.brandPosition = "col-sm-4 offset-sm-2 offset-md-2"; 5501 }else if(resource.properties["showVanguardLogo"] == 'false') { 5502 showHideElements.showLogo = "display: none"; 5503 showHideElements.brandPosition = "col-sm-4" 5504 } 5505 5506 return showHideElements; 5507 5508}); 5509(function() { 5510 'use strict'; 5511 $("#subscriptionLinkAuthorsDisplayComponentOdd").on("click", () => { 5512 dataLayerUploadAuthorDisplay(); 5513 }); 5514 5515 $("#subscriptionLinkAuthorsDisplayComponentEven").on("click", () => { 5516 //Use to track the conversion of user 5517 dataLayerUploadAuthorDisplay(); 5518 }); 5519 5520 5521 $(".authorReferencePageLinkClass").on("click", (event) => { 5522 window.dataLayer = window.dataLayer || []; 5523 5524 const doc = event.currentTarget; 5525 let authorId = doc.id; 5526 authorId = authorId.substring(0, authorId.length-1) +" "+ (parseInt(authorId.substring((authorId.length-1)))+1) 5527 5528 window.dataLayer.push({ 5529 "event": "callToAction", //e111 5530 "callToAction": { 5531 "location": $(document).find("title")[0].text, //v103 5532 "ctaName": authorId +"-"+ doc.innerText, //v104 5533 "ctaType": "Text Link" //v111 5534 } 5535 }); 5536 }); 5537 5538 function dataLayerUploadAuthorDisplay() { 5539 window.dataLayer = window.dataLayer || []; 5540 window.dataLayer.push({ 5541 "event": "callToAction", //e111 5542 "callToAction": { 5543 "location": $(document).find("title")[0].text, //v103 5544 "ctaName": "Subscribe now", //v104 5545 "ctaType": "Text Link" //v111 5546 } 5547 }); 5548 } 5549})(); 5550(() => { 5551 5552 $("#articleSubscriptionPrivacyPolicyLink").on("click", () => { 5553 //Use to track the conversion of user 5554 window.dataLayer = window.dataLayer || []; 5555 window.dataLayer.push({ 5556 "event" : "callToAction", //e111 5557 "callToAction": { 5558 "location": $(document).find("title")[0].text, //v103 5559 "ctaName": "Privacy Policy", //v104 5560 "ctaType": "Text Link" //v111 5561 } 5562 }); 5563 }); 5564 5565 $("#articleSubscriptionSubscribeButton").click(() => { 5566 5567 window.dataLayer = window.dataLayer || []; 5568 window.dataLayer.push({ 5569 "event" : "callToAction", //e111 5570 "callToAction": { 5571 "location": $(document).find("title")[0].text, //v103 5572 "ctaName": 'Subscribe Footer', //v104 5573 "ctaType": 'custom button' //v111 5574 } 5575 }); 5576 5577 5578 window.location.assign(`${origin}/content/corporatesite/us/en/corp/subscription-dashboard.html`); 5579 }); 5580 5581})(); 5582 5583$(document).ready(function(){ 5584 $('.close-alert').click(function(){ 5585 $(this).parent('.alert-title-row').parent('.alert-container').parent('.cmp-alert').parent('.alert').hide(); 5586 }); 5587 5588 let isAuthorMode = $('.alert-title-row').data('isauthor'); 5589 let alertEndDate = $('.cmp-alert').data('alertenddate'); 5590 5591 if (isAuthorMode === false) { 5592 let todayDate = new Date(); 5593 let dd = String(todayDate. getDate()). padStart(2, '0');
5594 let mm = String(todayDate. getMonth() + 1). padStart(2, '0'); //January is 0! 5595 let yyyy = todayDate.getFullYear(); 5596 todayDate = yyyy + '-' + mm + '-' + dd; 5597 if (todayDate > alertEndDate) { 5598 $('.cmp-alert').remove(); 5599 } 5600 } 5601}); 5602const fetchAlerts = function fetchAlerts(){ 5603 try { 5604 $.ajax({ 5605 url:'/api/corporatesite/alerts', 5606 method:'GET', 5607 dataType: 'json', 5608 success: function (data){ 5609 if (data.status === 401 || !data || data.noResource) { 5610 return; 5611 } 5612 let alertsComponent = document.querySelector('.alerts'); 5613 5614 let alertExpired = data.isExpired; 5615 let existingAlert = alertsComponent.querySelector('.cmp-alerts'); 5616 5617 // if the alert does not exist in the dom and it is not expired, create the alert component 5618 if (!existingAlert && !alertExpired){ 5619 createAlertHTML(data, alertsComponent); 5620 } 5621 5622 if (existingAlert && !alertExpired){ 5623 5624 //update alert component with new data 5625 updateAlertComponent(existingAlert, data); 5626 } 5627 5628 // When the content author deletes the alert Component, remove alert from page 5629 if(existingAlert && (data.noResource || data.isExpired)) { 5630 existingAlert.remove(); 5631 } 5632 }, 5633 error:function (xhr,status,error){ 5634 console.debug("Error: " + error + "with status :" + status + "xhr : " + xhr); 5635 logClientError("WARN", "Alerts API fetch failed: " + status + " " + error, "dynamicAlerts.js"); 5636 } 5637 }); 5638 }catch (e){ 5639 console.debug(e); 5640 logClientError("WARN", "Alerts fetch exception: " + (e && e.message ? e.message : String(e)), "dynamicAlerts.js"); 5641 } 5642} 5643 5644const createActionContainer = function createActionContainer(ctaPath, ctaName, ctaArrowStyling) { 5645 let ctaLocation = document.title.split(' | ')[0].trim(); 5646 return `<div class="cta-link-arrow-container"> 5647 <div class="row"> 5648 <div class="col-12"> 5649 <a href="${ctaPath}" class="${ctaArrowStyling}" data-ctaname="${ctaName}" data-ctalocation="${ctaLocation}" data-ctatype="Text Link" data-ctapath="${ctaPath}"> 5650 <span class="link-text">${ctaName}</span> 5651 <i class="arrow p-ctalink-arrow-14"></i> 5652 </a> 5653 </div> 5654 </div> 5655 </div> 5656 `; 5657} 5658 5659const createAlertHTML = function createAlertHTML(data, alertsComponent) { 5660 alertsComponent.innerHTML = ` 5661 <div class="cmp-alerts" style="background-color: ${data.alertBackgroundColor};"> 5662 <div class="container-fluid alert-container"> 5663 <div class="row alert-title-row" data-isauthor="false"> 5664 <div class="col-11 col-md-10"> 5665 <p class="alert-title-copy" style="color: ${data.fontColor}"> 5666 ${data.alertHeadline} 5667 </p> 5668 </div> 5669 </div> 5670 </div> 5671 </div> 5672 `; 5673 5674 if (data.alertSubHeadline != null && data.alertSubHeadline !== '') { 5675 let alertSubHeadline = document.createElement('p'); 5676 alertSubHeadline.className = 'alert-sub-copy'; 5677 alertSubHeadline.innerHTML = data.alertSubHeadline; 5678 alertSubHeadline.style.color = data.fontColor; 5679 alertsComponent.querySelector('.alert-title-row .col-11').appendChild(alertSubHeadline); 5680 } 5681 5682 if(data.alertCtaToggle === 'true' ) { 5683 let actionContainer = document.createElement('div'); 5684 actionContainer.className = 'alert-butn'; 5685 actionContainer.innerHTML = createActionContainer(data.alertCtaPath, data.alertCtaLabel, data.ctaArrowStyling) 5686 alertsComponent.querySelector('.alert-title-row .col-11').appendChild(actionContainer); 5687 } 5688} 5689 5690const updateAlertComponent = function updateAlertComponent(existingAlert, data) { 5691 if(existingAlert.style.backgroundColor === data.alertBackgroun
5691dColor){ 5692 existingAlert.style.backgroundColor = data.alertBackgroundColor; 5693 } 5694 let fontColor = existingAlert.getAttribute('data-fontColor') === data.fontColor ? existingAlert.getAttribute('data-fontColor') : data.fontColor; 5695 existingAlert.style.backgroundColor = data.alertBackgroundColor; 5696 let alertHeadline = existingAlert.querySelector('.alert-title-copy'); 5697 if (alertHeadline && alertHeadline.textContent !== data.alertHeadline) { 5698 alertHeadline.textContent = data.alertHeadline; 5699 alertHeadline.style.color = fontColor; 5700 } 5701 5702 let alertSubHeadline = existingAlert.querySelector('.alert-sub-copy'); 5703 if (alertSubHeadline && alertSubHeadline.textContent !== data.alertSubHeadline) { 5704 alertSubHeadline.textContent = data.alertSubHeadline; 5705 alertSubHeadline.style.color = fontColor; 5706 } 5707 5708 let actionContainer = existingAlert.querySelector('.alert-butn'); 5709 if (data.alertCtaToggle === 'true') { 5710 if (!actionContainer) { 5711 actionContainer = document.createElement('div'); 5712 actionContainer.className = 'alert-butn'; 5713 existingAlert.querySelector('.alert-title-row .col-11').appendChild(actionContainer); 5714 } 5715 actionContainer.innerHTML = createActionContainer(data.alertCtaPath, data.alertCtaLabel, data.ctaArrowStyling); 5716 } else if(data.alertCtaToggle === 'false' && actionContainer) { 5717 actionContainer.remove(); 5718 } 5719} 5720 5721 5722 5723$(document).ready(function () { 5724 if( document.querySelector(".alerts div:first-child").getAttribute("data-isauthor") === 'false' ) { 5725 setInterval(function () { 5726 fetchAlerts(); 5727 }, 5 * 60 * 1000); // Refresh every 5 minutes 5728 } 5729}); 5730$(document).ready(function(){ 5731 $('.cmp-text__phoneCall').on('click', function(event){ 5732 event.preventDefault(); 5733 if($(this).attr('data-phonenumber') != undefined){ 5734 window.open("tel: " + $(this).attr('data-phonenumber'), '_self'); 5735 } 5736 }); 5737 }); 5738$(document).ready(function () { 5739 $('.cmp-teaser--cardRegularWithBorder, .cmp-teaser--cardImageLeftWithBorder, .cmp-teaser--cardTeaserListWithBorder, .cmp-teaser__withoutActions').click((e) => { 5740 if (e.currentTarget.dataset.linkurl) { 5741 window.location.href = e.currentTarget.dataset.linkurl; 5742 } 5743 }); 5744}); 5745 5746document.addEventListener('keydown', (event) => { 5747 const focusedElement = document.activeElement; 5748 const isTeaserLink = focusedElement.classList.contains('cmp-teaser__withLink'); 5749 5750 if (isTeaserLink && event.key === 'Enter') { 5751 const linkUrl = focusedElement.getAttribute('data-linkurl'); 5752 if (linkUrl) { 5753 window.location.href = linkUrl; 5754 } 5755 } 5756}); 5757function buildThresholdList() { 5758 let thresholds = []; 5759 let numSteps = 100; 5760 for (let i = 1.0; i <= numSteps; i++) { 5761 let ratio = i / numSteps; 5762 thresholds.push(ratio); 5763 } 5764 thresholds.push(0); 5765 return thresholds; 5766} 5767 5768$(document).ready(function () { 5769 5770 let optionsAnimation = { 5771 root: null, 5772 rootMargin: '0px', 5773 threshold: buildThresholdList() 5774 5775 }; 5776 5777 let cardVisible = document.querySelector('#teaserFadeInAnimation'); 5778 let oldValue = 0; 5779 let newValue = 0; 5780 let up = false; 5781 window.addEventListener('scroll', (e) => { 5782 newValue = window.pageYOffset; 5783 if (oldValue > newValue) { 5784 up = true; 5785 } else if (oldValue < newValue) { 5786 up = false; 5787 } 5788 oldValue = newValue; 5789 }); 5790 5791 let reachedHundredScrollingDown = false; 5792 let reachedHundredScrollingUp = true; 5793 const observerForAnimation = new IntersectionObserver((entries) => { 5794 entries.forEach(entry => { 5795 5796 let parentTwoColumn = entry.target.closest('#twoColumnContainer'); 5797 if (parentTwoColumn) { 5798 let teaser = parentTwoColumn.querySelector('#leftRailContainer .cmp-teaser--cardAnimatedFadeIn .cmp-teaser'); 5799 if (!entry.isIntersecting && !up && reachedHundredScrollingDown) { 5800 teaser.style.opacity = 1; 5801 reachedHundredScrollingDown = false;
5802 } else if (!entry.isIntersecting && up && (reachedHundredScrollingDown || !reachedHundredScrollingUp)) { 5803 teaser.style.opacity = 0; 5804 reachedHundredScrollingUp = true; 5805 reachedHundredScrollingDown = false; 5806 } else if (entry.isIntersecting && !up) { 5807 if (entry.intersectionRatio >= 0 && entry.intersectionRatio < 1 && !reachedHundredScrollingDown) { 5808 teaser.style.opacity = entry.intersectionRatio; 5809 } else if (entry.intersectionRatio == 1) { 5810 teaser.style.opacity = 1; 5811 reachedHundredScrollingDown = true; 5812 } 5813 } else if (entry.isIntersecting && up) { 5814 if (entry.intersectionRatio >= 0 && entry.intersectionRatio < 1 && !reachedHundredScrollingUp) { 5815 teaser.style.opacity = entry.intersectionRatio; 5816 } else if (entry.intersectionRatio >= 0 && entry.intersectionRatio < 1 && reachedHundredScrollingDown) { 5817 teaser.style.opacity = entry.intersectionRatio; 5818 reachedHundredScrollingDown = true; 5819 } else if (entry.intersectionRatio == 1) { 5820 teaser.style.opacity = 1; 5821 reachedHundredScrollingUp = false; 5822 } 5823 } 5824 } 5825 }); 5826 }, optionsAnimation); 5827 if (cardVisible) { 5828 observerForAnimation.observe(cardVisible); 5829 } 5830}); 5831 5832const truncateToMaxCharacters = function truncateToMaxCharacters(target, maxCharacters) { 5833 let targetArray = document.querySelectorAll(target); 5834 if (targetArray.length > 0) { 5835 targetArray.forEach((targetItem) => { 5836 let stringValue = targetItem.textContent.toString().trim(); 5837 if (stringValue.length > parseInt(maxCharacters)) { 5838 targetItem.textContent = stringValue.substring(0, parseInt(maxCharacters) - 1) + "..."; 5839 } 5840 }); 5841 } 5842} 5843 5844$(document).ready(function () { 5845 const teaserSelector = ".cmp-teaser--cardImageLeft > .cmp-teaser__withImage > .cmp-teaser__content" 5846 const titleSelector = teaserSelector + " .cmp-teaser__title"; 5847 const titleSelectorWithLink = titleSelector + " > a.cmp-teaser__title-link"; 5848 const descriptionSelector = teaserSelector + " .cmp-teaser__description"; 5849 5850 truncateToMaxCharacters(titleSelectorWithLink, 67); 5851 truncateToMaxCharacters(titleSelector, 67); 5852 5853 truncateToMaxCharacters(descriptionSelector, 117); 5854}); 5855 5856$(document).ready(function () { 5857 $('.cmp-teaser__action-link').on('click', function(e) { 5858 e.preventDefault(); 5859 let ctaPath =$(this).attr('href'); 5860 if (ctaPath) { 5861 let isPdfContent = ctaPath.endsWith('.pdf'); 5862 let isCorpSiteContent = ctaPath.indexOf('/content/corporatesite/') !== -1; 5863 5864 if (!isPdfContent) { 5865 window.dataLayer = window.dataLayer || []; 5866 window.dataLayer.push({ 5867 "event": "callToAction", //e111 5868 "callToAction": { 5869 "location": $(document).find("title")[0].text, //v103 5870 "ctaName": $(this).find('.cmp-button__text').text(), //v104 5871 "ctaType": "Text Link" //v111 5872 } 5873 }); 5874 } 5875 if (isCorpSiteContent && !isPdfContent) { 5876 window.open(ctaPath, '_self'); 5877 } else { 5878 window.open(ctaPath, '_blank'); 5879 } 5880 } 5881 }); 5882}); 5883$(document).ready(function () { 5884 let teasers = document.getElementsByClassName("cmp-teaser"); 5885 Array.from(teasers).forEach(function (teaser) { 5886 let altText = teaser.dataset.alttext; 5887 if (altText) { 5888 let teaserImage = teaser.querySelector('.cmp-image__image'); 5889 teaserImage.alt = altText; 5890 } 5891 }); 5892}); 5893 5894var BUTTON_CLASSES = [ 5895 { parent: 'cmp-teaser--primaryButton', button: 'button-primary' }, 5896 { parent: 'cmp-teaser--secondaryButton', button: 'button-secondary' } 5897]; 5898 5899document.addEventListener('DOMContentLoaded', function() { 5900 document.querySelectorAll('.cmp-teaser__action-link').forEach(function(link) { 5901 var parent = link.closest('.teaser'); 5902 if (parent) { 5903 BUTTON_CLASSES.forEach(function(cfg) { 5904 if (parent.classList.contains(cfg.parent)) { 5905 link.classList.add(cfg.button); 5906 } 5907 }); 5908 } 5909 }); 5910}); 5911$(document).ready(function(){ 5912 if($('.cmp-tabs__tab').length>0){ 5913 subpageViewedEventonPageLoad(($(this))); 5914 $('.cmp-tabs__tab').on('click', function() { 5915 subpageViewedEventOnClick($(this)); 5916 }); 5917 } 5918}); 5919 5920function subpageViewedEventonPageLoad(document) { 5921 let tabTitleActiveOnPageLoad = document.find('.cmp-tabs__tab--active')[0].innerHTML; 5922 window.dataLayer = window.dataLayer || []; 5923 window.dataLayer.push({ 5924 "event": "subPageViewed",//e166 5925 "subPage": { 5926 "subPageID": tabTitleActiveOnPageLoad, //v105 5927 "spClicked": "false" 5928 } 5929 }); 5930} 5931 5932function subpageViewedEventOnClick(tab) { 5933 let tabTitleActiveOnClickEvent = (tab)[0].innerHTML; 5934 window.dataLayer = window.dataLayer || []; 5935 window.dataLayer.push({ 5936 "event": "subPageViewed", //e112 5937 "subPage": { 5938 "subPageID": tabTitleActiveOnClickEvent, //v105 5939 "spClicked": "true" 5940 } 5941 }); 5942} 5943(function() { 5944 'use strict'; 5945 5946 $(document).ready(function() { 5947 if ($('.cmp-tabs').length > 0) { 5948 initializeDropdown(); 5949 addEventListenerForTabs(); 5950 addEventListenerForDropdown(); 5951 } 5952 }); 5953 5954 function initializeDropdown() { 5955 let activeTab = document.querySelectorAll('.cmp-tabs__tab--active')[0].innerText; 5956 $("#cmp-tabs__dropdown option:contains('" + activeTab + "')").prop('selected', true); 5957 } 5958 5959 function addEventListenerForTabs() { 5960 document.querySelectorAll('.cmp-tabs__tab').forEach((tab) => { 5961 tab.addEventListener('click', function(event) { 5962 $('#cmp-tabs__dropdown option[selected]').prop('selected', false) 5963 $("#cmp-tabs__dropdown option:contains('" + tab.innerText + "')").prop('selected', true); 5964 }); 5965 }); 5966 } 5967 5968 function addEventListenerForDropdown() { 5969 const dropdown = document.getElementById('cmp-tabs__dropdown'); 5970 if (!dropdown) { 5971 return; 5972 } 5973 dropdown.addEventListener('change', function(event) { 5974 $("ol.cmp-tabs__tablist li:contains('" + event.target.value + "')").trigger('click'); 5975 }); 5976 } 5977})(); 5978(function() { 5979 'use strict'; 5980 5981 $(document).ready(function() { 5982 if ($('.cmp-tabs').length > 0) { 5983 EnhancedTabs.init(); 5984 } 5985 }); 5986 5987 const EnhancedTabs = { 5988 activateTabFromHashInProgress: false, 5989 5990 init() { 5991 this.addTabClickListeners(); 5992 this.addKeyboardNavigationListeners(); 5993 this.addMouseEventListeners(); 5994 this.addDropdownClickListeners(); 5995 this.synchronizeTabsAndDropdown(); 5996 this.addHashChangeListener(); 5997 this.activateTabFromHash(); 5998 this.pushSubpageViewedEventOnPageLoad(); 5999 }, 6000 6001 addTabClickListeners() { 6002 document.addEventListener('click', (e) => { 6003 const tab = e.target.closest('[data-cmp-hook-tabs="tab"]'); 6004 if (!tab) return; 6005 6006 const tablist = tab.closest('[role="tablist"]'); 6007 if (!tablist) return; 6008 6009 const isVertical = tablist.closest('.cmp-enhancedtabs--vertical'); 6010 const activeClass = isVertical ? 'cmp-enhancedtabs__tab-vertical--active' : 'cmp-enhancedtabs__tab--active'; 6011 6012 // Ensure only the clicked tab has the 'active' class 6013 tablist.querySelectorAll('[data-cmp-hook-tabs="tab"]').forEach((el) => { 6014 el.classList.remove('cmp-enhancedtabs__tab--active', 'cmp-e
6014nhancedtabs__tab-vertical--active'); 6015 if (el === tab) { 6016 el.classList.add(activeClass); 6017 } 6018 }); 6019 6020 // Push analytics event for tab click 6021 if (!EnhancedTabs.activateTabFromHashInProgress) { 6022 EnhancedTabs.pushSubpageViewedEventOnClick(tab); 6023 } 6024 }); 6025 }, 6026 6027 addKeyboardNavigationListeners() { 6028 document.addEventListener('keydown', (e) => { 6029 this.setKeyboardMode(); 6030 6031 const tab = e.target.closest('[data-cmp-hook-tabs="tab"]'); 6032 if (!tab) return; 6033 6034 const tablist = tab.closest('[role="tablist"]'); 6035 if (!tablist) return; 6036 6037 const isVertical = tablist.getAttribute('aria-orientation') === 'vertical'; 6038 const key = e.key; 6039 6040 let isPrev, isNext; 6041 if (isVertical) { 6042 isPrev = key === 'ArrowUp'; 6043 isNext = key === 'ArrowDown'; 6044 } else { 6045 isPrev = key === 'ArrowLeft' || key === 'ArrowUp'; 6046 isNext = key === 'ArrowRight' || key === 'ArrowDown'; 6047 } 6048 6049 const isHome = key === 'Home'; 6050 const isEnd = key === 'End'; 6051 6052 if (!isPrev && !isNext && !isHome && !isEnd) return; 6053 6054 const tabs = Array.from(tablist.querySelectorAll('[data-cmp-hook-tabs="tab"]')); 6055 const currentIndex = tabs.indexOf(tab); 6056 if (currentIndex === -1) return; 6057 6058 e.preventDefault(); 6059 6060 let nextIndex = currentIndex; 6061 if (isPrev) { 6062 nextIndex = (currentIndex - 1 + tabs.length) % tabs.length; 6063 } else if (isNext) { 6064 nextIndex = (currentIndex + 1) % tabs.length; 6065 } else if (isHome) { 6066 nextIndex = 0; 6067 } else if (isEnd) { 6068 nextIndex = tabs.length - 1; 6069 } 6070 6071 const nextTab = tabs[nextIndex]; 6072 if (nextTab) { 6073 nextTab.focus(); 6074 nextTab.click(); 6075 } 6076 }); 6077 }, 6078 6079 addMouseEventListeners() { 6080 window.addEventListener("mousedown", (event) => this.setMouseMode(event), true); 6081 window.addEventListener("pointerdown", (event) => this.setMouseMode(event), true); 6082 window.addEventListener("touchstart", (event) => this.setMouseMode(event), true); 6083 }, 6084 6085 addDropdownClickListeners() { 6086 // Select all dropdown buttons, to support multiple enhanced tabs components on a page 6087 $('[id$="__dropdown-button"]').each(function () { 6088 const $button = $(this); 6089 const tabsId = this.id.replace('__dropdown-button', ''); 6090 const $listbox = $(`#${tabsId}__listbox`); 6091 if ($listbox.length === 0) return; 6092 6093 $button.on('click', function () { 6094 const expanded = $button.attr('aria-expanded') === 'true'; 6095 $button.attr('aria-expanded', !expanded); 6096 $listbox.prop('hidden', expanded); 6097 if (!expanded) $listbox.focus(); 6098 }); 6099 6100 $listbox.on('click', '[role="option"]', function () { 6101 $listbox.find('[role="option"]').attr('aria-selected', 'false'); 6102 $(this).attr('aria-selected', 'true'); 6103 $button.text($(this).text()); 6104 $button.attr('aria-expanded', 'false'); 6105 $listbox.prop('hidden', true); 6106 }); 6107 }); 6108 }, 6109 6110 synchronizeTabsAndDropdown() { 6111 // Select all enhanced tabs components on the page 6112 $('.cmp-tabs').each(function () { 6113 const $tabCmp = $(this); 6114 const isVertical = $tabCmp.hasClass('cmp-enhancedtabs--vertical'); 6115 const $tabs = $tabCmp.find('[data-cmp-hook-tabs="tab"]'); 6116 const $listbox = $tabCmp.find('.cmp-enhancedtabs__listbox'); 6117 const $button = $tabCmp.find('.cmp-enhancedtabs__dropdown-button'); 6118 const $options = $listbox.find('[role="option"]'); 6119 6120 if ($tabs.length !== $options.length) { 6121 return; 6122 } 6123 6124 // Initial sync: determine active tab (fallback to first if none) 6125 const activeClass = isVertical ? '.cmp-enhancedtabs__tab-vertical--active' : '.cmp-e
6125nhancedtabs__tab--active'; 6126 let $activeTab = $tabs.filter(activeClass); 6127 if ($activeTab.length === 0) { 6128 $activeTab = $tabs.first(); 6129 } 6130 const activeIndex = $tabs.index($activeTab); 6131 6132 // Update listbox options selection 6133 $options.attr('aria-selected', 'false'); 6134 const $activeOption = $options.eq(activeIndex).attr('aria-selected', 'true'); 6135 6136 // Update dropdown button text 6137 if ($button.length && $activeOption.length) { 6138 $button.text($activeOption.text()); 6139 } 6140 6141 // When a tab is selected, update the dropdown 6142 $tabs.off('.enhancedTabs').on('click.enhancedTabs', function (e) { 6143 const index = $tabs.index(this); 6144 $options.attr('aria-selected', 'false'); 6145 const $selectedOption = $options.eq(index).attr('aria-selected', 'true'); 6146 if ($button.length) { 6147 $button.text($selectedOption.text()); 6148 } 6149 }); 6150 6151 // When a listbox option is selected, update the selected tab 6152 $listbox.off('.enhancedTabs').on('click.enhancedTabs', '[role="option"]', function () { 6153 const index = $options.index(this); 6154 const $selectedTab = $tabs.eq(index); 6155 if ($selectedTab.length) { 6156 $selectedTab.trigger('click'); 6157 } 6158 }); 6159 }); 6160 }, 6161 6162 activateTabFromHash() { 6163 const anchor = window.location.hash.substring(1); 6164 if (!anchor) return; 6165 6166 const $tab = $('#' + anchor); 6167 if ($tab.length) { 6168 const hasActiveClass = $tab.hasClass('cmp-enhancedtabs__tab--active') || 6169 $tab.hasClass('cmp-enhancedtabs__tab-vertical--active'); 6170 if (!hasActiveClass) { 6171 EnhancedTabs.activateTabFromHashInProgress = true; 6172 try { 6173 $tab.trigger('click'); 6174 } finally { 6175 EnhancedTabs.activateTabFromHashInProgress = false; 6176 } 6177 } 6178 $tab.get(0).scrollIntoView({ 6179 behavior: 'smooth', 6180 block: 'center' 6181 }); 6182 } 6183 }, 6184 6185 addHashChangeListener() { 6186 window.addEventListener("hashchange", this.activateTabFromHash); 6187 }, 6188 6189 pushSubpageViewedEventOnPageLoad() { 6190 $('.cmp-enhancedtabs__tab--active, .cmp-enhancedtabs__tab-vertical--active').each(function () { 6191 let normalizedTabTitle = EnhancedTabs.normalizeString(this.innerHTML); 6192 window.dataLayer = window.dataLayer || []; 6193 window.dataLayer.push({ 6194 "event": "subPageViewed", //e166 6195 "subPage": { 6196 "subPageID": normalizedTabTitle, //v105 6197 "spClicked": "false" 6198 } 6199 }); 6200 }); 6201 }, 6202 6203 pushSubpageViewedEventOnClick(tab) { 6204 let normalizedTabTitle = EnhancedTabs.normalizeString(tab.innerHTML); 6205 window.dataLayer = window.dataLayer || []; 6206 window.dataLayer.push({ 6207 "event": "subPageViewed", //e112 6208 "subPage": { 6209 "subPageID": normalizedTabTitle, //v105 6210 "spClicked": "true" 6211 } 6212 }); 6213 }, 6214 6215 normalizeString(str) { 6216 return typeof str === 'string' ? str.replace(/&/g, 'and') : ''; 6217 }, 6218 6219 setKeyboardMode() { 6220 document.body.setAttribute("data-focus-source", "keyboard"); 6221 }, 6222 6223 setMouseMode() { 6224 document.body.removeAttribute("data-focus-source"); 6225 }, 6226 }; 6227})(); 6228window.addEventListener('load', () => { 6229 document.querySelectorAll('.cmp-tabs').forEach(initIndicator); 6230}); 6231 6232function initIndicator(root) { 6233 const isVertical = root.classList.contains('cmp-enhancedtabs--vertical'); 6234 const tablist = root.querySelector('.cmp-tabs__tablist'); 6235 const hr = isVertical 6236 ? root.querySelector('.cmp-enhancedtabs__hr-vertical') 6237 : root.querySelector('.cmp-enhancedtabs__hr'); 6238 6239 if (!tablist || !hr) return; 6240 6241 const slideIndicatorToActiveTab = () => { 6242 const activeTab = isVertical 6243 ? (tablist.querySelector('.cmp-enhancedtabs__tab-vertical--active') || 6244 tablist.querySelector('.cmp-enhancedtabs__tab-vertical[aria-selected="true"]')) 6245 : (tablist.querySelector('.cmp-enhancedtabs__tab--active') || 6246 tablist.querySelector('.cmp-enhancedtabs__tab[aria-selected="true"]')); 6247 6248 if (!activeTab) return; 6249 6250 const tabRect = activeTab.getBoundingClientRect(); 6251 const hrRect = hr.getBoundingClientRect(); 6252 6253 if (isVertical) { 6254 const top = Math.round(tabRect.top - hrRect.top); 6255 const height = Math.round(tabRect.height);
6256 hr.style.setProperty('--indicator-top', `${top}px`); 6257 hr.style.setProperty('--indicator-height', `${height}px`); 6258 } else { 6259 const left = Math.round(tabRect.left - hrRect.left); 6260 const width = Math.round(tabRect.width); 6261 hr.style.setProperty('--indicator-left', `${left}px`); 6262 hr.style.setProperty('--indicator-width', `${width}px`); 6263 } 6264 }; 6265 6266 // Initialize the sliding indicator (defer to the next frame to ensure tab activation/layout are settled) 6267 window.requestAnimationFrame(() => { 6268 window.requestAnimationFrame(slideIndicatorToActiveTab); 6269 }); 6270 6271 // Add click and key listeners 6272 tablist.addEventListener('click', () => window.requestAnimationFrame(slideIndicatorToActiveTab)); 6273 tablist.addEventListener('keydown', (e) => { 6274 const keysToWatch = isVertical 6275 ? ['Enter', 'ArrowUp', 'ArrowDown', 'Home', 'End'] 6276 : ['Enter', 'ArrowLeft', 'ArrowRight', 'Home', 'End']; 6277 6278 if (keysToWatch.includes(e.key)) { 6279 window.requestAnimationFrame(slideIndicatorToActiveTab); 6280 } 6281 }); 6282 6283 // Keep in sync with class/ARIA changes 6284 const mo = new MutationObserver(() => window.requestAnimationFrame(slideIndicatorToActiveTab)); 6285 mo.observe(tablist, { 6286 subtree: true, 6287 attributes: true, 6288 attributeFilter: ['class', 'aria-selected'] 6289 }); 6290 6291 // Recalculate on resize 6292 window.addEventListener('resize', () => window.requestAnimationFrame(slideIndicatorToActiveTab)); 6293} 6294 6295$(".cmp-container--contentRowItem .cmp-container .aem-Grid .cmp-container").hover( 6296 function () { 6297 $(this).addClass("applyHoverToSingle"); 6298 }, function () { 6299 $(this).removeClass("applyHoverToSingle"); 6300 } 6301); 6302 6303const updateStickyTop = function updateStickyTop(selectorClass) { 6304 if (document.querySelectorAll(selectorClass).length > 0) { 6305 document.querySelectorAll(selectorClass).forEach((element) => { 6306 element.style.top = element.firstElementChild.getAttribute('data-stickytop') + "px"; 6307 }); 6308 } 6309}; 6310 6311$(document).ready( function() { 6312 "use strict"; 6313 updateStickyTop(".cmp-container--stickyBeyondS"); 6314 updateStickyTop(".cmp-container--stickyBeyondM"); 6315 updateStickyTop(".cmp-container--stickyBeyondL"); 6316}); 6317 6318 6319 6320 6321/******/ (() => { // webpackBootstrap 6322/******/ "use strict"; 6323/******/ var __webpack_modules__ = ({ 6324
6325/***/ "./src/js/app.js": 6326/*!***********************!*\ 6327 !*** ./src/js/app.js ***! 6328 \***********************/ 6329/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 6330 6331 __webpack_require__.r(__webpack_exports__); 6332 /* harmony import */ var _observers__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./observers */ "./src/js/observers.js"); 6333 /* harmony import */ var _movable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./movable */ "./src/js/movable.js"); 6334 /* harmony import */ var _vg_carousel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./vg-carousel */ "./src/js/vg-carousel.js"); 6335 /* harmony import */ var _vg_animate__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./vg-animate */ "./src/js/vg-animate.js"); 6336 /* harmony import */ var _vg_parallaxv2__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./vg-parallaxv2 */ "./src/js/vg-parallaxv2.js"); 6337 function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 6338
vendor: 9,050 bytes, line 6338
6338function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
6339 function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } 6340 function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } 6341 // import jquery from 'jquery'; 6342 // window.$ = window.jQuery = jquery; 6343 6344 6345 6346 6347 6348 6349 if (window.CQ && window.CQ.WCM) { 6350 window.disableOnResize = true; 6351 window.parallaxDisabled = true; 6352 window.animationDisabled = true; 6353 } 6354 var animateContainerSelector = '*[data-vg-slide]', 6355 carouselContainerSelector = '*[data-vg-carousel]', 6356 animateMutationObserver = new _observers__WEBPACK_IMPORTED_MODULE_0__.VgMutationObserver(animateContainerSelector, animationStart, { 6357 debug: false 6358 }), 6359 parallaxMutationObserver = new _observers__WEBPACK_IMPORTED_MODULE_0__.VgMutationObserver(carouselContainerSelector, carouselStart, { 6360 debug: false 6361 }); 6362 var carouselFound = false, 6363 slidesFound = false; 6364 animateMutationObserver.init(); 6365 parallaxMutationObserver.init(); 6366 function animationStart(_x, _x2) { 6367 return _animationStart.apply(this, arguments); 6368 } 6369 function _animationStart() { 6370 _animationStart = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee(container, found) { 6371 var myAnimate; 6372 return _regeneratorRuntime().wrap(function _callee$(_context) { 6373 while (1) switch (_context.prev = _context.next) { 6374 case 0: 6375 if (!(!found || slidesFound)) { 6376 _context.next = 2; 6377 break; 6378 } 6379 return _context.abrupt("return"); 6380 case 2: 6381 slidesFound = true; 6382 myAnimate = new _vg_animate__WEBPACK_IMPORTED_MODULE_3__.VgAnimate(_movable__WEBPACK_IMPORTED_MODULE_1__.Movable); 6383 _context.next = 6; 6384 return myAnimate.init({ 6385 container: null, 6386 debug: false, 6387 inViewPercent: 0.4, 6388 containerElSelector: '*[data-vg-slide]', 6389 exitSlideContainerElSelector: '*[data-vg-slide].htl-exit', 6390 animatablesSelector: '*[data-vg-animatable]', 6391 disabled: window.animationDisabled || false 6392 }); 6393 case 6: 6394 case "end": 6395 return _context.stop(); 6396 } 6397 }, _callee); 6398 })); 6399 return _animationStart.apply(this, arguments); 6400 } 6401 function carouselStart(container, found) { 6402 if (!found || carouselFound) return; 6403 var carousels = document.querySelectorAll(carouselContainerSelector); 6404 if (!carousels.length) return false; 6405 carouselFound = true; 6406 carousels.forEach(function (carousel) { 6407 var myMovable = new _movable__WEBPACK_IMPORTED_MODULE_1__.Movable(); 6408 var myParallax = new _vg_parallaxv2__WEBPACK_IMPORTED_MODULE_4__.VgParallax(myMovable); 6409 myParallax.init({ 6410 debug: false, 6411 inViewPercent: 0.25, 6412 parallaxablesSelector: '*[data-vg-parallaxable]', 6413 disabled: window.parallaxDisabled || false 6414 }); 6415 var myCarousel = new _vg_carousel__WEBPACK_IMPORTED_MODULE_2__.VgCarousel(myMovable, myParallax); 6416 myCarousel.init({ 6417 debug: false, 6418 carousel: carousel, 6419 carouselSelector: carouselContainerSelector, 6420 carouselScrollContainerSelector: '*[data-vg-carousel-scroll-container]', 6421 slideSelector: '*[data-vg-slide]', 6422 disableOnResize: window.disableOnResize || false, 6423 nextSelector: '#htl-controls_next', 6424 previousSelector: '#htl-controls_previous', 6425 mobileAutoAdvanceDelay: 3000, 6426 mobileAutoAdvanceElement: '.htl-intro-text-container', 6427 introCtaSelector: '.htl-intro-cta', 6428 throttleMax: 4 6429 }); 6430 }); 6431 } 6432 6433 /***/ }), 6434 6435
6435/***/ "./src/js/movable.js": 6436 /*!***************************!*\ 6437 !*** ./src/js/movable.js ***! 6438 \***************************/ 6439 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 6440 6441 __webpack_require__.r(__webpack_exports__); 6442 /* harmony export */ __webpack_require__.d(__webpack_exports__, { 6443 /* harmony export */ Movable: () => (/* binding */ Movable) 6444 /* harmony export */ }); 6445 function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 6446 function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 6447 function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 6448 function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 6449 function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } 6450 function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 6451 function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 6452 var Movable = /*#__PURE__*/function () { 6453 function Movable() { 6454 _classCallCheck(this, Movable); 6455 _defineProperty(this, "disableWidth", void 0); 6456 _defineProperty(this, "debug", void 0); 6457 this.debug = false; 6458 } 6459 6460 /** 6461 * 6462 */ 6463 return _createClass(Movable, [{ 6464 key: "log", 6465 value: function log() { 6466 if (this.debug) { 6467 var _console; 6468 (_console = console).log.apply(_console, arguments); 6469 } 6470 } 6471 }, { 6472 key: "setDebug", 6473 value: function setDebug() { 6474 var debug = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; 6475 this.debug = debug; 6476 } 6477 }, { 6478 key: "setDisableWidth", 6479 value: function setDisableWidth() { 6480 var width = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 992; 6481 this.disableWidth = width; 6482 } 6483 6484 /** 6485 * 6486 * @returns BOOLEAN 6487 */ 6488 }, { 6489 key: "isMobileWidth", 6490 value: function isMobileWidth() { 6491 this.log(this.disableWidth); 6492 return window.innerWidth < this.disableWidth; 6493 } 6494 6495 /** 6496 * 6497 * @param {HTMLElement} element 6498 * @param {FLOAT} inView 6499 * @returns NULL 6500 */ 6501 }, { 6502 key: "isInViewport", 6503 value: function isInViewport(element) { 6504 var inView = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.1; 6505 var rect = element.getBoundingClientRect(); 6506 var threshold = rect.width * inView; // % of the slide's width 6507 6508 return rect.right - threshold > 0 && rect.left + threshold < (window.innerWidth || document.documentElement.clientWidth); 6509 } 6510 }]); 6511 }(); 6512 6513 /***/ }), 6514 6515
vendor: 4,878 bytes, lines 6515-6565
6515/***/ "./src/js/observers.js": 6516 /*!*****************************!*\ 6517 !*** ./src/js/observers.js ***! 6518 \*****************************/ 6519 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 6520 6521 __webpack_require__.r(__webpack_exports__); 6522 /* harmony export */ __webpack_require__.d(__webpack_exports__, { 6523 /* harmony export */ VgIntersectionObserver: () => (/* binding */ VgIntersectionObserver), 6524 /* harmony export */ VgMutationObserver: () => (/* binding */ VgMutationObserver) 6525 /* harmony export */ }); 6526 function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 6527 function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; } 6528 function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } 6529 function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } 6530 function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } 6531 function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } 6532 function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 6533 function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 6534 function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 6535 function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } 6536 function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 6537 function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 6538 var VgIntersectionObserver = /*#__PURE__*/function () { 6539 /** 6540 * 6541 */ 6542 function VgIntersectionObserver() { 6543 _classCallCheck(this, VgIntersectionObserver); 6544 _defineProperty(this, "observer", void 0); 6545 _defineProperty(this, "callback", void 0); 6546 _defineProperty(this, "defaults", void 0); 6547 _defineProperty(this, "settings", void 0); 6548 this.defaults = { 6549 debug: true 6550 }; 6551 this.observer = new IntersectionObserver(this.vgCallback.bind(this), { 6552 root: null, 6553 rootMargin: '50px', 6554 threshold: 1.0 6555 }); 6556 } 6557 6558 /** 6559 * 6560 */ 6561 return _createClass(VgIntersectionObserver, [{ 6562 key: "log", 6563 value: function log() { 6564 if (this.settings.debug) { 6565 var _console;
6566 (_console = console).log.apply(_console, arguments); 6567 } 6568 } 6569 6570 /** 6571 * 6572 * @param {*} selector 6573 * @param {*} callback 6574 * @param {*} params 6575 */ 6576 }, { 6577 key: "init", 6578 value: function init(selector, callback) { 6579 var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 6580 this.settings = _objectSpread(_objectSpread({}, this.defaults), params); 6581 this.callback = callback; 6582 this.observer.observe(selector); 6583 } 6584 6585 /** 6586 * 6587 * @param {*} entries 6588 * @param {*} observer 6589 */ 6590 }, { 6591 key: "vgCallback", 6592 value: function vgCallback(entries, observer) { 6593 var _this = this; 6594 entries.forEach(function (entry) { 6595 if (entry.isIntersecting) { 6596 observer.unobserve(entry.target); 6597 _this.callback.call(null, { 6598 el: entry.target 6599 }); 6600 } else { 6601 _this.callback.call(null, { 6602 error: 'Not intersecting', 6603 el: entry.target 6604 }); 6605 } 6606 }); 6607 } 6608 }]); 6609 }(); 6610 var VgMutationObserver = /*#__PURE__*/function () { 6611 /** 6612 * 6613 * @param {*} targetSelector 6614 * @param {*} callBack 6615 * @param {*} params 6616 */ 6617 function VgMutationObserver(targetSelector, callBack) { 6618 var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 6619 _classCallCheck(this, VgMutationObserver); 6620 _defineProperty(this, "options", void 0); 6621 _defineProperty(this, "target", void 0); 6622 _defineProperty(this, "targetSelector", void 0); 6623 _defineProperty(this, "mutationObserver", void 0); 6624 _defineProperty(this, "callBack", void 0); 6625 _defineProperty(this, "defaults", void 0); 6626 _defineProperty(this, "settings", void 0); 6627 this.defaults = { 6628 debug: true 6629 }; 6630 this.settings = _objectSpread(_objectSpread({}, this.defaults), params); 6631 this.targetSelector = targetSelector; 6632 // this.log(this.targetSelector); 6633 this.callBack = callBack; 6634 this.init(); 6635 } 6636 6637 /** 6638 * 6639 */ 6640 return _createClass(VgMutationObserver, [{ 6641 key: "log", 6642 value: function log() { 6643 if (this.settings.debug) { 6644 var _console2; 6645 (_console2 = console).log.apply(_console2, arguments); 6646 } 6647 } 6648 6649 /** 6650 * 6651 */ 6652 }, { 6653 key: "init", 6654 value: function init() { 6655 this.options = { 6656 childList: true, 6657 attributes: true, 6658 subtree: true 6659 }; 6660 this.target = document.querySelector(this.targetSelector); 6661 this.mutationObserver = new MutationObserver(this.vgCallback.bind(this)); 6662 this.mutationObserver.observe(document.documentElement, this.options); 6663 } 6664 6665 /** 6666 * 6667 * @param {MutationRecord} mutationRecord 6668 * @param {MutationObserver} mutationObserver 6669 */ 6670 }, { 6671 key: "vgCallback", 6672 value: function vgCallback(mutationRecord, mutationObserver) { 6673 // this.log(`watchMe Function: in here`); 6674 var _iterator = _createForOfIteratorHelper(mutationRecord), 6675 _step; 6676 try { 6677 for (_iterator.s(); !(_step = _iterator.n()).done;) { 6678 var record = _step.value; 6679 // this.log(`Record Info: ${mutationRecord}`); 6680 if (record.type === "childList") { 6681 this.target = document.querySelector(this.targetSelector); 6682 // this.log(`Target Info: ${this.targetSelector} ${this.target}`); 6683 6684 if (this.target) { 6685 // console.log(this.target, 'Callback'); 6686 mutationObserver.disconnect(); 6687 this.log("".concat(this.targetSelector, " has been added and is ready for action")); 6688 this.callBack(this.target, true); 6689 } else { 6690 this.log("".concat(this.targetSelector, " has been deleted or was never present")); 6691 this.callBack(this.target, false); 6692 } 6693 break; 6694 } 6695 } 6696 } catch (err) { 6697 _iterator.e(err); 6698 } finally { 6699 _iterator.f(); 6700 } 6701 } 6702 }]); 6703 }(); 6704 6705 /***/ }), 6706 6707
6707/***/ "./src/js/vg-animate.js": 6708 /*!******************************!*\ 6709 !*** ./src/js/vg-animate.js ***! 6710 \******************************/ 6711 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 6712 6713 __webpack_require__.r(__webpack_exports__); 6714 /* harmony export */ __webpack_require__.d(__webpack_exports__, { 6715 /* harmony export */ VgAnimate: () => (/* binding */ VgAnimate) 6716 /* harmony export */ }); 6717
vendor: 9,050 bytes, line 6717
6717function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
vendor: 2,191 bytes, lines 6718-6726
6718 function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); } 6719 function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } 6720 function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } 6721 function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } 6722 function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } 6723 function _arrayWithHoles(r) { if (Array.isArray(r)) return r; } 6724 function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 6725 function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } 6726 function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {};
6726 r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } 6727 function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); } 6728 function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; } 6729 function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 6730 function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 6731 function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 6732 function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } 6733 function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 6734 function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 6735 var VgAnimate = /*#__PURE__*/function () { 6736 /** 6737 * 6738 */ 6739 function VgAnimate(base) { 6740 _classCallCheck(this, VgAnimate); 6741 _defineProperty(this, "base", void 0); 6742 _defineProperty(this, "containers", void 0); 6743 _defineProperty(this, "animatablesMap", void 0); 6744 // animatables; 6745 _defineProperty(this, "settings", void 0); 6746 _defineProperty(this, "defaults", void 0); 6747 _defineProperty(this, "animaitionMap", void 0); 6748 this.base = new base(); 6749 this.animatablesMap = new Map(); 6750 6751 // Map animation types to classnames 6752 this.animaitionMap = new Map(); 6753 this.animaitionMap.set('slideInTop', 'vg-animation-slide-in-top'); 6754 this.animaitionMap.set('slideInRight', 'vg-animation-slide-in-right'); 6755 this.animaitionMap.set('slideInBottom', 'vg-animation-slide-in-bottom'); 6756 this.animaitionMap.set('slideInLeft', 'vg-animation-slide-in-left'); 6757 this.animaitionMap.set('fadeIn', 'vg-animation-fadein'); 6758 this.animaitionMap.set('fadeOut', 'vg-animation-fadeout'); 6759 this.animaitionMap.set('zoomIn', 'vg-animation-zoomin'); 6760 this.animaitionMap.set('zoomOut', 'vg-animation-zoomout'); 6761 var containerEl = document.createElement('div'); 6762 containerEl.classList.add('slide'); 6763 this.defaults = { 6764 container: containerEl, 6765 containerElSelector: '.slide', 6766 exitSlideContainerElSelector: '.exit-slide', 6767 debug: true, 6768 animatablesSelector: '*[data-vg-animatable]', 6769 inViewPercent: 0.25, 6770 disableWidth: 992, 6771 disabled: false 6772 }; 6773 } 6774 6775 /** 6776 * 6777 * @param {*} params 6778 * @returns 6779 */ 6780 return _createClass(VgAnimate, [{ 6781 key: "init", 6782 value: (function () { 6783 var _init = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() { 6784 var _this2 = this; 6785 var params, 6786 _this, 6787 animationObserver, 6788 exitSlideObserver, 6789 _args = arguments; 6790 return _regeneratorRuntime().wrap(function _callee$(_context) { 6791 while (1) switch (_context.prev = _context.next) { 6792 case 0: 6793 params = _args.length > 0 && _args[0] !== undefined ? _args[0] : {}; 6794 this.settings = _objectSpread(_objectSpread({}, this.defaults), params); 6795 this.base.setDebug(this.settings.debug); 6796 this.base.setDisableWidth(this.settings.disableWidth); 6797 6798 // Handle any new animations 6799 if (_typeof(this.settings.animations) == 'object' && this.settings.animations.length) { 6800 Object.entries(this.settings.animations).forEac
6800h(function (_ref) { 6801 var _ref2 = _slicedToArray(_ref, 2), 6802 key = _ref2[0], 6803 value = _ref2[1]; 6804 _this2.animaitionMap.set(key, value); 6805 }); 6806 } 6807 6808 // if (!this.settings.container) return false; 6809 this.containers = document.querySelectorAll(this.settings.containerElSelector); 6810 if (this.containers) { 6811 _context.next = 8; 6812 break; 6813 } 6814 return _context.abrupt("return", false); 6815 case 8: 6816 this.containers.forEach(function (el) { 6817 var animatables = el.querySelectorAll(_this2.settings.animatablesSelector); 6818 // this.animatables = animatables; 6819 6820 if (!animatables.length) return false; 6821 _this2.base.log('Animatables: ', animatables); 6822 _this2.animatablesMap.set(el, animatables); 6823 return true; 6824 }); 6825 _this = this; 6826 animationObserver = new IntersectionObserver(function (entries, observer) { 6827 entries.forEach(function (entry) { 6828 if (entry.isIntersecting) { 6829 _this.animate(entry.target, observer); 6830 } 6831 }); 6832 }, { 6833 root: null, 6834 threshold: 0.5 6835 }); 6836 exitSlideObserver = new IntersectionObserver(function (entries, observer) { 6837 entries.forEach(function (entry) { 6838 if (entry.isIntersecting) { 6839 _this.animate(entry.target, observer); 6840 } 6841 }); 6842 }, { 6843 root: null, 6844 threshold: 0.25 6845 }); 6846 document.querySelectorAll(this.settings.containerElSelector).forEach(function (item) { 6847 animationObserver.observe(item); 6848 }); 6849 document.querySelectorAll(this.settings.exitSlideContainerElSelector).forEach(function (item) { 6850 exitSlideObserver.observe(item); 6851 }); 6852 case 14: 6853 case "end": 6854 return _context.stop(); 6855 } 6856 }, _callee, this); 6857 })); 6858 function init() { 6859 return _init.apply(this, arguments); 6860 } 6861 return init; 6862 }() 6863 /** 6864 * Starts the animations 6865 */ 6866 ) 6867 }, { 6868 key: "animate", 6869 value: (function () { 6870 var _animate = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee2(containerElem, observer) { 6871 var _this3 = this; 6872 var els; 6873 return _regeneratorRuntime().wrap(function _callee2$(_context2) { 6874 while (1) switch (_context2.prev = _context2.next) { 6875 case 0: 6876 if (!this.settings.disabled) { 6877 _context2.next = 2; 6878 break; 6879 } 6880 return _context2.abrupt("return"); 6881 case 2: 6882 els = this.animatablesMap.get(containerElem); 6883 if (els) { 6884 _context2.next = 5; 6885 break; 6886 } 6887 return _context2.abrupt("return"); 6888 case 5: 6889 if (!this.base.isMobileWidth()) { 6890 // Interate each ainimateable element to determine the animation type 6891 els.forEach(function (el) { 6892 var animation = el.dataset.vgAnimationEffect; 6893 6894 // Now execute the required animation 6895 if (!animation) return false; 6896 _this3.handleAnimation(el, animation); 6897 observer.unobserve(containerElem); 6898 }); 6899 } 6900 case 6: 6901 case "end": 6902 return _context2.stop(); 6903 } 6904 }, _callee2, this); 6905 })); 6906 function animate(_x, _x2) { 6907 return _animate.apply(this, arguments); 6908 } 6909 return animate; 6910 }() 6911 /** 6912 * Adds the appropriate animation class to an element 6913 * @param {*} el 6914 * @param {*} animation 6915 * @returns 6916 */ 6917 ) 6918 }, {
6919 key: "handleAnimation", 6920 value: function handleAnimation(el, animation) { 6921 var animationClass = this.animaitionMap.get(animation), 6922 animationSpeed = el.dataset.vgAnimationSpeed || 'moderate', 6923 animationDelay = el.dataset.vgAnimationDelay || 'none'; 6924 if (!animationClass) return; 6925 el.classList.add("".concat(animationClass, "-").concat(animationSpeed, "-").concat(animationDelay)); 6926 } 6927 }]); 6928 }(); 6929 6930 /***/ }), 6931 6932
6932/***/ "./src/js/vg-carousel.js": 6933 /*!*******************************!*\ 6934 !*** ./src/js/vg-carousel.js ***! 6935 \*******************************/ 6936 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 6937 6938 __webpack_require__.r(__webpack_exports__); 6939 /* harmony export */ __webpack_require__.d(__webpack_exports__, { 6940 /* harmony export */ VgCarousel: () => (/* binding */ VgCarousel) 6941 /* harmony export */ }); 6942 function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 6943 function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } 6944 function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } 6945 function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 6946 function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 6947 function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 6948 function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } 6949 function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 6950 function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 6951 var VgCarousel = /*#__PURE__*/function () { 6952 function VgCarousel(Base) { 6953 var Parallax = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; 6954 _classCallCheck(this, VgCarousel); 6955 _defineProperty(this, "base", void 0); 6956 _defineProperty(this, "scrollContainer", void 0); 6957 _defineProperty(this, "carousel", void 0); 6958 _defineProperty(this, "slides", void 0); 6959 _defineProperty(this, "parallax", void 0); 6960 _defineProperty(this, "settings", void 0); 6961 _defineProperty(this, "defaults", void 0); 6962 _defineProperty(this, "next", void 0); 6963 _defineProperty(this, "previous", void 0); 6964 _defineProperty(this, "introCta", void 0); 6965 _defineProperty(this, "slidesMap", void 0); 6966 this.ticking = false; 6967 this.base = Base; 6968 this.parallax = Parallax; 6969 this.slidesMap = new Map(); 6970 this.defaults = { 6971 throttleMax: 4, 6972 carouselSelector: '#vg-carousel', 6973 carousel: null, 6974 carouselScrollContainerSelector: '#vg-scroll-container', 6975 debug: true, 6976 slideSelector: '.slide', 6977 speedReductionRatio: 0.05, 6978 disableWidth: 992, 6979 disableOnResize: false, 6980 nextSelector: '.next', 6981 previousSelector: '.previous', 6982 mobileAutoAdvanceDelay: 1000, 6983 mobileAutoAdvanceElement: '.htl-intro-text-container', 6984 introCtaSelector: '.htl-intro-cta' 6985 }; 6986 } 6987 return _createClass(VgCarousel, [{ 6988 key: "init",
6989 value: function init() { 6990 var _this = this; 6991 var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 6992 this.currentSlide = 0; 6993 this.settings = _objectSpread(_objectSpread({}, this.defaults), params); 6994 this.throttleCount = 0; 6995 this.base.setDebug(this.settings.debug); 6996 this.base.setDisableWidth(this.settings.disableWidth); 6997 if (!this.settings.carouselSelector) return false; 6998 this.scrollContainer = document.querySelector(this.settings.carouselScrollContainerSelector); 6999 if (!this.scrollContainer) return false; 7000 this.carousel = this.settings.carousel || document.querySelector(this.settings.carouselSelector); 7001 if (!this.carousel) return false; 7002 this.handleIntroCta(); 7003 this.slides = this.carousel.querySelectorAll(this.settings.slideSelector); 7004 // Cache slides for later use (ordered as they appear in the DOM) 7005 var x = 0; 7006 this.slides.forEach(function (slide) { 7007 _this.slidesMap.set(x, slide); 7008 x++; 7009 }); 7010 this.setContainerHeight(this.scrollContainer, this.slides); 7011 this.setContainerWidth(this.carousel, this.slides); 7012 var ticking = false; 7013 var scrollTop = window.scrollY; 7014 this.update(scrollTop); 7015 window.addEventListener('scroll', function () { 7016 if (!ticking) { 7017 scrollTop = window.scrollY || document.documentElement.scrollTop; 7018 requestAnimationFrame(function () { 7019 _this.update(scrollTop); 7020 ticking = false; 7021 }); 7022 ticking = true; 7023 } 7024 }); 7025 if (!this.settings.disableOnResize) { 7026 document.addEventListener('resize', function () { 7027 _this.setContainerHeight(scrollContainer, _this.slides); 7028 _this.setContainerWidth(_this.carousel, _this.slides); 7029 }); 7030 } 7031 this.next = document.querySelector(this.settings.nextSelector); 7032 this.previous = document.querySelector(this.settings.previousSelector); 7033 if (this.next && this.previous) { 7034 this.next.addEventListener('click', function (e) { 7035 e.preventDefault(); 7036 _this.handleNext(); 7037 }); 7038 this.previous.addEventListener('click', function (e) { 7039 e.preventDefault(); 7040 _this.handlePrev(); 7041 }); 7042 } 7043 } 7044 }, { 7045 key: "setContainerHeight", 7046 value: function setContainerHeight(container, slides) { 7047 var totalWidth = Array.from(slides).reduce(function (total, slide) { 7048 return total + slide.offsetWidth; 7049 }, 0); 7050 container.style.height = "".concat(totalWidth + window.innerWidth, "px"); 7051 } 7052 }, { 7053 key: "setContainerWidth", 7054 value: function setContainerWidth(container, slides) { 7055 return; 7056 var totalWidth = Array.from(slides).reduce(function (total, slide) { 7057 return total + slide.offsetWidth; 7058 }, 0); 7059 container.style.width = "".concat(totalWidth + this.scrollContainer.offsetTop, "px"); 7060 } 7061 }, { 7062 key: "update", 7063 value: function update(scrollTop) { 7064 var _this2 = this; 7065 var carouselOffsetTop = this.scrollContainer.offsetTop; 7066 if (this.base.isMobileWidth()) return false; 7067 this.base.log('Top: ', this.scrollContainer.offsetTop); 7068 if (scrollTop <= carouselOffsetTop) { 7069 this.carousel.style.transform = 'translate3d(0px, 0, 0)'; 7070 } else { 7071 // let slide; 7072 var carouselHeight = this.scrollContainer.offsetHeight, 7073 verticalScroll = scrollTop - carouselOffsetTop, 7074 maxVerticalScroll = carouselHeight - window.innerHeight, 7075 // Clamp progress between 0 and 1 7076 progress = Math.min(Math.max(verticalScroll / maxVerticalScroll, 0), 1), 7077 // The total horizontal scroll distance equals (number of slides - 1) * viewport width. 7078 maxHorizontalScroll = this.getMaxHorizontalScroll(), 7079 translateX = -progress * maxHorizontalScroll; 7080 this.carousel.style.transform = "translate3d(".concat(translateX.toFixed(2), "px, 0, 0)"); 7081 // Update the current slide index (rounded) 7082 this.currentSlide = Math.round(progress * (this.slides.length)); 7083 //console.log('current slide: ' + this.currentSlide); 7084 7085 // Throttle carousel/Parallax movement 7086 if (this.throttleCount >= this.settings.throttleMax) { 7087 this.throttleCount = 0; 7088 if (this.parallax) { 7089 // window.requestAnimationFrame(() => { 7090 // Get the current slide element from the slide Map
7091 this.slides.forEach(function (slide, index) { 7092 _this2.parallax.update(slide, index); 7093 }); 7094 7095 // }); 7096 } 7097 } else { 7098 this.throttleCount++; 7099 } 7100 } 7101 } 7102 }, { 7103 key: "handleIntroCta", 7104 value: function handleIntroCta() { 7105 var _this3 = this; 7106 this.introCta = document.querySelector(this.settings.introCtaSelector); 7107 if (!this.introCta) return; 7108 this.introCta.addEventListener('click', function () { 7109 _this3.handleNext(); 7110 }); 7111 } 7112 }, { 7113 key: "handleNext", 7114 value: function handleNext() { 7115 if (this.currentSlide < this.slides.length - 1) { 7116 this.currentSlide++; 7117 this.scrollToSlide(this.currentSlide); 7118 } 7119 } 7120 }, { 7121 key: "handlePrev", 7122 value: function handlePrev() { 7123 if (this.currentSlide > 0) { 7124 this.currentSlide--; 7125 this.scrollToSlide(this.currentSlide); 7126 } 7127 } 7128 7129 // Calculate total horizontal scrollable distance. 7130 }, { 7131 key: "getMaxHorizontalScroll", 7132 value: function getMaxHorizontalScroll() { 7133 return this.carousel.scrollWidth - window.innerWidth; 7134 } 7135 7136 /* 7137 Helper function to calculate the window scrollY value for a given slide index. 7138 When slides have variable widths, we want to center the target slide in the viewport. 7139 1. We compute the slideâs center relative to the carouselWrapper. 7140 2. The desired horizontal translation (translateX) to center that slide is: 7141 desiredTranslateX = -(slide.offsetLeft + slide.offsetWidth/2 - window.innerWidth/2) 7142 3. Since our scroll-driven translation is: 7143 translateX = -progress * (carouselWrapper.scrollWidth - window.innerWidth) 7144 we can invert this to get the progress: 7145 progress = (slide.offsetLeft + slide.offsetWidth/2 - window.innerWidth/2) / (carouselWrapper.scrollWidth - window.innerWidth) 7146 4. Finally, the vertical scrollY value is: 7147 scrollY = carouselSection.offsetTop + progress * (carouselSection.offsetHeight - window.innerHeight) 7148 7149 Update: instead of centering, we are making the the start be pinned left 7150 */ 7151 }, { 7152 key: "getScrollYForSlide", 7153 value: function getScrollYForSlide(slideIndex) { 7154 var slide = this.slides[slideIndex], 7155 slideCenter = slide.offsetLeft + slide.offsetWidth / 2, 7156 viewportCenter = window.innerWidth / 2, 7157 maxHorizontalScroll = this.getMaxHorizontalScroll(); 7158 var 7159 // Calculate desired translateX (which is negative if we move left) 7160 //desiredTranslateX = -(slideCenter - viewportCenter), 7161 desiredTranslateX = -(slide.offsetLeft), 7162 // Invert the formula to compute progress 7163 progress = -desiredTranslateX / maxHorizontalScroll; 7164 7165 // Clamp progress between 0 and 1 7166 progress = Math.min(Math.max(progress, 0), 1); 7167 var carouselHeight = this.scrollContainer.offsetHeight, 7168 maxVerticalScroll = carouselHeight - window.innerHeight, 7169 targetScrollY = this.scrollContainer.offsetTop + progress * maxVerticalScroll; 7170 return targetScrollY; 7171 } 7172 }, { 7173 key: "scrollToSlide", 7174 value: function scrollToSlide(slideIndex) { 7175 var targetScroll = this.getScrollYForSlide(slideIndex); 7176 window.scrollTo({ 7177 top: targetScroll, 7178 behavior: 'smooth' 7179 }); 7180 } 7181 }]); 7182 }(); 7183 7184 /***/ }), 7185 7186
7186/***/ "./src/js/vg-parallaxv2.js": 7187 /*!*********************************!*\ 7188 !*** ./src/js/vg-parallaxv2.js ***! 7189 \*********************************/ 7190 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 7191 7192 __webpack_require__.r(__webpack_exports__); 7193 /* harmony export */ __webpack_require__.d(__webpack_exports__, { 7194 /* harmony export */ VgParallax: () => (/* binding */ VgParallax) 7195 /* harmony export */ }); 7196 function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 7197 function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } 7198 function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } 7199 function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } 7200 function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } 7201 function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } 7202 function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } 7203 function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } 7204 function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } 7205 var VgParallax = /*#__PURE__*/function () { 7206 function VgParallax(base) { 7207 _classCallCheck(this, VgParallax); 7208 _defineProperty(this, "base", void 0); 7209 _defineProperty(this, "scrollContainer", void 0); 7210 _defineProperty(this, "carousel", void 0); 7211 _defineProperty(this, "slides", void 0); 7212 _defineProperty(this, "settings", void 0); 7213 _defineProperty(this, "defaults", void 0); 7214 this.base = base; 7215 this.defaults = { 7216 debug: true, 7217 parallaxablesSelector: '*[data-vg-parallaxable]', 7218 parallaxableEffectAttribute: 'data-vg-parallax-effect', 7219 parallaxableSpeedAttribute: 'data-vg-parallax-speed', 7220 disableWidth: 992, 7221 inViewPercent: 0.1, 7222 disabled: false 7223 }; 7224 } 7225 return _createClass(VgParallax, [{ 7226 key: "init", 7227 value: function init() { 7228 var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 7229 this.settings = _objectSpread(_objectSpread({}, this.defaults), params); 7230 this.base.setDebug(this.settings.debug); 7231 this.base.setDisableWidth(this.settings.disableWidth); 7232 } 7233 }, { 7234 key: "update", 7235 value: function update(slide, index) { 7236 var _this = this; 7237 if (this.settings.disabled) return; 7238 var rect = slide.getBoundingClientRect(), 7239 isVisible = this.base.isInViewport(slide, this.settings.inViewPercent), 7240 isMobile = this.base.isMobileWidth(); 7241 var parallaxbles = slide.querySelectorAll(this.settings.parallaxablesSelector); 7242 if (parallaxbles.length && isVisible && !isMobile) {
7243 parallaxbles.forEach(function (parallax) { 7244 var effect = parallax.dataset.vgParallaxEffect, 7245 speed = parseFloat(parallax.dataset.vgParallaxSpeed) || 20, 7246 // Paralax movement bounds has been extended past 1 to 1.5 7247 // whcih should effectively cause the parallaxed element to move 7248 // 50% past the left bounds of its slide container 7249 progress = Math.max(0, Math.min(2, (window.innerWidth - rect.left) / window.innerWidth)), 7250 params = {}; 7251 if (index === 0) { 7252 progress = Math.max(0, (window.innerWidth - rect.left) / window.innerWidth); 7253 if (/^fade-in|^slide-fade-in/.test(effect)) { 7254 progress = Math.max(1, (window.innerWidth - rect.left) / window.innerWidth) - 1; 7255 } 7256 } 7257 if (isVisible) { 7258 params = { 7259 parallax: parallax, 7260 effect: effect, 7261 progress: progress, 7262 speed: speed 7263 }; 7264 7265 // Apply different effects dynamically 7266 switch (true) { 7267 case /^slideIn/.test(effect): 7268 _this.handleSlideEffects(params); 7269 break; 7270 case /^slideZoom/.test(effect): 7271 _this.handleSlideZoomEffects(params); 7272 break; 7273 case /^slideFade/.test(effect): 7274 _this.handleSlideFadeEffects(params); 7275 break; 7276 case /^zoom/.test(effect): 7277 _this.handleZoomEffects(params); 7278 break; 7279 case /^fade/.test(effect): 7280 _this.handleFadeEffects(params); 7281 break; 7282 } 7283 } else { 7284 parallax.style.opacity = 0; // Hide when out of view 7285 } 7286 }); 7287 } else if (isMobile) {
7288 parallaxbles.forEach(function (parallax) { 7289 // show everything 7290 parallax.style.opacity = 1; 7291 }); 7292 } 7293 } 7294 }, { 7295 key: "handleSlideEffects", 7296 value: function handleSlideEffects(params) { 7297 switch (params.effect) { 7298 case "slideInLeft": 7299 params.parallax.style.transform = "translateX(".concat(((1 - params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7300 params.parallax.style.opacity = 1; // Make visible 7301 break; 7302 case "slideInRight": 7303 params.parallax.style.transform = "translateX(".concat(((-1 + params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7304 params.parallax.style.opacity = 1; // Make visible 7305 break; 7306 case "slideInBottom": 7307 params.parallax.style.transform = "translateY(".concat(((1 - params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7308 params.parallax.style.opacity = 1; // Make visible 7309 break; 7310 case "slideInTop": 7311 params.parallax.style.transform = "translateY(".concat(((-1 + params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7312 params.parallax.style.opacity = 1; // Make visible 7313 break; 7314 } 7315 } 7316 }, { 7317 key: "handleSlideZoomEffects", 7318 value: function handleSlideZoomEffects(params) { 7319 switch (params.effect) { 7320 case "slideZoomInLeft": 7321 params.parallax.style.transform = "scale(".concat(0.1 + params.progress * params.speed * 0.1, ") translateX(").concat(((1 - params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7322 params.parallax.style.opacity = 1; // Make visible 7323 break; 7324 case "slideZoomInRight": 7325 params.parallax.style.transform = "scale(".concat(0.1 + params.progress * params.speed * 0.1, ") translateX(").concat(((-1 + params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7326 params.parallax.style.opacity = 1; // Make visible 7327 break; 7328 case "slideZoomInBottom": 7329 params.parallax.style.transform = "scale(".concat(0.1 + params.progress * params.speed * 0.1, ") translateY(").concat(((1 - params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7330 params.parallax.style.opacity = 1; // Make visible 7331 break; 7332 case "slideZoomInTop": 7333 params.parallax.style.transform = "scale(".concat(0.1 + params.progress * params.speed * 0.1, ") translateY(").concat(((-1 + params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7334 params.parallax.style.opacity = 1; // Make visible 7335 break; 7336 } 7337 } 7338 }, { 7339 key: "handleSlideFadeEffects", 7340 value: function handleSlideFadeEffects(params) { 7341 switch (params.effect) { 7342 case "slideFadeInLeft": 7343 params.parallax.style.transform = "scale(".concat(0.1 + params.progress * params.speed * 0.1, ") translateX(").concat(((1 - params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7344 params.parallax.style.opacity = params.progress * params.speed; 7345 break; 7346 case "slideFadeInRight": 7347 params.parallax.style.transform = "scale(".concat(0.1 + params.progress * params.speed * 0.1, ") translateX(").concat(((-1 + params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7348 params.parallax.style.opacity = params.progress * params.speed; 7349 break; 7350 case "slideFadeInBottom": 7351 params.parallax.style.transform = "scale(".concat(0.1 + params.progress * params.speed * 0.1, ") translateY(").concat(((1 - params.progress) * params.speed).toFixed(2), "px) translate3d(0,0,0)"); 7352 params.parallax.style.opacity = params.progress * params.speed; 7353 break; 7354 case "slideFadeInTop": 7355 params.parallax.style.transform = "scale(".concat(0.1 + params.progress * params.speed * 0.1, ") translateY(").concat(((-1 + params.progress) * params.speed).toFixed(2), "px) translate3d(0,
73550,0)"); 7356 params.parallax.style.opacity = params.progress * params.speed; 7357 break; 7358 } 7359 } 7360 }, { 7361 key: "handleZoomEffects", 7362 value: function handleZoomEffects(params) { 7363 switch (params.effect) { 7364 case "zoomIn": 7365 params.parallax.style.transform = "scale(".concat((0.5 + params.progress * params.speed * 0.5).toFixed(2), ") translate3d(0,0,0)"); 7366 params.parallax.style.opacity = 1; // Make visible 7367 break; 7368 case "zoomOut": 7369 params.parallax.style.transform = "scale(".concat((1.5 - params.progress * params.speed * 0.5).toFixed(2), ") translate3d(0,0,0)"); 7370 params.parallax.style.opacity = 1; // Make visible 7371 break; 7372 } 7373 } 7374 }, { 7375 key: "handleFadeEffects", 7376 value: function handleFadeEffects(params) { 7377 switch (params.effect) { 7378 case "fadeIn": 7379 params.parallax.style.opacity = params.progress * params.speed; 7380 break; 7381 case "fadeOut": 7382 params.parallax.style.opacity = (1 - params.progress) * params.speed; 7383 break; 7384 } 7385 } 7386 }]); 7387 }(); 7388 7389 /***/ }), 7390 7391
7391/***/ "./src/scss/app-lite.scss": 7392 /*!********************************!*\ 7393 !*** ./src/scss/app-lite.scss ***! 7394 \********************************/ 7395 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 7396 7397 __webpack_require__.r(__webpack_exports__); 7398 // extracted by mini-css-extract-plugin 7399 7400 7401 /***/ }), 7402 7403
7403/***/ "./src/scss/app.scss": 7404 /*!***************************!*\ 7405 !*** ./src/scss/app.scss ***! 7406 \***************************/ 7407 /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 7408 7409 __webpack_require__.r(__webpack_exports__); 7410 // extracted by mini-css-extract-plugin 7411 7412 7413 /***/ })
vendor: 6,511 bytes, lines 7413-7565
7413 7414 7415 /******/ }); 7416 /************************************************************************/ 7417 /******/ // The module cache 7418 /******/ var __webpack_module_cache__ = {}; 7419 /******/ 7420 /******/ // The require function 7421 /******/ function __webpack_require__(moduleId) { 7422 /******/ // Check if module is in cache 7423 /******/ var cachedModule = __webpack_module_cache__[moduleId]; 7424 /******/ if (cachedModule !== undefined) { 7425 /******/ return cachedModule.exports; 7426 /******/ } 7427 /******/ // Create a new module (and put it into the cache) 7428 /******/ var module = __webpack_module_cache__[moduleId] = { 7429 /******/ // no module.id needed 7430 /******/ // no module.loaded needed 7431 /******/ exports: {} 7432 /******/ }; 7433 /******/ 7434 /******/ // Execute the module function 7435 /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 7436 /******/ 7437 /******/ // Return the exports of the module 7438 /******/ return module.exports; 7439 /******/ } 7440 /******/ 7441 /******/ // expose the modules object (__webpack_modules__) 7442 /******/ __webpack_require__.m = __webpack_modules__; 7443 /******/ 7444 /************************************************************************/ 7445 /******/ /* webpack/runtime/chunk loaded */ 7446 /******/ (() => { 7447 /******/ var deferred = []; 7448 /******/ __webpack_require__.O = (result, chunkIds, fn, priority) => { 7449 /******/ if(chunkIds) { 7450 /******/ priority = priority || 0; 7451 /******/ for(var i = deferred.length; i > 0 && deferred[i - 1][2] > priority; i--) deferred[i] = deferred[i - 1]; 7452 /******/ deferred[i] = [chunkIds, fn, priority]; 7453 /******/ return; 7454 /******/ } 7455 /******/ var notFulfilled = Infinity; 7456 /******/ for (var i = 0; i < deferred.length; i++) { 7457 /******/ var [chunkIds, fn, priority] = deferred[i]; 7458 /******/ var fulfilled = true; 7459 /******/ for (var j = 0; j < chunkIds.length; j++) { 7460 /******/ if ((priority & 1 === 0 || notFulfilled >= priority) && Object.keys(__webpack_require__.O).every((key) => (__webpack_require__.O[key](chunkIds[j])))) { 7461 /******/ chunkIds.splice(j--, 1); 7462 /******/ } else { 7463 /******/ fulfilled = false; 7464 /******/ if(priority < notFulfilled) notFulfilled = priority; 7465 /******/ } 7466 /******/ } 7467 /******/ if(fulfilled) { 7468 /******/ deferred.splice(i--, 1) 7469 /******/ var r = fn(); 7470 /******/ if (r !== undefined) result = r; 7471 /******/ } 7472 /******/ } 7473 /******/ return result; 7474 /******/ }; 7475 /******/ })(); 7476 /******/ 7477 /******/ /* webpack/runtime/define property getters */ 7478 /******/ (() => { 7479 /******/ // define getter functions for harmony exports 7480 /******/ __webpack_require__.d = (exports, definition) => { 7481 /******/ for(var key in definition) { 7482 /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 7483 /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 7484 /******/ } 7485 /******/ } 7486 /******/ }; 7487 /******/ })(); 7488 /******/ 7489 /******/ /* webpack/runtime/hasOwnProperty shorthand */ 7490 /******/ (() => { 7491 /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 7492 /******/ })(); 7493 /******/ 7494 /******/ /* webpack/runtime/make namespace object */ 7495 /******/ (() => { 7496 /******/ // define __esModule on exports 7497 /******/ __webpack_require__.r = (exports) => { 7498 /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 7499 /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 7500 /******/ } 7501 /******/ Object.defineProperty(exports, '__esModule', { value: true }); 7502 /******/ }; 7503 /******/ })(); 7504 /******/ 7505 /******/ /* webpack/runtime/jsonp chunk loading */ 7506 /******/ (() => { 7507 /******/ // no baseURI 7508 /******/ 7509 /******/ // object to store loaded and loading chunks 7510 /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 7511 /******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded 7512 /******/ var installedChunks = { 7513 /******/ "/dist/js/app": 0, 7514 /******/ "dist/css/app": 0, 7515 /******/ "dist/css/app-lite": 0 7516 /******/ }; 7517 /******/ 7518 /******/ // no chunk on demand loading 7519 /******/ 7520 /******/ // no prefetching 7521 /******/ 7522 /******/ // no preloaded 7523 /******/ 7524 /******/ // no HMR 7525 /******/ 7526 /******/ // no HMR manifest 7527 /******/ 7528 /******/ __webpack_require__.O.j = (chunkId) => (installedChunks[chunkId] === 0); 7529 /******/ 7530 /******/ // install a JSONP callback for chunk loading 7531 /******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => { 7532 /******/ var [chunkIds, moreModules, runtime] = data; 7533 /******/ // add "moreModules" to the modules object, 7534 /******/ // then flag all "chunkIds" as loaded and fire callback 7535 /******/ var moduleId, chunkId, i = 0; 7536 /******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) { 7537 /******/ for(moduleId in moreModules) { 7538 /******/ if(__webpack_require__.o(moreModules, moduleId)) { 7539 /******/ __webpack_require__.m[moduleId] = moreModules[moduleId]; 7540 /******/ } 7541 /******/ } 7542 /******/ if(runtime) var result = runtime(__webpack_require__); 7543 /******/ } 7544 /******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data); 7545 /******/ for(;i < chunkIds.length; i++) { 7546 /******/ chunkId = chunkIds[i]; 7547 /******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) { 7548 /******/ installedChunks[chunkId][0](); 7549 /******/ } 7550 /******/ installedChunks[chunkId] = 0; 7551 /******/ } 7552 /******/ return __webpack_require__.O(result); 7553 /******/ } 7554 /******/ 7555 /******/ var chunkLoadingGlobal = self["webpackChunkparallax"] = self["webpackChunkparallax"] || []; 7556 /******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0)); 7557 /******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal)); 7558 /******/ })(); 7559 /******/ 7560 /************************************************************************/ 7561 /******/ 7562 /******/ // startup 7563 /******/ // Load entry module and return exports 7564 /******/ // This entry module depends on other loaded chunks and execution need to be delayed 7565 /******/ __webpack_require__.O(undefined, ["dist/css/app","dist/css/app-lite"], (
7565) => (__webpack_require__("./src/js/app.js"))) 7566 /******/ __webpack_require__.O(undefined, ["dist/css/app","dist/css/app-lite"], () => (__webpack_require__("./src/scss/app-lite.scss"))) 7567 /******/ var __webpack_exports__ = __webpack_require__.O(undefined, ["dist/css/app","dist/css/app-lite"], () => (__webpack_require__("./src/scss/app.scss"))) 7568 /******/ __webpack_exports__ = __webpack_require__.O(__webpack_exports__); 7569 /******/ 7570 /******/ })() 7571 ; 7572// ============================================================================= 7573// clientErrorLogger.js 7574// ============================================================================= 7575// PURPOSE: Ships client-side JS errors to the server so they appear in 7576// logs/error.log and are discoverable via Splunk. Uses function declaration 7577// (hoisted) so it is safe for any file in this bundle to call, regardless of 7578// concatenation order. 7579// 7580// USAGE: logClientError("ERROR" | "WARN", message, source) 7581// 7582// Example: logClientError("WARN", "Error processing URL: " + href, "external-link-icon.js"); 7583// 7584// WHY FIRE-AND-FORGET: A logging utility that throws or blocks on network 7585// errors would make every catch block worse. The inner try/catch guarantees 7586// this function is always safe to call. 7587// ============================================================================= 7588 7589function logClientError(level, message, source) { 7590 try { 7591 if (level !== "ERROR" && level !== "WARN") return; 7592 let msg = String(message != null ? message : "").substring(0, 500); 7593 let src = String(source != null ? source : "").substring(0, 200); 7594 if (!msg || !src) return; 7595 $.ajax({ 7596 type: "GET", 7597 url: "/api/corporatesite/client-log", 7598 data: { 7599 level: level, 7600 message: msg, 7601 source: src, 7602 url: window.location.href 7603 } 7604 }); 7605 } catch (e) { /* must never throw â logging failures must not impact UX */ } 7606} 7607 7608window.addEventListener('pageshow', function (event) { 7609 if (event.persisted) { 7610 window.location.reload(); 7611 } 7612}); 7613 7614if (performance.navigation.type === 2) { 7615 window.location.reload(); 7616} 7617 7618const errorIcon = 7619 `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor" class="bi bi-exclamation-diamond-fill" viewBox="0 0 16 16"> 7620 <path d="M9.05.435c-.58-.58-1.52-.58-2.1 0L.436 6.95c-.58.58-.58 1.519 0 2.098l6.516 6.516c.58.58 1.519.58 2.098 0l6.516-6.516c.58-.58.58-1.519
76200-2.098L9.05.435zM8 4c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 4.995A.905.905 0 0 1 8 4zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/> 7621 </svg>`; 7622 7623function validatingEmail(elementID) { 7624 const field = document.getElementById(elementID); 7625 const errorMessage = document.createElement('span'); 7626 errorMessage.setAttribute('role', 'alert'); 7627 errorMessage.setAttribute('tabindex', 0); 7628 const mailformat = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; 7629 const data = jQuery(`#${elementID}`).val(); 7630 const requiredEmail = document.getElementById('errorRequired'); 7631 const invalidEmail = document.getElementById('errorInvalid'); 7632 7633 if (data === '') { 7634 errorMessage.setAttribute('id', 'errorRequired'); 7635 errorMessage.innerHTML = `${errorIcon} <b>Error:</b> Email is required`; 7636 7637 if (!requiredEmail) { 7638 field.parentNode.insertBefore(errorMessage, field.nextSibling); 7639 field.focus(); 7640 } 7641 if (invalidEmail) { 7642 invalidEmail.remove(); 7643 } 7644 return false; 7645 7646 } else if (data !== '' && !data.match(mailformat)) { 7647 errorMessage.setAttribute('id', 'errorInvalid'); 7648 errorMessage.innerHTML = `${errorIcon} <b>Error:</b> Email is not valid`; 7649 7650 if (!invalidEmail) { 7651 field.parentNode.insertBefore(errorMessage, field.nextSibling); 7652 field.focus(); 7653 } 7654 if (requiredEmail) { 7655 requiredEmail.remove(); 7656 } 7657 return false; 7658 } 7659 7660 if (invalidEmail) { 7661 invalidEmail.remove(); 7662 } 7663 if (requiredEmail) { 7664 requiredEmail.remove(); 7665 } 7666 return true; 7667} 7668 7669function validatingIndustry(elementID) { 7670 const field = document.getElementById(elementID); 7671 const errorMessage = document.createElement('span'); 7672 errorMessage.setAttribute('id', 'errorIndustry'); 7673 errorMessage.setAttribute('role', 'alert'); 7674 errorMessage.setAttribute('tabindex', 0); 7675 const data = jQuery(`#${elementID}`).val(); 7676 const errorIndustry = document.getElementById('errorIndustry'); 7677 7678 if (data === 'Select industry...' && !errorIndustry) { 7679 errorMessage.innerHTML = `${errorIcon} <b>Error:</b> No industry selected`; 7680 field.parentNode.insertBefore(errorMessage, field.nextSibling); 7681 field.focus(); 7682 return false; 7683 } else if (data !== 'Select industry...' && errorIndustry) { 7684 errorIndustry.remove(); 7685 return false; 7686 } else if (data === 'Select industry...' && errorIndustry) { 7687 field.focus(); 7688 return false; 7689 } else { 7690 return true; 7691 } 7692} 7693 7694function displayErrorMessage(errorSpan){ 7695 errorSpan.innerHTML = `${errorIcon} <b>Error:</b> ${errorSpan.getAttribute('data-error-message')}`; 7696 errorSpan.setAttribute('class', 'errorMessageShow'); 7697} 7698 7699function hideErrorMessage(errorSpan){ 7700 errorSpan.innerHTML = ''; 7701} 7702 7703function generateValidator(inputValidationFx) { 7704 return function generateValidatorInner(inputId, errorSpanId) { 7705 let input; 7706 if (inputId){ 7707 input = jQuery(`#${inputId}`).val(); 7708 } 7709 7710 const errorSpan = jQuery(`#${errorSpanId}`)[0]; 7711 if (inputValidationFx(input)) { 7712 errorSpan.innerHTML = ''; 7713 errorSpan.setAttribute('class', 'errorMessageHide'); 7714 jQuery(`#${inputId}`).removeClass("errorMessageShow"); 7715 return true; 7716 } else { 7717 displayErrorMessage(errorSpan); 7718 jQuery(`#${inputId}`).addClass("errorMessageShow"); 7719 return false; 7720 } 7721 } 7722} 7723 7724function emailValidation(emailString) { 7725 const emailRegex = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; 7726 const isEmpty = emailString === ''; 7727 const isValid = emailRegex.test(emailString); 7728 return !isEmpty && isValid; 7729} 7730 7731function industryValidation(industry) { 7732 return industry !== 'Select industry...'; 7733} 7734 7735function optionalPhoneValidation(phone) { 7736 const regex = /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/; 7737 const isEmpty = phone === ''; 7738 const isValid = regex.test(phone); 7739 return isEmpty || isValid; 7740} 7741 7742function nameValidation(name) { 7743 const regex = /^[^- '](?=(?!.*[- '][- '.]))(?=(?!.*[.][-'.]))[A-Za-z0-9- '.]{2,}$/; 7744 const isEmpty = name === ''; 7745 const isValid = regex.test(name); 7746 return isEmpty || isValid; 7747} 7748 7749function requiredFieldValidation(field) { 7750 const isEmpty = field === ''; 7751 return !isEmpty; 7752} 7753 7754function requiredDropdownValidation(selectedOption) { 7755 return selectedOption !== 'Choose'; 7756} 7757 7758function requiredStateValidation(state) { 7759 const regex = /^[a-zA-Z]{2}$/; 7760 const isEmpty = state === ''; 7761 const isValid = regex.test(state); 7762 return !isEmpty && isValid; 7763} 7764 7765function requiredZipCodeValidation(zip) { 7766 const regex = /^\d{5}
7766$/; 7767 const isEmpty = zip === ''; 7768 const isValid = regex.test(zip); 7769 return !isEmpty && isValid; 7770} 7771 7772const validateEmail = generateValidator(emailValidation); 7773const validateIndustry = generateValidator(industryValidation); 7774const validateOptionalPhone = generateValidator(optionalPhoneValidation); 7775const validateRequiredField = generateValidator(requiredFieldValidation); 7776const validateRequiredDropdown = generateValidator(requiredDropdownValidation); 7777const validateRequiredState = generateValidator(requiredStateValidation); 7778const validateRequiredZipCode = generateValidator(requiredZipCodeValidation); 7779const validateName = generateValidator(nameValidation); 7780// formats input with numbers only, removing other non-numeric characters 7781function formatNumericInput(input) { 7782 return input.replace(/\D/g, ''); 7783} 7784 7785// formats input with alphabetic characters only, removing other non-alpha characters 7786function formatAlphabeticInput(input) { 7787 return input.replace(/[^a-zA-Z]/g, ''); 7788} 7789 7790// formats phone number as "(xxx) xxx-xxxx" 7791function formatPhoneInput(input) { 7792 input = input.replace(/\D/g, ''); 7793 const size = input.length; 7794 if (size > 0) { 7795 input = '(' + input; 7796 } 7797 if (size > 3) { 7798 input = input.slice(0,4) + ') ' + input.slice(4,11); 7799 } 7800 if (size > 6) { 7801 input = input.slice(0,9) + '-' + input.slice(9); 7802 } 7803 return input; 7804} 7805 7806// formats dollar amount with commas, e.g. "$xxx,xxx,xxx" 7807function formatDollarAmountInput(input) { 7808 input = input.replace(/\D/g, ''); 7809 const size = input.length; 7810 if (size > 0) { 7811 input = '$' + input; 7812 } 7813 return input.replace(/\B(?=(\d{3})+(?!\d))/g, ','); 7814} 7815 7816// ============================================================================= 7817// youtube-one-trust.js 7818// ============================================================================= 7819// PURPOSE: Shared utility layer for OneTrust cookie consent and YouTube IFrame 7820// API bootstrapping. This file is intentionally framework-free â no classes, 7821// no imports. It exposes plain functions that every YouTube/Wistia player class 7822// in this bundle calls directly. 7823// 7824// WHY IT EXISTS AS PLAIN FUNCTIONS (not a class): 7825// AEM concatenates all clientlib JS files into a single flat bundle with no 7826// module system. Functions declared with the `function` keyword are hoisted to 7827// the top of the bundle scope, making them available to every other file 7828// regardless of load order. Class expressions and `const`/`let` are NOT hoisted 7829// and would cause ReferenceErrors if any class file happened to be concatenated 7830// before this file. The js.txt ordering guarantees this file loads first, but 7831// the function-declaration pattern provides an extra safety net. 7832// 7833// RELATIONSHIP TO OTHER FILES: 7834// - YouTubePlayerBase.js calls _wireOneTrustAccepted / _wireOneTrustClosed 7835// which in turn call setupOneTrustAcceptListener / setupOneTrustCloseListener 7836// - WistiaVideoPlayerFallback.js and WistiaVideoBannerFallback.js call 7837// showOneTrustModal, setupOneTrustAcceptListener, setupOneTrustCloseListener 7838// directly when the Wistia player falls back to YouTube without consent 7839// - WistiaFallback.js calls loadYouTubeApi to bootstrap the YouTube IFrame API 7840// ============================================================================= 7841 7842 7843// ----------------------------------------------------------------------------- 7844// COOKIE UTILITIES 7845// These functions read and parse the OneTrust consent cookie to determine 7846// whether the user has granted targeting/advertising consent (group C0004). 7847// OneTrust sets a cookie named 'OptanonConsent' whose value is a URL-encoded 7848// string containing a 'groups' field, e.g. "groups=C0001:1,C0003:0,C0004:1". 7849// ----------------------------------------------------------------------------- 7850 7851// Reads a named field from within a named cookie value. 7852// Returns the field value as a string, or null if the cookie or field is absent. 7853function getCookieOneTrustCookie(name, field) { 7854 const value = `; ${document.cookie}`; 7855 const parts = value.split(`; ${name}=`); 7856 let cookieValue; 7857 if (parts.length >= 2) { 7858 cookieValue = parts.pop().split(';').shift(); 7859 } 7860 if (!cookieValue) return null; 7861 let decodedCookieValue = cookieValue; 7862 try { 7863 decodedCookieValue = decodeURIComponent(cookieValue); 7864 } catch (e) { 7865 console.error(`[OneTrust] Error decoding cookie value for ${name}:`, e); 7866 logClientError("ERROR", "[OneTrust] Error decoding cookie value for " + name + ": " + (e && e.message ? e.message :
7866String(e)), "youtube-one-trust.js"); 7867 decodedCookieValue = cookieValue; 7868 } 7869 const params = new URLSearchParams(decodedCookieValue); 7870 return params.get(field); 7871} 7872 7873// Returns true if the user has explicitly enabled targeting cookies (group C0004). 7874// This is the primary consent signal used when a player is first initialised. 7875// "C0004:1" means the group is enabled; "C0004:0" means disabled or not set. 7876function hasOneTrustTargetingConsent() { 7877 // Author simulation flag: set via page properties, rendered only when wcmmode=disabled. 7878 // Allows authors on TEST to preview YouTube players without a working OneTrust cookie. Removing for PROD 7879 //const analyticsDiv = document.getElementById('analytics-details'); 7880 //if (analyticsDiv && analyticsDiv.getAttribute('data-simulate-youtube-consent') === 'false') { 7881 // return false; 7882 //} 7883 const groups = getCookieOneTrustCookie('OptanonConsent', 'groups'); 7884 if (!groups) return false; 7885 const groupArr = groups.split(','); 7886 const c0004 = groupArr.find(g => g.startsWith('C0004:')); 7887 return c0004 === 'C0004:1'; 7888} 7889 7890 7891// ----------------------------------------------------------------------------- 7892// LEGACY CONSENT UI HELPERS 7893// These functions support the older procedural videoBannerFunctionality.js and 7894// videoComponentFunctionality.js files. They are NOT used by the new class-based 7895// architecture â new code uses setupOneTrustAcceptListener instead. 7896// Kept here to avoid breaking the legacy path until it is fully removed. 7897// ----------------------------------------------------------------------------- 7898 7899// Builds the inline consent message DOM node shown inside the video wrapper 7900// when the user has not yet granted consent. 7901function createOneTrustMessageConsent() { 7902 let messageConsentContainer = document.createElement('div'); 7903 messageConsentContainer.className = 'one-trust-message-consent_container'; 7904 let messageConsentText = document.createElement('p'); 7905 messageConsentText.className = 'one-trust-message-consent__text'; 7906 messageConsentText.innerHTML = 'You have certain cookies disabled on the Vanguard site. <br/>' + 7907 'In order to watch this video, you must agree to the use of cookies provided by YouTube. <br/>' + 7908 'Click <button class="video-one-trust-button-ack" title="Accept YouTube cookies" aria-label="Accept YouTube' + 7909 ' cookies"><span class="video-one-trust-label">here</span></button> ' + 7910 'to permit these cookies and watch the video. <br/>'; 7911 messageConsentContainer.appendChild(messageConsentText); 7912 return messageConsentContainer; 7913} 7914 7915// Injects a OneTrust disclosure overlay into wrapperElement for a given player index. 7916// Called by the legacy procedural code when a player is rendered without consent. 7917function createOneTrustDisclosure(wrapperElement, index) { 7918 let oneTrustContainer = document.createElement('div'); 7919 oneTrustContainer.className = 'video-one-trust-container'; 7920 oneTrustContainer.appendChild(createOneTrustMessageConsent()); 7921 wrapperElement.appendChild(oneTrustContainer); 7922} 7923 7924// Removes the consent overlay from a video container after consent is granted. 7925// Uses jQuery (legacy code dependency) to find and clean up the overlay elements. 7926function removeOverlayConsent(videoContainer) { 7927 videoContainer.find('.youtube-wrapper').removeClass('video-consent--overlay'); 7928 videoContainer.find('.video-one-trust-container').remove(); 7929} 7930 7931// Wires the close/dismiss buttons of the OneTrust modal for the legacy procedural 7932// players. Cleans up player iframes and UI elements when the user dismisses 7933// without granting consent, and removes the YouTubeCookieAccepted localStorage key. 7934function oneTrustCloseMessageClicked(playerElementsBanner, playerElementsVideo) { 7935 $('.hide-one-trust-modal, .close-one-trust-modal').click(function () { 7936 if (playerElementsBanner) { 7937 playerElementsBanner.forEach(function (element) { 7938 let videoWrapper = $(element).closest('.youtube-wrapper'); 7939 if (videoWrapper.find('.youtube-player')) { 7940 videoWrapper.find('.youtube-player').remove(); 7941 } 7942 if (videoWrapper.find('.player-overlay')) { 7943 videoWrapper.find('.player-overlay').remove(); 7944 } 7945 }); 7946 } 7947 if (playerElementsVideo) { 7948 playerElementsVideo.forEach(function (element) { 7949 let videoPlayerContainer = $(element).closest('.main-youtube-container'); 7950 videoPlayerContainer.find('.video-banner .play-pause-btn').remove(); 7951 videoPlayerContainer.find('.video-transcript-container').remove(); 7952 videoPlayerContainer.find('.video-transcript-modal').remove(); 7953 }); 7954 } 7955 if (localStorage.getItem('YouTubeCookieAccepted')) { 7956 localStorage.removeItem('YouTubeCookieAccepted'); 7957 } 7958 $(this).closest('.one-trust-modal').removeClass('show-one-trust-modal'); 7959 $(this).closest('.one-trust-modal').attr('aria-hidden', 'true'); 7960 document.querySelector('body').style.overflow = 'visible'; 7961 }); 7962} 7963 7964// Extracts the numeric player index from a player element id.
7965// Player ids follow the pattern "youtube-player-0", "video-youtube-player-1" etc. 7966// The index is always the last segment after the final dash. 7967function getVideoIndexFromId(elementId) { 7968 const parts = elementId.split('-'); 7969 return parseInt(parts[parts.length - 1], 10); 7970} 7971 7972 7973// ----------------------------------------------------------------------------- 7974// SHARED MODAL STATE AND CALLBACK QUEUES 7975// Used by both the native YouTube player classes and the Wistia fallback classes. 7976// 7977// var is intentional: AEM concatenates all clientlib JS into a single bundle with 7978// no module scope. These variables must be accessible to showOneTrustModal, 7979// setupOneTrustAcceptListener, and setupOneTrustCloseListener across the same 7980// flat script scope. 7981// 7982// CALLBACK QUEUE PATTERN: 7983// Multiple player instances on the same page (e.g. two Wistia fallbacks) may each 7984// call setupOneTrustAcceptListener before the user responds to the modal. Rather 7985// than registering one DOM listener per caller (which risks duplicate handlers), 7986// a single DOM listener is registered once (singleton via the flags in 7987// _oneTrustState). Every caller pushes its callback into the queue. When the user 7988// accepts or closes, ALL queued callbacks are drained atomically. This ensures no 7989// instance is silently skipped because it registered after the first one. 7990// ----------------------------------------------------------------------------- 7991var _oneTrustState = { 7992 modalShown: false, // true once the modal has been displayed 7993 modalClosed: false, // true once the user explicitly dismissed without accepting 7994 closeListenerSet: false, // true once the DOM close listener has been registered 7995 acceptListenerSet: false, // true once the DOM accept listener has been registered 7996}; 7997var _oneTrustAcceptCallbacks = []; // callbacks waiting for user to grant consent 7998var _oneTrustCloseCallbacks = []; // callbacks waiting for user to dismiss without consent 7999 8000 8001// ----------------------------------------------------------------------------- 8002// MODAL CONTROL 8003// ----------------------------------------------------------------------------- 8004 8005// Shows the OneTrust consent modal to the user. 8006// Guards against double-display: skips silently if the modal is already visible, 8007// has already been shown, or has already been closed in this page session. 8008// Also skips in AEM Author mode (data-isauthor="true") to avoid blocking authors. 8009function showOneTrustModal() { 8010 const modal = document.querySelector('#oneTrustModal'); 8011 if (!modal || modal.getAttribute('data-isauthor') !== 'false') { 8012 return; 8013 } 8014 if (modal.classList.contains('show-one-trust-modal')) { 8015 _oneTrustState.modalShown = true; 8016 return; 8017 } 8018 if (_oneTrustState.modalShown || _oneTrustState.modalClosed) { 8019 return; 8020 } 8021 _oneTrustState.modalShown = true; 8022 modal.classList.add('show-one-trust-modal'); 8023 modal.setAttribute('aria-hidden', 'false'); 8024 document.body.style.overflow = 'hidden'; // prevent background scrolling while modal is open 8025} 8026 8027 8028// ----------------------------------------------------------------------------- 8029// CONSENT LISTENER SETUP 8030// ----------------------------------------------------------------------------- 8031 8032// Registers a callback to be invoked when the user clicks the accept button on 8033// the OneTrust modal. Safe to call from multiple player instances â only one DOM 8034// listener is ever attached (singleton), but all callbacks are queued and drained 8035// together when the user accepts. 8036// 8037// The acceptListenerSet flag NEVER resets to false after firing â the DOM listener 8038// stays permanently attached so future instances created after an acceptance event 8039// (e.g. lazy-loaded components) can still queue callbacks for the next consent flow. 8040function setupOneTrustAcceptListener(onAccept) { 8041 // Queue the callback regardless â all queued callbacks are drained together when the user 8042 // accepts, ensuring every player instance on the page receives the consent signal. 8043 _oneTrustAcceptCallbacks.push(onAccept); 8044 8045 // Only one DOM listener is ever registered (singleton). Subsequent callers just queue above. 8046 if (_oneTrustState.acceptListenerSet) { 8047 return; 8048 } 8049 _oneTrustState.acceptListenerSet = true; 8050 8051 const button = document.querySelector('#oneTrustCookieAcceptance'); 8052 if (!button) { 8053 return; 8054 } 8055 button.addEventListener('click', function(e) { 8056 e.preventDefault(); 8057 // Persist consent in localStorage so subsequent page loads and Intersection Observer 8058 // checks can verify consent without re-reading the OneTrust cookie every time. 8059 localStorage.setItem('YouTubeCookieAccepted', 'true'); 8060 const modal = button.closest('.one-trust-modal'); 8061 if (modal) { 8062 modal.classList.remove('show-one-trust-modal'); 8063 modal.setAttribute('aria-hidden', 'true'); 8064 } 8065 document.body.style.overflow = 'visible'; 8066 window.dataLayer = window.dataLayer || []; 8067 window.dataLayer.push({ 8068 'event': 'callToAction', 8069 'callToAction': { 8070 'location': `OneTrustModal: ${document.title.split('|').pop().trim()}`,
8071 'ctaName': 'Accept YouTube Cookies', 8072 'ctaType': 'custom button', 8073 }, 8074 }); 8075 // Drain atomically: splice clears the queue before iterating so any callbacks registered 8076 // during the drain are held for the next acceptance event rather than called twice. 8077 _oneTrustAcceptCallbacks.splice(0).forEach(function(fn) { fn(); }); 8078 // Flag intentionally stays true â the listener remains for any future instances that may 8079 // be created after this acceptance (e.g. lazy-loaded components). 8080 }); 8081} 8082 8083// Registers a callback to be invoked when the user dismisses the OneTrust modal 8084// without accepting. onClose may be null â callers that only need the modal closed 8085// without any custom teardown can pass null and this function still handles the 8086// singleton DOM listener registration. 8087// 8088// Mirrors the same singleton + queue pattern as setupOneTrustAcceptListener. 8089function setupOneTrustCloseListener(onClose) { 8090 // Queue the callback if provided (may be null for callers that only need the modal closed). 8091 if (onClose) _oneTrustCloseCallbacks.push(onClose); 8092 8093 // Only one set of DOM listeners is ever registered (singleton). Subsequent callers just queue above. 8094 if (_oneTrustState.closeListenerSet) { 8095 return; 8096 } 8097 _oneTrustState.closeListenerSet = true; 8098 document.querySelectorAll('.hide-one-trust-modal, .close-one-trust-modal').forEach(function(btn) { 8099 btn.addEventListener('click', function() { 8100 _oneTrustState.modalClosed = true; 8101 // Remove the localStorage key so consent is not falsely assumed on next page load. 8102 localStorage.removeItem('YouTubeCookieAccepted'); 8103 btn.closest('.one-trust-modal') && btn.closest('.one-trust-modal').classList.remove('show-one-trust-modal'); 8104 btn.closest('.one-trust-modal') && btn.closest('.one-trust-modal').setAttribute('aria-hidden', 'true'); 8105 document.body.style.overflow = 'visible'; 8106 window.dataLayer = window.dataLayer || []; 8107 window.dataLayer.push({ 8108 'event': 'callToAction', 8109 'callToAction': { 8110 'location': `OneTrustModal: ${document.title.split('|').pop().trim()}`, 8111 'ctaName': 'Declined YouTube Cookies', 8112 'ctaType': 'custom button', 8113 }, 8114 }); 8115 // Drain atomically â same pattern as the accept listener. 8116 _oneTrustCloseCallbacks.splice(0).forEach(function(fn) { fn(); }); 8117 // Flag intentionally stays true â listeners remain for future instances. 8118 }); 8119 }); 8120} 8121 8122 8123// ----------------------------------------------------------------------------- 8124// YOUTUBE IFRAME API BOOTSTRAPPING 8125// ----------------------------------------------------------------------------- 8126 8127// Ensures the YouTube IFrame API script is loaded and calls onReady when the API 8128// is available. Safe to call multiple times â idempotent by design. 8129// 8130// HOW THE YOUTUBE IFRAME API WORKS: 8131// YouTube requires a <script src="https://www.youtube.com/iframe_api"> tag in the 8132// page. Once loaded, the API calls window.onYouTubeIframeAPIReady(). After that, 8133// new YT.Player(...) can be called to create player instances. 8134// 8135// HOW THIS FUNCTION HANDLES MULTIPLE CALLERS: 8136// Each Wistia fallback instance (and potentially other components) calls 8137// loadYouTubeApi independently. To avoid injecting the script tag multiple times 8138// and to support multiple onReady callbacks, this function: 8139// 1. If YT.Player already exists, calls onReady immediately. 8140// 2. Otherwise, chains the new onReady callback onto any existing 8141// window.onYouTubeIframeAPIReady handler (preserving previous callers). 8142// 3. Only injects the <script> tag if it is not already in the DOM. 8143function loadYouTubeApi(onReady) { 8144 if (window.YT && window.YT.Player) { 8145 onReady(); 8146 return; 8147 } 8148 const prev = window.onYouTubeIframeAPIReady; 8149 window.onYouTubeIframeAPIReady = function () { 8150 onReady(); 8151 if (prev) prev(); // preserve any previously registered onYouTubeIframeAPIReady handler 8152 }; 8153 if (!document.querySelector('script[src*="youtube.com/iframe_api"]')) { 8154 const tag = document.createElement('script'); 8155 tag.src = 'https://www.youtube.com/iframe_api'; 8156 document.head.appendChild(tag); 8157 } else { 8158 console.debug('[OneTrust] loadYouTubeApi: YT API script already in DOM, waiting for callback'); 8159 } 8160} 8161 8162// ============================================================================= 8163// YouTubePlayerBase.js 8164// ============================================================================= 8165// PURPOSE: Abstract base class for all YouTube player implementations on the site.
8166// Provides the shared infrastructure that every player type needs: consent state, 8167// player instance storage, banner visibility helpers, and OneTrust wiring. 8168// 8169// CLASS HIERARCHY (read this top-to-bottom to understand the full picture): 8170// 8171// YouTubePlayerBase â YOU ARE HERE â shared infrastructure 8172// âââ VideoBannerPlayer â autoplay muted background banners 8173// â âââ WistiaVideoBannerFallback â Wistia â YouTube fallback (banner mode) 8174// âââ VideoComponentPlayer â click-to-play inline video with analytics 8175// âââ WistiaVideoPlayerFallback â Wistia â YouTube fallback (player mode) 8176// 8177// CONSENT MODEL: 8178// YouTube cookies require explicit user consent under GDPR/CCPA (OneTrust group 8179// C0004). Players must not create YT.Player instances until consent is confirmed. 8180// Consent is tracked in two ways: 8181// 1. Constructor-injected `hasConsent` flag (read from OneTrust cookie at init time) 8182// 2. 'YouTubeCookieAccepted' localStorage key (set when user clicks Accept on the modal) 8183// _hasConsent() checks both, ensuring late-granted consent is always picked up. 8184// 8185// PROTECTED METHOD CONVENTION: 8186// Methods prefixed with _ are "protected" â they are intended to be called by 8187// subclasses only, not by external code. JavaScript does not enforce this, but 8188// the convention signals intent. Private methods (using #name syntax) are truly 8189// private and cannot be accessed outside this class at all. 8190// 8191// HOW TO ADD A NEW PLAYER TYPE: 8192// 1. Create a new class that extends YouTubePlayerBase (or one of its subclasses). 8193// 2. Implement _buildPlayerId(index), _initElement(element, index), and 8194// _onConsentGranted() â these are declared abstract via thrown errors below. 8195// 3. Call super(playerElements, hasConsent) from your constructor. 8196// 4. Call this.init() from the entry point that creates your player. 8197// ============================================================================= 8198 8199class YouTubePlayerBase { 8200 // Private fields â inaccessible outside this class, even to subclasses. 8201 // Subclasses must use the provided protected accessors (_hasConsent, _getPlayer, etc.) 8202 #hasConsent; // boolean: was consent granted when this instance was constructed? 8203 #playerElements; // HTMLElement[]: the DOM elements that become YouTube player containers 8204 #players = []; // YT.Player[]: indexed player instances, populated as players are created 8205 8206 // playerElements: NodeList or Array of DOM elements to initialise as YouTube players. 8207 // Pass an empty array [] for Wistia fallback classes that manage their 8208 // own element â they call _registerPlayer / _createYTPlayer directly. 8209 // hasConsent: boolean â whether the user has consented at construction time. 8210 // Use hasOneTrustTargetingConsent() || localStorage check in the caller. 8211 constructor(playerElements, hasConsent) { 8212 this.#playerElements = playerElements; 8213 this.#hasConsent = hasConsent; 8214 } 8215 8216 // --- Public getters --- 8217 // playerElements and players are exposed read-only to subclasses and external 8218 // initialisation code that needs to iterate over them. 8219 8220 get playerElements() { return this.#playerElements; } 8221 get players() { return this.#players; } 8222 8223 // Delegates to _hasConsent() so both the public getter and the method return 8224 // the same authoritative value â avoids silent divergence if consent is granted 8225 // post-construction via localStorage. 8226 get hasConsent() { return this._hasConsent(); } 8227 8228 // --- Lifecycle --- 8229 8230 // Entry point. Call this after constructing a player instance. 8231 // Assigns stable IDs to all player elements, initialises each one, then 8232 // wires up the OneTrust accept/close listeners so the player reacts to 8233 // future consent changes without needing to be reconstructed. 8234 init() { 8235 this.#playerElements.forEach((element, index) => { 8236 element.id = this._buildPlayerId(index); 8237 this._initElement(element, index); 8238 }); 8239 8240 this._wireOneTrustAccepted(); 8241 this._wireOneTrustClosed(); 8242 } 8243 8244 // --- Abstract methods (must be implemented by every subclass) --- 8245 // These throw errors at runtime if a subclass forgets to implement them, 8246 // acting as a lightweight interface contract. 8247 8248 // Returns the string id to assign to the player container element.
8249 // e.g. VideoBannerPlayer returns "youtube-player-0", "youtube-player-1", etc. 8250 _buildPlayerId(index) { 8251 throw new Error('_buildPlayerId must be implemented by subclass'); 8252 } 8253 8254 // Called once per element during init(). Subclasses use this to read data 8255 // attributes, create DOM structures (banners, overlays), and create the 8256 // YT.Player if consent is already available. 8257 _initElement(element, index) { 8258 throw new Error('_initElement must be implemented by subclass'); 8259 } 8260 8261 // Called when the user grants consent via the OneTrust modal after the 8262 // player was already initialised without consent. Subclasses use this to 8263 // create deferred YT.Player instances and reveal any previously hidden UI. 8264 _onConsentGranted() { 8265 throw new Error('_onConsentGranted must be implemented by subclass'); 8266 } 8267 8268 // --- OneTrust wiring --- 8269 // These methods register the player with the shared OneTrust callback queues 8270 // defined in youtube-one-trust.js. They are called automatically by init(). 8271 // Subclasses should not need to call these directly. 8272 8273 // Registers this player's consent-granted handler with the shared accept queue. 8274 // When the user clicks Accept on the OneTrust modal, _onConsentGranted() fires. 8275 _wireOneTrustAccepted() { 8276 setupOneTrustAcceptListener(() => { 8277 this._onConsentGranted(); 8278 }); 8279 } 8280 8281 // Registers this player's consent-denied handler with the shared close queue. 8282 // When the user dismisses the modal without accepting, _onConsentDenied() fires. 8283 // The base implementation is a no-op â subclasses override it if they need 8284 // to tear down UI elements when consent is revoked. 8285 _wireOneTrustClosed() { 8286 setupOneTrustCloseListener(() => { 8287 this._onConsentDenied(); 8288 }); 8289 } 8290 8291 // Default no-op â subclasses override to clean up UI on consent denial. 8292 _onConsentDenied() { 8293 console.debug(`[YTBase] ${this.constructor.name}: _onConsentDenied (default no-op)`); 8294 } 8295 8296 // --- Banner visibility helpers --- 8297 // Banner elements (thumbnail images shown before/after video plays) are toggled 8298 // by adding/removing the 'banner-hidden' CSS class. This is the single place 8299 // where that toggle lives â do not manipulate banner visibility directly elsewhere. 8300 8301 // Reveals the banner with the given DOM id by removing 'banner-hidden'. 8302 _showBanner(bannerId) { 8303 const banner = document.getElementById(bannerId); 8304 if (banner) banner.classList.remove('banner-hidden'); 8305 } 8306 8307 // Hides the banner with the given DOM id by adding 'banner-hidden'. 8308 _hideBanner(bannerId) { 8309 const banner = document.getElementById(bannerId); 8310 if (banner) banner.classList.add('banner-hidden'); 8311 } 8312 8313 // --- Player instance storage --- 8314 // YT.Player instances are stored in a private indexed array. Subclasses must 8315 // always use _setPlayer/_getPlayer rather than managing their own player 8316 // references â this keeps the analytics and state logic in subclasses 8317 // aligned with the correct player instance. 8318 8319 // Stores a YT.Player instance at the given index slot. 8320 _setPlayer(index, player) { 8321 this.#players[index] = player; 8322 } 8323 8324 // Retrieves the YT.Player instance at the given index slot. 8325 // Returns undefined if the player has not been created yet. 8326 _getPlayer(index) { 8327 return this.#players[index]; 8328 } 8329 8330 // --- Consent check --- 8331 // ALWAYS use this method to check consent â do not read localStorage directly 8332 // or use the hasConsent getter in conditional logic. 8333 _hasConsent() { 8334 const result = this.#hasConsent || localStorage.getItem('YouTubeCookieAccepted') === 'true'; 8335 return result; 8336 } 8337} 8338 8339// ============================================================================= 8340// VideoBannerPlayer.js 8341// ============================================================================= 8342// PURPOSE: Manages autoplay muted background video banners â the full-width 8343// looping videos used as decorative backgrounds in hero sections and the ISG 8344// (Interactive Story Guide) banner. These videos play automatically and silently; 8345// the user can optionally pause/resume them via an overlay button. 8346// 8347// EXTENDS: YouTubePlayerBase (see YouTubePlayerBase.js for base class contract) 8348// 8349// TWO OPERATING MODES â controlled by the isISG flag per player: 8350// 8351// STANDARD mode (isISG = false):
8352// - Video autoplays, loops, and is muted. 8353// - When video approaches 95%, the thumbnail banner is shown and the player 8354// seeks to 0 â creating a seamless loop without relying on YouTube's own loop 8355// playerVar (which is unreliable across browsers). 8356// - IntersectionObserver pauses/resumes when timeline slides scroll in/out of view. 8357// - visibilitychange handler pauses when the browser tab is hidden. 8358// 8359// ISG mode (isISG = true): 8360// - Same autoplay/muted setup but uses a 'playlist' playerVar instead of 'loop' 8361// for better cross-browser loop compatibility in the ISG context. 8362// - On first play, the thumbnail banner hides after 3 seconds (longer delay to 8363// let the video visually establish itself). On subsequent plays, 750ms delay. 8364// - The ISG end poller pauses the video at 95% and restores the banner, then on 8365// the next play event seeks to 0 and resumes â implementing a controlled loop. 8366// - ISG players are never observed by the IntersectionObserver or visibilitychange 8367// handler â they are managed by the ISG component's own scroll logic. 8368// 8369// CONSENT FLOW: 8370// - If consent is available at init time: YT.Player is created immediately. 8371// - If consent is not available: banners and overlays are created (so the page 8372// looks correct) but YT.Player creation is deferred. When the user accepts via 8373// OneTrust, _onConsentGranted() is called and all deferred players are created. 8374// - If the user denies/closes: _onConsentDenied() removes the player iframes and 8375// overlays so no YouTube content is rendered without consent. 8376// 8377// SUBCLASSES: 8378// WistiaVideoBannerFallback extends this class to handle the case where a Wistia 8379// video falls back to YouTube. It manages its own element (not a querySelectorAll 8380// list) and calls _initPlayerState / _createBannerPlayer directly. 8381// ============================================================================= 8382class VideoBannerPlayer extends YouTubePlayerBase { 8383 // Total number of player slots managed by this instance. Used to know when 8384 // all players have initialised so we can start the IntersectionObserver. 8385 #totalPlayers = 0; 8386 #nonIsgCount = 0; // counts players that will fire #onReady (ISG players do not) 8387 #initializedCount = 0; // counts how many players have fired their onReady event 8388 8389 // Per-player state arrays â indexed by the same index used in YouTubePlayerBase#players. 8390 // Using arrays (not scalars) is critical: a shared scalar would cause cross-player 8391 // state corruption on pages with multiple banner instances. 8392 #isISG = []; // boolean: is this player in ISG mode? 8393 #isgFirstIteration = []; // boolean: has this ISG player played for the first time? 8394 #reachedVideoEnd = []; // boolean: has the ISG poller flagged near-end? 8395 #manualPause = []; // boolean: did the user explicitly pause this player? 8396 #buttonOverlayClicked = []; // boolean: was the overlay button just clicked? (icon sync) 8397 #playerStates = []; // YT.PlayerState: last known state for each player 8398 #applyBannerInterval = []; // setInterval handles for the ISG end poller, per player 8399 #standardEndPollerInterval = []; // setInterval handles for the standard end poller, per player 8400 8401 #intersectionObserver; // shared IntersectionObserver for timeline slide autoplay 8402 8403 // YouTube IFrame API playerVars for standard banner mode. 8404 // All params are static across all banner instances so they live as a class-level constant. 8405 // Base playerVars shared by both standard and ISG banner modes. 8406 // 'loop' and 'playlist' are intentionally absent â they are added dynamically 8407 // in #buildPlayerVars/#buildISGPlayerVars with the video-specific id, because 8408 // YouTube's loop playerVar only suppresses the end screen when playlist is also 8409 // set to the same video id. Without playlist, YouTube renders its branding/end 8410 // screen for a moment before the loop fires, which is visible to the user. 8411 static #BASE_PLAYER_VARS = { 8412 'modestbranding': 1, // hide YouTube logo in the control bar 8413 'rel': 0, // don't show related videos at end 8414 'showinfo': 0, // hide video title overlay 8415 'controls': 0, // hide player controls (we provide our own overlay button) 8416 'disablekb': 1, // disable keyboard shortcuts (prevent focus-trap issues) 8417 'fs': 0, // disable fullscreen button 8418 'iv_load_policy': 3, // hide video annotations 8419 'cc_load_policy': 0, // hide closed captions by default 8420 'mute': 1, // muted â required for autoplay in most browsers 8421 'autohide': 1, 8422 'autoplay': 1, // autoplay on load 8423 'loop': 1, 8424 'origin': window.location.origin // required for postMessage security â must be a real URL 8425 }; 8426 8427 constructor(playerElements, hasConsent) { 8428 super(playerElements, hasConsent); 8429 this.#totalPlayers = playerElements.length; 8430 // Bind the IntersectionObserver callback to this instance so it can access 8431 // private fields and methods when called by the browser. 8432 this.#intersectionObserver = new IntersectionObserver( 8433 this.#onIntersection.bind(this), 8434 { root: null, rootMargin: '0px', threshold: 0.05 } 8435 ); 8436 } 8437 8438 // Returns the DOM id to assign to each player container element. 8439 // The base class calls this during init() before _initElement. 8440 _buildPlayerId(index) {
8441 return `youtube-player-${index}`; 8442 } 8443 8444 // Called once per element by the base class init() loop. 8445 // Reads the element's data attributes, creates the thumbnail banner and overlay 8446 // button, initialises per-player state, then creates the YT.Player if consent 8447 // is already available (or defers if not). 8448 _initElement(element, index) { 8449 const thumbnailUrl = element.getAttribute('data-background-url'); 8450 const isISG = element.closest('.cmp-isg-banner-media-container') !== null; 8451 this.#createBanner(element.parentNode, index, thumbnailUrl); 8452 this.#createOverlay(element.parentNode, index); 8453 this._initPlayerState(index, isISG); 8454 8455 if (this._hasConsent()) { 8456 const videoId = element.getAttribute('data-video-id'); 8457 this.#createPlayer(index, videoId, element.id); 8458 } else { 8459 console.debug(`[VideoBanner] index=${index}: no consent, deferring player creation`); 8460 } 8461 } 8462 8463 // Initialises the per-player state arrays for a given index and ISG mode. 8464 // Called by _initElement for native banner players, and called directly by 8465 // WistiaVideoBannerFallback which manages its own element outside the init() loop. 8466 _initPlayerState(index, isISG) { 8467 this.#isISG[index] = isISG; 8468 this.#isgFirstIteration[index] = true; 8469 this.#reachedVideoEnd[index] = false; 8470 this.#manualPause[index] = false; 8471 this.#buttonOverlayClicked[index] = false; 8472 // Ensure #totalPlayers covers Wistia fallback instances that pass their own index 8473 // rather than being counted in the constructor's playerElements.length. 8474 this.#totalPlayers = Math.max(this.#totalPlayers, index + 1); 8475 if (!isISG) this.#nonIsgCount++; 8476 } 8477 8478 // Protected hook for WistiaVideoBannerFallback: creates a YT.Player for a 8479 // specific index/videoId/containerId without going through the init() loop. 8480 // The native path uses #createPlayer directly; this method exposes it to subclasses. 8481 _createBannerPlayer(index, videoId, containerId) { 8482 this.#createPlayer(index, videoId, containerId); 8483 } 8484 8485 // Called by YouTubePlayerBase when the user grants consent via OneTrust modal. 8486 // Creates all YT.Player instances that were deferred during init() and removes 8487 // the consent overlay UI. 8488 _onConsentGranted() { 8489 this.playerElements.forEach((element, index) => { 8490 const videoId = element.getAttribute('data-video-id'); 8491 const playerId = element.getAttribute('id'); 8492 const videoContainer = element.closest('.youtube-container'); 8493 this.#createPlayer(index, videoId, playerId); 8494 this.#removeConsentOverlay(videoContainer); 8495 }); 8496 } 8497 8498 // Called by YouTubePlayerBase when the user closes the OneTrust modal without 8499 // accepting. Removes any player iframes and overlays that were created while 8500 // waiting for consent so no YouTube content is visible without consent. 8501 _onConsentDenied() { 8502 this.playerElements.forEach((element) => { 8503 const wrapper = element.closest('.youtube-wrapper'); 8504 if (!wrapper) return; 8505 wrapper.querySelector('.youtube-player')?.remove(); 8506 wrapper.querySelector('.player-overlay')?.remove(); 8507 }); 8508 } 8509 8510 // Creates the thumbnail banner div and appends it to wrapperElement. 8511 // The banner is the visible image shown before and after the video plays. 8512 // Its id ('banner-{index}') is used by _showBanner/_hideBanner in the base class. 8513 #createBanner(wrapperElement, index, thumbnailUrl) { 8514 const banner = document.createElement('div'); 8515 banner.className = 'video-banner'; 8516 banner.id = `banner-${index}`; 8517 banner.style.backgroundImage = `url(${thumbnailUrl})`; 8518 banner.setAttribute('data-player-index', index); 8519 banner.setAttribute('role', 'presentation'); // decorative â screen readers skip it 8520 wrapperElement.appendChild(banner); 8521 } 8522 8523 // Creates the play/pause overlay button and appends it to wrapperElement. 8524 // The overlay is hidden initially ('overlay-hidden') and revealed once the 8525 // player is playing. Both the button and the overlay div itself are clickable 8526 // so the entire video area acts as a toggle target. 8527 #createOverlay(wrapperElement, index) {
8528 const overlay = document.createElement('div'); 8529 overlay.className = 'player-overlay overlay-hidden'; 8530 overlay.id = `overlay-${index}`; 8531 overlay.setAttribute('data-player-index', index); 8532 8533 const button = document.createElement('button'); 8534 button.className = 'play-pause-btn'; 8535 button.setAttribute('data-player-index', index); 8536 button.setAttribute('aria-label', 'Pause video'); 8537 button.setAttribute('title', 'Pause video'); 8538 8539 const icon = document.createElement('div'); 8540 icon.className = 'pause-icon'; // starts as pause icon since video autoplays 8541 icon.id = `play-icon-${index}`; 8542 8543 // The same handler is attached to both the button and the overlay div. 8544 // buttonOverlayClicked[index] flags that an icon update is needed in 8545 // #updateButtonIcon, which only runs when this flag is true (to avoid 8546 // unnecessary DOM queries on every state change). 8547 const toggleHandler = (e) => { 8548 e.stopPropagation(); 8549 this.#buttonOverlayClicked[index] = true; 8550 this.#togglePlayPause(index); 8551 }; 8552 8553 button.appendChild(icon); 8554 button.addEventListener('click', toggleHandler); 8555 overlay.appendChild(button); 8556 overlay.addEventListener('click', toggleHandler); 8557 wrapperElement.appendChild(overlay); 8558 } 8559 8560 // Creates a new YT.Player instance for the given index/videoId/playerId. 8561 // Selects the correct playerVars (standard vs ISG) based on the #isISG flag. 8562 // Stores the player via _setPlayer so the base class array is authoritative. 8563 #createPlayer(index, videoId, playerId) { 8564 const isISG = this.#isISG[index]; 8565 const playerVars = isISG 8566 ? this.#buildISGPlayerVars(videoId) 8567 : VideoBannerPlayer.#BASE_PLAYER_VARS; 8568 8569 this._setPlayer(index, new YT.Player(playerId, { 8570 height: '100%', 8571 width: '100%', 8572 videoId, 8573 playerVars, 8574 events: { 8575 // onReady fires when the player iframe is fully loaded and the API is ready. 8576 // For standard banners we set lazy/inert attributes and start observers. 8577 // For ISG banners we skip #onReady â the ISG component controls timing itself. 8578 onReady: (event) => { 8579 if (!this.#isISG[index]) this.#onReady(event, index); 8580 }, 8581 // onStateChange fires on every player state transition (play, pause, end, etc.). 8582 // Standard and ISG banners have different state machines so they route separately. 8583 onStateChange: (event) => { 8584 if (this.#isISG[index]) { 8585 this.#handleStateChangeISG(index, event.data); 8586 } else { 8587 this.#handleStateChange(index, event.data); 8588 } 8589 }, 8590 // onError fires when YouTube cannot play the video (private, removed, quota, etc.). 8591 // Restore the banner so something is visible rather than a broken black box. 8592 onError: (event) => { 8593 logClientError("ERROR", "YouTube banner player error: code=" + event.data, "VideoBannerPlayer.js"); 8594 this._showBanner(`banner-${index}`); 8595 }, 8596 }, 8597 })); 8598 } 8599 8600 // Builds ISG-specific playerVars. Same as standard but omits loop:1 because the 8601 // ISG end poller (#startEndPollerISG) manages the loop itself via seekTo(0). 8602 #buildISGPlayerVars(videoId) { 8603 return { 8604 'modestbranding': 1, // Hide YouTube logo 8605 'rel': 0, // Disable related videos at the end 8606 'showinfo': 0, // Hide video title 8607 'controls': 0, // Hide player controls 8608 'disablekb': 1, // Disable keyboard controls 8609 'fs': 0, // Disable fullscreen button 8610 'iv_load_policy': 3, // Hide video annotations 8611 'cc_load_policy': 0, // Hide closed captions 8612 'mute': 1, // Mute the video 8613 'autohide': 1, 8614 'autoplay': 1, 8615 'playlist': videoId, 8616 'origin': window.location.origin 8617 }; 8618 } 8619 8620 // Called when a standard (non-ISG) player fires onReady. 8621 // Sets accessibility attributes on the iframe: 8622 // - 'loading=lazy': hints to the browser to defer non-visible resource loads. 8623 // - 'inert': makes the iframe non-interactive when hidden behind the banner, 8624 // preventing keyboard users from accidentally tabbing into a muted autoplay video. 8625 // Tracks initialisation count â once all players are ready, starts the 8626 // IntersectionObserver and the off-screen visibility handler. 8627 #onReady(event, index) { 8628 const iframe = event.target.getIframe(); 8629 iframe.setAttribute('loading', 'lazy'); 8630 iframe.setAttribute('inert', ''); 8631 this.#initializedCount++; 8632 if (this.#nonIsgCount > 0 && this.#initializedCount === this.#nonIsgCount) { 8633 console.debug('[VideoBanner] all players ready â starting IntersectionObserver and off-screen handler'); 8634 this.#observeTimelineSlides(); 8635 this.#handleOffScreen(); 8636 } 8637 } 8638 8639 // --- State machine: standard banner --- 8640 8641 // Handles YouTube player state changes for standard (non-ISG) banner players. 8642 // State transitions: 8643 // PLAYING â hide banner after 400ms delay (gives video time to render a frame 8644 // before the thumbnail disappears), start end poller.
8645 // ENDED â autoplay again after 100ms (YouTube fires ENDED briefly at loop point). 8646 // PAUSED/BUFFERING â wait 2500ms then restore banner if still paused 8647 // (the delay distinguishes a momentary buffer from a true pause). 8648 #handleStateChange(index, newState) { 8649 const stateName = this.#stateName(newState); 8650 this.#playerStates[index] = newState; 8651 this.#updateButtonIcon(index) 8652 8653 if (newState === YT.PlayerState.PLAYING) { 8654 setTimeout(() => this._hideBanner(`banner-${index}`), 400); 8655 this.#startEndPoller(index); 8656 } else if (newState === YT.PlayerState.ENDED) { 8657 this.#clearStandardEndPoller(index); 8658 this._showBanner(`banner-${index}`); 8659 setTimeout(() => this.#autoPlay(index), 100); 8660 } else if (newState === YT.PlayerState.BUFFERING || newState === YT.PlayerState.PAUSED) { 8661 this.#clearStandardEndPoller(index); 8662 setTimeout(() => { 8663 const player = this._getPlayer(index); 8664 if (!player || player.getPlayerState() === YT.PlayerState.PAUSED) { 8665 this._showBanner(`banner-${index}`); 8666 } 8667 }, 2500); 8668 } 8669 } 8670 8671 // --- State machine: ISG banner --- 8672 8673 // Handles YouTube player state changes for ISG banner players. 8674 // ISG mode has more complex loop semantics than standard banners: 8675 // 8676 // PLAYING â hide banner (3000ms on first iteration, 750ms after), start ISG end poller. 8677 // PAUSED â if #reachedVideoEnd is true, the poller paused us at 95% â 8678 // seek to 0 and play again to loop seamlessly. 8679 // If paused unexpectedly (buffering, API glitch) and not manually paused, 8680 // auto-resume. If manually paused, do nothing. 8681 // ENDED â YouTube reports ENDED at the true end â seek to 0 and replay. 8682 // 8683 // The updateButtonIcon call syncs the play/pause icon in the overlay button 8684 // whenever the player state changes â but only if the user recently clicked 8685 // the button (buttonOverlayClicked flag prevents unnecessary DOM queries). 8686 #handleStateChangeISG(index, newState) { 8687 const stateName = this.#stateName(newState); 8688 this.#playerStates[index] = newState; 8689 this.#updateButtonIcon(index); 8690 8691 if (newState === YT.PlayerState.PLAYING) { 8692 const delay = this.#isgFirstIteration[index] ? 3000 : 750; 8693 this.#isgFirstIteration[index] = false; 8694 setTimeout(() => this._hideBanner(`banner-${index}`), delay); 8695 this.#startEndPollerISG(index); 8696 } else if (newState === YT.PlayerState.PAUSED) { 8697 this.#clearEndPoller(index); 8698 this._showBanner(`banner-${index}`); 8699 if (this.#reachedVideoEnd[index]) { 8700 this.#reachedVideoEnd[index] = false; 8701 this._getPlayer(index).seekTo(0); 8702 this._getPlayer(index).playVideo(); 8703 } else if (!this.#manualPause[index]) { 8704 this.#autoPlay(index); 8705 } 8706 } else if (newState === YT.PlayerState.ENDED) { 8707 this.#clearEndPoller(index); 8708 this._showBanner(`banner-${index}`); 8709 this.#reachedVideoEnd[index] = false; 8710 this._getPlayer(index).seekTo(0); 8711 this._getPlayer(index).playVideo(); 8712 } 8713 } 8714 8715 // Polls playback progress every 50ms for standard (non-ISG) banner players. 8716 // Guards against stacking: if a poller is already running for this index, skips. 8717 // The 0/0 = Infinity case is harmless â seekTo(0) at time=0 is a no-op and the 8718 // banner is already visible at that point. 8719 #startEndPoller(index) { 8720 if (this.#standardEndPollerInterval[index]) return; 8721 this.#standardEndPollerInterval[index] = setInterval(() => { 8722 const player = this._getPlayer(index); 8723 if (!player) { this.#clearStandardEndPoller(index); return; } 8724 const rate = player.getCurrentTime() / player.getDuration(); 8725 if (parseFloat(rate.toFixed(2)) > 0.95) { 8726 this.#clearStandardEndPoller(index); 8727 this._showBanner(`banner-${index}`); 8728 player.seekTo(0); 8729 } 8730 }, 50); 8731 } 8732 8733 // Clears and nulls the standard end poller interval for the given index. 8734 #clearStandardEndPoller(index) { 8735 clearInterval(this.#standardEndPollerInterval[index]); 8736 this.#standardEndPollerInterval[index] = null; 8737 } 8738 8739 // Polls playback progress every 350ms for ISG banner players. 8740 // Unlike the standard poller, this does not seek immediately â it sets 8741 // #reachedVideoEnd[index] = true and pauses the video. The state machine in 8742 // #handleStateChangeISG then handles the seek-and-replay on the subsequent 8743 // PAUSED event, ensuring the banner is visible during the transition. 8744 // Guards against multiple intervals: if one is already running, skips. 8745 // Division-by-zero guard: stops polling permanently on zero duration (ISG 8746 // banners do not experience transient zero durations the same way). 8747 #startEndPollerISG(index) { 8748 if (this.#applyBannerInterval[index]) return;
8749 this.#applyBannerInterval[index] = setInterval(() => { 8750 const player = this._getPlayer(index); 8751 if (!player) { this.#clearEndPoller(index); return; } 8752 const state = player.getPlayerState(); 8753 if (state !== YT.PlayerState.PLAYING && state !== YT.PlayerState.BUFFERING) { 8754 this.#clearEndPoller(index); 8755 return; 8756 } 8757 const duration = player.getDuration(); 8758 if (!duration) { this.#clearEndPoller(index); return; } 8759 const rate = player.getCurrentTime() / duration; 8760 if (parseFloat(rate.toFixed(2)) > 0.95) { 8761 this.#clearEndPoller(index); 8762 this.#reachedVideoEnd[index] = true; 8763 this._showBanner(`banner-${index}`); 8764 setTimeout(() => player.pauseVideo(), 50); 8765 } 8766 }, 350); 8767 } 8768 8769 // Clears and nulls the ISG end poller interval for the given index. 8770 // Must be called whenever the ISG player transitions away from PLAYING, 8771 // otherwise multiple intervals accumulate and fight each other. 8772 #clearEndPoller(index) { 8773 clearInterval(this.#applyBannerInterval[index]); 8774 this.#applyBannerInterval[index] = null; 8775 } 8776 8777 // Handles the user clicking the overlay play/pause button. 8778 // Sets manualPause[index] so the ISG state machine knows not to auto-resume 8779 // when a manual pause event arrives. Clears it again on user-initiated play. 8780 #togglePlayPause(index) { 8781 if (!this._getPlayer(index)) return; 8782 if (this.#playerStates[index] === YT.PlayerState.PLAYING) { 8783 this.#manualPause[index] = true; 8784 this.#pause(index); 8785 } else { 8786 this.#manualPause[index] = false; 8787 this.#autoPlay(index); 8788 } 8789 } 8790 8791 // Calls playVideo() and, for non-ISG players, hides the banner after 600ms 8792 // (slightly longer than the PLAYINGâhide delay to account for buffering). 8793 #autoPlay(index) { 8794 const player = this._getPlayer(index); 8795 if (!player) return; 8796 player.playVideo(); 8797 if (!this.#isISG[index]) { 8798 setTimeout(() => this._hideBanner(`banner-${index}`), 600); 8799 } 8800 } 8801 8802 // Shows the banner and pauses the player. For non-ISG players a 500ms delay 8803 // before pauseVideo() prevents a brief "pause icon flash" visible to the user 8804 // when the banner slides in. ISG pauses immediately since the banner is shown first. 8805 #pause(index) { 8806 const player = this._getPlayer(index); 8807 if (!player) return; 8808 this._showBanner(`banner-${index}`); 8809 if (!this.#isISG[index]) { 8810 setTimeout(() => player.pauseVideo(), 500); 8811 } else { 8812 player.pauseVideo(); 8813 } 8814 } 8815 8816 // Updates the play/pause icon in the overlay button to match the current player state. 8817 // Only runs if buttonOverlayClicked[index] is true â that flag is set when the user 8818 // actually clicks the button, avoiding unnecessary DOM queries on every API state change. 8819 #updateButtonIcon(index) { 8820 if (!this.#buttonOverlayClicked[index]) return; 8821 const button = document.querySelector(`.player-overlay .play-pause-btn[data-player-index="${index}"]`); 8822 if (!button) return; 8823 const icon = button.querySelector('div'); 8824 const state = this._getPlayer(index)?.getPlayerState(); 8825 8826 if (icon.classList.contains('play-icon') && state === YT.PlayerState.PLAYING) { 8827 icon.classList.remove("play-icon"); 8828 icon.classList.add("pause-icon"); 8829 button.setAttribute('aria-label', 'Pause video'); 8830 this.#buttonOverlayClicked[index] = false; 8831 } else if (icon.classList.contains('pause-icon') && state === YT.PlayerState.PAUSED) { 8832 icon.classList.add("play-icon"); 8833 icon.classList.remove("pause-icon"); 8834 button.setAttribute('aria-label', 'Play video'); 8835 button.setAttribute('title', 'Play video'); 8836 this.#buttonOverlayClicked[index] = false; 8837 } 8838 } 8839 8840 // Wires the IntersectionObserver to all .horizontalTimelineSlide elements. 8841 // Timeline slides are carousel-style sections that each contain a banner player. 8842 // When a slide scrolls into the viewport, its player autoplays; when it scrolls 8843 // out, it pauses. Only called once all players have fired onReady. 8844 #observeTimelineSlides() { 8845 const slides = document.querySelectorAll('.horizontalTimelineSlide');
8846 slides.forEach(slide => this.#intersectionObserver.observe(slide)); 8847 } 8848 8849 // IntersectionObserver callback â fires when any observed timeline slide enters 8850 // or leaves the viewport. Finds the player associated with the slide by reading 8851 // the player's DOM id and extracting its index via getVideoIndexFromId. 8852 // Consent is re-checked here because the observer can fire after a page refresh 8853 // where the user may have revoked consent. 8854 #onIntersection(entries) { 8855 entries.forEach(entry => { 8856 const slide = entry.target.closest('.horizontalTimelineSlide'); 8857 const videoPlayer = slide?.querySelector('.youtube-player'); 8858 if (!videoPlayer) return; 8859 const playerIndex = getVideoIndexFromId(videoPlayer.id); 8860 if (entry.isIntersecting && this._hasConsent()) { 8861 this.#autoPlay(playerIndex); 8862 } else { 8863 this.#pause(playerIndex); 8864 } 8865 }); 8866 } 8867 8868 // Registers a visibilitychange handler that pauses all players when the browser 8869 // tab is hidden (e.g. user switches tabs) and resumes them when the tab becomes 8870 // visible again â but only if the player is still in the viewport and consent 8871 // is still valid. Skipped entirely for ISG players since ISG manages its own lifecycle. 8872 #handleOffScreen() { 8873 if (this.playerElements.some((_, i) => this.#isISG[i])) return; 8874 document.addEventListener('visibilitychange', () => { 8875 this.players.forEach((player, index) => { 8876 if (!player) return; 8877 if (document.visibilityState === 'hidden') { 8878 player.pauseVideo(); 8879 } else { 8880 const rect = player.getIframe().getBoundingClientRect(); 8881 const inViewport = rect.top >= 0 && rect.bottom <= window.innerHeight; 8882 if (inViewport && this._hasConsent()) { 8883 player.playVideo(); 8884 } 8885 } 8886 }); 8887 }); 8888 } 8889 8890 // Removes the consent overlay UI that was injected while waiting for consent. 8891 // Called after _onConsentGranted() creates the deferred players. 8892 #removeConsentOverlay(videoContainer) { 8893 videoContainer?.querySelector('.youtube-wrapper')?.classList.remove('video-consent--overlay'); 8894 videoContainer?.querySelector('.video-one-trust-container')?.remove(); 8895 } 8896 8897 // Converts a YT.PlayerState integer to a human-readable string for debug logging. 8898 #stateName(state) { 8899 const map = { '-1': 'UNSTARTED', 0: 'ENDED', 1: 'PLAYING', 2: 'PAUSED', 3: 'BUFFERING', 5: 'CUED' }; 8900 return map[state] ?? state; 8901 } 8902} 8903 8904// ============================================================================= 8905// VideoComponentPlayer.js 8906// ============================================================================= 8907// PURPOSE: Manages click-to-play inline video players used in editorial content 8908// (article pages, landing pages etc.). Unlike banner players, these are NOT 8909// autoplay â the user must click a play button or the thumbnail banner to start 8910// the video. They also fire Adobe Analytics events at playback milestones. 8911// 8912// EXTENDS: YouTubePlayerBase (see YouTubePlayerBase.js for base class contract) 8913// 8914// USER FLOW: 8915// 1. Page loads. The component renders a thumbnail banner with a play button. 8916// 2. If consent is available, the play button is active and clickable. 8917// 3. User clicks play button or the banner â _triggerPlay() fires. 8918// 4. If no YT.Player exists yet, _createYTPlayer() creates one. When the API 8919// fires onReady, _onPlayerReady() calls playVideo() and hides the banner. 8920// 5. While playing, analytics milestones are tracked at 25%, 50%, 75%, and 8921// on video complete. 8922// 6. When the video ends, the banner is restored so the user can replay. 8923// 8924// CONSENT FLOW: 8925// - If consent is available at init: play buttons are wired immediately. 8926// - If consent is not available: banners are rendered without play buttons. 8927// When consent is granted via OneTrust, _onConsentGranted() re-runs the 8928// banner setup to add the play buttons. 8929// - If consent is denied: play buttons, transcript links, and transcript modals 8930// are removed so no YouTube-dependent UI is visible. 8931// 8932// SUBCLASSES: 8933// WistiaVideoPlayerFallback extends this class to handle the Wistia â YouTube
8934// fallback in player (non-banner) mode. It bypasses _triggerPlay entirely and 8935// overrides _onPlayerReady to suppress immediate playback (cued mode). 8936// 8937// ANALYTICS: 8938// Adobe Analytics events are pushed to window.dataLayer (GTM-compatible). 8939// Events fired: mediaPlay, media25PercentPlayed, media50PercentPlayed, 8940// media75PercentPlayed, mediaComplete. Each event fires at most once per play 8941// session and resets when the video ends. 8942// ============================================================================= 8943class VideoComponentPlayer extends YouTubePlayerBase { 8944 // Per-player state arrays â indexed by the same index used in YouTubePlayerBase#players. 8945 #mediaNames = []; // string[]: the authored media name used as the analytics identifier 8946 #eventFlags = []; // object[]: per-player flags tracking which analytics events have fired 8947 #analyticsIntervals = []; // setInterval handles for the playback progress poller, per player 8948 8949 constructor(playerElements, hasConsent) { 8950 super(playerElements, hasConsent); 8951 } 8952 8953 // Returns the DOM id to assign to each player container element. 8954 // The base class calls this during init() before _initElement. 8955 _buildPlayerId(index) { 8956 return `video-youtube-player-${index}`; 8957 } 8958 8959 // Called once per element by the base class init() loop. 8960 // Reads the media name from the data attribute (used for analytics), initialises 8961 // event flags, and sets up the banner with the play button. 8962 _initElement(element, index) { 8963 this.#mediaNames[index] = element.getAttribute('data-video-name'); 8964 this.#eventFlags[index] = {}; 8965 this.#updateBanner(element.parentNode.querySelector('.video-banner'), index, element.getAttribute('data-video-id'), element.id); 8966 } 8967 8968 // Called when the user grants consent via OneTrust after the player was 8969 // initialised without it. Re-runs #updateBanner for each element to add 8970 // the play buttons that were skipped during the initial render. 8971 _onConsentGranted() { 8972 this.playerElements.forEach((element, index) => { 8973 const videoIndex = getVideoIndexFromId(element.id); 8974 this.#updateBanner( 8975 element.parentNode.querySelector('.video-banner'), 8976 videoIndex, 8977 element.getAttribute('data-video-id'), 8978 element.id 8979 ); 8980 }); 8981 } 8982 8983 // Called when the user dismisses the OneTrust modal without accepting. 8984 // Removes play buttons, transcript containers, and transcript modals from the 8985 // DOM so no YouTube-dependent UI is rendered without consent. 8986 _onConsentDenied() { 8987 this.playerElements.forEach((element) => { 8988 const container = element.closest('.main-youtube-container'); 8989 if (!container) return; 8990 container.querySelector('.video-banner .play-pause-btn')?.remove(); 8991 container.querySelector('.video-transcript-container')?.remove(); 8992 container.querySelector('.video-transcript-modal')?.remove(); 8993 container.querySelector('.youtube-details-container')?.remove(); 8994 }); 8995 } 8996 8997 // Protected hook used by WistiaVideoPlayerFallback to register itself with the 8998 // analytics system without going through the init() loop. Wistia fallback instances 8999 // manage their own DOM element, so they call this directly from their constructor 9000 // with their instance-specific index and the authored media name. 9001 _registerPlayer(index, mediaName) { 9002 this.#mediaNames[index] = mediaName; 9003 this.#eventFlags[index] = {}; 9004 } 9005 9006 // Creates and initialises a YT.Player for the given container, then wires onReady, 9007 // onStateChange, and onError events. When the player is ready, delegates to 9008 // _onPlayerReady(index) â whether playback starts immediately is determined by that 9009 // method. The base implementation plays immediately (safe because _createYTPlayer is 9010 // only called after a user click via _triggerPlay). Subclasses that need cued/no-autoplay 9011 // behaviour should override _onPlayerReady to suppress the playVideo() call. 9012 _createYTPlayer(index, videoId, containerId) { 9013 this._setPlayer(index, new YT.Player(containerId, { 9014 height: '100%', 9015 width: '100%', 9016 videoId, 9017 playerVars: { 9018 rel: 0, // don't show related videos at end 9019 modestbranding: 1, // hide YouTube logo in control bar 9020 playsinline: 1, // play inline on iOS instead of fullscreen 9021 origin: window.location.origin, // required for postMessage security 9022 }, 9023 events: { 9024 onReady: () => { 9025 this._onPlayerReady(index); 9026 }, 9027 onStateChange: (event) => this.#handleStateChange(index, event.data), 9028 onError: (event) => { 9029 console.error(`[VideoComponent] YT player error index=${index} code=${event.data}`); 9030 logClientError("ERROR", "YouTube player error: code=" + event.data, "VideoComponentPlayer.js"); 9031 }, 9032 }, 9033 })); 9034 } 9035 9036 // Called on user click. If a YT.Player already exists (user clicked play a second time 9037 // or replaying after completion), plays it directly. If no player exists yet, creates 9038 // one via _createYTPlayer â the player will call _onPlayerReady on API ready, which 9039 // then calls playVideo().
9040 // NOTE: WistiaVideoPlayerFallback does NOT call this method â it manages its own 9041 // reveal-and-play flow via #revealAndPlay to correctly handle the Wistia DOM structure. 9042 _triggerPlay(index, videoId, containerId) { 9043 const existing = this._getPlayer(index); 9044 if (existing) { 9045 existing.playVideo(); 9046 this._hideBanner(`video-component-banner-${index}`); 9047 } else { 9048 this._createYTPlayer(index, videoId, containerId); 9049 } 9050 } 9051 9052 // Called when YT.Player fires onReady. For the native VideoComponent path, the player 9053 // was just created in response to a user click (via _triggerPlay â _createYTPlayer), 9054 // so it is safe and correct to play immediately. 9055 // WistiaVideoPlayerFallback overrides this method to suppress playVideo() â the 9056 // fallback creates the player up-front in a cued state and only plays on user click. 9057 _onPlayerReady(index) { 9058 const player = this._getPlayer(index); 9059 if (player) player.playVideo(); 9060 this._hideBanner(`video-component-banner-${index}`); 9061 } 9062 9063 // Creates or updates the banner element for a player. 9064 // Assigns a stable id and data attribute, then â if consent is available â 9065 // creates the play button and wires click handlers on both the button and 9066 // the banner itself (so clicking the thumbnail also starts playback). 9067 // If consent is not available, the banner is set up without the button; 9068 // _onConsentGranted() will call this again to add the button later. 9069 #updateBanner(banner, index, videoId, playerId) { 9070 if (!banner) return; 9071 banner.id = `video-component-banner-${index}`; 9072 banner.setAttribute('data-player-index', index); 9073 9074 if (!this._hasConsent()) { 9075 return; 9076 } 9077 9078 if (banner.querySelector('.play-pause-btn')) { 9079 return; 9080 } 9081 9082 const button = document.createElement('button'); 9083 button.className = 'play-pause-btn'; 9084 button.setAttribute('data-player-index', index); 9085 button.setAttribute('aria-label', 'Play video'); 9086 button.setAttribute('title', 'Play video'); 9087 9088 const playIcon = document.createElement('div'); 9089 playIcon.className = 'play-icon'; 9090 button.appendChild(playIcon); 9091 9092 const play = (e) => { 9093 e.stopPropagation(); 9094 this._triggerPlay(index, videoId, playerId); 9095 }; 9096 button.addEventListener('click', play); 9097 banner.addEventListener('click', play); 9098 banner.appendChild(button); 9099 } 9100 9101 // Routes YouTube player state change events to the relevant handlers. 9102 // Only PLAYING and ENDED are acted upon â PAUSED and BUFFERING are intentionally 9103 // ignored since the play-button model does not need to react to transient pauses. 9104 #handleStateChange(index, newState) { 9105 const stateName = this.#stateName(newState); 9106 if (newState === YT.PlayerState.PLAYING) { 9107 this.#onPlay(index); 9108 } else if (newState === YT.PlayerState.ENDED) { 9109 this.#onEnded(index); 9110 } 9111 } 9112 9113 // Called when the player transitions to PLAYING. 9114 // Fires the mediaPlay analytics event (once per play session, guarded by eventFlags). 9115 // Starts the progress interval that polls getCurrentTime/getDuration every 500ms to 9116 // fire milestone events at 25%, 50%, 75%, and detect near-completion (>98%). 9117 #onPlay(index) { 9118 const name = this.#mediaNames[index]; 9119 if (!name) return; 9120 if (!this.#eventFlags[index][`${name}_played`]) { 9121 this.#pushAdobeEvent(name, 'mediaPlay'); 9122 this.#eventFlags[index][`${name}_played`] = true; 9123 } 9124 9125 // Clear any interval left over from a previous play session before starting a new one. 9126 // Without this, rapid state transitions (buffering â playing) stack up multiple intervals 9127 // that all poll simultaneously and never stop since only one reference is tracked. 9128 this.#clearAnalyticsInterval(index); 9129 this.#analyticsIntervals[index] = setInterval(() => { 9130 const player = this._getPlayer(index); 9131 if (!player) { this.#clearAnalyticsInterval(index); return; } 9132 // Stop polling if the player is no longer playing (ended, paused, or stopped). 9133 // This covers the case where the video ends with rate < 0.98 (e.g. rate jumped 9134 // from 0.97 to ENDED without passing the threshold tick) â without this guard 9135 // the interval runs forever printing milestone logs at a low rate. 9136 const state = player.getPlayerState(); 9137 if (state !== YT.PlayerState.PLAYING && state !== YT.PlayerState.BUFFERING) { 9138 this.#clearAnalyticsInterval(index); 9139 return; 9140 } 9141 // getDuration() returns 0 before YouTube metadata has loaded and for live streams.
9142 // Without this guard, 0/0 = Infinity which satisfies all milestone thresholds 9143 // simultaneously on the first tick, firing all three milestone events and 9144 // permanently clearing the interval before any real progress is tracked. 9145 const duration = player.getDuration(); 9146 if (!duration) return; 9147 const rate = player.getCurrentTime() / duration; 9148 this.#sendMilestone(name, index, rate); 9149 9150 // when the video reaches rate > 98% 9151 // clear the interval, and show the banner again so the user can replay if desired. 9152 if (rate > 0.98) { 9153 this._showBanner(`video-component-banner-${index}`); 9154 this.#clearAnalyticsInterval(index); 9155 } 9156 }, 500); 9157 } 9158 9159 // Called when the player transitions to ENDED. 9160 // Clears the analytics interval, fires mediaComplete, restores the banner, 9161 // and resets event flags so the next play session tracks milestones fresh. 9162 #onEnded(index) { 9163 this.#clearAnalyticsInterval(index); 9164 this._onVideoEnded(index); 9165 const name = this.#mediaNames[index]; 9166 if (!name) return; 9167 this.#pushAdobeEvent(name, 'mediaComplete'); 9168 this.#resetFlags(index); 9169 } 9170 9171 // Hook called by #onEnded. The base implementation restores the banner. 9172 // WistiaVideoPlayerFallback overrides this to also hide the YouTube container 9173 // and restore the Wistia banner element. 9174 _onVideoEnded(index) { 9175 this._showBanner(`video-component-banner-${index}`); 9176 } 9177 9178 // Checks the current playback rate (0.0â1.0) against milestone thresholds. 9179 // Uses independent if-blocks (NOT else-if) so multiple milestones can fire 9180 // in the same tick if the rate jumps across a boundary between polls. 9181 // Each milestone fires at most once per play session â guarded by eventFlags. 9182 #sendMilestone(name, index, rate) { 9183 const pct = Math.ceil(rate * 100); 9184 const flags = this.#eventFlags[index]; 9185 if (pct >= 25 && !flags[`${name}_25`]) { 9186 this.#pushAdobeEvent(name, 'media25PercentPlayed'); 9187 flags[`${name}_25`] = true; 9188 } 9189 if (pct >= 50 && !flags[`${name}_50`]) { 9190 this.#pushAdobeEvent(name, 'media50PercentPlayed'); 9191 flags[`${name}_50`] = true; 9192 } 9193 if (pct >= 75 && !flags[`${name}_75`]) { 9194 this.#pushAdobeEvent(name, 'media75PercentPlayed'); 9195 flags[`${name}_75`] = true; 9196 } 9197 } 9198 9199 // Pushes an Adobe Analytics event to window.dataLayer (Google Tag Manager format). 9200 // GTM picks this up and forwards it to Adobe Analytics via the configured tag rules. 9201 #pushAdobeEvent(mediaName, event) { 9202 window.dataLayer = window.dataLayer || []; 9203 window.dataLayer.push({ 9204 event, 9205 media: { mediaName, mediaType: 'video' }, 9206 }); 9207 } 9208 9209 // Resets all analytics event flags for a player after the video ends. 9210 // This allows the same milestones to fire again when the user replays the video. 9211 #resetFlags(index) { 9212 const name = this.#mediaNames[index]; 9213 this.#eventFlags[index] = { 9214 [`${name}_played`]: false, 9215 [`${name}_25`]: false, 9216 [`${name}_50`]: false, 9217 [`${name}_75`]: false, 9218 }; 9219 } 9220 9221 // Clears and nulls the analytics interval for a player. 9222 // Called on video end and when the player is destroyed mid-play. 9223 #clearAnalyticsInterval(index) { 9224 clearInterval(this.#analyticsIntervals[index]); 9225 this.#analyticsIntervals[index] = null; 9226 } 9227 9228 // Converts a YT.PlayerState integer to a human-readable string for debug logging. 9229 #stateName(state) { 9230 const map = { '-1': 'UNSTARTED', 0: 'ENDED', 1: 'PLAYING', 2: 'PAUSED', 3: 'BUFFERING', 5: 'CUED' }; 9231 return map[state] ?? state; 9232 } 9233} 9234 9235// ============================================================================= 9236// WistiaFallback.js 9237// ============================================================================= 9238// PURPOSE: Thin factory class that serves as the entry point for the Wistia â YouTube
9239// fallback system. Called by Player.js when the Wistia player fails to load or 9240// times out (typically after an 8-second timeout). 9241// 9242// WHY A FACTORY (not direct instantiation)? 9243// Player.js should not need to know about consent checking, YouTube API loading, 9244// or which fallback class to use. WistiaFallback encapsulates all of that: 9245// 1. Checks consent (OneTrust cookie + localStorage) 9246// 2. Instantiates the correct fallback class for the rendering mode 9247// 3. Bootstraps the YouTube IFrame API (idempotent â safe to call multiple times) 9248// 4. Calls execute() on the instance once the API is ready 9249// 9250// TWO RENDERING MODES: 9251// createVideoPlayerFallback â WistiaVideoPlayerFallback (click-to-play player) 9252// createVideoBannerFallback â WistiaVideoBannerFallback (autoplay muted banner) 9253// 9254// Player.js determines which mode to use based on the component's authored 9255// configuration and calls the appropriate factory method. 9256// 9257// CONSENT SNAPSHOT: 9258// Consent is checked once at factory call time and passed as the 'hasConsent' 9259// constructor argument. The fallback classes also check consent again at execute() 9260// time (via _hasConsent() which re-reads localStorage), providing two opportunities 9261// to pick up consent that was granted between page load and Wistia timeout. 9262// 9263// ============================================================================= 9264class WistiaFallback { 9265 static #idCounter = 0; 9266 static nextId() { return ++WistiaFallback.#idCounter; } 9267 9268 // Creates a click-to-play YouTube fallback player for Wistia in standard player mode. 9269 // Returns the WistiaVideoPlayerFallback instance (useful for testing and debugging, 9270 // but callers do not need to hold a reference for normal operation). 9271 static createVideoPlayerFallback(config) { 9272 // Check consent at factory call time. hasOneTrustTargetingConsent() reads the 9273 // OneTrust cookie; the localStorage check covers consent granted on a previous 9274 // page view or via the modal on this page before the Wistia timeout fired. 9275 const hasConsent = hasOneTrustTargetingConsent() || 9276 localStorage.getItem('YouTubeCookieAccepted') === 'true'; 9277 console.debug(`[WistiaFallback] createVideoPlayerFallback videoId=${config.youtubeVideoId} hasConsent=${hasConsent}`); 9278 9279 const instance = new WistiaVideoPlayerFallback(config, hasConsent); 9280 9281 // loadYouTubeApi is idempotent â if the YT API is already loaded (e.g. because 9282 // another player on the page already triggered it), onReady fires synchronously. 9283 // If not yet loaded, the script tag is injected and onReady fires asynchronously. 9284 loadYouTubeApi(() => { 9285 console.debug('[WistiaFallback] YT API ready â executing WistiaVideoPlayerFallback'); 9286 instance.execute(); 9287 }); 9288 9289 return instance; 9290 } 9291 9292 // Creates an autoplay muted YouTube fallback banner for Wistia in banner mode. 9293 // Returns the WistiaVideoBannerFallback instance. 9294 static createVideoBannerFallback(config) { 9295 const hasConsent = hasOneTrustTargetingConsent() || 9296 localStorage.getItem('YouTubeCookieAccepted') === 'true'; 9297 console.debug(`[WistiaFallback] createVideoBannerFallback videoId=${config.youtubeVideoId} hasConsent=${hasConsent}`); 9298 9299 const instance = new WistiaVideoBannerFallback(config, hasConsent); 9300 9301 loadYouTubeApi(() => { 9302 console.debug('[WistiaFallback] YT API ready â executing WistiaVideoBannerFallback'); 9303 instance.execute(); 9304 }); 9305 9306 return instance; 9307 } 9308} 9309 9310// ============================================================================= 9311// WistiaVideoBannerFallback.js 9312// ============================================================================= 9313// PURPOSE: YouTube fallback player for the Wistia video component in banner mode. 9314// When the Wistia player fails to load or times out in a hero/banner context, 9315// Player.js detects the failure and calls WistiaFallback.createVideoBannerFallback() 9316// which instantiates this class. 9317// 9318// EXTENDS: VideoBannerPlayer â YouTubePlayerBase 9319// 9320// BANNER MODE vs PLAYER MODE: 9321// The Wistia component has two distinct rendering modes, controlled by the authored 9322// 'isWistiaPlayerBanner' flag: 9323// - Player mode â WistiaVideoPlayerFallback (click-to-play with analytics) 9324// - Banner mode â THIS CLASS (autoplay muted background, play/pause overlay button) 9325// 9326// WHY A SEPARATE CLASS (not just VideoBannerPlayer)? 9327// The native VideoBannerPlayer uses the base class init() loop to process a 9328// querySelectorAll list of '.youtube-player' elements. The Wistia fallback is 9329// triggered at runtime for one specific element â there is no list, and the Wistia 9330// DOM structure differs from the native banner component structure. This class 9331// manages its own element and calls base class protected methods 9332// (_initPlayerState, _createBannerPlayer, _getPlayer) directly with its own index. 9333// 9334// KEY DESIGN DECISIONS: 9335// 9336// 1. INSTANCE INDEX (#index): 9337// Each instance gets a unique index via the auto-incrementing #nextIndex counter. 9338// Without this, all instances on the same page would collide at index 0: 9339// - _initPlayerState(0) would overwrite another instance's ISG flags 9340// - _createBannerPlayer(0) would overwrite another instance's YT.Player 9341// - div.id = 'banner-0' would create duplicate DOM ids 9342// - _getPlayer(0) in #wireOverlay would control the wrong player 9343// - The first instance's end-poller interval would be leaked forever 9344// 9345// 2. BANNER AND CONTAINER ELEMENTS: 9346// #mountBanner() creates a thumbnail div inside the .wistia-player-wrapper. 9347// #mountContainer() creates the YT.Player host div in the wistia element's parent. 9348// Both use WistiaFallback.nextId() (a shared monotonic counter) for unique DOM ids. 9349// 9350// 3. OVERLAY TOGGLE: 9351// Unlike the native VideoBannerPlayer which wires the toggle via #createOverlay, 9352// this class wires its own toggle via #wireOverlay because it uses the Wistia 9353// component's authored overlay and button elements (passed in via config) rather 9354// than creating new ones. 9355// 9356// 4. ISG DETECTION: 9357// The isISG flag is determined by checking whether the wistia element is inside 9358// a .cmp-isg-banner-media-container. This mirrors the native banner component's 9359// own detection logic and controls which state machine the base class uses. 9360// 9361// CONSENT FLOW: 9362// - If consent is available: player is created immediately on execute(). 9363// - If consent is not available: overlay is hidden and OneTrust modal is shown.
9364// On acceptance, the player is created and overlay is restored. 9365// On dismissal, nothing changes (overlay stays hidden). 9366// ============================================================================= 9367class WistiaVideoBannerFallback extends VideoBannerPlayer { 9368 #config; // the config object passed from WistiaFallback (see WistiaFallback.js) 9369 #wistiaElement; // the <wistia-player> DOM element being replaced 9370 #containerEl = null; // the <div> we create to host the YT.Player iframe 9371 #bannerEl = null; // the thumbnail banner <div> we create inside the wrapper 9372 9373 // Each instance gets its own slot in the base class player/state arrays via #index. 9374 // #nextIndex is a class-level counter that increments on every construction, ensuring no 9375 // two instances share the same slot. Without this, all instances would collide at index 0: 9376 // - _initPlayerState(0) on instance B overwrites ISG flags written by instance A 9377 // - _createBannerPlayer(0) on instance B overwrites instance A's YT.Player reference, 9378 // making instance A's player permanently uncontrollable 9379 // - div.id = 'banner-0' is duplicated in the DOM, causing _showBanner/_hideBanner to 9380 // target the wrong element 9381 // - instance A's #applyBannerInterval[0] is overwritten and the original interval leaks 9382 // forever since its reference is lost 9383 static #nextIndex = 0; 9384 #index; 9385 9386 // config object shape (provided by WistiaFallback.createVideoBannerFallback): 9387 // { 9388 // wistiaElement: HTMLElement â the <wistia-player> element to hide/replace 9389 // youtubeVideoId: string â YouTube video id to fall back to 9390 // thumbnailUrl: string â URL of the thumbnail image for the banner background 9391 // overlayElement: HTMLElement â the Wistia overlay element (may be null) 9392 // button: HTMLElement â the custom play/pause button element (may be null) 9393 // } 9394 constructor(config, hasConsent) { 9395 super([], hasConsent); // empty array: we manage our own element, not a querySelectorAll list 9396 // wistiaPlayer.js passes button as a jQuery object â unwrap to a plain DOM element 9397 // so #wireOverlay can use native addEventListener without branching on jquery. 9398 this.#config = config.button?.jquery ? { ...config, button: config.button[0] } : config; 9399 this.#wistiaElement = config.wistiaElement; 9400 this.#index = WistiaVideoBannerFallback.#nextIndex++; 9401 // Detect ISG mode by checking the component's container hierarchy. 9402 // This mirrors the native VideoBannerPlayer._initElement detection logic. 9403 const isISG = config.wistiaElement.closest('.cmp-isg-banner-media-container') !== null; 9404 // _initPlayerState initialises all per-player state arrays in the base class 9405 // (isISG flag, firstIteration flag, reachedVideoEnd, manualPause, etc.). 9406 this._initPlayerState(this.#index, isISG); 9407 console.debug(`[WistiaBannerFallback] constructed index=${this.#index} videoId=${config.youtubeVideoId} isISG=${isISG} hasConsent=${hasConsent}`); 9408 } 9409 9410 // Entry point called by WistiaFallback once the YouTube IFrame API is ready. 9411 // Creates the thumbnail banner, hides the original Wistia element, then either 9412 // creates the YouTube player immediately (consent available) or shows the 9413 // OneTrust modal and defers player creation until consent is granted. 9414 execute() { 9415 console.debug(`[WistiaBannerFallback] execute â hasConsent=${this._hasConsent()}`); 9416 this.#mountBanner(); 9417 this.#wistiaElement.style.display = 'none'; 9418 9419 if (this._hasConsent()) { 9420 this.#mountContainer(); 9421 // _createBannerPlayer is the protected hook from VideoBannerPlayer that 9422 // creates the YT.Player and stores it at the given index slot. 9423 // Pass the inner div's id â YT.Player replaces that element with its iframe. 9424 this._createBannerPlayer(this.#index, this.#config.youtubeVideoId, this.#containerEl.firstElementChild.id); 9425 this.#wireOverlay(); 9426 } else { 9427 console.debug('[WistiaBannerFallback] no consent â hiding overlay and showing OneTrust modal'); 9428 this.#hideOverlay(); 9429 showOneTrustModal(); 9430 // setupOneTrustAcceptListener queues this callback alongside other waiting instances. 9431 // All callbacks drain together when the user clicks Accept. 9432 setupOneTrustAcceptListener(() => { 9433 console.debug('[WistiaBannerFallback] consent granted â mounting and creating player'); 9434 this.#mountContainer(); 9435 // Pass the inner div's id â YT.Player replaces that element with its iframe. 9436 this._createBannerPlayer(this.#index, this.#config.youtubeVideoId, this.#containerEl.firstElementChild.id); 9437 this.#wireOverlay(); 9438 this.#showOverlay(); 9439 }); 9440 setupOneTrustCloseListener(null); // no teardown needed on dismissal for this component 9441 } 9442 } 9443 9444 // Creates the thumbnail banner div inside the .wistia-player-wrapper. 9445 // The banner id uses the instance-specific #index to avoid duplicate DOM ids 9446 // when multiple banner fallbacks exist on the same page ('banner-0' duplicated 9447 // would cause _showBanner/_hideBanner in the base class to target the wrong element). 9448 // The thumbnail image is applied as a CSS background if provided in config. 9449 #mountBanner() { 9450 if (this.#bannerEl) return; // idempotent â only mount once 9451 const wrapper = this.#wistiaElement.closest('.wistia-player-wrapper'); 9452 const div = document.createElement('div'); 9453 div.className = 'video-banner youtube-fallback-banner'; 9454 // Use instance-specific index to avoid duplicate DOM ids when multiple banner 9455 // fallbacks exist on the same page â 'banner-0' would otherwise appear twice. 9456 div.id = `banner-${this.#index}`; 9457 if (this.#config.thumbnailUrl) { 9458 div.style.backgroundImage = `url(${this.#config.thumbnailUrl})`; 9459 div.style.backgroundSize = 'cover'; 9460 div.style.backgroundPosition = 'center'; 9461 } 9462 wrapper.appendChild(div); 9463 this.#bannerEl = div; 9464 console.debug(`[WistiaBannerFallback] mounted banner id=${div.id}`); 9465 } 9466 9467 // Creates the wrapper that holds the YT.Player iframe. 9468 // #containerEl is an outer wrapper div that is never touched by the YouTube IFrame API â 9469 // any future visibility or state operations target this stable element. 9470 // The inner div (ytDiv) is what YT.Player replaces with its iframe; execute() passes 9471 // ytDiv's id to _createBannerPlayer so the API never reaches #containerEl. 9472 // Uses a shared monotonic counter (WistiaFallback.nextId()) for unique ids so 9473 // multiple fallback instances mounting within the same millisecond don't collide. 9474 #mountContainer() { 9475 if (this.#containerEl) return; // idempotent â only mount once 9476 const wrapper = document.createElement('div'); 9477 wrapper.className = 'youtube-fallback-player'; 9478 wrapper.style.cssText = 'width:100%;height:100%;position:absolute;top:0;left:0;'; 9479 9480 const ytDiv = document.createElement('div'); 9481 ytDiv.id = 'youtube-fallback-banner-' + WistiaFallback.nextId(); 9482 ytDiv.style.cssText = 'width:100%;height:100%;'; 9483 wrapper.appendChild(ytDiv); 9484 9485 this.#wistiaElement.parentNode.insertBefore(wrapper, this.#wistiaElement.nextSibling); 9486 this.#containerEl = wrapper; 9487 console.debug(`[WistiaBannerFallback] mounted YT container ytDiv id=${ytDiv.id}`); 9488 } 9489 9490 // Wires the play/pause toggle to the Wistia component's authored overlay and button 9491 // elements (both are passed in via config). Clicking either toggles playback. 9492 // _getPlayer uses this.#index so this handler always controls the player belonging 9493 // to this specific instance, not a shared slot 0 player. 9494 #wireOverlay() { 9495 console.debug(`[WistiaBannerFallback] #wireOverlay index=${this.#index}`); 9496 const toggle = (e) => { 9497 e.stopPropagation(); 9498 const player = this._getPlayer(this.#index); 9499 if (!player) return; 9500 const isPlaying = player.getPlayerState() === YT.PlayerState.PLAYING; 9501 console.debug(`[WistiaBannerFallback] overlay toggle index=${this.#index} â currently ${isPlaying ? 'playing' : 'paused'}`); 9502 if (isPlaying) { 9503 player.pauseVideo(); 9504 this.#updateIcon(false); 9505 } else { 9506 player.playVideo(); 9507 this.#updateIcon(true); 9508 } 9509 }; 9510 this.#config.overlayElement?.addEventListener('click', toggle); 9511 this.#config.button?.addEventListener('click', toggle); 9512 } 9513 9514 // Hides the overlay button and the overlay container when consent is not available. 9515 // The overlay is hidden rather than removed so it can be restored by #showOverlay 9516 // when consent is later granted, without needing to re-create the DOM elements. 9517 #hideOverlay() { 9518 console.debug(`[WistiaBannerFallback] #hideOverlay index=${this.#index}`); 9519 this.#config.button?.style && (this.#config.button.style.display = 'none'); 9520 this.#config.overlayElement?.classList.add('overlay-hidden'); 9521 } 9522 9523 // Restores the overlay button and container after consent is granted. 9524 #showOverlay() { 9525 console.debug(`[WistiaBannerFallback] #showOverlay index=${this.#index}`); 9526 this.#config.button?.style && (this.#config.button.style.display = ''); 9527 this.#config.overlayElement?.classList.remove('overlay-hidden'); 9528 } 9529 9530 // Updates the play/pause icon on the button to reflect the current playback state. 9531 // Uses classList.replace() to swap between 'play-icon' and 'pause-icon' classes, 9532 // and updates the aria-label for screen reader accessibility. 9533 // isPlaying=true means video is now playing so icon should show pause affordance. 9534 #updateIcon(isPlaying) { 9535 console.debug(`[WistiaBannerFallback] #updateIcon index=${this.#index} isPlaying=${isPlaying}`); 9536 const btn = this.#config.button?.[0] || this.#config.button; // support jQuery or plain HTMLElement 9537 if (!btn) return; 9538 const icon = btn.querySelector('div') || btn.querySelector('span'); 9539 if (!icon) return; 9540 if (isPlaying) { 9541 icon.classList.replace('play-icon', 'pause-icon'); 9542 btn.setAttribute('aria-label', 'Pause video'); 9543 } else { 9544 icon.classList.replace('pause-icon', 'play-icon'); 9545 btn.setAttribute('aria-label', 'Play video'); 9546 } 9547 } 9548} 9549 9550// ============================================================================= 9551// WistiaVideoPlayerFallback.js 9552// ============================================================================= 9553// PURPOSE: YouTube fallback player for the Wistia video component in standard 9554// (non-banner) player mode. When the Wistia player fails to load or times out, 9555// Player.js detects the failure and calls WistiaFallback.createVideoPlayerFallback() 9556// which instantiates this class. 9557// 9558// EXTENDS: VideoComponentPlayer â YouTubePlayerBase 9559// 9560// WHY A SEPARATE CLASS (not just VideoComponentPlayer)? 9561// The native VideoComponentPlayer is driven by a querySelectorAll list of DOM 9562// elements managed through the base class init() loop. The Wistia fallback is 9563// triggered at runtime for a single specific element â there is no list, no init() 9564// loop, and the Wistia DOM structure differs from the native YouTube component DOM. 9565// This class manages its own element and calls base class protected methods 9566// (_registerPlayer, _createYTPlayer, _getPlayer) directly with its own index. 9567// 9568// KEY DESIGN DECISIONS: 9569// 9570// 1. CUED MODE (no autoplay on creation): 9571// When the fallback fires, we create the YT.Player immediately (so it's ready 9572// for the first click) but do NOT play it. This is achieved by overriding 9573// _onPlayerReady() to suppress the base class's playVideo() call. The player 9574// sits in a cued/ready state until the user clicks.
9575// 9576// 2. PLAY ON CLICK (not on API ready): 9577// The user must click the play button or the Wistia banner to start playback. 9578// #revealAndPlay() handles the reveal of the YouTube container and starts 9579// playback. If the user clicks before the YT API has finished loading the 9580// player, #playPending is set to true and _onPlayerReady() picks it up. 9581// 9582// 3. INSTANCE INDEX (#index): 9583// Each instance gets a unique index via the auto-incrementing #nextIndex counter. 9584// This is critical: without it, all instances on the same page would collide at 9585// index 0 in the base class arrays, causing players to overwrite each other. 9586// 9587// 4. VISIBILITY: 9588// The YouTube container is created with the 'player-hidden' CSS class applied. 9589// It is revealed (class removed) only when the user clicks play. When the video 9590// ends, 'player-hidden' is re-applied and the Wistia banner is restored. 9591// Only the CSS class controls visibility â do NOT also set style.display, 9592// as inline styles survive class toggling and would break replay. 9593// 9594// CONSENT FLOW: 9595// - If consent is available: player is cued immediately on execute(). 9596// - If consent is not available: the play button is hidden and the OneTrust modal 9597// is shown. On acceptance, the player is cued and the button is revealed. 9598// On dismissal, nothing changes (the button stays hidden). 9599// ============================================================================= 9600class WistiaVideoPlayerFallback extends VideoComponentPlayer { 9601 #config; // the config object passed from WistiaFallback (see WistiaFallback.js) 9602 #wistiaElement; // the <wistia-player> DOM element being replaced 9603 #containerEl = null; // the <div> we create to host the YT.Player iframe 9604 #playPending = false; // true if user clicked before the YT API finished loading 9605 9606 // Each instance gets its own slot in the base class player/analytics arrays via #index. 9607 // #nextIndex is a class-level counter that increments on every construction, ensuring no 9608 // two instances share the same slot. Without this, all instances would collide at index 0: 9609 // the second instance's _registerPlayer(0) and _createYTPlayer(0) calls would silently 9610 // overwrite the first instance's media name and YT.Player reference, making the first 9611 // player permanently uncontrollable and its analytics events misfiring under the wrong name. 9612 static #nextIndex = 0; 9613 #index; 9614 9615 // config object shape (provided by WistiaFallback.createVideoPlayerFallback): 9616 // { 9617 // wistiaElement: HTMLElement â the <wistia-player> element to hide/replace 9618 // youtubeVideoId: string â YouTube video id to fall back to 9619 // mediaName: string â authored name used for Adobe Analytics events 9620 // bannerElement: HTMLElement â the Wistia banner/thumbnail shown before play 9621 // button: HTMLElement â the custom play button element (may be null) 9622 // onEnd: Function â optional callback fired when the video ends 9623 // } 9624 constructor(config, hasConsent) { 9625 super([], hasConsent); // empty array: we manage our own element, not a querySelectorAll list 9626 // wistiaPlayer.js passes button as a jQuery object â unwrap to a plain DOM element 9627 // so #wireClickHandlers can use native addEventListener without branching on jquery. 9628 this.#config = config.button?.jquery ? { ...config, button: config.button[0] } : config; 9629 this.#wistiaElement = config.wistiaElement; 9630 this.#index = WistiaVideoPlayerFallback.#nextIndex++; 9631 this._registerPlayer(this.#index, config.mediaName); 9632 console.debug(`[WistiaPlayerFallback] constructed index=${this.#index} videoId=${config.youtubeVideoId} mediaName=${config.mediaName} hasConsent=${hasConsent}`); 9633 } 9634 9635 // Entry point called by WistiaFallback once the YouTube IFrame API is ready. 9636 // Hides the original Wistia element (which has failed or timed out), then 9637 // either cues the YouTube player immediately (consent available) or shows the 9638 // OneTrust modal and defers everything until consent is granted. 9639 execute() { 9640 console.debug(`[WistiaPlayerFallback] execute â hiding wistia element, hasConsent=${this._hasConsent()}`); 9641 this.#wistiaElement.style.display = 'none'; 9642 9643 if (this._hasConsent()) { 9644 this.#mount(); 9645 this.#cuePlayer(); 9646 this.#wireClickHandlers(); 9647 this._showButton(); 9648 } else { 9649 console.debug('[WistiaPlayerFallback] no consent â hiding button and showing OneTrust modal'); 9650 this._hideButton(); 9651 showOneTrustModal(); 9652 // setupOneTrustAcceptListener queues this callback alongside any other players 9653 // on the page that are also waiting for consent. All callbacks drain together 9654 // when the user clicks Accept (see youtube-one-trust.js for the queue pattern). 9655 setupOneTrustAcceptListener(() => { 9656 console.debug('[WistiaPlayerFallback] consent granted â mounting and cueing player'); 9657 this.#mount(); 9658 this.#cuePlayer(); 9659 this.#wireClickHandlers(); 9660 this._showButton(); 9661 }); 9662 setupOneTrustCloseListener(null); // no teardown needed on dismissal for this component 9663 } 9664 } 9665 9666 // Initialises the YT.Player without triggering playback. The player is created via 9667 // _createYTPlayer which delegates onReady to _onPlayerReady. This class overrides 9668 // _onPlayerReady to suppress the base class playVideo() call â that override is what 9669 // makes the player cued rather than anything _createYTPlayer itself controls. 9670 #cuePlayer() { 9671 // Pass the inner div's id â YT.Player replaces that div with its iframe. 9672 // #containerEl is the outer wrapper and must not be passed here, as the API 9673 // would detach it and classList toggling in #revealAndPlay would stop working. 9674 const ytDivId = this.#containerEl.firstElementChild.id; 9675 console.debug(`[WistiaPlayerFallback] cueing YT player index=${this.#index} videoId=${this.#config.youtubeVideoId}
9675 ytDivId=${ytDivId}`); 9676 this._createYTPlayer(this.#index, this.#config.youtubeVideoId, ytDivId); 9677 } 9678 9679 // Override of VideoComponentPlayer._onPlayerReady. 9680 // The base class plays immediately when the player is ready (correct for the native 9681 // click-to-play flow where the player is created in response to a user click). 9682 // Here we suppress that behaviour because the player is created up-front in a cued 9683 // state â playback should only start when the user explicitly clicks the play button. 9684 // 9685 // Exception: if the user clicked the play button while the API was still loading 9686 // (#playPending = true), we play immediately when the API becomes ready. 9687 _onPlayerReady(index) { 9688 if (this.#playPending) { 9689 console.debug('[WistiaPlayerFallback] player ready â pending click, playing now'); 9690 this.#playPending = false; 9691 this._getPlayer(index)?.playVideo(); 9692 } else { 9693 console.debug('[WistiaPlayerFallback] player cued and ready â waiting for user click'); 9694 } 9695 } 9696 9697 // Override of VideoComponentPlayer._onVideoEnded. 9698 // After the video ends, hides the YouTube container by re-adding 'player-hidden' 9699 // and restores the Wistia banner so the page returns to its pre-play appearance. 9700 // Only the CSS class controls visibility â do NOT add style.display='none' here, 9701 // as that would survive the class toggle in #revealAndPlay and break replay. 9702 // Fires the optional onEnd callback if provided (e.g. for parent component cleanup). 9703 _onVideoEnded(_index) { 9704 console.debug('[WistiaPlayerFallback] video ended â hiding container, restoring banner'); 9705 if (this.#containerEl) { 9706 this.#containerEl.classList.add('player-hidden'); 9707 } 9708 this.#config.bannerElement?.classList.remove('banner-hidden'); 9709 this.#config.onEnd?.(); 9710 } 9711 9712 // Creates and inserts the wrapper that holds the YT.Player iframe. 9713 // #containerEl is an outer wrapper div that carries 'player-hidden' and is never 9714 // touched by the YouTube IFrame API â visibility toggling always targets this element. 9715 // The inner div (ytDiv) is what YT.Player replaces with its iframe; #cuePlayer() 9716 // passes ytDiv's id to _createYTPlayer so the API never reaches #containerEl. 9717 // Uses a shared monotonic counter (WistiaFallback.nextId()) for unique ids so 9718 // multiple fallback instances mounting within the same millisecond don't collide. 9719 #mount() { 9720 if (this.#containerEl) return; // idempotent â only mount once 9721 const wrapper = document.createElement('div'); 9722 wrapper.className = 'youtube-fallback-player player-hidden'; 9723 wrapper.style.cssText = 'width:100%;height:100%;'; 9724 9725 const ytDiv = document.createElement('div'); 9726 ytDiv.id = 'youtube-fallback-player-' + WistiaFallback.nextId(); 9727 ytDiv.style.cssText = 'width:100%;height:100%;'; 9728 wrapper.appendChild(ytDiv); 9729 9730 this.#wistiaElement.parentNode.insertBefore(wrapper, this.#wistiaElement.nextSibling); 9731 this.#containerEl = wrapper; 9732 console.debug(`[WistiaPlayerFallback] mounted YT container ytDiv id=${ytDiv.id}`); 9733 } 9734 9735 // Wires click handlers on the custom play button and the Wistia banner element. 9736 // Both elements trigger the same #revealAndPlay flow so the user can click 9737 // either the button or the thumbnail area to start playback. 9738 #wireClickHandlers() { 9739 console.debug(`[WistiaPlayerFallback] #wireClickHandlers index=${this.#index}`); 9740 const play = (e) => { 9741 e.stopPropagation(); 9742 console.debug('[WistiaPlayerFallback] play triggered by user click'); 9743 this.#revealAndPlay(); 9744 }; 9745 this.#config.button?.addEventListener('click', play); 9746 this.#config.bannerElement?.addEventListener('click', play); 9747 } 9748 9749 // Called when the user clicks play. Reveals the YouTube container by removing 9750 // 'player-hidden' and hides the Wistia banner. Then either plays the already-ready 9751 // player, or sets #playPending = true if the YT API hasn't finished loading yet 9752 // (in which case _onPlayerReady picks up the flag and plays when ready). 9753 #revealAndPlay() { 9754 // Reveal the YT container â only the class is toggled, never style.display, 9755 // so replay after video-end works correctly. 9756 this.#containerEl.classList.remove('player-hidden'); 9757 this.#config.bannerElement?.classList.add('banner-hidden'); 9758 console.debug('[WistiaPlayerFallback] container revealed, banner hidden'); 9759 9760 const player = this._getPlayer(this.#index); 9761 if (player) { 9762 console.debug('[WistiaPlayerFallback] calling playVideo on existing player'); 9763 player.playVideo(); 9764 } else { 9765 // Player not ready yet â set pending flag so _onPlayerReady triggers play 9766 // when the YouTube IFrame API finishes loading the player. 9767 console.debug('[WistiaPlayerFallback] player not ready yet â flagging pending play'); 9768 this.#playPending = true; 9769 } 9770 } 9771 9772 // Hides the custom play button (used when consent is not yet available). 9773 _hideButton() { 9774 console.debug('[WistiaPlayerFallback] _hideButton'); 9775 if (this.#config.button) this.#config.button.style.display = 'none'; 9776 } 9777 9778 // Restores the custom play button to its default visible state. 9779 _showButton() { 9780 console.debug('[WistiaPlayerFallback] _showButton'); 9781 if (this.#config.button) this.#config.button.style.display = ''; 9782 } 9783} 9784 9785// Add "back-to-top" button to page... 9786const emptyDiv = document.createElement("div"); 9787 9788emptyDiv.innerHTML = ` 9789 <button class="back-to-top-btn" type="button" aria-hidden="true" title="Scroll back to top of web page."> 9790 <svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" fill="currentColor" class="bi bi-arrow-up-short" viewBox="0 0 16 16"> 9791 <path fill-rule="evenodd" d="M8 12a.5.5 0 0 0 .5-.5V5.707l2.146 2.147a.5.5 0 0 0 .708-.708l-3-3a.5.5 0 0 0-.708 0l-3 3a.5.5 0 1 0 .708.708
9791L7.5 5.707V11.5a.5.5 0 0 0 .5.5z"/> 9792 </svg> 9793 </button>` 9794 9795document.body.append(emptyDiv); 9796 9797// Go back to top of page on button click... 9798const backToTopButton = document.getElementsByClassName("back-to-top-btn")[0]; 9799 9800backToTopButton.setAttribute("tabIndex", "-1"); 9801 9802backToTopButton.addEventListener("click", (e) => { 9803 document.body.scrollTop = 0; // For Safari 9804 document.documentElement.scrollTop = 0; // For Chrome, Firefox, and IE 9805 e.currentTarget.blur(); // Remove button focus 9806 9807 skipRecordSubPageViewed = true; 9808 9809 window.dataLayer = window.dataLayer || []; 9810 window.dataLayer.push({ 9811 "event": "callToAction", //e111 9812 "callToAction": { 9813 "location": document.title.split("|")[0].trim(), //v103 9814 "ctaName": 'back to top', //v104 9815 "ctaType": 'custom button' //v111 9816 } 9817 }); 9818 9819 setTimeout(function() { 9820 skipRecordSubPageViewed = false; 9821 }, 2000); 9822}); 9823 9824// Show/Hide button based on scroll direction... 9825window.onscroll = () => { 9826 let currentDistanceFromTop = window.pageYOffset || document.documentElement.scrollTop; 9827 backToTopButton.blur(); 9828 // Do not show the back-to-top button until the user has scrolled down 2000px 9829 if (currentDistanceFromTop <= 2000) { 9830 backToTopButton.classList.remove("back-to-top-btn-grow"); 9831 backToTopButton.setAttribute('aria-hidden', 'true'); 9832 backToTopButton.setAttribute("tabIndex", "-1"); // Focus not allowed while hidden 9833 } 9834 else { 9835 backToTopButton.classList.add("back-to-top-btn-grow"); 9836 backToTopButton.setAttribute('aria-hidden', 'false'); 9837 backToTopButton.setAttribute("tabIndex", "0"); // Focus allowed if it's not hidden 9838 } 9839} 9840 9841$(document).ready(() => { 9842 const notTimelineClass = document.querySelector(".horizontalTimeline") == null ? "not-timeline-page" : ""; 9843 const backTopButton = document.querySelector(".back-to-top-btn"); 9844 9845 if(backTopButton){ 9846 backTopButton.classList.add(notTimelineClass); 9847 } 9848}); 9849 9850$(function () { 9851 const currentHost = window.location.host; 9852 let allAnchorTags = document.querySelectorAll('a'); 9853 allAnchorTags.forEach((anchorTag) => { 9854 try { 9855 const rawHref = anchorTag.getAttribute('href'); 9856 if (rawHref === null || rawHref.trim() === '' || rawHref === '#') return; 9857 let url = new URL(anchorTag.href); 9858 let anchorTagClassName = anchorTag.className; 9859 9860 if (url.host === currentHost || anchorTagClassName.includes('cmp-sec-tile__card--link') || anchorTagClassName.includes('vgn-link') || anchorTagClassName.includes('cmp-deepLinks')) { 9861 return; 9862 } 9863 9864 //set value of iconClass indicating it is an external link 9865 let iconClass = 'external-link-icon'; 9866 9867 //if the link is external, remove the ::after pseudo-element by setting the content to none 9868 anchorTag.classList.add("external-link-update-arrow-content"); 9869 9870 // Remove CTA blue arrow <i> element so only the external link icon is shown 9871 const existingCtaArrow = anchorTag.querySelector('i.arrow'); 9872 if (existingCtaArrow) { 9873 existingCtaArrow.remove(); 9874 } 9875 9876 // applicable only for cta buttons - check if existing <i> already has the icon class 9877 const existingIconWithClass = anchorTag.querySelector('i.external-link-icon'); 9878 if (existingIconWithClass) { 9879 // Ensure existing icon has the correct class without adding a duplicate 9880 existingIconWithClass.className = iconClass; 9881 return; 9882 } 9883 9884 if (anchorTag.getAttribute("data-iconurl") && anchorTag.getAttribute("data-iconurl") !== 'empty') { 9885 let iconSelector = "span." + anchorTag.getAttribute("data-iconurl"); 9886 const iconElement = anchorTag.querySelector(iconSelector); 9887 if (iconElement) iconElement.remove(); 9888 } 9889 9890 // Create icon element and append it (don't use innerHTML to avoid replacing italic/bold tags) 9891 const icon = document.createElement('i'); 9892 icon.className = iconClass; 9893 icon.setAttribute('aria-label', 'Opens in a new tab');
9894 anchorTag.appendChild(icon); 9895 } catch (exception) { 9896 console.warn("Error processing URL: ", anchorTag.href, exception); 9897 logClientError("WARN", "Error processing URL: " + anchorTag.href + " - " + (exception && exception.message ? exception.message : String(exception)), "external-link-icon.js"); 9898 } 9899 }); 9900}); 9901 9902// Add subscribe banner to page if the template is an article and 9903// the subscribeBannerFlag is set to false or not set at all and 9904// the page is not in a preview environment. 9905 9906$(document).ready(function () { 9907 const templateNameElement = document.getElementById("templatename"); 9908 const templateName = templateNameElement.getAttribute("data-templatename"); 9909 let hideBanner = templateNameElement.getAttribute("data-subscribeBannerFlag"); 9910 if (templateName === "article-template" && (hideBanner === "false" || hideBanner == null) && !isPreviewEnvironment()) { 9911 addSubscribeBannerToPage(); 9912 } 9913}); 9914 9915function isPreviewEnvironment() { 9916 let subdomain = window.location.hostname.split(".")[0]; 9917 let previewEnvironments = ["corpaempubpre", "corpaemdisppre"]; 9918 return previewEnvironments.includes(subdomain); 9919} 9920 9921function addSubscribeBannerToPage () { 9922 9923 const subscribeBannerContainer = document.createElement("div"); 9924 subscribeBannerContainer.innerHTML = ` 9925 <div class="subscribe-banner"> 9926 <p class="cmp-articletopicbodycopy__sectionBodyCopy">Get Vanguard's insights delivered straight to your inbox.</p> 9927 <div class="btn-secondary-download-container"> 9928 <a href="/content/corporatesite/us/en/corp/subscription-dashboard.html" id="subscribe-banner-button" class="subscribe-banner__button button button-secondary-download align-self-center button-padding-26" target="_blank" tabindex="0"> 9929 Subscribe 9930 </a> 9931 </div> 9932 <div class="cancel-button-container" style="background-color: #040505"> 9933 <a href="javascript:;" id="magic-box-icon" role="button" aria-label="Dismiss" tabindex="0"> 9934 <svg focusable="false" viewBox="0 0 13 13" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Clear" class="subscribe-banner__magic-box-clear-svg"><title>Clear</title><g fill="currentColor"><path d="m7.881 6.501 4.834-4.834c.38-.38.38-1.001 0-1.381s-1.001-.38-1.381 0l-4.834 4.834-4.834-4.835c-.38-.38-1.001-.38-1.381 0s-.38 1.001 0 1.381l4.834 4.834-4.834 4.834c-.38.38-.38 1.001 0 1.381s1.001.38 1.381 0l4.834-4.834 4.834 4.834c.38.38 1.001.38 1.381 0s .38-1.001 0-1.381z"></path></g></svg> 9935 </a> 9936 </div> 9937 </div>`; 9938 document.body.append(subscribeBannerContainer); 9939 9940 const subscribeBanner = document.getElementsByClassName("subscribe-banner")[0]; 9941 const subscribeBannerButton = document.getElementById("subscribe-banner-button"); 9942 const subscribeBannerCancelButton = document.getElementById("magic-box-icon"); 9943 9944 // Show/Hide banner/button based on scroll distance... 9945 window.addEventListener("scroll", () => { 9946 let subscribeBannerCurrentDistanceFromTop = window.pageYOffset || document.documentElement.scrollTop; 9947 subscribeBanner.blur(); 9948 // Do not show the banner/button until the user has scrolled down 1000px 9949 if (subscribeBannerCurrentDistanceFromTop <= 1000) { 9950 subscribeBanner.classList.remove("subscribe-banner--show-subscribe-banner"); 9951 subscribeBannerButton.setAttribute('aria-hidden', 'true'); 9952 subscribeBannerButton.setAttribute("tabIndex", "-1"); // Focus not allowed while hidden 9953 subscribeBannerCancelButton.setAttribute('aria-hidden', 'true'); 9954 subscribeBannerCancelButton.setAttribute("tabIndex", "-1"); 9955 } else { 9956 subscribeBanner.classList.add("subscribe-banner--show-subscribe-banner"); 9957 subscribeBannerButton.setAttribute('aria-hidden', 'false'); 9958 subscribeBannerButton.setAttribute("tabIndex", "0"); 9959 subscribeBannerCancelButton.setAttribute('aria-hidden', 'false'); 9960 subscribeBannerCancelButton.setAttribute("tabIndex", "0"); 9961 } 9962 }); 9963 9964 // Cancel button functionality 9965 subscribeBannerCancelButton.addEventListener("click", () => { 9966 window.dataLayer = window.dataLayer || []; 9967 window.dataLayer.push({ 9968 "event": "callToAction", //e111 9969 "callToAction": { 9970 "location": $(document).find("title")[0].text, //v103 9971 "ctaName": 'Subscribe Banner Dismiss', //v104 9972 "ctaType": 'custom button' //v111 9973 } 9974 }); 9975 subscribeBanner.remove(); 9976 }); 9977 9978 subscribeBannerButton.addEventListener("click", () => { 9979 window.dataLayer = window.dataLayer || []; 9980 window.dataLayer.push({ 9981 "event": "callToAction", //e111 9982 "callToAction": { 9983 "location": $(document).find("title")[0].text, //v103 9984 "ctaName": 'Subscribe Banner', //v104 9985 "ctaType": 'custom button' //v111 9986 } 9987 }); 9988 }); 9989} 9990(() => { 9991 const bannerElements = document.querySelectorAll('.videoBanner .youtube-container'); 9992 const componentElements = document.querySelectorAll('.videoComponent .main-youtube-container'); 9993 9994 const hasBanners = bannerElements.length > 0; 9995 const hasComponents = componentElements.length > 0; 9996 9997 console.debug(`[Bootstrap] videoBanner containers=${bannerElements.length} videoComponent containers=${componentElements.length}`); 9998 9999 if (!hasBanners && !hasComponents) { 10000 console.debug('[Bootstrap] no YouTube video components found, exiting'); 10001 return; 10002 } 10003 10004 localStorage.removeItem('YouTubeCookieAccepted'); 10005 10006 const hasConsent = hasOneTrustTargetingConsent(); 10007 console.debug(`[Bootstrap] OneTrust targeting consent=${hasConsent}`); 10008 10009 if (!hasConsent) { 10010 showOneTrustModal(); 10011 } else { 10012 console.debug('[Bootstrap] consent already granted â setting YouTubeCookieAccepted'); 10013 localStorage.setItem('YouTubeCookieAccepted', 'true'); 10014 } 10015 10016 loadYouTubeApi(() => { 10017 console.debug('[Bootstrap] YT API ready â initialising controllers'); 10018 10019 if (hasBanners) { 10020 const playerEls = document.querySelectorAll('.youtube-player'); 10021 console.debug(`[Bootstrap] initialising VideoBannerPlayer with ${playerEls.length} element(s)`); 10022 const bannerController = new VideoBannerPlayer(Array.from(playerEls), hasConsent); 10023 bannerController.init(); 10024 } 10025 10026 if (hasComponents) { 10027 const playerEls = document.querySelectorAll('.video-youtube-player'); 10028 console.debug(`[Bootstrap] initialising VideoComponentPlayer with ${playerEls.length} element(s)`); 10029 const componentController = new VideoComponentPlayer(Array.from(playerEls), hasConsent); 10030 componentController.init(); 10031 } 10032 }); 10033})(); 10034
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.