PageSourceSearch

https://www.fondazioneadrianolivetti.it/wp-content/plugins/change-…/js/passwordless-otp-login.js?ver=2.4.0

js fondazioneadrianolivetti.it collected 2026-09-25 05:18:37 UTC 21,445 bytes, 769 lines download raw bytes

1(function () {
2	'use strict';
3
4	if (typeof aioLoginOtp === 'undefined') {
5		return;
6	}
7
8	var state = {
9		channel: '',
10		token: '',
11		length: 4,
12		resendIn: 0,
13		resendTimer: null,
14		redirectUrl: '',
15		redirectTimer: null,
16		redirectStarted: false,
17		verifyBlocked: false,
18	};
19
20	var loginEl = document.getElementById('login');
21	var panel = document.getElementById('aio-login-otp-panel');
22
23	if (!panel) {
24		return;
25	}
26
27	function mountPanelInsideLoginform() {
28		var body = document.body.classList;
29		return body.contains('aio-login__template-09')
30			|| body.contains('aio-login__template-06')
31			|| body.contains('aio-login__template-01');
32	}
33
34	function mountPanelInLogin() {
35		if (!loginEl) {
36			loginEl = document.getElementById('login');
37		}
38		if (!loginEl || !panel) {
39			return;
40		}
41		var form = document.getElementById('loginform');
42		if (mountPanelInsideLoginform() && form) {
43			if (panel.parentNode === form) {
44				return;
45			}
46			if (document.body.classList.contains('aio-login__template-09')) {
47				var logo = form.querySelector('h1');
48				if (logo) {
49					form.insertBefore(panel, logo.nextSibling);
50					return;
51				}
52			}
53			form.insertBefore(panel, form.firstChild);
54			return;
55		}
56		if (form && form.parentNode) {
57			if (panel.parentNode === form.parentNode && panel.nextElementSibling === form) {
58				return;
59			}
60			form.parentNode.insertBefore(panel, form);
61			return;
62		}
63		if (panel.parentNode !== loginEl) {
64			loginEl.insertBefore(panel, loginEl.firstChild);
65		}
66	}
67
68	mountPanelInLogin();
69
70	/**
71	 * Place OTP buttons after username/password and before social login (all templates).
72	 */
73	function positionOtpButtons() {
74		var form = document.getElementById('loginform');
75		if (!form) {
76			return;
77		}
78		var wrapper = form.querySelector('.aio-login-otp-buttons-wrapper');
79		if (!wrapper) {
80			return;
81		}
82		var magicLink = form.querySelector('.aio-login-magic-link-buttons-wrapper');
83		if (magicLink && magicLink.parentNode === form) {
84			form.insertBefore(wrapper, magicLink);
85			return;
86		}
87		var social = form.querySelector('.aio-login-social-login-buttons-wrapper');
88		if (social && social.parentNode === form) {
89			form.insertBefore(wrapper, social);
90			return;
91		}
92		var remember = form.querySelector('p.forgetmenot');
93		if (remember && remember.parentNode === form) {
94			form.insertBefore(wrapper, remember);
95			return;
96		}
97		var submit = form.querySelector('p.submit');
98		if (submit && submit.parentNode === form) {
99			form.insertBefore(wrapper, submit);
100		}
101	}
102
103	positionOtpButtons();
104	if (typeof window.aioLoginPositionFormCaptchas === 'function') {
105		window.aioLoginPositionFormCaptchas();
106	}
107	if (document.readyState === 'loading') {
108		document.addEventListener('DOMContentLoaded', function () {
109			mountPanelInLogin();
110			positionOtpButtons();
111			if (typeof window.aioLoginPositionFormCaptchas === 'function') {
112				window.aioLoginPositionFormCaptchas();
113			}
114		});
115	}
116
117	var steps = {
118		contact: panel.querySelector('[data-step="contact"]'),
119		verify: panel.querySelector('[data-step="verify"]'),
120		success: panel.querySelector('[data-step="success"]'),
121	};
122
123	var els = {
124		title: panel.querySelector('.aio-login-otp-step--contact .aio-login-otp-panel__title'),
125		contactError: panel.querySelector('.aio-login-otp-step--contact .aio-login-otp-panel__error'),
126		verifyError: panel.querySelector('.aio-login-otp-step--verify .aio-login-otp-panel__error'),
127		emailWrap: panel.querySelector('.aio-login-otp-field--email'),
128		smsWrap: panel.querySelector('.aio-login-otp-field--sms'),
129		email: document.getElementById('aio-login-otp-email'),
130		country: document.getElementById('aio-login-otp-country'),
131		phone: document.getElementById('aio-login-otp-phone'),
132		phoneDial: document.getElementById('aio-login-otp-phone-dial'),
133		captcha: panel.querySelector('.aio-login-otp-captcha'),
134		sendBtn: panel.querySelector('.aio-login-otp-send'),
135		verifyBtn: panel.querySelector('.aio-login-otp-verify'),
136		otpCode: document.getElementById('aio-login-otp-code'),
137		resendBtn: panel.querySelector('.aio-login-otp-resend'),
138		resendTimer: panel.querySelector('.aio-login-otp-resend-timer'),
139		continueBtn: panel.querySelector('.aio-login-otp-continue'),
140		backToLoginWrap: panel.querySelector('.aio-login-otp-panel__back-wrap'),
141	};
142
143	function showError(el, message, useHtml) {
144		if (!el) {
145			return;
146		}
147		if (useHtml) {
148			el.innerHTML = message || '';
149		} else {
150			el.textContent = message || '';
151		}
152		el.hidden = !message;
153	}
154
155	function formatNotRegisteredError(data) {
156		var msg = (data && data.message) ? data.message : '';
157		var registerUrl = (data && data.register_url) || (aioLoginOtp && aioLoginOtp.registerUrl) || '';
158		if (!registerUrl) {
159			return msg;
160		}
161		var label = (aioLoginOtp.i18n && aioLoginOtp.i18n.registerLink) ? aioLoginOtp.i18n.registerLink : 'Register';
162		return msg + ' <a href="' + registerUrl.replace(/"/g, '&quot;') + '">' + label + '</a>';
163	}
164
165	function showStep(name) {
166		Object.keys(steps).forEach(function (key) {
167			if (!steps[key]) {
168				return;
169			}
170			steps[key].hidden = key !== name;
171		});
172		if (els.backToLoginWrap) {
173			els.backToLoginWrap.hidden = name === 'success';
174		}
175		if (name === 'verify' && els.otpCode) {
176			setTimeout(function () {
177				els.otpCode.focus();
178			}, 0);
179		}
180	}
181
182	function showOtpView() {
183		if (!loginEl) {
184			loginEl = document.getElementById('login');
185		}
186		if (loginEl) {
187			loginEl.classList.add('aio-login-otp-view--active');
188		}
189		panel.hidden = false;
190		panel.setAttribute('aria-hidden', 'false');
191	}
192
193	function showLoginView() {
194		if (loginEl) {
195			loginEl.classList.remove('aio-login-otp-view--active');
196		}
197		panel.hidden = true;
198		panel.setAttribute('aria-hidden', 'true');
199		state.channel = '';
200		state.token = '';
201		state.verifyBlocked = false;
202		state.redirectUrl = '';
203		state.redirectStarted = false;
204		if (state.redirectTimer) {
205			clearTimeout(state.redirectTimer);
206			state.redirectTimer = null;
207		}
208		clearResendTimer();
209		showError(els.contactError, '');
210		showError(els.verifyError, '');
211	}
212
213	function clearResendTimer() {
214		if (state.resendTimer) {
215			clearInterval(state.resendTimer);
216			state.resendTimer = null;
217		}
218	}
219
220	function populateCountries() {
221		if (!els.country || !aioLoginOtp.countryCodes) {
222			return;
223		}
224		els.country.innerHTML = '';
225		var defaultIso = aioLoginOtp.defaultCountryIso || '';
226		var defaultCode = aioLoginOtp.defaultCountry || '';
227		aioLoginOtp.countryCodes.forEach(function (c) {
228			var opt = document.createElement('option');
229			opt.value = c.code;
230			opt.textContent = c.label;
231			opt.dataset.iso = c.iso;
232			if (defaultIso && c.iso === defaultIso) {
233				opt.selected = true;
234			} else if (!defaultIso && c.code === defaultCode) {
235				opt.selected = true;
236			}
237			els.country.appendChild(opt);
238		});
239		syncDialDisplay();
240	}
241
242	function configureOtpField(len) {
243		if (!els.otpCode) {
244			return;
245		}
246		state.length = len || 4;
247		els.otpCode.maxLength = state.length;
248		els.otpCode.value = '';
249		els.otpCode.setAttribute(
250			'aria-label',
251			(aioLoginOtp.i18n && aioLoginOtp.i18n.otpLabel) ? aioLoginOtp.i18n.otpLabel : 'Verification code'
252		);
253		updateVerifyEnabled();
254	}
255
256	function bindOtpCodeField() {
257		if (!els.otpCode || els.otpCode.dataset.bound === '1') {
258			return;
259		}
260		els.otpCode.dataset.bound = '1';
261		els.otpCode.addEventListener('input', function () {
262			els.otpCode.value = els.otpCode.value.replace(/\D/g, '').slice(0, state.length);
263			updateVerifyEnabled();
264		});
265		els.otpCode.addEventListener('keydown', function (e) {
266			if (e.key === 'Enter') {
267				e.preventDefault();
268				tryVerify();
269			}
270		});
271		els.otpCode.addEventListener('paste', function (e) {
272			e.preventDefault();
273			var pasted = (e.clipboardData || window.clipboardData).getData('text').replace(/\D/g, '').slice(0, state.length);
274			els.otpCode.value = pasted;
275			updateVerifyEnabled();
276		});
277	}
278
279	function getOtpValue() {
280		if (!els.otpCode) {
281			return '';
282		}
283		return els.otpCode.value.replace(/\D/g, '');
284	}
285
286	function clearOtpInputs() {
287		if (!els.otpCode) {
288			return;
289		}
290		els.otpCode.value = '';
291		els.otpCode.focus();
292		updateVerifyEnabled();
293	}
294
295	function digitsOnly(value) {
296		return String(value || '').replace(/\D/g, '');
297	}
298
299	function currentDialCode() {
300		if (!els.country) {
301			return '';
302		}
303		return String(els.country.value || '');
304	}
305
306	function currentCountryIso() {
307		if (!els.country || !els.country.selectedOptions || !els.country.selectedOptions[0]) {
308			return '';
309		}
310		return els.country.selectedOptions[0].dataset.iso || '';
311	}
312
313	function syncDialDisplay() {
314		if (els.phoneDial) {
315			els.phoneDial.textContent = currentDialCode();
316		}
317	}
318
319	function normalizeLocalPhoneDigits(raw) {
320		return digitsOnly(raw);
321	}
322
323	function parsePastedPhone(pasted) {
324		var raw = String(pasted || '').trim();
325		var ccDigits = digitsOnly(currentDialCode());
326		var numDigits = digitsOnly(raw);
327
328		if (raw.indexOf('+') === 0 && ccDigits !== '' && numDigits.indexOf(ccDigits) === 0) {
329			return numDigits.slice(ccDigits.length);
330		}
331
332		// Full international paste without "+" (e.g. 16465180948 for +1).
333		if (ccDigits !== '' && numDigits.length > ccDigits.length + 6 && numDigits.indexOf(ccDigits) === 0) {
334			return numDigits.slice(ccDigits.length);
335		}
336
337		return numDigits;
338	}
339
340	function getInvalidPhoneMessage() {
341		return (aioLoginOtp.i18n && aioLoginOtp.i18n.invalidPhone)
342			? aioLoginOtp.i18n.invalidPhone
343			: 'Please enter a valid phone number.';
344	}
345
346	function validatePhone(showFieldError) {
347		if (!els.phone) {
348			return { valid: false, digits: '', e164: '' };
349		}
350
351		var ccDigits = digitsOnly(currentDialCode());
352		var numDigits = normalizeLocalPhoneDigits(els.phone.value);
353
354		if (numDigits === '') {
355			if (showFieldError) {
356				showError(els.contactError, getInvalidPhoneMessage());
357			}
358			return { valid: false, digits: '', e164: '' };
359		}
360
361		var e164 = '+' + ccDigits + numDigits;
362		if (e164.length < 8 || e164.length > 16) {
363			if (showFieldError) {
364				showError(els.contactError, getInvalidPhoneMessage());
365			}
366			return { valid: false, digits: numDigits, e164: e164 };
367		}
368
369		return { valid: true, digits: numDigits, e164: e164 };
370	}
371
372	function sanitizePhoneInput() {
373		if (!els.phone) {
374			return;
375		}
376		var normalized = normalizeLocalPhoneDigits(els.phone.value);
377		if (els.phone.value !== normalized) {
378			els.phone.value = normalized;
379		}
380	}
381
382	function updateSendEnabled() {
383		if (!els.sendBtn) {
384			return;
385		}
386		var ok = false;
387		if (state.channel === 'email') {
388			ok = els.email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(els.email.value.trim());
389		} else if (state.channel === 'sms') {
390			ok = validatePhone(false).valid;
391		}
392		if (ok && aioLoginOtp.captchaRequired && window.aioLoginPasswordlessCaptcha) {
393			ok = window.aioLoginPasswordlessCaptcha.satisfied(els.captcha, aioLoginOtp.captcha);
394		}
395		els.sendBtn.disabled = !ok;
396	}
397
398	function updateVerifyEnabled() {
399		if (!els.verifyBtn) {
400			return;
401		}
402		if (state.verifyBlocked) {
403			els.verifyBtn.disabled = true;
404			return;
405		}
406		els.verifyBtn.disabled = getOtpValue().length !== state.length;
407	}
408
409	function renderCaptcha() {
410		if (!window.aioLoginPasswordlessCaptcha) {
411			return;
412		}
413		window.aioLoginPasswordlessCaptcha.render(els.captcha, aioLoginOtp.captcha, updateSendEnabled);
414	}
415
416	function appendCaptchaToFormData(fd) {
417		if (window.aioLoginPasswordlessCaptcha) {
418			return window.aioLoginPasswordlessCaptcha.appendToFormDataAsync(els.captcha, aioLoginOtp.captcha, fd);
419		}
420		if (!aioLoginOtp.captcha || !aioLoginOtp.captcha.provider) {
421			return Promise.resolve();
422		}
423		var p = aioLoginOtp.captcha.provider;
424		if (p === 'recaptcha' && aioLoginOtp.captcha.version === 'v3' && window.aioLoginPasswordlessCaptcha) {
425			return window.aioLoginPasswordlessCaptcha.getRecaptchaV3Token(aioLoginOtp.captcha, 'login').then(function (token) {
426				fd.append('g-recaptcha-response', token);
427			});
428		}
429		if (p === 'recaptcha') {
430			var resp = '';
431			if (window.grecaptcha) {
432				resp = window.grecaptcha.getResponse();
433			}
434			fd.append('g-recaptcha-response', resp);
435		} else if (p === 'hcaptcha' && window.hcaptcha) {
436			fd.append('h-captcha-response', window.hcaptcha.getResponse());
437		} else if (p === 'turnstile') {
438			var el = els.captcha.querySelector('[name="cf-turnstile-response"]');
439			if (el) {
440				fd.append('cf-turnstile-response', el.value);
441			}
442		}
443		return Promise.resolve();
444	}
445
446	function ajax(action, data) {
447		var fd = new FormData();
448		fd.append('action', action);
449		fd.append('nonce', aioLoginOtp.nonce);
450		Object.keys(data).forEach(function (k) {
451			fd.append(k, data[k]);
452		});
453		return appendCaptchaToFormData(fd).then(function () {
454			return fetch(aioLoginOtp.ajaxUrl, { method: 'POST', body: fd, credentials: 'same-origin' }).then(function (r) {
455				return r.json();
456			});
457		});
458	}
459
460	function startResendCountdown(seconds) {
461		clearResendTimer();
462		state.resendIn = seconds;
463		if (els.resendBtn) {
464			els.resendBtn.hidden = true;
465			els.resendBtn.disabled = true;
466		}
467		if (els.resendTimer) {
468			els.resendTimer.hidden = false;
469			els.resendTimer.textContent = aioLoginOtp.i18n.resendIn.replace('%d', String(seconds));
470		}
471		state.resendTimer = setInterval(function () {
472			state.resendIn -= 1;
473			if (state.resendIn <= 0) {
474				clearResendTimer();
475				if (els.resendTimer) {
476					els.resendTimer.hidden = true;
477				}
478				if (els.resendBtn) {
479					els.resendBtn.hidden = false;
480					els.resendBtn.disabled = false;
481				}
482				return;
483			}
484			if (els.resendTimer) {
485				els.resendTimer.textContent = aioLoginOtp.i18n.resendIn.replace('%d', String(state.resendIn));
486			}
487		}, 1000);
488	}
489
490	function openChannel(channel) {
491		if (channel === 'sms' && (!aioLoginOtp.countryCodes || !aioLoginOtp.countryCodes.length)) {
492			showError(
493				els.contactError,
494				(aioLoginOtp.i18n && aioLoginOtp.i18n.noSmsCountries)
495					? aioLoginOtp.i18n.noSmsCountries
496					: 'SMS login is not available.'
497			);
498			return;
499		}
500		state.channel = channel;
501		state.token = '';
502		state.verifyBlocked = false;
503		showError(els.contactError, '');
504		showError(els.verifyError, '');
505		if (els.emailWrap) {
506			els.emailWrap.hidden = channel !== 'email';
507		}
508		if (els.smsWrap) {
509			els.smsWrap.hidden = channel !== 'sms';
510		}
511		if (els.title) {
512			els.title.textContent = channel === 'email' ? aioLoginOtp.i18n.continueEmail : aioLoginOtp.i18n.continueSms;
513		}
514		showStep('contact');
515		showOtpView();
516		renderCaptcha();
517		if (channel === 'sms') {
518			syncDialDisplay();
519		}
520		updateSendEnabled();
521		setTimeout(function () {
522			renderCaptcha();
523			updateSendEnabled();
524			if (channel === 'email' && els.email) {
525				els.email.focus();
526			}
527			if (channel === 'sms' && els.phone) {
528				els.phone.focus();
529			}
530		}, 80);
531	}
532
533	function showVerifyBlocked(message, length) {
534		state.verifyBlocked = true;
535		showError(els.contactError, '');
536		configureOtpField(length || 4);
537		showStep('verify');
538		showError(els.verifyError, message);
539		if (els.verifyBtn) {
540			els.verifyBtn.disabled = true;
541		}
542		if (els.resendBtn) {
543			els.resendBtn.hidden = true;
544			els.resendBtn.disabled = true;
545		}
546		if (els.resendTimer) {
547			els.resendTimer.hidden = true;
548		}
549	}
550
551	function doSend() {
552		var payload = { channel: state.channel };
553		if (state.channel === 'email') {
554			payload.email = els.email.value.trim();
555		} else {
556			var phoneData = validatePhone(true);
557			if (!phoneData.valid) {
558				updateSendEnabled();
559				return;
560			}
561			payload.country_code = els.country.value;
562			payload.phone = phoneData.digits;
563			payload.country_iso = currentCountryIso();
564		}
565		els.sendBtn.disabled = true;
566		ajax('aio_login_otp_send', payload).then(function (res) {
567			els.sendBtn.disabled = false;
568			if (!res.success) {
569				if (res.data && res.data.code === 'verify_blocked') {
570					showVerifyBlocked(res.data.message, res.data.length);
571					return;
572				}
573				if (res.data && res.data.code === 'not_registered') {
574					showError(els.contactError, formatNotRegisteredError(res.data), true);
575				} else {
576					showError(els.contactError, (res.data && res.data.message) || 'Error');
577				}
578				return;
579			}
580			state.token = res.data.token;
581			state.length = res.data.length || 4;
582			state.verifyBlocked = false;
583			configureOtpField(state.length);
584			showStep('verify');
585			startResendCountdown(res.data.resend_in || 60);
586			clearOtpInputs();
587		}).catch(function () {
588			els.sendBtn.disabled = false;
589			showError(els.contactError, 'Network error');
590		});
591	}
592
593	function goToRedirect() {
594		if (!state.redirectUrl || state.redirectStarted) {
595			return;
596		}
597		state.redirectStarted = true;
598		if (state.redirectTimer) {
599			clearTimeout(state.redirectTimer);
600			state.redirectTimer = null;
601		}
602		window.location.href = state.redirectUrl;
603	}
604
605	function enableResendNow() {
606		clearResendTimer();
607		if (els.resendBtn) {
608			els.resendBtn.hidden = false;
609			els.resendBtn.disabled = false;
610		}
611		if (els.resendTimer) {
612			els.resendTimer.hidden = true;
613		}
614	}
615
616	function isVerifyBlockedResponse(data) {
617		if (!data || !data.code) {
618			return false;
619		}
620		return data.code === 'verify_blocked' || data.code === 'ip_blocked' || data.code === 'max_attempts';
621	}
622
623	function tryVerify() {
624		if (els.verifyBtn.disabled || state.verifyBlocked) {
625			return;
626		}
627		els.verifyBtn.disabled = true;
628		ajax('aio_login_otp_verify', { token: state.token, otp: getOtpValue() }).then(function (res) {
629			if (!res.success) {
630				if (isVerifyBlockedResponse(res.data)) {
631					showVerifyBlocked((res.data && res.data.message) || 'Error', state.length);
632					return;
633				}
634				if (res.data && res.data.code === 'otp_expired') {
635					enableResendNow();
636				}
637				els.verifyBtn.disabled = false;
638				updateVerifyEnabled();
639				showError(els.verifyError, (res.data && res.data.message) || 'Error');
640				clearOtpInputs();
641				return;
642			}
643			state.redirectUrl = res.data.redirect || '';
644			if (res.data.requires_2fa && aioLoginOtp.i18n) {
645				var successTitle = steps.success && steps.success.querySelector('.aio-login-otp-panel__title');
646				var successMessage = steps.success && steps.success.querySelector('.aio-login-otp-success-message');
647				if (successTitle && aioLoginOtp.i18n.successTitle2fa) {
648					successTitle.textContent = aioLoginOtp.i18n.successTitle2fa;
649				}
650				if (successMessage && aioLoginOtp.i18n.successMessage2fa) {
651					successMessage.textContent = aioLoginOtp.i18n.successMessage2fa;
652				}
653			}
654			showStep('success');
655			if (state.redirectTimer) {
656				clearTimeout(state.redirectTimer);
657			}
658			state.redirectStarted = false;
659			state.redirectTimer = setTimeout(function () {
660				goToRedirect();
661			}, res.data.requires_2fa ? 800 : 1500);
662		}).catch(function () {
663			els.verifyBtn.disabled = false;
664			showError(els.verifyError, 'Network error');
665		});
666	}
667
668	function doResend() {
669		els.resendBtn.disabled = true;
670		ajax('aio_login_otp_resend', { token: state.token }).then(function (res) {
671			if (!res.success) {
672				if (res.data && res.data.code === 'verify_blocked') {
673					showVerifyBlocked(res.data.message, res.data.length);
674					return;
675				}
676				if (res.data && res.data.code === 'invalid_session') {
677					showError(els.verifyError, (res.data && res.data.message) || 'Error');
678					return;
679				}
680				if (res.data && res.data.resend_in) {
681					startResendCountdown(res.data.resend_in);
682				} else {
683					els.resendBtn.disabled = false;
684				}
685				showError(els.verifyError, (res.data && res.data.message) || 'Error');
686				return;
687			}
688			state.verifyBlocked = false;
689			startResendCountdown(res.data.resend_in || 60);
690			clearOtpInputs();
691			showError(els.verifyError, '');
692			updateVerifyEnabled();
693		}).catch(function () {
694			els.resendBtn.disabled = false;
695			showError(els.verifyError, 'Network error');
696		});
697	}
698
699	document.querySelectorAll('.aio-login-otp-launcher').forEach(function (btn) {
700		btn.addEventListener('click', function () {
701			openChannel(btn.getAttribute('data-channel'));
702		});
703	});
704
705	panel.querySelectorAll('[data-otp-back-to-login]').forEach(function (btn) {
706		btn.addEventListener('click', showLoginView);
707	});
708
709	panel.querySelectorAll('[data-otp-back]').forEach(function (btn) {
710		btn.addEventListener('click', function () {
711			if (!steps.verify.hidden) {
712				showStep('contact');
713				showError(els.verifyError, '');
714				return;
715			}
716			showLoginView();
717		});
718	});
719
720	if (els.sendBtn) {
721		els.sendBtn.addEventListener('click', doSend);
722	}
723	if (els.verifyBtn) {
724		els.verifyBtn.addEventListener('click', tryVerify);
725	}
726	if (els.resendBtn) {
727		els.resendBtn.addEventListener('click', doResend);
728	}
729	if (els.continueBtn) {
730		els.continueBtn.addEventListener('click', goToRedirect);
731	}
732	if (els.email) {
733		els.email.addEventListener('input', updateSendEnabled);
734	}
735	if (els.phone) {
736		els.phone.addEventListener('input', function () {
737			sanitizePhoneInput();
738			showError(els.contactError, '');
739			updateSendEnabled();
740		});
741		els.phone.addEventListener('paste', function (e) {
742			e.preventDefault();
743			var pasted = (e.clipboardData || window.clipboardData).getData('text');
744			els.phone.value = parsePastedPhone(pasted);
745			showError(els.contactError, '');
746			updateSendEnabled();
747		});
748		els.phone.addEventListener('keydown', function (e) {
749			if (e.key === 'Enter' && els.sendBtn && !els.sendBtn.disabled) {
750				e.preventDefault();
751				doSend();
752			}
753		});
754	}
755	if (els.country) {
756		els.country.addEventListener('change', function () {
757			syncDialDisplay();
758			sanitizePhoneInput();
759			showError(els.contactError, '');
760			updateSendEnabled();
761			if (els.phone) {
762				els.phone.focus();
763			}
764		});
765	}
766
767	populateCountries();
768	bindOtpCodeField();
769})();

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.