PageSourceSearch

https://georgioulawpllc.com/wp-content/themes/paperstreet/js/main.js?ver=3.26033

js georgioulawpllc.com collected 2026-10-04 06:34:32 UTC 20,935 bytes, 655 lines download raw bytes

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

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.