1/* global Swiper, Fancybox, Lenis */ 2(function ($) { 3 "use strict"; 4 5 function swiperClickToSlide(swiper) { 6 if (!swiper || !swiper.el) return; 7 $(swiper.el).on("click", function (e) { 8 if ($(e.target).closest("a, button, input, textarea, select, .swiper-scrollbar").length) return; 9 var offset = e.pageX - $(this).offset().left; 10 var width = $(this).outerWidth(); 11 if (offset < width * 0.35) { 12 swiper.slidePrev(); 13 } else if (offset > width * 0.65) { 14 swiper.slideNext(); 15 } 16 }); 17 } 18 19 /* ============================================================ 20 ANNOUNCEMENT SWIPER 21 ============================================================ */ 22 23 /* ============================================================ 24 STICKY HEADER â hide top-bar on scroll 25 ============================================================ */ 26 function initStickyHeader() { 27 var topBar = document.querySelector(".site-header__top-bar"); 28 if (!topBar) return; 29 30 var origH = topBar.offsetHeight; 31 topBar.style.height = origH + "px"; 32 33 var isScrolled = false; 34 35 window.addEventListener( 36 "scroll", 37 function () { 38 var scrollY = window.scrollY || window.pageYOffset; 39 var shouldScroll = scrollY > 10; 40 if (shouldScroll === isScrolled) return; 41 isScrolled = shouldScroll; 42 43 if (isScrolled) { 44 topBar.style.height = "0"; 45 topBar.style.paddingTop = "0"; 46 topBar.style.paddingBottom = "0"; 47 topBar.style.opacity = "0"; 48 } else { 49 topBar.style.height = origH + "px"; 50 topBar.style.paddingTop = ""; 51 topBar.style.paddingBottom = ""; 52 topBar.style.opacity = ""; 53 } 54 }, 55 { passive: true }, 56 ); 57 } 58 59 /* ============================================================ 60 LENIS SMOOTH SCROLL 61 ============================================================ */ 62 function initLenis() { 63 if (typeof Lenis === "undefined") return; 64 65 var lenis = new Lenis({ 66 autoRaf: true, 67 }); 68 69 // Forward Lenis scroll position to anchor links 70 $(document).on("click", 'a[href^="#"]', function (e) { 71 var target = $(this).attr("href"); 72 if (target === "#") return; 73 var $target = $(target); 74 if (!$target.length) return; 75 e.preventDefault(); 76 lenis.scrollTo($target[0]); 77 }); 78 } 79 80 /* ============================================================ 81 MEGA MENU 82 ============================================================ */ 83 function initMegaMenu() { 84 var $navItems = $(".nav-item--has-mega"); 85 if (!$navItems.length) return; 86 87 var hideTimer = null; 88 89 function closeAll() { 90 $navItems 91 .find(".mega-menu") 92 .removeClass("mega-menu--open") 93 .attr("aria-hidden", "true"); 94 } 95 96 $navItems.each(function () { 97 var $item = $(this); 98 var $menu = $item.find(".mega-menu"); 99 100 if (!$menu.length) return; 101 102 function open() { 103 clearTimeout(hideTimer); 104 closeAll(); 105 $menu.addClass("mega-menu--open").attr("aria-hidden", "false"); 106 } 107 108 function scheduleClose() { 109 hideTimer = setTimeout(function () { 110 $menu 111 .removeClass("mega-menu--open") 112 .attr("aria-hidden", "true"); 113 }, 120); 114 } 115 116 $item.on("mouseenter", open).on("mouseleave", scheduleClose); 117 118 $menu 119 .on("mouseenter", function () { 120 clearTimeout(hideTimer); 121 }) 122 .on("mouseleave", scheduleClose); 123 }); 124 125 $(document) 126 .on("keydown", function (e) { 127 if (e.key === "Escape") closeAll(); 128 }) 129 .on("click", function (e) { 130 if (!$(e.target).closest(".nav-item--has-mega").length) { 131 closeAll(); 132 } 133 }); 134 } 135 136 /* ============================================================ 137 MOBILE MENU 138 ============================================================ */ 139 function initMobileMenu() { 140 var $header = $(".site-header"); 141 var $burger = $(".site-header__burger"); 142 var $menu = $("#mobileMenu"); 143 144 if (!$header.length || !$burger.length || !$menu.length) return; 145 146 function openMenu() { 147 $header.addClass("site-header--open"); 148 $menu.attr("aria-hidden", "false"); 149 $burger.attr("aria-expanded", "true"); 150 $("body").addClass("scroll-locked"); 151 } 152 153 function closeMenu() { 154 $header.removeClass("site-header--open"); 155 $menu.attr("aria-hidden", "true"); 156 $burger.attr("aria-expanded", "false"); 157 $("body").removeClass("scroll-locked"); 158 } 159 160 $burger.on("click", openMenu); 161 $("#mobileClose").on("click", closeMenu); 162 $("#menuOverlay").on("click", closeMenu); 163 164 $(document).on("keydown", function (e) { 165 if (e.key === "Escape" && $header.hasClass("site-header--open")) { 166 closeMenu(); 167 $burger.trigger("focus"); 168 } 169 }); 170 171 $menu.on("click", ".site-header__mobile-trigger", function () { 172 var $trigger = $(this); 173 var $item = $trigger.closest(".site-header__mobile-item"); 174 var isOpen = $item.hasClass("site-header__mobile-item--is-open"); 175 176 $menu 177 .find(".site-header__mobile-item--is-open") 178 .removeClass("site-header__mobile-item--is-open") 179 .find(".site-header__mobile-trigger") 180 .attr("aria-expanded", "false"); 181 182 if (!isOpen) { 183 $item.addClass("site-header__mobile-item--is-open"); 184 $trigger.attr("aria-expanded", "true"); 185 } 186 }); 187 } 188 189 /* ============================================================ 190 ANNOUNCEMENT SWIPER 191 ============================================================ */ 192 function initAnnouncementSwiper() { 193 if (!$("#announcementSwiper").length) return; 194 new Swiper("#announcementSwiper", { 195 loop: true, 196 autoplay: { delay: 4000, disableOnInteraction: false }, 197 allowTouchMove: false, 198 effect: "fade", 199 fadeEffect: { crossFade: true }, 200 navigation: { 201 prevEl: ".site-header__top-arrow--prev", 202 nextEl: ".site-header__top-arrow--next", 203 }, 204 }); 205 } 206 207 /* ============================================================ 208 NEWS SWIPER 209 ============================================================ */ 210 function initNewsSwiper() { 211 var baseConfig = { 212 slidesPerView: 1.2, 213 centeredSlides: true, 214 loop: true, 215 spaceBetween: 0, 216 grabCursor: true, 217 scrollbar: { el: ".news-swiper__scrollbar", draggable: true }, 218 breakpoints: { 219 769: { 220 slidesPerView: "auto", 221 centeredSlides: true, 222 loop: true, 223 loopedSlides: 8, 224 }, 225 }, 226 }; 227 if ($("#newsSwiper").length) { 228 var newsSwiper = new Swiper("#newsSwiper", $.extend({}, baseConfig, { 229 loopedSlides: $("#newsSwiper .swiper-slide").length, 230 })); 231 swiperClickToSlide(newsSwiper); 232 setTimeout(function () { 233 newsSwiper.slideNext(); 234 }, 600); 235 } 236 $( 237 "#communityNewsSwiper, #blogSingleSwiper, #relatedProgramsSwiper, #programsSwiper", 238 ).each(function () { 239 var slideCount = $(this).find(".swiper-slide").length; 240 var swiper = new Swiper(this, $.extend({}, baseConfig, { 241 loopedSlides: slideCount, 242 })); 243 swiperClickToSlide(swiper); 244 }); 245 } 246 247 /* ============================================================ 248 EVENTS SWIPER 249 ============================================================ */ 250 function initEventsSwiper() { 251 if (!$("#eventsSwiper").length) return; 252 var swiper = new Swiper("#eventsSwiper", { 253 slidesPerView: 1.15, 254 spaceBetween: 0, 255 grabCursor: true, 256 scrollbar: { el: ".programs-swiper__scrollbar", draggable: true }, 257 breakpoints: { 769: { slidesPerView: 2.8 } }, 258 }); 259 swiperClickToSlide(swiper); 260 } 261 function initprogramssliderSwiper() { 262 if (!$('#section-programs-sliderSwiper').length) return 263 var swiper = new Swiper('#section-programs-sliderSwiper', { 264 slidesPerView: 1.2, 265 centeredSlides: false, 266 spaceBetween: 0, 267 grabCursor: true, 268 scrollbar: { 269 el: '.news-swiper__scrollbar', 270 draggable: true, 271 }, 272 breakpoints: { 273 769: { 274 slidesPerView: 2.8, 275 centeredSlides: false, 276 }, 277 }, 278 }) 279 swiperClickToSlide(swiper) 280 } 281 function initsingleeventsNewsSwiper(){ 282 if (!$('#singleeventsNewsSwiper').length) return 283 var swiper = new Swiper('#singleeventsNewsSwiper', { 284 slidesPerView: 1.2, 285 centeredSlides: false, 286 spaceBetween: 0, 287 grabCursor: true, 288 scrollbar: { 289 el: '.news-swiper__scrollbar', 290 draggable: true, 291 }, 292 breakpoints: { 293 769: { 294 slidesPerView: 2.8, 295 centeredSlides: false, 296 }, 297 }, 298 }) 299 swiperClickToSlide(swiper) 300 } 301 /* ============================================================ 302 PROGRAMS SWIPER + PERSONA DROPDOWN 303 ============================================================ */ 304 function initProgramsSwiper() { 305 var $section = $(".programs"); 306 if (!$section.length) return; 307 308 var $swiperEls = $section.find(".programs-swiper"); 309 if (!$swiperEls.length) return; 310 311 var $btn = $("#personaBtn"); 312 var $dropdown = $("#personaDropdown"); 313 var $btnText = $("#personaBtnText"); 314 315 if (!$btn.length || !$dropdown.length || !$btnText.length) return; 316 317 var swiperConfig = { 318 slidesPerView: 1.2, 319 centeredSlides: true, 320 loop: true, 321 spaceBetween: 0, 322 grabCursor: true, 323 scrollbar: { 324 el: ".programs-swiper__scrollbar", 325 draggable: true, 326 }, 327 breakpoints: { 328 769: { 329 slidesPerView: "auto", 330 centeredSlides: false, 331 loop: false, 332 }, 333 }, 334 }; 335 336 var swiperInstances = {}; 337 338 function initSwiper($el) { 339 var idx = $el.index(); 340 if (swiperInstances[idx]) { 341 swiperInstances[idx].destroy(true, true); 342 } 343 var config = $.extend({}, swiperConfig, { 344 scrollbar: { el: $el.find(".programs-swiper__scrollbar")[0], draggable: true }, 345 }); 346 var swiper = new Swiper($el[0], config); 347 swiperClickToSlide(swiper); 348 swiperInstances[idx] = swiper; 349 } 350 351 $swiperEls.each(function () { 352 initSwiper($(this)); 353 }); 354 355 function showPersona(value) { 356 $swiperEls.each(function () { 357 var $el = $(this); 358 var show = $el.data("persona") === value;
359 if ($el.is(":visible") === show) return; 360 if (show) { 361 $el.show(); 362 initSwiper($el); 363 } else { 364 $el.hide(); 365 } 366 }); 367 } 368 369 function open() { 370 $dropdown.prop("hidden", false); 371 $btn.attr("aria-expanded", "true"); 372 } 373 374 function close() { 375 $dropdown.prop("hidden", true); 376 $btn.attr("aria-expanded", "false"); 377 } 378 379 $btn.on("click", function () { 380 if ($dropdown.prop("hidden")) { 381 open(); 382 } else { 383 close(); 384 } 385 }); 386 387 $dropdown 388 .on("click", ".programs__persona-option", function () { 389 var $option = $(this); 390 391 $btnText.text($option.text().trim().toUpperCase()); 392 393 $dropdown 394 .find(".programs__persona-option") 395 .attr("aria-selected", "false"); 396 397 $option.attr("aria-selected", "true"); 398 399 showPersona($option.data("value")); 400 close(); 401 402 $btn.trigger("focus"); 403 }) 404 .on("keydown", function (e) { 405 var $opts = $dropdown.find(".programs__persona-option"); 406 var idx = $opts.index(document.activeElement); 407 408 if (e.key === "ArrowDown") { 409 e.preventDefault(); 410 $opts 411 .eq(Math.min(idx + 1, $opts.length - 1)) 412 .trigger("focus"); 413 } else if (e.key === "ArrowUp") { 414 e.preventDefault(); 415 $opts.eq(Math.max(idx - 1, 0)).trigger("focus"); 416 } else if (e.key === "Escape") { 417 close(); 418 $btn.trigger("focus"); 419 } else if (e.key === "Enter" || e.key === " ") { 420 e.preventDefault(); 421 $(document.activeElement).trigger("click"); 422 } 423 }); 424 425 $(document).on("click", function (e) { 426 if ( 427 !$btn.is(e.target) && 428 !$btn.has(e.target).length && 429 !$dropdown.is(e.target) && 430 !$dropdown.has(e.target).length 431 ) { 432 close(); 433 } 434 }); 435 } 436 437 /* ============================================================ 438 PROGRAMS SLIDER + POPUP 439 ============================================================ */ 440 function initProgramsSliderPopup() { 441 var $section = $(".programs-slider"); 442 if (!$section.length) return; 443 444 /* Init Swiper */ 445 var $swiperEl = $section.find("#programsSliderSwiper"); 446 if ($swiperEl.length) { 447 new Swiper("#programsSliderSwiper", { 448 slidesPerView: 1.2, 449 centeredSlides: true, 450 loop: true, 451 spaceBetween: 0, 452 grabCursor: true, 453 scrollbar: { 454 el: $swiperEl.find(".programs-swiper__scrollbar")[0], 455 draggable: true, 456 }, 457 breakpoints: { 458 769: { 459 slidesPerView: "auto", 460 centeredSlides: false, 461 loop: true, 462 }, 463 }, 464 }); 465 } 466 } 467 468 /* ============================================================ 469 MISSION WORD REVEAL 470 ============================================================ */ 471 function initMissionReveal() { 472 var $targets = $( 473 ".js-mission-reveal, [id='missionText'], [id='campsText']", 474 ); 475 if (!$targets.length) return; 476 477 var revealers = []; 478 479 $targets.each(function () { 480 var $el = $(this); 481 if ($el.data("missionRevealReady")) { 482 return; 483 } 484 485 var plainText = $el.text().trim(); 486 if (!plainText.length) return; 487 488 $el.html( 489 plainText 490 .split(/\s+/) 491 .map(function (w) { 492 return '<span class="mission__word">' + w + "</span>"; 493 }) 494 .join(" "), 495 ); 496 497 var $spans = $el.find(".mission__word"); 498 var $section = $el.closest(".mission").length 499 ? $el.closest(".mission") 500 : $el.parent(); 501 $el.data("missionRevealReady", true); 502 503 revealers.push(function () { 504 var rect = $section[0].getBoundingClientRect(); 505 var progress = Math.min( 506 1, 507 Math.max( 508 0, 509 (window.innerHeight / 2 - rect.top) / 510 (rect.height * 0.7), 511 ), 512 ); 513 var count = Math.round(progress * $spans.length); 514 515 $spans.each(function (i) { 516 $(this).toggleClass("mission__word--revealed", i < count); 517 }); 518 }); 519 }); 520 521 if (!revealers.length) return; 522 523 function revealAll() { 524 revealers.forEach(function (fn) { 525 fn(); 526 }); 527 } 528 529 $(window) 530 .off("scroll.missionReveal") 531 .on("scroll.missionReveal", revealAll); 532 revealAll(); 533 } 534 535 /* ============================================================ 536 SCHEDULE TABS 537 ============================================================ */ 538 function initScheduleTabs() { 539 $(document).on("click", ".schedule__tab", function () { 540 var $tab = $(this); 541 var panelId = $tab.attr("aria-controls"); 542 543 $(".schedule__tab").each(function () { 544 var $t = $(this); 545 $t.removeClass("schedule__tab--active").attr( 546 "aria-selected", 547 "false", 548 ); 549 $("#" + $t.attr("aria-controls")).addClass( 550 "schedule__panel--hidden", 551 ); 552 }); 553 554 $tab.addClass("schedule__tab--active").attr( 555 "aria-selected", 556 "true", 557 ); 558 $("#" + panelId).removeClass("schedule__panel--hidden"); 559 }); 560 } 561 562 /* ============================================================ 563 FAQ ACCORDION + LOAD MORE 564 ============================================================ */ 565 function initFaqAccordion() { 566 $(document).on('click', '.faq__question', function () { 567 var $question = $(this) 568 var $item = $question.closest('.faq__item') 569 var $section = $item.closest('.faq') 570 var isOpen = $item.hasClass('faq__item--open') 571 572 $section 573 .find('.faq__item--open') 574 .removeClass('faq__item--open') 575 .find('.faq__question') 576 .attr('aria-expanded', 'false') 577 578 if (!isOpen) { 579 $item.addClass('faq__item--open') 580 $question.attr('aria-expanded', 'true') 581 } 582 }) 583 584 $(document).on("click", ".faq__load-more-btn", function () { 585 var $btn = $(this); 586 var $section = $btn.closest(".faq"); 587 var $grid = $section.find(".faq__grid"); 588 var $cols = $grid.find(".faq__col"); 589 var perPage = $section.data("faq-per-page") || 5; 590 591 var remaining = $section.data("faq-remaining") || []; 592 if (!remaining.length) { 593 $btn.closest(".faq__load-more").remove(); 594 return; 595 } 596 597 var itemsToLoad = remaining.slice(0, perPage); 598 var remainingItems = remaining.slice(perPage); 599 600 var $template = $section.find(".faq__item-template"); 601 var templateHtml = $template.length 602 ? $template.html() 603 : '<div class="faq__item"><button class="faq__question" aria-expanded="false"><span>{{question}}</span></button><div class="faq__answer">{{answer}}</div></div>'; 604 605 var colIdx = 0;
606 itemsToLoad.forEach(function (item, i) { 607 var html = templateHtml 608 .replace(/{{question}}/g, item.question) 609 .replace(/{{answer}}/g, item.answer); 610 var $col = $cols.eq(colIdx); 611 $col.append(html); 612 if ((i + 1) % Math.ceil(itemsToLoad.length / 2) === 0) { 613 colIdx = (colIdx + 1) % 2; 614 } 615 }); 616 617 $section.data("faq-remaining", remainingItems); 618 619 if (!remainingItems.length) { 620 $btn.closest(".faq__load-more").remove(); 621 } 622 }); 623 } 624 625 /* ============================================================ 626 CAMPS FILTER DROPDOWNS 627 ============================================================ */ 628 function initCampsFilters() { 629 if (!$(".camp-filter").length) return; 630 631 $(document) 632 .on("click", ".camps-catalogue__filter-btn", function (e) { 633 e.stopPropagation(); 634 635 var $btn = $(this); 636 var $filter = $btn.closest(".camp-filter"); 637 var $dropdown = $filter.find(".camp-filter__dropdown"); 638 var isHidden = $dropdown.prop("hidden"); 639 640 $(".camp-filter__dropdown").prop("hidden", true); 641 $(".camps-catalogue__filter-btn").attr( 642 "aria-expanded", 643 "false", 644 ); 645 646 $dropdown.prop("hidden", !isHidden); 647 $btn.attr("aria-expanded", String(isHidden)); 648 }) 649 650 .on("click", ".camp-filter__dropdown", function (e) { 651 e.stopPropagation(); 652 653 var $dropdown = $(this); 654 var $option = $(e.target).closest(".camp-filter__option"); 655 656 if (!$option.length) return; 657 658 var $filter = $dropdown.closest(".camp-filter"); 659 var isCheckbox = $filter.hasClass("camp-filter--checkbox"); 660 661 if (isCheckbox) { 662 var isSelected = $option.attr("aria-selected") === "true"; 663 $option.attr("aria-selected", String(!isSelected)); 664 } else { 665 $dropdown 666 .find(".camp-filter__option") 667 .attr("aria-selected", "false"); 668 $option.attr("aria-selected", "true"); 669 $filter 670 .find(".camp-filter__label") 671 .text($option.text().trim()); 672 $dropdown.prop("hidden", true); 673 $filter 674 .find(".camps-catalogue__filter-btn") 675 .attr("aria-expanded", "false"); 676 } 677 }) 678 679 .on("keydown", ".camp-filter__dropdown", function (e) { 680 var $dropdown = $(this); 681 var $opts = $dropdown.find(".camp-filter__option"); 682 var idx = $opts.index(document.activeElement); 683 var $btn = $dropdown 684 .closest(".camp-filter") 685 .find(".camps-catalogue__filter-btn"); 686 687 if (e.key === "ArrowDown") { 688 e.preventDefault(); 689 $opts 690 .eq(Math.min(idx + 1, $opts.length - 1)) 691 .trigger("focus"); 692 } else if (e.key === "ArrowUp") { 693 e.preventDefault(); 694 $opts.eq(Math.max(idx - 1, 0)).trigger("focus"); 695 } else if (e.key === "Escape") { 696 $dropdown.prop("hidden", true); 697 $btn.attr("aria-expanded", "false").trigger("focus"); 698 } else if (e.key === "Enter" || e.key === " ") { 699 e.preventDefault(); 700 $(document.activeElement).trigger("click"); 701 } 702 }) 703 704 .on("click", function (e) { 705 if (!$(e.target).closest(".camp-filter").length) { 706 $(".camp-filter__dropdown").prop("hidden", true); 707 $(".camps-catalogue__filter-btn").attr( 708 "aria-expanded", 709 "false", 710 ); 711 } 712 }); 713 } 714 715 /* ============================================================ 716 GENERIC SCROLL SLIDER 717 ============================================================ */ 718 function initSlider(trackId, dotsSelector) { 719 var $track = $("#" + trackId); 720 var $dots = $(dotsSelector); 721 if (!$track.length || !$dots.length) return; 722 723 function updateDots(index) { 724 $dots.each(function (i) { 725 $(this) 726 .toggleClass("slider-progress__dot--active", i === index) 727 .attr("aria-selected", String(i === index)); 728 }); 729 } 730 731 $dots.on("click", function () { 732 var index = parseInt($(this).data("index"), 10); 733 var $items = $track.children(); 734 if ($items.eq(index).length) { 735 $track[0].scrollTo({ 736 left: $items[index].offsetLeft - $track[0].offsetLeft, 737 behavior: "smooth", 738 }); 739 } 740 updateDots(index); 741 }); 742 743 var observer = new IntersectionObserver( 744 function (entries) { 745 entries.forEach(function (entry) { 746 if (!entry.isIntersecting) return; 747 var index = Array.from($track[0].children).indexOf( 748 entry.target, 749 ); 750 if (index !== -1) updateDots(index); 751 }); 752 }, 753 { root: $track[0], threshold: 0.5 },
754 ); 755 756 $track.children().each(function () { 757 observer.observe(this); 758 }); 759 } 760 761 /* ============================================================ 762 FEATURED TABS 763 ============================================================ */ 764 function initFeaturedTabs() { 765 $(document).on("click", ".featured__tab", function () { 766 var panelId = $(this).attr("aria-controls"); 767 $(".featured__tab") 768 .removeClass("featured__tab--active") 769 .attr("aria-selected", "false"); 770 $(".featured__panel").removeClass("featured__panel--active"); 771 $(this) 772 .addClass("featured__tab--active") 773 .attr("aria-selected", "true"); 774 $("#" + panelId).addClass("featured__panel--active"); 775 }); 776 } 777 778 /* ============================================================ 779 INVOLVED TABS 780 ============================================================ */ 781 function initInvolvedTabs() { 782 $(".involved__tabs").each(function () { 783 var $tabGroup = $(this); 784 var $tabs = $tabGroup.find(".involved__tab"); 785 if (!$tabs.length) return; 786 787 function activate(panelId) { 788 $tabs.each(function () { 789 $(this) 790 .removeClass("involved__tab--active") 791 .attr("aria-selected", "false"); 792 $("#" + $(this).attr("aria-controls")).addClass( 793 "involved__panel--hidden", 794 ); 795 }); 796 $tabGroup 797 .find('[aria-controls="' + panelId + '"]') 798 .addClass("involved__tab--active") 799 .attr("aria-selected", "true"); 800 $("#" + panelId).removeClass("involved__panel--hidden"); 801 } 802 803 $tabs.on("click", function () { 804 activate($(this).attr("aria-controls")); 805 }); 806 807 var $select = $('<select class="involved__tabs-select">').attr( 808 "aria-label", 809 $tabGroup.attr("aria-label") || "Select tab", 810 ); 811 $tabs.each(function () { 812 $("<option>") 813 .val($(this).attr("aria-controls")) 814 .text($(this).text().trim()) 815 .prop("selected", $(this).attr("aria-selected") === "true") 816 .appendTo($select); 817 }); 818 $select.on("change", function () { 819 activate($(this).val()); 820 }); 821 $tabGroup.after($select); 822 }); 823 } 824 825 /* ============================================================ 826 TEAM CARD EXPAND 827 ============================================================ */ 828 function initTeamCardExpand() { 829 $(document) 830 .on("click", ".team-card__expand", function () { 831 $(this).closest(".team-card").addClass("team-card--expanded"); 832 $(this).attr("aria-expanded", "true"); 833 }) 834 .on("click", ".team-card__collapse", function () { 835 var $card = $(this).closest(".team-card"); 836 $card.removeClass("team-card--expanded"); 837 $card.find(".team-card__expand").attr("aria-expanded", "false"); 838 }); 839 } 840 841 /* ============================================================ 842 NOTIFY ME â Klaviyo onsite popup with program context 843 ============================================================ */ 844 function initNotifyMe() { 845 $(document).on('click', '.js-klaviyo-notify', function () { 846 var $btn = $(this); 847 var formId = $btn.data('form') || (window.spyhopNotify && window.spyhopNotify.formId) || 'TKDYXx'; 848 var props = { 849 'Program Interest': $btn.data('program-title') || document.title, 850 'Program URL': $btn.data('program-url') || window.location.href, 851 'Program ID': String($btn.data('program-id') || ''), 852 'Program Type': $btn.data('program-type') || '' 853 }; 854 // Pass context to Klaviyo so form submission is attributed to this program. 855 // Requires hidden fields with same names in Klaviyo form builder (optional). 856 try { 857 if (window.klaviyo) { 858 window.klaviyo.push(['identify', props]); 859 } else if (window._learnq) { 860 window._learnq.push(['identify', props]); 861 } 862 } catch (e) {} 863 window._klOnsite = window._klOnsite || []; 864 window._klOnsite.push(['openForm', formId]); 865 }); 866 } 867 868 /* ============================================================ 869 NEWSLETTER FORM 870 ============================================================ */ 871 function initNewsletterForm() { 872 var $form = $(".site-footer__form"), 873 $input = $form.find(".site-footer__input"), 874 $row = $form.find(".site-footer__form-row"), 875 $submit = $form.find(".site-footer__submit"), 876 $submitSvg = $submit.find("svg"); 877 878 if (!$form.length) return; 879 880 $form.on("submit", function (e) { 881 e.preventDefault(); 882 883 var email = $input.val().trim(); 884 if (!email) return; 885 886 if (typeof validate !== "undefined") { 887 var v = validate({ email: email }, { email: { email: { message: "Enter a valid email" } } }); 888 if (v) { 889 $row.addClass("site-footer__form-row--error"); 890 $input.one("input", function () { $row.removeClass("site-footer__form-row--error"); }); 891 return; 892 } 893 } 894 895 $row.removeClass("site-footer__form-row--error"); 896 $submit.prop("disabled", true).addClass("site-footer__submit--loading"); 897 $submitSvg.hide(); 898 899 $.post(spyhopNewsletter.ajax_url, { 900 action: "spyhop_newsletter_subscribe", 901 nonce: spyhopNewsletter.nonce, 902 email: email, 903 }) 904 .done(function (res) { 905 if (res.success) { 906 showToast(res.data.message || "Thank you for subscribing!", "success"); 907 $input.val(""); 908 } else { 909 showToast(res.data.message || "Something went wrong.", "error"); 910 } 911 }) 912 .fail(function () { 913 showToast("Network error. Please try again.", "error"); 914 }) 915 .always(function () { 916 $submit.prop("disabled", false).removeClass("site-footer__submit--loading"); 917 $submitSvg.show(); 918 }); 919 }); 920 } 921 922 /* ============================================================ 923 TOAST NOTIFICATION 924 ============================================================ */ 925 function showToast(message, type) { 926 var $toast = $( 927 '<div class="toast toast--' + type + '">' + 928 '<span class="toast__message">' + message + "</span>" + 929 '<button class="toast__close" aria-label="Close">×</button>' + 930 "</div>" 931 ); 932 933 $toast.find(".toast__close").on("click", function () { 934 removeToast($toast); 935 }); 936 937 setTimeout(function () { 938 removeToast($toast); 939 }, 5000); 940 941 $("body").append($toast); 942 requestAnimationFrame(function () { 943 $toast.addClass("toast--visible"); 944 }); 945 } 946 947 function removeToast($toast) { 948 if ($toast.hasClass("toast--removing")) return; 949 $toast.addClass("toast--removing"); 950 $toast.on("transitionend", function () { 951 $toast.remove(); 952 }); 953 } 954 955 window.showToast = showToast; 956 957 /* ============================================================ 958 MEGA MENU CTA OVERLAY 959 ============================================================ */ 960 function initMegaMenuCtaOverlay() { 961 $(document) 962 .on("click", ".mega-menu__cta-btn", function (e) { 963 e.preventDefault(); 964 e.stopPropagation(); 965 $(this) 966 .closest(".mega-menu__cta") 967 .find(".mega-menu__cta-overlay") 968 .addClass("mega-menu__cta-overlay--open") 969 .attr("aria-hidden", "false"); 970 }) 971 .on("click", ".mega-menu__cta-overlay-close", function (e) { 972 e.stopPropagation(); 973 $(this) 974 .closest(".mega-menu__cta-overlay") 975 .removeClass("mega-menu__cta-overlay--open") 976 .attr("aria-hidden", "true"); 977 }) 978 .on("mouseleave", ".nav-item--has-mega", function () { 979 $(this) 980 .find(".mega-menu__cta-overlay") 981 .removeClass("mega-menu__cta-overlay--open") 982 .attr("aria-hidden", "true"); 983 }); 984 } 985 986 /* ============================================================ 987 CAMPS FILTER DRAWER (mobile) 988 ============================================================ */ 989 function initCampsFilterDrawer() { 990 var $drawer = $("#campsFilterDrawer"); 991 if (!$drawer.length) return; 992 993 function close() { 994 $drawer 995 .removeClass("camps-filter-drawer--open") 996 .attr("aria-hidden", "true"); 997 $("body").removeClass("scroll-locked"); 998 } 999 1000 $(document).on("click", "#campsMobileFilterBtn", function () { 1001 $drawer 1002 .addClass("camps-filter-drawer--open") 1003 .attr("aria-hidden", "false"); 1004 $("body").addClass("scroll-locked"); 1005 }); 1006 1007 $(document).on("click", "#campsFilterClose, #campsFilterOverlay", close); 1008 1009 $(document).on("keydown", function (e) { 1010 if (e.key === "Escape" && $drawer.hasClass("camps-filter-drawer--open")) { 1011 close(); 1012 } 1013 }); 1014 1015 $drawer.on("click", ".camps-filter-drawer__section-btn", function () { 1016 var $section = $(this).closest(".camps-filter-drawer__section"); 1017 var isOpen = $section.hasClass("camps-filter-drawer__section--open"); 1018 $section.toggleClass("camps-filter-drawer__section--open", !isOpen); 1019 $(this).attr("aria-expanded", String(!isOpen)); 1020 }); 1021 } 1022 1023 /* ============================================================ 1024 GALLERY MOSAIC SLIDER + FANCYBOX 1025 ============================================================ */ 1026 function initGalleryMosaic() { 1027 $(".gallery-mosaic__desktop-swiper").each(function () { 1028 if (!this.id) return; 1029 var swiper = new Swiper("#" + this.id, { 1030 slidesPerView: "auto", 1031 freeMode: true, 1032 grabCursor: true, 1033 }); 1034 swiperClickToSlide(swiper); 1035 }); 1036 1037 $(".gallery-mosaic__mobile-swiper, .post-mosaic__swiper").each(function () { 1038 if (!this.id) return; 1039 var swiper = new Swiper("#" + this.id, { 1040 slidesPerView: 1, 1041 grabCursor: true, 1042 }); 1043 swiperClickToSlide(swiper); 1044 }); 1045 1046 if (typeof Fancybox !== "undefined") { 1047 Fancybox.bind("[data-fancybox]", { groupAll: false }); 1048 } 1049 } 1050 1051 /* ============================================================ 1052 EXAMPLE EVENTS REVEAL 1053 ============================================================ */ 1054 function initExampleEventsReveal() { 1055 var sections = document.querySelectorAll(".js-example-events"); 1056 if (!sections.length) return; 1057 1058 if (!("IntersectionObserver" in window)) { 1059 sections.forEach(function (section) { 1060 section.classList.add("is-visible"); 1061 }); 1062 return; 1063 } 1064 1065 var observer = new IntersectionObserver( 1066 function (entries, obs) {
1067 entries.forEach(function (entry) { 1068 if (!entry.isIntersecting) return; 1069 entry.target.classList.add("is-visible"); 1070 obs.unobserve(entry.target); 1071 }); 1072 }, 1073 { threshold: 0.2 }, 1074 ); 1075 1076 sections.forEach(function (section) { 1077 observer.observe(section); 1078 }); 1079 } 1080 1081 /* ============================================================ 1082 PRICING PACKAGES REVEAL 1083 ============================================================ */ 1084 function initPricingPackagesReveal() { 1085 var sections = document.querySelectorAll(".js-pricing-packages"); 1086 if (!sections.length) return; 1087 1088 if (!("IntersectionObserver" in window)) { 1089 sections.forEach(function (section) { 1090 section.classList.add("is-visible"); 1091 }); 1092 return; 1093 } 1094 1095 var observer = new IntersectionObserver( 1096 function (entries, obs) { 1097 entries.forEach(function (entry) { 1098 if (!entry.isIntersecting) return; 1099 entry.target.classList.add("is-visible"); 1100 obs.unobserve(entry.target); 1101 }); 1102 }, 1103 { threshold: 0.18 }, 1104 ); 1105 1106 sections.forEach(function (section) { 1107 observer.observe(section); 1108 }); 1109 } 1110 1111 /* ============================================================ 1112 RESOURCES TABS 1113 ============================================================ */ 1114 function initResourcesTabs() { 1115 $(document).on("click", ".resources__tab", function () { 1116 var panelId = $(this).attr("aria-controls"); 1117 $(".resources__tab").each(function () { 1118 $(this) 1119 .removeClass("resources__tab--active") 1120 .attr("aria-selected", "false"); 1121 $("#" + $(this).attr("aria-controls")).addClass( 1122 "resources__panel--hidden", 1123 ); 1124 }); 1125 $(this) 1126 .addClass("resources__tab--active") 1127 .attr("aria-selected", "true"); 1128 $("#" + panelId).removeClass("resources__panel--hidden"); 1129 }); 1130 } 1131 1132 /* ============================================================ 1133 CAMPS CATALOGUE FILTER (AJAX) 1134 ============================================================ */ 1135 function initCampsFilter() { 1136 console.log("[camps] initCampsFilter"); 1137 var $grid = $("#campsCatalogueGrid"); 1138 var $section = $("#camps-catalogue"); 1139 if (!$grid.length || !$section.length) { console.log("[camps] skip: no grid/section"); return; } 1140 1141 var $loadMoreBtn = $("#campsLoadMore"); 1142 var $countEl = $(".camps-catalogue__count"); 1143 var $resetBtns = $(".camps-catalogue__reset"); 1144 1145 var postIds = String($section.data("post-ids") || "") 1146 .split(",") 1147 .map(Number) 1148 .filter(Boolean); 1149 var perPage = parseInt($section.data("per-page"), 10) || 9; 1150 var programType = String($section.data("program-type") || ""); 1151 var initialCards = $grid.find(".camp-card").length; 1152 console.log("[camps] init programType=" + programType + " data-attr=" + $section.attr("data-program-type") + " initialCards=" + initialCards + " perPage=" + perPage); 1153 var labelTaxes = String($section.data("label-taxes") || ""); 1154 console.log("[camps] labelTaxes=" + labelTaxes); 1155 1156 var activeFilters = {}; 1157 var currentPage = 1; 1158 var isLoading = false; 1159 var mobileLimited = false; 1160 var storageKey = "campsCatalogue_" + programType; 1161 1162 function saveCampsState() { 1163 try { 1164 var state = { currentPage: currentPage, scrollY: window.scrollY }; 1165 sessionStorage.setItem(storageKey, JSON.stringify(state)); 1166 } catch (e) {} 1167 } 1168 1169 function clearCampsState() { 1170 try { sessionStorage.removeItem(storageKey); } catch (e) {} 1171 } 1172 1173 /* ââ Mobile: limit initial view to 5 cards ââ */ 1174 function applyMobileLimit() { 1175 if ($(window).width() > 768) { mobileLimited = false; return; } 1176 var $cards = $grid.find(".camp-card"); 1177 if ($cards.length > 5) { 1178 $cards.slice(5).hide(); 1179 mobileLimited = true; 1180 } else { 1181 mobileLimited = false; 1182 } 1183 } 1184 applyMobileLimit(); 1185 1186 /* ââ Build AJAX payload ââ */ 1187 function buildPayload(page) { 1188 var payload = { 1189 action: "spyhop_catalogue", 1190 nonce: spyhopAjax.nonce, 1191 paged: page, 1192 per_page: perPage, 1193 program_type: programType, 1194 label_taxes: labelTaxes, 1195 filters: {}, 1196 }; 1197 if (postIds.length) payload.post__in = postIds; 1198 1199 $.each(activeFilters, function (source, values) { 1200 if (values === null || values === undefined) return; 1201 var arr = Array.isArray(values) ? values : [values]; 1202 arr = $.grep(arr, function (v) { 1203 return v !== ""; 1204 }); 1205 if (arr.length) payload.filters[source] = arr; 1206 }); 1207 1208 return payload; 1209 } 1210 1211 /* ââ Staggered entrance animation for new cards ââ */ 1212 function animateIn($cards) { 1213 $cards.addClass("camp-card--new"); 1214 $cards.each(function (i) { 1215 $(this).css("animation-delay", i * 0.045 + "s"); 1216 }); 1217 setTimeout(function () { 1218 $cards.removeClass("camp-card--new").css("animation-delay", ""); 1219 }, 900); 1220 } 1221 1222 /* ââ Core fetch ââ */ 1223 function doFetch(page, append) { 1224 if (isLoading) return; 1225 isLoading = true; 1226 if (!append) $grid.addClass("is-fetching"); 1227 1228 $.ajax({ 1229 url: spyhopAjax.url, 1230 type: "POST", 1231 data: buildPayload(page), 1232 success: function (res) { 1233 $grid.removeClass("is-fetching"); 1234 if (!res.success) { 1235 console.log("[camps] doFetch failed: not success"); 1236 isLoading = false;
1237 return; 1238 } 1239 1240 var d = res.data; 1241 var html = d.html || ""; 1242 console.log("[camps] doFetch ok: has_more=" + d.has_more + " total=" + d.total + " paged=" + d.paged + " htmlLen=" + html.length); 1243 currentPage = d.paged; 1244 1245 if (append) { 1246 var $new = $($.parseHTML(html)); 1247 $grid.append($new); 1248 animateIn($new.filter(".camp-card")); 1249 saveCampsState(); 1250 } else { 1251 var $new = $($.parseHTML(html)); 1252 animateIn($new.filter(".camp-card")); 1253 $grid.empty().append($new); 1254 applyMobileLimit(); 1255 clearCampsState(); 1256 } 1257 if (d.has_more) { 1258 $loadMoreBtn.removeAttr("hidden"); 1259 } else { 1260 $loadMoreBtn.attr("hidden", ""); 1261 } 1262 if ($countEl.length) { 1263 $countEl.text( 1264 d.total > 0 ? d.total + " PROGRAMS" : "0 PROGRAMS", 1265 ); 1266 } 1267 isLoading = false; 1268 }, 1269 error: function (jqXHR, textStatus) { 1270 console.log("[camps] doFetch error:", textStatus); 1271 $grid.removeClass("is-fetching"); 1272 isLoading = false; 1273 }, 1274 }); 1275 } 1276 1277 /* ââ Sync drawer state from activeFilters ââ */ 1278 function syncDrawer(source) { 1279 var arr = activeFilters[source] 1280 ? Array.isArray(activeFilters[source]) 1281 ? activeFilters[source] 1282 : [String(activeFilters[source])] 1283 : []; 1284 $( 1285 '.camps-filter-drawer__section[data-filter="' + 1286 source + 1287 '"] .camps-filter-drawer__option', 1288 ).each(function () { 1289 var v = String($(this).data("value") || ""); 1290 $(this).attr( 1291 "aria-selected", 1292 v && arr.indexOf(v) !== -1 ? "true" : "false", 1293 ); 1294 }); 1295 } 1296 1297 /* ââ Sync desktop filter state from activeFilters ââ */ 1298 function syncDesktop(source) { 1299 var arr = activeFilters[source] 1300 ? Array.isArray(activeFilters[source]) 1301 ? activeFilters[source] 1302 : [String(activeFilters[source])] 1303 : []; 1304 var $filter = $('.camp-filter[data-filter="' + source + '"]'); 1305 var isCheckbox = $filter.hasClass("camp-filter--checkbox"); 1306 1307 $filter.find(".camp-filter__option").each(function () { 1308 var v = String($(this).data("value") || ""); 1309 var selected = v ? arr.indexOf(v) !== -1 : arr.length === 0; 1310 $(this).attr("aria-selected", selected ? "true" : "false"); 1311 }); 1312 1313 if (!isCheckbox) { 1314 var activeText = $filter 1315 .find('.camp-filter__option[aria-selected="true"]') 1316 .first() 1317 .text() 1318 .trim(); 1319 $filter.find(".camp-filter__label").text(activeText); 1320 } 1321 } 1322 1323 /* ââ Desktop filter option click ââ */ 1324 $(document).on("click", ".camp-filter__option", function () { 1325 var $filter = $(this).closest(".camp-filter"); 1326 var source = String($filter.data("filter")); 1327 var value = String($(this).data("value") || ""); 1328 var isCheckbox = $filter.hasClass("camp-filter--checkbox"); 1329 1330 if (isCheckbox) { 1331 var current = Array.isArray(activeFilters[source]) 1332 ? activeFilters[source].slice() 1333 : []; 1334 var idx = current.indexOf(value); 1335 if (value) { 1336 if (idx === -1) current.push(value); 1337 else current.splice(idx, 1); 1338 } 1339 activeFilters[source] = current.length ? current : null; 1340 } else { 1341 activeFilters[source] = value || null; 1342 } 1343 1344 syncDrawer(source); 1345 currentPage = 1; 1346 doFetch(1, false); 1347 }); 1348 1349 /* ââ Drawer option click ââ */ 1350 $(document).on("click", ".camps-filter-drawer__option", function () { 1351 var $sec = $(this).closest(".camps-filter-drawer__section"); 1352 var source = String($sec.data("filter")); 1353 var isSelected = $(this).attr("aria-selected") === "true"; 1354 $(this).attr("aria-selected", isSelected ? "false" : "true"); 1355 1356 var selected = []; 1357 $sec.find( 1358 '.camps-filter-drawer__option[aria-selected="true"]', 1359 ).each(function () { 1360 var v = String($(this).data("value") || ""); 1361 if (v) selected.push(v); 1362 }); 1363 activeFilters[source] = selected.length ? selected : null; 1364 1365 syncDesktop(source); 1366 currentPage = 1; 1367 doFetch(1, false); 1368 1369 $("#campsFilterClose").trigger("click"); 1370 }); 1371 1372 /* ââ Reset ââ */ 1373 $resetBtns.on("click", function () { 1374 activeFilters = {}; 1375 $(".camp-filter__option").attr("aria-selected", "false"); 1376 $( 1377 ".camp-filter:not(.camp-filter--checkbox) .camp-filter__option--all", 1378 ) 1379 .attr("aria-selected", "true") 1380 .each(function () { 1381 $(this) 1382 .closest(".camp-filter") 1383 .find(".camp-filter__label") 1384 .text( 1385 $(this) 1386 .closest(".camp-filter") 1387 .data("default-label") || "", 1388 ); 1389 }); 1390 $(".camps-filter-drawer__option").attr("aria-selected", "false"); 1391 currentPage = 1; 1392 doFetch(1, false); 1393 }); 1394 1395 /* ââ Save scroll when clicking a camp card ââ */ 1396 $(document).on("click", ".camp-card a", function () { 1397 if (currentPage > 1) saveCampsState(); 1398 }); 1399 1400 /* ââ Load More ââ */ 1401 $loadMoreBtn.on("click", function () { 1402 console.log("[camps] load more click: mobileLimited=" + mobileLimited + " currentPage=" + currentPage); 1403 // Mobile: 5 cards initially â first click reveals remaining local cards 1404 if (mobileLimited) { 1405 $grid.find(".camp-card").show(); 1406 mobileLimited = false;
1407 var hasMore = $section.data("has-more") ? true : false; 1408 if (!hasMore) $loadMoreBtn.attr("hidden", ""); 1409 return; 1410 } 1411 doFetch(currentPage + 1, true); 1412 }); 1413 1414 /* ââ Restore state on back navigation ââ */ 1415 (function () { 1416 var saved; 1417 try { saved = JSON.parse(sessionStorage.getItem(storageKey)); } catch (e) {} 1418 console.log("[camps] saved state:", saved); 1419 if (saved && saved.currentPage > 1) { 1420 console.log("[camps] restoring to page " + saved.currentPage); 1421 var targetPage = saved.currentPage; 1422 var restoreScroll = saved.scrollY || 0; 1423 $loadMoreBtn.attr("hidden", ""); 1424 (function fetchChain(page) { 1425 if (page > targetPage) { 1426 if (restoreScroll) { $("html, body").animate({ scrollTop: restoreScroll }, 400); } 1427 return; 1428 } 1429 $.ajax({ 1430 url: spyhopAjax.url, 1431 type: "POST", 1432 data: buildPayload(page), 1433 success: function (res) { 1434 if (res.success && res.data.html) { 1435 var d = res.data; 1436 var h = d.html || ""; 1437 currentPage = d.paged; 1438 var $new = $($.parseHTML(h)); 1439 $grid.append($new); 1440 animateIn($new.filter(".camp-card")); 1441 if (d.has_more) { 1442 $loadMoreBtn.removeAttr("hidden"); 1443 } else { 1444 $loadMoreBtn.attr("hidden", ""); 1445 } 1446 if ($countEl.length) { 1447 $countEl.text( 1448 d.total > 0 ? d.total + " PROGRAMS" : "0 PROGRAMS", 1449 ); 1450 } 1451 } 1452 fetchChain(page + 1); 1453 }, 1454 }); 1455 })(2); 1456 } 1457 })(); 1458 } 1459 1460 /* ============================================================ 1461 TOGGLE NAVIGATOR 1462 ============================================================ */ 1463 function initToggleNav() { 1464 $(document).on("click", ".toggle-nav__btn", function () { 1465 var panelId = $(this).attr("aria-controls"); 1466 $(".toggle-nav__btn") 1467 .removeClass("toggle-nav__btn--active") 1468 .attr("aria-selected", "false") 1469 .each(function () { 1470 var p = $(this).attr("aria-controls"); 1471 if (p) $("#" + p).prop("hidden", true); 1472 }); 1473 $(this) 1474 .addClass("toggle-nav__btn--active") 1475 .attr("aria-selected", "true"); 1476 $("#" + panelId).prop("hidden", false); 1477 }); 1478 } 1479 1480 /* ============================================================ 1481 SPY HOP WAY â COLUMN EXPAND (click toggle) 1482 ============================================================ */ 1483 function initSpyHopWay() { 1484 function openCol($col) { 1485 $col.addClass("spy-hop-way__col__open"); 1486 $col.find(".spy-hop-way__col_btn").attr("aria-expanded", "true"); 1487 } 1488 1489 function closeCol($col) { 1490 $col.removeClass("spy-hop-way__col__open"); 1491 setTimeout(function () { 1492 $col.find(".spy-hop-way__col_btn").attr("aria-expanded", "false"); 1493 }, 300); 1494 } 1495 1496 $(document).on("click", ".spy-hop-way__col_btn", function () { 1497 var $col = $(this).closest(".spy-hop-way__col"); 1498 var isOpen = $col.hasClass("spy-hop-way__col__open"); 1499 if (isOpen) { 1500 closeCol($col); 1501 } else { 1502 $(".spy-hop-way__col").not($col).each(function () { 1503 closeCol($(this)); 1504 }); 1505 openCol($col); 1506 } 1507 }); 1508 1509 $(document).on("click", ".spy-hop-way__col_close", function () { 1510 var $col = $(this).closest(".spy-hop-way__col"); 1511 closeCol($col); 1512 }); 1513 } 1514 1515 /* ============================================================ 1516 PROGRAMS FILTER (static) 1517 ============================================================ */ 1518 function initProgramsFilter() { 1519 var $grid = $("#programsCatalogueGrid"); 1520 if (!$grid.length) return; 1521 1522 var activeFilters = { 1523 skill: null, 1524 medium: null, 1525 age: null, 1526 season: null, 1527 }; 1528 var cycleMap = { 1529 skill: ["beginner", "intermediate", "advanced", null], 1530 medium: ["film", "music", "audio", "design", "mixed-media", null], 1531 age: ["youth", "teen", null], 1532 season: ["summer", "spring", null], 1533 }; 1534 1535 function applyFilters() { 1536 $grid.find(".program-card").each(function () { 1537 var $card = $(this); 1538 var match = 1539 (!activeFilters.skill || 1540 $card.data("skill") === activeFilters.skill) && 1541 (!activeFilters.medium || 1542 $card.data("medium") === activeFilters.medium) && 1543 (!activeFilters.age || 1544 $card.data("age") === activeFilters.age) && 1545 (!activeFilters.season || 1546 $card.data("season") === activeFilters.season); 1547 $card.toggle(match); 1548 }); 1549 } 1550 1551 $("[data-filter]").on("click", function () { 1552 var key = $(this).data("filter"); 1553 var values = cycleMap[key]; 1554 if (!values) return; 1555 var idx = values.indexOf(activeFilters[key]); 1556 activeFilters[key] = values[(idx + 1) % values.length]; 1557 applyFilters(); 1558 }); 1559 1560 $("#programsReset").on("click", function () { 1561 $.each(activeFilters, function (k) { 1562 activeFilters[k] = null; 1563 }); 1564 $grid.find(".program-card").show(); 1565 }); 1566 1567 $("#programsLoadMore").on("click", function () {
1568 $(this).text("No more programs to show.").prop("disabled", true); 1569 }); 1570 } 1571 1572 /* ============================================================ 1573 EVENTS FILTER (Featured Events/News Tabs) 1574 ============================================================ */ 1575 function initEventsFilter() { 1576 var $section = $("#featuredEventsNews"); 1577 if (!$section.length) return; 1578 1579 var $grid = $("#upcomingEventsGrid"); 1580 var $dropdown = $(".events-filter__dropdown"); 1581 var $resetBtn = $(".events-filter__reset"); 1582 var $label = $(".events-filter__label"); 1583 1584 var isLoading = false; 1585 var activeType = ""; 1586 1587 function doFetch(type) { 1588 if (isLoading) return; 1589 isLoading = true; 1590 1591 $.ajax({ 1592 url: spyhopAjax.url, 1593 type: "POST", 1594 data: { 1595 action: "spyhop_events_filter", 1596 nonce: spyhopAjax.events_nonce, 1597 event_type: type, 1598 }, 1599 success: function (res) { 1600 isLoading = false; 1601 if (!res.success) return; 1602 1603 $grid.html(res.data.html); 1604 }, 1605 }); 1606 } 1607 1608 $dropdown.on("click", ".events-filter__option", function () { 1609 var val = $(this).data("value") || ""; 1610 activeType = activeType === val ? "" : val; 1611 $dropdown 1612 .find(".events-filter__option") 1613 .attr("aria-selected", "false"); 1614 if (activeType) { 1615 $(this).attr("aria-selected", "true"); 1616 $label.text($(this).text().toUpperCase()); 1617 $resetBtn.show(); 1618 } else { 1619 $label.text("TYPE"); 1620 $resetBtn.hide(); 1621 } 1622 doFetch(activeType); 1623 }); 1624 1625 $resetBtn.on("click", function () { 1626 activeType = ""; 1627 $label.text("TYPE"); 1628 $resetBtn.hide(); 1629 $dropdown 1630 .find(".events-filter__option") 1631 .attr("aria-selected", "false"); 1632 doFetch(""); 1633 }); 1634 1635 /* Past Events LOAD MORE */ 1636 var $loadMore = $("#pastEventsLoadMore"); 1637 var $pastGrid = $("#pastEventsGrid"); 1638 var pastPage = 1; 1639 var pastPerPage = parseInt($section.data("past-per-page"), 10) || 6; 1640 var totalPast = parseInt($section.data("total-past"), 10) || 0; 1641 1642 $loadMore.on("click", function () { 1643 if ($(this).prop("disabled")) return; 1644 pastPage++; 1645 1646 $.ajax({ 1647 url: spyhopAjax.url, 1648 type: "POST", 1649 data: { 1650 action: "spyhop_past_events_load", 1651 nonce: spyhopAjax.events_nonce, 1652 paged: pastPage, 1653 per_page: pastPerPage, 1654 }, 1655 success: function (res) { 1656 if (!res.success) return; 1657 $pastGrid.append(res.data.html); 1658 if (!res.data.has_more) { 1659 $loadMore 1660 .prop("disabled", true) 1661 .text("No more events."); 1662 } 1663 }, 1664 }); 1665 }); 1666 } 1667 1668 /* ============================================================ 1669 NEWS FILTER + LOAD MORE (Featured News Tab) 1670 ============================================================ */ 1671 function initNewsFilter() { 1672 var $section = $("#featuredEventsNews"); 1673 if (!$section.length) return; 1674 1675 var $grid = $("#newsGrid"); 1676 var $loadMore = $("#newsLoadMore"); 1677 var $resetBtn = $(".news-filter__reset"); 1678 var $authorLabel = $(".news-filter__label--author"); 1679 var $topicLabel = $(".news-filter__label--topic"); 1680 var $authorOpts = $( 1681 ".news-filter__dropdown--author .news-filter__option", 1682 ); 1683 var $topicOpts = $( 1684 ".news-filter__dropdown--topic .news-filter__option", 1685 ); 1686 1687 if (!$grid.length) return; 1688 1689 var activeAuthor = ""; 1690 var activeTopic = ""; 1691 var newsPage = 1; 1692 var newsPerPage = parseInt($section.data("news-per-page"), 10) || 6; 1693 var isLoading = false; 1694 var isFetchedAll = false; 1695 1696 function applyFilters() { 1697 $grid.find(".camp-card").each(function () { 1698 var $card = $(this); 1699 var matchAuthor = 1700 !activeAuthor || $card.data("author") === activeAuthor; 1701 var matchTopic = 1702 !activeTopic || $card.data("topic") === activeTopic; 1703 $card.toggle(matchAuthor && matchTopic); 1704 }); 1705 } 1706 1707 /* Author dropdown click */ 1708 $(document).on( 1709 "click", 1710 ".news-filter__dropdown--author .news-filter__option", 1711 function () { 1712 var val = $(this).data("value") || ""; 1713 activeAuthor = activeAuthor === val ? "" : val; 1714 $authorOpts.attr("aria-selected", "false"); 1715 if (activeAuthor) { 1716 $(this).attr("aria-selected", "true"); 1717 $authorLabel.text(activeAuthor.toUpperCase()); 1718 } else { 1719 $authorLabel.text("AUTHOR"); 1720 } 1721 applyFilters(); 1722 }, 1723 ); 1724 1725 /* Topic dropdown click */ 1726 $(document).on( 1727 "click", 1728 ".news-filter__dropdown--topic .news-filter__option", 1729 function () { 1730 var val = $(this).data("value") || ""; 1731 activeTopic = activeTopic === val ? "" : val; 1732 $topicOpts.attr("aria-selected", "false"); 1733 if (activeTopic) { 1734 $(this).attr("aria-selected", "true"); 1735 $topicLabel.text($(this).text().toUpperCase()); 1736 } else { 1737 $topicLabel.text("TOPIC"); 1738 } 1739 applyFilters(); 1740 }, 1741 ); 1742 1743 /* Reset */ 1744 $resetBtn.on("click", function () { 1745 activeAuthor = ""; 1746 activeTopic = ""; 1747 $authorLabel.text("AUTHOR"); 1748 $topicLabel.text("TOPIC"); 1749 $authorOpts.attr("aria-selected", "false"); 1750 $topicOpts.attr("aria-selected", "false"); 1751 applyFilters(); 1752 }); 1753 1754 /* Load More */ 1755 $loadMore.on("click", function () { 1756 if (isLoading || isFetchedAll) return; 1757 isLoading = true; 1758 newsPage++; 1759 1760 $.ajax({ 1761 url: spyhopAjax.url, 1762 type: "POST", 1763 data: { 1764 action: "spyhop_featured_news_load", 1765 nonce: $section.data("featured-nonce") || spyhopAjax.featured_nonce, 1766 paged: newsPage, 1767 per_page: newsPerPage, 1768 }, 1769 success: function (res) { 1770 isLoading = false;
1771 if (!res.success) return; 1772 var $new = $($.parseHTML(res.data.html)); 1773 $grid.append($new); 1774 applyFilters(); 1775 if (!res.data.has_more) { 1776 isFetchedAll = true; 1777 $loadMore.prop("disabled", true).text("NO MORE NEWS"); 1778 } 1779 }, 1780 error: function () { 1781 isLoading = false; 1782 }, 1783 }); 1784 }); 1785 } 1786 1787 /* ============================================================ 1788 INIT 1789 ============================================================ */ 1790 $(function () { 1791 initStickyHeader(); 1792 initLenis(); 1793 initMegaMenu(); 1794 initMobileMenu(); 1795 initAnnouncementSwiper(); 1796 initNewsSwiper(); 1797 initEventsSwiper(); 1798 initprogramssliderSwiper(); 1799 initProgramsSwiper(); 1800 initProgramsSliderPopup(); 1801 initMissionReveal(); 1802 initScheduleTabs(); 1803 initFaqAccordion(); 1804 initCampsFilters(); 1805 initSlider( 1806 "relatedTrack", 1807 "#relatedTrack ~ .slider-progress .slider-progress__dot", 1808 ); 1809 initFeaturedTabs(); 1810 initInvolvedTabs(); 1811 initCampsFilterDrawer(); 1812 initTeamCardExpand(); 1813 initNotifyMe(); 1814 initNewsletterForm(); 1815 initMegaMenuCtaOverlay(); 1816 initGalleryMosaic(); 1817 initExampleEventsReveal(); 1818 initPricingPackagesReveal(); 1819 initResourcesTabs(); 1820 initCampsFilter(); 1821 initToggleNav(); 1822 initSpyHopWay(); 1823 initProgramsFilter(); 1824 initEventsFilter(); 1825 initNewsFilter(); 1826 initsingleeventsNewsSwiper(); 1827 initByTheNumbers(); 1828 initYouthMatters(); 1829 1830 $(".site-footer__back-top").on("click", function (e) { 1831 e.preventDefault(); 1832 $("html, body").animate({ scrollTop: 0 }, 600); 1833 }); 1834 }); 1835 /* ============================================================ 1836 BY THE NUMBERS â STAT EXPAND (width transition) 1837 ============================================================ */ 1838 function initByTheNumbers() { 1839 $(document).on("click", ".by-the-numbers__stat-btn", function () { 1840 var $stat = $(this).closest(".by-the-numbers__stat"); 1841 var $allStats = $(".by-the-numbers__stat"); 1842 var isOpen = $stat.hasClass("by-the-numbers__stat--expanded"); 1843 1844 if (isOpen) { 1845 $allStats.removeClass("by-the-numbers__stat--expanded by-the-numbers__stat--collapsed"); 1846 $allStats.find(".by-the-numbers__stat-btn").attr("aria-expanded", "false"); 1847 } else { 1848 $allStats.each(function () { 1849 if ($(this).is($stat)) return; 1850 $(this).removeClass("by-the-numbers__stat--expanded").addClass("by-the-numbers__stat--collapsed"); 1851 }); 1852 $stat.removeClass("by-the-numbers__stat--collapsed").addClass("by-the-numbers__stat--expanded"); 1853 $stat.find(".by-the-numbers__stat-btn").attr("aria-expanded", "true"); 1854 } 1855 }); 1856 } 1857 1858 /* ============================================================ 1859 YOUTH MATTERS â typewriter tagline 1860 ============================================================ */ 1861 function initYouthMatters() { 1862 var $section = $(".youth-matters"); 1863 1864 if (!$section.length) return; 1865 1866 var $wordEl = $section.find(".youth-matters__word"); 1867 var $verbEl = $section.find(".youth-matters__verb"); 1868 var $dynamicEl = $section.find(".youth-matters__dynamic"); 1869 1870 var words; 1871 try { words = JSON.parse($section.attr("data-words")); } catch (e) { return; } 1872 if (!words || !words.length) return; 1873 1874 var suffix = $section.data("suffix") || "Matters"; 1875 var suffixPlural = $section.data("suffix-plural") || "Matter"; 1876 1877 var HOLD_AFTER_TYPE = 1750; 1878 var DELETE_DURATION = 400; 1879 var TYPE_PER_CHAR = 70; 1880 var PAUSE_BEFORE_TYPE = 180; 1881 1882 function sleep(ms) { return new Promise(function (r) { setTimeout(r, ms); }); } 1883 1884 async function typeWord(text) { 1885 $dynamicEl.addClass("youth-matters__typing"); 1886 for (var i = 1; i <= text.length; i++) { 1887 $wordEl.text(text.slice(0, i)); 1888 await sleep(TYPE_PER_CHAR); 1889 } 1890 $dynamicEl.removeClass("youth-matters__typing"); 1891 } 1892 1893 async function deleteWord(text) { 1894 $dynamicEl.addClass("youth-matters__typing"); 1895 var step = Math.max(15, DELETE_DURATION / Math.max(text.length, 1)); 1896 for (var i = text.length - 1; i >= 0; i--) { 1897 $wordEl.text(text.slice(0, i)); 1898 await sleep(step); 1899 } 1900 $dynamicEl.removeClass("youth-matters__typing"); 1901 } 1902 1903 async function run() { 1904 var i = 0; 1905 while (true) { 1906 var item = words[i % words.length]; 1907 $verbEl.text(item.plural ? suffixPlural : suffix); 1908 await typeWord(item.word); 1909 await sleep(HOLD_AFTER_TYPE); 1910 await deleteWord(item.word); 1911 await sleep(PAUSE_BEFORE_TYPE); 1912 i++; 1913 } 1914 } 1915 1916 run(); 1917 } 1918 1919})(jQuery);
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.