PageSourceSearch

https://www.justborn.com/wp-content/themes/studioswift/blocks/highlight-slider/script.js

js justborn.com collected 2026-10-02 08:01:06 UTC 5,912 bytes, 242 lines download raw bytes

1/**
2 * Highlight Slider Block - JavaScript
3 *
4 * @package StudioSwift
5 */
6
7(function () {
8	'use strict';
9
10	const DESKTOP_BREAKPOINT = '(min-width: 1199px)';
11
12	/**
13	 * Set active state for a brand scroller item by index.
14	 *
15	 * @param {NodeList} items
16	 * @param {NodeList} images
17	 * @param {NodeList} overlayItems
18	 * @param {number|null} index
19	 */
20	function setActiveItem(items, images, overlayItems, index) {
21		items.forEach((item) => item.classList.remove('is-active'));
22		images.forEach((img) => img.classList.remove('is-active'));
23		overlayItems.forEach((item) => item.classList.remove('is-active'));
24
25		if (index === null || index < 0) {
26			return;
27		}
28
29		if (items[index]) {
30			items[index].classList.add('is-active');
31		}
32
33		if (images[index]) {
34			images[index].classList.add('is-active');
35		}
36
37		if (overlayItems[index]) {
38			overlayItems[index].classList.add('is-active');
39		}
40	}
41
42	/**
43	 * Tear down an initialized brand scroller instance.
44	 *
45	 * @param {HTMLElement} brandScroller
46	 */
47	function destroyHighlightSlider(brandScroller) {
48		if (brandScroller._highlightSliderCleanup) {
49			brandScroller._highlightSliderCleanup();
50			delete brandScroller._highlightSliderCleanup;
51		}
52
53		delete brandScroller.dataset.highlightSliderInit;
54	}
55
56	/**
57	 * Initialize Highlight Slider functionality
58	 */
59	function initHighlightSlider() {
60		const brandScroller = document.querySelector('.js-brand-scroller');
61
62		// Element not present (e.g. editor preview with no content yet).
63		if (!brandScroller) {
64			return;
65		}
66
67		if (brandScroller.dataset.highlightSliderInit === 'true') {
68			destroyHighlightSlider(brandScroller);
69		}
70
71		brandScroller.dataset.highlightSliderInit = 'true';
72
73		const brandScrollerItems = brandScroller.querySelectorAll('.js-brand-scroller-item');
74		const brandScrollerImages = brandScroller.querySelectorAll('.js-brand-scroller-bg');
75		const brandScrollerOverlayItems = brandScroller.querySelectorAll('.js-brand-scroller-overlay-item');
76		const brandScrollerListItems = brandScroller.querySelectorAll('.js-brand-scroller-list > li');
77		const brandScrollerCount = brandScrollerItems.length;
78		const desktopMediaQuery = window.matchMedia(DESKTOP_BREAKPOINT);
79
80		let scrollTween = null;
81		let previousTargetItem = null;
82		let lastHoveredIndex = null;
83		let hoverHandlers = null;
84
85		function destroyScrollMode() {
86			if (scrollTween) {
87				if (scrollTween.scrollTrigger) {
88					scrollTween.scrollTrigger.kill();
89				}
90				scrollTween.kill();
91				scrollTween = null;
92			}
93
94			previousTargetItem = null;
95		}
96
97		function destroyHoverMode() {
98			if (!hoverHandlers) {
99				return;
100			}
101
102			hoverHandlers.listItems.forEach((listItem, index) => {
103				listItem.removeEventListener('mouseenter', hoverHandlers.onItemEnter[index]);
104			});
105			hoverHandlers = null;
106			lastHoveredIndex = null;
107		}
108
109		function resetActiveState() {
110			brandScroller.classList.remove('brand-scroller--active');
111			setActiveItem(brandScrollerItems, brandScrollerImages, brandScrollerOverlayItems, null);
112			previousTargetItem = null;
113		}
114
115		function initScrollMode() {
116			gsap.registerPlugin(ScrollTrigger);
117			resetActiveState();
118
119			scrollTween = gsap.to(brandScroller, {
120				scrollTrigger: {
121					trigger: brandScroller,
122					start: 'top top',
123					end: '+=100%',
124					pin: true,
125					scrub: 2,
126					onUpdate: (self) => {
127						const progress = self.progress;
128						const targetItem = Math.min(
129							Math.floor(progress * brandScrollerCount) + 1,
130							brandScrollerCount
131						);
132
133						if (targetItem === previousTargetItem) {
134							return;
135						}
136
137						setActiveItem(
138							brandScrollerItems,
139							brandScrollerImages,
140							brandScrollerOverlayItems,
141							targetItem - 1
142						);
143						previousTargetItem = targetItem;
144					},
145					onEnter: () => {
146						brandScroller.classList.add('brand-scroller--active');
147					},
148					onLeaveBack: () => {
149						resetActiveState();
150					},
151				},
152			});
153		}
154
155		function setHoveredItem(index) {
156			lastHoveredIndex = index;
157			brandScroller.classList.add('brand-scroller--active');
158			setActiveItem(
159				brandScrollerItems,
160				brandScrollerImages,
161				brandScrollerOverlayItems,
162				index
163			);
164		}
165
166		function initHoverMode() {
167			resetActiveState();
168			lastHoveredIndex = null;
169
170			const onItemEnter = [];
171
172			brandScrollerListItems.forEach((listItem, index) => {
173				const handler = () => {
174					if (index === lastHoveredIndex) {
175						return;
176					}
177
178					setHoveredItem(index);
179				};
180
181				onItemEnter[index] = handler;
182				listItem.addEventListener('mouseenter', handler);
183			});
184
185			hoverHandlers = {
186				listItems: brandScrollerListItems,
187				onItemEnter,
188			};
189		}
190
191		function setupMode() {
192			if (desktopMediaQuery.matches) {
193				destroyScrollMode();
194				initHoverMode();
195			} else {
196				destroyHoverMode();
197				initScrollMode();
198			}
199
200			if (typeof ScrollTrigger !== 'undefined') {
201				ScrollTrigger.refresh();
202			}
203		}
204
205		const onBreakpointChange = () => {
206			setupMode();
207		};
208
209		setupMode();
210		desktopMediaQuery.addEventListener('change', onBreakpointChange);
211
212		brandScroller._highlightSliderCleanup = () => {
213			desktopMediaQuery.removeEventListener('change', onBreakpointChange);
214			destroyScrollMode();
215			destroyHoverMode();
216			resetActiveState();
217		};
218	}
219
220	// Refresh ScrollTrigger pin dimensions when viewport size changes (single listener)
221	let resizeTimeout;
222	window.addEventListener('resize', () => {
223		clearTimeout(resizeTimeout);
224		resizeTimeout = setTimeout(() => {
225			if (typeof ScrollTrigger !== 'undefined') {
226				ScrollTrigger.refresh();
227			}
228		}, 100);
229	});
230
231	// Initialize on DOM ready
232	if (document.readyState === 'loading') {
233		document.addEventListener('DOMContentLoaded', initHighlightSlider);
234	} else {
235		initHighlightSlider();
236	}
237
238	// Reinitialize for dynamically loaded content (e.g., Gutenberg preview)
239	if (window.acf) {
240		window.acf.addAction('render_block_preview/type=highlight-slider', initHighlightSlider);
241	}
242})();

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.