PageSourceSearch

https://www.dentalplans.com/_static/??-eJydjksOwjAMRC+EMSwK7QJxlpC…3JypQ8y9xyUEnmr8jl4zZfjeTqMw3Aap/UFOiJ+7A==

js dentalplans.com collected 2026-10-03 22:40:25 UTC 52,665 bytes, 1,465 lines download raw bytes

1function fillPACode(form) {
2	var hiddenInput = form.querySelector('input[name="pa_code"]');
3	var textInput = form.querySelector('.pacode-input');
4	if ( hiddenInput && textInput ) {
5		hiddenInput.value = 'PA' + textInput.value.replace(/\D/g, '');
6	}
7}
8
9function checkForResults(form) {
10	var hiddenInput = form.querySelector('input[name="pa_code"]');
11	if ( ! hiddenInput ) {
12		return;
13	}
14	fillPACode(form);
15	var pa_code = hiddenInput.value;
16
17	jQuery.ajax({
18		method: "GET",
19		url: `${window.DP_REST_API.v1}/dpapi/proxy/dentist/v1/dentalsearch/dentist/${pa_code}`,
20		success: async function (data) {
21			if ( data.dentists.length === 0 ) {
22				doFormError(form);
23			} else {
24				if ( data.dentists && data.dentists[0].dentistAddress && data.dentists[0].dentistAddress.zip ) {
25					var pa_code_zip = data.dentists[0].dentistAddress.zip;
26					if ( typeof window.overrideLocation === 'function' ) {
27						await window.overrideLocation(pa_code_zip);
28					}
29				}
30				form.submit();
31			}
32		},
33		error: function (data) {
34			console.error("pa code search Error: " + pa_code, data);
35		},
36	});
37}
38
39function doFormError(form) {
40	var textInput = form.querySelector('.pacode-input');
41	if ( textInput ) {
42		textInput.classList.add('error');
43	}
44	form.querySelectorAll('.error-message').forEach(function (el) {
45		el.classList.remove('hidden');
46	});
47}
48
49function clearFormError(form) {
50	var textInput = form.querySelector('.pacode-input');
51	if ( textInput ) {
52		textInput.classList.remove('error');
53	}
54	form.querySelectorAll('.error-message').forEach(function (el) {
55		el.classList.add('hidden');
56	});
57}
58
59function disableFormSubmit(form, disable) {
60	var submitBtn = form.querySelector('[type="submit"]');
61	if ( submitBtn ) {
62		submitBtn.disabled = disable;
63	}
64}
65
66function isFormSubmitDisabled(form) {
67	var submitBtn = form.querySelector('[type="submit"]');
68	return submitBtn ? submitBtn.disabled : true;
69}
70
71document.addEventListener("DOMContentLoaded", function () {
72	var forms = document.querySelectorAll('.dentist-pa-code-form');
73
74	forms.forEach(function (form) {
75		var input = form.querySelector('.pacode-input');
76		if ( ! input ) {
77			return;
78		}
79
80		input.addEventListener('input', function (event) {
81			input.value = event.target.value.replace(/\D/g, '');
82			clearFormError(form);
83			disableFormSubmit(form, input.value.length < 1);
84		});
85
86		input.addEventListener('paste', function (event) {
87			event.preventDefault();
88			input.value = (event.clipboardData || window.clipboardData).getData("text").replace(/\D/g, '');
89			clearFormError(form);
90			disableFormSubmit(form, input.value.length < 1);
91		});
92	});
93
94	document.body.addEventListener("submit", function (event) {
95		var form = event.target;
96		if ( ! form || ! form.classList || ! form.classList.contains('dentist-pa-code-form') ) {
97			return;
98		}
99
100		event.preventDefault();
101		if ( ! isFormSubmitDisabled(form) ) {
102			fillPACode(form);
103			checkForResults(form);
104		}
105	});
106});
107;
108
109document.addEventListener('DOMContentLoaded', function () {
110
111	/////////////////////////////////////////////////////////////////////////////////////////////
112	// Define elements.
113
114	let searchFormBlock = null;
115
116	let overlay = null;
117
118	let closeButton = null;
119
120	let searchForm = null;
121
122	let filterByWrapper            = null;
123	let filterByToggleButton       = null;
124	let filterByOptionsWrapper     = null;
125	let filterByOptionsInputs      = null;
126	let filterByOptionsInputsArray = null;
127
128	let searchInput = null;
129
130	let autosuggestWrapper        = null;
131	let autosuggestContent        = null;
132
133	let bootstrapFocusTrapModals = [];
134
135	/////////////////////////////////////////////////////////////////////////////////////////////
136	// Define state variables.
137
138	let autosuggestAbortController = null;
139	let filterByOptionPreviousValue = null;
140
141	/////////////////////////////////////////////////////////////////////////////////////////////
142	// Define helpers and handlers.
143
144	function renderMobileHeaderBrand( container ) {
145		const brand = window.dp_search_form && window.dp_search_form.mobile_header_brand;
146		if ( ! container || ! brand || ! brand.home_url ) {
147			return;
148		}
149		const link = document.createElement( 'a' );
150		link.href = brand.home_url;
151		if ( brand.logo && brand.logo.url ) {
152			link.className = 'site-home-link';
153			const img = document.createElement( 'img' );
154			img.src = brand.logo.url;
155			if ( brand.logo.srcset ) {
156				img.setAttribute( 'srcset', brand.logo.srcset );
157			}
158			if ( brand.logo.sizes ) {
159				img.setAttribute( 'sizes', brand.logo.sizes );
160			}
161			img.alt = typeof brand.logo.alt === 'string' ? brand.logo.alt : '';
162			img.decoding = 'async';
163			link.appendChild( img );
164			container.appendChild( link );
165		} else if ( brand.site_title ) {
166			link.textContent = brand.site_title;
167			container.appendChild( link );
168		}
169	}
170
171	function init() {
172
173		if ( ! searchFormBlock ) {
174			searchFormBlock = document.createElement('div');
175			searchFormBlock.id = 'dp-site-search-form';
176			searchFormBlock.classList.add('dp-block-search-form');
177
178			searchFormBlock.innerHTML = `
179				<div class="dp-block-search-form__overlay"></div>
180				<div class="dp-block-search-form__inner">
181					<div class="dp-block-search-form__mobile-header">
182						<button type="button" class="dp-block-search-form__close" aria-label="Close search">
183							<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
184								<path d="M4 4L20 20" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
185								<path d="M4 20L20 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
186							</svg>
187						</button>
188						<div class="dp-block-search-form__mobile-logo"></div>
189					</div>
190					<form name="dp-search-form" method="get" action="${window.dp_search_form.form_action}">
191						<div class="dp-search-form-main">
192							<div class="dp-search-form__filter-by-dropdown">
193								<button type="button" class="filter-by-dropdown__toggle" aria-controls="dp-site-search-form__category-options">
194									<span class="filter-by-dropdown__toggle-label">Category:</span>
195									<span class="filter-by-dropdown__toggle-value">All</span>
196									<svg xmlns="http://www.w3.org/2000/svg" width="14" height="9" viewBox="0 0 14 9" fill="none">
197										<path d="M12.3135 1.65674L6.65662 7.31359L0.999769 1.65674" stroke="#2445EA" stroke-width="2" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
198									</svg>
199								</button>
200								<div id="dp-site-search-form__category-options" class="filter-by-dropdown__options visually-hidden">
201									<label>
202										<input type="radio" name="facet" value="all" class="visually-hidden" checked />
203										All
204									</label>
205									${window.dp_search_form.search_filter_options.map(option => {
206										return `
207											<label>
208												<input type="radio" name="facet" value="${option.value}" class="visually-hidden" />
209												${option.label}
210											</label>
211										`;
212									}).join('')}
213								</div>
214							</div>
215							<input name="s" type="search" placeholder="What are you looking for..." autocomplete="off" required />
216							<input type="reset" value="Clear" class="clear-search-input" />
217							<button type="submit" class="submit-search-input">
218								<svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 25 25" fill="none">
219									<title>Search</title>
220									<circle cx="9.6" cy="9.6" r="8.6" stroke="currentColor" stroke-width="2"/>
221									<path d="M16.8 16.7998L24 23.9998" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
222								</svg>
223							</button>
224						</div>
225						<div class="dp-search-form-autosuggest">
226							<div class="dp-search-form-autosuggest__content"></div>
227							<p class="default-message">Need to find a dentist? Look <a href="${window.dp_search_form.dentist_href}">here &rarr;</a></p>
228						</div>
229					</form>
230				</div>
231			`;
232
233			document.body.appendChild(searchFormBlock);
234
235			const mobileLogoEl = searchFormBlock.querySelector( '.dp-block-search-form__mobile-logo' );
236			renderMobileHeaderBrand( mobileLogoEl );
237		}
238
239		overlay = searchFormBlock.querySelector('.dp-block-search-form__overlay');
240
241		closeButton = searchFormBlock.querySelector('.dp-block-search-form__close');
242
243		searchForm = searchFormBlock.querySelector('form[name="dp-search-form"]');
244
245		filterByWrapper            = searchFormBlock.querySelector('.dp-search-form__filter-by-dropdown');
246		filterByToggleButton       = filterByWrapper.querySelector('.filter-by-dropdown__toggle');
247		filterByOptionsWrapper     = filterByWrapper.querySelector('.filter-by-dropdown__options');
248		filterByOptionsInputs      = filterByOptionsWrapper.querySelectorAll('input[name="facet"]');
249		filterByOptionsInputsArray = Array.from(filterByOptionsInputs);
250
251		searchInput = searchFormBlock.querySelector('input[name="s"]');
252
253		autosuggestWrapper = searchFormBlock.querySelector('.dp-search-form-autosuggest');
254		autosuggestContent = autosuggestWrapper.querySelector('.dp-search-form-autosuggest__content');
255
256		bootstrapFocusTrapModals = [];
257
258		// Define state variables.
259
260		autosuggestAbortController = null;
261		filterByOptionPreviousValue = getFilterByOptionCurrentValue();
262
263		// Attach event listeners.
264
265		overlay.addEventListener('click', event => {
266			if ( event.target === event.currentTarget ) {
267				// Only toggle visibility if no child element was targeted.
268				toggleSearchFormVisibility();
269			}
270		});
271
272		if ( closeButton ) {
273			closeButton.addEventListener('click', () => {
274				if ( document.body.classList.contains('dp-block-search-form--open') ) {
275					toggleSearchFormVisibility();
276				}
277			});
278		}
279
280		filterByToggleButton.addEventListener('click', toggleFilterByOptionsVisibility);
281
282		searchForm.addEventListener('reset', event => {
283			setTimeout(
284				() => {
285					// This changes the selected category, so remember the value after reset.
286					// It's now the new "previous" option.
287					filterByOptionPreviousValue = getFilterByOptionCurrentValue();
288					// Now handle search input change.
289					handleSearchInputChange(event);
290				},
291				0 // Fire right after this event to ensure the form values have already been reset.
292			);
293		});
294
295		searchForm.addEventListener('submit', event => {
296			const query               = searchInput.value.trim();
297			const [ , filterByLabel ] = getFilterByOptionCurrentValue();
298			dataLayerPush({
299				event: ( event?.submitter?.classList?.contains('submit-more-results') ? "more_search_success" : "term_search_success" ),
300				search_term: query,
301				search_category: filterByLabel,
302			});
303		});
304
305		filterByOptionsInputs.forEach(input => {
306			input.addEventListener('change', handleSearchInputChange);
307		});
308
309		searchInput.addEventListener('input', handleSearchInputChange);
310	}
311
312	function dataLayerPush(data) {
313		window.dataLayer = window.dataLayer || [];
314		window.dataLayer.push(data);
315		window.console.log('pushed to dataLayer ->', JSON.stringify(data, null, 4));
316	}
317
318	function toggleSearchFormVisibility() {
319		if ( !! document.body.classList.toggle('dp-block-search-form--open') ) {
320			// Prevent Bootstrap's modal focus trapping from preventing focus.
321			bootstrapFocusTrapModals.length = 0;
322			bsModals = document.querySelectorAll('.modal.show[aria-modal="true"][role="dialog"]');
323			if ( bsModals ) {
324				bsModals.forEach(modal => {
325					const bsModalInstance = bootstrap.Modal.getInstance(modal);
326					if ( bsModalInstance?._focustrap?._isActive ) {
327						// Only capture modals with focus trapping enabled.
328						bootstrapFocusTrapModals.push(bsModalInstance);
329						bsModalInstance._focustrap.deactivate(); // See https://github.com/twbs/bootstrap/blob/v5.2.3/js/src/modal.js#L70
330					}
331				});
332			}
333			// Auto-focus the search input field.
334			searchInput.focus();
335			// Send analytics event.
336			dataLayerPush({
337				event: "search_overlay_opened",
338			});
339		} else {
340			// Re-activate Bootstrap modal focus trapping.
341			if ( bootstrapFocusTrapModals ) {
342				bootstrapFocusTrapModals.forEach(modalInstance => {
343					modalInstance._focustrap.activate();
344				})
345			}
346		}
347	}
348
349	function toggleFilterByOptionsVisibility() {
350		filterByOptionsWrapper.classList.toggle('visually-hidden');
351	}
352
353	async function handleSearchInputChange(event) {
354
355		// Ensure current filter option is set.
356		setStateFilterBy(getFilterByOptionCurrent());
357
358		if (filterByOptionsInputsArray.includes(event.target)) {
359			// Change event from filter category input selection.
360			const [ , prevCategoryLabel ] = filterByOptionPreviousValue;
361			filterByOptionPreviousValue = getFilterByOptionCurrentValue();
362			const [ , newCategoryLabel ] = filterByOptionPreviousValue;
363			dataLayerPush({
364				event: "search_category_interest",
365				previous_category: prevCategoryLabel,
366				new_category: newCategoryLabel,
367			});
368		}
369
370		// Ensure the filter options are hidden while user searches.
371		filterByOptionsWrapper.classList.add('visually-hidden');
372
373		// Act based on input value.
374		if ( searchInput?.value ) {
375
376			searchFormBlock.classList.add('has-query-input');
377
378			const query                            = searchInput.value.trim();
379			const [ filterByValue, filterByLabel ] = getFilterByOptionCurrentValue();
380			const limit                            = 4;
381			const autosuggestResults               = await fetchAutosuggestResults(query, filterByValue, limit);
382			if (
383				'success' === autosuggestResults?.status &&
384				autosuggestResults?.data?.topSearchResults?.length
385			) {
386				// Found search results.
387
388				const topSearchResultsListItems = autosuggestResults.data.topSearchResults.map(
389					post => {
390						if ( ! post?.search_result_item_html ) {
391							return '';
392						}
393						return `<li>${post.search_result_item_html}</li>`;
394					}
395				);
396
397				let maybeMoreButton = ``;
398				if ( autosuggestResults?.data?.totalSearchResultsCount > limit ) {
399					maybeMoreButton = `<button type="submit" class="submit-more-results">More...</button>`;
400				}
401
402				autosuggestContent.innerHTML = `
403					<div class="top-search-results">
404						<p class="heading">Top Results For <strong>${query}</strong> in <strong>${filterByLabel}</strong></p>
405						<ul>
406							${topSearchResultsListItems.join("\n")}
407						</ul>
408						${maybeMoreButton}
409					</div>
410				`;
411
412				autosuggestContent.querySelectorAll('.top-search-results li a').forEach(el => {
413					el.addEventListener('click', () => {
414						const itemTitle = el.querySelector('.search-result-item__title');
415						dataLayerPush({
416							event: "suggested_search_success",
417							search_term: query,
418							search_category: filterByLabel,
419							suggested_name: ( itemTitle?.innerText ) ? itemTitle.innerText.trim() : el.innerText.trim(),
420							suggested_url: el.href,
421						});
422					});
423				});
424			} else {
425				// No search results retrieved.
426
427				const viewHoursTooltipHTML = `
428					<strong>Mon&ndash;Fri:</strong> 8:30am &ndash; 9pm<br />
429					<strong>Sat:</strong> 9am &ndash; 6pm<br />
430					<strong>Sun:</strong> Closed<br />
431					<small>All times are in EST</small>
432				`;
433
434				autosuggestContent.innerHTML = `
435					<div class="top-search-results no-results">
436						<p class="heading">We didn't find anything that matches &ldquo;${query}&rdquo;.</p>
437						<div class="no-results__cta">
438							<div class="cta__see-faq">
439								<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" fill="none">
440									<path d="M2.55996 35.9501C2.99996 33.8101 3.54996 31.6801 4.50996 29.3401C2.92996 26.3301 1.51996 23.1101 1.39996 19.7101C1.17996 13.2801 5.74996 7.46005 11.33 4.25005C13.06 3.26005 14.9 2.45005 16.84 1.98005C23.55 0.340052 31.1 3.16005 35.17 8.74005C39.24 14.3201 39.64 22.3101 36.21 28.3101C32.79 34.3101 25.77 38.0301 18.88 37.5801C15.87 37.3801 12.84 36.4101 10.76 34.2701C8.02996 35.
4400101 5.29996 35.7501 2.54996 35.9501H2.55996Z" stroke="#2445EA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
441									<path d="M15.3096 15.6403C15.4596 13.8203 16.5596 12.0803 18.1896 11.2503C19.8196 10.4203 21.9296 10.5903 23.3596 11.7303C24.7896 12.8703 25.3896 14.9703 24.7096 16.6703C24.2796 17.7403 23.4196 18.5803 22.5096 19.3003C21.7396 19.9103 20.8996 20.4803 20.3496 21.3003C19.7996 22.1203 19.6496 23.3003 20.2796 24.0503" stroke="#2445EA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
442									<path d="M20.3203 28.6702L20.3703 29.1902C20.5403 29.2902 20.8003 29.2202 20.8903 29.0402C20.9803 28.8602 20.8903 28.6102 20.3203 28.6702Z" stroke="#2445EA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
443									<path d="M2.55996 35.9501C2.99996 33.8101 3.54996 31.6801 4.50996 29.3401C2.92996 26.3301 1.51996 23.1101 1.39996 19.7101C1.17996 13.2801 5.74996 7.46005 11.33 4.25005C13.06 3.26005 14.9 2.45005 16.84 1.98005C23.55 0.340052 31.1 3.16005 35.17 8.74005C39.24 14.3201 39.64 22.3101 36.21 28.3101C32.79 34.3101 25.77 38.0301 18.88 37.5801C15.87 37.3801 12.84 36.4101 10.76 34.2701C8.02996 35.
4430101 5.29996 35.7501 2.54996 35.9501H2.55996Z" stroke="#2445EA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
444									<path d="M15.3096 15.6403C15.4596 13.8203 16.5596 12.0803 18.1896 11.2503C19.8196 10.4203 21.9296 10.5903 23.3596 11.7303C24.7896 12.8703 25.3896 14.9703 24.7096 16.6703C24.2796 17.7403 23.4196 18.5803 22.5096 19.3003C21.7396 19.9103 20.8996 20.4803 20.3496 21.3003C19.7996 22.1203 19.6496 23.3003 20.2796 24.0503" stroke="#2445EA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
445									<path d="M20.3203 28.6702L20.3703 29.1902C20.5403 29.2902 20.8003 29.2202 20.8903 29.0402C20.9803 28.8602 20.8903 28.6102 20.3203 28.6702Z" stroke="#2445EA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
446								</svg>
447								<p>Take a look at our frequently asked&nbsp;questions</p>
448								<a href="${dp_search_form.faq_href}" class="dp-button dp-button--tertiary">Go to FAQ</a>
449							</div>
450							<div class="cta__call-us">
451								<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" fill="none">
452									<g clip-path="url(#clip0_1_828)">
453									<path d="M20.0001 38.8801C30.4273 38.8801 38.8801 30.4273 38.8801 20.0001C38.8801 9.57298 30.4273 1.12012 20.0001 1.12012C9.57298 1.12012 1.12012 9.57298 1.12012 20.0001C1.12012 30.4273 9.57298 38.8801 20.0001 38.8801Z" stroke="#2445EA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
454									<path d="M9.29004 29.2897C10.09 25.6597 12.76 22.4897 16.21 21.0897C17.93 20.3897 19.8 20.1097 21.57 19.5297C23.34 18.9497 25.1 17.9897 26.01 16.3597C27.04 14.4997 26.68 12.0197 25.3 10.3897C23.92 8.7597 21.67 7.9897 19.55 8.2097C18.48 8.3197 17.42 8.6797 16.57 9.3397C14.9 10.6497 14.31 13.0597 14.93 15.0897C15.55 17.1197 17.22 18.7197 19.18 19.5397C20.93 20.2697 22.89 20.4197 24.64 21.1497C27.97 22.5297 30.25 26.1397 30.06 29.7397" stroke="#2445EA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
455									</g>
456									<defs>
457									<clipPath id="clip0_1_828">
458										<rect width="40" height="40" fill="white"/>
459									</clipPath>
460									</defs>
461								</svg>
462								<p>Give us a call, we are here&nbsp;to&nbsp;help</p>
463								<button type="button" class="call-us__view-hours" data-bs-toggle="tooltip" data-bs-html="true" data-bs-placement="top" data-bs-fallbackPlacements="['top', 'bottom', 'left', 'right']" data-bs-title="${viewHoursTooltipHTML}">View Hours</button>
464								<a href="${dp_search_form.dp_phone_href}" class="dp-button dp-phone dp-phone-without-country-code">${dp_search_form.dp_phone_label}</a>
465							</div>
466						</div>
467					</div>
468				`;
469
470				// Enable the Bootstrap tooltip.
471				autosuggestContent.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
472					return new bootstrap.Tooltip(el);
473				});
474			}
475
476			// Retain query params on dynamically added hyperlinks.
477			dpSetPhoneNumber(autosuggestContent);
478		} else {
479			// No search input value.
480			searchFormBlock.classList.remove('has-query-input');
481			autosuggestContent.innerHTML = '';
482		}
483	}
484
485	function setStateFilterBy(inputEl) {
486		let buttonLabel = 'All';
487		if ( inputEl?.labels?.length ) {
488			buttonLabel = inputEl.labels[0].innerText;
489		}
490		filterByToggleButton.querySelector('span.filter-by-dropdown__toggle-value').innerText = buttonLabel;
491		filterByOptionsWrapper.classList.add('visually-hidden');
492	}
493
494	function getFilterByOptionCurrent() {
495		for ( const input of filterByOptionsInputs ) {
496			if ( input.checked ) {
497				return input;
498			}
499		}
500		return null;
501	}
502
503	function getFilterByOptionCurrentValue() {
504		const inputEl = getFilterByOptionCurrent();
505
506		let value = 'all';
507		if ( inputEl?.value ) {
508			value = inputEl.value;
509		}
510
511		let label = 'All';
512		if ( inputEl?.labels?.[0]?.innerText ) {
513			label = inputEl.labels[0].innerText;
514		}
515
516		return [ value, label ];
517	}
518
519	async function fetchAutosuggestResults(query, filterBy, limit) {
520
521		if ( ! query ) {
522			return null; // Don't fetch for blank queries.
523		}
524
525		// Abort any existing request.
526		if ( autosuggestAbortController ) {
527			autosuggestAbortController.abort();
528		}
529
530		// Create a new AbortController for the current request.
531		autosuggestAbortController = new AbortController();
532
533		try {
534
535			const response = await fetch(
536				`${window.DP_REST_API.v1}/search/autosuggest/?s=${query}&facet=${filterBy}&limit=${limit}`,
537				{
538					"method": 'GET',
539					"credentials": 'same-origin',
540					"signal": autosuggestAbortController.signal
541				}
542			);
543
544			if ( ! response.ok ) {
545				throw new Error('Network response was not ok');
546			}
547
548			const data = await response.json();
549			return data;
550		} catch ( error ) {
551			if ( 'AbortError' !== error.name ) {
552				window.console.error('Failed to fetch search autosuggestion results:', error);
553			}
554		}
555
556		return null; // The data couldn't be fetched.
557	}
558
559	/////////////////////////////////////////////////////////////////////////////////////////////
560	// Attach entry point event listeners.
561
562	document.querySelectorAll('.search-link').forEach(link => {
563		link.addEventListener('click', event => {
564			event.preventDefault();
564 // Do not navigate href.
565
566			// Initialize the search form if it's not already initialized.
567			if ( ! searchFormBlock ) {
568				init();
569			}
570
571			// Toggle the search form visibility.
572			toggleSearchFormVisibility();
573		});
574	});
575}, { once: true } );
576;
577/**
578 * Plan search results filter and sort.
579 * Direct links: URL params plans, benefits, sort are applied on load (e.g. ?plans=f_net_cigna&benefits=f_bene_vision&sort=low-price).
580 */
581var selectedSort = 'featured';
582var andMode = true;
583
584function sortCards() {
585	jQuery('.insurance-results-block, .cards-results-box').each(function () {
586		let $resultBox = jQuery(this);
587		let $sortedCards = $resultBox.find('.plan.card').sort(function (a, b) {
588			var aVal = parseFloat(jQuery(a).data(selectedSort)) || 0;
589			var bVal = parseFloat(jQuery(b).data(selectedSort)) || 0;
590			if (aVal < bVal)
591				return -1;
592			if (aVal > bVal)
593				return 1;
594			return 0;
595
596		});
597		$resultBox.html($sortedCards);
598	});
599}
600
601function filterCards() {
602
603	//cards-results-box
604	let $activeFilters = jQuery('.added-search-pill-box .filter-pill');
605
606	if ($activeFilters.length) {
607		let $plansFilters = jQuery('.added-search-pill-box .filter-pill.plan');
608		let $beneFilters = jQuery('.added-search-pill-box .filter-pill.filter');
609
610		//hide everything
611		jQuery('.plan.card').hide();
612
613		if ($plansFilters.length && $beneFilters.length && andMode) {
614
615			let planSelector = [];
616			$plansFilters.each(function (idx, el) {
617				planSelector.push('.' + jQuery(this).data('filter'));
618			});
619			planSelector = planSelector.join(', ');
620
621			let beneSelector = [];
622			$beneFilters.each(function (idx, el) {
623				beneSelector.push('.' + jQuery(this).data('filter'));
624			});
625			beneSelector = beneSelector.join(', ');
626
627			jQuery(planSelector).filter(beneSelector).show();
628
629		} else {
630			//show what we should
631			$activeFilters.each(function (idx, el) {
632				jQuery('.plan.card.' + jQuery(this).data('filter')).show();
633			});
634		}
635	} else {
636		//if nothing selected show all
637		jQuery('.plan.card').show();
638	}
639
640	//resort based on sort
641	sortCards();
642
643	// jQuery(document).trigger("render:cards");
644	updateResultCounts();
645}
646
647function updateResultCounts() {
648
649	jQuery('.insurance-results-block, .cards-results-box').each(function () {
650		let $resultBox = jQuery(this);
651		let $header = $resultBox.prev('.card-results-header');
652		let $activeCards = $resultBox.find('.plan.card:visible');
653		$header.find('.num_of_plans').text($activeCards.length);
654		if ($activeCards.length > 0) {
655			$header.find('.hide-when-nothing-found,.plan-results-benefits-header').show();
656		} else {
657			$header.find('.hide-when-nothing-found,.plan-results-benefits-header').hide();
658		}
659		if ($activeCards.length) {
660			$header.find('.results_found_header').show();
661			$header.find('.results_not_found_header').hide();
662		} else {
663			$header.find('.results_not_found_header').show();
664			$header.find('.results_found_header').hide();
665		}
666	});
667}
668
669function hideAllFilterBoxes() {
670	jQuery('.filter-dropdown').removeClass('active');
671	jQuery(".slider-svg").removeClass("selected");
672	jQuery(".filter-pop-out-box").addClass("hidden");
673}
674
675function updateDropDownCounts() {
676	let count = jQuery('.added-search-pill-box .filter-pill.filter').length;
677
678	jQuery('.filter-dropdown.benefits .filter-count-wrap').attr('data-filter-count', count);
679	jQuery('.filter-dropdown.benefits .filter-count').text(count);
680	count = jQuery('.added-search-pill-box .filter-pill.plan').length;
681	jQuery('.filter-dropdown.all-plans-filter-btn .filter-count-wrap').attr('data-filter-count', count);
682	jQuery('.filter-dropdown.all-plans-filter-btn .filter-count').text(count);
683}
684
685/**
686 * Updates the URL to reflect current filter and sort state so the view can be shared or bookmarked.
687 * Preserves zip from the URL, or adds it from .results-zip when present; sets plans, benefits, sort from the DOM.
688 */
689function syncUrlToFilters() {
690	if ( ! jQuery( '.plan-search-results-filter-layout' ).length ) {
691		return;
692	}
693	var params = new URLSearchParams( window.location.search );
694	params.delete( 'plans' );
695	params.delete( 'benefits' );
696	params.delete( 'sort' );
697	// Preserve zip from URL, or add from displayed .results-zip so shared links include location.
698	if ( ! params.get( 'zip' ) ) {
699		var zipEl = document.querySelector( '.results-zip' );
700		var zipText = zipEl ? zipEl.textContent.trim() : '';
701		if ( /^\d{5}$/.test( zipText ) ) {
702			params.set( 'zip', zipText );
703		}
704	}
705	var planPills = jQuery( '.added-search-pill-box .filter-pill.plan' );
706	var benePills = jQuery( '.added-search-pill-box .filter-pill.filter' );
707	var planValues = [];
708	planPills.each( function () { planValues.push( jQuery( this ).data( 'filter' ) ); } );
709	var beneValues = [];
710	benePills.each( function () { beneValues.push( jQuery( this ).data( 'filter' ) ); } );
711	if ( planValues.length ) {
712		params.set( 'plans', planValues.join( ',' ) );
713	}
714	if ( beneValues.length ) {
715		params.set( 'benefits', beneValues.join( ',' ) );
716	}
717	if ( selectedSort && selectedSort !== 'featured' ) {
718		params.set( 'sort', selectedSort );
719	}
720	var query = params.toString();
721	var url = window.location.pathname + ( query ? '?' + query : '' );
722	window.history.replaceState( null, '', url );
723}
724
725/**
726 * Pre-apply filters and sort from URL params (plans, benefits, sort).
727 * Supported params: plans (e.g. f_net_delta-dental), benefits (e.g. f_bene_vision), sort (e.g. low-price).
728 * Only applies values that match existing .planfilter elements.
729 */
730function applyFiltersAndSortFromUrl() {
731	var $pillBox = jQuery('.added-search-pill-box');
732	if ( ! $pillBox.length || ! jQuery('.cards-results-box .plan.card').length ) {
733		return;
734	}
735	var params = new URLSearchParams( window.location.search );
736
737	// Sort: match .planfilter[data-sort] in .filter-pop-out-box.sort
738	var sortParam = params.get( 'sort' );
739	if ( sortParam ) {
740		var $sortFilter = jQuery( '.filter-pop-out-box.sort .planfilter[data-sort="' + sortParam + '"]' );
741		if ( $sortFilter.length ) {
742			selectedSort = sortParam;
743			jQuery( '.filter-pop-out-box.sort .planfilter' ).removeClass( 'selected' );
744			$sortFilter.addClass( 'selected' );
745			var $activeSort = jQuery( '#active-sort' );
746			if ( $activeSort.length ) {
747				$activeSort.text( $sortFilter.text().trim() );
748			}
749			sortCards();
750		}
751	}
752
753	// Plans (network): add pills and mark .planfilter selected in .filter-pop-out-box.plan
754	var plansParam = params.get( 'plans' );
755	if ( plansParam ) {
756		var planValues = plansParam.split( ',' ).map( function ( s ) { return s.trim(); } ).filter( Boolean );
757		jQuery( '.filter-pill.plan' ).remove();
758		jQuery( '.filter-pop-out-box.plan .planfilter' ).removeClass( 'selected' ).find( '.pill-close' ).addClass( 'hidden' );
759		planValues.forEach( function ( filterValue ) {
760			var $option = jQuery( '.filter-pop-out-box.plan .planfilter[data-filter="' + filterValue + '"]' );
761			if ( $option.length ) {
762				$option.addClass( 'selected' ).find( '.pill-close' ).removeClass( 'hidden' );
763				var label = $option.find( '.pill-label' ).text();
764				var pillHtml = '<div class="filter-pill selected plan" data-filter="' + filterValue + '">' +
765					'<span class="pill-label">' + label + '</span>' +
766					'<span class="pill-close" tabindex="0">' +
767					'<svg width="12" height="12" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg"><title>Remove</title>' +
768					'<path d="M3 1L18 16" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>' +
769					'<path d="M3 16L18 1" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg></span></div>';
770				$pillBox.append( pillHtml );
771			}
772		} );
773	}
774
775	// Benefits: add pills and mark .planfilter selected in .filter-pop-out-box.filter
776	var benefitsParam = params.get( 'benefits' );
777	if ( benefitsParam ) {
778		var benefitValues = benefitsParam.split( ',' ).map( function ( s ) { return s.trim(); } ).filter( Boolean );
779		jQuery( '.filter-pill.filter' ).remove();
780		jQuery( '.filter-pop-out-box.filter .planfilter' ).removeClass( 'selected' ).find( '.pill-close' ).addClass( 'hidden' );
781		benefitValues.forEach( function ( filterValue ) {
782			var $option = jQuery( '.filter-pop-out-box.filter .planfilter[data-filter="' + filterValue + '"]' );
783			if ( $option.length ) {
784				$option.addClass( 'selected' ).find( '.pill-close' ).removeClass( 'hidden' );
785				var label = $option.find( '.pill-label' ).text();
786				var pillHtml = '<div class="filter-pill selected filter" data-filter="' + filterValue + '">' +
787					'<span class="pill-label">' + label + '</span>' +
788					'<span class="pill-close" tabindex="0">' +
789					'<svg width="12" height="12" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg"><title>Remove</title>' +
790					'<path d="M3 1L18 16" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>' +
791					'<path d="M3 16L18 1" stroke="currentColor" stroke-width="3" stroke-linecap="round"/></svg></span></div>';
792				$pillBox.append( pillHtml );
793			}
794		} );
795	}
796
797	if ( plansParam || benefitsParam ) {
798		updateDropDownCounts();
799		filterCards();
800	} else if ( sortParam ) {
801		updateResultCounts();
802	}
803}
804
805function setFeatureLabel( attr, text ) {
806	let search_params = new URLSearchParams(window.location.search);
807	let highest = 0;
808	jQuery('.dental-plan.plan.card[data-' + attr + ']').each(function () {
809		let $card = jQuery(this);
810		let currentValue = $card.data(attr);
811		if (currentValue < highest) {
812			highest = currentValue;
813		}
814	});
815	jQuery('.dental-plan.plan.card[data-' + attr + '="' + highest + '"]')
816		.addClass('has-feature-label ' + attr);
817	jQuery('.dental-plan.plan.card[data-' + attr + '="' + highest + '"] .plan-price')
818		.after('<div class="feature-label"><span>' + text + '</span></div>');
819}
820
821document.addEventListener("DOMContentLoaded", function () {
822
823	// setFeatureLabel('best-savings', 'Most Savings');
824	setFeatureLabel('member-count', 'Most Popular');
825
826	var $pillBox = jQuery('.added-search-pill-box');
827
828	var inputs = document.querySelectorAll(".planfilter");
829
830	jQuery('.filter-dropdown').click(function () {
831		hideAllFilterBoxes();
832		$this = jQuery(this);
833		$this.addClass('active');
834		$popOut = $this.next('.filter-pop-out-box');
835		$popOut.removeClass('hidden');
836		$this.find('.slider-svg').addClass('selected');
837		//synch sort
838
839	});
840
841	jQuery('.filter-dropdown').on('keypress', function (e) {
842		var key = e.key;
843		e.preventDefault();
844		if (key === " " || key === "Enter") {
845			// Only send click events for Space
846			// or Enter keys
847			jQuery(this).click(); // Run the click event for element
848		}
849	});
850
851	//toggle selected state of filter buttons
852	jQuery('.planfilter').click(function () {
853		let $button = jQuery(this);
854		let $filterSet = $button.parents('.filter-pop-out-box');
855		if ($filterSet.hasClass('sort')) {
856			$filterSet.find('.planfilter').removeClass('selected');
857		}
858		$button.toggleClass('selected');
859		if ($button.hasClass('selected')) {
860			$button.find('.pill-close').removeClass('hidden');
861		} else {
862			$button.find('.pill-close').addClass('hidden');
863		}
864	});
865
866	jQuery('.accept-filter').click(function (e) {
867		let $filterSet = jQuery(this).parents('.filter-pop-out-box');
868		let $dropDown = $filterSet.prev('.filter-dropdown');
869		let pillClass = '';
870
871		//clear out active filters in this class
872		if ($filterSet.hasClass('sort')) {
873			//sort
874			$sortButton = $filterSet.find('.planfilter.selected');
875			selectedSort = $sortButton.data('sort');
876			jQuery('#active-sort').text($sortButton.text());
877			sortCards();
878		} else {
879			//filter
880			if ($filterSet.hasClass('plan')) {
881				$pillBox.find('.filter-pill.plan').remove();
882				pillClass = 'plan';
883			} else if ($filterSet.hasClass('filter')) {
884				$pillBox.find('.filter-pill.filter').remove();
885				pillClass = 'filter';
886			}
887			//add the new ones
888			$filterSet.find('.planfilter.selected').each(function () {
889				let pillHtml = `
890					<div class="filter-pill selected ${pillClass}" data-filter="${jQuery(this).data('filter')}">
891						<span class="pill-label">${jQuery(this).find('.pill-label').text()}</span>
892						<span class="pill-close" tabindex="0">
893							<svg width="12" height="12" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
894								<title>Remove</title>
895								<path d="M3 1L18 16" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
896								<path d="M3 16L18 1" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
897							</svg>
898						</span>
899					</div>
900				`;
901				$pillBox.append(pillHtml);
902			});
903
904			updateDropDownCounts();
905
906			//filter!
907			filterCards();
908		}
909		hideAllFilterBoxes();
910		syncUrlToFilters();
911	});
912
913	jQuery('.clear-filter').click(function (e) {
914		let $filterSet = jQuery(this).parents('.filter-pop-out-box');
915		let $dropDown = $filterSet.prev('.filter-dropdown');
916
917		//clear out active filters in this class
918		if ($filterSet.hasClass('sort')) {
919			selectedSort = 'featured';
920			$filterSet.find('.planfilter').removeClass('selected');
921			$sortButton = $filterSet.find('*[data-sort="featured"]').addClass('selected');
922			jQuery('#active-sort').text($sortButton.text());
923			sortCards();
924		} else {
925			$filterSet.find('.planfilter').removeClass('selected');
926			$filterSet.find('.planfilter .pill-close').addClass('hidden');
927			if ($filterSet.hasClass('plan')) {
928				$pillBox.find('.filter-pill.plan').remove();
929
930			} else if ($filterSet.hasClass('filter')) {
931				$pillBox.find('.filter-pill.filter').remove();
932
933			}
934			//update Count
935			$dropDown.find('.filter-count-wrap').attr('data-filter-count', 0);
936			$dropDown.find('.filter-count').text(0);
937			filterCards();
938		}
939
940		hideAllFilterBoxes();
941		syncUrlToFilters();
942	});
943
944	jQuery(".plan-search-results-filter-layout").on("click", ".filter-pill.selected", function () {
945		$button = jQuery(this);
946		$filter = jQuery('.filter-pop-out-box [data-filter="' + $button.data('filter') + '"]');
947		$filter.removeClass('selected');
948		$filter.find('.pill-close').addClass('hidden');
949
950		if ($button.parents('.added-search-pill-box').length) {
951			$button.remove();
952			filterCards();
953		}
954
955		updateDropDownCounts();
956		syncUrlToFilters();
957	});
958
959	jQuery('.close-button-box').click(function () {
960		hideAllFilterBoxes();
961	});
962
963	jQuery('.plan-search-show-button').click(function () {
964		const showing = jQuery(this).parent().hasClass('showing');
965
966		if (showing) {
967			jQuery(this).parent().removeClass('showing');
968			jQuery('.plan-search-filter-box').slideUp(300);
969		} else {
970			jQuery(this).parent().addClass('showing');
971			jQuery('.plan-search-filter-box').slideDown(300);
972		}
973		;
974
975	});
976
977	// Pre-apply filters/sort from URL (server-rendered results).
978	applyFiltersAndSortFromUrl();
979
980	updateResultCounts();
981}, { once: true });
982
983// Pre-apply filters/sort from URL when results are loaded via AJAX (async plan search).
984document.addEventListener( 'planSearchResultsLoaded', function () {
985	applyFiltersAndSortFromUrl();
986}, { once: false } );
987;
988/**
989 * @file Plan comparison tray + compare navigation for plan results.
990 * @requires bootstrap.Modal (Bootstrap 5) for max-selection modal; optional jQuery.modal fallback.
991 * @function getSelectedPlans
992 * @function setPlanComparisonCards
993 * @function renderPlanComparisionCard (returns DocumentFragment of card content)
994 */
995
996(function () {
997	'use strict';
998
999	/**
1000	 * Escape a string for use in a CSS attribute selector [data-plan-id="…"].
1001	 *
1002	 * @param {string|number} value
1003	 * @return {string}
1004	 */
1005	function escapeCssAttr(value) {
1006		const s = String(value);
1007		if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {
1008			return CSS.escape(s);
1009		}
1010		return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
1011	}
1012
1013	/**
1014	 * @param {Element} el
1015	 * @return {boolean}
1016	 */
1017	function isElementVisible(el) {
1018		if (!el || typeof el.getBoundingClientRect !== 'function') {
1019			return false;
1020		}
1021		const style = window.getComputedStyle(el);
1022		if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) {
1023			return false;
1024		}
1025		const rect = el.getBoundingClientRect();
1026		return rect.width > 0 && rect.height > 0;
1027	}
1028
1029	/**
1030	 * Legacy PHP results use `.dp-plan-toggle`; React v2 plan search sets `data-show-family` on
1031	 * `#react-plan-search-results` (see PlanSearchContextProvider).
1032	 *
1033	 * @return {boolean}
1034	 */
1035	function isFamilyPricingSelectedForCompare() {
1036		const reactMount = document.getElementById('react-plan-search-results');
1037		if (reactMount && reactMount.getAttribute('data-show-family') === '1') {
1038			return true;
1039		}
1040		const planToggle = document.querySelector('.dp-plan-toggle');
1041		const toggleSwitch = planToggle ? planToggle.querySelector('.plan-toggle-switch') : null;
1042		return Boolean(toggleSwitch && toggleSwitch.classList.contains('family-selected'));
1043	}
1044
1045	function showComparePlansModal() {
1046		const modalEl = document.getElementById('comparePlansModal');
1047		if (!modalEl) {
1048			return;
1049		}
1050		if (typeof bootstrap !== 'undefined' && bootstrap.Modal) {
1051			bootstrap.Modal.getOrCreateInstance(modalEl).show();
1052			return;
1053		}
1054		if (typeof window.jQuery !== 'undefined' && window.jQuery.fn && window.jQuery.fn.modal) {
1055			window.jQuery(modalEl).modal('show');
1056		}
1057	}
1058
1059	/**
1060	 * Plan cards: React v2 uses .dp-plan-search-plan-card; legacy PHP uses .plan.card.
1061	 * Prefer a card that exposes a logo (data attribute or .card-image-box img).
1062	 *
1063	 * @param {string|number} planId
1064	 * @return {Element|null}
1065	 */
1066	function findPlanCard(planId) {
1067		const idAttr = escapeCssAttr(planId);
1068		const selector = '.dp-plan-search-plan-card[data-plan-id="' + idAttr + '"]';
1069		const reactCards = Array.prototype.slice.call(document.querySelectorAll(selector));
1070		if (reactCards.length) {
1071			const withLogo = reactCards.filter(function (el) {
1072				const fromAttr = el.getAttribute('data-plan-logo');
1073				if (fromAttr && String(fromAttr).length) {
1074					return true;
1075				}
1076				const img = el.querySelector('.card-image-box img');
1077				const imgSrc = img ? img.getAttribute('src') : '';
1078				return Boolean(imgSrc && String(imgSrc).length);
1079			});
1080			const pick = withLogo.length ? withLogo : reactCards;
1081			return pick[0];
1082		}
1083		return document.querySelector('.plan.card[data-plan-id="' + idAttr + '"]');
1084	}
1085
1086	/**
1087	 * Set modal title without HTML string concatenation (WordPress VIP JS rules).
1088	 *
1089	 * @param {string} before Text before the emphasized count.
1090	 * @param {string} countText Count shown inside .count (escaped via textContent).
1091	 * @param {string} after Text after the count.
1092	 */
1093	function setCompareModalTitleParts(before, countText, after) {
1094		const el = document.querySelector('#comparePlansModal .modal-title');
1095		if (!el) {
1096			return;
1097		}
1098		el.textContent = '';
1099		el.appendChild(document.createTextNode(before));
1100		const span = document.createElement('span');
1101		span.className = 'count';
1102		span.textContent = countText;
1103		el.appendChild(span);
1104		el.appendChild(document.createTextNode(after));
1105	}
1106
1107	function getPlanItems(root) {
1108		return Array.prototype.slice.call(root.querySelectorAll('.plan-item'));
1109	}
1110
1111	function getSelectedPlans(root) {
1112		const planItems = getPlanItems(root);
1113		const maxPlans = planItems.filter(isElementVisible).length;
1114		let planToRemove = '';
1115		/* Checkboxes live on plan cards site-wide, not inside `.plan-search-compare-selector` (legacy jQuery used `$('.dp-compare input:checked')`). */
1116		const checked = document.querySelectorAll('.dp-compare input:checked');
1117		let selectedPlans = [];
1118		for (let i = 0; i < checked.length; i++) {
1119			const input = checked[i];
1120			const id = input.getAttribute('data-plan-id');
1121			if (id != null && id !== '') {
1122				selectedPlans.push(id);
1123			}
1124		}
1125		selectedPlans = selectedPlans.filter(function (id, index, arr) {
1126			return arr.indexOf(id) === index;
1127		});
1128		// Legacy jQuery used `$(this).prop('checked', false)` inside a non-bound function (this === window),
1129		// so the second clause was effectively always truthy; keep behavior: trim when over max.
1130		if (selectedPlans.length > maxPlans) {
1131			setCompareModalTitleParts('You can compare up to ', String(maxPlans), ' plans.');
1132			showComparePlansModal();
1133			root.classList.add('warning');
1134			setTimeout(function () {
1135				root.classList.remove('warning');
1136			}, 1500);
1137			planToRemove = selectedPlans.pop();
1138			const esc = escapeCssAttr(planToRemove);
1139			const toUncheck = document.querySelector('.dp-compare input[data-plan-id="' + esc + '"]');
1140			if (toUncheck) {
1141				toUncheck.checked = false;
1142				toUncheck.dispatchEvent(new Event('change', { bubbles: true }));
1143			}
1144		}
1145		const compareBtns = root.querySelectorAll('.compare-plans');
1146		if (selectedPlans.length > 1) {
1147			compareBtns.forEach(function (btn) {
1148				btn.classList.remove('btn-secondary');
1149			});
1150		} else {
1151			compareBtns.forEach(function (btn) {
1152				btn.classList.add('btn-secondary');
1153			});
1154		}
1155		return selectedPlans;
1156	}
1157
1158	/**
1159	 * Build tray card DOM (no HTML string interpolation — VIP-friendly).
1160	 *
1161	 * @param {string|number} planId
1162	 * @return {DocumentFragment}
1163	 */
1164	function renderPlanComparisionCard(planId) {
1165		const frag = document.createDocumentFragment();
1166		const planCard = findPlanCard(planId);
1167		if (!planCard) {
1168			return frag;
1169		}
1170		const price =
1171			planCard.getAttribute('data-low-price') ||
1172			(planCard.dataset && planCard.dataset.lowPrice) ||
1173			'0';
1174		const planName =
1175			(planCard.dataset && planCard.dataset.itemName) ||
1176			planCard.getAttribute('data-item_name') ||
1177			planCard.getAttribute('data-item-name') ||
1178			'';
1179		const logoFromAttr = planCard.getAttribute('data-plan-logo') || '';
1180		const logoImg = planCard.querySelector('.card-image-box img');
1181		const logoFromImg = logoImg ? logoImg.getAttribute('src') || '' : '';
1182		const logoImageSrc = logoFromAttr && String(logoFromAttr).length ? logoFromAttr : logoFromImg;
1183
1184		let displayPrice = price;
1185		try {
1186			const pricingAttribute = planCard.getAttribute('data-pricing');
1187			if (pricingAttribute) {
1188				const pricingData = JSON.parse(pricingAttribute);
1189				const planType = isFamilyPricingSelectedForCompare() ? 'family' : 'individual';
1190				if (pricingData[planType] && pricingData[planType].monthly != null) {
1191					displayPrice = pricingData[planType].monthly;
1192				}
1193			}
1194		} catch (error) {
1195			console.error('Error parsing pricing data for plan comparison card:', error);
1196		}
1197
1198		const removeLink = document.createElement('a');
1199		removeLink.href = '#';
1200		removeLink.className =
1201			'position-absolute top-0 start-100 translate-middle badge rounded-pill remove';
1202		const removeIcon = document.createElement('img');
1203		removeIcon.src =
1204			'/wp-content/plugins/dp-shared-plugins/dp-blocks/plan-compare-selector/x-icon.svg';
1205		removeIcon.alt = 'remove';
1206		const removeSr = document.createElement('span');
1207		removeSr.className = 'visually-hidden';
1208		removeSr.textContent = 'remove';
1209		removeLink.appendChild(removeIcon);
1210		removeLink.appendChild(removeSr);
1211
1212		const cardBody = document.createElement('div');
1213		cardBody.className = 'card-body';
1214		const logoEl = document.createElement('img');
1215		logoEl.className = 'compare-plan-logo';
1216		logoEl.setAttribute('src', logoImageSrc);
1217		logoEl.setAttribute('alt', planName);
1218		const titleEl = document.createElement('div');
1219		titleEl.className = 'card-title fs-5 fw-bold';
1220		titleEl.textContent = '$' + (Number(displayPrice) || 0).toFixed(2) + '/month';
1221		cardBody.appendChild(logoEl);
1222		cardBody.appendChild(titleEl);
1223
1224		frag.appendChild(removeLink);
1225		frag.appendChild(cardBody);
1226		return frag;
1227	}
1228
1229	/**
1230	 * Update monthly price labels on filled compare-tray slots when individual/family toggles.
1231	 * Uses `data-pricing` copied onto each `.plan-item` in `setPlanComparisonCards`.
1232	 *
1233	 * @param {Element|null} root `.plan-search-compare-selector`
1234	 */
1235	function updateCompareTrayPrices(root) {
1236		if (!root) {
1237			return;
1238		}
1239		const planType = isFamilyPricingSelectedForCompare() ? 'family' : 'individual';
1240		root.querySelectorAll('.plan-item').forEach(function (item) {
1241			if (!item.getAttribute('data-plan-id')) {
1242				return;
1243			}
1244			let pricingData;
1245			try {
1246				const pricingAttribute = item.getAttribute('data-pricing');
1247				if (!pricingAttribute) {
1248					return;
1249				}
1250				pricingData = JSON.parse(pricingAttribute);
1251			} catch (error) {
1252				console.error('Error parsing pricing data for comparison card update:', error);
1253				return;
1254			}
1255			const pricing = pricingData[planType];
1256			if (!pricing || pricing.monthly == null) {
1257				return;
1258			}
1259			const cardTitle = item.querySelector('.card-title');
1260			if (cardTitle) {
1261				cardTitle.textContent = '$' + pricing.monthly.toFixed(2) + '/month';
1262			}
1263		});
1264	}
1265
1266	function setPlanComparisonCards(root, selectedPlans) {
1267		const planItems = getPlanItems(root);
1268		planItems.forEach(function (item) {
1269			item.innerHTML = '';
1270			item.classList.remove('text-bg-light');
1271			item.classList.add('text-bg-secondary');
1272		});
1273		/* Matches legacy jQuery order: runs after clear, so this set usually no-ops until slots are filled. */
1274		root.querySelectorAll('.plan-item .card-body *').forEach(function (el) {
1275			el.style.opacity = '0';
1276		});
1277		selectedPlans.forEach(function (planId, i) {
1278			const item = planItems[i];
1279			if (!item) {
1280				return;
1281			}
1282			item.innerHTML = '';
1283			item.appendChild(renderPlanComparisionCard(planId));
1284			item.setAttribute('data-plan-id', String(planId));
1285			item.classList.add('text-bg-light');
1286			item.classList.remove('text-bg-secondary');
1287
1288			const originalPlanCard = findPlanCard(planId);
1289			const pricingData = originalPlanCard
1290				? originalPlanCard.getAttribute('data-pricing')
1291				: null;
1292			if (pricingData) {
1293				item.setAttribute('data-pricing', pricingData);
1294			}
1295		});
1296		setTimeout(function () {
1297			root.querySelectorAll('.plan-item .card-body *').forEach(function (el) {
1298				el.style.opacity = '1';
1299			});
1300		}, 200);
1301		root.setAttribute('data-selected-plans', selectedPlans.join(','));
1302		root.setAttribute('data-plan-count', String(selectedPlans.length));
1303		const compareBtns = root.querySelectorAll('.compare-plans');
1304		if (selectedPlans.length > 1) {
1305			compareBtns.forEach(function (btn) {
1306				btn.classList.remove('btn-secondary');
1307			});
1308		} else {
1309			compareBtns.forEach(function (btn) {
1310				btn.classList.add('btn-secondary');
1311			});
1312		}
1313	}
1314
1315	/** Native `.dp-compare` checkboxes (legacy PHP cards) — sets `data-plan-count` so fixed tray CSS shows. */
1316	function syncTrayFromCompareCheckboxes(tray) {
1317		if (!tray) {
1318			return;
1319		}
1320		const selectedPlans = getSelectedPlans(tray);
1321		setPlanComparisonCards(tray, selectedPlans);
1322	}
1323
1324	function initPlanTypePricingRefresh() {
1325		const reactMount = document.getElementById('react-plan-search-results');
1326		if (reactMount && typeof MutationObserver !== 'undefined') {
1327			const observer = new MutationObserver(function (mutations) {
1328				for (let i = 0; i < mutations.length; i++) {
1329					if (
1330						mutations[i].type === 'attributes' &&
1331						mutations[i].attributeName === 'data-show-family'
1332					) {
1333						updateCompareTrayPrices(document.querySelector('.plan-search-compare-selector'));
1334						return;
1335					}
1336				}
1337			});
1338			observer.observe(reactMount, {
1339				attributes: true,
1340				attributeFilter: ['data-show-family'],
1341			});
1342		}
1343
1344		document.addEventListener('planToggleChanged', function () {
1345			updateCompareTrayPrices(document.querySelector('.plan-search-compare-selector'));
1346		});
1347	}
1348
1349	function initPlanCompareSelector() {
1350		const root = document.querySelector('.plan-search-compare-selector');
1351
1352		initPlanTypePricingRefresh();
1353
1354		window.__dpSetPlanComparisonCards = function (planIds) {
1355			const tray = document.querySelector('.plan-search-compare-selector');
1356			if (tray) {
1357				setPlanComparisonCards(tray, planIds);
1358			}
1359		};
1360
1361		if (!window.__dpPlanCompareCheckboxListener) {
1362			window.__dpPlanCompareCheckboxListener = true;
1363			document.addEventListener('change', function (e) {
1364				const t = e.target;
1365				if (!t || t.tagName !== 'INPUT' || t.type !== 'checkbox') {
1366					return;
1367				}
1368				if (!t.closest('.dp-compare')) {
1369					return;
1370				}
1371				syncTrayFromCompareCheckboxes(document.querySelector('.plan-search-compare-selector'));
1372			});
1373		}
1374
1375		if (!root) {
1376			return;
1377		}
1378
1379		window.addEventListener('pageshow', function () {
1380			if (typeof window.__dpPlanCompareOnPageshow === 'function') {
1381				window.__dpPlanCompareOnPageshow();
1382				return;
1383			}
1384			const selectedPlans = getSelectedPlans(root);
1385			setPlanComparisonCards(root, selectedPlans);
1386		});
1387
1388		const navEntry = window.performance.getEntriesByType('navigation')[0];
1389		const isBackForward =
1390			(navEntry && navEntry.type === 'back_forward') ||
1391			(typeof window.performance.navigation !== 'undefined' &&
1392				window.performance.navigation.type === 2);
1393		if (isBackForward) {
1394			if (typeof window.__dpPlanCompareOnPageshow === 'function') {
1395				window.__dpPlanCompareOnPageshow();
1396			} else {
1397				const selectedPlans = getSelectedPlans(root);
1398				setPlanComparisonCards(root, selectedPlans);
1399			}
1400		}
1401
1402		root.addEventListener('click', function (e) {
1403			const removeBtn = e.target.closest('.remove');
1404			if (!removeBtn || !removeBtn.closest('.plan-item')) {
1405				return;
1406			}
1407			e.preventDefault();
1408			const planItem = removeBtn.closest('.plan-item');
1409			const planId = planItem ? planItem.getAttribute('data-plan-id') : null;
1410			if (planId == null) {
1411				return;
1412			}
1413			if (typeof window.__dpPlanCompareRemoveFromTray === 'function') {
1414				window.__dpPlanCompareRemoveFromTray(planId);
1415			} else {
1416				const esc = escapeCssAttr(planId);
1417				const input = document.querySelector('.dp-compare input[data-plan-id="' + esc + '"]');
1418				if (input) {
1419					input.checked = false;
1420					input.dispatchEvent(new Event('change', { bubbles: true }));
1421				}
1422			}
1423		});
1424
1425		const compareButtons = root.querySelectorAll('.compare-plans');
1426		compareButtons.forEach(function (btn) {
1427			btn.addEventListener('click', function (e) {
1428				e.preventDefault();
1429				const raw = root.getAttribute('data-selected-plans') || '';
1430				const selectedPlans = raw.split(',').map(function (s) {
1431					return s.trim();
1432				});
1433				if (selectedPlans.length > 1) {
1434					const zip = window.userAddress && window.userAddress.postalCode;
1435					const affid = window.DP_AFFID;
1436					const compareUrl = new URL('/compare-plans/', window.location.origin);
1437					const planIdsParam = selectedPlans.filter(Boolean).join(',');
1438					compareUrl.searchParams.set('plan_ids', planIdsParam);
1439					if (zip) {
1440						compareUrl.searchParams.set('zip', String(zip).trim());
1441					}
1442					if (affid) {
1443						compareUrl.searchParams.set('affid', String(affid).trim());
1444					}
1445					if (isFamilyPricingSelectedForCompare()) {
1446						compareUrl.searchParams.set('plan_type', 'family');
1447					}
1448					window.location.assign(compareUrl.href);
1449				} else {
1450					setCompareModalTitleParts('Please select at least ', '2', ' plans to compare.');
1451					showComparePlansModal();
1452				}
1453			});
1454		});
1455
1456		syncTrayFromCompareCheckboxes(root);
1457	}
1458
1459	if (document.readyState === 'loading') {
1460		document.addEventListener('DOMContentLoaded', initPlanCompareSelector);
1461	} else {
1462		initPlanCompareSelector();
1463	}
1464})();
1465;

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.