1/* 2===================================== 3Add website specific custom fucntions 4This JS reference is given to site-script.js 5===================================== 6*/ 7 8var $notEditMode = !$('.cms-page-is-in-edit-mode').length; 9var $editMode = $('.cms-page-is-in-edit-mode').length; 10var $notPublishedMode = !$('.cms-page-is-published').length; 11 12var $isHome = $('#home-page').length; 13var $notHome = !$isHome; 14const $window = $(window); 15 16// gsap.registerPlugin(ScrollTrigger); 17 18gsap.registerPlugin(SplitText, ScrollTrigger); 19 20 21$(document).ready(function () { 22 initializeStickyHeader(); 23 initializeUnslick(); 24 initializeHeroFunctionality(); 25 initializeFooterSocialWrapper(); 26 initializeImageThumbnailGallery(); 27 initializeStackedGallery(); 28 // initializeStackedGallerySlider(); 29 initializeThemeToggleButton(); 30 // initializeCustomSlider(); 31 initializeCustomGallerySlider(); 32 initializeCustomCardSlider(); 33 initializeCustomTabbedSlider(); 34 initializeCustomDisplaySlider(); 35 initializeInfographicChart(); 36 initializeSiteBookmark(); 37 initializeCustomPreloader(); 38 initializeLanguageToggle(); 39 initializeVideoPopup(); 40 initializeSgDivisionFilter(); 41 initializeStackedDisplaySlider(); 42 initializeBBDirectory(); 43 initializeContentIntroCheck(); 44 initializeVideoControls(); 45 removeAriaHiddenFromVideoSlides(); 46 // initializeAlternateHomeVersionLoader(); 47 initializeSiteClearGridWrapper(); 48 initializeAnchorOffset(); 49 cloneGalleryButton(); 50 utilityLinksClone(); 51 52 // blockquoteParallax(); 53 54 // initializefedCustomSlider(); 55 56 57}); 58 59// ================================ 60// Site Clear Grid Wrapper 61// ================================ 62function initializeSiteClearGridWrapper() { 63 $("#site-main-content-wrapper").append('<div class="site-clear-grid-wrapper"></div>'); 64} 65 66// ================================ 67// Anchor Offset Function 68// ================================ 69function initializeAnchorOffset() { 70 if (!$notEditMode) return; 71 72 const offset = 75; 73 74 function scrollToTarget(targetId) { 75 const element = document.getElementById(targetId); 76 if (element) { 77 const targetPosition = element.getBoundingClientRect().top + window.pageYOffset - offset; 78 $('html, body').animate({ scrollTop: targetPosition }, 800); 79 } 80 } 81 82 // Handle anchor clicks 83 $(document).on('click', 'a[href*="#"]', function(e) { 84 const href = $(this).attr('href'); 85 if (href.startsWith('#') || (href.includes('#') && !href.startsWith('http'))) { 86 e.preventDefault(); 87 scrollToTarget(href.split('#')[1]); 88 } 89 }); 90 91 // Handle URL hash on page load 92 if (window.location.hash) { 93 setTimeout(() => scrollToTarget(window.location.hash.substring(1)), 100); 94 } 95} 96 97// ================================ 98// Sticky Header 99// ================================ 100function initializeStickyHeader() { 101 if ($notEditMode) { 102 let ticking = false; 103 let lastScrollTop = 0; 104 const fsHeaderHeight = 0; 105 const $body = $('body'); 106 107 function updateStickyHeader() { 108 const scroll = window.pageYOffset || document.documentElement.scrollTop; 109 110 // Handle sticky header activation 111 if (scroll > fsHeaderHeight) { 112 $body.addClass("sticky-header-active"); 113 } else { 114 $body.removeClass("sticky-header-active"); 115 } 116 117 // Handle scroll direction for banner visibility 118 if (scroll > lastScrollTop && scroll > 100) { 119 // Scrolling down - hide banner 120 $body.removeClass('scrolled-up'); 121 } else if (scroll < lastScrollTop) { 122 // Scrolling up - show banner 123 $body.addClass('scrolled-up'); 124 } 125 126 lastScrollTop = scroll <= 0 ? 0 : scroll; // For Mobile or negative scrolling 127 ticking = false; 128 } 129 130 function requestTick() { 131 if (!ticking) { 132 requestAnimationFrame(updateStickyHeader); 133 ticking = true; 134 } 135 } 136 137 // Use passive event listener for better performance 138 window.addEventListener('scroll', requestTick, { passive: true });
139 140 // Initial check 141 updateStickyHeader(); 142 } 143} 144 145 146 147 148// Custom Unslick Features 149// ================================ 150function initializeUnslick() { 151 152 // Check if cms-gallery-feature exists on the page before running unslick 153 if ($('.unslick-wrapper').length) { 154 $(".unslick").each(function () { 155 var $slickWrapper = $(this).find('.site-slick-wrapper'); 156 157 // Check if element exists, has slick function, and is initialized 158 if ($slickWrapper.length && 159 typeof $slickWrapper.slick === 'function' && 160 $slickWrapper.hasClass('slick-initialized')) { 161 $slickWrapper.slick('unslick'); 162 } 163 }); 164 } 165} 166 167// ================================ 168// Hero 169// ================================ 170function initializeHeroFunctionality() { 171 if ($notEditMode) { 172 const $heroElement = $('#site-hero-wrapper'); 173 174 if (!$($heroElement).hasClass("no-features")) { 175 $('body').addClass('has-hero'); 176 } 177 178 const $landingHero = $('.landing-hero-wrapper'); 179 180 // Add scroll down button to landing hero 181 if ($landingHero.length) { 182 $landingHero.each(function() { 183 const $scrollDownButton = $('<button class="scroll-down-button">Scroll Down</button>'); 184 const $image = $(this).find('.site-slider-image-block').prepend('<div class="lines"><span class="line1"></span><span class="line2"></span><span class="line3"></span></div>'); 185 $(this).append($scrollDownButton); 186 }); 187 } 188 189 //Scroll down button 190 $(document).on("click", ".scroll-down-button", function (event) { 191 event.preventDefault(); 192 193 // Use modern smooth scrolling with fallback 194 const targetElement = document.getElementById("site-main-grid-block"); 195 if (targetElement) { 196 targetElement.scrollIntoView({ 197 behavior: 'smooth', 198 block: 'start' 199 }); 200 } else { 201 // Fallback to jQuery animate if element not found 202 $('html, body').animate({ 203 scrollTop: $("#site-main-grid-block").offset().top 204 }, 800); 205 } 206 }); 207 208 } 209} 210 211 212// ================================ 213// Footer Social Wrapper 214// ================================ 215 216function initializeFooterSocialWrapper() { 217 // Function to move social wrapper based on screen size 218 function moveFooterSocialWrapper() { 219 const socialWrapper = $('.site-footer-social-wrapper'); 220 const footerColumn1 = $('#site-footer-column-1'); 221 const footerBottom = $('.site-footer-bottom'); 222 223 if (socialWrapper.length && footerColumn1.length && footerBottom.length) { 224 if (window.innerWidth < 1024) { 225 // Move to footer bottom for screens under 1024px 226 if (socialWrapper.parent().is(footerColumn1)) { 227 socialWrapper.prependTo(footerBottom); 228 } 229 } else { 230 // Move back to footer column 1 for screens 1024px and above 231 if (socialWrapper.parent().is(footerBottom)) { 232 socialWrapper.appendTo(footerColumn1); 233 } 234 } 235 } 236 } 237 238 // Initial call 239 moveFooterSocialWrapper(); 240 241 // Call on window resize with debounce 242 let resizeTimer; 243 $(window).on('resize', function() { 244 clearTimeout(resizeTimer); 245 resizeTimer = setTimeout(function() { 246 moveFooterSocialWrapper(); 247 }, 250); 248 }); 249} 250 251 252 253// ================================ 254// Image thumbnail Gallery 255// ================================ 256function initializeImageThumbnailGallery() { 257 258 const $imageThumbnailGallery = $('.site-image-gallery-thumbnails-wrapper .site-slick-wrapper'); 259 260 // Check if image thumbnail gallery elements exist 261 if ($imageThumbnailGallery.length === 0) { 262 return; 263 } 264 265 if (!$imageThumbnailGallery.hasClass('slick-initialized')) { 266 267 // Declare caption container variable in higher scope 268 let $captionContainer; 269 270 $imageThumbnailGallery.on('init', function(event, slick) { 271 272 // Create a wrapper container for captions and dots
273 const $captionDotsWrapper = $('<div class="site-image-gallery-caption-dots-wrapper"></div>'); 274 275 // Move the dots into the wrapper 276 const $dots = $imageThumbnailGallery.find('.slick-dots'); 277 $dots.appendTo($captionDotsWrapper); 278 279 // Create caption container inside the wrapper 280 $captionContainer = $('<div class="site-image-gallery-caption-container"></div>'); 281 $captionDotsWrapper.prepend($captionContainer); 282 283 // Insert the wrapper after the slick list 284 $imageThumbnailGallery.find('.slick-list').after($captionDotsWrapper); 285 286 var $imageThumbnailGalleryDots = $imageThumbnailGallery.find('.slick-dots li'); 287 288 // Batch DOM reads and writes to prevent layout thrashing 289 const slides = $('.site-image-gallery-thumbnails-wrapper .site-slick-wrapper .slick-slide:not(.slick-cloned)'); 290 const dotButtons = []; 291 292 // First, collect all data without DOM writes 293 slides.each(function(i) { 294 var slideImage = $(this).find('img').attr('src'); 295 var slideCaption = $(this).find('.site-slider-caption').html(); 296 297 // Store dot button for later batch update 298 const $dotButton = $imageThumbnailGalleryDots.eq(i).find('button'); 299 if ($dotButton.length && slideImage) { 300 dotButtons.push({ button: $dotButton, image: slideImage }); 301 } 302 303 // Create caption elements for the synced slider 304 if (slideCaption) { 305 var $captionSlide = $('<div class="caption-slide" data-slide-index="' + i + '">' + slideCaption + '</div>'); 306 $captionContainer.append($captionSlide); 307 } 308 }); 309 310 // Batch all background-image updates in a single operation 311 if (dotButtons.length > 0) { 312 requestAnimationFrame(function() { 313 dotButtons.forEach(function(item) { 314 item.button.css('background-image', 'url(' + item.image + ')'); 315 }); 316 }); 317 } 318 319 // Show first caption initially 320 $captionContainer.find('.caption-slide').first().addClass('active'); 321 322 // Calculate and set min-height based on the tallest caption slide 323 // Use requestAnimationFrame to defer calculation and batch DOM reads/writes 324 // This prevents layout thrashing that causes scroll lag on macOS 325 requestAnimationFrame(function() { 326 let maxHeight = 0; 327 const $captionSlides = $captionContainer.find('.caption-slide'); 328 329 // Batch all DOM reads: first show all slides, then measure 330 $captionSlides.removeClass('active'); 331 $captionSlides.addClass('active'); 332 333 // Force a single layout calculation by reading all heights at once 334 $captionSlides.each(function() { 335 const slideHeight = $(this).outerHeight(); 336 if (slideHeight > maxHeight) { 337 maxHeight = slideHeight; 338 } 339 }); 340 341 // Batch DOM writes: hide all except first, then set min-height 342 $captionSlides.removeClass('active'); 343 $captionContainer.find('.caption-slide').first().addClass('active'); 344 345 // Set the min-height on the caption container 346 if (maxHeight > 0) { 347 $captionContainer.css('min-height', maxHeight + 'px'); 348 } 349 }); 350 351 }); 352 353 // Handle slide changes to sync captions 354 $imageThumbnailGallery.on('afterChange', function(event, slick, currentSlide) { 355 if ($captionContainer) {
356 $captionContainer.find('.caption-slide').removeClass('active'); 357 $captionContainer.find('.caption-slide[data-slide-index="' + currentSlide + '"]').addClass('active'); 358 } 359 }); 360 361 // Handle dot clicks to sync captions 362 $imageThumbnailGallery.on('click', '.slick-dots li button', function() { 363 if ($captionContainer) { 364 var slideIndex = $(this).parent().index(); 365 $captionContainer.find('.caption-slide').removeClass('active'); 366 $captionContainer.find('.caption-slide[data-slide-index="' + slideIndex + '"]').addClass('active'); 367 } 368 }); 369 370 $imageThumbnailGallery.slick({ 371 infinite: false, 372 slidesToShow: 1, 373 slidesToScroll: 1, 374 arrows: false, 375 dots: true, 376 fade: true, 377 378 }); 379 } 380 381} 382 383 384/* 385===================================== 386Stacked Gallery 387===================================== 388*/ 389function initializeStackedGallery() { 390 //if ($notEditMode) { 391 // Only run on screen sizes 1024px and upwards 392 if (window.innerWidth >= 1024 && $('.site-row-block-stacked-cards-container').length) { 393 394 $( ".site-row-block-stacked-cards-container" ).wrapInner( "<div class='site-row-block-stacked-cards-inner'></div>"); 395 396 // Check if site-row-block-stacked-top exists and prepend to inner container 397 const $stackedTop = $('.site-row-block-stacked-top'); 398 if ($stackedTop.length) { 399 $stackedTop.prependTo('.site-row-block-stacked-cards-inner'); 400 } 401 402 $('.site-stacked-gallery-wrapper').each(function() { 403 const $container = $(this); 404 const $slickWrapper = $container.find('.site-slick-wrapper'); 405 406 // Create caption container and append to .site-row-block-stacked-cards-container
407 const $captionContainer = $('<div class="site-stacked-gallery-caption-container"><div class="site-stacked-gallery-caption-inner"></div></div>'); 408 $container.closest('.site-row-block-stacked-cards-container .site-row-block-stacked-cards-inner').append($captionContainer); 409 410 // Loop through and find site-slider-caption and append each to the new caption container 411 $slickWrapper.find('.site-slide').each(function(index) { 412 const $captionBlock = $(this).find('.site-slider-caption'); 413 if ($captionBlock.length) { 414 const $captionElement = $('<div class="caption-element" data-slide-index="' + index + '">' + $captionBlock.html() + '</div>'); 415 $captionContainer.find('.site-stacked-gallery-caption-inner').append($captionElement); 416 } 417 }); 418 419 // Set the first slide as active by default 420 const $firstSlide = $slickWrapper.find('.site-slide').first(); 421 const $firstCaption = $captionContainer.find('.caption-element').first(); 422 $firstSlide.addClass('active'); 423 $firstCaption.addClass('active'); 424 425 // Add hover functionality to caption elements 426 $captionContainer.find('.caption-element').on('mouseenter', function() { 427 const slideIndex = $(this).data('slide-index'); 428 const $slides = $slickWrapper.find('.site-slide'); 429 430 // Clear any existing timeouts to prevent conflicts 431 if (window.stackedGalleryTimeout) { 432 clearTimeout(window.stackedGalleryTimeout); 433 } 434 435 // Remove active class from all captions and slides 436 $captionContainer.find('.caption-element').removeClass('active'); 437 $slides.removeClass('active'); 438 439 // Add active class to current caption and corresponding slide 440 $(this).addClass('active'); 441 $slides.eq(slideIndex).addClass('active'); 442 }); 443 444 // Reset to default state when mouse leaves caption 445 $captionContainer.find('.caption-element').on('mouseleave', function() { 446 const $slides = $slickWrapper.find('.site-slide'); 447 const $firstSlide = $slides.first(); 448 const $firstCaption = $captionContainer.find('.caption-element').first(); 449 450 // Remove active class from caption immediately 451 $captionContainer.find('.caption-element').removeClass('active'); 452 453 // Add a small delay before removing slide active class for smoother transition 454 window.stackedGalleryTimeout = setTimeout(function() { 455 $slides.removeClass('active'); 456 457 // Return active state to first slide and caption 458 $firstSlide.addClass('active'); 459 $firstCaption.addClass('active'); 460 }, 200); 461 }); 462 }); 463 } 464 //} 465} 466 467/* 468===================================== 469Stacked Gallery Slider version not in use 470===================================== 471*/ 472// function initializeStackedGallerySlider() { 473 474// if ($('.site-row-block-stacked-cards-slider-container').length) { 475 476// //$( ".site-row-block-stacked-cards-slider-container" ).wrapInner( "<div class='site-row-block-stacked-cards-slider-inner'></div>"); 477 478// const $stackedGallerySliderContainer = $('.site-stacked-gallery-slider-wrapper'); 479// const $customStackedGallerySlider = $stackedGallerySliderContainer.find('.site-slick-wrapper'); 480 481 482// const $customSliderArrows = $('<div class="stacked-slider-arrow-container"><button class="slick-arrow slick-prev">Previous</button><button class="slick-arrow slick-next">Next</button></div>'); 483// $customSliderArrows.appendTo($stackedGallerySliderContainer); 484 485// // Create caption container and append to .site-row-block-stacked-cards-container
486// const $sliderCaptionContainer = $('<div class="site-stacked-display-caption-container"></div>'); 487 488// $sliderCaptionContainer.appendTo($stackedGallerySliderContainer); 489 490// // Loop through and find site-slider-caption and append each to the new caption container 491// $customStackedGallerySlider.find('.site-slide').each(function(index) { 492// const $captionBlock = $(this).find('.site-slider-caption'); 493// if ($captionBlock.length) { 494// const $captionElement = $('<div class="caption-element" data-slide-index="' + index + '">' + $captionBlock.html() + '</div>'); 495// $sliderCaptionContainer.append($captionElement); 496// } 497// }); 498 499 500 501// if ($customStackedGallerySlider.length) { 502 503// $customStackedGallerySlider.each(function() { 504// var $stackedSlider = $(this); 505 506// $stackedSlider.slick({ 507// slidesToShow: 1, 508// slidesToScroll: 1, 509// dots: false, 510// arrows: true, 511// infinite: true, 512// fade: true, 513// prevArrow: $stackedGallerySliderContainer.find('.stacked-slider-arrow-container .slick-prev'), 514// nextArrow: $stackedGallerySliderContainer.find('.stacked-slider-arrow-container .slick-next'), 515// asNavFor: $sliderCaptionContainer, 516// }); 517 518// // Initialize synchronized caption slider 519 520// $sliderCaptionContainer.slick({ 521// slidesToShow: 1, 522// slidesToScroll: 1, 523// dots: false, 524// arrows: false, 525// infinite: true, 526// fade: true, 527// speed: 600, 528// asNavFor: $stackedSlider, 529// }); 530 531 532// }); 533 534// } 535// } 536// } 537 538 539/* 540===================================== 541Stacked Gallery Slider version 542===================================== 543*/ 544function initializeStackedDisplaySlider() { 545 546 if ($('.site-row-block-stacked-cards-display-container').length) { 547 548 $( ".site-row-block-stacked-cards-display-container" ).wrapInner( "<div class='site-row-block-stacked-cards-slider-inner'></div>"); 549 550 const $stackedDisplaySliderContainer = $('.site-display-stacked-cards-wrapper'); 551 const $customStackedDisplaySlider = $stackedDisplaySliderContainer.find('.site-display-list'); 552 553 554 // Create caption container and append to .site-row-block-stacked-cards-container
555 const $sliderCaptionContainer = $('<div class="site-stacked-slider-caption-container"></div>'); 556 557 $sliderCaptionContainer.appendTo($stackedDisplaySliderContainer); 558 559 const $displayCustomSliderArrows = $('<div class="stacked-slider-arrow-container"><button class="slick-arrow slick-prev">Previous</button><button class="slick-arrow slick-next">Next</button></div>'); 560 $displayCustomSliderArrows.appendTo($stackedDisplaySliderContainer); 561 562 // Create separate mobile arrows container (just arrows, no dots) 563 const $displayCustomSliderArrowsMobile = $('<div class="mobile-stacked-arrow-container"><button class="slick-arrow slick-prev">Previous</button><button class="slick-arrow slick-next">Next</button></div>'); 564 565 566 // Loop through and find site-slider-caption and append each to the new caption container 567 $customStackedDisplaySlider.find('.site-display-wrapper-listitem').each(function(index) { 568 const $captionBlock = $(this).find('.site-display-data-block'); 569 if ($captionBlock.length) { 570 const $captionElement = $('<div class="caption-element" data-slide-index="' + index + '"><div class="caption-element-inner">' + $captionBlock.html() + '</div></div>'); 571 $sliderCaptionContainer.append($captionElement); 572 573 // Now find and wrap the bookmark button inside the caption element 574 const $displayBookmarkButtons = $captionElement.find('.site-bookmark-button'); 575 $displayBookmarkButtons.each(function() { 576 const $displayBookmarkButton = $(this); 577 578 // Only process if not already processed (check for data attribute) 579 if (!$displayBookmarkButton.data('collection-text-added')) { 580 // Only wrap if not already wrapped 581 if (!$displayBookmarkButton.find('.site-bookmark-button-inner').length) { 582 $displayBookmarkButton.wrapInner('<div class="site-bookmark-button-inner"></div>'); 583 } 584 585 // Update bookmark button text with subtext based on button class 586 const $innerDiv = $displayBookmarkButton.find('.site-bookmark-button-inner'); 587 if ($innerDiv.length) { 588 if ($displayBookmarkButton.hasClass("site-bookmark-display-button-add")) { 589 $innerDiv.html('Add <small >to my collection</small>'); 590 } else if ($displayBookmarkButton.hasClass("site-bookmark-display-button-remove")) { 591 $innerDiv.html('Remove <small>from my collection</small>'); 592 } else { 593 // Default to add if no specific class is found 594 $innerDiv.html('Add <small>to my collection</small>'); 595 } 596 597 // Mark as processed to prevent duplicate processing 598 $displayBookmarkButton.data('collection-text-added', true); 599 } 600 } 601 }); 602 } 603 }); 604 605 606 if ($customStackedDisplaySlider.length) { 607 608 $customStackedDisplaySlider.each(function() { 609 var $stackedSlider = $(this); 610 611 $stackedSlider.slick({ 612 slidesToShow: 1, 613 slidesToScroll: 1, 614 dots: false, 615 arrows: true, 616 infinite: false, 617 fade: true, 618 prevArrow: $stackedDisplaySliderContainer.find('.stacked-slider-arrow-container .slick-prev'), 619 nextArrow: $stackedDisplaySliderContainer.find('.stacked-slider-arrow-container .slick-next'), 620 asNavFor: $sliderCaptionContainer, 621 }); 622 623 624 // Initialize synchronized caption slider 625 $sliderCaptionContainer.slick({ 626 slidesToShow: 1, 627 slidesToScroll: 1, 628 dots: false,
629 arrows: false, 630 infinite: false, 631 fade: true, 632 speed: 600, 633 asNavFor: $stackedSlider, 634 responsive: [ 635 { 636 breakpoint: 1024, 637 settings: { 638 slidesToShow: 1, 639 slidesToScroll: 1, 640 fade: false, 641 adaptiveHeight: true, 642 } 643 } 644 645 ] 646 }); 647 648 // Add mobile arrows to the caption container 649 var $mobileArrows = $displayCustomSliderArrowsMobile.clone(); 650 $mobileArrows.addClass('mobile-stacked-slide-arrows'); 651 $sliderCaptionContainer.prepend($mobileArrows); 652 653 // Set up click handlers for mobile arrows 654 $mobileArrows.find('.slick-prev').on('click', function() { 655 $stackedSlider.slick('slickPrev'); 656 }); 657 658 $mobileArrows.find('.slick-next').on('click', function() { 659 $stackedSlider.slick('slickNext'); 660 }); 661 662 //Function to update disabled state on mobile arrows 663 function updateMobileArrowsDisabledState() { 664 var currentSlide = $stackedSlider.slick('slickCurrentSlide'); 665 var slideCount = $stackedSlider.slick('getSlick').slideCount; 666 667 // Get the mobile arrow buttons 668 var $mobilePrevButton = $mobileArrows.find('.slick-prev'); 669 var $mobileNextButton = $mobileArrows.find('.slick-next'); 670 671 // Remove disabled class first 672 $mobilePrevButton.removeClass('slick-disabled'); 673 $mobileNextButton.removeClass('slick-disabled'); 674 675 // Add disabled class based on current position 676 if (currentSlide === 0) { 677 // On first slide - disable prev button 678 $mobilePrevButton.addClass('slick-disabled'); 679 } 680 681 if (currentSlide >= slideCount - 1) { 682 // On last slide - disable next button 683 $mobileNextButton.addClass('slick-disabled'); 684 } 685 } 686 687 // Update disabled state on init 688 $sliderCaptionContainer.on('init reInit', function(event, slick) { 689 updateMobileArrowsDisabledState(); 690 }); 691 692 // Update disabled state on slide change 693 $stackedSlider.on('afterChange', function(event, slick, currentSlide) { 694 updateMobileArrowsDisabledState(); 695 }); 696 697 // Initial update 698 setTimeout(updateMobileArrowsDisabledState, 100); 699 700 // Handle resize to reinitialize sliders when crossing breakpoint 701 var lastWidth = $(window).width(); 702 var resizeTimer; 703 704 $(window).on('resize', function() { 705 clearTimeout(resizeTimer); 706 resizeTimer = setTimeout(function() { 707 var currentWidth = $(window).width(); 708 709 // Check if we crossed the 1024px breakpoint 710 if ((lastWidth >= 1024 && currentWidth < 1024) || (lastWidth < 1024 && currentWidth >= 1024)) { 711 712 try { 713 // Save current slide index 714 var currentSlideIndex = $stackedSlider.slick('slickCurrentSlide'); 715 716 // Destroy both sliders 717 $sliderCaptionContainer.slick('unslick'); 718 $stackedSlider.slick('unslick'); 719 720 // Remove old mobile arrows 721 $mobileArrows.remove(); 722 723 // Reinitialize main slider 724 $stackedSlider.slick({ 725 slidesToShow: 1, 726 slidesToScroll: 1, 727 dots: false, 728 arrows: true, 729 infinite: false, 730 fade: true, 731 initialSlide: currentSlideIndex, 732 prevArrow: $stackedDisplaySliderContainer.find('.stacked-slider-arrow-container .slick-prev'), 733 nextArrow: $stackedDisplaySliderContainer.find('.stacked-slider-arrow-container .slick-next'), 734 asNavFor: $sliderCaptionContainer, 735 }); 736 737 // Reinitialize caption slider 738 $sliderCaptionContainer.slick({ 739 slidesToShow: 1, 740 slidesToScroll: 1, 741 dots: false,
742 arrows: false, 743 infinite: false, 744 fade: true, 745 speed: 600, 746 initialSlide: currentSlideIndex, 747 asNavFor: $stackedSlider, 748 responsive: [ 749 { 750 breakpoint: 1024, 751 settings: { 752 slidesToShow: 1, 753 slidesToScroll: 1, 754 fade: false, 755 adaptiveHeight: true, 756 } 757 } 758 ] 759 }); 760 761 // Re-add mobile arrows 762 $mobileArrows = $displayCustomSliderArrowsMobile.clone(); 763 $mobileArrows.addClass('mobile-stacked-slide-arrows'); 764 $sliderCaptionContainer.prepend($mobileArrows); 765 766 // Re-attach click handlers 767 $mobileArrows.find('.slick-prev').on('click', function() { 768 $stackedSlider.slick('slickPrev'); 769 }); 770 771 $mobileArrows.find('.slick-next').on('click', function() { 772 $stackedSlider.slick('slickNext'); 773 }); 774 775 // Update disabled state 776 setTimeout(updateMobileArrowsDisabledState, 100); 777 778 } catch(e) { 779 console.error('Error reinitializing sliders:', e); 780 } 781 } 782 783 lastWidth = currentWidth; 784 }, 300); 785 }); 786 787 788 }); 789 790 } 791 } 792} 793 794/* 795===================================== 796Theme Toggle Button 797===================================== 798*/ 799function initializeThemeToggleButton() { 800 if ($notEditMode) { 801 // Check if we're on home or landing page 802 const isHomePage = $('.home-page').length > 0; 803 const isLandingPage = $('.landing-page').length > 0; 804 805 if (isHomePage || isLandingPage) { 806 const $heroWrapper = $('#site-hero-wrapper'); 807 808 if ($heroWrapper.length) { 809 // Create toggle button container
810 const $toggleContainer = $('<div class="theme-toggle-container"></div>'); 811 812 // Create toggle button 813 const $toggleButton = $('<button class="theme-toggle-button" title="Toggle Theme"></button>'); 814 815 // Append button to container, then container to hero wrapper 816 $toggleContainer.append($toggleButton); 817 $heroWrapper.append($toggleContainer); 818 819 // Add click event to toggle body class 820 $toggleButton.on('click', function(e) { 821 e.preventDefault(); // Prevent any default button behavior 822 823 const $body = $('body'); 824 const $button = $(this); 825 826 // Add a small delay to ensure smooth transition 827 if ($body.hasClass('custom-primary-theme')) { 828 $body.removeClass('custom-primary-theme'); 829 // $button.text('ð').attr('title', 'Toggle Theme'); 830 } else { 831 $body.addClass('custom-primary-theme'); 832 // $button.text('âï¸').attr('title', 'Toggle Theme'); 833 } 834 }); 835 } 836 } 837 } 838 839} 840 841 842/* 843===================================== 844Custom Slider using Swiper.js 845===================================== 846*/ 847// function initializeCustomSlider() { 848 849// const $customSliderContainer = $('.site-gallery-slider-wrapper'); 850// const $customSlider = $customSliderContainer.find('.site-slick-wrapper'); 851 852// // Check if custom slider elements exist 853// if ($customSlider.length === 0) { 854// return; 855// } 856 857// $customSlider.each(function() { 858 859// var $customSliderItem = $(this); 860 861// // Check if Swiper is already initialized on this element 862// if ($customSliderItem.find('.swiper-container').hasClass('swiper-initialized')) { 863 864// return; 865// } 866 867// // First, destroy Slick slider if it exists 868// if ($customSliderItem.hasClass('slick-initialized')) { 869 870// $customSliderItem.slick('unslick'); 871// } 872 873// // Remove any Slick-specific classes and data 874// $customSliderItem.removeClass('slick-initialized slick-slider'); 875// $customSliderItem.find('.slick-slide').removeClass('slick-slide slick-active slick-center slick-current slick-cloned'); 876// $customSliderItem.find('.slick-list, .slick-track').remove(); 877 878// // Create a completely new Swiper structure
879// const $swiperContainer = $('<div class="swiper-container"></div>'); 880// const $swiperWrapper = $('<div class="swiper-wrapper"></div>'); 881 882// // Move all slides to the new Swiper structure 883// const slides = $customSliderItem.find('.site-slide'); 884 885// // Clone slides to ensure enough slides for smooth looping 886// const totalSlides = slides.length; 887// const minSlidesForLoop = 8; // Minimum slides needed for smooth loop 888 889// // If we have fewer slides than needed for smooth looping, clone them 890// if (totalSlides < minSlidesForLoop) { 891// const clonesNeeded = minSlidesForLoop - totalSlides; 892 893// // Add original slides 894// slides.each(function(index) { 895// var $slide = $(this).clone(); 896// $slide.addClass('swiper-slide'); 897// $slide.attr('data-swiper-slide-index', index); 898// $swiperWrapper.append($slide); 899// }); 900 901// // Add cloned slides to reach minimum count 902// for (let i = 0; i < clonesNeeded; i++) { 903// const originalIndex = i % totalSlides; 904// const $originalSlide = slides.eq(originalIndex); 905// var $clonedSlide = $originalSlide.clone(); 906// $clonedSlide.addClass('swiper-slide swiper-slide-cloned'); 907// $clonedSlide.attr('data-swiper-slide-index', totalSlides + i); 908// $swiperWrapper.append($clonedSlide); 909// } 910// } else { 911// // If we have enough slides, just add them normally 912// slides.each(function(index) { 913// var $slide = $(this).clone(); 914// $slide.addClass('swiper-slide'); 915// $slide.attr('data-swiper-slide-index', index); 916// $swiperWrapper.append($slide); 917// }); 918// } 919 920// // Replace the old structure with the new Swiper structure 921// $swiperContainer.append($swiperWrapper); 922// $customSliderItem.empty().append($swiperContainer); 923 924// // Add navigation arrows to the slider container
925// const $navigationContainer = $('<div class="swiper-navigation"></div>'); 926// const $prevButton = $('<div class="swiper-button-prev"></div>'); 927// const $nextButton = $('<div class="swiper-button-next"></div>'); 928// $navigationContainer.append($prevButton, $nextButton); 929 930// $customSliderContainer.append($navigationContainer); 931 932// // Initialize Swiper after ensuring Slick is destroyed 933// // Use the specific swiper container for this slider, not document.querySelector 934// const swiperElement = $swiperContainer[0]; 935 936// // Check the structure 937// const swiperWrapper = swiperElement.querySelector('.swiper-wrapper'); 938// const swiperSlides = swiperWrapper.querySelectorAll('.swiper-slide'); 939 940// // Only initialize if we have slides 941// if (swiperSlides.length > 0) { 942// // const swiper = new Swiper(swiperElement, { 943// // slidesPerView: 'auto', 944// // spaceBetween: 32, 945// // loop: true, 946// // // autoplay: { 947// // // delay: 3000, 948// // // disableOnInteraction: false, 949// // // pauseOnMouseEnter: false, 950// // // }, 951// // centeredSlides: true, 952// // speed: 1000, 953// // effect: 'slide', 954// // navigation: { 955// // nextEl: '.swiper-button-next', 956// // prevEl: '.swiper-button-prev', 957// // }, 958// // }); 959 960// const swiper = new Swiper($swiperContainer[0], { 961// slidesPerView: 'auto', 962// spaceBetween: 32, 963// loop: true, 964// centeredSlides: true, 965// speed: 1000, 966// loopedSlides: slides.length, 967// slidesOffsetBefore: 340, 968// navigation: { 969// nextEl: '.swiper-button-next', 970// prevEl: '.swiper-button-prev', 971// }, 972// }); 973 974// // Start autoplay only if swiper and autoplay are properly initialized 975// // if (swiper && swiper.autoplay) { 976// // swiper.autoplay.start(); 977// // } 978// } 979 980// }); 981// } 982 983 984 985/* 986===================================== 987Custom Sliders Gallery 988===================================== 989*/ 990function initializeCustomGallerySlider() { 991 992 const $customGallerySliderContainer = $('.custom-gallery-slider-wrapper'); 993 const $customGallerySlider = $customGallerySliderContainer.find('.site-slick-wrapper'); 994 995 // Check if custom gallery slider elements exist 996 if ($customGallerySlider.length === 0) { 997 return; 998 } 999 1000 const $customSliderArrows = $('<div class="slider-arrow-container"><button class="slick-arrow slick-prev">Previous</button><button class="slick-arrow slick-next">Next</button></div>'); 1001 $customSliderArrows.appendTo($customGallerySliderContainer); 1002 1003 // Create separate mobile arrows container (just arrows, no dots) 1004 const $customSliderArrowsMobile = $('<div class="mobile-arrow-container"><button class="slick-arrow slick-prev">Previous</button><button class="slick-arrow slick-next">Next</button></div>'); 1005 1006 $customGallerySlider.each(function() { 1007 var $gallerySliderItem = $(this); 1008 var $gallerySliderPanel = $gallerySliderItem.closest('.custom-gallery-slider-wrapper'); 1009 1010 // Extract caption text for custom dots 1011 var $slides = $gallerySliderItem.find('.site-slide'); 1012 var customDots = []; 1013 1014 1015 $slides.each(function(index) { 1016 var $caption = $(this).find('.site-slider-caption .site-container .site-slider-caption-data h4'); 1017 1018 // Try alternative selectors if the first one doesn't work 1019 if (!$caption.length) { 1020 $caption = $(this).find('.site-slider-caption h4'); 1021 } 1022 1023 var captionText = $caption.length ? $caption.text().trim() : 'Slide ' + (index + 1); 1024 customDots.push(captionText); 1025 }); 1026 1027 1028 $gallerySliderItem.slick({ 1029 slidesToShow: 1, 1030 slidesToScroll: 1, 1031 dots: true, 1032 arrows: true, 1033 infinite: true, 1034 fade: true, 1035 prevArrow: $gallerySliderPanel.find('.slider-arrow-container .slick-prev'), 1036 nextArrow: $gallerySliderPanel.find('.slider-arrow-container .slick-next'), 1037 customPaging: function(slider, i) { 1038 return '<button type="button" class="custom-dot" data-sli
1038de="' + i + '">' + customDots[i] + '</button>'; 1039 }, 1040 responsive: [ 1041 { 1042 breakpoint: 1024, 1043 settings: { 1044 slidesToShow: 1, 1045 slidesToScroll: 1, 1046 adaptiveHeight: true, 1047 } 1048 } 1049 ] 1050 }); 1051 1052 // Move the slick dots into the slider-arrow-container 1053 var $dotsContainer = $gallerySliderPanel.find('.slick-dots'); 1054 var $arrowContainer = $gallerySliderPanel.find('.slider-arrow-container'); 1055 1056 // if ($dotsContainer.length && $arrowContainer.length) { 1057 // $dotsContainer.prependTo($arrowContainer); 1058 1059 // // Add custom styling for the custom dots 1060 // $dotsContainer.addClass('custom-dots-container'); 1061 // } 1062 1063 $dotsContainer.prependTo($gallerySliderPanel); 1064 // Add custom styling for the custom dots 1065 $dotsContainer.addClass('custom-dots-container'); 1066 1067 // Simple mobile arrow setup - clone mobile arrows under each slide image 1068 $slides.each(function() { 1069 var $slide = $(this); 1070 var $imageBlock = $slide.find('.site-slider-image-block'); 1071 1072 if ($imageBlock.length) { 1073 // Clone the mobile arrows (no dots) and add them under the image 1074 var $mobileArrows = $customSliderArrowsMobile.clone(); 1075 $mobileArrows.addClass('mobile-slide-arrows'); 1076 $mobileArrows.insertAfter($imageBlock); 1077 1078 // Add click handlers to make mobile arrows work 1079 $mobileArrows.find('.slick-prev').on('click', function() { 1080 $gallerySliderItem.slick('slickPrev'); 1081 }); 1082 1083 $mobileArrows.find('.slick-next').on('click', function() { 1084 $gallerySliderItem.slick('slickNext'); 1085 }); 1086 } 1087 }); 1088 1089 }); 1090 1091} 1092 1093/* 1094===================================== 1095Custom Card Slider 1096===================================== 1097*/ 1098function initializeCustomCardSlider() { 1099 1100 const $customCardSliderContainer = $('.site-card-slider-wrapper'); 1101 const $customCardSlider = $customCardSliderContainer.find('.site-slick-wrapper'); 1102 1103 // Check if custom card slider elements exist 1104 if ($customCardSlider.length === 0) { 1105 return; 1106 } 1107 1108 const $customCardSliderArrows = $('<div class="slider-arrow-container"><button class="slick-arrow slick-prev">Previous</button><button class="slick-arrow slick-next">Next</button></div>'); 1109 $customCardSliderArrows.appendTo($customCardSliderContainer); 1110 1111 $customCardSlider.each(function() { 1112 var $slider = $(this); 1113 1114 // var $sliderPanel = $slider.closest('.custom-slider-wrapper'); 1115 $slider.slick({ 1116 slidesToShow: 1, 1117 slidesToScroll: 1, 1118 dots: false, 1119 arrows: true, 1120 infinite: true, 1121 centerMode: true, 1122 centerPadding: '0px', 1123 variableWidth: true, 1124 prevArrow: $customCardSliderContainer.find('.slider-arrow-container .slick-prev'), 1125 nextArrow: $customCardSliderContainer.find('.slider-arrow-container .slick-next'), 1126 1127 }); 1128 1129 //Add custom class to next or previous slide depending on change direction 1130 $slider.on('beforeChange', (event, slick, currentSlide, nextSlide) => { 1131 let nextIndex = currentSlide + 1; // assume moving right 1132 1133 if ((currentSlide > nextSlide && (nextSlide !== 0 || currentSlide === 1)) || (currentSlide === 0 && nextSlide === slick.slideCount - 1)) { 1134 // console.log('previous'); 1135 nextIndex = currentSlide - 1; // nope, moving left 1136 } 1137 1138 $(`[data-slick-index="${nextIndex}"]`).addClass('slick-target'); 1139 }); 1140 1141 // clear custom class after transition 1142 $slider.on('afterChange', () => { 1143 $('.slick-slide').removeClass('slick-target'); 1144 }); 1145 1146 1147 });
1148 1149 1150} 1151 1152 1153 1154/* 1155===================================== 1156Custom Tabbed Gallery Slider 1157===================================== 1158*/ 1159 1160function initializeCustomTabbedSlider() { 1161 1162 const $customSliderContainer = $('.custom-tabbed-autoscroll-gallery-wrapper'); 1163 const $customSlider = $customSliderContainer.find('.site-slick-wrapper'); 1164 1165 if ($customSlider.length === 0) return; 1166 1167 $customSlider.each(function () { 1168 const $slider = $(this); 1169 1170 // Destroy Slick if initialized 1171 if ($slider.hasClass('slick-initialized')) { 1172 $slider.slick('unslick'); 1173 } 1174 $slider.removeClass('slick-initialized slick-slider'); 1175 $slider.find('.slick-slide').removeClass('slick-slide slick-active slick-center slick-current slick-cloned'); 1176 $slider.find('.slick-list, .slick-track').remove(); 1177 1178 // Clone slides to ensure enough slides for smooth looping 1179 let slides = $slider.find('.site-slide'); 1180 const totalSlides = slides.length; 1181 const minSlidesForLoop = 8; // Minimum slides needed for smooth loop 1182 1183 // If we have fewer slides than needed for smooth looping, clone them 1184 if (totalSlides < minSlidesForLoop) { 1185 const clonesNeeded = minSlidesForLoop - totalSlides; 1186 1187 // Add cloned slides to reach minimum count 1188 for (let i = 0; i < clonesNeeded; i++) { 1189 const originalIndex = i % totalSlides; 1190 const $originalSlide = slides.eq(originalIndex); 1191 const $clonedSlide = $originalSlide.clone(); 1192 $clonedSlide.addClass('swiper-slide-cloned'); 1193 $slider.append($clonedSlide); 1194 } 1195 slides = $slider.find('.site-slide'); // refresh after cloning 1196 } 1197 1198 // Build main Swiper structure
1199 const $swiperContainer = $('<div class="swiper-container"></div>'); 1200 const $swiperWrapper = $('<div class="swiper-wrapper"></div>'); 1201 1202 slides.each(function (index) { 1203 const $slide = $(this).clone(); 1204 $slide.addClass('swiper-slide'); 1205 $slide.attr('data-swiper-slide-index', index); 1206 $swiperWrapper.append($slide); 1207 }); 1208 1209 $swiperContainer.append($swiperWrapper); 1210 $slider.empty().append($swiperContainer); 1211 1212 // Add navigation arrows to the main container 1213 const $navigationContainer = $('<div class="swiper-navigation"></div>'); 1214 const $prevButton = $('<div class="swiper-button-prev"></div>'); 1215 const $nextButton = $('<div class="swiper-button-next"></div>'); 1216 $navigationContainer.append($prevButton, $nextButton); 1217 1218 $customSliderContainer.find('.custom-tabbed-autoscroll-gallery').append($navigationContainer); 1219 1220 // Build caption Swiper 1221 const $captionContainer = $('<
1221div class="custom-tabbed-slider-caption-container"></div>'); 1222 const $captionSwiperContainer = $('<div class="swiper-container caption-swiper"></div>'); 1223 const $captionSwiperWrapper = $('<div class="swiper-wrapper"></div>'); 1224 1225 slides.each(function (index) { 1226 const $captionBlock = $(this).find('.site-slider-caption'); 1227 const $captionSlide = $('<div class="swiper-slide caption-slide"></div>'); 1228 1229 if ($captionBlock.length) { 1230 $captionSlide.append($captionBlock.clone()); 1231 } else { 1232 $captionSlide.html('<div class="empty-caption"></div>'); 1233 } 1234 $captionSwiperWrapper.append($captionSlide); 1235 }); 1236 1237 $captionSwiperContainer.append($captionSwiperWrapper); 1238 $captionContainer.append($captionSwiperContainer); 1239 $customSliderContainer.append($captionContainer); 1240 1241 // Initialize main Swiper 1242 const mainSwiper = new Swiper($swiperContainer[0], { 1243 slidesPerView: 'auto', 1244 spaceBetween: 32, 1245 loop: true, 1246 centeredSlides: true, 1247 speed: 1000, 1248 loopedSlides: slides.length, 1249 slidesOffsetBefore: window.innerWidth < 1024 ? 0 : 340, 1250 navigation: { 1251 nextEl: '.swiper-button-next', 1252 prevEl: '.swiper-button-prev', 1253 }, 1254 autoplay: { 1255 delay: 10000, // 10 seconds 1256 disableOnInteraction: false, 1257 pauseOnMouseEnter: true, 1258 }, 1259 }); 1260 1261 1262 // Initialize caption Swiper with fade effect 1263 const captionSwiper = new Swiper($captionSwiperContainer[0], { 1264 slidesPerView: 1, 1265 loop: false, 1266 allowTouchMove: false, 1267 speed: 500, 1268 effect: 'fade', 1269 fadeEffect: { crossFade: true }, 1270 autoHeight: true, // Automatically adjust height based on active slide content 1271 }); 1272 1273 // Sync captions with main slider 1274 mainSwiper.on('slideChange', function () { 1275 captionSwiper.slideTo(mainSwiper.realIndex); 1276 }); 1277 1278 }); 1279} 1280 1281 1282 1283 1284 1285 1286/* 1287===================================== 1288Custom Card Slider 1289===================================== 1290*/ 1291// function initializeCustomDisplaySlider() { 1292 1293// const $customDisplaySliderContainer = $('.site-display-slider-wrapper'); 1294 1295// // Check if container exists 1296// if ($customDisplaySliderContainer.length === 0) { 1297// return; 1298// } 1299 1300// const $customDisplaySlider = $customDisplaySliderContainer.find('.site-display-list'); 1301 1302// // Check if custom card slider elements exist 1303// if ($customDisplaySlider.length === 0) { 1304// return; 1305// } 1306 1307// const $customDisplaySliderArrows = $('<div class="display-slider-arrow-container"><button class="slick-arrow slick-prev">Previous</button><button class="slick-arrow slick-next">Next</button></div>'); 1308 1309// // Check if site-display-filters exists, otherwise append to customDisplaySlider 1310// const $siteDisplayFilters = $customDisplaySliderContainer.find('.site-display-filters'); 1311// if ($siteDisplayFilters.length > 0) { 1312// $customDisplaySliderArrows.appendTo($siteDisplayFilters); 1313// } else { 1314// $customDisplaySliderArrows.appendTo($customDisplaySliderContainer); 1315// } 1316 1317// // Store references to arrow elements for later use 1318// const $prevArrow = $customDisplaySliderContainer.find('.display-slider-arrow-container .slick-prev'); 1319// const $nextArrow = $customDisplaySliderContainer.find('.display-slider-arrow-container .slick-next'); 1320 1321// // Remove 'By' text from site-display-filter-title elements 1322// $customDisplaySliderContainer.find('.site-display-filter-title').each(function() { 1323// var $title = $(this); 1324// var text = $title.text(); 1325// if (text.includes('By')) { 1326// $title.text(text.replace(/By\s*/g, '').trim());
1327// } 1328// // Remove space before colon 1329// var currentText = $title.text(); 1330// $title.text(currentText.replace(/\s+:/g, ':')); 1331// }); 1332 1333// $customDisplaySlider.each(function() { 1334// var $displayslider = $(this); 1335 1336// // Check if slider has slides before initializing 1337// var $slides = $displayslider.find('> *'); 1338// if ($slides.length === 0) { 1339 1340// return; 1341// } 1342 1343// // Check if slider is already initialized to prevent double initialization 1344// if ($displayslider.hasClass('slick-initialized')) { 1345// return; 1346// } 1347 1348// // var $sliderPanel = $slider.closest('.custom-slider-wrapper'); 1349// try { 1350// $displayslider.slick({ 1351// slidesToShow: 1, 1352// slidesToScroll: 1, 1353// dots: false, 1354// arrows: $prevArrow.length > 0 && $nextArrow.length > 0, // Only enable arrows if they exist 1355// infinite: $slides.length > 1, // Only enable infinite if there are multiple slides 1356// centerMode: true, 1357// centerPadding: '0px', 1358// variableWidth: true, 1359// prevArrow: $prevArrow.length > 0 ? $prevArrow : null, 1360// nextArrow: $nextArrow.length > 0 ? $nextArrow : null, 1361// accessibility: true, // Explicitly enable accessibility 1362// adaptiveHeight: false, 1363// lazyLoad: 'ondemand' // Add lazy loading to prevent issues with missing images 1364// }); 1365// } catch (error) { 1366// // console.error('Error initializing custom display slider:', error); 1367// } 1368 1369// }); 1370 1371// // Add click event for filter data block labels to refresh slider 1372// $customDisplaySliderContainer.find('.site-display-filters .site-display-filter-desc .site-display-filter-wrapper .site-display-filter-data-block label').on('click', function() { 1373// // Refresh all sliders in the container 1374// $customDisplaySliderContainer.find('.site-display-list').each(function() { 1375// $(this).slick('refresh'); 1376// }); 1377// }); 1378 1379// } 1380 1381 1382 1383/* 1384===================================== 1385Custom Card Slider New Version 1386===================================== 1387*/ 1388function initializeCustomDisplaySlider() { 1389 1390 const $customDisplaySliderContainer = $('.site-display-slider-wrapper'); 1391 1392 // Check if container exists 1393 if ($customDisplaySliderContainer.length === 0) { 1394 return; 1395 } 1396 1397 const $customDisplaySlider = $customDisplaySliderContainer.find('.slick-slider'); 1398 1399 1400 // const $siteDisplayFilters = $customDisplaySliderContainer.find('.site-display-filters'); 1401 if ($customDisplaySliderContainer.find('.site-display-filters').length > 0) { 1402 $customDisplaySliderContainer.addClass('has-filters'); 1403 } else { 1404 $customDisplaySliderContainer.removeClass('has-filters'); 1405 } 1406 1407 1408 // Remove 'By' text from site-display-filter-title elements 1409 $customDisplaySliderContainer.find('.site-display-filter-title').each(function() { 1410 var $title = $(this); 1411 var text = $title.text(); 1412 if (text.includes('By')) { 1413 $title.text(text.replace(/By\s*/g, '').trim()); 1414 } 1415 // Remove space before colon 1416 var currentText = $title.text(); 1417 $title.text(currentText.replace(/\s+:/g, ':')); 1418 }); 1419 1420 $customDisplaySlider.each(function() { 1421 var $displayslider = $(this); 1422 1423 // Initialize the slider with proper configuration 1424 $displayslider.slick("slickSetOption",{ 1425 slidesToShow: 1, 1426 slidesToScroll: 1, 1427 variableWidth: true, 1428 }); 1429 1430 // Refresh slick slider to handle any dynamic content changes
1431 $displayslider.slick('refresh'); 1432 1433 }); 1434 1435 1436 1437} 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447/* 1448===================================== 1449Custom Infographic 1450===================================== 1451*/ 1452 1453// create gradient based on canvas size 1454function createGradient(ctx, size) { 1455 const gradient = ctx.createLinearGradient(0, 0, 0, size); 1456 const isCustomTheme = document.body.classList.contains('custom-primary-theme'); 1457 1458 if (isCustomTheme) { 1459 1460 gradient.addColorStop(0, 'rgba(255, 100, 100, 0.4)'); // Brightened red for plus-lighter effect 1461 gradient.addColorStop(1, 'rgba(100, 150, 255, 0.4)'); // Brightened blue for plus-lighter effect 1462 } else { 1463 gradient.addColorStop(0, 'rgba(213, 28, 41, 0.3)'); 1464 gradient.addColorStop(1, 'rgba(43, 60, 115, 0.3)'); 1465 } 1466 return gradient; 1467} 1468 1469function initializeInfographicChart() { 1470 if (typeof Chart === 'undefined') { 1471 return; 1472 } 1473 1474 var $container = $('.infographic-wrapper.cms-feature-wrapper .site-slide .site-slider-caption .site-slider-caption-block .site-container'); 1475 if (!$container.length) { 1476 return; 1477 } 1478 1479 $container.each(function(index) { 1480 var $thisContainer = $(this); 1481 var $h2 = $thisContainer.find('h2').first(); 1482 if (!$h2.length) { 1483 return; 1484 } 1485 1486 // Extract just the percentage number 1487 var match = $h2.text().match(/(\d+)%/); 1488 if (!match) { 1489 return; 1490 } 1491 var percent = parseInt(match[1], 10); 1492 percent = Math.max(0, Math.min(100, percent)); // clamp between 0â100 1493 1494 // Check if chart wrapper already exists and clean it up 1495 var $existingWrapper = $thisContainer.find('.circle-progress-wrapper-chartjs'); 1496 if ($existingWrapper.length) { 1497 // Destroy existing chart if it exists 1498 var existingChart = $existingWrapper.data('chart'); 1499 if (existingChart) { 1500 existingChart.destroy(); 1501 } 1502 $existingWrapper.remove(); 1503 } 1504 1505 // Responsive canvas wrapper 1506 var isCustomTheme = document.body.classList.contains('custom-primary-theme'); 1507 1508 var canvasMarkup = ` 1509 <div class="circle-progress-wrapper-chartjs" style="position:relative; width:100%; max-width:500px; aspect-ratio:1/1; margin:0 auto;"> 1510 <canvas id="doughnut-chart-${index}"></canvas> 1511 </div> 1512 `; 1513 1514 $thisContainer.append(canvasMarkup); 1515 1516 // Ensure the container is properly sized before proceeding 1517 setTimeout(function() { 1518 initializeChartForContainer($thisContainer, index, percent); 1519 }, 50); 1520 }); 1521 1522 //Redraw gradients on window resize 1523 window.addEventListener('resize', function() { 1524 $('.circle-progress-wrapper-chartjs').each(function() { 1525 var chart = $(this).data('chart'); 1526 if (chart) { 1527 var ctx = chart.ctx; 1528 var size = ctx.canvas.offsetHeight || ctx.canvas.offsetWidth; 1529 var newGradient = createGradient(ctx, size); 1530 1531 chart.data.datasets[0].backgroundColor[0] = newGradient; 1532 chart.update(); 1533 } 1534 }); 1535 }); 1536 1537 // If inside slider, set up accordingly 1538 var $infographicSlider = $('.infographic-slider-wrapper .site-slick-wrapper'); 1539 if ($infographicSlider.length) { 1540 setupInfographicSlider(); 1541 } 1542 1543 // Listen for theme changes and re-initialize charts 1544 $(document).on('click', '.theme-toggle-button', function() { 1545 setTimeout(function() { 1546 // Re-initialize charts after theme change 1547 $('.circle-progress-wrapper-chartjs').each(function() { 1548 var $wrapper = $(this); 1549 var chart = $wrapper.data('chart'); 1550 var percent = $wrapper.data('percent'); 1551 1552 if (chart && percent !== undefined) { 1553 // Check current theme state 1554 var isCustomTheme = document.body.classList.contains('custom-primary-theme'); 1555 1556 // Update gradient with new theme colors 1557 var ctx = chart.ctx; 1558 var size = ctx.canvas.offsetHeight || ctx.canvas.offsetWidth; 1559 var newGradient = createGradient(ctx, size); 1560 chart.data.datasets[0].backgroundColor[0] = newGradient; 1561 1562 // Update chart
1563 chart.update(); 1564 } 1565 }); 1566 }, 100); // Small delay to ensure theme class is applied 1567 }); 1568} 1569 1570 1571function initializeChartForContainer($thisContainer, index, percent) { 1572 var $wrapper = $thisContainer.find('.circle-progress-wrapper-chartjs'); 1573 if (!$wrapper.length) return; 1574 1575 try { 1576 var canvas = document.getElementById(`doughnut-chart-${index}`); 1577 var ctx = canvas.getContext('2d'); 1578 1579 // --- Simplified, predictable sizing --- 1580 var browserWidth = window.innerWidth; 1581 var targetSize = browserWidth <= 768 ? 300 : 500; // mobile vs desktop 1582 1583 // Apply consistent explicit sizing 1584 canvas.width = targetSize; 1585 canvas.height = targetSize; 1586 canvas.style.width = targetSize + 'px'; 1587 canvas.style.height = targetSize + 'px'; 1588 1589 // Completely disable pointer events to prevent any tooltips 1590 canvas.style.pointerEvents = 'none'; 1591 1592 var size = targetSize; 1593 var gradient = createGradient(ctx, size); 1594 1595 var chart = new Chart(ctx, { 1596 type: 'doughnut', 1597 data: { 1598 datasets: [{ 1599 data: [0, 100], // start at 0% 1600 backgroundColor: [gradient, 'transparent'], 1601 borderWidth: 0, 1602 cutout: '50%', 1603 }], 1604 }, 1605 options: { 1606 responsive: false, // now we control the size manually 1607 maintainAspectRatio: false, 1608 animation: { 1609 animateRotate: false, 1610 animateScale: false, 1611 }, 1612 plugins: { 1613 legend: { display: false }, 1614 tooltip: { 1615 enabled: false, 1616 external: null, 1617 filter: function() { return false; }, 1618 callbacks: {} 1619 }, 1620 }, 1621 elements: { 1622 arc: { borderWidth: 0 }, 1623 }, 1624 }, 1625 }); 1626 1627 // Store chart + percent 1628 $wrapper.data('chart', chart); 1629 $wrapper.data('percent', percent); 1630 1631 // Animate when scrolled into view 1632 $wrapper.waypoint( 1633 function (direction) { 1634 var chart = $wrapper.data('chart'); 1635 var percent = $wrapper.data('percent'); 1636 if (chart && percent !== undefined) { 1637 chart.data.datasets[0].data = [percent, 100 - percent]; 1638 chart.update(); 1639 } 1640 this.destroy(); 1641 }, 1642 { offset: '80%' } 1643 ); 1644 } catch (error) { 1645 console.error('Chart.js error:', error); 1646 } 1647} 1648 1649 1650function setupInfographicSlider() { 1651 var $infographicSlider = $('.infographic-slider-wrapper .site-slick-wrapper'); 1652 var $infographicSliderText = $('.infographic-text-wrapper.cms-feature-wrapper table.site-table-content tbody'); 1653 1654 if ($infographicSlider.length) { 1655 if (!$infographicSlider.hasClass('slick-initialized')) { 1656 $infographicSlider.slick({ 1657 slidesToShow: 1, 1658 slidesToScroll: 1, 1659 dots: true, 1660 arrows: false, 1661 fade: true, 1662 speed: 600, 1663 asNavFor: $infographicSliderText 1664 }); 1665 } 1666 1667 if (!$infographicSliderText.hasClass('slick-initialized')) { 1668 $infographicSliderText.slick({ 1669 slidesToShow: 1, 1670 slidesToScroll: 1, 1671 dots: false, 1672 arrows: false, 1673 fade: true, 1674 speed: 600, 1675 asNavFor: $infographicSlider 1676 }); 1677 } 1678 1679 // Listen for slide changes 1680 $infographicSlider.off('afterChange').on('afterChange', function(event, slick, currentSlide) { 1681 // Reset all charts to 0% immediately 1682 $('.circle-progress-wrapper-chartjs').each(function() { 1683 var $wrapper = $(this); 1684 var chart = $wrapper.data('chart'); 1685 if (chart) { 1686 chart.data.datasets[0].data = [0, 100]; 1687 chart.update('none'); 1688 } 1689 }); 1690 1691 // Animate the active slide's chart using currentSlide index 1692 setTimeout(function() { 1693 var $activeSlide = $(slick.$slides[currentSlide]); 1694 var $activeContainer = $activeSlide.find('.circle-progress-wrapper-chartjs'); 1695 1696 if ($activeContainer.length) { 1697 var chart = $activeContainer.data('chart'); 1698 var percent = $activeContainer.data('percent'); 1699 1700 if (chart && percent !== undefined) { 1701 chart.data.datasets[0].data = [percent, 100 - percent];
1702 chart.update(); 1703 } 1704 } 1705 }, 100); // Reduced timeout to 100ms 1706 }); 1707 1708 // Also listen for beforeChange to ensure charts are reset 1709 $infographicSlider.off('beforeChange').on('beforeChange', function(event, slick, currentSlide, nextSlide) { 1710 // Reset all charts before the slide changes 1711 $('.circle-progress-wrapper-chartjs').each(function() { 1712 var $wrapper = $(this); 1713 var chart = $wrapper.data('chart'); 1714 if (chart) { 1715 chart.data.datasets[0].data = [0, 100]; 1716 chart.update('none'); 1717 } 1718 }); 1719 }); 1720 } 1721} 1722 1723 1724/* 1725===================================== 1726Video Popup Function 1727===================================== 1728*/ 1729function initializeVideoPopup() { 1730 $("a.video-popup-button, a.custom-popup, .custom-popup .site-slide-link").click(function (event) { 1731 event.preventDefault(); 1732 1733 $(".video-popup-button").removeClass("video-popup-button-active"); 1734 $(".video-popup-section").remove(); 1735 1736 $(this).addClass("video-popup-button-active"); 1737 1738 $('<div>', { 1739 id: 'video-popup-section', 1740 class: 'video-popup-section video-popup-wrapper video-popup-wrapper-active', 1741 }).insertAfter("#page-wrapper"); 1742 1743 $(".video-popup-section").append("<div class='video-popup'></div>"); 1744 $(".video-popup").append("<div class='remove-video-popup'></div>"); 1745 1746 const videoUrl = $(this).attr('href'); 1747 const videoId = getVideoIdFromURL(videoUrl); 1748 1749 if (isVimeoVideo(videoUrl)) { 1750 const videoSrc = `https://player.vimeo.com/video/${videoId}?autoplay=1`; 1751 $('.video-popup-section .video-popup').append(`<iframe width="100%" height="500" src="${videoSrc}" frameborder="0" allow="autoplay" allowfullscreen></iframe>`); 1752 } else if (isYouTubeVideo(videoUrl)) { 1753 const videoSrc = `https://www.youtube.com/embed/${videoId}?autoplay=1`; 1754 $('.video-popup-section .video-popup').append(`<iframe width="100%" height="500" src="${videoSrc}" frameborder="0" allow="autoplay" allowfullscreen></iframe>`); 1755 } else if (!isYouTubeVideo(videoUrl) && !isVimeoVideo(videoUrl) && !videoUrl.endsWith(".mp4") && !videoUrl.endsWith(".webm")) { 1756 $('.video-popup-section .video-popup').append(`<iframe class="has-page-link" width="100%" height="500" src="${videoUrl}" frameborder="0" allow="autoplay" allowfullscreen></iframe>`); 1757 } else { 1758 $('.video-popup-section .video-popup').append(` 1759 <video width="100%" height="auto" controls autoplay> 1760 <source src="${videoUrl}" type="video/mp4"> 1761 Your browser does not support the video tag. 1762 </video> 1763 `); 1764 } 1765 }); 1766 1767 function getVideoIdFromURL(url) { 1768 const youtubeRegex = /(?:youtube\.com\/(?:[^/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?/ ]{11})/; 1769 const vimeoRegex = /vimeo\.com\/(?:channels\/|groups\/[^\/]*\/videos\/|video\/|)([0-9]+)/; 1770 1771 const youtubeMatch = url.match(youtubeRegex); 1772 const vimeoMatch = url.match(vimeoRegex); 1773 1774 if (youtubeMatch) { 1775 return youtubeMatch[1]; 1776 } else if (vimeoMatch) { 1777 return vimeoMatch[1]; 1778 } else { 1779 return null; 1780 } 1781 } 1782 1783 function isYouTubeVideo(url) { 1784 return /(?:youtube\.com\/(?:[^/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)/.test(url); 1785 } 1786 1787 function isVimeoVideo(url) { 1788 return /vimeo\.com/.test(url); 1789 } 1790 1791 $(function () { 1792 $(document).on('click', '.remove-video-popup', function () { 1793 var $this = $(this).closest(".video-popup-wrapper"); 1794 1795 $this.css('transition', 'opacity 500ms'); 1796 $this.css('opacity', 0); 1797 1798 // After the transition, remove the element 1799 setTimeout(function () { 1800 $this.remove(); 1801 }, 500); 1802 }); 1803 });
1804} 1805 1806/* 1807===================================== 1808Style Guide Example Age Grade Filtering 1809===================================== 1810*/ 1811function initializeSgDivisionFilter() { 1812 if ($notEditMode) { 1813 // Check if grade selection elements exist 1814 const $gradeSelectionLinks = $('.site-grade-selection-wrapper.cms-feature-wrapper .cms-feature-datablock ul li a'); 1815 1816 if ($gradeSelectionLinks.length === 0) { 1817 return; 1818 } 1819 1820 // Load All Grades content on page load 1821 // if(window.location.pathname === "/sg-landing-academic-division") { 1822 // sgloadContentFromUrl("/ec-elementary-division"); 1823 // } 1824 1825 1826 $gradeSelectionLinks.on('click', function(e) { 1827 e.preventDefault(); 1828 1829 // Remove active class from all anchors 1830 $('.site-grade-selection-wrapper.cms-feature-wrapper .cms-feature-datablock ul li a').removeClass('active'); 1831 // Add active class to the clicked anchor 1832 $(this).addClass('active'); 1833 1834 var pageUrl = $(this).attr('href'); 1835 sgloadContentFromUrl(pageUrl); 1836 loadContentFromUrl(pageUrl); 1837 1838 $('html, body').animate({scrollTop: $('.site-grade-selection-wrapper').offset().top - 200 }, 'slow'); 1839 }); 1840 } 1841} 1842 1843 1844//Example version 1845function sgloadContentFromUrl(pageUrl = "/ec-elementary-division"){ 1846 if(window.location.pathname !== "/sg-landing-academic-division"){ 1847 return; 1848 } 1849 1850 $.ajax({
1851 url: pageUrl, 1852 method: 'GET', 1853 success(response) { 1854 const $wrapper = $('.site-grade-selection-wrapper'); 1855 const newContent = $(response).find('#site-main-content-wrapper').html(); 1856 1857 // Remove any previously loaded content 1858 $wrapper.nextAll().remove(); 1859 1860 // Insert new content after the wrapper 1861 $wrapper.after(newContent); 1862 1863 window.dispatchEvent(new Event('resize')); 1864 initializeUnslick(); 1865 // initializeCustomSlider(); 1866 initializeCustomGallerySlider(); 1867 initializeVideoPopup(); 1868 }, 1869 complete() { 1870 // Call the function immediately 1871 initializeAjaxGridSystem(); 1872 } 1873 }); 1874} 1875 1876//Site version 1877function loadContentFromUrl(pageUrl = "/early-childhood-division"){ 1878 if(window.location.pathname !== "/academics"){ 1879 return; 1880 } 1881 1882 $.ajax({ 1883 url: pageUrl, 1884 method: 'GET', 1885 success(response) { 1886 const $wrapper = $('.site-grade-selection-wrapper'); 1887 const newContent = $(response).find('#site-main-content-wrapper').html(); 1888 1889 // Remove any previously loaded content 1890 $wrapper.nextAll().remove(); 1891 1892 // Insert new content after the wrapper 1893 $wrapper.after(newContent); 1894 1895 window.dispatchEvent(new Event('resize')); 1896 initializeUnslick(); 1897 // initializeCustomSlider(); 1898 initializeCustomGallerySlider(); 1899 initializeVideoPopup(); 1900 initializeCustomTabbedSlider(); 1901 }, 1902 complete() { 1903 // Call the function immediately 1904 initializeAjaxGridSystem(); 1905 } 1906 }); 1907} 1908 1909// Move this function outside the AJAX call so it's accessible 1910function initializeAjaxGridSystem() { 1911 $('.site-row-wrapper').each(function () { 1912 var $nextGroup = $(this).nextUntil('.site-row-wrapper').add(this); 1913 $nextGroup.add($nextGroup).wrapAll('<div class="site-row-container">'); 1914 }); 1915 1916 var classNames = []; 1917 $('div[class*="site-row-block-"]').each(function (i, el) { 1918 var allClasses = Array.from(el.classList); 1919 1920 var nonWrapperClasses = allClasses.filter(function (c) { 1921 return c.startsWith('site-row-block-') && !c.endsWith('-wrapper'); 1922 }); 1923 1924 nonWrapperClasses.forEach(function (className) { 1925 $(el).removeClass(className); 1926 }); 1927 1928 if (nonWrapperClasses.length > 0) { 1929 $(el).parents(".site-row-container").addClass(nonWrapperClasses.join(' ')); 1930 } 1931 }); 1932} 1933 1934 1935/* 1936===================================== 1937GSAP Fill Text 1938===================================== 1939*/ 1940 1941// function initializeFillText() { 1942// const headings = document.querySelectorAll( 1943// ".fill-text-wrapper .cms-text-feature-data .cms-feature-datablock h1, " + 1944// ".fill-text-wrapper .cms-text-feature-data .cms-feature-datablock h2" 1945// ); 1946 1947// // Check if fill-text-wrapper elements exist 1948// if (headings.length === 0) { 1949// return; 1950// } 1951 1952// if ($editMode) { 1953// return; 1954// } 1955
1956// headings.forEach((heading) => { 1957// let split, tl; 1958 1959// const createSplit = () => { 1960// // Clean up previous 1961// split && split.revert(); 1962// tl && tl.kill(); 1963 1964// // Split text into chars 1965// split = new SplitText(heading, { 1966// type: "words,chars", 1967// wordsClass: "split-word", 1968// charsClass: "split-char" 1969// }); 1970// heading._split = split; 1971 1972// // Base color is always red, fill color by theme 1973// const isCustomTheme = document.body.classList.contains("custom-primary-theme"); 1974// const fillColor = isCustomTheme ? "#ffffff" : "#2B3C73"; 1975// const baseColor = "#E90010"; 1976 1977// // Init chars with base color (red) 1978// split.chars.forEach((char) => { 1979// char.dataset.fillActive = "0"; 1980// char.dataset.baseColor = baseColor; 1981// char.dataset.fillColor = fillColor; 1982// gsap.set(char, { color: baseColor }); 1983// }); 1984 1985// // Build timeline 1986// tl = gsap.timeline({ 1987// scrollTrigger: { 1988// trigger: heading, 1989// start: "top 90%", 1990// end: "bottom 20%", 1991// scrub: 1.2, 1992// markers: false 1993// } 1994// }); 1995// heading._st = tl; 1996 1997// // Smooth transition per char with easing 1998// split.chars.forEach((char, i) => { 1999// tl.to( 2000// char, 2001// { 2002// duration: 0.3, 2003// ease: "power2.out", 2004// color: char.dataset.fillColor, 2005// onStart: () => { char.dataset.fillActive = "1"; }, 2006// onReverseComplete: () => { 2007// char.dataset.fillActive = "0"; 2008// gsap.set(char, { color: char.dataset.baseColor }); 2009// } 2010// }, 2011// (i * 0.05) + 0.001 2012// ); 2013// }); 2014 2015// tl.progress(0, false); // force to start state 2016// }; 2017 2018// createSplit(); 2019 2020// // Debounce resize 2021// const debouncer = gsap.delayedCall(0.2, createSplit).pause(); 2022// window.addEventListener("resize", () => debouncer.restart(true)); 2023// }); 2024 2025// // Update theme colors when toggled 2026// const updateThemeColor = () => { 2027// // Recreate all splits with new theme colors
2028// headings.forEach((heading) => { 2029// if (heading._split) { 2030// // Store current scroll progress 2031// const currentProgress = heading._st ? heading._st.progress() : 0; 2032 2033// // Recreate the split with new colors 2034// const createSplit = () => { 2035// const split = heading._split; 2036// const tl = heading._st; 2037 2038// // Clean up previous 2039// split && split.revert(); 2040// tl && tl.kill(); 2041 2042// // Split text into chars 2043// const newSplit = new SplitText(heading, { 2044// type: "words,chars", 2045// wordsClass: "split-word", 2046// charsClass: "split-char" 2047// }); 2048// heading._split = newSplit; 2049 2050// // Base color is always red, fill color by theme 2051// const isCustomTheme = document.body.classList.contains("custom-primary-theme"); 2052// const fillColor = isCustomTheme ? "#ffffff" : "#2B3C73"; 2053// const baseColor = "#E90010"; 2054 2055// // Init chars with base color (red) 2056// newSplit.chars.forEach((char) => { 2057// char.dataset.fillActive = "0"; 2058// char.dataset.baseColor = baseColor; 2059// char.dataset.fillColor = fillColor; 2060// gsap.set(char, { color: baseColor }); 2061// }); 2062 2063// // Build timeline 2064// const newTl = gsap.timeline({ 2065// scrollTrigger: { 2066// trigger: heading, 2067// start: "top 90%", 2068// end: "bottom 20%", 2069// scrub: 1.2, 2070// markers: false 2071// } 2072// }); 2073// heading._st = newTl; 2074 2075// // Instant switch per char (no interpolation) 2076// newSplit.chars.forEach((char, i) => { 2077// newTl.to( 2078// char, 2079// { 2080// duration: 0.3, 2081// ease: "power2.out", 2082// color: char.dataset.fillColor, 2083// onStart: () => { char.dataset.fillActive = "1"; }, 2084// onReverseComplete: () => { 2085// char.dataset.fillActive = "0"; 2086// gsap.set(char, { color: char.dataset.baseColor }); 2087// } 2088// }, 2089// (i * 0.05) + 0.001 2090// ); 2091// }); 2092 2093// // Restore scroll progress 2094// newTl.progress(currentProgress, false); 2095// }; 2096 2097// createSplit(); 2098// } 2099// }); 2100 2101// ScrollTrigger.refresh(); 2102// }; 2103 2104// // Theme toggle button listener 2105// document.addEventListener("click", (e) => { 2106// if (e.target.classList.contains("theme-toggle-button")) { 2107// setTimeout(updateThemeColor, 50); 2108// } 2109// }); 2110// } 2111 2112// // Wait until fonts are loaded before initializing 2113// document.fonts.ready.then(() => { 2114// initializeFillText(); 2115// ScrollTrigger.refresh(); 2116// }); 2117 2118function initializeFillText() { 2119 const headings = document.querySelectorAll( 2120 ".fill-text-wrapper .cms-text-feature-data .cms-feature-datablock h1, " + 2121 ".fill-text-wrapper .cms-text-feature-data .cms-feature-datablock h2" 2122 ); 2123 2124 if (headings.length === 0 || $editMode) return; 2125
2126 headings.forEach((heading) => { 2127 let split, tl; 2128 2129 const createSplit = () => { 2130 // Always wait for fonts before creating SplitText 2131 document.fonts.ready.then(() => { 2132 // Clean up previous 2133 split && split.revert(); 2134 tl && tl.kill(); 2135 2136 // Split text into chars 2137 split = new SplitText(heading, { 2138 type: "words,chars", 2139 wordsClass: "split-word", 2140 charsClass: "split-char" 2141 }); 2142 heading._split = split; 2143 2144 // Determine colors based on theme 2145 const isCustomTheme = document.body.classList.contains("custom-primary-theme"); 2146 const fillColor = isCustomTheme ? "#ffffff" : "#2B3C73"; 2147 const baseColor = "#E90010"; 2148 2149 // Initialize chars with base color (red) 2150 split.chars.forEach((char) => { 2151 char.dataset.fillActive = "0"; 2152 char.dataset.baseColor = baseColor; 2153 char.dataset.fillColor = fillColor; 2154 gsap.set(char, { color: baseColor }); 2155 }); 2156 2157 // Build ScrollTrigger timeline 2158 tl = gsap.timeline({ 2159 scrollTrigger: { 2160 trigger: heading, 2161 start: "top 90%", 2162 end: "bottom 20%", 2163 scrub: 1.2, 2164 markers: false 2165 } 2166 }); 2167 heading._st = tl; 2168 2169 // Animate each char's color transition smoothly 2170 split.chars.forEach((char, i) => { 2171 tl.to( 2172 char, 2173 { 2174 duration: 0.3, 2175 ease: "power2.out", 2176 color: char.dataset.fillColor, 2177 onStart: () => { char.dataset.fillActive = "1"; }, 2178 onReverseComplete: () => { 2179 char.dataset.fillActive = "0"; 2180 gsap.set(char, { color: char.dataset.baseColor }); 2181 } 2182 }, 2183 (i * 0.05) + 0.001 2184 ); 2185 }); 2186 2187 tl.progress(0, false); 2188 }); 2189 }; 2190 2191 // Initial Split 2192 createSplit(); 2193 2194 // Debounced resize re-split (safe with fonts) 2195 document.fonts.ready.then(() => { 2196 const debouncer = gsap.delayedCall(0.2, createSplit).pause(); 2197 window.addEventListener("resize", () => debouncer.restart(true)); 2198 }); 2199 }); 2200 2201 // Theme color update function 2202 const updateThemeColor = () => {
2203 headings.forEach((heading) => { 2204 if (!heading._split) return; 2205 2206 // Preserve scroll progress 2207 const currentProgress = heading._st ? heading._st.progress() : 0; 2208 2209 // Wait for fonts before recreating split 2210 document.fonts.ready.then(() => { 2211 const oldSplit = heading._split; 2212 const oldTl = heading._st; 2213 oldSplit && oldSplit.revert(); 2214 oldTl && oldTl.kill(); 2215 2216 const newSplit = new SplitText(heading, { 2217 type: "words,chars", 2218 wordsClass: "split-word", 2219 charsClass: "split-char" 2220 }); 2221 heading._split = newSplit; 2222 2223 const isCustomTheme = document.body.classList.contains("custom-primary-theme"); 2224 const fillColor = isCustomTheme ? "#ffffff" : "#2B3C73"; 2225 const baseColor = "#E90010"; 2226 2227 newSplit.chars.forEach((char) => { 2228 char.dataset.fillActive = "0"; 2229 char.dataset.baseColor = baseColor; 2230 char.dataset.fillColor = fillColor; 2231 gsap.set(char, { color: baseColor }); 2232 }); 2233 2234 const newTl = gsap.timeline({ 2235 scrollTrigger: { 2236 trigger: heading, 2237 start: "top 90%", 2238 end: "bottom 20%", 2239 scrub: 1.2, 2240 markers: false 2241 } 2242 }); 2243 heading._st = newTl; 2244 2245 newSplit.chars.forEach((char, i) => { 2246 newTl.to( 2247 char, 2248 { 2249 duration: 0.3, 2250 ease: "power2.out", 2251 color: char.dataset.fillColor, 2252 onStart: () => { char.dataset.fillActive = "1"; }, 2253 onReverseComplete: () => { 2254 char.dataset.fillActive = "0"; 2255 gsap.set(char, { color: char.dataset.baseColor }); 2256 } 2257 }, 2258 (i * 0.05) + 0.001 2259 ); 2260 }); 2261 2262 // Restore progress 2263 newTl.progress(currentProgress, false); 2264 }); 2265 }); 2266 2267 ScrollTrigger.refresh(); 2268 }; 2269 2270 // Theme toggle listener 2271 document.addEventListener("click", (e) => { 2272 if (e.target.classList.contains("theme-toggle-button")) { 2273 setTimeout(updateThemeColor, 50); 2274 } 2275 }); 2276 } 2277 2278 // Initialize only after fonts load initially 2279 document.fonts.ready.then(() => { 2280 initializeFillText(); 2281 ScrollTrigger.refresh(); 2282 }); 2283 2284/* 2285===================================== 2286Cookie Utilities 2287===================================== 2288*/ 2289 2290// Cookie utility functions for preloader control 2291function setCookie(name, value, days) { 2292 const expires = new Date(); 2293 expires.setTime(expires.getTime() + (days * 24 * 60 * 60 * 1000)); 2294 document.cookie = name + '=' + value + ';expires=' + expires.toUTCString() + ';path=/'; 2295} 2296 2297function getCookie(name) { 2298 const nameEQ = name + "="; 2299 const ca = document.cookie.split(';'); 2300 for (let i = 0; i < ca.length; i++) { 2301 let c = ca[i]; 2302 while (c.charAt(0) === ' ') c = c.substring(1, c.length); 2303 if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); 2304 } 2305 return null; 2306} 2307 2308function shouldShowPreloader() { 2309 const preloaderCookie = getCookie('preloader_shown'); 2310 if (!preloaderCookie) return true; 2311 2312 const lastShown = new Date(preloaderCookie); 2313 const now = new Date(); 2314 const daysSinceLastShown = (now - lastShown) / (1000 * 60 * 60 * 24); 2315 2316 // Show preloader if it's been more than 1 day since last shown 2317 return daysSinceLastShown >= 1; 2318} 2319 2320function markPreloaderAsShown() { 2321 const now = new Date().toISOString(); 2322 setCookie('preloader_shown', now, 1); // Cookie expires in 1 day 2323} 2324 2325/* 2326===================================== 2327Custom Preloader 2328===================================== 2329*/ 2330 2331function initializeCustomPreloader() { 2332 if ($editMode) return; 2333 2334 const $preloaderContainer = $('.site-preloader-wrapper'); 2335 if (!$preloaderContainer.length) return; 2336 2337 // Check if preloader should be shown based on cookie 2338 if (!shouldShowPreloader()) { 2339 // Hide preloader if it shouldn't be shown 2340 $preloaderContainer.hide(); 2341 return; 2342 } 2343 2344 // Show preloader by adding the CSS class 2345 $preloaderContainer.addClass('preloader-should-show'); 2346 $('body').addClass('preloader-active'); 2347 2348 const $enterButton = $('.site-preloader-wrapper .site-slider-more-desc .site-slider-more-desc-data .site-button-icon'); 2349 $enterButton.addClass('preloader-enter-btn'); 2350 $preloaderContainer.append($enterButton); 2351 const $preloaderLogo = $('.site-preloader-wrapper .preloder-logo'); 2352 $preloaderContainer.prepend($preloaderLogo); 2353 2354 $preloaderContainer.off('click.preloader').on('click.preloader', '.preloader-enter-btn', function (e) { 2355 e.preventDefault(); 2356 e.stopPropagation(); 2357 2358 let bg = '#f7f4f6'; 2359 try { 2360 const cs = getComputedStyle($preloaderContainer[0]); 2361 bg = cs.backgroundColor || bg; 2362 } catch (_) {} 2363 2364 const uid = 'pre-mask-' + Date.now(); 2365 const maskId = `preloader-hole-mask-${uid}`; 2366 const circleId = `preloader-hole-${uid}`; 2367 2368 const $svgOverlay = $(` 2369 <div class="preloader-reveal-svg" style="position:fixed;inset:0;z-index:10000;pointer-events:none;"> 2370 <svg viewBox="0 0 100 100" preserveAspectRatio="xMidYMid slice" width="100%" height="100%" aria-hidden="true"> 2371 <defs> 2372 <mask id="${maskId}" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse"> 2373 <rect x="0" y="0" width="100" height="100" fill="#fff"/> 2374 <circle id="${circleId}" cx="50" cy="50" r="0" fill="#000"/> 2375 </mask> 2376 </defs> 2377 <rect x="0" y="0" width="100" height="100" fill="${bg}" mask="url(#${maskId})"/> 2378 </svg> 2379 </div> 2380 `).appendTo('body'); 2381 2382 const hole = document.getElementById(circleId); 2383 2384 2385 const targetR = 80; 2386 2387 gsap.to(hole, { 2388 duration: 1.8, 2389 ease: 'power1.out', 2390 attr: { r: targetR }, 2391 onStart() { 2392 // Hide preloader content so the hole reveals the page 2393 gsap.set($preloaderContainer, { opacity: 0 }); 2394 }, 2395 onComplete() { 2396 // Mark preloader as shown so it won't appear again for 1 day 2397 markPreloaderAsShown(); 2398 2399 $('body').removeClass('preloader-active'); 2400 $preloaderContainer.remove(); 2401 $svgOverlay.remove(); 2402 } 2403 }); 2404 });
2405} 2406 2407 2408 2409 2410/* 2411===================================== 2412Site Bookmark 2413===================================== 2414*/ 2415 2416function initializeSiteBookmark() { 2417 if ($editMode) return; 2418 2419 const $siteBookmarkWrapper = $('.site-bookmark-wrapper'); 2420 if (!$siteBookmarkWrapper.length) return; 2421 2422 const $bookmarkButton = $('.site-bookmark-button'); 2423 const $siteDisplayDataBlock = $('.site-display-bookmark-wrapper .site-display-data-block'); 2424 2425 2426 const $customBookmarkButton = $('.custom-bookmark-button-wrapper'); 2427 2428 // Update custom bookmark button text based on current page URL 2429 if ($customBookmarkButton.length) { 2430 const $customBookmarkButtonElement = $customBookmarkButton.find('.site-bookmark-button'); 2431 if ($customBookmarkButtonElement.length) { 2432 // Get current page URL path 2433 const currentPath = window.location.pathname; 2434 // Remove leading slash, replace hyphens with spaces, and capitalize first letter, or use 'Home' for root 2435 const pageName = currentPath === '/' ? 'Home' : currentPath.substring(1).replace(/-/g, ' ').charAt(0).toUpperCase() + currentPath.substring(1).replace(/-/g, ' ').slice(1); 2436 2437 // Update button text with subtext based on bookmark state 2438 if ($customBookmarkButtonElement.hasClass("site-bookmark-single-button-add")) { 2439 $customBookmarkButtonElement.html(pageName + '<small> Add to my collection</small>'); 2440 } else if ($customBookmarkButtonElement.hasClass("site-bookmark-single-button-remove")) { 2441 $customBookmarkButtonElement.html(pageName + '<small> Remove from my collection</small>'); 2442 } else { 2443 // Default to add if no specific class is found 2444 $customBookmarkButtonElement.html(pageName + '<small> Add to my collection</small>'); 2445 } 2446 $customBookmarkButtonElement.wrapInner('<div class="site-bookmark-button-inner"></div>'); 2447 } 2448 } 2449 2450 // Function to update custom bookmark button text 2451 function updateCustomBookmarkButtonText() { 2452 // console.log('updateCustomBookmarkButtonText called'); 2453 const $customBookmarkButton = $('.custom-bookmark-button-wrapper'); 2454 // console.log('Custom bookmark button found:', $customBookmarkButton.length); 2455 if ($customBookmarkButton.length) { 2456 const $customBookmarkButtonElement = $customBookmarkButton.find('.site-bookmark-button').first(); 2457 // console.log('Custom bookmark button element found:', $customBookmarkButtonElement.length); 2458 if ($customBookmarkButtonElement.length) { 2459 // console.log('Current classes:', $customBookmarkButtonElement.attr('class')); 2460 // Get current page URL path 2461 const currentPath = window.location.pathname; 2462 const pageName = currentPath === '/' ? 'Home' : currentPath.substring(1).replace(/-/g, ' ').charAt(0).toUpperCase() + currentPath.substring(1).replace(/-/g, ' ').slice(1); 2463 // console.log('Page name:', pageName); 2464 2465 // Check if inner div exists, if not create it 2466 let $innerDiv = $customBookmarkButtonElement.find('.site-bookmark-button-inner'); 2467 // console.log('Inner div found:', $innerDiv.length); 2468 if (!$innerDiv.length) { 2469 // console.log('Creating inner div'); 2470 $customBookmarkButtonElement.wrapInner('<div class="site-bookmark-button-inner"></div>'); 2471 $innerDiv = $customBookmarkButtonElement.find('.site-bookmark-button-inner'); 2472 } 2473 2474 // Update text content inside the inner div based on current state 2475 if ($customBookmarkButtonElement.hasClass("site-bookmark-single-button-add")) { 2476 // console.log('Setting text to ADD'); 2477 $innerDiv.html(pageName + '<small> Add to my collection</small>'); 2478 } else if ($customBookmarkButtonElement.hasClass("site-bookmark-single-button-remove")) { 2479 // console.log('Setting text to REMOVE'); 2480 $innerDiv.html(pageName + '<small> Remove from my collection</small>'); 2481 } else { 2482 // console.log('Setting text to DEFAULT ADD'); 2483 // Default to add if no specific class is found 2484 $innerDiv.html(pageName + '<small> Add to my collection</small>'); 2485 } 2486 // console.log('Text update completed'); 2487 } 2488 } 2489 } 2490 2491 if ($customBookmarkButton.length && $('#site-hero-wrapper .site-hero-wrapper').length) { 2492 $customBookmarkButton.appendTo('#site-hero-wrapper .site-hero-wrapper .site-slider'); 2493 } 2494 2495 // Force update bookmark button text on page load 2496 setTimeout(function() { 2497 updateCustomBookmarkButtonText(); 2498 }, 100); 2499 2500 if ($bookmarkButton.length && $siteDisplayDataBlock.length) { 2501 // Try to move the button to the first display data block 2502 //$bookmarkButton.prependTo($siteDisplayDataBlock.first()); 2503 2504 // Update the text content of the moved button 2505 $bookmarkButton.each(function () { 2506 if ($(this).hasClass("site-bookmark-display-button-add")) { 2507 $(this).html("Explorer<small> Add to my collection</small> "); 2508 } else if ($(this).hasClass("site-bookmark-display-button-remove")) { 2509 $(this).html("Remove<small>from your collection </small>"); 2510 } 2511 $(this).wrapInner('<div class="site-bookmark-button-inner"></div>'); 2512 }); 2513 } 2514 2515 2516 // Find and move site-bookmark-icon after #site-header 2517 const $bookmarkIcon = $('.site-bookmark-icon'); 2518 if ($bookmarkIcon.length) { 2519 $bookmarkIcon.insertAfter("#site-header"); 2520 2521 // Initially hide the bookmark icon 2522 $bookmarkIcon.hide(); 2523
2524 // Function to check scroll position and show/hide bookmark icon (mobile only) 2525 function handleBookmarkIconVisibility() { 2526 // Only run scroll-based logic on mobile devices (screen width <= 768px) 2527 const isMobile = $(window).width() <= 768; 2528 if (!isMobile) { 2529 // On desktop, always show the icon 2530 $bookmarkIcon.fadeIn(300); 2531 return; 2532 } 2533 2534 // Mobile: show/hide based on scroll position 2535 const windowHeight = $(window).height(); 2536 const scrollTop = $(window).scrollTop(); 2537 2538 // Show bookmark icon when user has scrolled a quarter of the screen height 2539 if (scrollTop >= windowHeight / 4) { 2540 $bookmarkIcon.fadeIn(300); 2541 } else { 2542 $bookmarkIcon.fadeOut(300); 2543 } 2544 } 2545 2546 // Initial check 2547 handleBookmarkIconVisibility(); 2548 2549 // Listen for scroll events 2550 $(window).on('scroll', handleBookmarkIconVisibility); 2551 2552 // Listen for resize events to recalculate on window resize 2553 $(window).on('resize', handleBookmarkIconVisibility); 2554 } 2555 2556 const $bookmarkPanel = $("<div class='site-bookmark-panel-wrapper'><div class='bookmark-panel-inner'></div></div>"); 2557 $bookmarkPanel.insertAfter("#site-header"); 2558 2559 // Add close button to the panel 2560 const $closeButton = $('<button class="site-bookmark-panel-close"></button>'); 2561 $bookmarkPanel.prepend($closeButton); 2562 2563 // Add click event to bookmark icon to toggle panel 2564 $bookmarkIcon.on('click', function() { 2565 $bookmarkPanel.addClass('active'); 2566 $bookmarkIcon.addClass('panel-active'); 2567 }); 2568 2569 // Add click event to close button 2570 $closeButton.on('click', function() { 2571 $bookmarkPanel.removeClass('active'); 2572 $bookmarkIcon.removeClass('panel-active'); 2573 }); 2574 2575 // Append the site bookmark wrapper to the bookmark panel inner 2576 $siteBookmarkWrapper.appendTo($bookmarkPanel.find('.bookmark-panel-inner')); 2577 2578 // Append site-bookmark-text-feature-wrapper to site-bookmark-block 2579 const $textFeatureWrapper = $('.site-bookmark-text-feature-wrapper'); 2580 if ($textFeatureWrapper.length) { 2581 $textFeatureWrapper.appendTo($siteBookmarkWrapper.find('.site-bookmark-block')); 2582 } 2583 2584 // Add placeholder items to fill the 3x3 grid (9 total slots) 2585 const $bookmarkList = $siteBookmarkWrapper.find('.site-bookmark-list'); 2586 const existingItems = $bookmarkList.find('.site-bookmark-wrapper-listitem').length; 2587 const totalSlots = 9; // 3x3 grid 2588 const placeholdersNeeded = totalSlots - existingItems; 2589 2590 // Add placeholder items for empty slots 2591 for (let i = 0; i < placeholdersNeeded; i++) { 2592 $bookmarkList.append('<div class="site-bookmark-placeholder-item"></div>'); 2593 } 2594 2595 2596 // Function to remove a placeholder when a bookmark is added 2597 window.removeBookmarkPlaceholder = function() { 2598 const $placeholder = $bookmarkList.find('.site-bookmark-placeholder-item').first(); 2599 if ($placeholder.length) { 2600 $placeholder.remove(); 2601 } 2602 }; 2603 2604 // Function to add a placeholder when a bookmark is removed 2605 window.addBookmarkPlaceholder = function() { 2606 // Add placeholder at the end to maintain grid order 2607 $bookmarkList.append('<div class="site-bookmark-placeholder-item"></div>'); 2608 }; 2609 2610 // Override the calculateBookmarkCounter function to ensure correct counting 2611 const originalCalculateBookmarkCounter = window.calculateBookmarkCounter; 2612 2613 window.calculateBookmarkCounter = function() { 2614 const bookmarkCount = $(".site-bookmark-wrapper .site-bookmark-wrapper-listitem").length; 2615 2616 // Call the original function first 2617 if (originalCalculateBookmarkCounter) { 2618 originalCalculateBookmarkCounter(); 2619 } 2620 2621 // Now override the counter display with the correct count 2622 const $counter = $(".site-bookmark-icon .site-bookmark-count"); 2623 if ($counter.length) { 2624 if (bookmarkCount > 0) { 2625 $counter.text(bookmarkCount).show(); 2626 } else { 2627 $counter.hide(); 2628 } 2629 } 2630 }; 2631 2632 // Listen for bookmark addition/removal events - only manage placeholders 2633 $(document).on("amais:bookmark:added", function() { 2634 // console.log('amais:bookmark:added event fired'); 2635 // Remove a placeholder when a bookmark is added and move the new bookmark to the top 2636 setTimeout(function() { 2637 // Remove the first placeholder (from the top) 2638 window.removeBookmarkPlaceholder(); 2639 2640 // Move the last added bookmark to the beginning of the list (top position) 2641 const $lastBookmark = $bookmarkList.find('.site-bookmark-wrapper-listitem').last(); 2642 if ($lastBookmark.length) { 2643 $lastBookmark.prependTo($bookmarkList); 2644 } 2645 2646 // Update custom bookmark button text when bookmark is added 2647 updateCustomBookmarkButtonText(); 2648 }, 100); 2649 });
2650 2651 $(document).on("amais:bookmark:removed", function() { 2652 // console.log('amais:bookmark:removed event fired'); 2653 // Add a placeholder when a bookmark is removed 2654 setTimeout(function() { 2655 window.addBookmarkPlaceholder(); 2656 2657 // Update custom bookmark button text when bookmark is removed 2658 updateCustomBookmarkButtonText(); 2659 }, 100); 2660 }); 2661 2662 // Check for site-bookmark-button in site-display-bookmark-wrapper and add class 2663 $('.site-display-bookmark-wrapper').each(function () { 2664 const $wrapper = $(this); 2665 const $bookmarkButtonInWrapper = $wrapper.find('.site-bookmark-button'); 2666 2667 if ($bookmarkButtonInWrapper.length > 0) { 2668 $wrapper.addClass('has-bookmark-button'); 2669 } 2670 }); 2671 2672 // Check for has-prompt-wrapper class and prepend site-card-prompt-wrapper 2673 $('.site-display-bookmark-wrapper').each(function () { 2674 const $wrapper = $(this); 2675 const $wrapperInner = $wrapper.find('.site-display-wrapper'); 2676 2677 if ($wrapper.hasClass('has-prompt-wrapper')) { 2678 const $promptWrapper = $('.site-card-prompt-wrapper'); 2679 if ($promptWrapper.length) { 2680 $promptWrapper.prependTo($wrapperInner); 2681 } 2682 } 2683 }); 2684 2685 2686 2687 // Create popup overlay for bookmark details 2688 const $popupOverlay = $('<div class="site-bookmark-popup-overlay"><div class="site-bookmark-popup-content"><div class="site-bookmark-popup-content-inner"><button class="site-bookmark-popup-go-back">Go Back</button><div class="site-bookmark-popup-body"></div></div></div></div>'); 2689 const $popupOverlaycloseButton = $('<button class="site-bookmark-popup-close">×</button>'); 2690 $popupOverlaycloseButton.prependTo($popupOverlay); 2691 $popupOverlay.insertAfter($bookmarkPanel); 2692 2693 // Use event delegation to handle clicks on bookmark items (including dynamically added ones) 2694 $bookmarkList.on('click', '.site-bookmark-wrapper-listitem', function(e) { 2695 e.preventDefault(); 2696 2697 const $listItem = $(this); 2698 2699 // Find the image and data block within this item 2700 const $bookmarkImage = $listItem.find('.site-bookmark-image'); 2701 const $bookmarkDataBlock = $listItem.find('.site-bookmark-data-block'); 2702 2703 // Clone the content for the popup 2704 const $clonedImage = $bookmarkImage.clone(); 2705 const $clonedDataBlock = $bookmarkDataBlock.clone(); 2706 2707 // Clear previous content and add new content to popup 2708 const $popupBody = $popupOverlay.find('.site-bookmark-popup-body'); 2709 $popupBody.empty(); 2710 $popupBody.append($clonedImage); 2711 $popupBody.append($clonedDataBlock); 2712 2713 // Show the popup 2714 $popupOverlay.addClass('active'); 2715 }); 2716 2717 // Add click handler to close popup 2718 $popupOverlay.find('.site-bookmark-popup-close').on('click', function() { 2719 $popupOverlay.removeClass('active'); 2720 $bookmarkPanel.removeClass('active'); 2721 $bookmarkIcon.removeClass('panel-active'); 2722 }); 2723 2724 // Add click handler to close popup 2725 $popupOverlay.find('.site-bookmark-popup-go-back').on('click', function() { 2726 $popupOverlay.removeClass('active'); 2727 }); 2728 2729 2730 2731 2732 // Add click handler to close popup when clicking overlay background 2733 $popupOverlay.on('click', function(e) { 2734 if (e.target === this) { 2735 $popupOverlay.removeClass('active'); 2736 } 2737 }); 2738} 2739 2740 2741 2742 2743/* 2744===================================== 2745Language Toggle 2746===================================== 2747*/ 2748function initializeLanguageToggle() { 2749 if ($notEditMode) { 2750 const $languageContainer = $('.site-language-translation'); 2751 const $englishTrigger = $('.english-trigger'); 2752 const $frenchTrigger = $('.french-trigger'); 2753 const $germanTrigger = $('.german-trigger'); 2754 const $spanishTrigger = $('.spanish-trigger'); 2755 const $chineseTrigger = $('.mandarin-trigger'); 2756 const $englishContent = $('.site-row-block-english-translation'); 2757 const $frenchContent = $('.site-row-block-french-translation'); 2758 const $germanContent = $('.site-row-block-german-translation'); 2759 const $spanishContent = $('.site-row-block-spanish-translation'); 2760 const $chineseContent = $('.site-row-block-mandarin-translation'); 2761 2762 // Check if language toggle elements exist 2763 if ($languageContainer.length === 0 || $englishTrigger.length === 0) { 2764 return; 2765 } 2766 2767 // Set initial state - English visible, others hidden 2768 $englishContent.addClass('language-active').removeClass('language-hidden'); 2769 $frenchContent.addClass('language-hidden').removeClass('language-active'); 2770 $germanContent.addClass('language-hidden').removeClass('language-active'); 2771 $spanishContent.addClass('language-hidden').removeClass('language-active'); 2772 $chineseContent.addClass('language-hidden').removeClass('language-active'); 2773 $englishTrigger.addClass('active'); 2774 $frenchTrigger.removeClass('active'); 2775 $germanTrigger.removeClass('active'); 2776 $spanishTrigger.removeClass('active'); 2777 $chineseTrigger.removeClass('active'); 2778 2779 // Helper function to switch language 2780 function switchLanguage(activeTrigger, activeContent) { 2781 // Remove active class from all triggers 2782 $englishTrigger.removeClass('active'); 2783 $frenchTrigger.removeClass('active'); 2784 $germanTrigger.removeClass('active'); 2785 $spanishTrigger.removeClass('active'); 2786 $chineseTrigger.removeClass('active'); 2787 2788 // Hide all content 2789 $englishContent.removeClass('language-active').addClass('language-hidden'); 2790 $frenchContent.removeClass('language-active').addClass('language-hidden'); 2791 $germanContent.removeClass('language-active').addClass('language-hidden'); 2792 $spanishContent.removeClass('language-active').addClass('language-hidden'); 2793 $chineseContent.removeClass('language-active').addClass('language-hidden'); 2794 2795 // Show active content and trigger 2796 activeTrigger.addClass('active'); 2797 activeContent.removeClass('language-hidden').addClass('language-active'); 2798 2799 // Refresh slick sliders 2800 if (typeof $.fn.slick !== 'undefined') { 2801 $('.slick-initialized').slick('refresh'); 2802 } 2803 2804 // Fix dots container positioning after refresh 2805 setTimeout(function() { 2806 $('.custom-gallery-slider-wrapper').each(function() { 2807 var $sliderPanel = $(this); 2808 var $dotsContainer = $sliderPanel.find('.slick-dots'); 2809 var $arrowContainer = $sliderPanel.find('.slider-arrow-container'); 2810 2811 if ($dotsContainer.length && $arrowContainer.length && !$dotsContainer.parent().is($arrowContainer)) { 2812 $dotsContainer.prependTo($arrowContainer); 2813 $dotsContainer.addClass('custom-dots-container'); 2814 } 2815 }); 2816 }, 150); 2817 } 2818 2819 // English trigger click handler 2820 $englishTrigger.on('click', function(e) { 2821 e.preventDefault(); 2822 switchLanguage($(this), $englishContent); 2823 }); 2824 2825 // French trigger click handler 2826 $frenchTrigger.on('click', function(e) { 2827 e.preventDefault(); 2828 switchLanguage($(this), $frenchContent); 2829 }); 2830 2831 // German trigger click handler 2832 $germanTrigger.on('click', function(e) { 2833 e.preventDefault(); 2834 switchLanguage($(this), $germanContent); 2835 }); 2836 2837 // Spanish trigger click handler 2838 $spanishTrigger.on('click', function(e) { 2839 e.preventDefault(); 2840 switchLanguage($(this), $spanishContent); 2841 }); 2842 2843 // Chinese trigger click handler 2844 $chineseTrigger.on('click', function(e) { 2845 e.preventDefault(); 2846 switchLanguage($(this), $chineseContent); 2847 }); 2848 } 2849} 2850 2851/* 2852===================================== 2853Alternate Home Version Loader 2854===================================== 2855*/ 2856// function initializeAlternateHomeVersionLoader() { 2857// // Check if we're on the home page - do this inside the function to ensure DOM is ready 2858// if (!$('#home-page').length) { 2859// return; 2860// } 2861 2862// // Get the last version shown from localStorage 2863// let lastVersion = localStorage.getItem('pageVersion'); 2864 2865// // If no version stored, start with version 1 (0) 2866// if (lastVersion === null) { 2867// lastVersion = 0; 2868// } else { 2869// lastVersion = parseInt(lastVersion); 2870// } 2871 2872// // Alternate to the other version 2873// const currentVersion = lastVersion === 0 ? 1 : 0; 2874 2875// // Store the current version for next time 2876// localStorage.setItem('pageVersion', currentVersion.toString()); 2877 2878// // Load the appropriate version 2879// if (currentVersion === 1) { 2880// loadAlternateVersion(); 2881// } 2882 2883// function loadAlternateVersion() { 2884// // Replace this with your actual alternate page URL 2885// const alternatePageUrl = '/home-alternative-version'; 2886 2887// fetch(alternatePageUrl) 2888// .then(response => { 2889// if (!response.ok) { 2890// throw new Error(`HTTP error! status: ${response.status}`); 2891// } 2892// return response.text();
2893// }) 2894// .then(html => { 2895// // Create a temporary div to parse the HTML 2896// const tempDiv = document.createElement('div'); 2897// tempDiv.innerHTML = html; 2898 2899// // Extract only the content from the site-main-grid-block in the response 2900// const alternateContent = tempDiv.querySelector('#site-main-grid-block'); 2901 2902// if (alternateContent) { 2903// // Get the inner content of the alternate page's site-main-grid-block 2904// const alternateInnerHTML = alternateContent.innerHTML; 2905 2906// // Check if the target element exists 2907// const targetElement = document.getElementById('site-main-grid-block'); 2908// if (targetElement) { 2909// targetElement.innerHTML = alternateInnerHTML; 2910 2911// // Reinitialize functions after content replacement 2912// window.dispatchEvent(new Event('resize')); 2913// initializeUnslick(); 2914// initializeAjaxGridSystem();
2915// initializeInfographicChart(); 2916// initializeStackedDisplaySlider(); 2917// initializeSiteBookmark(); 2918// initializeCustomPreloader(); 2919 2920// // Reinitialize fill text with ScrollTrigger refresh 2921// initializeFillText(); 2922// ScrollTrigger.refresh(); 2923// } 2924// } 2925// }) 2926// .catch(error => { 2927// console.error('Error loading alternate version:', error); 2928// }); 2929// } 2930// } 2931 2932/* 2933===================================== 2934Directory 2935===================================== 2936*/ 2937function initializeBBDirectory() { 2938 $(".site-bb-directory-grid").each(function () { 2939 2940 // Check for cms-bb-parent-image-block and prepend if missing 2941 $(this) 2942 .find(".cms-bb-result-item-data") 2943 .each(function () { 2944 if ($(this).find(".cms-bb-parent-image-block, .placeholder-image-block").length === 0) { 2945 $(this).prepend("<div class='placeholder-image-block'></div>"); 2946 } 2947 }); 2948 }); 2949} 2950 2951/* 2952===================================== 2953Content Intro Check 2954===================================== 2955*/ 2956function initializeContentIntroCheck() { 2957 if ($notEditMode) { 2958 // Check if .site-row-block-intro-wrapper exists on the page 2959 if ($('.site-row-block-intro-wrapper').length > 0) { 2960 $('body').addClass('has-content-intro'); 2961 2962 // Function to check and apply height adjustment 2963 function checkAndAdjustHeights() { 2964 const $customSidebarWrapper = $('.custom-sidebar-wrapper .cms-text-feature-data'); 2965 const $introWrapper = $('.site-row-block-intro-wrapper .cms-text-feature-data'); 2966 2967 if ($customSidebarWrapper.length > 0 && $introWrapper.length > 0) { 2968 const sidebarHeight = $customSidebarWrapper.outerHeight(); 2969 2970 // Get the natural height of intro wrapper by temporarily removing min-height 2971 const currentMinHeight = $introWrapper.css('min-height'); 2972 $introWrapper.css('min-height', ''); 2973 const introHeight = $introWrapper.outerHeight(); 2974 // Restore min-height if it existed 2975 if (currentMinHeight && currentMinHeight !== '0px' && currentMinHeight !== 'none') { 2976 $introWrapper.css('min-height', currentMinHeight); 2977 } 2978 2979 // console.log('Sidebar height:', sidebarHeight); 2980 // console.log('Intro height:', introHeight); 2981 2982 // Only proceed if both elements have actual height (not 0) 2983 if (sidebarHeight > 0 && introHeight > 0) { 2984 if (sidebarHeight > introHeight) { 2985 const adjustedHeight = sidebarHeight + 60; 2986 // console.log('Applying min-height:', adjustedHeight + 'px'); 2987 $introWrapper.css('min-height', adjustedHeight + 'px'); 2988 } else { 2989 // Clear min-height if intro is naturally taller than sidebar 2990 // console.log('Clearing min-height - intro is taller'); 2991 $introWrapper.css('min-height', ''); 2992 } 2993 } else { 2994 // console.log('Elements have no height yet, will retry...'); 2995 // Retry after a short delay if elements don't have height yet 2996 setTimeout(checkAndAdjustHeights, 200); 2997 } 2998 } else { 2999 //console.log('Elements not found - Sidebar:', $customSidebarWrapper.length, 'Intro:', $introWrapper.length); 3000 } 3001 } 3002 3003 // Run immediately 3004 checkAndAdjustHeights();
3005 3006 // Run after a short delay to ensure elements are fully rendered 3007 setTimeout(checkAndAdjustHeights, 100); 3008 setTimeout(checkAndAdjustHeights, 500); 3009 setTimeout(checkAndAdjustHeights, 1000); 3010 3011 // Run after images are loaded 3012 $(window).on('load', checkAndAdjustHeights); 3013 3014 // Run on window resize with debounce 3015 let resizeTimer; 3016 $(window).on('resize', function() { 3017 clearTimeout(resizeTimer); 3018 resizeTimer = setTimeout(checkAndAdjustHeights, 250); 3019 }); 3020 } 3021 } 3022} 3023 3024 3025 3026 3027 3028/* 3029===================================== 3030Remove aria-hidden from cloned slides with video elements 3031===================================== 3032*/ 3033function removeAriaHiddenFromVideoSlides() { 3034 if ($notEditMode) { 3035 // Find all cloned slides that have video elements 3036 $('.custom-tabbed-autoscroll-gallery .site-slide.slick-cloned').each(function() { 3037 const $slide = $(this); 3038 const $video = $slide.find('.site-slider-video-block video'); 3039 3040 // If this cloned slide has a video, remove aria-hidden 3041 if ($video.length > 0) { 3042 $slide.removeAttr('aria-hidden'); 3043 // console.log('Removed aria-hidden from cloned slide with video'); 3044 } 3045 }); 3046 3047 // Also set up a MutationObserver to watch for new cloned slides 3048 const observer = new MutationObserver(function(mutations) { 3049 mutations.forEach(function(mutation) { 3050 if (mutation.type === 'childList') { 3051 mutation.addedNodes.forEach(function(node) { 3052 if (node.nodeType === 1) { // Element node 3053 const $node = $(node); 3054 if ($node.hasClass('slick-cloned')) { 3055 const $video = $node.find('.site-slider-video-block video'); 3056 if ($video.length > 0) { 3057 $node.removeAttr('aria-hidden'); 3058 // console.log('Removed aria-hidden from newly created cloned slide with video'); 3059 } 3060 } 3061 } 3062 }); 3063 } 3064 }); 3065 }); 3066 3067 // Observe the gallery container for changes 3068 const galleryContainer = document.querySelector('.custom-tabbed-autoscroll-gallery'); 3069 if (galleryContainer) { 3070 observer.observe(galleryContainer, { 3071 childList: true, 3072 subtree: true 3073 }); 3074 } 3075 } 3076} 3077 3078/* 3079===================================== 3080Video Controls with Play/Pause Button 3081===================================== 3082*/ 3083function initializeVideoControls() { 3084 if ($notEditMode) { 3085 // Find all video elements that need custom controls (including cloned slides) 3086 $('.custom-tabbed-autoscroll-gallery .site-slide, .site-gallery-slider-wrapper .site-slide').each(function() { 3087 // console.log('Processing video slide:', $(this).hasClass('slick-cloned') ? 'CLONED' : 'ORIGINAL'); 3088 3089 const $frameVideo = $(this).find('.site-slider-video-block video'); 3090 3091 // Check if video element exists 3092 if ($frameVideo.length === 0) { 3093 return; // Skip if no video found 3094 } 3095 3096 3097 const $frameVideoContainer = $frameVideo.closest('.site-slider-video-block'); 3098 3099 // Only add controls if they don't already exist 3100 if (!$frameVideoContainer.find('.custom-video-controls').length) { 3101 // Create custom controls container
3102 const $frameControlsContainer = $('<div class="custom-video-controls"></div>'); 3103 3104 // Create progress bar container 3105 const $frameProgressContainer = $('<div class="video-progress-container"></div>'); 3106 const $frameProgressBar = $('<div class="video-progress-bar"></div>'); 3107 const $frameProgressFill = $('<div class="video-progress-fill"></div>'); 3108 3109 // Create play/pause button 3110 const $framePlayPauseBtn = $('<button class="video-play-pause-btn" aria-label="Play video"></button>'); 3111 3112 // Assemble progress bar 3113 $frameProgressBar.append($frameProgressFill); 3114 $frameProgressContainer.append($frameProgressBar); 3115 3116 // Add elements to controls container 3117 $frameControlsContainer.append($frameProgressContainer); 3118 $frameControlsContainer.append($framePlayPauseBtn); 3119 3120 // Add controls to video container 3121 $frameVideoContainer.append($frameControlsContainer); 3122 3123 // Set initial state 3124 $frameVideo.prop('muted', true); // Start muted 3125 $frameVideo.prop('controls', false); // Hide default controls 3126 $frameVideo.prop('autoplay', false); // Disable autoplay 3127 $frameVideo.prop('loop', false); // Disable loop 3128 3129 // Ensure video is paused on load (with safety check) 3130 if ($frameVideo[0]) { 3131 $frameVideo[0].pause(); 3132 } 3133 3134 // Update progress bar function 3135 function updateFrameProgress() { 3136 if ($frameVideo[0] && !isNaN($frameVideo[0].duration)) { 3137 const progress = ($frameVideo[0].currentTime / $frameVideo[0].duration) * 100; 3138 $frameProgressFill.css('width', progress + '%'); 3139 } 3140 } 3141 3142 // Handle play/pause button click 3143 $framePlayPauseBtn.on('click', function(e) { 3144 e.preventDefault(); 3145 e.stopPropagation(); 3146 3147 // console.log('Video play button clicked'); // Debug log 3148 3149 // Safety check to ensure video element exists 3150 if (!$frameVideo[0]) { 3151 console.error('Video element not found'); 3152 return; 3153 } 3154 3155 // Check if video has a source 3156 if (!$frameVideo[0].src && !$frameVideo[0].currentSrc) { 3157 console.error('Video has no source'); 3158 return; 3159 } 3160 3161 if ($frameVideo[0].paused) { 3162 // Stop all audio clips before playing video 3163 $('audio').each(function() { 3164 if (!this.paused) { 3165 this.pause(); 3166 this.currentTime = 0; 3167 } 3168 }); 3169 3170 // Reset all audio button states 3171 $('.audio-button').removeClass('playing').addClass('paused'); 3172 3173 // Pause the Slick slider when video starts playing 3174 const $slider = $(this).closest('.slick-initialized'); 3175 if ($slider.length > 0) { 3176 $slider.slick('slickPause'); 3177 // console.log('Slider paused for video playback'); 3178 } 3179 3180 // Play video - keep muted initially to avoid autoplay policy issues 3181 $frameVideo[0].play().then(() => { 3182 $framePlayPauseBtn.addClass('playing'); 3183 $framePlayPauseBtn.attr('aria-label', 'Pause video'); 3184 // Try to unmute after successful play 3185 $frameVideo.prop('muted', false); 3186 }).catch(error => { 3187 console.error('Error playing video:', error);
3188 // If play fails, try with muted 3189 $frameVideo.prop('muted', true); 3190 $frameVideo[0].play().then(() => { 3191 $framePlayPauseBtn.addClass('playing'); 3192 $framePlayPauseBtn.attr('aria-label', 'Pause video'); 3193 }).catch(mutedError => { 3194 console.error('Error playing muted video:', mutedError); 3195 }); 3196 }); 3197 } else { 3198 // Pause video 3199 $frameVideo[0].pause(); 3200 $framePlayPauseBtn.removeClass('playing'); 3201 $framePlayPauseBtn.attr('aria-label', 'Play video'); 3202 3203 // Resume the Slick slider when video is paused 3204 const $slider = $(this).closest('.slick-initialized'); 3205 if ($slider.length > 0) { 3206 $slider.slick('slickPlay'); 3207 // console.log('Slider resumed after video pause'); 3208 } 3209 } 3210 }); 3211 3212 // Handle progress bar click for seeking 3213 $frameProgressBar.on('click', function(e) { 3214 e.preventDefault(); 3215 e.stopPropagation(); 3216 3217 if ($frameVideo[0] && !isNaN($frameVideo[0].duration)) { 3218 const rect = this.getBoundingClientRect(); 3219 const clickX = e.clientX - rect.left; 3220 const progressBarWidth = rect.width; 3221 const seekTime = (clickX / progressBarWidth) * $frameVideo[0].duration; 3222 3223 $frameVideo[0].currentTime = seekTime; 3224 } 3225 }); 3226 3227 // Handle video timeupdate event for progress bar 3228 $frameVideo.on('timeupdate', updateFrameProgress); 3229 3230 // Handle video loadedmetadata event 3231 $frameVideo.on('loadedmetadata', function() { 3232 updateFrameProgress(); 3233 }); 3234 3235 // Handle video ended event 3236 $frameVideo.on('ended', function() { 3237 $framePlayPauseBtn.removeClass('playing'); 3238 $framePlayPauseBtn.attr('aria-label', 'Play video'); 3239 updateFrameProgress(); // Ensure progress bar shows 100% 3240 3241 // Resume the Slick slider when video ends 3242 const $slider = $(this).closest('.slick-initialized'); 3243 if ($slider.length > 0) { 3244 $slider.slick('slickPlay'); 3245 // console.log('Slider resumed after video ended'); 3246 } 3247 }); 3248 3249 // Handle video play event 3250 $frameVideo.on('play', function() { 3251 $framePlayPauseBtn.addClass('playing'); 3252 $framePlayPauseBtn.attr('aria-label', 'Pause video'); 3253 }); 3254 3255 // Handle video pause event 3256 $frameVideo.on('pause', function() { 3257 $framePlayPauseBtn.removeClass('playing'); 3258 $framePlayPauseBtn.attr('aria-label', 'Play video'); 3259 }); 3260 } 3261 }); 3262 } 3263} 3264 3265/* 3266===================================== 3267Search Placeholder Text Function 3268===================================== 3269*/ 3270 3271// Set the search placeholder text 3272document.addEventListener('DOMContentLoaded', function() { 3273 const searchInput = document.querySelector('.site-search-wrapper .site-search-block.site-search-option3 .site-search-data .site-search-input'); 3274 if (searchInput) { 3275 searchInput.placeholder = 'Search this website...'; 3276 } 3277}); 3278 3279 3280 3281 3282// function blockquoteParallax() { 3283// console.log('blockquoteParallax function called'); 3284// const parallaxSections = document.querySelectorAll('.site-blockquote-wrapper'); 3285// console.log('Found parallax sections:', parallaxSections.length); 3286 3287// if (parallaxSections.length === 0) {
3288// console.log('No parallax sections found, returning'); 3289// return; 3290// } 3291 3292// function updateParallax() { 3293// const scrollY = window.scrollY; 3294// parallaxSections.forEach((section, index) => { 3295// const rect = section.getBoundingClientRect(); 3296// const sectionTop = rect.top + window.scrollY; 3297// const sectionHeight = rect.height; 3298// const windowHeight = window.innerHeight; 3299 3300// // Calculate how much of the section is visible (0 to 1) 3301// const scrolled = scrollY - sectionTop; 3302// const progress = Math.max(0, Math.min(1, scrolled / (sectionHeight + windowHeight))); 3303 3304// // Different speeds for different elements - more visible for testing 3305// const speedBefore = 1.5; // globe moves slower 3306// const speedAfter = 2.0; // outer rings move faster 3307 3308// // Apply parallax transforms - start moving immediately when section comes into view 3309// const beforeOffset = progress * speedBefore * 100; // Start at 0, move as section scrolls 3310// const afterOffset = progress * speedAfter * 100; 3311 3312// // Debug logging for first section 3313// if (index === 0) { 3314// console.log('Section 0 - Progress:', progress, 'Before offset:', beforeOffset, 'After offset:', afterOffset); 3315// } 3316 3317// section.style.setProperty('--before-transform', `${beforeOffset}px`); 3318// section.style.setProperty('--after-transform', `${afterOffset}px`); 3319 3320// // Temporary test - add a visible indicator that the function is working 3321// if (index === 0) { 3322// section.style.border = `5px solid red`; 3323// section.style.borderColor = `hsl(${progress * 360}, 70%, 50%)`; 3324// section.style.backgroundColor = `rgba(255, 0, 0, 0.1)`; 3325// section.style.position = 'relative'; 3326// section.style.zIndex = '1000'; 3327// } 3328// }); 3329// requestAnimationFrame(updateParallax); 3330// } 3331 3332// updateParallax(); 3333// } 3334 3335function cloneGalleryButton() { 3336 $('.site-gallery-image-wrapper').each(function() { 3337 const $wrapper = $(this); 3338 const $slide = $wrapper.find('.site-slide'); 3339 3340 if ($slide.length > 0) { 3341 const $buttonContainer = $slide.find('.site-slider-more-desc'); 3342 3343 if ($buttonContainer.length > 0) { 3344 $wrapper.addClass('has-mobile-button'); 3345 const $clonedButton = $buttonContainer.clone(); 3346 $clonedButton.addClass('gallery-button-container'); 3347 $clonedButton.insertAfter($wrapper); 3348 } 3349 } 3350 }); 3351} 3352 3353 3354 3355function utilityLinksClone() { 3356 $('#site-header-utility-links').each(function() { 3357 const $utilityLinks = $(this); 3358 const $targetContainer = $('.site-header-hamburger-wrapper .site-navigation-container .site-navigation-wrapper'); 3359 3360 // Check if clone already exists to avoid duplicates 3361 if ($targetContainer.length && !$targetContainer.find('.site-header-utility-links').length) { 3362 const $clonedLinks = $utilityLinks.clone(); 3363 $targetContainer.append($clonedLinks); 3364 } 3365 }); 3366} 3367 3368
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.