PageSourceSearch

https://www.cumminsandwhite.com/wp-content/themes/paperstreet/js/main.js?ver=3.26041

js cumminsandwhite.com collected 2026-10-07 03:19:14 UTC 20,650 bytes, 637 lines download raw bytes

1// Wrap all custom JS in an anonymous function that passes the alias "$" to everything so that "jQuery" doesn't have to be used
2(function ($) {
3	// Global variables
4	var megaMenuTarget = $(".menu-item-360"); // Where the mega menu is placed - remove if no mega menu
5	var enableLazyImageFade = $("body").hasClass("lazy-images-fade-in");
6
7	// Begin JavaScript/jQuery setup
8	// The "on ready"
9	$(function () {
10		checkScroll();
11		setupYouTube();
12		setHeaderHeight();
13
14		// Prepare links within the body content with the scroll-to class if they point to internal ID anchors
15		$('.content a[href^="#"]').each(function () {
16			// Add class 'scroll-to' to the link
17			$(this).addClass("scroll-to");
18
19			// Extract the anchor target and set it as a data attribute, minus the hashtag
20			var anchorTarget = $(this).attr("href").substring(1);
21			$(this).attr("data-scroll-to-id", anchorTarget);
22		});
23
24		// Reposition Gravity Forms labels so the HTML structure works with label CSS
25		$(".gfield").each(function () {
26			var label = $(this).find("label.gfield_label");
27			var formControl = $(this).find(".ginput_container").children().first();
28			label.detach().insertAfter(formControl);
29		});
30
31		// Video gallery category filter
32		if ($("#video-category-filter").exists()) {
33			$("#video-category-filter").on("change", function () {
34				var selectedCategory = $(this).val();
35
36				$(".videos-single").each(function () {
37					var categories = $(this).data("category").split(" ");
38					if (selectedCategory == "" || categories.includes(selectedCategory)) {
39						$(this).show();
40					} else {
41						$(this).hide();
42					}
43				});
44
45				animateScroll("videos-container");
46			});
47		}
48
49		// Prevent empty searches on forms with the "prevent-blank-search" class
50		document.querySelectorAll(".prevent-blank-search").forEach(function (element) {
51			element.addEventListener(
52				"submit",
53				function (e) {
54					var value = $(this).find("input").val().trim();
55
56					// Test for whitespace values
57					if (!value || !/\S/.test(value)) {
58						alert("Please enter a search term.");
59
60						e.preventDefault();
61					}
62				},
63				{ passive: false },
64			);
65		});
66
67		// Initialize mega menu (if needed)
68		if (megaMenuTarget && isDesktopScreen()) {
69			$(megaMenuTarget).addClass("static");
70			$("#mega-menu").detach().appendTo(megaMenuTarget);
71		}
72
73		// Scroll to ID from a select dropdown. Useful on mobile
74		$(".scroll-to-select").on("change", function (e) {
75			// Get selected option then scroll to it
76			var selectedOption = $(this).find(":selected").val();
77			animateScroll($(selectedOption).offset().top);
78		});
79
80		// Copies content from a selected area to a target; used only on some modules
81		$(".content-selector").on("click", function () {
82			$(this).toggleClass("active");
83			$(this).siblings().removeActive();
84
85			$("#content-selector-target").removeClass("animated fast fadeIn");
86			$("#content-selector-target").html($(this).html());
87
88			setTimeout(function () {
89				$("#content-selector-target").addClass("animated fast fadeIn");
90			}, 200);
91		});
92
93		// Accessibility: Ensures main navigation dropdowns remain open for keyboard focus
94		$(".main-navigation-menu li, .mega-menu li")
95			.on("focusin mouseover", function () {
96				$(this).addActive();
97				$(this).siblings().removeActive();
98			})
99			.on("mouseleave", function () {
100				$(this).removeActive();
101			});
102
103		// Blog Grid - Masonry and move pagination
104		if ($("#blog-grid").exists()) {
105			if (isDesktopScreen()) {
106				// Initializes Masonry layout; see: https://masonry.desandro.com/options.html
107				$("#blog-grid-inner").masonry({
108					fitWidth: true,
109					gutter: 30, // Horizontal space between layout items
110					itemSelector: ".post", // Target specific class to avoid other elements being repositioned
111				});
112			}
113
114			// Move pagination to final position, outside of grid
115			$(".blog-grid .wp-pagenavi").detach().appendTo("#blog-grid-pagination");
116		}
117
118		// LAZY LOADING WITH LOZAD
119		// Use class "lazy-img" and attribute "data-img-src" to lazy load an <img> and "data-animation-class" to animate
120		// To randomly pick an image from a set, use attribute "data-img-srcs" like so: data-img-srcs="imageurl.
120jpg, imageurl.jpg"
121		var imageLazyLoader = lozad(".lazy-img", {
122			rootMargin: "10px 0px",
123			threshold: 0.1,
124			load: function (elementInView) {
125				if ($(elementInView).attr("data-img-srcs")) {
126					var imagesList = $(elementInView).attr("data-img-srcs").split(",");
127					var imageToLoad = imagesList[Math.floor(Math.random() * imagesList.length)];
128				} else {
129					var imageToLoad = $(elementInView).attr("data-img-src");
130				}
131
132				// Trigger fade-in after image loads
133				if (enableLazyImageFade) {
134					$(elementInView).one("load", function () {
135						$(this).addClass("lazy-fade-in-loaded");
136					});
137
138					$(elementInView).attr("src", imageToLoad);
139
140					// Fallback for cached images
141					if (elementInView.complete && elementInView.naturalHeight !== 0) {
142						$(elementInView).addClass("lazy-fade-in-loaded");
143					}
144				} else {
145					$(elementInView).attr("src", imageToLoad);
146				}
147
148				// Optionally, add animation classes at the same time
149				if ($(elementInView).attr("data-animation-class")) {
150					var animateClass = "animated " + $(elementInView).attr("data-animation-class");
151					$(elementInView).addClass(animateClass);
152				}
153			},
154		});
155		imageLazyLoader.observe();
156
157		// Use class "lazy-bg" and attribute "data-bg-src" on any element using a CSS background image lazy load
158		// To randomly pick background image from a set, use attribute "data-bg-srcs" like so: data-bg-srcs="imageurl.jpg, imageurl.jpg"
159		var backgroundImageLazyLoader = lozad(".lazy-bg", {
160			rootMargin: "10px 0px",
161			threshold: 0.1,
162			load: function (elementInView) {
163				if ($(elementInView).attr("data-bg-srcs")) {
164					var imagesList = $(elementInView).attr("data-bg-srcs").split(",");
165					var imageToLoad = imagesList[Math.floor(Math.random() * imagesList.length)];
166					console.log("Lazy loading a randomly-selected background image: " + imageToLoad);
167				} else {
168					var imageToLoad = $(elementInView).attr("data-bg-src");
169					console.log("Lazy loading a background image: " + imageToLoad);
170				}
171
172				$(elementInView).css("background-image", "url(" + imageToLoad + ")");
173			},
174		});
175		backgroundImageLazyLoader.observe();
176
177		// Add an active class to an element when it enters view
178		// Optionally, add a "data-delay" attribute equal to milliseconds, e.g. 500 for 0.5 seconds
179		var activeInViewTrigger = lozad(".active-in-view", {
180			rootMargin: "10px 0px",
181			threshold: 0.1,
182			load: function (elementInView) {
183				var delayActive = 0;
184
185				if ($(elementInView).attr("data-delay")) {
186					delayActive = $(elementInView).attr("data-delay");
187				}
188
189				setTimeout(function () {
190					$(elementInView).addActive();
191					$(elementInView).removeClass("active-in-view");
192				}, delayActive);
193			},
194		});
195		activeInViewTrigger.observe();
196
197		// Trigger animate in view with a specific animation class added
198		// TO USE: Add class "animate-in-view" to an element plus a data attribute "data-animation-class" equal to an animation class name
199		var specificAnimateInViewTrigger = lozad(".animate-in-view", {
200			rootMargin: "10px 0px",
201			threshold: 0.1,
202			load: function (elementInView) {
203				var animToUse = $(elementInView).attr("data-animation-class");
204				$(elementInView).addClass("animated " + animToUse);
205			},
206		});
207		specificAnimateInViewTrigger.observe();
208
209		// Lazy load Google Map
210		// Requires data attribute "data-embed-map-link" to be set
211		var mapLoadInViewTrigger = lozad(".load-map-in-view", {
212			rootMargin: "10px 0px",
213			threshold: 0.1,
214			load: function (elementInView) {
215				var embedMapWidth = $(elementInView).attr("data-embed-map-width")
216					? $(elementInView).attr("data-embed-map-width")
217					: 600;
218				var embedMapHeight = $(elementInView).attr("data-embed-map-height")
219					? $(elementInView).attr("data-embed-map-height")
220					: 450;
221
222				// Add live <iframe> containing Google Map once the element is in view
223				$(elementInView).append(
224					'<iframe src="' +
225						$(elementInView).attr("data-embed-map-link") +
226						'" width="' +
227						embedMapWidth +
228						'" height="' +
229						embedMapHeight +
230						'" frameborder="0" style="border:0;" allowfullscreen="" aria-hidden="false" tabindex="0"></iframe>',
231				);
232			},
233		});
234		mapLoadInViewTrigger.observe();
235
236		// On page print, download images
237		window.onbeforeprint = function () {
238			$(".lazy-img").each(function () {
239				var src = $(this).attr("data-img-src");
240				$(this).attr("src", src);
241			});
242
243			$(".lazy-bg").each(function () {
244				var src = $(this).attr("data-bg-src");
245				$(this).css("background-image", src);
246			});
247		};
248
249		// END LAZY LOADING WITH LOZAD
250	});
251
252	// Window *Load = Entire page including the DOM is loaded
253	$(window).on("load", function () {
254		prepareLinks();
255
256		// On page load, check inputs for text to add active
257		$("input, textarea").each(function () {
258			activateInputLabel($(this));
259		});
260
261		// INPUT ACTIVE CLASS CONTROL
262		// On text inputs, add class when the field holds a value
263		$("input, textarea").on("input change", function () {
264			activateInputLabel($(this));
265		});
266
267		// Detects text inside inputs to activate or deactivate label styling
268		function activateInputLabel(thisInput) {
269			// Add validation to ensure we have a valid jQuery object
270			if (!thisInput || !thisInput.length) {
271				return;
272			}
273
274			var thisInputValue = thisInput.val();
275
276			if (thisInputValue && thisInput.isActive() == false) {
277				thisInput.addActive();
278			} else if (thisInputValue == "") {
279				thisInput.removeActive();
280			}
281		}
282
283		// On dropdowns, add class when <select> is used
284		$("select").on("change", function () {
285			$(this).addActive();
286		});
287
288		$("select").on("focusin focusout", function () {
289			$(this).closest(".select-wrapper").toggleClass("active");
290		});
291		// END INPUT ACTIVE CLASS CONTROL
292
293		// ATTORNEY SEARCH HELPERS
294		// Prevent empty-value form inputs from sending data
295		$("#attorney-search-form").submit(function () {
296			$('input[value=""]:not(.active)').attr("disabled", true);
297			$('option[value=""]').attr("disabled", true);
298		});
299
300		// If attorney search results are present, scroll to the section
301		if ($("#attorney-search-bar").exists()) {
302			animateScroll("attorney-search-bar", 300);
303			$("#attorney-search-inner").attr("tabindex", "0");
304			$("#attorney-search-inner").focus();
305		}
306		// END ATTORNEY SEARCH HELPERS
307
308		// TABLE OF CONTENTS
309		// Scan target for h2s and populate widget with scroll links
310		$(".toc-widget").each(function () {
311			var $widget = $(this);
312			var $list = $widget.find(".toc-list");
313			var $select = $widget.find(".toc-select");
314			var target = $widget.data("toc-target") || ".content";
315			var headings = $(target).find("h2").not(".toc-widget h2");
316
317			if (headings.length < 2) {
318				var $sidebarTOC = $widget.closest("#sidebar-toc");
319				if ($sidebarTOC.length) {
320					$sidebarTOC.remove();
321				} else {
322					$widget.remove();
323				}
324				return;
325			}
326
327			headings.each(function () {
328				var thisHeadingText = $(this).text().trim();
329
330				if (thisHeadingText === "") {
331					return;
332				}
333
334				var thisHeadingID =
335					"jump-" +
336					thisHeadingText
337						.toLowerCase()
338						.replace(/ /g, "-")
339						.replace(/[^\w-]+/g, "");
340
341				$(this).attr("id", thisHeadingID);
342
343				var titleCaseText = thisHeadingText.replace(/\w+/g, function (word) {
344					if (word === word.toUpperCase() && word.length > 1) return word;
345					return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase();
346				});
347
348				$list.append(
349					'<li><button class="scroll-to no-button" data-scroll-to-id="' +
350						thisHeadingID +
351						'">' +
352						titleCaseText +
353						"</button></li>",
354				);
355				$select.append('<option value="#' + thisHeadingID + '">' + titleCaseText + "</option>");
356			});
357		});
358		// END TABLE OF CONTENTS
359
360		// CONTENT TAB BUTTONS
361		// Used on attorney bios
362		$(".content-tab-activate").on("click", function () {
363			$(".content-tab-activate, .content-tab").removeActive();
364			$("[data-content-tab='" + $(this).attr("data-content-tab") + "']").addActive();
365			$(this).addActive();
366		});
367
368		// Check URL parameters for tab activation on page load
369		var tabParam = getURLParameter("tab");
370
371		if (tabParam) {
372			// Check if a tab with this parameter exists
373			if ($("[data-content-tab='" + tabParam + "']").length) {
374				// Remove active class from all tabs and content
375				$(".content-tab-activate, .content-tab").removeActive();
376				// Activate the requested tab and its content
377				$("[data-content-tab='" + tabParam + "']").addActive();
378
379				// Also update the mobile select dropdown if it exists
380				if ($("#select-content-skip").length) {
381					$("#select-content-skip").val(tabParam);
382				}
383			}
384		}
385
386		$(".mobile-tab").on("click", function () {
387			animateScroll("main");
388		});
389		// END CONTENT TAB BUTTONS
390
391		// ACCORDION CONTROL
392		// Toggle accordion open and closed
393		if ($(".accordion-item").length) {
394			// If there are accordions on this page, set their ARIA properties
395			$(".accordion-item-title").attr("aria-expanded", "false");
396
397			// Event listener for accordion items
398			$(".accordion-item-title").on("click", function () {
399				var parentAccordionItem = $(this).closest(".accordion-item");
400
401				// Check data attribute values on the clicked accordion
402				var closeSiblings = $(this).data("close-siblings") !== false;
403
404				// Close sibling accordions, if the data attribute allows it
405				if (closeSiblings) {
406					parentAccordionItem.siblings().removeClass("active");
407					parentAccordionItem
408						.siblings()
409						.find(".accordion-item-title")
410						.attr("aria-expanded", "false");
411				}
412
413				if (!parentAccordionItem.hasClass("active")) {
414					// Open the clicked accordion
415					parentAccordionItem.addClass("active");
416					$(this).attr("aria-expanded", "true");
417				} else {
418					// Close the clicked accordion
419					parentAccordionItem.removeClass("active");
420					$(this).attr("aria-expanded", "false");
421				}
422			});
423		}
424
425		// Open first accordion on load
426		if ($(".open-first").exists()) {
427			$(".open-first").each(function () {
428				$(this)
429					.find(".accordion-item:first-of-type .accordion-item-title")
430					.addClass("active")
431					.attr("aria-expanded", true);
432			});
433		}
434		// END ACCORDION CONTROL
435	});
436
437	///////////////////////////////////
438	// Scrolls and On-Screen Reveals //
439	///////////////////////////////////
440
441	// Gradually scrolls window to element on click of any element with the "scroll-to" class
442	$("body").on("click", ".scroll-to", function (e) {
443		e.preventDefault();
444		var elementToScrollToID = $(this).attr("data-scroll-to-id");
445
446		if (!elementToScrollToID) {
447			return;
448		}
449
450		animateScroll(elementToScrollToID);
451
452		if (elementToScrollToID == "main") {
453			// After scrolling completes, temporarily add tabindex and focus #main
454			$(elementToScrollToID).attr("tabindex", "-1").focus().removeAttr("tabindex");
455		}
456	});
457
458	// Scrolls window back to top of page
459	$(".scroll-to-top").on("click", function (e) {
460		e.preventDefault();
461		animateScroll(0);
462	});
463
464	// Recurring events on scroll
465	$(window).on("scroll", function () {
466		// Control frequency of scroll checks for performance
467		let scrollDebounceTimer;
468		clearTimeout(scrollDebounceTimer);
469		scrollDebounceTimer = setTimeout(checkScroll, 5);
470	});
471
472	function checkScroll() {
473		// Add class to body when scrolled beyond top of page
474		if ($(window).scrollTop() > 0) {
475			$("body").addClass("scrolled");
476		} else {
477			$("body").removeClass("scrolled");
478		}
479
480		// If scrolled to bottom, activate slideout feature in footer
481		if ($("#bottom-slideout").exists()) {
482			// Detect page bottom
483			if (getScrollPercent() > 0.98) {
484				$("#bottom-slideout").addActive();
485			} else {
486				$("#bottom-slideout").removeActive();
487			}
488		}
489
490		// Check elements that are revealed on scroll
491		$(".scroll-reveal").each(function () {
492			// If element is on screen and not yet revealed, add classes
493			var cur = $(this);
494			if (isScrolledIntoView(cur)) {
495				if (!cur.hasClass("animated")) {
496					cur.addClass("animated fadeInUp");
497				}
498			}
499		});
500	}
501
502	/////////////////////
503	//  YouTube Embeds //
504	/////////////////////
505
506	function setupYouTube() {
507		// Prepare YouTube video placeholders that turn into iframe on click
508		if ($(".youtube-player").length == 0) {
509			return;
510		}
511
512		var div;
513		var n;
514		var v = document.getElementsByClassName("youtube-player");
515
516		for (n = 0; n < v.length; n++) {
517			div = document.createElement("div");
518			div.setAttribute("data-id", v[n].dataset.id);
519			div.setAttribute("data-list", v[n].dataset.list);
520			div.innerHTML =
521				'<img src="https://img.youtube.com/vi/' + v[n].dataset.id + '/hqdefault.jpg" alt="">';
522
523			// Add attributes and classes to the div to make interactive
524			$(div).addClass("youtube-player-inner").attr("tabindex", "0");
525			$(div).addClass("youtube-player-inner").attr("aria-label", "Play video");
526			$(div).on("click keypress", getYouTubeFrame);
527
528			// Add div to this player
529			v[n].appendChild(div);
530		}
531	}
532
533	function getYouTubeFrame() {
534		var iframe = document.createElement("iframe");
535		var embed = "https://www.youtube.com/embed/ID?autoplay=1";
536
537		iframe.setAttribute("src", embed.replace("ID", this.dataset.id));
538		iframe.setAttribute("frameborder", "0");
539		iframe.setAttribute(
540			"allow",
541			"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
542		);
543		iframe.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
544		iframe.setAttribute("allowfullscreen", "");
545		iframe.setAttribute("title", "YouTube video player");
546
547		// Clear the parent container and append iframe
548		var container = this.parentNode;
549		container.innerHTML = "";
550		container.appendChild(iframe);
551	}
552
553	////////////
554	//  Misc. //
555	////////////
556
557	// Returns true if on homepage
558	// Requires WordPress body classes to be in effect
559	function isHomepage() {
560		if (jQuery("body.home").length) {
561			return true;
562		}
563		return false;
564	}
565
566	// Returns true no homepage class is present
567	function isSubpage() {
568		if (jQuery("body.home").length) {
569			return false;
570		}
571		return true;
572	}
573
574	// Returns true if element is scrolled into view
575	// Usage: isScrolledIntoView(element)
576	function isScrolledIntoView(elem) {
577		let docViewTop = $(window).scrollTop();
578		let docViewBottom = docViewTop + $(window).height();
579		let elemTop = $(elem).offset().top;
580		let elemBottom = elemTop + $(elem).outerHeight();
581
582		return elemTop <= docViewBottom && elemBottom >= docViewTop;
583	}
584
585	// Prepare all links with appropriate attributes and classes
586	function prepareLinks() {
587		// External links: Add attributes for external links
588		$("a")
589			.filter(function () {
590				return this.hostname && this.hostname !== location.hostname;
591			})
592			.attr("target", "_blank")
593			.attr("rel", "noopener")
594			.addClass("external-link");
595
596		// PDF links: Open PDFs in new tab
597		$('a[href$=".pdf"]').each(function () {
598			$(this).attr("target", "_blank");
599		});
600
601		// Accessibility: Alert the user that a link will open in a new tab
602		$('a[target="_blank"]').each(function () {
603			// Check if the link already has an aria-label
604			var ariaLabel = $(this).attr("aria-label");
605
606			if (ariaLabel) {
607				// If the link already has an aria-label, append the new text
608				$(this).attr("aria-label", ariaLabel + " (Opens in a new tab)");
609			} else {
610				// If no aria-label exists, add a new one with the "Opens in a new tab" text
611				$(this).attr("aria-label", "Opens in a new tab");
612			}
613		});
614
615		// Identify telephone links
616		$('a[href^="tel:"]').each(function () {
617			$(this).addClass("tel-link");
618		});
619	}
620
621	// Recurring events on resize
622	window.addEventListener("resize", function () {
623		setHeaderHeight();
624	});
625
626	// Update CSS value equal to header height
627	function setHeaderHeight() {
628		const headerID = isMobileScreen() ? "header-mobile" : "header-desktop";
629		var header = document.getElementById(headerID);
630
631		if (header) {
632			const headerHeight = header.offsetHeight;
633			document.documentElement.style.setProperty("--header-height", `${headerHeight}px`);
634		}
635	}
636
637})(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.