1document.addEventListener('DOMContentLoaded', function () { 2 if (document.querySelector('.new-home-hero-big-slider') && 3 document.querySelector('.new-home-hero-small-slider')) { 4 const slidesCount = document.querySelectorAll('.new-home-hero__big-slide').length; 5 const bigSlider = new Splide('.new-home-hero-big-slider', { 6 type: 'loop', 7 autoplay: true, 8 interval: 10000, 9 arrows: false, 10 pagination: false, 11 loop: true, 12 speed: 2000, 13 focusOnScroll: false, 14 drag: false, 15 perMove: 1, 16 mediaQuery: 'min', 17 clones: slidesCount, 18 }).mount(); 19 20 const smallSlider = new Splide('.new-home-hero-small-slider', { 21 type: 'loop', 22 perPage: 1.25, 23 gap: 15, 24 focusOnScroll: true, 25 mediaQuery: 'min', 26 breakpoints: { 27 640: { 28 perPage: 2.25, 29 }, 30 992: { 31 perPage: 3, 32 gap: 10, 33 }, 34 }, 35 autoplay: true, 36 interval: 10000, 37 loop: true, 38 speed: 2000, 39 pagination: false, 40 perMove: 1, 41 clones: slidesCount, 42 }).mount(); 43 44 smallSlider.sync(bigSlider); 45 } 46 47 // (() => { 48 // const root = document.querySelector('[data-course-filter]'); 49 // if (!root) return; 50 // 51 // const selects = [...root.querySelectorAll('[data-filter]')]; 52 // const cards = [...root.querySelectorAll('[data-course-card]')]; 53 // const empty = root.querySelector('[data-course-empty]'); 54 // const resetBtn = root.querySelector('[data-course-reset]'); 55 // if (!selects.length || !cards.length) return; 56 // 57 // const hasActiveFilters = () => selects.some((s) => s.value.trim() !== ''); 58 // 59 // const toggleReset = () => { 60 // if (!resetBtn) return; 61 // resetBtn.hidden = !hasActiveFilters(); 62 // }; 63 // 64 // const getFilters = () => { 65 // const filters = {}; 66 // selects.forEach((sel) => { 67 // const tax = sel.getAttribute('data-filter'); 68 // const val = sel.value.trim(); 69 // if (tax) filters[tax] = val; 70 // }); 71 // return filters; 72 // }; 73 // 74 // const matches = (card, filters) => { 75 // return Object.entries(filters).every(([tax, val]) => { 76 // if (!val) return true; 77 // const attr = card.getAttribute(`data-${tax}`) || ''; 78 // return attr.split(/\s+/).includes(val); 79 // }); 80 // }; 81 // 82 // const apply = () => { 83 // const filters = getFilters(); 84 // let visibleCount = 0; 85 // 86 // cards.forEach((card) => { 87 // const ok = matches(card, filters); 88 // card.style.display = ok ? '' : 'none'; 89 // if (ok) visibleCount += 1; 90 // }); 91 // 92 // if (empty) empty.hidden = visibleCount !== 0; 93 // toggleReset(); 94 // }; 95 // 96 // const reset = () => { 97 // selects.forEach((s) => { 98 // s.value = ''; 99 // // Optional: trigger change for any custom UI hooks 100 // s.dispatchEvent(new Event('change', { bubbles: true })); 101 // }); 102 // 103 // if (empty) empty.hidden = true; 104 // toggleReset(); 105 // // apply() already ran via change events, but safe: 106 // apply(); 107 // }; 108 // 109 // selects.forEach((sel) => sel.addEventListener('change', apply)); 110 // if (resetBtn) resetBtn.addEventListener('click', reset); 111 // 112 // apply(); 113 // })(); 114 (() => { 115 const root = document.querySelector('[data-course-filter]'); 116 if (!root) return; 117 118 const selects = [...root.querySelectorAll('[data-filter]')]; 119 const checkbox = root.querySelector('[data-filter-ampap]'); 120 const cards = [...root.querySelectorAll('[data-course-card]')]; 121 122 const resetBtn = root.querySelector('[data-course-reset]'); 123 const emptyMsg = root.querySelector('[data-course-empty]'); 124 125 if (!cards.length) return; 126 127 const getFilters = () => { 128 const filters = {};
129 selects.forEach((sel) => { 130 const tax = sel.getAttribute('data-filter'); 131 const val = sel.value.trim(); 132 if (tax) filters[tax] = val; 133 }); 134 135 return filters; 136 }; 137 138 const matches = (card, filters) => { 139 // Checkbox filter (AMPAP elective) 140 if (checkbox && checkbox.checked) { 141 if ((card.getAttribute('data-ampap') || '0') !== '1') return false; 142 } 143 144 // AND logic for selects 145 return Object.entries(filters).every(([tax, val]) => { 146 if (!val) return true; 147 const attr = card.getAttribute(`data-${tax}`) || ''; 148 return attr.split(/\s+/).includes(val); 149 }); 150 }; 151 152 const apply = () => { 153 const filters = getFilters(); 154 let visibleCount = 0; 155 let hasAnyFilter = false; 156 157 selects.forEach((sel) => { 158 if (sel.value.trim()) hasAnyFilter = true; 159 }); 160 if (checkbox && checkbox.checked) hasAnyFilter = true; 161 162 cards.forEach((card) => { 163 const ok = matches(card, filters); 164 card.style.display = ok ? '' : 'none'; 165 if (ok) visibleCount += 1; 166 }); 167 168 if (resetBtn) resetBtn.hidden = !hasAnyFilter; 169 if (emptyMsg) emptyMsg.hidden = visibleCount !== 0; 170 }; 171 172 const reset = () => { 173 selects.forEach((sel) => { 174 sel.value = ''; 175 }); 176 if (checkbox) checkbox.checked = false; 177 apply(); 178 }; 179 180 selects.forEach((sel) => sel.addEventListener('change', apply)); 181 if (checkbox) checkbox.addEventListener('change', apply); 182 if (resetBtn) resetBtn.addEventListener('click', reset); 183 184 apply(); 185 })(); 186 187 (() => { 188 if (!document.querySelector('#upcoming_sessions')) return; 189 190 const el = document.querySelector('#upcoming_sessions.splide'); 191 if (!el || typeof Splide === 'undefined') return; 192 193 const upcomingSessionsSlider = new Splide(el, { 194 type: 'slide', 195 perPage: 3, 196 perMove: 1, 197 gap: '24px', 198 pagination: false, 199 arrows: true, 200 drag: true, 201 rewind: false, 202 speed: 600, 203 breakpoints: { 204 1200: {perPage: 3, gap: '24px'}, 205 992: {perPage: 2, gap: '20px'}, 206 640: {perPage: 1, gap: '16px'}, 207 }, 208 }); 209 upcomingSessionsSlider.mount(); 210 })(); 211 212 function loadVideo(containerClass, delay = 2500) { 213 const videoContainers = document.querySelectorAll(containerClass); 214 215 if (!videoContainers.length) return; 216 217 videoContainers.forEach(videoContainer => { 218 const videoElement = videoContainer.querySelector('[data-video="content"]'); 219 const previewImage = videoContainer.querySelector('[data-video="preview"]'); 220 const videoPath = videoElement.getAttribute('data-path'); 221 222 if (!videoPath) return; 223 224 setTimeout(() => { 225 videoElement.setAttribute('src', videoPath); 226 videoElement.load(); 227 228 videoElement.addEventListener('loadeddata', () => { 229 previewImage.style.opacity = 0; 230 previewImage.style.pointerEvents = 'none'; 231 232 videoElement.play().catch(error => { 233 console.error('Error playing video:', error); 234 }); 235 }); 236 }, delay); 237 }); 238 } 239 240 loadVideo('.home-video'); 241 242 const newHomeCounterAnimator = new CounterAnimator('.new-home-numbers__number', { 243 useObserver: true, 244 duration: 1000 245 }); 246 247 const newHomeTabs = new Tabs({ 248 tabLinkClass: 'tabs-heading__btn', 249 tabContentClass: 'tabs-content__item', 250 activeClass: 'active' 251 }); 252 253 const newHomeAccordion = new Accordion('.new-home-mission__tabs-heading', '.new-home-mission__tabs-content'); 254}); 255 256const PLACEHOLDER_INPUT_SELECTOR = '.gform_wrapper input:not([type=radio]):not([type=checkbox]):not([type=submit]):not([type=button]):not
256([type=image]):not([type=file]):not([type=search])'; 257 258function generatePlaceholders() { 259 if (typeof jQuery === 'undefined') { 260 return; 261 } 262 263 jQuery(PLACEHOLDER_INPUT_SELECTOR).each(function () { 264 const $field = jQuery(this); 265 const $container = $field.parent(); 266 267 if ($container.find('.fake_placeholder').length) { 268 return; 269 } 270 271 let placeholder; 272 let placeholderValue; 273 274 if ($field.attr('placeholder')) { 275 placeholderValue = $field.attr('placeholder'); 276 } else if ($field.closest('.ginput_complex').length && $field.siblings('label').length) { 277 placeholderValue = $field.siblings('label').text(); 278 } else if ($field.closest('.gfield').find('.gfield_label').length) { 279 placeholderValue = $field.closest('.gfield').find('.gfield_label').text().replace('*', '').replace('(Required)', ''); 280 } else { 281 placeholderValue = $field.closest('.form-row').find('label').text().replace('*', ''); 282 } 283 284 if ($field.attr('aria-required') === 'true' || $field.closest('.form-row').find('label').find('.required').length) { 285 placeholder = placeholderValue + '<span class="asterisk">*</span>'; 286 } else { 287 placeholder = placeholderValue; 288 } 289 290 $container.append(`<div class="fake_placeholder">${placeholder}</div>`); 291 292 if ($field.val()) { 293 $container.find('.fake_placeholder').addClass('focus'); 294 } 295 }); 296} 297 298function initFormPlaceholders() { 299 generatePlaceholders(); 300} 301 302function bindPlaceholderEvents() { 303 if (typeof jQuery === 'undefined' || jQuery(document).data('placeholderEventsBound')) { 304 return; 305 } 306 307 jQuery(document) 308 .on('focus.placeholder', PLACEHOLDER_INPUT_SELECTOR, function () { 309 jQuery(this).parent().find('.fake_placeholder').addClass('focus'); 310 }) 311 .on('focusout.placeholder', PLACEHOLDER_INPUT_SELECTOR, function () { 312 if (!jQuery(this).val()) { 313 jQuery(this).parent().find('.fake_placeholder').removeClass('focus'); 314 } 315 }); 316 317 jQuery(document).data('placeholderEventsBound', true); 318} 319 320document.addEventListener('gform/post_render', initFormPlaceholders); 321document.addEventListener('DOMContentLoaded', () => { 322 bindPlaceholderEvents(); 323 initFormPlaceholders(); 324}); 325 326if (typeof jQuery !== 'undefined') { 327 jQuery(document).on('gform_post_render', initFormPlaceholders); 328 bindPlaceholderEvents(); 329 initFormPlaceholders(); 330} else { 331 document.addEventListener('DOMContentLoaded', () => { 332 if (typeof jQuery !== 'undefined') { 333 jQuery(document).on('gform_post_render', initFormPlaceholders); 334 bindPlaceholderEvents(); 335 initFormPlaceholders(); 336 } 337 }); 338} 339 340class Tabs { 341 constructor(options) { 342 this.tabLinkClass = options.tabLinkClass; 343 this.tabContentClass = options.tabContentClass; 344 this.activeClass = options.activeClass; 345 346 this.tabLinks = document.querySelectorAll(`.${this.tabLinkClass}`); 347 this.tabContents = document.querySelectorAll(`.${this.tabContentClass}`); 348 349 if (!this.tabLinks.length || !this.tabContents.length) return; 350 351 this.init(); 352 } 353 354 hideAllTabs() { 355 this.tabLinks.forEach(link => { 356 link.classList.remove(this.activeClass); 357 link.setAttribute('aria-selected', 'false'); 358 }); 359 360 this.tabContents.forEach(content => { 361 content.classList.remove(this.activeClass); 362 }); 363 } 364 365 showTab(tabLink, tabContent) { 366 tabLink.classList.add(this.activeClass); 367 tabLink.setAttribute('aria-selected', 'true'); 368 tabContent.classList.add(this.activeClass); 369 } 370 371 initFirstTab() { 372 const firstLink = this.tabLinks[0]; 373 const firstContent = this.tabContents[0]; 374 375 this.hideAllTabs(); 376 this.showTab(firstLink, firstContent); 377 } 378 379 init() { 380 this.tabLinks.forEach(link => { 381 link.addEventListener('click', (e) => { 382 e.preventDefault(); 383 const targetId = link.getAttribute('aria-controls'); 384 const targetContent = document.getElementById(targetId); 385 386 this.hideAllTabs(); 387 this.showTab(link, targetContent); 388 }); 389 }); 390 391 this.initFirstTab(); 392 } 393} 394 395class Accordion { 396 constructor(buttonSelector, contentSelector, activeClass = 'active') { 397 this.buttons = document.querySelectorAll(buttonSelector); 398 this.activeClass = activeClass; 399 this.contents = document.querySelectorAll(contentSelector); 400 401 if (this.buttons.length === 0 || this.contents.length === 0) return; 402 403 this.init(); 404 this.openFirstItem(); 405 } 406 407 init() { 408 this.buttons.forEach((button, index) => { 409 button.addEventListener('click', () => { 410 this.toggleItem(index); 411 }); 412 }); 413 } 414 415 toggleItem(index) { 416 const isActive = this.buttons[index].classList.contains(this.activeClass); 417 418 this.buttons.forEach((button) => button.classList.remove(this.activeClass));
419 this.contents.forEach((content) => content.classList.remove(this.activeClass)); 420 421 if (!isActive) { 422 this.buttons[index].classList.add(this.activeClass); 423 this.contents[index].classList.add(this.activeClass); 424 } 425 } 426 427 openFirstItem() { 428 if (this.buttons.length > 0 && this.contents.length > 0) { 429 this.buttons[0].classList.add(this.activeClass); 430 this.contents[0].classList.add(this.activeClass); 431 } 432 } 433} 434 435class CounterAnimator { 436 constructor(selector, options = {}) { 437 this.counters = Array.from(document.querySelectorAll(selector)); 438 if (!this.counters.length) return; 439 440 this.useObserver = options.useObserver ?? true; 441 this.duration = options.duration ?? 1200; // ms 442 this.threshold = options.threshold ?? 0.1; 443 444 if (this.useObserver && 'IntersectionObserver' in window) { 445 this.initObserver(); 446 } else { 447 this.counters.forEach((el) => this.start(el)); 448 } 449 } 450 451 start(el) { 452 if (!el || el.dataset.animated === '1') return; 453 454 const target = Number(el.getAttribute('data-target')); 455 if (!Number.isFinite(target)) return; 456 457 el.dataset.animated = '1'; 458 459 const decimals = this.getDecimalPlaces(target); 460 const startTime = performance.now(); 461 const from = 0; 462 463 const tick = (now) => { 464 const t = Math.min((now - startTime) / this.duration, 1); 465 const current = from + (target - from) * t; 466 467 el.textContent = this.formatNumber(current.toFixed(decimals)); 468 469 if (t < 1) { 470 requestAnimationFrame(tick); 471 return; 472 } 473 474 // Final value 475 el.textContent = this.formatNumber(target.toFixed(decimals)); 476 477 // Add "+" suffix if wrapper has data-plus 478 const stats = el.closest('.new-home-numbers__stats'); 479 const hasPlus = stats && stats.hasAttribute('data-plus'); 480 481 if (hasPlus && el.dataset.plusApplied !== '1') { 482 el.textContent += '+'; 483 el.dataset.plusApplied = '1'; 484 } 485 }; 486 487 requestAnimationFrame(tick); 488 } 489 490 initObserver() { 491 const observer = new IntersectionObserver( 492 (entries) => { 493 entries.forEach((entry) => { 494 if (!entry.isIntersecting) return; 495 this.start(entry.target); 496 observer.unobserve(entry.target); 497 }); 498 }, 499 {threshold: this.threshold} 500 ); 501 502 this.counters.forEach((el) => observer.observe(el)); 503 } 504 505 getDecimalPlaces(value) { 506 const s = String(value); 507 const idx = s.indexOf('.'); 508 return idx === -1 ? 0 : s.length - idx - 1; 509 } 510 511 formatNumber(strNumber) { 512 const [intPart, decPart] = String(strNumber).split('.'); 513 const withSep = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ','); 514 return decPart ? `${withSep}.${decPart}` : withSep; 515 } 516} 517 518/** 519 * Global Gravity Forms "Loading" state for submit buttons 520 */ 521document.addEventListener('gform/theme/scripts_loaded', () => { 522 523 // Using GF async filter to intercept the submission process 524 gform.utils.addAsyncFilter('gform/submission/pre_submission', async (data) => { 525 const formElement = data.form; 526 if (!formElement) return data; 527 528 // Find our custom button and the text span 529 const submitBtn = formElement.querySelector('.nextg-submit-button'); 530 const textSpan = submitBtn?.querySelector('.nextg-submit-button__text'); 531 532 if (submitBtn && textSpan) { 533 // Store original text in a data-attribute if not already stored 534 if (!submitBtn.dataset.originalText) { 535 submitBtn.dataset.originalText = textSpan.textContent; 536 } 537 538 // Apply BEM modifier for loading state 539 submitBtn.classList.add('nextg-submit-button--loading'); 540 541 // Change text 542 textSpan.textContent = 'Loading...'; 543 } 544 545 return data; 546 }); 547}); 548 549/** 550 * Reset button state if there's a validation error 551 * (GF triggers 'gform_post_render' when the form reloads with errors) 552 */ 553if (typeof jQuery !== 'undefined') { 554 jQuery(document).on('gform_post_render', (event, formId) => { 555 const form = document.getElementById(`gform_${formId}`); 556 const submitBtn = form?.querySelector('.nextg-submit-button'); 557 const textSpan = submitBtn?.querySelector('.nextg-submit-button__text'); 558 559 if (submitBtn && textSpan && submitBtn.dataset.originalText) { 560 // Remove loading state and restore text 561 submitBtn.classList.remove('nextg-submit-button--loading'); 562 textSpan.textContent = submitBtn.dataset.originalText; 563 } 564 }); 565}
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.