PageSourceSearch

https://www.beechnut.com/wp-content/plugins/899-testing/assets/js/script.js?v=1790968985

js beechnut.com collected 2026-10-03 21:39:06 UTC 28,565 bytes, 815 lines download raw bytes

1// --- reCAPTCHA v2 callback must be global ---
2window.recaptchaCallback = function (token) {
3	var $ = window.jQuery;
4
5	// store token
6	$('.recaptcha-token').val(token);
7
8	// verify server-side (optional but you already do this)
9	$.ajax({
10		url: '/wp-json/899/v1/verify-recaptcha',
11		method: 'POST',
12		data: { token: token }
13	}).done(function (resp) {
14		if (!resp || !resp.success) {
15			$('.recaptcha-token').val('');         // clear token on failure
16			alert('reCAPTCHA verification failed. Please try again.');
17		}
18	}).always(function () {
19		// enable button if UPC + Lot + reCAPTCHA are present
20		var hasUPC = ($('.upc-value').val() || '').length > 0;
21		var hasLot = ($('.lot-code').val() || '').length > 0;
22		var recok = ($('.recaptcha-token').val() || '') !== '';
23		$('.search-button').attr('disabled', !(hasUPC && hasLot && recok));
24	});
25};
26
27
28
29
30
31jQuery(document).ready(function ($) {
32	var source_json = '';
33	var manual_list = null;
34	var current_upc_rows = []; // rows for the currently selected product type
35	var batchesByUPC = {};     // { upc:string -> Array<batch> }
36	var current_lots = [];
37	var manualListLoaded = false;
38	var pendingProductEl = null;
39	/**
40	 * ---------------------------------------------------------------------------
41	 * Ensure UI pieces exist (we keep the hidden .upc-value/.lot-code/.buy-date
42	 * for your logging; we render a visual search + dropdown next to them)
43	 * ---------------------------------------------------------------------------
44	 */
45	function ensureUPCUI() {
46		var $upcField = $('.upc-field');
47		if (!$upcField.length) return;
48
49		// Hide the raw numeric input used only for logging; we set its value programmatically
50		var $hiddenUPC = $upcField.find('input.upc-value');
51		if ($hiddenUPC.length) { $hiddenUPC.css({ position: 'absolute', left: '-9999px' }); }
52
53
54		// Numeric search input (users type here)
55		if (!$('#upc-search').length) {
56			$upcField.append(
57				'<input id="upc-search" type="text" inputmode="numeric" ' +
58				'pattern="[0-9]*" placeholder="Type UPC to filter (numbers only)" ' +
59				'style="margin-top:8px;display:block;width:100%;">'
60			);
61		}
62
63		// Visual results dropdown (ul/li style)
64		if (!$('#upc-results').length) {
65			$upcField.css('position', 'relative'); // for absolute dropdown if you style it
66			$upcField.append(
67				'<div id="upc-results" class="upc-results" role="listbox" ' +
68				'aria-expanded="false" style="display:none;"></div>'
69			);
70		}
71
72		// Selected preview (name + upc + image) shown after user picks an item
73		if (!$('#upc-preview').length) {
74			$upcField.append(
75				'<div id="upc-preview" style="display:none;gap:12px;align-items:center;margin-top:10px;">' +
76				'<img id="upc-preview-img" alt="" style="width:52px;height:52px;object-fit:contain;display:none;">' +
77				'<div style="flex:1 1 auto">' +
78				'<div class="name" style="font-weight:600;"></div>' +
79				'<div class="upc" style="font-size:12px;color:#666;"></div>' +
80				'</div>' +
81				// NEW: clear button
82				'<button type="button" id="upc-clear" ' +
83				'style="flex:0 0 auto;border:0;background:#e8e8e8;padding:6px 10px;border-radius:4px;cursor:pointer;">Change</button>' +
84				'</div>'
85			);
86		}
87
88		// LOT CODE select UI (we still keep your hidden .lot-code / .buy-date for logs)
89		var $lotInput = $('.lot-code');
90		if ($lotInput.length) { $lotInput.css({ position: 'absolute', left: '-9999px' }); } // hide the raw input
91
92		// Keep original <select> as a hidden fallback
93		if (!$('#lot-code-select').length) {
94			$lotInput.after(
95				'<select id="lot-code-select" style="position:absolute;left:-9999px;top:auto;">' +
96				'<option value="">Select Best By Date — lot code</option>' +
97				'</select>'
98			);
99		}
100
101		// Lot search + results + preview (mirrors UPC UX)
102		if (!$('#lot-ui').length) {
103			$('#lot-code-select').after(
104
105				'<div id="lot-ui" style="display:none">' +
106				'<div class="input-label">Best By Date - Lot Code*</div>' +
107				'<input id="lot-search" type="text" placeholder="Search Best By Date (e.g. 01DEC2027) or lot code..." ' +
108				'style="display:block;width:100%;margin-top:6px;">' +
109				'<div id="lot-results" class="upc-results" role="listbox" aria-expanded="false" style="display:none;"></div>' +
110				'<div id="lot-preview" style="display:none;gap:12px;align-items:baseline;margin-top:10px;margin-bottom:20px">' +
111				'<div class=""preview-inner>' +
112				'<div class="best" style="font-weight:600;"></div>' +
113				'<div class="lot"  style="font-size:12px;color:#666;"></div>' +
114				'</div>' +
115				'<button type="button" id="lot-clear" ' +
116				'style="flex:0 0 auto;border:0;background:#e8e8e8;padding:6px 10px;border-radius:4px;cursor:pointer;">Change</button>' +
117				'</div>' +
118				'</div>'
119			);
120		}
121
122	}
123	ensureUPCUI();
124
125	/**
126	 * ---------------------------------------------------------------------------
127	 * Fetch batches (source_json) & index by UPC
128	 * ---------------------------------------------------------------------------
129	 */
130	var endpoint = '/wp-json/899/v1/fetch-' + test_data.dataSource + '-content/';
131	$.ajax({
132		url: endpoint,
133		method: 'POST',
134		dataType: 'json',
135		success: function (data) {
136			source_json = data || {};
137// 			console.log(source_json)
138			batchesByUPC = {};
139			if (source_json && Array.isArray(source_json.batches)) {
140				source_json.batches.forEach(function (b) {
141					var u = normalizeUPC(b.upcCode); // <-- add single leading 0 if missing
142					if (!u) return;
143					(batchesByUPC[u] = batchesByUPC[u] || []).push(b);
144				});
145			}
146
147		},
148		error: function () {
149			fetchIP().then(ip => {
150				sendLogData({
151					'upc': '', 'lot_code': '', 'bestby': '', 'ip_address': ip, 'action': '',
152					'logged_in_status': test_data.logged_in_status,
153					'product_type': '', 'failure_details': '', 'success_details': '',
154					'events': 'form_submission_failed_db_error'
155				});
156			});
157		}
158	});
159
160	/**
161	 * ---------------------------------------------------------------------------
162	 * Fetch manual list (used to populate UPC dropdown)
163	 * ---------------------------------------------------------------------------
164	 */
165	$.ajax({
166		url: '/wp-json/test-log/v1/manual-product-list',
167		method: 'GET',
168		cache: false,
169		dataType: 'json',
170		success: function (resp) {
171			manual_list = (resp && resp.rows) ? resp.rows : [];
172			manualListLoaded = true;
173			if (pendingProductEl) {
174				var $el = pendingProductEl;
175				pendingProductEl = null;
176				$el.trigger('click');
177			}
178		},
179		error: function () {
180			fetchIP().then(ip => {
181				sendLogData({
182					'upc': '', 'lot_code': '', 'bestby': '', 'ip_address': ip, 'action': '',
183					'logged_in_status': test_data.logged_in_status, 'product_type': '',
184					'failure_details': 'manual_product_list_fetch_failed', 'success_details': '',
185					'events': 'form_submission_failed_db_error'
186				});
187			});
188		}
189	});
190
191	/**
192	 * ---------------------------------------------------------------------------
193	 * Utilities
194	 * ---------------------------------------------------------------------------
195	 */
196	function sendLogData(data) {
197		$.ajax({
198			url: test_data.rest_url,
199			method: 'POST',
200			data: JSON.stringify(data),
201			contentType: 'application/json',
202			success: function (r) { console.log('Success:', r); },
203			error: function (xhr) {
204				console.log('Error:', xhr && xhr.responseText ? xhr.responseText : 'unknown');
205				alert('Failed to send log data.');
206			}
207		});
208	}
209
210	function convertDateString(dateStr) {
211		const months = {
212			JAN: '01', FEB: '02', MAR: '03', APR: '04', MAY: '05', JUN: '06',
213			JUL: '07', AUG: '08', SEP: '09', OCT: '10', NOV: '11', DEC: '12'
214		};
215		let day = dateStr.substring(0, 2);
216		let monthAbbr = dateStr.substring(2, 5);
217		let year = dateStr.substring(5);
218		let month = months[monthAbbr];
219		if (!month) { alert('Bestby date is invalid'); throw new Error('Invalid Date'); }
220		return day + month + year;
221	}
222
223	function escapeHtml(s) {
224		return (s || '').replace(/[&<>"'`=\/]/g, function (c) {
225			return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;', '/': '&#x2F;', '`': '&#x60;', '=': '&#x3D;' }[c];
226		});
227	}
228
229	function fetchIP() { return new Promise((resolve, reject) => { $.getJSON('https://api.ipify.org?format=json', d => resolve(d.ip)).fail(reject) }); }
230
231	// Put this next to escapeHtml / fetchIP helpers
232	function normalizeUPC(raw) {
233		var digits = (raw || '').toString().replace(/\D+/g, '');
234		if (!digits) return '';
235		// ensure ONE leading 0 only
236		return digits.charAt(0) === '0' ? digits : ('0' + digits);
237	}
238
239	function isDateLike(s) {
240		// matches 01DEC2027 (DDMMMYYYY, MMM uppercase)
241		return /^[0-3]\d[A-Z]{3}\d{4}$/.test((s || '').toString().trim().toUpperCase());
242	}
243
244	// Some rows occasionally have bestBy/lotCode swapped. Correct them here.
245	function normalizeBestLot(bestIn, lotIn) {
246		var best = (bestIn || '').toString().trim();
247		var lot = (lotIn || '').toString().trim();
248		// If best is NOT a date but lot IS a date, swap them.
249		if (!isDateLike(best) && isDateLike(lot)) {
250			var t = best; best = lot; lot = t;
251		}
252		return {
253			best: best.toUpperCase().replace(/\s+/g, ''),
254			lot: lot.toLowerCase().replace(/\s+/g, '')
255		};
256	}
257
258	function formatLabel(best, lot) {
259		// Always "Expiry — Lot" for display
260		return best + ' — ' + lot;
261	}
262
263	function renderLotList(rows) {
264		var $list = $('#lot-results'); if (!$list.length) return;
265		if (!rows || !rows.length) {
266			$list.empty().hide().attr('aria-expanded', 'false');
267			return;
268		}
269		var html = rows.map(function (r) {
270			return '' +
271				'<div class="upc-item lot-item" role="option" tabindex="0" ' +
272				'data-best="' + escapeHtml(r.best) + '" ' +
273				'data-lot="' + escapeHtml(r.lot) + '">' +
274				'<div class="t" style="display:flex;justify-content:space-between;align-items:center;gap:12px;">' +
275				'<div class="lt" style="min-width:0">' +
276				'<div class="name" style="font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">'
277				+ escapeHtml(formatLabel(r.best, r.lot)) + '</div>' +
278				'</div>' +
279				'</div>' +
280				'</div>';
281		}).join('');
282		$list.html(html).show().attr('aria-expanded', 'true');
283	}
284
285	function filterLotList(query) {
286		query = (query || '').toString().trim().toLowerCase();
287		if (!query) return current_lots.slice(0, 500);
288		return current_lots.filter(function (r) {
289			return r.best.toLowerCase().indexOf(query) !== -1
290				|| r.lot.toLowerCase().indexOf(query) !== -1;
291		}).slice(0, 500);
292	}
293
294	function selectLotFromList(best, lot) {
295		// write hidden fields used by logging
296		$('.lot-code').val(lot);
297		$('.buy-date').val(best);
298
299		// update preview
300		$('#lot-preview .best').text(best + ' - ' + lot);
301		// sync the hidden <select> to the chosen pair (fallback parity)
302		var $sel = $('#lot-code-select');
303		var matched = false;
304		$sel.find('option').each(function () {
305			var $o = $(this);
306			if (($o.data('bestby') || '').toString().toUpperCase().replace(/\s+/g, '') === best
307				&& ($o.data('lot') || '').toString().toLowerCase().replace(/\s+/g, '') === lot) {
308				$sel.val($o.attr('value'));
309				matched = true;
310				return false;
311			}
312		});
313		if (!matched) { $sel.val(''); } // harmless if no option matches
314
315		// close list & hide search
316		$('#lot-results').hide().attr('aria-expanded', 'false');
317		$('#lot-search').val('').hide();
318		$('#lot-preview').css('display', 'flex');
319
320		updateButtonState();
321	}
322
323	/**
324	 * ---------------------------------------------------------------------------
325	 * UPC dropdown (UL/LI with images) — render + filter + select
326	 * ---------------------------------------------------------------------------
327	 */
328	function renderUPCList(rows) {
329		var $list = $('#upc-results'); if (!$list.length) return;
330
331		if (!rows || !rows.length) {
332			$list.empty().hide().attr('aria-expanded', 'false');
333			return;
334		}
335
336		// Build the list with real <img src="..."> so the browser caches them.
337		var html = rows.map(function (r) {
338			var imgHtml = r.image_url
339				? '<img src="' + escapeHtml(r.image_url) + '" loading="lazy" alt="" ' +
340				'style="width:52px;height:52px;object-fit:contain;flex:0 0 auto;">'
341				: '';
342
343			return '' +
344				'<div class="upc-item" role="option" tabindex="0" ' +
345				'data-upc="' + escapeHtml(r.upc) + '" ' +
346				'data-name="' + escapeHtml(r.short_name || '') + '" ' +
347				'data-img="' + escapeHtml(r.image_url || '') + '">' +
348				'<div class="t" style="display:flex;justify-content:space-between;align-items:center;gap:12px;">' +
349				'<div class="lt" style="min-width:0">' +
350				'<div class="name" style="font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">'
351				+ escapeHtml(r.short_name || '') + '</div>' +
352				'<div class="upc" style="font-size:12px;color:#666;">UPC #' + escapeHtml(r.upc) + '</div>' +
353				'</div>' +
354				imgHtml +
355				'</div>' +
356				'</div>';
357		}).join('');
358
359		$list.html(html).show().attr('aria-expanded', 'true');
360	}
361
362	function filterUPCList(query) {
363		query = (query || '').toString();
364		if (!query) return current_upc_rows.slice(0, 500); // cap
365		return current_upc_rows.filter(function (r) {
366			return (r.upc && r.upc.indexOf(query) !== -1);
367		}).slice(0, 500);
368	}
369	function selectUPCFromList(upc, name, img) {
370		// write into hidden .upc-value for logging
371		$('.upc-value').val(upc).attr('maxlength', (upc || '').length);
372
373		// update preview
374		$('#upc-preview .name').text(name || '');
375		$('#upc-preview .upc').text(upc ? ('UPC #' + upc) : '');
376		if (img) {
377			$('#upc-preview-img').attr('src', img).show();
378		} else {
379			$('#upc-preview-img').attr('src', '').hide();
380		}
381
382		// populate Lot Code select for this UPC
383		buildLotCodeSelect(upc);
384
385		// close list & hide search input; show preview
386		$('#upc-results').hide().attr('aria-expanded', 'false');
387		$('#upc-search').val('').hide();
388		$('#upc-preview').css('display', 'flex');
389
390		$('#lot-preview').hide();
391		$('#lot-search').val('').show();
392		$('#lot-ui').slideDown(300)
393		updateButtonState();
394	}
395
396
397	// Build Lot Code select from source_json batches
398	function buildLotCodeSelect(upc) {
399		var $sel = $('#lot-code-select'); if (!$sel.length) return;
400		$sel.empty().append('<option value="">Select Best By Date — lot code</option>');
401
402		var matches = batchesByUPC[upc] || [];
403
404		// Normalize & index
405		current_lots = matches.map(function (b, i) {
406			var norm = normalizeBestLot(b.bestBy, b.lotCode); // swap if needed
407			// keep hidden fallback <select> synced
408			$sel.append(
409				'<option value="' + i + '" data-bestby="' + escapeHtml(norm.best) + '" data-lot="' + escapeHtml(norm.lot) + '">' +
410				escapeHtml(formatLabel(norm.best, norm.lot)) +
411				'</option>'
412			);
413			return { idx: i, best: norm.best, lot: norm.lot };
414		});
415
416		// ✅ Detect "no test data"
417		var validCount = current_lots.filter(function (r) { return r.best && r.lot; }).length;
418		var isPuffies = ($('.search-box .search-title').text().trim() === 'Puffies');
419		var baseLabel = isPuffies ? 'Best By Date*' : 'Best By Date - Lot Code*';
420		if (validCount === 0) {
421			$('#lot-ui .input-label').html(baseLabel + ' <span style="color:#c00;font-weight:600;">(No test data found)</span>');
422		} else {
423			$('#lot-ui .input-label').text(baseLabel);
424		}
425
426		// Reset visible Lot UI
427		$('#lot-preview').hide();
428		$('#lot-search').val('').show();
429		renderLotList(current_lots);
430	}
431
432
433	/**
434	 * ---------------------------------------------------------------------------
435	 * Events: search input (numeric only), list select, outside click, lot select
436	 * ---------------------------------------------------------------------------
437	 */
438	// Numeric-only and instant filter
439	$(document).on('input', '#upc-search', function () {
440		var digits = ($(this).val() || '').replace(/\D+/g, ''); // numeric-only
441		$(this).val(digits);
442		renderUPCList(filterUPCList(digits));
443	});
444
445
446	// Open list on focus/click into the search box
447	$(document).on('focus click', '#upc-search', function () {
448		var q = $('#upc-search').val();
449		renderUPCList(filterUPCList(q));
450	});
451
452	// Pick an item (mouse)
453	$(document).on('click', '#upc-results .upc-item', function () {
454		var upc = normalizeUPC($(this).data('upc'));   // <-- normalize here too
455		var name = ($(this).data('name') || '').toString();
456		var img = ($(this).data('img') || '').toString();
457		selectUPCFromList(upc, name, img);
458	});
459
460
461	// Pick an item (keyboard)
462	$(document).on('keydown', function (e) {
463		if (e.key === 'Escape') {
464			$('#upc-results, #lot-results').hide().attr('aria-expanded', 'false');
465		}
466	});
467	// Close dropdowns when clicking outside
468	$(document).on('click', function (e) {
469		// UPC dropdown
470		if (!$(e.target).closest('.upc-field').length) {
471			$('#upc-results').hide().attr('aria-expanded', 'false');
472		}
473		// LOT dropdown
474		if (!$(e.target).closest('#lot-ui').length) {
475			$('#lot-results').hide().attr('aria-expanded', 'false');
476		}
477	});
478
479
480	// Lot code select -> set hidden .lot-code & .buy-date
481	$(document).on('change', '#lot-code-select', function () {
482		var $opt = $(this).find('option:selected');
483		var lot = ($opt.data('lot') || '').toString().replace(/\s+/g, '').toLowerCase();
484		var best = ($opt.data('bestby') || '').toString().replace(/\s+/g, '').toUpperCase();
485
486		$('.lot-code').val(lot);
487		$('.buy-date').val(best);
488
489		updateButtonState();
490	});
491
492	// Clear the selected UPC and return to the search input
493	// Clear the selected UPC and return to the search input
494	$(document).on('click', '#upc-clear', function () {
495		// clear hidden fields
496		$('.upc-value').val('');
497		$('.lot-code').val('');
498		$('.buy-date').val('');
499
500		// reset UI
501		$('#upc-preview-img').attr('src', '').hide();
502		$('#upc-preview .name').text('');
503		$('#upc-preview .upc').text('');
504		$('#upc-preview').hide();
505
506		$('#lot-code-select').html('<option value="">Select Best By Date — lot code</option>');
507		$('#upc-results').hide().empty().attr('aria-expanded', 'false');
508
509		// show search again and focus it
510		$('#upc-search').val('').show().trigger('focus');
511
512		// 🔧 NEW: hard-reset Lot UI
513		current_lots = [];                          // clear in-memory lots
514		$('#lot-ui').hide();                        // hide the Lot UI
515		$('#lot-results').hide().empty().attr('aria-expanded', 'false'); // close dropdown
516		$('#lot-search').val('');                   // clear search box
517		$('#lot-preview').hide().find('.best,.lot').text(''); // clear preview texts
518
519		updateButtonState();
520	});
521
522
523	// Lot search input: filter as you type
524	$(document).on('input', '#lot-search', function () {
525		var q = ($(this).val() || '');
526		renderLotList(filterLotList(q));
527	});
528
529	// Open lot list on focus/click
530	$(document).on('focus click', '#lot-search', function () {
531		var q = $('#lot-search').val();
532		renderLotList(filterLotList(q));
533	});
534
535	// Select a lot (mouse)
536	$(document).on('click', '#lot-results .lot-item', function () {
537		var best = ($(this).data('best') || '').toString();
538		var lot = ($(this).data('lot') || '').toString();
539		selectLotFromList(best, lot);
540	});
541
542	// Select a lot (keyboard)
543	$(document).on('keydown', '#lot-results .lot-item', function (e) {
544		if (e.key === 'Enter' || e.key === ' ') {
545			e.preventDefault();
546			$(this).click();
547		}
548	});
549
550	// Clear lot selection & go back to search
551	$(document).on('click', '#lot-clear', function () {
552		$('.lot-code').val('');
553		$('.buy-date').val('');
554
555		$('#lot-preview .best').text('');
556		$('#lot-preview .lot').text('');
557		$('#lot-preview').hide();
558
559		$('#lot-results').hide().empty().attr('aria-expanded', 'false');
560		$('#lot-search').val('').show().trigger('focus');
561
562		updateButtonState();
563	});
564
565	/**
566	 * ---------------------------------------------------------------------------
567	 * Product-type click (match by data-product-key = ACF name_key)
568	 * Pad UPCs with a leading "0" if missing
569	 * ---------------------------------------------------------------------------
570	 */
571	$(document).on('click', '.product-type', function () {
572
573		if (!manualListLoaded) {
574			e.preventDefault();
575			pendingProductEl = $(this);
576			// Optional: light hint in UI (non-blocking)
577			$('#upc-results').hide().attr('aria-expanded', 'false');
578			$('#upc-search').val(''); // keeps it clean
579			alert('UPC Data is loading...')
580			return; // bail now; we'll replay this click in success callback above
581		}
582		$('#upc-search').val('').show();
583		$('#upc-results').hide().empty().attr('aria-expanded', 'false');
584		$('#upc-preview-img').attr('src', '').hide();
585		$('#upc-preview .name').text(''); $('#upc-preview .upc').text('');
586		$('#upc-preview').hide();
587		$('.upc-value,.lot-code,.buy-date').val('');
588		$('#lot-code-select').html('<option value="">Select Best By Date — lot code</option>');
589		$('#lot-ui').hide();
590
591
592		var title = $(this).find('.product-title').text();
593		var typeKey = $(this).data('product-key') || ''; // preferred exact key
594
595		// NEW: filter manual_list by the canonical product key and normalize UPCs
596		current_upc_rows = (Array.isArray(manual_list) ? manual_list : [])
597			.filter(function (r) { return (r.type_899 || '') === typeKey; })
598			.map(function (r) {
599				return {
600					upc: normalizeUPC(r.upc),      // <-- digits + single leading 0
601					short_name: r.short_name || '',
602					image_url: r.image_url || '',
603					type_899: r.type_899 || ''
604				};
605			});
606
607		// Fallback by visible title if nothing matched the key
608		if (!current_upc_rows.length && title) {
609			var t = (title || '').trim().toLowerCase();
610			current_upc_rows = (Array.isArray(manual_list) ? manual_list : [])
611				.filter(function (r) { return ((r.type_899 || '').trim().toLowerCase() === t); })
612				.map(function (r) {
613					return {
614						upc: normalizeUPC(r.upc),
615						short_name: r.short_name || '',
616						image_url: r.image_url || '',
617						type_899: r.type_899 || ''
618					};
619				});
620		}
621
622
623		// Show all for this type initially
624		renderUPCList(current_upc_rows);
625
626		// Safe reCAPTCHA reset
627		if (window.grecaptcha && typeof grecaptcha.reset === 'function') { try { grecaptcha.reset(); } catch (e) { } }
628		$('.recaptcha-token').val('');
629		updateButtonState();
630
631		var guideUrl = $(this).data('guide-url');
632		$('.search-box').attr('data-selected-type', title);
633		$('.search-box .search-title').text(title);
634		$('.search-box .guide-img').attr('src', guideUrl);
635
636		if ($(this).data('comparative') == 1) { $('td.fda-col,td.eu-col').show(0); }
637		else { $('td.fda-col,td.eu-col').hide(0); }
638
639		if (!$('.search-box').is(':visible')) {
640			$('.search-box').slideDown(function () {
641				document.querySelector('.search-box').scrollIntoView({ behavior: 'smooth' });
642			});
643		} else {
644			document.querySelector('.search-box').scrollIntoView({ behavior: 'smooth' });
645		}
646	});
647
648	/**
649	 * ---------------------------------------------------------------------------
650	 * Validation & buttons
651	 * ---------------------------------------------------------------------------
652	 */
653	var user_ip = '';
654	fetchIP().then(ip => user_ip = ip).catch(() => user_ip = 'null');
655
656	function updateButtonState() {
657		var hasUPC = ($('.upc-value').val() || '').length > 0;
658		var hasLot = ($('.lot-code').val() || '').length > 0;
659		var recaptchaVerified = $('.recaptcha-token').val() !== '';
660
661		$('.search-button').attr('disabled', !(hasUPC && hasLot && recaptchaVerified));
662	}
663
664
665
666	// Keep button state fresh as user types (buy-date stays as-is; we fill via lot select)
667	$('.input-field input').on('input', function () { updateButtonState(); });
668
669	/**
670	 * ---------------------------------------------------------------------------
671	 * Search button: keep your original matching/logging logic (unchanged)
672	 * ---------------------------------------------------------------------------
673	 */
674	$('.search-button').on('click', function () {
675		var upcInput = ($('.upc-value').val() || '').toString();
676		var lot_code = ($('.lot-code').val() || '').toString();
677		var bestby = ($('.buy-date').val() || '').toString();
678		var product_type = $(this).closest('.search-box').data('selected-type');
679
680		var data = {
681			'upc': upcInput,
682			'lot_code': lot_code,
683			'bestby': bestby,
684			'ip_address': (typeof user_ip !== 'undefined' ? user_ip : ''),
685			'action': '',
686			'logged_in_status': test_data.logged_in_status,
687			'product_type': product_type,
688			'failure_details': '',
689			'success_details': '',
690			'events': ''
691		};
692
693		// Find the exact batch (should always exist since selections came from source_json)
694		var foundBatch = null;
695		var list = batchesByUPC[upcInput] || [];
696		for (var i = 0; i < list.length; i++) {
697			var b = list[i];
698			var lot = (b.lotCode || '').toString().replace(/\s+/g, '').toLowerCase();
699			var bb = (b.bestBy || '').toString().replace(/\s+/g, '').toUpperCase();
700			if (lot === lot_code && bb === bestby) { foundBatch = b; break; }
701		}
702
703		if (!foundBatch) {
704			// Unexpected edge case: log once as generic failure
705			data.action = 'failed';
706			data.failure_details = 'Selected UPC/Lot/BestBy not found in batches';
707			data.events = 'form_submission_failed_db_error';
708			sendLogData(data);
709			alert('Sorry, something went wrong. Please try again.');
710			return;
711		}
712
713		// Fill the popup results
714		var results = foundBatch.results || [];
715		$('input.popup-checkbox').show(0);
716		for (var j = 0; j < results.length; j++) {
717			var r = results[j];
718			var id = (r.name || '').replace(/ /g, '-');
719			var val = (r.actualValue || '') === '' ? 'n/a' : r.actualValue;
720
721			if (val === 'n/a') {
722				$('.' + id + ' .popup-checkbox').hide(0);
723			} else {
724				var m = val.match(/([<>]?)(\d+(\.\d+)?)/);
725				if (m) { var p = m[1], n = m[2]; if (!n.includes('.')) n += '.0'; val = p + n; }
726			}
727			$('#' + id).text(val);
728		}
729
730		// Success path + popup
731		data.action = 'success';
732		data.success_details = 'Search Success - From dropdown selection';
733		data.events = 'form_submission_success,greensearchbox_clicked_success,';
734		sendLogData(data);
735
736		$('.no-match').hide(0);
737		$('.page1').show(0);
738		$('.page2').hide(0);
739
740		var recaptchaVerified = $('.recaptcha-token').val() !== '';
741		if (!recaptchaVerified) { alert('Please Verify Google Captcha!'); return; }
742
743		$.magnificPopup.open({
744			items: { src: '#popup-page1' },
745			type: 'inline',
746			removalDelay: 100,
747			mainClass: 'mfp-fade',
748			callbacks: {
749				close: function () {
750					$('.search-box').hide(0, function () {
751						$('#flavor_area').hide(0);
752						$('.upc-value,.lot-code,input.buy-date').val('');
753						$('html, body').animate({ scrollTop: $("#product_types").offset().top }, 2000);
754					});
755				}
756			}
757		});
758
759		if (Array.isArray(test_data.rewardcodes) && test_data.rewardcodes.indexOf(upcInput) !== -1) {
760			$('.top_box').show();
761		} else {
762			$('.top_box').hide();
763		}
764	});
765
766
767	/**
768	 * ---------------------------------------------------------------------------
769	 * Popup radios logging (unchanged)
770	 * ---------------------------------------------------------------------------
771	 */
772	function convertDateToISO8601(dateStr) {
773		const months = { JAN: '01', FEB: '02', MAR: '03', APR: '04', MAY: '05', JUN: '06', JUL: '07', AUG: '08', SEP: '09', OCT: '10', NOV: '11', DEC: '12' };
774		const day = dateStr.substring(0, 2), monthAbbr = dateStr.substring(2, 5).toUpperCase(), year = dateStr.substring(5);
775		const month = months[monthAbbr]; if (!month) throw new Error('Invalid month');
776		return `${year}-${month}-${day}`;
777	}
778	$('.form-border input').on('click', function () {
779		var rawBestBuyDate = $('.buy-date').val().replace(/\s+/g, '').toUpperCase();
780		var s_title = $('h2.search-title').text();
781		if (s_title === 'Pouches with Meat') {
782			rawBestBuyDate = rawBestBuyDate.substring(0, 5) + '20' + rawBestBuyDate.substring(5);
783		}
784		var parts = convertDateToISO8601(rawBestBuyDate).split('-');
785		var dObj = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10));
786		var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
787		var d = dObj.getDate(), suffix = (d >= 11 && d <= 13) ? 'th' : ({ 1: 'st', 2: 'nd', 3: 'rd' }[d % 10] || 'th');
788		var readableDate = d + suffix + ' ' + monthNames[dObj.getMonth()] + ', ' + dObj.getFullYear();
789
790		var upcSelected = $('.upc-value').val();
791		var lotCode = $('.lot-code').val();
792
793		$('.resultData').text(`Your Results for ${upcSelected}, lot code ${lotCode}, best by date ${readableDate}`);
794		$('.page1').slideUp(500); $('.page2').slideDown(500);
795
796		var value = $(this).val();
797		var product_type = $('.search-box').data('selected-type');
798
799		fetchIP().then(ip => {
800			var bestby = convertDateString($('.buy-date').val().replace(/\s+/g, '').toUpperCase());
801			sendLogData({
802				'upc': upcSelected,
803				'lot_code': lotCode,
804				'bestby': bestby,
805				'ip_address': ip,
806				'action': 'success',
807				'logged_in_status': test_data.logged_in_status,
808				'product_type': product_type,
809				'failure_details': '',
810				'success_details': '',
811				'events': 'understandingbox_click_' + value
812			});
813		});
814	});
815});

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.