PageSourceSearch

https://www.acvinyl.com/wp-content/themes/sparkling/assets/js/functions.js?ver=2.7.0

js acvinyl.com collected 2026-10-04 02:30:04 UTC 9,128 bytes, 346 lines download raw bytes

1/**
2 * Sparkling theme scripts.
3 *
4 * Plain DOM APIs -- no jQuery. The two globals (SparklingIsMobile and
5 * generateMobileMenu) are kept on window because child themes may call them.
6 */
7( function () {
8	'use strict';
9
10	/**
11	 * Run a callback once the DOM is ready.
12	 *
13	 * @param {Function} fn Callback.
14	 */
15	function ready( fn ) {
16		if ( document.readyState !== 'loading' ) {
17			fn();
18		} else {
19			document.addEventListener( 'DOMContentLoaded', fn );
20		}
21	}
22
23	/**
24	 * Add class names to every element matching a selector.
25	 *
26	 * @param {string} selector CSS selector.
27	 * @param {Array}  classes  Class names to add.
28	 */
29	function addClasses( selector, classes ) {
30		document.querySelectorAll( selector ).forEach( function ( el ) {
31			el.classList.add.apply( el.classList, classes );
32		} );
33	}
34
35	/**
36	 * jQuery's "swing" easing, so the scroll-to-top motion is unchanged.
37	 *
38	 * @param {number} p Progress from 0 to 1.
39	 * @return {number} Eased progress.
40	 */
41	function swing( p ) {
42		return 0.5 - Math.cos( p * Math.PI ) / 2;
43	}
44
45	function prefersReducedMotion() {
46		return window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches;
47	}
48
49	/**
50	 * Fade an element in or out, matching jQuery's default 400ms fade.
51	 *
52	 * @param {Element} el   Target element.
53	 * @param {boolean} into True to fade in, false to fade out.
54	 */
55	function fade( el, into ) {
56		if ( ! el || el.dataset.sparklingFading === String( into ) ) {
57			return;
58		}
59
60		el.dataset.sparklingFading = String( into );
61
62		if ( prefersReducedMotion() ) {
63			el.style.opacity = into ? '1' : '';
64			el.style.display = into ? 'block' : 'none';
65			return;
66		}
67
68		var duration = 400;
69		var start    = null;
70		var from     = parseFloat( window.getComputedStyle( el ).opacity );
71
72		if ( isNaN( from ) ) {
73			from = into ? 0 : 1;
74		}
75
76		if ( into ) {
77			el.style.display = 'block';
78		}
79
80		function step( ts ) {
81			if ( start === null ) {
82				start = ts;
83			}
84
85			var p = Math.min( ( ts - start ) / duration, 1 );
86			var v = from + ( ( into ? 1 : 0 ) - from ) * swing( p );
87
88			el.style.opacity = String( v );
89
90			if ( p < 1 ) {
91				window.requestAnimationFrame( step );
92			} else if ( ! into ) {
93				el.style.display = 'none';
94				el.style.opacity = '';
95			}
96		}
97
98		window.requestAnimationFrame( step );
99	}
100
101	/**
102	 * Detect a mobile user agent.
103	 *
104	 * Kept as-is (including the UA sniff) because the mobile menu styling below
105	 * depends on it and child themes may override this function.
106	 *
107	 * @return {boolean} True on a mobile user agent.
108	 */
109	window.SparklingIsMobile = function () {
110		return (
111			navigator.userAgent.match( /Android/i ) ||
112			navigator.userAgent.match( /webOS/i ) ||
113			navigator.userAgent.match( /iPhone/i ) ||
114			navigator.userAgent.match( /iPod/i ) ||
115			navigator.userAgent.match( /iPad/i ) ||
116			navigator.userAgent.match( /BlackBerry/ )
117		);
118	};
119
120	/**
121	 * Toggle the tap-friendly menu class on touch devices at tablet width and up.
122	 */
123	window.generateMobileMenu = function () {
124		var menu = document.querySelector( '#masthead .site-navigation-inner .navbar-collapse > ul.nav' );
125
126		if ( ! menu ) {
127			return;
128		}
129
130		if ( window.SparklingIsMobile() && window.innerWidth > 767 ) {
131			menu.classList.add( 'sparkling-mobile-menu' );
132		} else {
133			menu.classList.remove( 'sparkling-mobile-menu' );
134		}
135	};
136
137	/**
138	 * Apply Bootstrap classes to markup WordPress generates.
139	 */
140	function styleCoreMarkup() {
141		addClasses( '.comment-reply-link', [ 'btn', 'btn-sm', 'btn-default' ] );
142		addClasses(
143			'#submit, button[type=submit], html input[type=button], input[type=reset], input[type=submit]',
144			[ 'btn', 'btn-default' ]
145		);
146		addClasses( '.widget_rss ul', [ 'media-list' ] );
147		addClasses( '.postform', [ 'form-control' ] );
148		addClasses( 'table#wp-calendar', [ 'table', 'table-striped' ] );
149	}
150
151	/**
152	 * Scroll-to-top button: reveal past 100px, and smooth-scroll on click.
153	 */
154	function initScrollToTop() {
155		var button = document.querySelector( '.scroll-to-top' );
156
157		if ( ! button ) {
158			return;
159		}
160
161		function onScroll() {
162			fade( button, ( window.pageYOffset || document.documentElement.scrollTop ) > 100 );
163		}
164
165		window.addEventListener( 'scroll', onScroll, { passive: true } );
166		onScroll();
167
168		button.addEventListener( 'click', function ( event ) {
169			event.preventDefault();
170
171			var from = window.pageYOffset || document.documentElement.scrollTop;
172
173			if ( ! from ) {
174				return;
175			}
176
177			if ( prefersReducedMotion() ) {
178				window.scrollTo( 0, 0 );
179				return;
180			}
181
182			var duration = 800;
183			var start    = null;
184
185			function step( ts ) {
186				if ( start === null ) {
187					start = ts;
188				}
189
190				var p = Math.min( ( ts - start ) / duration, 1 );
191
192				window.scrollTo( 0, from * ( 1 - swing( p ) ) );
193
194				if ( p < 1 ) {
195					window.requestAnimationFrame( step );
196				}
197			}
198
199			window.requestAnimationFrame( step );
200		} );
201	}
202
203	/**
204	 * Open or close a menu item's submenu and report it on its toggle button.
205	 *
206	 * @param {Element} item The <li>.
207	 * @param {boolean} open Whether to open it.
208	 */
209	function setSubmenuOpen( item, open ) {
210		item.classList.toggle( 'open', open );
211
212		var toggle = item.querySelector( ':scope > .sparkling-dropdown' );
213
214		if ( toggle ) {
215			toggle.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
216		}
217	}
218
219	/**
220	 * Submenu toggles in the navbar.
221	 */
222	function initDropdowns() {
223		document.querySelectorAll( '.sparkling-dropdown' ).forEach( function ( toggle ) {
224			toggle.addEventListener( 'click', function ( event ) {
225				event.preventDefault();
226				setSubmenuOpen( toggle.parentNode, ! toggle.parentNode.classList.contains( 'open' ) );
227			} );
228		} );
229
230		// A "#" parent item has nowhere to go, so it opens its submenu instead of
231		// jumping to the top of the page.
232		document.querySelectorAll( '.navbar-nav li.dropdown > a[href="#"]' ).forEach( function ( link ) {
233			link.addEventListener( 'click', function ( event ) {
234				event.preventDefault();
235				setSubmenuOpen( link.parentNode, ! link.parentNode.classList.contains( 'open' ) );
236			} );
237		} );
238
239		// Escape closes the submenu that holds focus and returns focus to its toggle.
240		document.addEventListener( 'keydown', function ( event ) {
241			if ( 'Escape' !== event.key || ! event.target.closest ) {
242				return;
243			}
244
245			var item = event.target.closest( '.navbar-nav li.open' );
246
247			if ( ! item ) {
248				return;
249			}
250
251			setSubmenuOpen( item, false );
252
253			var toggle = item.querySelector( ':scope > .sparkling-dropdown' );
254
255			if ( toggle ) {
256				toggle.focus();
257			}
258		} );
259	}
260
261	/**
262	 * Keep the menu button's aria-expanded in step with the collapsed menu.
263	 *
264	 * Bootstrap only updates triggers whose data-target is an #id; this one
265	 * targets the .navbar-ex1-collapse class, which child themes rely on.
266	 * Bootstrap announces the change through jQuery events, so listen there.
267	 */
268	function initMenuToggleState() {
269		var toggle = document.querySelector( '.navbar-toggle[data-target=".navbar-ex1-collapse"]' );
270
271		if ( ! toggle || ! window.jQuery ) {
272			return;
273		}
274
275		window.jQuery( document ).on( 'shown.bs.collapse hidden.bs.collapse', '.navbar-ex1-collapse', function ( event ) {
276			toggle.setAttribute( 'aria-expanded', 'shown' === event.type ? 'true' : 'false' );
277		} );
278	}
279
280	/**
281	 * Sticky header: keep the fixed navbar below the admin bar, reserve its
282	 * height above the content, and make in-page links stop below it.
283	 *
284	 * The space was measured once, at load, so it went stale when the menu
285	 * wrapped to a second row on a narrower window. With the admin bar the
286	 * navbar sat at top: 0, under the bar, and the space was set to the
287	 * difference of the two heights, so the bar hid the top of the navbar.
288	 * And an anchor -- #comments, a "Skip to main content" target, a menu
289	 * link to a section -- scrolled its target under the navbar.
290	 */
291	function initStickyHeader() {
292		var navbar = document.querySelector( '.navbar-fixed-top' );
293		var header = navbar ? navbar.closest( 'header' ) : null;
294
295		if ( ! header ) {
296			return;
297		}
298
299		var adminbar = document.getElementById( 'wpadminbar' );
300		var queued   = false;
301
302		function layout() {
303			queued = false;
304
305			var top = 0;
306			var barFixed = false;
307
308			if ( adminbar ) {
309				// Below 600px the admin bar scrolls away with the page.
310				barFixed = 'fixed' === window.getComputedStyle( adminbar ).position;
311				top      = Math.max( 0, adminbar.getBoundingClientRect().bottom );
312				navbar.style.top = top + 'px';
313			}
314
315			var navHeight = navbar.offsetHeight;
316
317			header.style.marginBottom = navHeight + 'px';
318			document.documentElement.style.scrollPaddingTop = ( ( barFixed ? top : 0 ) + navHeight ) + 'px';
319		}
320
321		function queue() {
322			if ( ! queued ) {
323				queued = true;
324				window.requestAnimationFrame( layout );
325			}
326		}
327
328		layout();
329		window.addEventListener( 'resize', queue );
330
331		if ( adminbar ) {
332			window.addEventListener( 'scroll', queue, { passive: true } );
333		}
334	}
335
336	ready( function () {
337		styleCoreMarkup();
338		initScrollToTop();
339		initDropdowns();
340		initMenuToggleState();
341		initStickyHeader();
342		window.generateMobileMenu();
343
344		window.addEventListener( 'resize', window.generateMobileMenu );
345	} );
346}() );

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.