PageSourceSearch

https://aci.aero/wp-content/themes/bb-theme-child/js/nextgScripts.js?ver=1788341268

js aci.aero collected 2026-09-24 17:08:59 UTC 18,830 bytes, 565 lines download raw bytes

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.