PageSourceSearch

https://studentsdemandaction.org/wp-content/themes/bd/js/theme.js?ver=0.38368500%201790367155

js studentsdemandaction.org collected 2026-09-25 20:48:08 UTC 14,242 bytes, 488 lines download raw bytes

1/**
2 * Theme JS
3 *
4 * @package bd
5 */
6
7var $ = jQuery;
8
9$( function() {
10
11	// Shareable images — copy link buttons.
12	( function() {
13		$( document ).on( 'click', '.js--copy-link', function() {
14			var $btn = $( this );
15			var $input = $btn.siblings( '.js--text-to-copy' );
16			var $text = $btn.find( '.js--copy-button-text' );
17
18			if ( navigator.clipboard && $input.length ) {
19				navigator.clipboard.writeText( $input.val() ).then( function() {
20					$text.text( 'Copied!' );
21					setTimeout( function() {
22						$text.text( 'Copy Link' );
23					}, 2000 );
24				} );
25			}
26		} );
27	} )();
28
29	// Announcement banner dismiss with session cookie.
30	( function() {
31		var banner = $( '#alert-banner' );
32		var closeBtn = $( '#alert-banner-close' );
33
34		if ( ! banner.length ) {
35			return;
36		}
37
38		// Check if already dismissed this session.
39		if ( 'true' === sessionStorage.getItem( 'bd_banner_dismissed' ) ) {
40			banner.addClass( 'alert-banner--hidden' );
41			return;
42		}
43
44		closeBtn.on( 'click', function( e ) {
45			e.preventDefault();
46			e.stopPropagation();
47			banner.addClass( 'alert-banner--hidden' );
48			sessionStorage.setItem( 'bd_banner_dismissed', 'true' );
49		} );
50	} )();
51
52	var navigation = {
53		$nav: $( '.nav' ),
54		$navTrigger: $( '.nav__trigger' ),
55		$navContent: $( '.nav__content' ),
56		$navList: $( '.nav__list' ),
57
58		init: function() {
59			var self = this;
60
61			self.$navTrigger.on( 'click', function() {
62				var isExpanded = self.$navTrigger.attr( 'aria-expanded' ) === 'true';
63				self.$navTrigger.attr( 'aria-expanded', ! isExpanded );
64				self.$nav.attr( 'aria-hidden', isExpanded );
65
66				if ( ! self.$navTrigger.hasClass( 'is-active' ) ) {
67					self.$navTrigger.addClass( 'is-active' );
68
69					if ( ! self.$nav.hasClass( 'is-active' ) ) {
70						self.$nav.addClass( 'is-active' );
71						self.$nav.on( 'transitionend', function( e ) {
72							if (
73								e.originalEvent.propertyName === 'width' &&
74								self.$navTrigger.hasClass( 'is-active' )
75							) {
76								self.$navContent.addClass( 'is-active' );
77							}
78						} );
79					} else {
80						self.$navContent.addClass( 'is-active' );
81					}
82
83					if ( $( 'html' ).hasClass( 'no-csstransitions' ) ) {
84						self.$navContent.addClass( 'is-active' );
85					}
86				} else {
87					self.$navTrigger.removeClass( 'is-active' );
88
89					if ( self.$navContent.hasClass( 'is-active' ) ) {
90						self.$navContent.removeClass( 'is-active' );
91						self.$navContent.on( 'transitionend', function( e ) {
92							if (
93								e.originalEvent.propertyName === 'opacity' &&
94								! self.$navTrigger.hasClass( 'is-active' )
95							) {
96								self.$nav.removeClass( 'is-active' );
97							}
98						} );
99					} else {
100						self.$nav.removeClass( 'is-active' );
101					}
102
103					if ( $( 'html' ).hasClass( 'no-csstransitions' ) ) {
104						self.$nav.removeClass( 'is-active' );
105					}
106				}
107			} );
108		},
109	};
110	navigation.init();
111
112	var current = location.pathname;
113	if ( '/' !== current ) {
114		$( '.bd_main_menu li a' ).each( function() {
115			var $this = $( this );
116			if ( -1 !== $this.attr( 'href' ).indexOf( current ) ) {
117				$this.addClass( 'active' );
118			}
119		} );
120	}
121
122	$( '.Trigger' ).on( 'click', function() {
123		if ( $( '.Trigger-top' ).hasClass( 'no-animation' ) ) {
124			$( '.Trigger-top' ).removeClass( 'no-animation' );
125			$( '.Trigger-bottom' ).removeClass( 'no-animation' );
126		}
127
128		$( '.bd_header' ).toggleClass( 'staticHeader' );
129
130		if ( $( '.Trigger' ).hasClass( 'is-active' ) ) {
131			$( '.bd_page' ).addClass( 'bd_no_scroll' );
132		} else {
133			$( '.bd_page' ).removeClass( 'bd_no_scroll' );
134		}
135	} );
136
137	// Keyboard support for mobile nav submenu chevrons.
138	$( '.nav__item i' ).attr( 'role', 'button' ).attr( 'tabindex', '0' ).attr( 'aria-label', 'Toggle submenu' );
139	$( '.nav__item i' ).on( 'keydown', function( e ) {
140		if ( 13 === e.keyCode || 32 === e.keyCode ) {
141			e.preventDefault();
142			$( this ).trigger( 'click' );
143		}
144	} );
145
146	$( '.nav__item i' ).on( 'click', function( e ) {
147		e.stopPropagation();
148
149		var subMenu = $( this ).closest( 'li' ).find( '.sub-menu' );
150		if ( subMenu.hasClass( 'bd_visible' ) ) {
151			subMenu.removeClass( 'bd_visible' );
152		} else {
153			$( '.sub-menu' ).removeClass( 'bd_visible' );
154			subMenu.addClass( 'bd_visible' );
155		}
156	} );
157
158	// Interstitial — outbound redirect modal for allowlisted domains.
159	( function() {
160		var interstitialSites = [
161			'everytownresearch.org',
162			'everytownsupportfund.org',
163			'wearorange.org',
164			'nrawatch.org',
165			'everystat.org',
166			'ask.everytownresearch.org',
167			'gunsensevoter.org',
168			'besmartforkids.org',
169			'onethingyoucando.org',
170			'momentsthatsurvive.org',
171			'smokinggun.org',
172			'demandaseat.org',
173			'citygrip.org'
174		];
175
176		var $modal = $( '.js--interstitial' );
177		var $currentLink = $( '.js--interstitial-current-link' );
178
179		if ( ! $modal.length ) {
180			return;
181		}
182
183		function matchesDomain( host, domain ) {
184			return host === domain || host.slice( -domain.length - 1 ) === '.' + domain;
185		}
186
187		function isInterstitialHost( host ) {
188			for ( var i = 0; i < interstitialSites.length; i++ ) {
189				if ( matchesDomain( host, interstitialSites[i] ) ) {
190					return true;
191				}
192			}
193			return false;
194		}
195
196		function shouldShowInterstitial( linkHost ) {
197			if ( linkHost === window.location.hostname ) {
198				return false;
199			}
200			return isInterstitialHost( linkHost );
201		}
202
203		$( 'a[href]' ).each( function() {
204			if ( shouldShowInterstitial( this.hostname ) ) {
205				$( this ).addClass( 'js--interstitial-link' );
206			}
207		} );
208
209		$( document ).on( 'click', '.js--interstitial-link', function( e ) {
210			e.preventDefault();
211			e.stopImmediatePropagation();
212
213			var href = this.href;
214			var $clicked = $( this );
215
216			$currentLink.text( window.location.hostname );
217			$modal.attr( 'aria-hidden', 'false' ).addClass( 'active' );
218			$( 'body' ).addClass( 'bd_no_scroll' );
219
220			setTimeout( function() {
221				window.open( href, '_blank' ) || window.location.assign( href );
222
223				setTimeout( function() {
224					$modal.attr( 'aria-hidden', 'true' ).removeClass( 'active' );
225					$( 'body' ).removeClass( 'bd_no_scroll' );
226					$clicked.focus();
227				}, 3000 );
228			}, 3000 );
229		} );
230	} )();
231
232	$( 'a[href^="http"]' ).click( function( e ) {
233		if ( undefined !== $( this ).attr( 'data-lity' ) ) {
234			return;
235		}
236
237		if ( $( this ).hasClass( 'js--interstitial-link' ) ) {
238			return;
239		}
240
241		var a = new RegExp( '/' + window.location.host + '/' );
242		if ( ! a.test( this.href ) ) {
243			if ( ! $( this ).hasClass( 'self-blank' ) ) {
244				e.preventDefault();
245				window.open( this.href );
246			}
247		}
248	} );
249
250	// Social sharing functions (single post).
251	window.copyFunction = function( textToCopy, button ) {
252		var textArea = document.createElement( 'textarea' );
253		textArea.value = textToCopy;
254		document.body.appendChild( textArea );
255		textArea.select();
256		document.execCommand( 'copy' );
257		document.body.removeChild( textArea );
258		button.innerHTML = 'Copied! <span><i class="fa-solid fa-check"></i></span>';
259		button.disabled = true;
260
261		setTimeout( function() {
262			button.innerHTML = 'Copy Link <span><i class="fa-solid fa-link"></i></span>';
263			button.disabled = false;
264		}, 2000 );
265	};
266
267	window.fbShare = function( url, winWidth, winHeight ) {
268		var winTop = ( screen.height / 2 ) - ( winHeight / 2 );
269		var winLeft = ( screen.width / 2 ) - ( winWidth / 2 );
270		window.open(
271			'https://www.facebook.com/sharer/sharer.php?u=' + encodeURIComponent( url ),
272			'share',
273			'height=' + winHeight + ',top=' + winTop + ',left=' + winLeft + ',width=' + winWidth + ',resizable=1'
274		);
275	};
276
277	window.tweetWindow = function( url, winWidth, winHeight ) {
278		var winTop = ( screen.height / 2 ) - ( winHeight / 2 );
279		var winLeft = ( screen.width / 2 ) - ( winWidth / 2 );
280		window.open(
281			'https://twitter.com/intent/tweet?text=' + encodeURIComponent( url ),
282			'tweet',
283			'height=' + winHeight + ',top=' + winTop + ',left=' + winLeft + ',width=' + winWidth + ',resizable=1'
284		);
285	};
286
287	window.linkedInWindow = function( url, winWidth, winHeight ) {
288		var winTop = ( screen.height / 2 ) - ( winHeight / 2 );
289		var winLeft = ( screen.width / 2 ) - ( winWidth / 2 );
290		window.open(
291			'https://www.linkedin.com/shareArticle?mini=true&url=' + encodeURIComponent( url ),
292			'share',
293			'height=' + winHeight + ',top=' + winTop + ',left=' + winLeft + ',width=' + winWidth + ',resizable=1'
294		);
295	};
296
297	// Archive AJAX news loading.
298	if ( $( '#filters' ).length ) {
299		var page = 1;
300
301		var loadNews = function( pageNum ) {
302			pageNum = pageNum || 1;
303			jQuery.ajax( {
304				type: 'POST',
305				url: _wpUtilSettings.ajax.url,
306				data: $.extend( {}, $( '#filters' ).serializeJSON(), {
307					page: pageNum,
308				} ),
309				success: function( res ) {
310					if ( 1 === pageNum ) {
311						$( '#ajaxnews' ).html( res );
312					} else {
313						$( '#ajaxnews' ).append( res );
314						if ( '' === res ) {
315							$( '#loadmore' ).hide();
316						}
317					}
318
319					$( '#loadmore' ).removeAttr( 'disabled' );
320					$( '#loadmore' ).html( 'Load More' );
321				},
322			} );
323		};
324
325		loadNews();
326
327		$( '#loadmore' ).click( function( e ) {
328			$( this ).attr( 'disabled', true );
329			$( this ).html( 'Load More...' );
330			e.preventDefault();
331			page++;
332			loadNews( page );
333		} );
334	}
335	
336	// Function to check if animations should be disabled
337    function shouldDisableAnimations() {
338        const isMobile = window.innerWidth <= 768;
339        const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
340        const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
341        
342        return isMobile || prefersReducedMotion || isMobileDevice;
343    }
344
345    // Function to check if element is in viewport (partial visibility is enough)
346    function isInViewport(element) {
347        const rect = element.getBoundingClientRect();
348        const windowHeight = window.innerHeight || document.documentElement.clientHeight;
349        
350        // Element is visible if any part is in viewport
351        return (
352            rect.top < windowHeight &&
353            rect.bottom > 0
354        );
355    }
356
357    // Initialize AOS with proper error handling
358    function initializeAOS() {
359        // Check if AOS is available
360        if (typeof AOS === 'undefined') {
361            console.warn('AOS library not loaded');
362            return;
363        }
364
365        // Set up staggered items (always get delays)
366        if (!shouldDisableAnimations()) {
367            document.querySelectorAll('.staggered-item').forEach((element, index) => {
368                element.setAttribute('data-aos-delay', index * 250);
369            });
370        }
371
372        // Handle elements that need conditional delays based on viewport
373        const delayedElements = document.querySelectorAll('.aos-delayed-animation[data-aos]');
374        
375        delayedElements.forEach((element) => {
376            const inView = isInViewport(element);
377            
378            if (inView) {
379                // IN VIEWPORT (portrait/first fold): Keep the staggered delay
380                // The delay should already be set, so we don't remove it
381                console.log('Element in view, keeping delay:', element.getAttribute('data-aos-delay'));
382            } else {
383                // NOT IN VIEWPORT (landscape/below fold): Remove delay for instant animation
384                console.log('Element below fold, removing delay from:', element.className);
385                element.setAttribute('data-aos-delay', '0');
386            }
387        });
388
389        // Initialize AOS
390        try {
391            AOS.init({
392                disable: shouldDisableAnimations(),
393                once: true,
394                duration: 1000,
395                offset: 50, // Start animation when element is 50px from viewport
396            });
397            
398            // Small delay to catch any late elements
399            setTimeout(() => {
400                AOS.refresh();
401            }, 100);
402            
403        } catch (error) {
404            console.error('AOS initialization error:', error);
405        }
406    }
407
408    // Initialize when DOM is ready (not waiting for all images to load)
409    if (document.readyState === 'loading') {
410        document.addEventListener('DOMContentLoaded', initializeAOS);
411    } else {
412        // DOM already loaded, init immediately
413        initializeAOS();
414    }
415
416
417	// Subnav — auto-build sidebar links from anchor headings.
418	( function() {
419		var navContainers = document.querySelectorAll( '.js--subnav' );
420		if ( ! navContainers.length ) {
421			return;
422		}
423
424		var headings = document.querySelectorAll( 'h2.heading-with-anchor' );
425
426		if ( headings.length ) {
427			navContainers.forEach( function( nav ) {
428				nav.classList.add( 'has-items' );
429			} );
430		}
431
432		headings.forEach( function( heading ) {
433			var li = document.createElement( 'li' );
434			var a = document.createElement( 'a' );
435			a.textContent = heading.textContent;
436			a.setAttribute( 'href', '#' + heading.id );
437
438			a.addEventListener( 'click', function( e ) {
439				e.preventDefault();
440				var target = document.getElementById( heading.id );
441				if ( target ) {
442					var headerHeight = document.querySelector( 'header' ) ? document.querySelector( 'header' ).getBoundingClientRect().height : 0;
443					var offset = target.getBoundingClientRect().top + window.pageYOffset - headerHeight - 40;
444					window.scrollTo( { top: offset, behavior: 'smooth' } );
445				}
446
447				// Update active link.
448				var prev = document.querySelector( '.subnav__selected-link' );
449				if ( prev ) {
450					prev.classList.remove( 'subnav__selected-link' );
451				}
452				a.classList.add( 'subnav__selected-link' );
453
454				// Update URL hash.
455				if ( history.pushState ) {
456					history.pushState( null, null, '#' + heading.id );
457				}
458			} );
459
460			li.appendChild( a );
461			navContainers.forEach( function( nav ) {
462				nav.appendChild( li.cloneNode( true ) );
463			} );
464		} );
465	} )();
466
467	const header = $('header');
468	var current = false; // false = not scrolled, true = scrolled
469
470	$(window).on('scroll', function () {
471		requestAnimationFrame(function () {
472			let s = $(window).scrollTop();
473// 			console.log('s', s);
474// 			console.log('current', current);
475			// switch ON only after >110px
476			if (!current && s > 50) {
477				header.addClass('scrolled');
478				current = true;
479			}
480
481			// switch OFF only after <90px
482			if (s === 0) {
483				header.removeClass('scrolled');
484				current = false;
485			}
486		});
487	});
488} );

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.