PageSourceSearch

https://westernnativevoice.org/wp-content/themes/zee/assets/js/app.js

js westernnativevoice.org collected 2026-09-26 00:10:52 UTC 13,010 bytes, 428 lines download raw bytes

1(function($){
2	/* Browser Detection
3	---------------------------------------------------------------------- */
4	// Detect mobile device
5	var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Nokia|Opera Mini/i.test(navigator.userAgent) ? true : false;
6
7	// Add class "is-mobile" to </body>
8	if(isMobile) {
9		$("body").addClass("is-mobile");
10	}
11	
12	/* Responsive Sizing Variables
13	---------------------------------------------------------------------- */
14	var win = $(window);
15	var headerHeight = $(".header-wrapper").outerHeight();
16	//var swiperHeight = win.height() - headerHeight;
17	var scroll_bar_width = getScrollbarWidth();
18	var scroll_bar_half_width = scroll_bar_width/2;
19
20	win.bind("load resize blur focus", function() {
21		headerHeight = $(".header-wrapper").outerHeight();
22		//swiperHeight = win.height() - headerHeight;
23		scroll_bar_width = getScrollbarWidth();
24		scroll_bar_half_width = scroll_bar_width/2;
25		
26		// This calculated CSS variable is needed to properly calculate the .alignfull class, taking into account the browser's scrollbar width
27		$("html").attr('body','--scroll-bar-half-width:' + scroll_bar_half_width + 'px');
28		$("body").css('--scroll-bar-half-width',scroll_bar_half_width + 'px');
29		
30		// Offset the hero content by the height of the header in order to center it within the area unobscured by the fixed header
31		$('.section-hero-content-container').css('padding-top', headerHeight);
32	});
33	
34	function getScrollbarWidth() {
35		var div = $('<div style="width:50px;height:50px;overflow:hidden;position:absolute;top:-200px;left:-200px;"><div style="height:100px;"></div>');
36		// Append our div, do our calculation and then remove it
37		$('body').append(div);
38		var w1 = $('div', div).innerWidth();
39		div.css('overflow-y', 'scroll');
40		var w2 = $('div', div).innerWidth();
41		$(div).remove();
42		return (w1 - w2);
43	}
44
45	/* Animations trigger on scroll
46	---------------------------------------------------------------------- */
47	$(function(){
48		new WOW().init(); 
49	});
50
51	/* Parallax Initialization
52	---------------------------------------------------------------------- */
53	$(function(){
54		var parallaxEL = document.getElementsByClassName('parallax-bg');
55		var parallax = new simpleParallax(parallaxEL, {
56			scale: 1.5,
57			orientation: 'down',
58		});
59	});
60
61	/* Initialize :: Magnific
62	---------------------------------------------------------------------- */
63	$(function(){
64		$('.popup-youtube, .popup-video, .popup-vimeo, .popup-gmaps').magnificPopup({
65			type: 'iframe',
66			mainClass: 'mfp-fade',
67			removalDelay: 160,
68			preloader: false,
69			fixedContentPos: false
70		});
71
72		$('.popup-media').magnificPopup({
73			type: 'image'
74		});
75	});
76
77	/* Functionality :: Sticky Header Animation
78	---------------------------------------------------------------------- */
79	$(function(){
80
81		// Add a class of 'is-stuck' to the sticky header when it is pinned for styling purposes
82		var observer = new IntersectionObserver(function(entries) {
83			if(entries[0].intersectionRatio === 0){
84				document.querySelector("#header-wrapper").classList.add("is-stuck");
85
86				var headerHeightScrolled = $(".header-wrapper").outerHeight();
87
88				// Add an offset for sticky elements based on the height of the scrolled header
89				$(".sticky-top:not(.header-wrapper)").css("top", headerHeightScrolled + 15);
90
91				// Add an offset for scroll target elements based on the height of the scrolled header, plus an additional 15px of padding
92				$(':target').css('scroll-margin-top', headerHeightScrolled + 15);
93
94			} else if(entries[0].intersectionRatio === 1){
95				document.querySelector("#header-wrapper").classList.remove("is-stuck");
96			}
97		}, { threshold: [0,1] });
98
99		observer.observe(document.querySelector("#header-wrapper-spacer"));
100
101	});
102
103	/* Initialize :: Swipers
104	---------------------------------------------------------------------- */
105	$(function(){	
106
107		// Check for the existence of more than one slide before initializing.
108		if ($(".swiper-home").length && ($(".swiper-home .swiper-wrapper .swiper-slide").length > 1) ) {
109			var swiperHome = new Swiper(".swiper-home .swiper-container", {
110				grabCursor: true,
111				effect: 'fade',
112				fadeEffect: {
113					crossFade: true
114				},
115				speed: 1000,
116				spaceBetween: 0,
117				centeredSlides: true,
118				slidesPerView: 1,
119				slideToClickedSlide: true,
120				watchOverflow: true,
121				loop: true,
122				autoplay: {
123					delay: 7000,
124				},
125				pagination: {
126					el: ".swiper-pagination",
127				},
128				on: {
129					init: function () {
130
131						var $this = this;
132						var $slideActive = $($this.slides[$this.activeIndex]);
133
134						// Play video on load
135						if(!isMobile) {
136							$slideActive.find("video").each(function() {
137								$(this).get(0).play();
138							}); 
139						}
140					},
141
142					transitionStart: function () {
143
144						var $this = this;
145						var $slideActive = $($this.slides[$this.activeIndex]);
146
147						// Play video
148						//if(!isMobile) {
149							$slideActive.find("video").each(function() {
150								$(this).get(0).play();
151							});
152						//}
153
154					},
155
156					transitionEnd: function () {
157						var $this = this;
158						var $slideActive = $($this.slides[$this.activeIndex]);
159
160						// Pause video
161						$slideActive.prevAll().find("video").each(function() {
162							$(this).get(0).pause();
163						});
164						$slideActive.nextAll().find("video").each(function() {
165							$(this).get(0).pause();
166						});
167					}
168				}
169			});
170		} else {
171			$(".swiper-home .swiper-button-next").hide();
172			$(".swiper-home .swiper-button-prev").hide();
173		}
174
175		if ($(".swiper-testimonials").length) {
176			
177			$( ".swiper-testimonials .testimonial-block" ).wrap( "<div class='swiper-slide py-4 px-5'></div>" );
178			
179			var swiperTestimonials = new Swiper(".swiper-testimonials .swiper-container", {
180				effect: "slide",
181				grabCursor: true,
182				spaceBetween: 0,
183				centeredSlides: true,
184				slidesPerView: 1,
185				slideToClickedSlide: true,
186
187				loop: true,
188				autoplay: {
189					delay: 7000,
190				},
191				navigation: {
192					nextEl: ".swiper-testimonials .swiper-button-next",
193					prevEl: ".swiper-testimonials .swiper-button-prev",
194				}
195			});
196		}
197		
198		// Swiper :: Posts
199		if ($(".swiper-posts").length) {
200			var swiperPosts = new Swiper(".swiper-posts .swiper-container", {
201				grabCursor: true,
202				spaceBetween: 0,
203				slidesPerView: 1,
204				centeredSlides: false,
205				slideToClickedSlide: true,
206				loop: false,
207				watchOverflow: true,
208				breakpoints: {
209					575: {
210						slidesPerView: 2,
211						centeredSlides: false,
212					},
213					992: {
214						slidesPerView: 3,
215						centeredSlides: false,
216					}
217				},
218				navigation: {
219					nextEl: ".swiper-posts .swiper-button-next",
220					prevEl: ".swiper-posts .swiper-button-prev",
221				}
222			});
223		}
224		
225	});
226	
227/* Countdown Timers
228---------------------------------------------------------------------- */
229
230class Countdown {
231	constructor(el) {
232		this.el = el;
233
234		// Standard attribute on <time>
235		const dt = el.getAttribute("datetime");
236		this.targetDate = new Date(dt);
237
238		// Guard: if invalid date, bail (optional but helpful)
239		if (Number.isNaN(this.targetDate.getTime())) {
240			// eslint-disable-next-line no-console
241			console.warn("Countdown: invalid datetime attribute:", dt, el);
242			return;
243		}
244
245		this.createCountDownParts();
246		this.countdownFunction();
247
248		this.countdownLoopId = setInterval(
249			this.countdownFunction.bind(this),
250			1000
251		);
252	}
253
254	createCountDownParts() {
255		["days", "hours", "minutes", "seconds"].forEach((part) => {
256			const partEl = document.createElement("div");
257			partEl.classList.add("countdown-part", part);
258
259			const partInnerEl = document.createElement("div");
260			partInnerEl.classList.add("countdown-part-inner");
261
262			const labelEl = document.createElement("span");
263			labelEl.classList.add("countdown-label");
264			labelEl.innerText = part;
265
266			const numberEl = document.createElement("div");
267			numberEl.classList.add("countdown-number", "h2", "mb-0");
268			numberEl.innerText = "0";
269
270			partInnerEl.append(numberEl, labelEl);
271			partEl.append(partInnerEl);
272
273			this.el.append(partEl);
274
275			// Store the INNER wrapper so CSS vars work and we can update number text easily
276			this[part] = partInnerEl;
277		});
278	}
279
280	countdownFunction() {
281		const currentDate = new Date();
282
283		if (currentDate >= this.targetDate) {
284			clearInterval(this.countdownLoopId);
285			// Optionally force zeros at the end:
286			["days", "hours", "minutes", "seconds"].forEach((part) => {
287				this[part].style.setProperty("--value", 0);
288				this[part].children[0].innerText = "0";
289			});
290			return;
291		}
292
293		const remaining = this.getRemaining(this.targetDate, currentDate);
294
295		Object.entries(remaining).forEach(([part, value]) => {
296			this[part].style.setProperty("--value", value);
297			this[part].children[0].innerText = String(value);
298		});
299	}
300
301	getRemaining(target, now) {
302		let seconds = Math.floor((target - now) / 1000);
303		let minutes = Math.floor(seconds / 60);
304		let hours = Math.floor(minutes / 60);
305		let days = Math.floor(hours / 24);
306
307		hours = hours - days * 24;
308		minutes = minutes - (days * 24 * 60) - (hours * 60);
309		seconds = seconds - (days * 24 * 60 * 60) - (hours * 60 * 60) - (minutes * 60);
310
311		return { days, hours, minutes, seconds };
312	}
313}
314
315// Initialize once DOM is ready (covers cases where script is in <head>)
316document.addEventListener("DOMContentLoaded", () => {
317	const countdownEls = document.querySelectorAll(".countdown");
318	countdownEls.forEach((countdownEl) => new Countdown(countdownEl));
319});
320
321	
322	
323	/* Functionality :: Layering utility class
324	---------------------------------------------------------------------- */
325	function layerUp(){
326		var layerHeight;
327		
328		$('.layer-up').each(function(i, obj) {
329
330			layerHeight = $(this).outerHeight() / 2;
331
332			$(this).prev().css("padding-bottom", layerHeight);
333			$(this).children(".layer-up-content").css("margin-top", -layerHeight);
334
335		});
336	}
337	$(window).resize(function() {
338		layerUp();
339	});
340
341	$(document).ready(function() {
342		layerUp();
343	});
344	
345	/* Desktop Search Functionality
346	---------------------------------------------------------------------- */
347	$(document).ready(function() {
348		"use strict";
349
350		// Remove Search if user Resets Form or hits Escape
351		$('.navbar-top-collapse form[role="search"] button[type="reset"]').on('click keyup', function(event) {
352			if (event.which == 27 && $('.navbar-top-collapse form[role="search"]').hasClass('active') || $(event.currentTarget).attr('type') == 'reset') {
353				closeSearch();
354			}
355		});
356
357		function closeSearch() {
358			var $form = $('.navbar-top-collapse form[role="search"].active')
359			$form.find('input').val('');
360			$form.removeClass('active');
361		}
362
363		// Show Search if form is not active // event.preventDefault() is important, this prevents the form from submitting
364		$(document).on('click', '.navbar-top-collapse form[role="search"]:not(.active) button[type="submit"]', function(event) {
365			event.preventDefault();
366			var $form = $(this).closest('form'),
367				$input = $form.find('input');
368			$form.addClass('active');
369			$input.focus();
370		});
371
372		$('a[href="#header-search-desktop"]').on('click', function(event) {
373			event.preventDefault();
374			$('#header-search-desktop').addClass('open');
375			$('#header-search-desktop > form > input[type="search"]').focus();
376		});
377
378		$('#header-search-desktop, #header-search-desktop button.close').on('click keyup', function(event) {
379			if (event.target == this || event.target.classList.contains('fa-times') || event.keyCode == 27) {
380				$(this).removeClass('open');
381			}
382		});
383	});
384
385	/* Bootstrap Fix :: Always append all modals to the end of the HTML body to take care of any z-indexing issues
386	---------------------------------------------------------------------- */
387	$(document).ready(function($){
388		$('.modal-dialog').parent().on('show.bs.modal', function(e){ $(e.relatedTarget.attributes['data-bs-target'].value).appendTo('body'); });
389	});
390
391	/* Polyfill :: Object-fit CSS IE 11 Fallback
392	---------------------------------------------------------------------- */
393	// Detect objectFit support
394	if('objectFit' in document.documentElement.style === false) {
395		
396		// assign HTMLCollection with parents of images with objectFit to variable
397		var container = document.getElementsByClassName('tab-pane-image');
398		
399		// Loop through HTMLCollection
400		for(var i = 0; i < container.length; i++) {
401			// Asign image source to variable
402			var imageSource = container[i].querySelector('img').src;
403
404			// Hide image
405			container[i].querySelector('img').style.display = 'none';
406
407			// Add background-size: cover
408			container[i].style.backgroundSize = 'cover';
409
410			// Add background-image: and put image source here
411			container[i].style.backgroundImage = 'url(' + imageSource + ')';
412
413			// Add background-position: center center
414			container[i].style.backgroundPosition = 'center center';
415		}
416	}
417
418	/* Initialize :: Mobile Navigation
419	---------------------------------------------------------------------- */
420	$(function(){
421		"use strict";
422		jQuery("#menu-toggle").click(function(e) {
423			e.preventDefault();
424			jQuery("#site-wrapper").toggleClass("toggled");
425		});
426	});
427	
428})(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.