PageSourceSearch

https://whitehousestours.com/wp-content/plugins/wht-home-code-buil…/js/wht-home-code.js?ver=0.2.72.1785364269

js whitehousestours.com collected 2026-09-25 23:05:53 UTC 77,152 bytes, 2,192 lines download raw bytes

1(function () {
2	'use strict';
3
4	var root = document.querySelector('.wht-home-code-build-root, .wht-home-code');
5
6	if (!root) {
7		return;
8	}
9
10	var bodyHadHelperClass = document.body.classList.contains('wht-home-code-page');
11	document.body.classList.add('wht-home-code-page');
12
13	var video = root.querySelector('[data-wht-hero-video]');
14	var videoModal = root.querySelector('[data-wht-video-modal]');
15	var videoFrame = root.querySelector('[data-wht-video-frame]');
16	var videoModalTitle = root.querySelector('[data-wht-video-title]');
17	var videoCloseButton = root.querySelector('button[data-wht-video-close]');
18	var videoTriggers = root.querySelectorAll('[data-wht-video-trigger]');
19	var reviewCarousel = root.querySelector('[data-wht-review-carousel]');
20	var requestForm = root.querySelector('[data-wht-request-form]');
21	var tourGrid = root.querySelector('.wht-home-code__tour-grid');
22	var cardDebugMarker = root.querySelector('[data-wht-card-debug-marker]');
23	var formConfig = window.whtHomeCodeBuild || {};
24	var pluginVersion = formConfig.pluginVersion || '0.2.66';
25	var cssUrl = formConfig.cssUrl || '';
26	var jsUrl = formConfig.jsUrl || '';
27	var cssVersion = formConfig.cssVersion || pluginVersion;
28	var jsAssetVersion = formConfig.jsVersion || pluginVersion;
29	var jsVersion = pluginVersion;
30	var criticalCssVersion = formConfig.criticalCssVersion || pluginVersion;
31	var previewMode = formConfig.previewMode || 'normal_page';
32	var previewPageId = typeof formConfig.pageId !== 'undefined' ? formConfig.pageId : '';
33	var previewId = typeof formConfig.previewId !== 'undefined' ? formConfig.previewId : '';
34	var previewNoncePresent = !!formConfig.previewNoncePresent;
35	var thumbnailIdPresent = !!formConfig.thumbnailIdPresent;
36	var debugQueryDetected = window.location.search.indexOf('wht_debug_form=1') !== -1;
37	var isDebugMode = !!formConfig.debugForm || debugQueryDetected;
38	var mobileMarkerVisible = !!formConfig.mobileMarkerVisible || debugQueryDetected;
39	var mobileDebugVisible = !!formConfig.mobileDebugVisible || mobileMarkerVisible || debugQueryDetected;
40	var rootWrapperExists = root.classList.contains('wht-home-code-build-root');
41	var lastVideoTrigger = null;
42
43	if (isDebugMode) {
44		root.classList.add('wht-home-code--debug');
45	}
46
47	function normalizeText(value) {
48		return (value || '').replace(/\s+/g, ' ').trim().toLowerCase();
49	}
50
51	function cleanHeaderNavigation() {
52		var header = document.querySelector('header, .site-header, #masthead');
53		var navList;
54		var existingToursGroup;
55		var existingPrivateGroup;
56		var groupItem;
57		var groupButton;
58		var subMenu;
59		var privateGroupItem;
60		var privateGroupButton;
61		var privateSubMenu;
62		var hasContact = false;
63		var routes = [
64			{
65				label: 'M\u00e1laga Wine Tour',
66				url: window.location.origin + '/tour/wine-tour-malaga/'
67			},
68			{
69				label: 'Organic Olive Oil & Wine Tour',
70				url: window.location.origin + '/tour/olive-oil-wine-tour-malaga/'
71			},
72			{
73				label: 'Mijas Vineyard Wine & Olive Oil Tasting',
74				url: window.location.origin + '/tour/top-mijas-vineyard-tasting/'
75			},
76			{
77				label: 'M\u00e1laga City Wine Tasting',
78				url: window.location.origin + '/tour/malaga-city-wine-tasting/'
79			}
80		];
81		var privateRoutes = [
82			{
83				label: 'Private tours',
84				url: window.location.origin + '/private-tours/'
85			},
86			{
87				label: 'Business tours',
88				url: '#wht-home-request'
89			}
90		];
91		var hiddenLabels = [
92			'regular tours',
93			'small-group tours',
94			'small group tours',
95			'tours & tastings',
96			'tours and tastings',
97			'mijas vineyard wine & olive oil tasting',
98			'vineyard wine & olive oil tasting',
99			'm\u00e1laga city wine tasting',
100			'malaga city wine tasting',
101			'wine tour from m\u00e1laga',
102			'wine tour from malaga',
103			'm\u00e1laga wine tour',
104			'malaga wine tour',
105			'organic olive oil & wine tour from m\u00e1laga',
106			'organic olive oil & wine tour from malaga',
107			'organic wine & olive oil tour from m\u00e1laga',
108			'organic wine & olive oil tour from malaga',
109			'organic wine & olive oil tour',
110			'olive oil & wine tour'
111		];
112
113		if (!header) {
114			return;
115		}
116
117		Array.prototype.forEach.call(header.querySelectorAll('a'), function (link) {
118			var linkText = normalizeText(link.textContent);
119
120			if (linkText === 'other experiences' || linkText.indexOf('other experiences') === 0) {
121				(link.closest('li, .menu-item') || link).classList.add('wht-home-header-hidden-item');
122			}
123
124			if (hiddenLabels.indexOf(linkText) !== -1) {
125				(link.closest('li, .menu-item') || link).classList.add('wht-home-header-hidden-item');
126			}
127
128			if (linkText === 'private & business tours' || linkText === 'private and business tours' || linkText.indexOf('private & business tours') === 0 || linkText.indexOf('private and business tours') === 0) {
129				(link.closest('li, .menu-item') || link).classList.add('wht-home-header-hidden-item');
130			}
131
132			if (linkText === 'private tours / events' || linkText === 'private tours & events' || linkText === 'private tours and events') {
133				(link.closest('li, .menu-item') || link).classList.add('wht-home-header-hidden-item');
134			}
135
136			if (linkText === 'wine shop' || linkText === 'shop') {
137				link.classList.add('wht-home-code-wine-shop-link');
138				link.setAttribute('href', '/shop/');
139			}
140
141			if (linkText === 'contact' || link.getAttribute('href') === '#wht-home-request') {
142				hasContact = true;
143			}
144		});
145
146		navList = header.querySelector('nav ul, .main-navigation ul, .ast-builder
146-menu ul');
147		existingToursGroup = header.querySelector('.wht-home-code-menu-group');
148		existingPrivateGroup = header.querySelector('.wht-home-code-private-menu-group');
149
150		if (!navList) {
151			return;
152		}
153
154		if (!existingToursGroup) {
155			groupItem = document.createElement('li');
156			groupItem.className = 'menu-item wht-home-code-menu-group';
157
158			groupButton = document.createElement('button');
159			groupButton.className = 'wht-home-code-menu-button';
160			groupButton.type = 'button';
161			groupButton.setAttribute('aria-haspopup', 'true');
162			groupButton.textContent = 'Tours & Tastings';
163
164			subMenu = document.createElement('ul');
165			subMenu.className = 'wht-home-code-submenu';
166			subMenu.setAttribute('aria-label', 'Tours and tastings');
167
168			routes.forEach(function (route) {
169				var item = document.createElement('li');
170				var link = document.createElement('a');
171
172				link.href = route.url;
173				link.textContent = route.label;
174				item.appendChild(link);
175				subMenu.appendChild(item);
176			});
177
178			groupItem.appendChild(groupButton);
179			groupItem.appendChild(subMenu);
180			navList.insertBefore(groupItem, navList.firstChild);
181		}
182
183		if (!existingPrivateGroup) {
184			privateGroupItem = document.createElement('li');
185			privateGroupItem.className = 'menu-item wht-home-code-menu-group wht-home-code-private-menu-group';
186
187			privateGroupButton = document.createElement('button');
188			privateGroupButton.className = 'wht-home-code-menu-button';
189			privateGroupButton.type = 'button';
190			privateGroupButton.setAttribute('aria-haspopup', 'true');
191			privateGroupButton.textContent = 'Private Tours & Events';
192
193			privateSubMenu = document.createElement('ul');
194			privateSubMenu.className = 'wht-home-code-submenu';
195			privateSubMenu.setAttribute('aria-label', 'Private tours and events');
196
197			privateRoutes.forEach(function (route) {
198				var item = document.createElement('li');
199				var link = document.createElement('a');
200
201				link.href = route.url;
202				link.textContent = route.label;
203				item.appendChild(link);
204				privateSubMenu.appendChild(item);
205			});
206
207			privateGroupItem.appendChild(privateGroupButton);
208			privateGroupItem.appendChild(privateSubMenu);
209
210			if (groupItem && groupItem.parentNode === navList) {
211				navList.insertBefore(privateGroupItem, groupItem.nextSibling);
212			} else {
213				navList.insertBefore(privateGroupItem, navList.firstChild);
214			}
215		}
216
217		if (!hasContact) {
218			var contactItem = document.createElement('li');
219			var contactLink = document.createElement('a');
220
221			contactItem.className = 'menu-item wht-home-code-contact-item';
222			contactLink.href = '#wht-home-request';
223			contactLink.textContent = 'Contact';
224			contactLink.setAttribute('aria-label', 'Contact White Houses Tours');
225			contactItem.appendChild(contactLink);
226			navList.appendChild(contactItem);
227		}
228	}
229
230	function fallbackMobileMenuRoutes() {
231		return [
232			{
233				label: 'M\u00e1laga Wine Tour',
234				url: window.location.origin + '/tour/wine-tour-malaga/'
235			},
236			{
237				label: 'Organic Olive Oil & Wine Tour',
238				url: window.location.origin + '/tour/olive-oil-wine-tour-malaga/'
239			},
240			{
241				label: 'Mijas Vineyard Wine & Olive Oil Tasting',
242				url: window.location.origin + '/tour/top-mijas-vineyard-tasting/'
243			},
244			{
245				label: 'M\u00e1laga City Wine Tasting',
246				url: window.location.origin + '/tour/malaga-city-wine-tasting/'
247			},
248			{
249				label: 'Private & Business Tours',
250				url: '#wht-home-private'
251			},
252			{
253				label: 'Other Experiences',
254				url: '#wht-home-request'
255			},
256			{
257				label: 'Wine Shop',
258				url: '/shop/'
259			}
260		];
261	}
262
263	function buildFallbackMobileHeaderLayer() {
264		var layer = document.createElement('div');
265		var panel = document.createElement('nav');
266		var closeButton = document.createElement('button');
267		var closeIcon = document.createElement('span');
268		var list = document.createElement('ul');
269
270		layer.className = 'wht-home-code-mobile-header-layer';
271		layer.setAttribute('data-wht-mobile-header-layer', '');
272		layer.setAttribute('data-wht-mobile-menu-rendered-by', 'js-fallback');
273		layer.setAttribute('data-wht-plugin-version', pluginVersion);
274
275		if (mobileDebugVisible) {
276			var debug = document.createElement('div');
277			debug.className = 'wht-home-code-mobile-debug-proof';
278			debug.setAttribute('data-wht-mobile-menu-debug', '');
279			debug.setAttribute('data-wht-mobile-version-marker', '');
280			debug.innerHTML = 'WHT compact menu debug &middot; plugin <span data-wht-mobile-debug-plugin>' + pluginVersion + '</span> &middot; breakpoint compact &middot; dedicated panel <span data-wht-mobile-debug-dom>js-fallback</span> &middot; links <span data-wht-mobile-debug-count>0</span> &middot; old panel hidden <span data-wht-mobile-debug-old-hidden>yes</span> &middot; hamburger connected <span data-wht-mobile-debug-connected>pending</span> &middot; open <span data-wht-mobile-debug-open>no</span>';
281			layer.appendChild(debug);
282		}
283
284		panel.id = 'wht-home-code-mobile-menu';
285		panel.className = 'wht-home-code-mobile-panel';
286		panel.setAttribute('data-wht-mobile-menu-panel', '');
287		panel.setAttribute('data-wht-mobile-menu-source', 'js-fallback');
288		panel.setAttribute('aria-label', 'White Houses Tours mobile menu');
289		panel.setAttribute('aria-hidden', 'true');
290		panel.setAttribute('hidden', '');
291
292		closeButton.className = 'wht-home-code-mobile-panel__close';
293		closeButton.type = 'button';
294		closeButton.setAttribute('data-wht-mobile-menu-close', '');
295		closeButton.setAttribute('aria-label', 'Close menu');
296
297		closeIcon.setAttribute('aria-hidden', 'true');
298		closeIcon.innerHTML = '&times;';
299		closeButton.appendChild(closeIcon);
300
301		list.className = 'wht-home-code-mobile-panel__list';
302		list.setAttribute('data-wht-mobile-links-count', '7');
303
304		fallbackMobileMenuRoutes().forEach(function (route) {
305			var item = document.createElement('li');
306			var link = document.createElement('a');
307
308			link.href = route.url;
309			link.textContent = route.label;
310			item.appendChild(link);
311			list.appendChild(item);
312		});
313
314		panel.appendChild(closeButton);
315		panel.appendChild(list);
316		layer.appendChild(panel);
317		document.body.appendChild(layer);
318
319		return layer;
320	}
321
322	function ensureMobileHeaderLayer() {
323		var layer = document.querySelector('[data-wht-mobile-header-layer]');
324
325		if (!layer) {
326			layer = buildFallbackMobileHeaderLayer();
327		}
328
329		return layer;
330	}
331
332	function ensureMobileHeaderPanel() {
333		var header = document.querySelector('header, .site-header, #masthead');
334		var layer = ensureMobileHeaderLayer();
335		var panel = layer ? layer.querySelector('[data-wht-mobile-menu-panel]') : null;
336
337		if (header && layer && layer.parentNode !== header) {
338			header.appendChild(layer);
339		}
340
341		return panel;
342	}
343
344	function isMobileViewport() {
345		return window.matchMedia && window.matchMedia('(max-width: 1199px)').matches;
346	}
347
348	function isThemeMobileMenuOpen(header) {
349		var expandedToggle;
350
351		if (!header) {
352			return false;
353		}
354
355		expandedToggle = header.querySelector('.menu-toggle[aria-expanded="true"], .main-header-menu-toggle[aria-expanded="true"], .ast-mobile-menu-trigger-minimal[aria-expanded="true"]');
356
357		return !!expandedToggle
358			|| !!header.querySelector('.ast-mobile-popup-drawer.active, .ast-mobile-popup-drawer.show, .main-header-bar-navigation.toggle-on, .main-header-bar-navigation.toggled, .main-navigation.toggled, .main-navigation.toggle-on')
359			|| document.body.classList.contains('ast-main-header-nav-open');
360	}
361
362	function updateMobilePanelPosition() {
363		var header = document.querySelector('header, .site-header, #masthead');
364		var rect;
365		var top = 88;
366
367		if (header && header.getBoundingClientRect) {
368			rect = header.getBoundingClientRect();
369			top = Math.max(0, Math.round(rect.bottom));
370		}
371
372		document.documentElement.style.setProperty('--wht-mobile-panel-top', top + 'px');
373		document.body.style.setProperty('--wht-mobile-panel-top', top + 'px');
374	}
375
376	function updateMobileHeaderDebug(connectedCount) {
377		var panel = document.querySelector('[data-wht-mobile-menu-panel]');
378		var debug = document.querySelector('[data-wht-mobile-menu-debug]');
379		var links = panel ? panel.querySelectorAll('a') : [];
380		var toggles = document.querySelectorAll('[data-wht-mobile-menu-bridge="true"]');
381		var connected = typeof connectedCount === 'number' ? connectedCount : toggles.length;
382
383		if (!debug) {
384			return;
385		}
386
387		var pluginNode = debug.querySelector('[data-wht-mobile-debug-plugin]');
388		var domNode = debug.querySelector('[data-wht-mobile-debug-dom]');
389		var countNode = debug.querySelector('[data-wht-mobile-debug-count]');
390		var connectedNode = debug.querySelector('[data-wht-mobile-debug-connected]');
391		var openNode = debug.querySelector('[data-wht-mobile-debug-open]');
392
393		if (pluginNode) {
394			pluginNode.textContent = pluginVersion;
395		}
396
397		if (domNode) {
398			domNode.textContent = panel ? 'yes' : 'no';
399		}
400
401		if (countNode) {
402			countNode.textContent = String(links.length);
403		}
404
405		if (connectedNode) {
406			connectedNode.textContent = connected > 0 ? 'yes' : 'no';
407		}
408
409		if (openNode) {
410			openNode.textContent = document.body.classList.contains('wht-mobile-menu-is-open') ? 'true' : 'false';
411		}
412	}
413
414	function mobileToggleSelector() {
415		return '.menu-toggle, .main-header-menu-toggle, .ast-mobile-menu-trigger-minimal, .ast-mobile-popup-drawer .menu-toggle-close, .ast-button-wrap button, button[aria-label*="Menu"], button[aria-label*="menu"], [aria-controls*="menu"], [class*="menu-toggle"]';
416	}
417
418	function closestMobileToggle(target) {
419		var header = document.querySelector('header, .site-header, #masthead');
420		var toggle = target && target.closest ? target.closest(mobileToggleSelector()) : null;
421
422		if (!toggle || !header || !header.contains(toggle) || toggle.closest('[data-wht-mobile-menu-panel]')) {
423			return null;
424		}
425
426		return toggle;
427	}
428
429	function setMobileToggleState(isOpen) {
430		var header = document.querySelector('header, .site-header, #masthead');
431
432		if (!header) {
433			return;
434		}
435
436		Array.prototype.forEach.call(header.querySelectorAll('[data-wht-mobile-menu-bridge="true"], ' + mobileToggleSelector()), function (toggle) {
437			if (!header.contains(toggle)) {
438				return;
439			}
440
441			toggle.setAttribute('aria-controls', 'wht-home-code-mobile-menu');
442			toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
443			toggle.classList.toggle('wht-home-code-mobile-toggle-open', !!isOpen);
444		});
445	}
446
447	function setMobileHeaderPanelOpen(isOpen) {
448		var header = document.querySelector('header, .site-header, #masthead');
449		var panel = ensureMobileHeaderPanel();
450
451		if (!header || !panel) {
452			updateMobileHeaderDebug(0);
453			return;
454		}
455
456		if (isOpen && isMobileViewport()) {
457			updateMobilePanelPosition();
458			document.body.classList.add('wht-home-code-mobile-menu-open');
459			document.body.classList.add('wht-mobile-menu-is-open');
460			panel.removeAttribute('hidden');
461			panel.setAttribute('aria-hidden', 'false');
462			setMobileToggleState(true);
463			updateMobileHeaderDebug();
464			return;
465		}
466
467		document.body.classList.remove('wht-home-code-mobile-menu-open');
468		document.body.classList.remove('wht-mobile-menu-is-open');
469		panel.setAttribute('hidden', '');
470		panel.setAttribute('aria-hidden', 'true');
471		setMobileToggleState(false);
472		updateMobileHeaderDebug();
473	}
474
475	function syncMobileHeaderPanel() {
476		var header = document.querySelector('header, .site-header, #masthead');
477
478		if (!isMobileViewport()) {
479			setMobileHeaderPanelOpen(false);
480			return;
481		}
482
483		if (document.body.classList.contains('wht-mobile-menu-is-open')) {
484			updateMobilePanelPosition();
485			updateMobileHeaderDebug();
486			return;
487		}
488
489		setMobileHeaderPanelOpen(isThemeMobileMenuOpen(header));
490	}
491
492	function connectMobileHeaderToggles(header) {
493		var connectedCount = 0;
494
495		if (!header) {
496			return connectedCount;
497		}
498
499		Array.prototype.forEach.call(header.querySelectorAll(mobileToggleSelector() + ', [aria-controls="wht-home-code-mobile-menu"]'), function (toggle) {
500			if (toggle.getAttribute('data-wht-mobile-menu-bridge') === 'true') {
501				connectedCount += 1;
502				return;
503			}
504
505			toggle.setAttribute('data-wht-mobile-menu-bridge', 'true');
506			toggle.setAttribute('aria-controls', 'wht-home-code-mobile-menu');
507			toggle.addEventListener('click', function (event) {
508				var wasOpen = document.body.classList.contains('wht-mobile-menu-is-open');
509
510				if (isMobileViewport()) {
511					event.preventDefault();
512					event.stopPropagation();
513					if (event.stopImmediatePropagation) {
514						event.stopImmediatePropagation();
515					}
516				}
517
518				setMobileHeaderPanelOpen(!wasOpen);
519			});
520			connectedCount += 1;
521		});
522
523		updateMobileHeaderDebug(connectedCount);
524
525		return connectedCount;
526	}
527
528	function installMobileHeaderPanel() {
529		var header = document.querySelector('header, .site-header, #masthead');
530		var observer;
531
532		if (!header) {
533			return;
534		}
535
536		ensureMobileHeaderPanel();
537		updateMobilePanelPosition();
538		connectMobileHeaderToggles(header);
539
540		if (document.documentElement.getAttribute('data-wht-mobile-capture-installed') !== 'true') {
541			document.documentElement.setAttribute('data-wht-mobile-capture-installed', 'true');
542			document.addEventListener('click', function (event) {
543				var toggle;
544				var wasOpen;
545				var mobilePanel;
546
547				if (!isMobileViewport()) {
548					return;
549				}
550
551				toggle = closestMobileToggle(event.target);
552
553				if (!toggle) {
554					mobilePanel = document.querySelector('[data-wht-mobile-menu-panel]');
555					if (
556						document.body.classList.contains('wht-mobile-menu-is-open') &&
557						!header.contains(event.target) &&
558						(!mobilePanel || !mobilePanel.contains(event.target))
559					) {
560						setMobileHeaderPanelOpen(false);
561					}
562					return;
563				}
564
565				wasOpen = document.body.classList.contains('wht-mobile-menu-is-open');
566				event.preventDefault();
567				event.stopPropagation();
568
569				if (event.stopImmediatePropagation) {
570					event.stopImmediatePropagation();
571				}
572
573				toggle.setAttribute('data-wht-mobile-menu-bridge', 'true');
574				setMobileHeaderPanelOpen(!wasOpen);
575			}, true);
576		}
577
578		header.addEventListener('click', function (event) {
579			if (event.target.closest('[data-wht-mobile-menu-close]')) {
580				event.preventDefault();
581				setMobileHeaderPanelOpen(false);
582				return;
583			}
584
585			if (event.target.closest('[data-wht-mobile-menu-panel] a')) {
586				setMobileHeaderPanelOpen(false);
587			}
588		});
589
590		document.addEventListener('keydown', function (event) {
591			if (event.key === 'Escape') {
592				setMobileHeaderPanelOpen(false);
593			}
594		});
595
596		if (window.MutationObserver) {
597			observer = new MutationObserver(function () {
598				window.setTimeout(function () {
599					connectMobileHeaderToggles(header);
600					updateMobilePanelPosition();
601					updateMobileHeaderDebug();
602				}, 0);
603			});
604			observer.observe(header, {
605				attributes: true,
606				subtree: true,
607				attributeFilter: ['class', 'aria-expanded']
608			});
609		}
610
611		window.addEventListener('resize', function () {
612			updateMobilePanelPosition();
613			syncMobileHeaderPanel();
614		});
615		syncMobileHeaderPanel();
616		updateMobileHeaderDebug();
617	}
618
619	function positionHeaderSubmenus() {
620		var header = document.querySelector('header, .site-header, #masthead');
621		var viewportPadding = 16;
622
623		if (!header) {
624			return;
625		}
626
627		Array.prototype.forEach.call(header.querySelectorAll('.wht-home-code-submenu'), function (subMenu) {
628			var rect;
629			var shift = 0;
630
631			subMenu.style.setProperty('--wht-submenu-shift', '0px');
632			rect = subMenu.getBoundingClientRect();
633
634			if (rect.left < viewportPadding) {
635				shift += viewportPadding - rect.left;
636			}
637
638			if (rect.right + shift > window.innerWidth - viewportPadding) {
639				shift -= rect.right + shift - (window.innerWidth - viewportPadding);
640			}
641
642			subMenu.style.setProperty('--wht-submenu-shift', shift.toFixed(0) + 'px');
643		});
644	}
645
646	cleanHeaderNavigation();
647	installMobileHeaderPanel();
648	positionHeaderSubmenus();
649	window.setTimeout(positionHeaderSubmenus, 0);
650	window.addEventListener('resize', positionHeaderSubmenus);
651
652	function ensureCardDebugMarker() {
653		if (!isDebugMode || cardDebugMarker) {
654			return;
655		}
656
657		cardDebugMarker = document.createElement('div');
658		cardDebugMarker.className = 'wht-home-code__card-debug-marker';
659		cardDebugMarker.setAttribute('data-wht-card-debug-marker', '');
660		cardDebugMarker.setAttribute('data-wht-card-selector', '.wht-home-code__tour-card');
661
662		if (tourGrid && tourGrid.parentNode) {
663			tourGrid.parentNode.insertBefore(cardDebugMarker, tourGrid);
664			return;
665		}
666
667		root.insertBefore(cardDebugMarker, root.firstChild);
668	}
669
670	function updateCardDebugMarker() {
671		var cardSelector = '.wht-home-code__tour-card';
672		var cards;
673		var bulletLists;
674		var heights = [];
675		var overflows = [];
676		var topGaps = [];
677		var actionBottomGaps = [];
678		var bulletBottomGaps = [];
679		var bodyPaddingTopValues = [];
680		var bodyPaddingBottomValues = [];
681		var ctaFoundCount = 0;
682		var ctaClipped = false;
683		var ctaClipDetails = [];
684		var cssSourceMarker = 'unknown';
685		var criticalCssLoaded = criticalCssVersion;
686		var microfactsStyle = 'unknown';
687		var microfactsLineStripCount = 0;
688		var microfactsDetails = [];
689		var mediaDimensions = [];
690		var mediaStable = true;
691		var allEqual;
692		var withinAcceptedHeight;
693		var bulletsSingleColumn = true;
694		var bulletLayoutDetails = [];
695
696		if (!isDebugMode) {
697			return;
698		}
699
700		ensureCardDebugMarker();
701
702		if (!cardDebugMarker) {
703			return;
704		}
705
706		cards = root.querySelectorAll(cardSelector);
707		bulletLists = root.querySelectorAll(cardSelector + ' .wht-home-code__bullet-list');
708		if (window.getComputedStyle) {
709			var rootStyles = window.getComputedStyle(root);
710			cssSourceMarker = (rootStyles.getPropertyValue('--wht-card-css-source') || 'unknown').replace(/["']/g, '').trim();
711			criticalCssLoaded = (rootStyles.getPropertyValue('--wht-card-critical-css-version') || criticalCssVersion || 'unknown').replace(/["']/g, '').trim();
712			microfactsStyle = (rootStyles.getPropertyValue('--wht-card-microfacts-style') || 'unknown').trim();
713		}
714
715		Array.prototype.forEach.call(cards, function (card, index) {
716			var rect = card.getBoundingClientRect();
717			var firstText = card.querySelector('.wht-home-code__card-label');
718			var actions = card.querySelector('.wht-home-code__tour-actions');
719			var cta = actions ? actions.querySelector('.wht-home-code__text-link') : null;
720			var bullets = card.querySelector('.wht-home-code__bullet-list');
721			var facts = card.querySelector('.wht-home-code__tour-facts');
722			var factList = card.querySelector('.wht-home-code__fact-list');
723			var media = card.querySelector('.wht-home-code__tour-image-wrap');
724			var body = card.querySelector('.wht-home-code__tour-body');
725			var bodyStyles = body ? window.getComputedStyle(body) : null;
726			var factsStyles = facts ? window.getComputedStyle(facts) : null;
727			var factItem = factList ? factList.querySelector('li') : null;
728			var factItemStyles = factItem ? window.getComputedStyle(factItem) : null;
729			var height = Math.round(rect.height);
730			var contentOverflows = card.scrollHeight >
730 card.clientHeight + 1 || card.scrollWidth > card.clientWidth + 1;
731			var firstTextTop = firstText ? Math.round(firstText.getBoundingClientRect().top - rect.top) : 0;
732			var actionBottom = actions ? Math.round(rect.bottom - actions.getBoundingClientRect().bottom) : 0;
733			var bulletBottom = bullets ? Math.round(rect.bottom - bullets.getBoundingClientRect().bottom) : 0;
734			var bodyPaddingTop = bodyStyles ? Math.round(parseFloat(bodyStyles.paddingTop) || 0) : 0;
735			var bodyPaddingBottom = bodyStyles ? Math.round(parseFloat(bodyStyles.paddingBottom) || 0) : 0;
736			var mediaRect = media ? media.getBoundingClientRect() : null;
737			var mediaWidth = mediaRect ? Math.round(mediaRect.width) : 0;
738			var mediaHeight = mediaRect ? Math.round(mediaRect.height) : 0;
739			var ctaRect;
740			var ctaIsClipped = false;
741
742			heights.push(height);
743			overflows.push(contentOverflows);
744			topGaps.push(firstTextTop);
745			actionBottomGaps.push(actionBottom);
746			bulletBottomGaps.push(bulletBottom);
747			bodyPaddingTopValues.push(bodyPaddingTop);
748			bodyPaddingBottomValues.push(bodyPaddingBottom);
749			mediaDimensions.push(mediaWidth + 'x' + mediaHeight);
750
751			if (!mediaRect || mediaWidth <= 0 || mediaHeight <= 0 || Math.abs(mediaHeight - height) > 18) {
752				mediaStable = false;
753			}
754
755			if (cta) {
756				ctaFoundCount += 1;
757				ctaRect = cta.getBoundingClientRect();
758				ctaIsClipped = ctaRect.left < rect.left || ctaRect.right > rect.right || ctaRect.top < rect.top || ctaRect.bottom > rect.bottom || cta.scrollWidth > cta.clientWidth + 1 || cta.scrollHeight > cta.clientHeight + 1;
759				if (ctaIsClipped) {
760					ctaClipped = true;
761				}
762				ctaClipDetails.push('card ' + (index + 1) + ': ' + (ctaIsClipped ? 'clipped' : 'ok'));
763			} else {
764				ctaClipped = true;
765				ctaClipDetails.push('card ' + (index + 1) + ': missing');
766			}
767
768			if (factsStyles && factItemStyles) {
769				var hasStripLines = parseFloat(factsStyles.borderTopWidth) > 0 && parseFloat(factsStyles.borderBottomWidth) > 0;
770				var hasNoChip = factItemStyles.backgroundColor === 'rgba(0, 0, 0, 0)' && (factItemStyles.borderTopStyle === 'none' || parseFloat(factItemStyles.borderTopWidth) === 0);
771				if (hasStripLines && hasNoChip) {
772					microfactsLineStripCount += 1;
773				}
774				microfactsDetails.push('card ' + (index + 1) + ': ' + (hasStripLines && hasNoChip ? 'line-strip' : 'check-css'));
775			}
776
777			card.setAttribute('data-wht-card-debug-label', 'card ' + (index + 1) + ' \u00b7 ' + height + 'px');
778		});
779
780		Array.prototype.forEach.call(bulletLists, function (list, index) {
781			var styles = window.getComputedStyle(list);
782			var gridColumns = styles.gridTemplateColumns || 'none';
783			var columnCount = styles.columnCount || 'auto';
784			var display = styles.display || '';
785			var flexWrap = styles.flexWrap || '';
786			var gridColumnCount = gridColumns.split(' ').filter(Boolean).length;
787			var isSingle = columnCount !== '2' && gridColumnCount <= 1 && !(display.indexOf('flex') !== -1 && flexWrap !== 'nowrap');
788
789			if (!isSingle) {
790				bulletsSingleColumn = false;
791			}
792
793			bulletLayoutDetails.push('card ' + (index + 1) + ': ' + gridColumns + ', columns ' + columnCount + ', display ' + display + ', wrap ' + flexWrap);
794		});
795
796		allEqual = heights.length === 4 && heights.every(function (height) {
797			return height === heights[0];
798		});
799		withinAcceptedHeight = heights.length === 4 && heights.every(function (height) {
800			return height >= 336 && height <= 350;
801		});
802
803		cardDebugMarker.hidden = false;
804		cardDebugMarker.textContent = 'WHT card debug \u00b7 plugin ' + pluginVersion + ' \u00b7 preview_mode ' + previewMode + ' \u00b7 page_id ' + previewPageId + ' \u00b7 preview_id ' + previewId + ' \u00b7 preview_nonce present ' + (previewNoncePresent ? 'yes' : 'no') + ' \u00b7 _thumbnail_id present ' + (thumbnailIdPresent ? 'yes' : 'no') + ' \u00b7 css url ' + (cssUrl || 'n/a') + ' \u00b7 css ' + cssVersion + ' \u00b7 critical css ' + criticalCssLoaded + ' \u00b7 js url ' + (jsUrl || 'n/a') + ' \u00b7 js ' + jsAssetVersion + ' \u00b7 root wrapper exists ' + (rootWrapperExists ? 'yes' : 'no') + ' \u00b7 body helper existed before JS ' + (bodyHadHelperClass ? 'yes' : 'no') + ' \u00b7 body helper class exists ' + (document.body.classList.contains('wht-home-code-page') ? 'yes' : 'no') + ' \u00b7 css source ' + cssSourceMarker + ' \u00b7 microfacts ' + microfactsStyle + ' \u00b7 microfacts loaded: ' + (microfactsLineStripCount === 4 ? 'line-strip' : 'check-css') + ' \u00b7 height selector ' + cardSelector + ' \u00b7 card heights: ' + (heights.length ? heights.join(' / ') : 'none') + ' px \u00b7 equal: ' + (allEqual ? 'yes' : 'no') + ' \u00b7 height 336-350px: ' + (withinAcceptedHeight ? 'yes' : 'no') + ' \u00b7 media: ' + (mediaDimensions.length ? mediaDimensions.join(' / ') : 'none') + ' \u00b7 media stable: ' + (mediaStable ? 'yes' : 'no') + ' \u00b7 content top padding: ' + (bodyPaddingTopValues.length ? bodyPaddingTopValues.join(' / ') : 'none') + ' px \u00b7 content bottom padding: ' + (bodyPaddingBottomValues.length ? bodyPaddingBottomValues.join(' / ') : 'none') + ' px \u00b7 first text top gaps: ' + (topGaps.length ? topGaps.join(' / ') : 'none') + ' px \u00b7 CTA found: ' + ctaFoundCount + '/4 \u00b7 CTA clipped: ' + (ctaClipped ? 'yes' : 'no') + ' \u00b7 CTA details: ' + (ctaClipDetails.length ? ctaClipDetails.join(' | ') : 'none') + ' \u00b7 CTA bottom gaps: ' + (actionBottomGaps.length ? actionBottomGaps.join(' / ') : 'none') + ' px \u00b7 bullet bottom gaps: ' + (bulletBottomGaps.length ? bulletBottomGaps.join(' / ') : 'none') + ' px \u00b7 bullet layout: ' + (bulletsSingleColumn ? 'single-column' : 'not single-column') + ' \u00b7 microfacts details: ' + (microfactsDetails.length ? microfactsDetails.join(' | ') : 'none') + ' \u00b7 bullet selector .wht-home-code__bullet-list \u00b7 bullet computed: ' + (bulletLayoutDetails.length ? bulletLayoutDetails.join(' | ') : 'none') + ' \u00b7 overflow: ' + (overflows.indexOf(true) === -1 ? 'no' : 'yes');
805	}
806
807	if (isDebugMode) {
808		updateCardDebugMarker();
809		window.setTimeout(updateCardDebugMarker, 0);
810		window.setTimeout(updateCardDebugMarker, 500);
811		window.addEventListener('load', updateCardDebugMarker);
812		window.addEventListener('resize', updateCardDebugMarker);
813	}
814
815	function buildYoutubeEmbedUrl(videoId) {
816		return 'https://www.youtube-nocookie.com/embed/' + encodeURIComponent(videoId) + '?rel=0&modestbranding=1&playsinline=1';
817	}
818
819	function openVideoModal(trigger) {
820		var videoId = trigger.getAttribute('data-wht-video-id');
821		var videoTitle = trigger.getAttribute('data-wht-video-title') || 'Experience preview';
822
823		if (!videoModal || !videoFrame || !videoId) {
824			return;
825		}
826
827		lastVideoTrigger = trigger;
828		videoFrame.setAttribute('src', buildYoutubeEmbedUrl(videoId));
829		videoFrame.setAttribute('title', videoTitle);
830
831		if (videoModalTitle) {
832			videoModalTitle.textContent = videoTitle;
833		}
834
835		videoModal.hidden = false;
836		videoModal.setAttribute('aria-hidden', 'false');
837		document.body.classList.add('wht-home-code-modal-open');
838
839		if (videoCloseButton) {
840			videoCloseButton.focus();
841		}
842	}
843
844	function closeVideoModal() {
845		if (!videoModal || videoModal.hidden) {
846			return;
847		}
848
849		videoModal.hidden = true;
850		videoModal.setAttribute('aria-hidden', 'true');
851		document.body.classList.remove('wht-home-code-modal-open');
852
853		if (videoFrame) {
854			videoFrame.setAttribute('src', '');
855			videoFrame.setAttribute('title', 'Experience preview');
856		}
857
858		if (videoModalTitle) {
859			videoModalTitle.textContent = 'Experience preview';
860		}
861
862		if (lastVideoTrigger) {
863			lastVideoTrigger.focus();
864			lastVideoTrigger = null;
865		}
866	}
867
868	if (videoModal && videoFrame && videoTriggers.length) {
869		Array.prototype.forEach.call(videoTriggers, function (trigger) {
870			trigger.addEventListener('click', function () {
871				openVideoModal(trigger);
872			});
873		});
874
875		videoModal.addEventListener('click', function (event) {
876			var closeTarget = event.target.closest ? event.target.closest('[data-wht-video-close]') : null;
877
878			if (closeTarget) {
879				closeVideoModal();
880			}
881		});
882
883		document.addEventListener('keydown', function (event) {
884			if (event.key === 'Escape') {
885				closeVideoModal();
886			}
887		});
888	}
889
890	if (reviewCarousel) {
891		var reviewTrack = reviewCarousel.querySelector('[data-wht-review-track]');
892		var reviewPrev = root.querySelector('[data-wht-review-prev]');
893		var reviewNext = root.querySelector('[data-wht-review-next]');
894		var reviewScrollTimer;
895
896		function updateReviewControls() {
897			if (!reviewTrack || !reviewPrev || !reviewNext) {
898				return;
899			}
900
901			var maxScroll = reviewTrack.scrollWidth - reviewTrack.clientWidth - 2;
902
903			reviewPrev.disabled = reviewTrack.scrollLeft <= 2;
904			reviewNext.disabled = reviewTrack.scrollLeft >= maxScroll;
905		}
906
907		function scrollReviews(direction) {
908			if (!reviewTrack) {
909				return;
910			}
911
912			reviewTrack.scrollBy({
913				left: direction * Math.max(260, reviewTrack.clientWidth * 0.9),
914				behavior: 'smooth'
915			});
916		}
917
918		if (reviewPrev && reviewNext && reviewTrack) {
919			reviewPrev.addEventListener('click', function () {
920				scrollReviews(-1);
921			});
922
923			reviewNext.addEventListener('click', function () {
924				scrollReviews(1);
925			});
926
927			reviewTrack.addEventListener('scroll', function () {
928				window.clearTimeout(reviewScrollTimer);
929				reviewScrollTimer = window.setTimeout(updateReviewControls, 80);
930			});
931
932			window.addEventListener('resize', updateReviewControls);
933			updateReviewControls();
934		}
935	}
936
937	if (requestForm && requestForm.dataset.whtRequestBound !== '1') {
938		requestForm.dataset.whtRequestBound = '1';
939
940		var formMessage = requestForm.querySelector('[data-wht-form-message]');
941		var debugMarker = requestForm.querySelector('[data-wht-js-form-debug-marker]');
942		var debugPanel = requestForm.querySelector('[data-wht-form-debug-panel]');
943		var selfTestResult = requestForm.querySelector('[data-wht-self-test-result]');
944		var mailSelfTestButton = requestForm.querySelector('[data-wht-mail-self-test]');
945		var ajaxSelfTestButton = requestForm.querySelector('[data-wht-ajax-self-test]');
946		var copyDiagnosticButton = requestForm.querySelector('[data-wht-copy-diagnostic]');
947		var submitButton = requestForm.querySelector('button[type="submit"]');
948		var nameField = requestForm.querySelector('[name="wht_home_code_name"]');
949		var emailField = requestForm.querySelector('[name="wht_home_code_email"]');
950		var messageField = requestForm.querySelector('[name="wht_home_code_message"]');
951		var pageUrlField = requestForm.querySelector('[name="wht_home_code_page_url"]');
952		var nonceField = requestForm.querySelector('[name="wht_home_code_nonce"]');
953		var requestIdField = requestForm.querySelector('[name="wht_home_code_request_id"]');
954		var originalSubmitHtml = submitButton ? submitButton.innerHTML : '';
955		var isFormDebug = isDebugMode;
956		var formMatchCount = root.querySelectorAll('[data-wht-request-form]').length;
957		var handlerAttachCount = parseInt(requestForm.getAttribute('data-wht-handler-count') || '0', 10);
958		var isSubmitting = false;
959		var activeRequestId = '';
960		var diagnosticState = {
961			lastSubmit: null,
962			lastSelfTest: null,
963			jsErrors: [],
964			unhandledRejections: []
965		};
966
967		if (isNaN(handlerAttachCount)) {
968			handlerAttachCount = 0;
969		}
970
971		handlerAttachCount += 1;
972		requestForm.setAttribute('data-wht-handler-count', String(handlerAttachCount));
973
974		if (isFormDebug && !debugMarker) {
975			debugMarker = document.createElement('div');
976			debugMarker.className = 'wht-home-code__form-debug-marker wht-home-code__form-debug-marker--js';
977			debugMarker.setAttribute('data-wht-js-form-debug-marker', '');
978			requestForm.insertBefore(debugMarker, formMessage || requestForm.firstChild);
979		}
980
981		if (isFormDebug && !debugPanel) {
982			debugPanel = document.createElement('div');
983			debugPanel.className = 'wht-home-code__form-debug-panel';
984			debugPanel.setAttribute('data-wht-form-debug-panel', '');
985			debugPanel.hidden = true;
986
987			if (formMessage && formMessage.nextSibling) {
988				requestForm.insertBefore(debugPanel, formMessage.nextSibling);
989			} else {
990				requestForm.insertBefore(debugPanel, requestForm.firstChild);
991			}
992		}
993
994		function yesNo(value) {
995			return value ? 'yes' : 'no';
996		}
997
998		function boolDebugValue(value) {
999			if (value === true) {
1000				return 'true';
1001			}
1002
1003			if (value === false) {
1004				return 'false';
1005			}
1006
1007			return 'n/a';
1008		}
1009
1010		function responseLooksBlocked(value) {
1011			var lowered = String(value || '').toLowerCase();
1012
1013			return lowered.indexOf('apbct') !== -1 || lowered.indexOf('cleantalk') !== -1 || lowered.indexOf('blocked') !== -1 || lowered.indexOf('forbidden') !== -1 || lowered.indexOf('spam check') !== -1;
1014		}
1015
1016		function containsSubmittedPii(value) {
1017			var lowered = String(value || '').toLowerCase();
1018			var fieldValues = [nameField, emailField, messageField].map(function (field) {
1019				return field && field.value ? String(field.value).trim().toLowerCase() : '';
1020			}).filter(function (fieldValue) {
1021				return fieldValue.length >= 4;
1022			});
1023
1024			return fieldValues.some(function (fieldValue) {
1025				return lowered.indexOf(fieldValue) !== -1;
1026			});
1027		}
1028
1029		function safeRawResponsePreview(value) {
1030			var text = String(value || '').trim();
1031
1032			if (!text || containsSubmittedPii(text)) {
1033				return 'not shown';
1034			}
1035
1036			return text.replace(/\s+/g, ' ').slice(0, 360);
1037		}
1038
1039		function safeDiagnosticText(value) {
1040			var text = String(value || '').trim();
1041
1042			if (!text || containsSubmittedPii(text)) {
1043				return 'not shown';
1044			}
1045
1046			return text.replace(/\s+/g, ' ').slice(0, 240);
1047		}
1048
1049		function describeError(error) {
1050			if (!error) {
1051				return 'unknown';
1052			}
1053
1054			if (typeof error === 'string') {
1055				return safeDiagnosticText(error);
1056			}
1057
1058			if (error.message) {
1059				return safeDiagnosticText(error.name ? error.name + ': ' + error.message : error.message);
1060			}
1061
1062			return safeDiagnosticText(String(error));
1063		}
1064
1065		function recordJsDiagnostic(type, error) {
1066			var entry = {
1067				type: type,
1068				message: describeError(error),
1069				timestamp: new Date().toISOString()
1070			};
1071
1072			if (type === 'unhandledrejection') {
1073				diagnosticState.unhandledRejections.push(entry);
1074				diagnosticState.unhandledRejections = diagnosticState.unhandledRejections.slice(-5);
1075			} else {
1076				diagnosticState.jsErrors.push(entry);
1077				diagnosticState.jsErrors = diagnosticState.jsErrors.slice(-5);
1078			}
1079
1080			return entry;
1081		}
1082
1083		function latestDiagnosticEntry(list) {
1084			return list && list.length ? list[list.length - 1] : null;
1085		}
1086
1087		if (isFormDebug && !window.whtHomeCodeBuildDebugErrorsBound) {
1088			window.whtHomeCodeBuildDebugErrorsBound = true;
1089			window.addEventListener('error', function (event) {
1090				recordJsDiagnostic('window.onerror', event.error || event.message || 'window error');
1091			});
1092			window.addEventListener('unhandledrejection', function (event) {
1093				recordJsDiagnostic('unhandledrejection', event.reason || 'unhandled rejection');
1094			});
1095		}
1096
1097		function escapeHtml(value) {
1098			return String(value == null ? '' : value)
1099				.replace(/&/g, '&amp;')
1100				.replace(/</g, '&lt;')
1101				.replace(/>/g, '&gt;')
1102				.replace(/"/g, '&quot;')
1103				.replace(/'/g, '&#039;');
1104		}
1105
1106		function showFormMessage(type, message) {
1107			if (!formMessage) {
1108				return;
1109			}
1110
1111			formMessage.hidden = false;
1112			formMessage.classList.remove('wht-home-code__form-message--success');
1113			formMessage.classList.remove('wht-home-code__form-message--error');
1114			formMessage.classList.add('wht-home-code__form-message--' + type);
1115			formMessage.textContent = message;
1116		}
1117
1118		function clearDebugPanel() {
1119			if (!debugPanel) {
1120				return;
1121			}
1122
1123			debugPanel.hidden = true;
1124			debugPanel.textContent = '';
1125		}
1126
1127		function updateDebugMarker() {
1128			if (!isFormDebug || !debugMarker) {
1129				return;
1130			}
1131
1132			debugMarker.hidden = false;
1133			debugMarker.textContent = 'WHT JS form debug active \u00b7 js ' + jsVersion + ' \u00b7 preview_mode ' + previewMode + ' \u00b7 submit handler attached \u00b7 forms found: ' + formMatchCount + ' \u00b7 handlers: ' + handlerAttachCount + ' \u00b7 css asset ' + cssVersion + ' \u00b7 js asset ' + jsAssetVersion + ' \u00b7 critical css ' + criticalCssVersion + ' \u00b7 debug param detected by JS: ' + (debugQueryDetected ? 'yes' : 'no') + ' \u00b7 admin self-tests: ' + (formConfig.canRunSelfTests ? 'available' : 'not available') + ' \u00b7 ajax config: ' + (formConfig.ajaxUrl ? 'admin_ajax yes' : 'admin_ajax missing') + ', ' + (formConfig.fallbackUrl ? 'fallback yes' : 'fallback missing') + ', ' + (formConfig.nonce ? 'nonce yes' : 'nonce missing');
1134		}
1135
1136		function setFormLoading(isLoading) {
1137			requestForm.classList.toggle('is-loading', isLoading);
1138			requestForm.setAttribute('aria-busy', isLoading ? 'true' : 'false');
1139
1140			if (submitButton) {
1141				submitButton.disabled = isLoading;
1142				submitButton.innerHTML = isLoading ? 'Sending...' : originalSubmitHtml;
1143			}
1144		}
1145
1146		function createRequestId() {
1147			var randomPart = Math.random().toString(36).slice(2, 10);
1148
1149			if (window.crypto && typeof window.crypto.getRandomValues === 'function') {
1150				var bytes = new Uint32Array(2);
1151				window.crypto.getRandomValues(bytes);
1152				randomPart = bytes[0].toString(36) + bytes[1].toString(36);
1153			}
1154
1155			return 'wht-' + Date.now().toString(36) + '-' + randomPart;
1156		}
1157
1158		function endpointTypeFor(url) {
1159			if (!url) {
1160				return 'unknown';
1161			}
1162
1163			if (formConfig.fallbackUrl && url === formConfig.fallbackUrl) {
1164				return 'fallback';
1165			}
1166
1167			if (url.indexOf('admin-ajax.php') !== -1) {
1168				return 'admin_ajax';
1169			}
1170
1171			return 'unknown';
1172		}
1173
1174		function buildFormData(requestId) {
1175			if (pageUrlField) {
1176				pageUrlField.value = window.location.href;
1177			}
1178
1179			if (nonceField && formConfig.nonce) {
1180				nonceField.value = formConfig.nonce;
1181			}
1182
1183			var formData = new FormData(requestForm);
1184			formData.set('action', 'wht_home_code_request');
1185			formData.set('wht_home_code_debug', isFormDebug ? '1' : '0');
1186			formData.set('wht_home_code_request_id', requestId);
1187
1188			if (requestIdField) {
1189				requestIdField.value = requestId;
1190			}
1191
1192			return formData;
1193		}
1194
1195		function getFieldValue(name) {
1196			var field = requestForm.querySelector('[name="' + name + '"]');
1197
1198			return field ? (field.value || '').trim() : '';
1199		}
1200
1201		function isValidEmail(value) {
1202			return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
1203		}
1204
1205		function validateBeforeSubmit() {
1206			var name = getFieldValue('wht_home_code_name');
1207			var email = getFieldValue('wht_home_code_email');
1208			var message = getFieldValue('wht_home_code_message');
1209
1210			if (messageField && typeof messageField.setCustomValidity === 'function') {
1211				messageField.setCustomValidity('');
1212				messageField.removeAttribute('aria-invalid');
1213			}
1214
1215			if (!name || !email || !message) {
1216				return {
1217					valid: false,
1218					message: 'Please add your name, email and tell us what we can do for you.',
1219					clientValidation: 'missing_fields'
1220				};
1221			}
1222
1223			if (!isValidEmail(email)) {
1224				return {
1225					valid: false,
1226					message: 'Please add a valid email address.',
1227					clientValidation: 'invalid_email'
1228				};
1229			}
1230
1231			if (message.length < 20) {
1232				if (messageField && typeof messageField.setCustomValidity === 'function') {
1233					messageField.setCustomValidity('Please enter at least 20 characters.');
1234					messageField.setAttribute('aria-invalid', 'true');
1235				}
1236
1237				return {
1238					valid: false,
1239					message: 'Please check the highlighted fields and try again.',
1240					fieldMessage: 'Please enter at least 20 characters.',
1241					field: messageField,
1242					clientValidation: 'client_validation_message_too_short'
1243				};
1244			}
1245
1246			return {
1247				valid: true,
1248				message: '',
1249				clientValidation: ''
1250			};
1251		}
1252
1253		if (messageField) {
1254			messageField.addEventListener('input', function () {
1255				if (typeof messageField.setCustomValidity === 'function') {
1256					messageField.setCustomValidity('');
1257				}
1258				messageField.removeAttribute('aria-invalid');
1259			});
1260		}
1261
1262		function createRequestStats(requestId) {
1263			return {
1264				requestId: requestId,
1265				requestsFired: 0,
1266				endpoints: [],
1267				fallbackAttempted: false,
1268				clientValidation: ''
1269			};
1270		}
1271
1272		function noteRequest(stats, endpoint, fallbackUsed) {
1273			if (!stats) {
1274				return;
1275			}
1276
1277			stats.requestsFired += 1;
1278			stats.fallbackAttempted = stats.fallbackAttempted || !!fallbackUsed;
1279			stats.endpoints.push(endpointTypeFor(endpoint) + ': ' + endpoint);
1280		}
1281
1282		function renderDebugPanel(responseData, transport, stats, clientValidation) {
1283			var debug;
1284			var rows;
1285			var notes = [];
1286			var endpoints;
1287			var serverCode;
1288			var requestId;
1289			var html;
1290			var latestError;
1291			var latestRejection;
1292
1293			if (!isFormDebug || !debugPanel) {
1294				return;
1295			}
1296
1297			responseData = responseData || {};
1298			debug = responseData.debug || {};
1299			transport = transport || {};
1300			stats = stats || createRequestStats(responseData.request_id || activeRequestId || 'unknown');
1301			endpoints = stats.endpoints && stats.endpoints.length ? stats.endpoints.join(' | ') : (transport.endpoint || 'none');
1302			serverCode = clientValidation || responseData.code || debug.server_code || 'unknown';
1303			requestId = responseData.request_id || transport.requestId || stats.requestId || activeRequestId || 'unknown';
1304			latestError = latestDiagnosticEntry(diagnosticState.jsErrors);
1305			latestRejection = latestDiagnosticEntry(diagnosticState.unhandledRejections);
1306
1307			rows = [
1308				['request_id', requestId],
1309				['plugin version', debug.plugin_version || responseData.plugin_version || pluginVersion],
1310				['js version', jsVersion],
1311				['js asset version', jsAssetVersion],
1312				['css asset version', cssVersion],
1313				['critical css version', criticalCssVersion],
1314				['preview mode', previewMode],
1315				['page_id', previewPageId],
1316				['preview_id', previewId],
1317				['preview_nonce present', yesNo(previewNoncePresent)],
1318				['_thumbnail_id present', yesNo(thumbnailIdPresent)],
1319				['submit handler', 'attached'],
1320				['handlers attached', handlerAttachCount],
1321				['forms found', formMatchCount],
1322				['debug query param', yesNo(debugQueryDetected)],
1323				['ajax config', (formConfig.ajaxUrl ? 'admin_ajax yes' : 'admin_ajax missing') + ', ' + (formConfig.fallbackUrl ? 'fallback yes' : 'fallback missing') + ', ' + (formConfig.nonce ? 'nonce yes' : 'nonce missing')],
1324				['endpoint used', endpoints],
1325				['endpoint type', transport.endpointType || debug.endpoint_type || 'unknown'],
1326				['requests fired', stats.requestsFired || 0],
1327				['fallback attempted', yesNo(stats.fallbackAttempted || transport.fallbackUsed)],
1328				['HTTP status', typeof transport.httpStatus !== 'undefined' ? transport.httpStatus : 'n/a'],
1329				['response content type', transport.contentType || 'n/a'],
1330				['response shape', transport.responseType || 'unknown'],
1331				['CleanTalk/APBCT detected', yesNo(!!transport.cleantalkDetected || serverCode === 'cleantalk_blocked')],
1332				['JS parsed response.success', boolDebugValue(transport.parsedSuccess)],
1333				['server code', serverCode],
1334				['handler reached', yesNo(!!debug.handler_reached)],
1335				['wp_mail called', yesNo(!!debug.wp_mail_called)],
1336				['wp_mail returned', boolDebugValue(debug.wp_mail_returned)],
1337				['JS errors captured', diagnosticState.jsErrors.length],
1338				['latest JS error', latestError ? latestError.type + ' ' + latestError.message : 'none'],
1339				['unhandled promise rejections captured', diagnosticState.unhandledRejections.length],
1340				['latest unhandled rejection', latestRejection ? latestRejection.message : 'none'],
1341				['raw response preview', transport.rawResponsePreview || 'not shown']
1342			];
1343
1344			diagnosticState.lastSubmit = {
1345				requestId: requestId,
1346				responseData: responseData,
1347				transport: transport,
1348				stats: stats,
1349				serverCode: serverCode,
1350				clientValidation: clientValidation || ''
1351			};
1352
1353			if (clientValidation) {
1354				rows.push(['client validation', clientValidation]);
1355				notes.push('Client validation stopped the request before admin-ajax, fallback or wp_mail.');
1356			}
1357
1358			if (serverCode === 'cleantalk_blocked') {
1359				notes.push('Root cause: CleanTalk/WAF interception. PHP handler reached: no. wp_mail called: no.');
1360				notes.push('Next action: whitelist this specific staging form endpoint/action or adju
1360st CleanTalk settings minimally. Do not disable CleanTalk globally.');
1361			} else if (serverCode === 'mail_failed') {
1362				notes.push('Root cause: wp_mail returned false or the mail transport rejected the message.');
1363				notes.push('Next action: check SMTP/Hostinger mail transport configuration.');
1364			} else if (serverCode === 'handler_not_reached') {
1365				notes.push('Root cause: WordPress AJAX handler was not reached.');
1366				notes.push('Next action: inspect endpoint, nonce, cache, WAF or unexpected HTML/WordPress 0 response.');
1367			} else if (serverCode === 'wp_zero') {
1368				notes.push('Root cause: WordPress returned 0, which usually means the AJAX action did not reach the expected handler.');
1369			} else if (serverCode === 'html_response') {
1370				notes.push('Root cause: endpoint returned HTML instead of WordPress JSON.');
1371			} else if (serverCode === 'invalid_json') {
1372				notes.push('Root cause: endpoint returned non-JSON output, usually HTML, PHP warning/notice, BOM or security plugin output.');
1373			} else if (serverCode === 'ajax_network_error') {
1374				notes.push('Root cause: browser could not complete the fetch request or no AJAX endpoint/config was available.');
1375			} else if (serverCode === 'js_exception') {
1376				notes.push('Root cause: JavaScript exception interrupted the submit flow after the handler attached.');
1377			} else if (serverCode === 'sent_but_ui_failed') {
1378				notes.push('Root cause: server reported success but the UI response did not include the expected sent/mail flag.');
1379			}
1380
1381			debugPanel.hidden = false;
1382			html = '<p class="wht-home-code__form-debug-panel-title">WHT FORM DEBUG ACTIVE</p><dl class="wht-home-code__form-debug-list">';
1383			rows.forEach(function (row) {
1384				html += '<div class="wht-home-code__form-debug-row"><dt>' + escapeHtml(row[0]) + '</dt><dd>' + escapeHtml(row[1]) + '</dd></div>';
1385			});
1386			html += '</dl>';
1387
1388			if (notes.length) {
1389				html += '<p class="wht-home-code__form-debug-note">' + escapeHtml(notes.join(' ')) + '</p>';
1390			}
1391
1392			debugPanel.innerHTML = html;
1393		}
1394
1395		function renderReadyDebugPanel() {
1396			var requestId = 'not_submitted';
1397			var endpoint = formConfig.ajaxUrl || formConfig.fallbackUrl || '';
1398			var readyPayload;
1399			var readyStats;
1400
1401			if (!isFormDebug || !debugPanel) {
1402				return;
1403			}
1404
1405			readyPayload = failedPayload('WHT form debug is ready. Submit the form to capture request diagnostics.', 'unknown', requestId, {
1406				endpoint_type: endpointTypeFor(endpoint),
1407				handler_reached: false,
1408				wp_mail_called: false,
1409				wp_mail_returned: null
1410			});
1411			readyStats = createRequestStats(requestId);
1412
1413			renderDebugPanel(
1414				readyPayload.data,
1415				{
1416					endpoint: endpoint || 'none',
1417					endpointType: endpointTypeFor(endpoint),
1418					httpStatus: 'n/a',
1419					contentType: 'n/a',
1420					responseType: 'ready',
1421					fallbackUsed: false,
1422					requestId: requestId,
1423					parsedSuccess: null,
1424					rawResponsePreview: 'not shown'
1425				},
1426				readyStats
1427			);
1428		}
1429
1430		function buildDebugErrorMessage(message, responseData, transport) {
1431			var parts = [];
1432
1433			if (!isFormDebug) {
1434				return message;
1435			}
1436
1437			responseData = responseData || {};
1438			transport = transport || {};
1439
1440			parts.push('request_id: ' + (responseData.request_id || transport.requestId || activeRequestId || 'unknown'));
1441			parts.push('endpoint_type: ' + (transport.endpointType || 'unknown'));
1442
1443			if (typeof transport.httpStatus !== 'undefined') {
1444				parts.push('status: ' + transport.httpStatus);
1445			}
1446
1447			parts.push('response: ' + (transport.responseType || (transport.validJson === false ? 'invalid_json' : 'unknown')));
1448			parts.push('server: ' + (responseData.code || 'unknown_error'));
1449
1450			if (transport.fallbackUsed) {
1451				parts.push('fallback: yes');
1452			}
1453
1454			if (responseData.debug && responseData.debug.handler) {
1455				parts.push('handler: ' + responseData.debug.handler);
1456			}
1457
1458			if (responseData.debug && typeof responseData.debug.mail_sent !== 'undefined') {
1459				parts.push('mail: ' + (responseData.debug.mail_sent ? 'true' : 'false'));
1460			}
1461
1462			return message + ' WHT FORM DEBUG ACTIVE panel below has request_id, endpoint, HTTP status and server code.';
1463		}
1464
1465		function buildTransport(endpoint, fallbackUsed, requestId, response) {
1466			return {
1467				endpoint: endpoint,
1468				endpointType: endpointTypeFor(endpoint),
1469				httpStatus: response ? response.status : 0,
1470				responseOk: response ? response.ok : false,
1471				contentType: response && response.headers && typeof response.headers.get === 'function' ? (response.headers.get('content-type') || '') : '',
1472				responseType: 'json',
1473				validJson: true,
1474				fallbackUsed: !!fallbackUsed,
1475				requestId: requestId,
1476				parsedSuccess: null,
1477				rawResponsePreview: 'not shown',
1478				cleantalkDetected: false
1479			};
1480		}
1481
1482		function failedPayload(message, code, requestId, debugData) {
1483			var debug = {
1484				request_id: requestId,
1485				plugin_version: pluginVersion,
1486				server_code: code,
1487				endpoint_type: 'unknown',
1488				handler_reached: false,
1489				wp_mail_called: false,
1490				wp_mail_returned: null,
1491				mail_sent: false
1492			};
1493
1494			if (debugData) {
1495				Object.keys(debugData).forEach(function (key) {
1496					debug[key] = debugData[key];
1497				});
1498			}
1499
1500			return {
1501				success: false,
1502				data: {
1503					message: message || 'Sorry, something went wrong. Please email us directly at [email protected].',
1504					code: code,
1505					mail_sent: false,
1506					request_id: requestId,
1507					plugin_version: pluginVersion,
1508					debug: debug
1509				}
1510			};
1511		}
1512
1513		function textMentionsCleanTalk(value) {
1514			return responseLooksBlocked(value);
1515		}
1516
1517		function parseAjaxResponse(response, endpoint, fallbackUsed, requestId) {
1518			return response.text().then(function (text) {
1519				var transport = buildTransport(endpoint, fallbackUsed, requestId, response);
1520				var trimmed = (text || '').trim();
1521				var lowered = trimmed.toLowerCase();
1522				var parsed;
1523
1524				transport.rawResponsePreview = safeRawResponsePreview(trimmed);
1525				transport.cleantalkDetected = textMentionsCleanTalk(trimmed);
1526
1527				if (trimmed === '0') {
1528					transport.validJson = false;
1529					transport.responseType = 'wp_zero';
1530					transport.parsedSuccess = null;
1531
1532					return {
1533						payload: failedPayload(null, 'wp_zero', requestId, {
1534							endpoint_type: transport.endpointType
1535						}),
1536						transport: transport
1537					};
1538				}
1539
1540				if (lowered.indexOf('<html') !== -1 || lowered.charAt(0) === '<') {
1541					transport.validJson = false;
1542					transport.responseType = 'html';
1543					transport.parsedSuccess = null;
1544
1545					return {
1546						payload: failedPayload(null, textMentionsCleanTalk(lowered) ? 'cleantalk_blocked' : 'html_response', requestId, {
1547							endpoint_type: transport.endpointType
1548						}),
1549						transport: transport
1550					};
1551				}
1552
1553				try {
1554					parsed = JSON.parse(text);
1555					transport.parsedSuccess = parsed && typeof parsed.success === 'boolean' ? parsed.success : null;
1556
1557					if (
1558						(parsed && parsed.apbct && parsed.apbct.blocked) ||
1559						textMentionsCleanTalk(JSON.stringify(parsed))
1560					) {
1561						return {
1562							payload: failedPayload(null, 'cleantalk_blocked', requestId, {
1563								endpoint_type: transport.endpointType
1564							}),
1565							transport: transport
1566						};
1567					}
1568
1569					if (!parsed || typeof parsed.success === 'undefined' || typeof parsed.data === 'undefined') {
1570						return {
1571							payload: failedPayload(null, 'handler_not_reached', requestId, {
1572								endpoint_type: transport.endpointType
1573							}),
1574							transport: transport
1575						};
1576					}
1577
1578					if (parsed.data && !parsed.data.request_id) {
1579						parsed.data.request_id = requestId;
1580					}
1581
1582					return {
1583						payload: parsed,
1584						transport: transport
1585					};
1586				} catch (error) {
1587					transport.validJson = false;
1588					transport.responseType = textMentionsCleanTalk(lowered) ? 'html' : 'invalid_json';
1589					transport.parsedSuccess = null;
1590
1591					return {
1592						payload: failedPayload(null, transport.responseType === 'html' ? 'cleantalk_blocked' : 'invalid_json', requestId, {
1593							endpoint_type: transport.endpointType
1594						}),
1595						transport: transport
1596					};
1597				}
1598			});
1599		}
1600
1601		function shouldTryFallback(result) {
1602			var payload = result && result.payload ? result.payload : {};
1603			var responseData = payload && payload.data ? payload.data : {};
1604			var transport = result && result.transport ? result.transport : {};
1605			var safeCodes = ['cleantalk_blocked'];
1606
1607			if (transport.responseType === 'wp_zero' && responseData.code === 'wp_zero') {
1608				return true;
1609			}
1610
1611			return safeCodes.indexOf(responseData.code) !== -1;
1612		}
1613
1614		function sendForm(endpoint, allowFallback, fallbackUsed, requestId, stats) {
1615			function fetchEndpoint(url) {
1616				noteRequest(stats, url, fallbackUsed);
1617
1618				return fetch(url, {
1619					method: 'POST',
1620					credentials: 'same-origin',
1621					body: buildFormData(requestId)
1622				}).then(function (response) {
1623					return parseAjaxResponse(response, url, fallbackUsed, requestId);
1624				});
1625			}
1626
1627			return fetchEndpoint(endpoint)
1628				.catch(function (error) {
1629					return {
1630						payload: failedPayload(null, 'ajax_network_error', requestId, {
1631							endpoint_type: endpointTypeFor(endpoint)
1632						}),
1633						transport: {
1634							endpoint: endpoint,
1635							endpointType: endpointTypeFor(endpoint),
1636							httpStatus: 0,
1637							responseOk: false,
1638							contentType: '',
1639							responseType: 'network_error',
1640							validJson: false,
1641							fallbackUsed: !!fallbackUsed,
1642							networkError: true,
1643							requestId: requestId,
1644							parsedSuccess: null,
1645							rawResponsePreview: 'not shown'
1646						}
1647					};
1648				})
1649				.then(function (result) {
1650					if (
1651						allowFallback &&
1652						result &&
1653						result.payload &&
1654						!result.payload.success &&
1655						shouldTryFallback(result) &&
1656						formConfig.fallbackUrl &&
1657						endpoint !== formConfig.fallbackUrl
1658					) {
1659						return sendForm(formConfig.fallbackUrl, false, true, requestId, stats);
1660					}
1661
1662					return result;
1663				});
1664		}
1665
1666		function setSelfTestLoading(button, isLoading) {
1667			if (!button) {
1668				return;
1669			}
1670
1671			button.disabled = isLoading;
1672			button.setAttribute('aria-busy', isLoading ? 'true' : 'false');
1673		}
1674
1675		function renderSelfTestResult(title, result, stats) {
1676			var payload = result && result.payload ? result.payload : {};
1677			var responseData = payload && payload.data ? payload.data : {};
1678			var debug = responseData.debug || {};
1679			var transport = result && result.transport ? result.transport : {};
1680			var serverCode = responseData.server_code || responseData.code || debug.server_code || 'unknown';
1681			var requestId = responseData.self_test_request_id || responseData.request_id || debug.request_id || (stats && stats.requestId) || transport.requestId || 'unknown';
1682			var rows;
1683			var html;
1684			var notes = [];
1685
1686			if (!selfTestResult) {
1687				return;
1688			}
1689
1690			rows = [
1691				['test', title],
1692				['request_id', requestId],
1693				['plugin version', responseData.plugin_version || debug.plugin_version || pluginVersion],
1694				['js version', jsVersion],
1695				['css version', cssVersion],
1696				['critical css version', criticalCssVersion],
1697				['preview mode', previewMode],
1698				['endpoint', transport.endpoint || (formConfig.ajaxUrl || 'none')],
1699				['endpoint type', transport.endpointType || debug.endpoint_type || 'unknown'],
1700				['requests fired', stats && stats.requestsFired ? stats.requestsFired : 0],
1701				['HTTP status', typeof transport.httpStatus !== 'undefined' ? transport.httpStatus : 'n/a'],
1702				['response content type', transport.contentType || 'n/a'],
1703				['response shape', transport.responseType || 'unknown'],
1704				['response.success parsed', boolDebugValue(transport.parsedSuccess)],
1705				['server code', serverCode],
1706				['handler reached', yesNo(!!debug.handler_reached)],
1707				['wp_mail called', yesNo(!!debug.wp_mail_called || !!responseData.wp_mail_called)],
1708				['wp_mail returned', boolDebugValue(typeof debug.wp_mail_returned !== 'undefined' ? debug.wp_mail_returned : responseData.wp_mail_returned)],
1709				['timestamp', responseData.timestamp || debug.timestamp || 'n/a'],
1710				['CleanTalk/APBCT detected', yesNo(!!transport.cleantalkDetected || serverCode === 'cleantalk_blocked')],
1711				['raw response preview', transport.rawResponsePreview || 'not shown']
1712			];
1713
1714			diagnosticState.lastSelfTest = {
1715				requestId: requestId,
1716				responseData: responseData,
1717				transport: transport,
1718				stats: stats,
1719				serverCode: serverCode,
1720				title: title
1721			};
1722
1723			if (serverCode === 'cleantalk_blocked') {
1724				notes.push('CleanTalk/WAF intercepted this request before the plugin handler could complete.');
1725			}
1726
1727			if (serverCode === 'mail_failed') {
1728				notes.push('wp_mail returned false for this test; check SMTP/Hostinger mail transport.');
1729			}
1730
1731			selfTestResult.hidden = false;
1732			html = '<p class="wht-home-code__self-test-result-title">' + escapeHtml(title) + '</p><dl class="wht-home-code__form-debug-list">';
1733			rows.forEach(function (row) {
1734				html += '<div class="wht-home-code__form-debug-row"><dt>' + escapeHtml(row[0]) + '</dt><dd>' + escapeHtml(row[1]) + '</dd></div>';
1735			});
1736			html += '</dl>';
1737
1738			if (notes.length) {
1739				html += '<p class="wht-home-code__form-debug-note">' + escapeHtml(notes.join(' ')) + '</p>';
1740			}
1741
1742			selfTestResult.innerHTML = html;
1743		}
1744
1745		function buildMailSelfTestData(requestId) {
1746			var formData = new FormData();
1747
1748			formData.set('action', formConfig.mailSelfTestAction || 'wht_home_code_mail_self_test');
1749			formData.set('wht_home_code_debug', '1');
1750			formData.set('wht_home_code_request_id', requestId);
1751			formData.set('wht_home_code_self_test_nonce', formConfig.selfTestNonce || '');
1752
1753			return formData;
1754		}
1755
1756		function buildAjaxHandlerSelfTestData(requestId) {
1757			var formData = new FormData();
1758
1759			formData.set('action', 'wht_home_code_request');
1760			formData.set('wht_home_code_action', 'request');
1761			formData.set('wht_home_code_nonce', formConfig.nonce || '');
1762			formData.set('wht_home_code_debug', '1');
1763			formData.set('wht_home_code_admin_self_test', '1');
1764			formData.set('wht_home_code_self_test_nonce', formConfig.selfTestNonce || '');
1765			formData.set('wht_home_code_request_id', requestId);
1766			formData.set('wht_home_code_name', 'WHT QA Self-Test');
1767			formData.set('wht_home_code_email', '[email protected]');
1768			formData.set('wht_home_code_message', 'This is a staging admin-only AJAX handler self-test for the WHT Home contact form. Please ignore this message.');
1769			formData.set('wht_home_code_page_url', window.location.href);
1770			formData.set('wht_home_code_company', '');
1771
1772			return formData;
1773		}
1774
1775		function runAdminSelfTest(button, title, buildData) {
1776			var endpoint = formConfig.ajaxUrl || '';
1777			var requestId = createRequestId();
1778			var stats = createRequestStats(requestId);
1779
1780			if (!formConfig.canRunSelfTests || !formConfig.selfTestNonce || !endpoint || !window.fetch || !window.FormData) {
1781				renderSelfTestResult(title, {
1782					payload: failedPayload('Self-test unavailable in this browser/session.', 'ajax_network_error', requestId, {
1783						endpoint_type: endpointTypeFor(endpoint)
1784					}),
1785					transport: {
1786						endpoint: endpoint || 'none',
1787						endpointType: endpointTypeFor(endpoint),
1788						httpStatus: 0,
1789						contentType: '',
1790						responseType: 'network_error',
1791						validJson: false,
1792						requestId: requestId,
1793						parsedSuccess: null,
1794						rawResponsePreview: 'not shown'
1795					}
1796				}, stats);
1797				return;
1798			}
1799
1800			setSelfTestLoading(button, true);
1801			noteRequest(stats, endpoint, false);
1802
1803			fetch(endpoint, {
1804				method: 'POST',
1805				credentials: 'same-origin',
1806				body: buildData(requestId)
1807			})
1808				.then(function (response) {
1809					return parseAjaxResponse(response, endpoint, false, requestId);
1810				})
1811				.catch(function () {
1812					return {
1813						payload: failedPayload('Self-test request could not be completed.', 'ajax_network_error', requestId, {
1814							endpoint_type: endpointTypeFor(endpoint)
1815						}),
1816						transport: {
1817							endpoint: endpoint,
1818							endpointType: endpointTypeFor(endpoint),
1819							httpStatus: 0,
1820							contentType: '',
1821							responseType: 'network_error',
1822							validJson: false,
1823							requestId: requestId,
1824							parsedSuccess: null,
1825							rawResponsePreview: 'not shown'
1826						}
1827					};
1828				})
1829				.then(function (result) {
1830					renderSelfTestResult(title, result, stats);
1831				})
1832				.finally(function () {
1833					setSelfTestLoading(button, false);
1834				});
1835		}
1836
1837		function buildCopyDiagnosticReport() {
1838			var last = diagnosticState.lastSubmit || diagnosticState.lastSelfTest || {};
1839			var responseData = last.responseData || {};
1840			var debug = responseData.debug || {};
1841			var transport = last.transport || {};
1842			var stats = last.stats || {};
1843			var latestError = latestDiagnosticEntry(diagnosticState.jsErrors);
1844			var latestRejection = latestDiagnosticEntry(diagnosticState.unhandledRejections);
1845			var serverCode = last.serverCode || responseData.code || debug.server_code || 'unknown';
1846			var requestId = last.requestId || responseData.request_id || debug.request_id || 'not_submitted';
1847			var lines = [
1848				'WHT form diagnostic',
1849				'plugin version: ' + pluginVersion,
1850				'css version: ' + cssVersion,
1851				'critical css version: ' + criticalCssVersion,
1852				'js version: ' + jsVersion,
1853				'preview mode detected: ' + previewMode,
1854				'page_id: ' + previewPageId,
1855				'preview_id: ' + previewId,
1856				'preview_nonce present: ' + yesNo(previewNoncePresent),
1857				'_thumbnail_id present: ' + yesNo(thumbnailIdPresent),
1858				'root wrapper exists: ' + yesNo(rootWrapperExists),
1859				'body helper existed before JS: ' + yesNo(bodyHadHelperClass),
1860				'body helper class exists: ' + yesNo(document.body.classList.contains('wht-home-code-page')),
1861				'endpoint: ' + (transport.endpoint || formConfig.ajaxUrl || 'none'),
1862				'endpoint type: ' + (transport.endpointType || debug.endpoint_type || endpointTypeFor(formConfig.ajaxUrl || '')),
1863				'nonce present: ' + yesNo(!!formConfig.nonce),
1864				'submit handler attached: yes',
1865				'handlers attached: ' + handlerAttachCount,
1866				'forms found: ' + formMatchCount,
1867				'last submit request_id: ' + requestId,
1868				'requests fired: ' + (stats.requestsFired || 0),
1869				'fallback attempted: ' + yesNo(stats.fallbackAttempted || transport.fallbackUsed),
1870				'HTTP status: ' + (typeof transport.httpStatus !== 'undefined' ? transport.httpStatus : 'n/a'),
1871				'response type: ' + (transport.responseType || 'unknown'),
1872				'response.success parsed: ' + boolDebugValue(transport.parsedSuccess),
1873				'server code: ' + serverCode,
1874				'handler reached: ' + yesNo(!!debug.handler_reached),
1875				'wp_mail called: ' + yesNo(!!debug.wp_mail_called || !!responseData.wp_mail_called),
1876				'wp_mail returned: ' + boolDebugValue(typeof debug.wp_mail_returned !== 'undefined' ? debug.wp_mail_returned : responseData.wp_mail_returned),
1877				'JS errors captured: ' + diagnosticState.jsErrors.length,
1878				'latest JS error: ' + (latestError ? latestError.type + ' ' + latestError.message : 'none'),
1879				'unhandled promise rejections captured: ' + diagnosticState.unhandledRejections.length,
1880				'latest unhandled rejection: ' + (latestRejection ? latestRejection.message : 'none'),
1881				'CleanTalk detected: ' + yesNo(!!transport.cleantalkDetected || serverCode === 'cleantalk_blocked'),
1882				'raw response preview: ' + (transport.rawResponsePreview || 'not shown')
1883			];
1884
1885			return lines.map(safeDiagnosticText).join('\n');
1886		}
1887
1888		function showCopiedDiagnostic(report, copied) {
1889			if (!selfTestResult) {
1890				return;
1891			}
1892
1893			selfTestResult.hidden = false;
1894			selfTestResult.innerHTML = '<p class="wht-home-code__self-test-result-title">' + (copied ? 'Diagnostic copied' : 'Diagnostic ready') + '</p><pre class="wht-home-code__diagnostic-pre">' + escapeHtml(report) + '</pre>';
1895		}
1896
1897		function copyDiagnosticReport() {
1898			var report = buildCopyDiagnosticReport();
1899
1900			if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
1901				navigator.clipboard.writeText(report).then(function () {
1902					showCopiedDiagnostic(report, true);
1903				}).catch(function (error) {
1904					recordJsDiagnostic('clipboard_error', error);
1905					showCopiedDiagnostic(report, false);
1906				});
1907				return;
1908			}
1909
1910			recordJsDiagnostic('clipboard_unavailable', 'navigator.clipboard unavailable');
1911			showCopiedDiagnostic(report, false);
1912		}
1913
1914		if (mailSelfTestButton) {
1915			mailSelfTestButton.addEventListener('click', function () {
1916				runAdminSelfTest(mailSelfTestButton, 'WHT wp_mail self-test', buildMailSelfTestData);
1917			});
1918		}
1919
1920		if (ajaxSelfTestButton) {
1921			ajaxSelfTestButton.addEventListener('click', function () {
1922				runAdminSelfTest(ajaxSelfTestButton, 'WHT AJAX handler self-test', buildAjaxHandlerSelfTestData);
1923			});
1924		}
1925
1926		if (copyDiagnosticButton) {
1927			copyDiagnosticButton.addEventListener('click', copyDiagnosticReport);
1928		}
1929
1930		updateDebugMarker();
1931		renderReadyDebugPanel();
1932
1933		requestForm.addEventListener('submit', function (event) {
1934			event.preventDefault();
1935			event.stopPropagation();
1936
1937			try {
1938			if (isSubmitting) {
1939				return;
1940			}
1941
1942			var requestId = createRequestId();
1943			activeRequestId = requestId;
1944
1945			var stats = createRequestStats(requestId);
1946			var validation = validateBeforeSubmit();
1947
1948			clearDebugPanel();
1949
1950			if (!validation.valid) {
1951				stats.clientValidation = validation.clientValidation;
1952				showFormMessage('error', validation.message);
1953				if (validation.field && typeof validation.field.reportValidity === 'function') {
1954					validation.field.reportValidity();
1955				}
1956				renderDebugPanel(
1957					{
1958						message: validation.message,
1959						code: 'validation_failed',
1960						mail_sent: false,
1961						request_id: requestId,
1962						plugin_version: pluginVersion,
1963						debug: {
1964							request_id: requestId,
1965							plugin_version: pluginVersion,
1966							server_code: 'validation_failed',
1967							endpoint_type: 'unknown',
1968							handler_reached: false,
1969							wp_mail_called: false,
1970							wp_mail_returned: null,
1971							mail_sent: false
1972						}
1973					},
1974					{
1975						endpoint: '',
1976						endpointType: 'unknown',
1977						httpStatus: 'n/a',
1978						contentType: 'n/a',
1979						responseType: 'client_validation',
1980						fallbackUsed: false,
1981						requestId: requestId,
1982						parsedSuccess: null,
1983						rawResponsePreview: 'not shown'
1984					},
1985					stats,
1986					validation.clientValidation
1987				);
1988				return;
1989			}
1990
1991			if (!window.fetch || !window.FormData || (!formConfig.ajaxUrl && !formConfig.fallbackUrl)) {
1992				var noFetchPayload = failedPayload(
1993					'Sorry, something went wrong. Please email us directly at [email protected].',
1994					'ajax_network_error',
1995					requestId,
1996					{
1997						endpoint_type: formConfig.ajaxUrl ? 'admin_ajax' : (formConfig.fallbackUrl ? 'fallback' : 'unknown')
1998					}
1999				);
2000				var noFetchTransport = {
2001					endpoint: formConfig.ajaxUrl || formConfig.fallbackUrl || '',
2002					endpointType: formConfig.ajaxUrl ? 'admin_ajax' : (formConfig.fallbackUrl ? 'fallback' : 'unknown'),
2003					httpStatus: 0,
2004					contentType: '',
2005					responseType: 'network_error',
2006					validJson: false,
2007					requestId: requestId,
2008					parsedSuccess: null,
2009					rawResponsePreview: 'not shown'
2010				};
2011
2012				showFormMessage('error', buildDebugErrorMessage(noFetchPayload.data.message, noFetchPayload.data, noFetchTransport));
2013				renderDebugPanel(noFetchPayload.data, noFetchTransport, stats);
2014				return;
2015			}
2016
2017			isSubmitting = true;
2018			setFormLoading(true);
2019
2020			sendForm(formConfig.ajaxUrl || formConfig.fallbackUrl, true, false, requestId, stats)
2021				.then(function (result) {
2022					if (requestId !== activeRequestId) {
2023						return;
2024					}
2025
2026					var payload = result && result.payload ? result.payload : {};
2027					var transport = result && result.transport ? result.transport : {};
2028					var responseData = payload && payload.data ? payload.data : {};
2029					var message = responseData.message || 'Sorry, something went wrong. Please email us directly at [email protected].';
2030
2031					if (
2032						responseData.code === 'validation_failed' &&
2033						responseData.field_message &&
2034						messageField &&
2035						typeof messageField.setCustomValidity === 'function'
2036					) {
2037						messageField.setCustomValidity(responseData.field_message);
2038						messageField.setAttribute('aria-invalid', 'true');
2039						if (typeof messageField.reportValidity === 'function') {
2040							messageField.reportValidity();
2041						}
2042					}
2043
2044					if (payload && payload.success && responseData.mail_sent) {
2045						showFormMessage('success', message);
2046						renderDebugPanel(responseData, transport, stats);
2047						requestForm.reset();
2048						if (pageUrlField) {
2049							pageUrlField.value = window.location.href;
2050						}
2051						return;
2052					}
2053
2054					if (payload && payload.success && !responseData.mail_sent) {
2055						responseData.code = responseData.code || 'sent_but_ui_failed';
2056						responseData.message = responseData.message || 'Sorry, something went wrong. Please email us directly at [email protected].';
2057						if (responseData.debug) {
2058							responseData.debug.server_code = responseData.debug.server_code || 'sent_but_ui_failed';
2059						}
2060						message = responseData.message;
2061					}
2062
2063					showFormMessage('error', buildDebugErrorMessage(message, responseData, transport));
2064					renderDebugPanel(responseData, transport, stats);
2065				})
2066				.catch(function (error) {
2067					if (requestId !== activeRequestId) {
2068						return;
2069					}
2070
2071					recordJsDiagnostic('submit_promise_catch', error);
2072					var caughtPayload = failedPayload(
2073						'Sorry, something went wrong. Please email us directly at [email protected].',
2074						'ajax_network_error',
2075						requestId,
2076						{
2077							endpoint_type: formConfig.ajaxUrl ? 'admin_ajax' : (formConfig.fallbackUrl ? 'fallback' : 'unknown')
2078						}
2079					);
2080					var caughtTransport = {
2081						endpoint: formConfig.ajaxUrl || formConfig.fallbackUrl || '',
2082						endpointType: formConfig.ajaxUrl ? 'admin_ajax' : (formConfig.fallbackUrl ? 'fallback' : 'unknown'),
2083						httpStatus: 0,
2084						contentType: '',
2085						responseType: 'network_error',
2086						validJson: false,
2087						requestId: requestId,
2088						parsedSuccess: null,
2089						rawResponsePreview: 'not shown'
2090					};
2091
2092					showFormMessage('error', buildDebugErrorMessage(caughtPayload.data.message, caughtPayload.data, caughtTransport));
2093					renderDebugPanel(caughtPayload.data, caughtTransport, stats);
2094				})
2095				.finally(function () {
2096					if (requestId === activeRequestId) {
2097						isSubmitting = false;
2098						setFormLoading(false);
2099					}
2100				});
2101			} catch (error) {
2102				var exceptionRequestId = activeRequestId || createRequestId();
2103				var exceptionStats = createRequestStats(exceptionRequestId);
2104				var exceptionEntry = recordJsDiagnostic('submit_exception', error);
2105				var exceptionPayload = failedPayload(
2106					'Sorry, something went wrong. Please email us directly at [email protected].',
2107					'js_exception',
2108					exceptionRequestId,
2109					{
2110						endpoint_type: formConfig.ajaxUrl ? 'admin_ajax' : (formConfig.fallbackUrl ? 'fallback' : 'unknown'),
2111						handler_reached: false,
2112						wp_mail_called: false,
2113						wp_mail_returned: null
2114					}
2115				);
2116				var exceptionTransport = {
2117					endpoint: formConfig.ajaxUrl || formConfig.fallbackUrl || '',
2118					endpointType: formConfig.ajaxUrl ? 'admin_ajax' : (formConfig.fallbackUrl ? 'fallback' : 'unknown'),
2119					httpStatus: 0,
2120					contentType: '',
2121					responseType: 'js_exception',
2122					validJson: false,
2123					requestId: exceptionRequestId,
2124					parsedSuccess: null,
2125					rawResponsePreview: exceptionEntry.message || 'not shown'
2126				};
2127
2128				isSubmitting = false;
2129				setFormLoading(false);
2130				showFormMessage('error', buildDebugErrorMessage(exceptionPayload.data.message, exceptionPayload.data, exceptionTransport));
2131				renderDebugPanel(exceptionPayload.data, exceptionTransport, exceptionStats);
2132			}
2133		});
2134	}
2135
2136	if (!video) {
2137		return;
2138	}
2139
2140	var source = video.querySelector('source[data-src]');
2141	var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
2142	var desktopMedia = window.matchMedia('(min-width: 768px)');
2143
2144	function unloadVideo() {
2145		video.pause();
2146
2147		if (source) {
2148			source.removeAttribute('src');
2149		}
2150
2151		video.classList.remove('is-loaded');
2152		video.load();
2153	}
2154
2155	function loadVideo() {
2156		if (!source || source.getAttribute('src')) {
2157			return;
2158		}
2159
2160		source.setAttribute('src', source.getAttribute('data-src'));
2161		video.load();
2162	}
2163
2164	function syncHeroVideo() {
2165		if (reduceMotion.matches || !desktopMedia.matches) {
2166			unloadVideo();
2167			return;
2168		}
2169
2170		loadVideo();
2171
2172		var playRequest = video.play();
2173
2174		if (playRequest && typeof playRequest.catch === 'function') {
2175			playRequest.catch(function () {});
2176		}
2177	}
2178
2179	video.addEventListener('loadeddata', function () {
2180		video.classList.add('is-loaded');
2181	});
2182
2183	syncHeroVideo();
2184
2185	if (typeof reduceMotion.addEventListener === 'function') {
2186		reduceMotion.addEventListener('change', syncHeroVideo);
2187		desktopMedia.addEventListener('change', syncHeroVideo);
2188	} else if (typeof reduceMotion.addListener === 'function') {
2189		reduceMotion.addListener(syncHeroVideo);
2190		desktopMedia.addListener(syncHeroVideo);
2191	}
2192})();

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.