PageSourceSearch

https://mayaskies.net/wp-content/themes/wikipress/assets/js/main.js?ver=1.0.0

js mayaskies.net collected 2026-10-02 14:25:08 UTC 7,736 bytes, 291 lines download raw bytes

1/**
2 * WikiPress Theme - Main JavaScript
3 *
4 * Lightweight vanilla JS for mobile menu, TOC, search toggle,
5 * scroll highlighting, and other interactive features.
6 *
7 * @package WikiPress
8 */
9
10( function () {
11	'use strict';
12
13	/**
14	 * Mobile menu toggle.
15	 */
16	function initMobileMenu() {
17		var toggle = document.querySelector( '.menu-toggle' );
18		var menu = document.getElementById( 'primary-menu' );
19
20		if ( ! toggle || ! menu ) {
21			return;
22		}
23
24		toggle.addEventListener( 'click', function () {
25			var expanded = toggle.getAttribute( 'aria-expanded' ) === 'true';
26			toggle.setAttribute( 'aria-expanded', expanded ? 'false' : 'true' );
27			menu.classList.toggle( 'is-active' );
28		} );
29
30		// Close mobile menu when clicking outside.
31		document.addEventListener( 'click', function ( e ) {
32			if ( ! toggle.contains( e.target ) && ! menu.contains( e.target ) ) {
33				toggle.setAttribute( 'aria-expanded', 'false' );
34				menu.classList.remove( 'is-active' );
35			}
36		} );
37
38		// Handle sub-menu toggles on mobile.
39		var subMenus = menu.querySelectorAll( '.menu-item-has-children > .sub-menu' );
40		subMenus.forEach( function ( subMenu ) {
41			var parentLink = subMenu.previousElementSibling;
42			if ( parentLink && parentLink.tagName === 'A' ) {
43				parentLink.addEventListener( 'click', function ( e ) {
44					if ( window.innerWidth <= 768 ) {
45						e.preventDefault();
46						subMenu.classList.toggle( 'is-active' );
47					}
48				} );
49			}
50		} );
51	}
52
53	/**
54	 * Header search toggle.
55	 */
56	function initSearchToggle() {
57		var toggle = document.querySelector( '.search-toggle' );
58		var form = document.getElementById( 'header-search-form' );
59
60		if ( ! toggle || ! form ) {
61			return;
62		}
63
64		toggle.addEventListener( 'click', function () {
65			var expanded = toggle.getAttribute( 'aria-expanded' ) === 'true';
66			toggle.setAttribute( 'aria-expanded', expanded ? 'false' : 'true' );
67			form.setAttribute( 'aria-hidden', expanded ? 'false' : 'true' );
68			form.classList.toggle( 'is-active' );
69
70			if ( ! expanded ) {
71				// Focus the search field when opened.
72				var searchField = form.querySelector( '.search-field' );
73				if ( searchField ) {
74					setTimeout( function () {
75						searchField.focus();
76					}, 100 );
77				}
78			}
79		} );
80
81		// Close search when clicking outside.
82		document.addEventListener( 'click', function ( e ) {
83			if ( ! toggle.contains( e.target ) && ! form.contains( e.target ) ) {
84				toggle.setAttribute( 'aria-expanded', 'false' );
85				form.setAttribute( 'aria-hidden', 'true' );
86				form.classList.remove( 'is-active' );
87			}
88		} );
89	}
90
91	/**
92	 * Table of Contents toggle (mobile).
93	 */
94	function initTOCToggle() {
95		var tocToggles = document.querySelectorAll( '.toc-toggle' );
96
97		tocToggles.forEach( function ( toggle ) {
98			toggle.addEventListener( 'click', function () {
99				var list = toggle.nextElementSibling;
100				if ( ! list ) {
101					return;
102				}
103
104				var expanded = toggle.getAttribute( 'aria-expanded' ) === 'true';
105				toggle.setAttribute( 'aria-expanded', expanded ? 'false' : 'true' );
106				list.classList.toggle( 'collapsed' );
107
108				// Update toggle icon.
109				var icon = toggle.querySelector( '.toc-toggle-icon' );
110				if ( icon ) {
111					icon.textContent = expanded ? '[+]' : '[−]';
112				}
113			} );
114		} );
115	}
116
117	/**
118	 * Active section highlighting in TOC during scroll.
119	 * Uses IntersectionObserver for performance.
120	 */
121	function initTOCScrollHighlight() {
122		var tocLinks = document.querySelectorAll( '.toc-list a' );
123		if ( tocLinks.length === 0 ) {
124			return;
125		}
126
127		// Get all heading elements referenced by TOC links.
128		var headingIds = [];
129		tocLinks.forEach( function ( link ) {
130			var href = link.getAttribute( 'href' );
131			if ( href && href.startsWith( '#' ) ) {
132				headingIds.push( href.substring( 1 ) );
133			}
134		} );
135
136		if ( headingIds.length === 0 ) {
137			return;
138		}
139
140		var observer = new IntersectionObserver(
141			function ( entries ) {
142				entries.forEach( function ( entry ) {
143					if ( entry.isIntersecting ) {
144						// Remove active class from all TOC links.
145						tocLinks.forEach( function ( link ) {
146							link.classList.remove( 'active' );
147						} );
148
149						// Add active class to corresponding TOC link.
150						var activeLink = document.querySelector(
151							'.toc-list a[href="#' + CSS.escape( entry.target.id ) + '"]'
152						);
153						if ( activeLink ) {
154							activeLink.classList.add( 'active' );
155						}
156					}
157				} );
158			},
159			{
160				rootMargin: '-80px 0px -70% 0px',
161				threshold: 0,
162			}
163		);
164
165		headingIds.forEach( function ( id ) {
166			var heading = document.getElementById( id );
167			if ( heading ) {
168				observer.observe( heading );
169			}
170		} );
171	}
172
173	/**
174	 * Smooth scroll for anchor links (skip link, TOC links).
175	 */
176	function initSmoothScroll() {
177		document.addEventListener( 'click', function ( e ) {
178			var link = e.target.closest( 'a[href^="#"]' );
179			if ( ! link ) {
180				return;
181			}
182
183			var href = link.getAttribute( 'href' );
184			if ( href === '#' ) {
185				return;
186			}
187
188			var target = document.querySelector( href );
189			if ( ! target ) {
190				return;
191			}
192
193			// Don't intercept TOC toggle buttons or navigation links.
194			if ( link.closest( '.toc-toggle' ) || link.closest( '.menu-toggle' ) ) {
195				return;
196			}
197
198			e.preventDefault();
199			target.scrollIntoView( { behavior: 'smooth', block: 'start' } );
200
201			// Update URL hash without jump.
202			history.pushState( null, null, href );
203		} );
204	}
205
206	/**
207	 * Handle keyboard navigation for collapsible elements.
208	 */
209	function initKeyboardAccessibility() {
210		// Allow Enter/Space to toggle collapsible elements.
211		document.addEventListener( 'keydown', function ( e ) {
212			if ( e.key === 'Enter' || e.key === ' ' ) {
213				var target = e.target.closest( '.toc-toggle, .menu-toggle, .search-toggle' );
214				if ( target && target.tagName === 'BUTTON' ) {
215					// Buttons already handle Enter/Space natively.
216					return;
217				}
218			}
219
220			// Escape to close menus/search.
221			if ( e.key === 'Escape' ) {
222				var menu = document.getElementById( 'primary-menu' );
223				var menuToggle = document.querySelector( '.menu-toggle' );
224				var searchForm = document.getElementById( 'header-search-form' );
225				var searchToggle = document.querySelector( '.search-toggle' );
226
227				if ( menu && menu.classList.contains( 'is-active' ) ) {
228					menu.classList.remove( 'is-active' );
229					if ( menuToggle ) {
230						menuToggle.setAttribute( 'aria-expanded', 'false' );
231						menuToggle.focus();
232					}
233				}
234
235				if ( searchForm && searchForm.classList.contains( 'is-active' ) ) {
236					searchForm.classList.remove( 'is-active' );
237					searchForm.setAttribute( 'aria-hidden', 'true' );
238					if ( searchToggle ) {
239						searchToggle.setAttribute( 'aria-expanded', 'false' );
240						searchToggle.focus();
241					}
242				}
243			}
244		} );
245	}
246
247	/**
248	 * Header scroll-shrink effect.
249	 * Adds 'is-scrolled' class when the page is scrolled down.
250	 */
251	function initHeaderScroll() {
252		var header = document.querySelector( '.site-header' );
253		if ( ! header ) {
254			return;
255		}
256
257		var scrollThreshold = 50;
258		var isScrolled = false;
259
260		function onScroll() {
261			var shouldBeScrolled = window.scrollY > scrollThreshold;
262			if ( shouldBeScrolled !== isScrolled ) {
263				isScrolled = shouldBeScrolled;
264				header.classList.toggle( 'is-scrolled', isScrolled );
265			}
266		}
267
268		window.addEventListener( 'scroll', onScroll, { passive: true } );
269		onScroll(); // Check initial state.
270	}
271
272	/**
273	 * Initialize all features when DOM is ready.
274	 */
275	function init() {
276		initMobileMenu();
277		initSearchToggle();
278		initTOCToggle();
279		initTOCScrollHighlight();
280		initSmoothScroll();
281		initKeyboardAccessibility();
282		initHeaderScroll();
283	}
284
285	// Run on DOMContentLoaded.
286	if ( document.readyState === 'loading' ) {
287		document.addEventListener( 'DOMContentLoaded', init );
288	} else {
289		init();
290	}
291} )();

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.