PageSourceSearch

https://mikemichalowicz.com/wp-content/themes/mike/js/header-sticky.js?ver=1790263693

js mikemichalowicz.com collected 2026-09-24 17:09:26 UTC 14,533 bytes, 517 lines download raw bytes

1/* ================================================================
2   G3 Carousel (Used in sticky nav)
3   ================================================================ */
4//Carousel
5function g3_carousel(ele) {
6	const $car_container = ele;
7	const $car = $car_container.querySelector('.nav-carousel');
8	const $carousel_items = $car_container.querySelectorAll('.nav-carousel__slide');
9	const $carousel_items_rev = Array.prototype.slice.call($car_container.querySelectorAll('.nav-carousel__slide')).reverse();
10	const $arrow_r = $car.querySelector('.nav-carousel__arrow--r');
11	const $arrow_l = $car.querySelector('.nav-carousel__arrow--l');
12	const $carousel = $car_container.querySelector('.nav-carousel__slider');
13	const $toc_container = $car_container.querySelector('.nav-carousel__toc');
14	let toc_exists = true;
15	let position = 0;
16	let cur_slide = 1;
17	const slide_qty = $carousel_items.length;
18	const speed = 0.5;
19	const auto_speed = 3;
20	const initial_on = 1; // use to offset first slide on load
21	if ($toc_container == null) {
22		toc_exists = false;
23	}
24
25	function set_items() {
26		$carousel_items.forEach(function ($this, index) {
27			gsap.set($this, {
28				left: `${index * 100}%`,
29			});
30		});
31		gsap.to($car, {
32			alpha: 1,
33			duration: 0.5,
34		});
35	}
36
37	function toc_init() {
38		let toc_markup = '';
39		for (let index = 0; index < slide_qty; index++) {
40			if (index == 0) {
41				toc_markup += '<div class="nav-carousel__toc-item g3-active-toc"></div>';
42			} else {
43				toc_markup += '<div class="nav-carousel__toc-item"></div>';
44			}
45		}
46		$toc_container.innerHTML = toc_markup;
47		$toc_container.querySelectorAll('.nav-carousel__toc-item').forEach(function ($this, index) {
48			$this.addEventListener('click', function () {
49				const goto = parseInt(getNodeIndex($this) + 1);
50				if (goto != cur_slide) {
51					auto_play_disable();
52					if (goto > cur_slide) {
53						const diff = parseInt(goto - cur_slide);
54						position += diff;
55						cur_slide = parseInt(goto);
56						gsap.to($carousel, {
57							duration: speed,
58							x: `${-(position * 100)}%`,
59							ease: Power1.easeOut,
60							onUpdate: item_repos,
61							onUpdateParams: ['r'],
62						});
63						toc();
64					} else {
65						const diff = parseInt(cur_slide - goto);
66						position -= diff;
67						cur_slide = parseInt(goto);
68						gsap.to($carousel, {
69							duration: speed,
70							x: `${-(position * 100)}%`,
71							ease: Power1.easeOut,
72							onUpdate: item_repos,
73							onUpdateParams: ['l'],
74						});
75						if (toc_exists) {
76							toc();
77						}
78					}
79				}
80			});
81		});
82	}
83
84	//set_items();
85
86	if (toc_exists) {
87		toc_init();
88	}
89
90	$arrow_r.addEventListener('click', function () {
91		auto_play_disable();
92		step_right();
93	});
94
95	function save_position() {
96		sessionStorage.setItem('g3_carousel_position', position);
97		sessionStorage.setItem('g3_carousel_slide', cur_slide);
98	}
99
100	function step_right() {
101		position++;
102		set_slide_num(1);
103		gsap.to($carousel, {
104			duration: speed,
105			x: `${-(position * 100)}%`,
106			ease: Power1.easeOut,
107			onUpdate: item_repos,
108		});
109		save_position();
110	}
111
112	$arrow_l.addEventListener('click', function () {
113		auto_play_disable();
114		step_left();
115	});
116
117	function step_left() {
118		position--;
119		set_slide_num(-1);
120		gsap.to($carousel, {
121			duration: speed,
122			x: `${-(position * 100)}%`,
123			ease: Power1.easeOut,
124			onUpdate: item_repos,
125		});
126		save_position();
127	}
128
129	function item_repos() {
130		$carousel_items.forEach(function ($item, index) {
131			// Integrated offset calculation directly using getBoundingClientRect
132			const itemRect = $item.getBoundingClientRect();
133			const containerRect = $item.parentNode.parentNode.getBoundingClientRect();
134			const posX = itemRect.left - containerRect.left;
135			const itemWidth = $carousel_items[0].offsetWidth;
136
137			if (posX < -itemWidth) {
138				gsap.set($item, {
139					x: `+=${slide_qty * 100}%`,
140				});
141			} else if (posX > itemWidth * (slide_qty - 1)) {
142				gsap.set($item, {
143					x: `-=${slide_qty * 100}%`,
144				});
145			}
146		});
147	}
148
149	// Draggable.create($carousel, {
150	// 	type: 'x',
151	// 	edgeResistance: 1,
152	// 	dragResistance: 0,
153	// 	zIndexBoost: false,
154	// 	inertia: false,
155	// 	dragClickables: true,
156	// 	lockAxis: true,
157	// 	minimumMovement: 2,
158	// 	onDragEnd: function () {
159	// 		const a = this.lockedAxis;
160	// 		if (a == 'y') {
161	// 			const d = this.getDirection('start');
162	// 			if (d == 'left') {
163	// 				step_right();
164	// 			} else if (d == 'right') {
165	// 				step_left();
166	// 			}
167	// 		}
168	// 	},
169	// 	onDragStart: function () {
170	// 		//auto_play_disable();
171	// 	},
172	// 	onDrag: function () {
173	// 		//item_repos();
174	// 	},
175	// });
176
177	function ss_auto_play() {
178		if ($car.classList.contains('g3-auto-lpay')) {
179			TweenMax.delayedCall(auto_speed, function () {
180				if ($car.classList.contains('g3-auto-play')) {
181					auto_speed = 3;
182					step_right();
183					ss_auto_play();
184				}
185			});
186		}
187	}
188
189	function auto_play_disable() {
190		if ($car.classList.contains('g3-auto-play')) {
191			$car.classList.remove('g3-auto-play');
192		}
193	}
194
195	function set_slide_num(n) {
196		cur_slide = parseInt(cur_slide + n);
197		if (cur_slide > slide_qty) {
198			cur_slide = 1;
199		}
200		if (cur_slide < 1) {
201			cur_slide = slide_qty;
202		}
203		if (toc_exists) {
204			toc();
205		}
206	}
207
208	function toc() {
209		$car_container.querySelector('.g3-active-toc').classList.remove('g3-active-toc');
210		$car_container.querySelector(`.nav-carousel__toc-item:nth-child(${cur_slide})`).classList.add('g3-active-toc');
211	}
212
213	function init() {
214		const saved_position = sessionStorage.getItem('g3_carousel_position');
215		const saved_slide = sessionStorage.getItem('g3_carousel_slide');
216		if (saved_position !== null && saved_slide !== null) {
217			position = parseInt(saved_position);
218			cur_slide = parseInt(saved_slide);
219		} else {
220			position = parseInt(initial_on - 1);
221			cur_slide = parseInt(initial_on);
222		}
223		gsap.set($carousel, {
224			x: `${-(position * 100)}%`,
225		});
226		item_repos();
227	}
228
229	init();
230
231	if ($car.classList.contains('g3-auto-play')) {
232		ss_auto_play();
233	}
234}
235// Usage: g3_carousel(document.querySelector('#g3GiveBack__Carousel'));
236
237/* ================================================================
238   Header Sticky execution
239   ================================================================ */
240document.addEventListener('DOMContentLoaded', function () {
241	// Handle navigation highlighting based on body class
242	const bodyClasses = document.body.classList;
243	const stickyNavItems = document.querySelectorAll('.header__nav-item');
244
245	stickyNavItems.forEach((item) => {
246		const itemClass = item.dataset.navTrigger;
247		if (itemClass && bodyClasses.contains(itemClass)) {
248			item.classList.add('active');
249		}
250	});
251
252	// Header tray functionality
253	const $headerTrays = document.querySelectorAll('.header__tray');
254	const $headerTrayTriggers = document.querySelectorAll('.header__nav-tray-trigger');
255
256	// Only initialize if the elements exist
257	if ($headerTrays.length > 0 && $headerTrayTriggers.length > 0) {
258		// Create a map to track mouse state for each tray
259		const trayStates = new Map();
260
261		// Initialize each tray with its corresponding trigger(s)
262		$headerTrays.forEach(function (tray) {
263			// Get the tray ID or data attribute to identify which tray this is
264			const trayId = tray.dataset.trayId;
265
266			// Find triggers that should open this tray
267			const relatedTriggers = Array.from($headerTrayTriggers).filter((trigger) => trigger.dataset.navTrigger === trayId);
268
269			// Initialize state for this tray
270			trayStates.set(trayId, { isMouseOver: false });
271
272			function openTray() {
273				let trayHeight = tray.offsetHeight;
274				gsap.to(tray, {
275					y: trayHeight,
276					duration: 0.5,
277					ease: 'power2.out',
278					overwrite: true,
279				});
280			}
281
282			function closeTray() {
283				const state = trayStates.get(trayId);
284				if (!state.isMouseOver) {
285					gsap.to(tray, {
286						y: -5,
287						duration: 0.5,
288						ease: 'power2.out',
289					});
290				}
291			}
292
293			// Add event listeners to related triggers
294			relatedTriggers.forEach(function (trigger) {
295				trigger.addEventListener('mouseenter', function () {
296					// Close any other open trays first (optional)
297					closeAllTraysExcept(trayId);
298
299					// Update state and open this tray
300					trayStates.get(trayId).isMouseOver = true;
301					openTray();
302				});
303
304				trigger.addEventListener('mouseleave', function () {
305					trayStates.get(trayId).isMouseOver = false;
306					// Small delay to check if mouse moved to tray
307					gsap.delayedCall(0.15, closeTray);
308				});
309			});
310
311			// Add event listeners to the tray itself
312			tray.addEventListener('mouseenter', function () {
313				trayStates.get(trayId).isMouseOver = true;
314				openTray();
315			});
316
317			tray.addEventListener('mouseleave', function () {
318				trayStates.get(trayId).isMouseOver = false;
319				closeTray();
320			});
321		});
322
323		// Helper function to close all trays except the active one
324		function closeAllTraysExcept(activeTrayId) {
325			trayStates.forEach((state, trayId) => {
326				if (trayId !== activeTrayId) {
327					state.isMouseOver = false;
328					const tray = document.querySelector(`.header__tray[data-tray-id="${trayId}"]`);
329					gsap.to(tray, {
330						y: -5,
331						duration: 0.3,
332						ease: 'power2.out',
333					});
334				}
335			});
336		}
337	}
338
339	function getSameOriginPath(url) {
340		try {
341			const linkUrl = new URL(url, window.location.origin);
342			if (linkUrl.origin !== window.location.origin) {
343				return null;
344			}
345			return linkUrl.pathname.replace(/\/$/, '');
346		} catch (e) {
347			return null;
348		}
349	}
350
351	// Mobile Navigation
352	const hamburgerMenu = document.querySelector('.header--sticky__hamburger-menu');
353	const mobileNav = document.querySelector('.header--sticky__mobile-nav');
354	const mobileNavItems = document.querySelectorAll('.header--sticky__mobile-nav-item');
355
356	if (hamburgerMenu && mobileNav) {
357		hamburgerMenu.addEventListener('click', function () {
358			this.classList.toggle('active');
359			mobileNav.classList.toggle('active');
360		});
361
362		// Handle submenu toggles
363		mobileNavItems.forEach((item) => {
364			const submenu = item.querySelector('.header--sticky__mobile-submenu');
365			if (submenu) {
366				item.querySelector('.header--sticky__mobile-nav-link').addEventListener('click', function (e) {
367					e.preventDefault();
368					submenu.classList.toggle('active');
369				});
370			}
371		});
372
373		// Open the current page's main item and highlight the active links
374		const mobileCurrentPath = window.location.pathname.replace(/\/$/, '');
375		const mobileForcedSubNav = (document.body.dataset.g3SubNav || '').replace(/\/$/, '');
376		const mobilePaths = new Set();
377		if (mobileCurrentPath) {
378			mobilePaths.add(mobileCurrentPath);
379		}
380		if (mobileForcedSubNav) {
381			mobilePaths.add(mobileForcedSubNav);
382		}
383
384		mobileNavItems.forEach((item) => {
385			const submenu = item.querySelector('.header--sticky__mobile-submenu');
386			const mainLink = item.querySelector('.header--sticky__mobile-nav-link');
387			let isCurrent = false;
388
389			// Match against submenu links (mark the matching sub item orange)
390			if (submenu) {
391				submenu.querySelectorAll('a').forEach((subLink) => {
392					const subPath = getSameOriginPath(subLink.href);
393					if (subPath && mobilePaths.has(subPath)) {
394						subLink.closest('.header--sticky__mobile-submenu-item').classList.add('is-current');
395						isCurrent = true;
396					}
397				});
398			}
399
400			// Match against the main link (covers items without a submenu, e.g. Contact)
401			if (mainLink && !mainLink.classList.contains('js-mobile-search-trigger')) {
402				const href = mainLink.getAttribute('href');
403				if (href && href !== '#') {
404					const mainPath = getSameOriginPath(mainLink.href);
405					if (mainPath && mobilePaths.has(mainPath)) {
406						isCurrent = true;
407					}
408				}
409			}
410
411			if (isCurrent) {
412				item.classList.add('is-current');
413				if (submenu) {
414					submenu.classList.add('active');
415				}
416			}
417		});
418	}
419
420	//Logo audio
421	const $playBtn = document.getElementById('header_logo_audio-play-btn');
422	const audioPath = '/mp3/';
423	const audioPathOgg = '/ogg/';
424
425	// Create array of all audio files with both formats
426	const audioFiles = Array.from({ length: 17 }, (_, i) => ({
427		mp3: audioPath + `m${i + 1}.mp3`,
428		ogg: audioPathOgg + `m${i + 1}.ogg`,
429	}));
430
431	let currentIndex = 0;
432	const audioElement = document.createElement('audio');
433
434	function playNextAudio() {
435		// Reset the audio element
436		audioElement.pause();
437		audioElement.currentTime = 0;
438		audioElement.removeAttribute('src');
439		audioElement.load();
440
441		// Set both source formats
442		audioElement.innerHTML = `
443			<source src="${audioFiles[currentIndex].mp3}" type="audio/mpeg">
444			<source src="${audioFiles[currentIndex].ogg}" type="audio/ogg">
445		`;
446
447		// Load and play the audio
448		audioElement.load();
449		const playPromise = audioElement.play();
450
451		if (playPromise !== undefined) {
452			playPromise
453				.then(() => {
454					// Move to next file after current one finishes
455					audioElement.addEventListener(
456						'ended',
457						() => {
458							currentIndex = (currentIndex + 1) % audioFiles.length;
459						},
460						{ once: true },
461					);
462				})
463				.catch((error) => {
464					console.error('Audio playback failed:', error);
465				});
466		}
467	}
468
469	$playBtn.addEventListener('click', playNextAudio);
470
471	// Check for section classes and highlight appropriate nav item
472	const body = document.body;
473	const navItems = document.querySelectorAll('.header__nav-item');
474
475	// Remove any existing active classes
476	navItems.forEach((item) => {
477		item.classList.remove('header__nav-item--active');
478	});
479
480	// Get all body classes
481	const currentBodyClasses = Array.from(body.classList);
482
483	// Find nav item with matching data-nav-trigger
484	navItems.forEach((item) => {
485		const trigger = item.getAttribute('data-nav-trigger');
486		if (trigger && currentBodyClasses.includes(trigger)) {
487			item.classList.add('header__nav-item--active');
488		}
489	});
490
491	// Highlight active tray nav items based on current URL and/or data-g3-sub-nav
492	const currentPath = window.location.pathname.replace(/\/$/, '');
493	const forcedSubNav = (document.body.dataset.g3SubNav || '').replace(/\/$/, '');
494	const subNavPaths = new Set();
495	if (currentPath) {
496		subNavPaths.add(currentPath);
497	}
498	if (forcedSubNav) {
499		subNavPaths.add(forcedSubNav);
500	}
501
502	document.querySelectorAll('[data-js="sub-nav-item"]').forEach(function (item) {
503		const link = item.tagName === 'A' ? item : item.querySelector('a');
504		if (link) {
505			const linkPath = getSameOriginPath(link.href);
506			if (linkPath && subNavPaths.has(linkPath)) {
507				const title = item.querySelector('.header__tray-title');
508				if (title) {
509					title.classList.add('header__tray-title--active');
510				}
511			}
512		}
513	});
514
515	// Initialize sticky nav carousel
516	g3_carousel(document.querySelector('#sticky-nav-books-carousel'));
517});

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.