PageSourceSearch

https://www.cbinsights.com/wp-content/themes/marketing-theme/asset…/js/components/nav.js?x17484&ver=1790179913

js cbinsights.com collected 2026-09-24 08:24:16 UTC 9,891 bytes, 369 lines download raw bytes

1document.addEventListener("DOMContentLoaded", () => {
2	const mobileMenuToggle = document.querySelector("#mobile-menu-toggle");
3	const menuToggleIcon = document.querySelector("#menu-toggle-icon");
4	const mobileMenuItems = document.querySelectorAll(".mobile-menu-item");
5	const headerBannerMobile = document.querySelector(".header-banner--mobile");
6	const body = document.body;
7	const header = document.querySelector("header");
8	const mobileMenuInitialized =
9		mobileMenuToggle &&
10		mobileMenuToggle.getAttribute("data-initialized") === "true";
11
12	window.navScrollHandled = false;
13
14	const isIOS =
15		/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
16	const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
17	const isIOSSafari = isIOS && isSafari;
18
19	// Additional offset for iOS Safari
20	const iOSOffset = isIOSSafari ? 45 : 0;
21
22	// WordPress admin bar offset
23	const wpAdminBar = document.querySelector("#wpadminbar");
24	const wpAdminBarOffset = wpAdminBar && wpAdminBar.offsetHeight > 0 ? 32 : 0;
25
26	const isHomePage =
27		window.location.pathname === "/" ||
28		window.location.pathname === "/index.php" ||
29		window.location.pathname === "/index.html";
30	const searchParams = new URLSearchParams(window.location.search);
31	const targetHash = searchParams.get("target_hash");
32
33	let isHandlingScroll = false;
34
35	if (isHomePage) {
36		document.querySelectorAll(".nav-item").forEach((item) => {
37			item.classList.add("no-hover");
38
39			setTimeout(() => {
40				item.classList.remove("no-hover");
41			}, 500);
42		});
43
44		if (targetHash) {
45			setTimeout(() => {
46				const targetElement = document.getElementById(targetHash);
47				if (targetElement) {
48					smoothScrollToElement(targetElement, "#" + targetHash);
49				}
50			}, 100);
51		}
52	}
53
54	const closeAllSubitems = () => {
55		document
56			.querySelectorAll(".nav-dropdown.open")
57			.forEach((dropdown) => dropdown.classList.remove("open"));
58		document
59			.querySelectorAll(".mobile-menu-item svg.open")
60			.forEach((icon) => icon.classList.remove("open"));
61	};
62
63	const handleMobileMenuToggle = (event) => {
64		const mobileNavigation = document.querySelector(".mobile-navigation");
65		if (!mobileNavigation || !menuToggleIcon) return;
66
67		mobileNavigation.classList.toggle("open");
68		menuToggleIcon.classList.toggle("open");
69		body.classList.toggle("mobile-menu-is-open");
70
71		if (mobileNavigation.classList.contains("open")) {
72			if (header) header.style.top = "0";
73		} else {
74			handleScroll();
75		}
76
77		event.stopPropagation();
78		event.preventDefault();
79		closeAllSubitems();
80	};
81
82	const handleMobileMenuItemClick = (event) => {
83		const icon = event.currentTarget.querySelector("svg");
84		if (!icon) return;
85
86		const dropdown = event.currentTarget.nextElementSibling;
87
88		if (icon.classList.contains("open")) {
89			icon.classList.remove("open");
90			if (dropdown) {
91				dropdown.classList.remove("open");
92			}
93		} else {
94			closeAllSubitems();
95			icon.classList.add("open");
96			if (dropdown) {
97				dropdown.classList.add("open");
98			}
99		}
100
101		event.stopPropagation();
102	};
103
104	// Smooth scroll to element with fixed offset
105	const smoothScrollToElement = (element, hash, parentNavItem = null) => {
106		if (!element) return;
107
108		if (isHandlingScroll) return;
109		isHandlingScroll = true;
110
111		const isBusinessGraph = element.id === "business-graph";
112		const isDataItem =
113			element.classList.contains("data-item") ||
114			element.closest(".data-item");
115		const dataItem = isDataItem
116			? element.classList.contains("data-item")
117				? element
118				: element.closest(".data-item")
119			: null;
120		const isPrimaryResearch =
121			element.id === "primary-research" ||
122			element.closest("#primary-research");
123		const primaryResearchTitle = isPrimaryResearch
124			? element.closest("#primary-research")
125			: null;
126
127		if (isBusinessGraph) {
128			window.navScrollHandled = true;
129		}
130
131		document.body.offsetHeight;
132
133		requestAnimationFrame(() => {
134			const elementPosition =
135				element.getBoundingClientRect().top + window.scrollY;
136
137			let offset;
138			if (
139				(isDataItem && dataItem) ||
140				(isPrimaryResearch && primaryResearchTitle)
141			) {
142				offset = 140;
143			} else {
144				offset = 80 + iOSOffset;
145			}
146
147			const offsetPosition = elementPosition - offset;
148
149			window.scrollTo({
150				top: offsetPosition,
151				behavior: "smooth",
152			});
153
154			if (parentNavItem) {
155				parentNavItem.classList.add("no-hover");
156
157				setTimeout(() => {
158					parentNavItem.classList.remove("no-hover");
159				}, 1000);
160			}
161
162			setTimeout(() => {
163				const oldHash = window.location.hash;
164				history.replaceState(null, "", hash);
165
166				if (oldHash !== hash) {
167					if (isBusinessGraph) {
168						// Send info to business-graph.js
169						const customEvent = new CustomEvent(
170							"navHandledHashChange",
171							{
172								detail: {
173									hash: hash,
174									scrollHandled: true,
175								},
176							}
177						);
178						window.dispatchEvent(customEvent);
179						window.dispatchEvent(new HashChangeEvent("hashchange"));
180						setTimeout(() => {
181							window.navScrollHandled = false;
182						}, 1000);
183					} else {
184						window.dispatchEvent(new HashChangeEvent("hashchange"));
185					}
186				}
187
188				setTimeout(() => {
189					if (isIOSSafari) {
190						const newPosition =
191							element.getBoundingClientRect().top +
192							window.scrollY;
193						if (
194							Math.abs(newPosition - window.scrollY - offset) > 10
195						) {
196							window.scrollTo({
197								top: newPosition - offset,
198								behavior: "auto",
199							});
200						}
201					} else {
202						const newPosition =
203							element.getBoundingClientRect().top +
204							window.scrollY;
205						if (
206							Math.abs(newPosition - window.scrollY - offset) > 10
207						) {
208							window.scrollTo({
209								top: newPosition - offset,
210								behavior: "auto",
211							});
212						}
213					}
214					isHandlingScroll = false;
215				}, 300);
216			}, 500);
217		});
218	};
219
220	const resetScrollLock = () => {
221		isHandlingScroll = false;
222	};
223
224	// Handle all link clicks including jumplinks
225	const handleSubitemClick = (event) => {
226		const href = event.currentTarget.getAttribute("href");
227
228		if (!href) return;
229
230		const businessGraphPattern = /#business-graph(?:-(\d+))?$/;
231		const match = href.match(businessGraphPattern);
232		const isHashLink = href.startsWith("#") || href.includes("/#");
233
234		if (isHashLink) {
235			event.preventDefault();
236			const parentNavItem = event.currentTarget.closest(".nav-item");
237
238			let targetId;
239			if (match) {
240				targetId = "business-graph";
241			} else if (href.includes("/#")) {
242				targetId = href.split("/#")[1];
243			} else {
244				targetId = href.substring(1);
245			}
246
247			const targetElement = document.getElementById(targetId);
248
249			if (targetElement && isHomePage) {
250				if (document.querySelector(".mobile-navigation.open")) {
251					handleMobileMenuToggle({
252						preventDefault: () => {},
253						stopPropagation: () => {},
254					});
255				}
256
257				setTimeout(() => {
258					smoothScrollToElement(targetElement, href, parentNavItem);
259				}, 50);
260			} else if (href.includes("/#")) {
261				if (document.querySelector(".mobile-navigation.open")) {
262					handleMobileMenuToggle({
263						preventDefault: () => {},
264						stopPropagation: () => {},
265					});
266				}
267
268				window.location.href = href;
269			} else if (targetElement) {
270				if (document.querySelector(".mobile-navigation.open")) {
271					handleMobileMenuToggle({
272						preventDefault: () => {},
273						stopPropagation: () => {},
274					});
275				}
276
277				setTimeout(() => {
278					smoothScrollToElement(targetElement, href, parentNavItem);
279				}, 50);
280			}
281		}
282	};
283
284	const handleScroll = () => {
285		if (isHandlingScroll) return;
286		if (!header || !headerBannerMobile) return;
287
288		const bannerOffset = headerBannerMobile.offsetHeight;
289
290		if (isIOSSafari && window.scrollY > 0) {
291			header.classList.add("scrolled");
292			header.style.position = "fixed";
293			header.style.top = `${wpAdminBarOffset}px`;
294			return;
295		}
296
297		if (window.scrollY > bannerOffset) {
298			header.classList.add("scrolled");
299			header.style.top = `${wpAdminBarOffset}px`;
300		} else {
301			header.classList.remove("scrolled");
302			header.style.top = `${wpAdminBarOffset + bannerOffset - window.scrollY}px`;
303		}
304	};
305
306	const debounce = (func, wait) => {
307		let timeout;
308		return function executedFunction(...args) {
309			const later = () => {
310				clearTimeout(timeout);
311				func(...args);
312			};
313			clearTimeout(timeout);
314			timeout = setTimeout(later, wait);
315		};
316	};
317
318	if (window.location.hash) {
319		setTimeout(() => {
320			const businessGraphPattern = /#business-graph(?:-(\d+))?$/;
321			const match = window.location.hash.match(businessGraphPattern);
322
323			let targetId;
324			if (match) {
325				targetId = "business-graph";
326			} else {
327				targetId = window.location.hash.substring(1);
328			}
329
330			const targetElement = document.getElementById(targetId);
331			if (targetElement) {
332				smoothScrollToElement(targetElement, window.location.hash);
333			}
334		}, 300);
335	}
336
337	// Mobile menu toggle - only add if not already initialized
338	if (mobileMenuToggle && !mobileMenuInitialized) {
339		mobileMenuToggle.addEventListener("click", handleMobileMenuToggle);
340		mobileMenuToggle.setAttribute("data-initialized", "true");
341	}
342
343	// Mobile menu subitem toggle - only add if not already initialized
344	mobileMenuItems.forEach((menuItem) => {
345		if (!menuItem.hasAttribute("data-initialized")) {
346			menuItem.addEventListener("click", handleMobileMenuItemClick);
347			menuItem.setAttribute("data-initialized", "true");
348		}
349	});
350
351	// Add click event listeners to all nav-subitem elements
352	document.querySelectorAll(".nav-subitem").forEach((subitem) => {
353		subitem.addEventListener("click", handleSubitemClick);
354	});
355
356	// Show and hide nav based on scroll position
357	window.addEventListener("scroll", handleScroll, { passive: true });
358
359	// Adjust position on resize with debouncing
360	window.addEventListener("resize", debounce(handleScroll, 250));
361
362	// Listen for header banner close event
363	document.addEventListener("header_banner_closed", () => {
364		handleScroll();
365	});
366
367	// Just in case
368	setTimeout(resetScrollLock, 3000);
369});

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.