PageSourceSearch

https://www.myagileprivacy.com/wp-content/plugins/agile-plugin/js/functions.js

js myagileprivacy.com collected 2026-09-24 08:47:00 UTC 42,176 bytes, 1,240 lines download raw bytes

1// functions.js — Front-end utilities for the Agile Privacy plugin.
2//
3// Sections:
4//   1. Countdown timers          — agile_initCountdown
5//   2. Mautic form integration   — internalMauticFormSend
6//   3. Fluent Forms tracking     — trackFluentFormEvents
7//   4. GA / Meta / Matomo events — trackGAEvents
8//   5. CC365 modal               — MapCC365Modal (IIFE)
9//   6. CC365 public API          — window.MapCC365 (IIFE)
10//   7. Pricing checkbox          — cc365PricingCheckbox
11//   8. Product view tracking     — mapTrackProducts
12//   9. Pills blocks              — initPillsBlock
13//  10. DOMContentLoaded bootstrap
14
15// Scans all .countdown-container elements and starts a 1-second interval for each,
16// reading expiry from data-scadenza and language from data-lang (IT default, EN supported).
17// Clears the interval and zeroes all values when the expiry date is reached.
18function agile_initCountdown() {
19	console.info("- INIT COUNTDOWN -");
20	var countdown_container_array = document.querySelectorAll('.countdown-container');
21
22	countdown_container_array.forEach(this_countdown => {
23
24		let expire_date = this_countdown.getAttribute('data-scadenza');
25		let current_lang = this_countdown.getAttribute('data-lang');
26
27		let expire_date_totime = new Date(expire_date).getTime();
28
29		let days_container = this_countdown.querySelector('.col-days');
30		let hours_container = this_countdown.querySelector('.col-hours');
31		let mins_container = this_countdown.querySelector('.col-mins');
32		let secs_container = this_countdown.querySelector('.col-secs');
33
34		let x = setInterval(function () {
35			let now = new Date().getTime();
36			let distance = expire_date_totime - now;
37
38			if (distance > 0) {
39				var days = Math.floor(distance / (1000 * 60 * 60 * 24));
40				var hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
41				var minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
42				var seconds = Math.floor((distance % (1000 * 60)) / 1000);
43			} else {
44				var days = 0;
45				var hours = 0;
46				var minutes = 0;
47				var seconds = 0;
48
49				clearInterval(x);
50
51			}
52
53			var days_text = 'giorni';
54			var hours_text = 'ore';
55			var mins_text = 'minuti';
56			var secs_text = 'secondi';
57
58			if (current_lang == 'en') {
59				var days_text = 'days';
60				var hours_text = 'hours';
61				var mins_text = 'minutes';
62				var secs_text = 'seconds';
63			}
64
65			days_container.innerHTML = '<span>' + days + "</span>" + days_text;
66			hours_container.innerHTML = '<span>' + hours + "</span>" + hours_text;
67			mins_container.innerHTML = '<span>' + minutes + "</span>" + mins_text;
68			secs_container.innerHTML = '<span>' + seconds + "</span>" + secs_text;
69
70
71		}, 1000);
72
73
74	});
75}
76
77// Wrapper around parseInt that always uses base 10.
78function _parseInt(number) {
79	return parseInt(number, 10);
80}
81
82// Handles submission of Mautic lead-magnet forms (4 form names supported:
83// agile_lead_magnet_7_2_errori, _en, agile_lead_playground, _en).
84// Builds the Mautic payload, POSTs it via AJAX, fires Meta/GA4/Matomo lead events on success,
85// and redirects the user to the thank-you page after 5 seconds.
86function internalMauticFormSend(caller) {
87
88	console.log('internalMauticFormSend');
89
90	var customer_lang = 'en';
91
92	var $html = jQuery('html');
93	var html_lang = $html.attr('lang');
94
95	if (html_lang == 'it-IT') {
96		customer_lang = 'it';
97	}
98
99	var afterfinish_func = jQuery(caller).attr('data-after');
100	var afterfinish_func2 = jQuery(caller).attr('data-after2');
101	var afterfinish_func3 = jQuery(caller).attr('data-after3');
102
103	var form = caller.parents('form');
104	var form_name = form.attr('name');
105	var next_action = '';
106	var url_to_send_to = '';
107	var formId = '';
108	var returnString = '';
109	var formName = '';
110	var redirect_to = null;
111
112	var event_to_track = null;
113
114	if (form_name == 'agile_lead_magnet_7_2_errori') {
115		next_action = 'redirect';
116		redirect_to = "https://www.myagileprivacy.com/download-guida-gratuita/";
117
118		event_to_track = 'lead';
119
120		// var $frm_first_name = jQuery( ':input[name="frm_first_name"]', form );
121		// var frm_first_name = $frm_first_name.val();
122
123		var $frm_email = jQuery(':input[name="frm_email"]', form);
124		var frm_email = $frm_email.val();
125
126		var $frm_telefono = jQuery(':input[name="frm_telefono"]', form);
127		var frm_telefono = $frm_telefono.val();
128
129		var $frm_ruolo = jQuery(':input[name="frm_ruolo_aziendale"]', form);
130		var frm_ruolo = $frm_ruolo.val();
131
132		var $frm_consenso_marketing = jQuery(':input[name="frm_consenso_marketing"]', form);
133		var frm_consenso_marketing = ($frm_consenso_marketing.prop('checked') ? 1 : 0);
134
135		url_to_send_to = 'https://www.robomarketing.it/automation/myagileprivacy.com/form/submit?formId=103';
136		formId = '103';
137		returnString = '';
138		formName = 'leadmagnet72errori';
139
140		var object_to_send = new Object();
141
142		//object_to_send['mauticform[first_name]'] = frm_first_name;
143		object_to_send['mauticform[customer_lang]'] = customer_lang;
144		object_to_send['mauticform[email]'] = frm_email;
145		object_to_send['mauticform[telefono1]'] = frm_telefono;
146		object_to_send['mauticform[ruolo]'] = frm_ruolo;
147
148
149		object_to_send['mauticform[privacy][]'] = 1;
150		object_to_send['mauticform[formId]'] = formId;
151		object_to_send['mauticform[return]'] = returnString;
152		object_to_send['mauticform[formName]'] = formName;
153		object_to_send['mauticform[consenso_marketing]'] = frm_consenso_marketing;
154		object_to_send['mauticform[messenger]'] = 1;
155
156		var data_to_send = jQuery.param(object_to_send);
157	}
158
159	if (form_name == 'agile_lead_magnet_7_2_errori_en') {
160		next_action = 'redirect';
161		redirect_to = "https://www.myagileprivacy.com/en/download-free-guide/";
162
163		event_to_track = 'lead';
164
165		// var $frm_first_name = jQuery( ':input[name="frm_first_name"]', form );
166		// var frm_first_name = $frm_first_name.val();
167
168		var $frm_email = jQuery(':input[name="frm_email"]', form);
169		var frm_email = $frm_email.val();
170
171		var $frm_telefono = jQuery(':input[name="frm_telefono"]', form);
172		var frm_telefono = $frm_telefono.val();
173
174		var $frm_ruolo = jQuery(':input[name="frm_ruolo_aziendale"]', form);
175		var frm_ruolo = $frm_ruolo.val();
176
177		var $frm_consenso_marketing = jQuery(':input[name="frm_consenso_marketing"]', form);
178		var frm_consenso_marketing = ($frm_consenso_marketing.prop('checked') ? 1 : 0);
179
180		url_to_send_to = 'https://www.robomarketing.it/automation/myagileprivacy.com/form/submit?formId=107';
181		formId = '107';
182		returnString = '';
183		formName = 'leadmagnet72errorien';
184
185		var object_to_send = new Object();
186
187		//object_to_send['mauticform[first_name]'] = frm_first_name;
188		object_to_send['mauticform[customer_lang]'] = customer_lang;
189		object_to_send['mauticform[email]'] = frm_email;
190		object_to_send['mauticform[telefono1]'] = frm_telefono;
191		object_to_send['mauticform[ruolo]'] = frm_ruolo;
192
193
194		object_to_send['mauticform[privacy][]'] = 1;
195		object_to_send['mauticform[formId]'] = formId;
196		object_to_send['mauticform[return]'] = returnString;
197		object_to_send['mauticform[formName]'] = formName;
198		object_to_send['mauticform[consenso_marketing]'] = frm_consenso_marketing;
199		object_to_send['mauticform[messenger]'] = 1;
200
201		var data_to_send = jQuery.param(object_to_send);
202	}
203
204	if (form_name == 'agile_lead_playground') {
205		next_action = 'redirect';
206		redirect_to = "https://playground.myagileprivacy.com/_______start.html";
207
208		event_to_track = 'lead';
209
210		// var $frm_first_name = jQuery( ':input[name="frm_first_name"]', form );
211		// var frm_first_name = $frm_first_name.val();
212
213		var $frm_email = jQuery(':input[name="frm_email"]', form);
214		var frm_email = $frm_email.val();
215
216		var $frm_tipologia_utente = jQuery(':input[name="frm_tipologia_utente"]', form);
217		var frm_tipologia_utente = $frm_tipologia_utente.val();
218
219		var $frm_consenso_marketing = jQuery(':input[name="frm_consenso_marketing"]', form);
220		var frm_consenso_marketing = ($frm_consenso_marketing.prop('checked') ? 1 : 0);
221
222		url_to_send_to = 'https://www.robomarketing.it/automation/myagileprivacy.com/form/submit?formId=104';
223		formId = '104';
224		returnString = '';
225		formName = 'leadplayground';
226
227		var object_to_send = new Object();
228
229		//object_to_send['mauticform[first_name]'] = frm_first_name;
230		object_to_send['mauticform[customer_lang]'] = customer_lang;
231		object_to_send['mauticform[email]'] = frm_email;
232		object_to_send['mauticform[tipologia_utente]'] = frm_tipologia_utente;
233
234
235		object_to_send['mauticform[privacy][]'] = 1;
236		object_to_send['mauticform[formId]'] = formId;
237		object_to_send['mauticform[return]'] = returnString;
238		object_to_send['mauticform[formName]'] = formName;
239		object_to_send['mauticform[consenso_marketing]'] = frm_consenso_marketing;
240		object_to_send['mauticform[messenger]'] = 1;
241
242		var data_to_send = jQuery.param(object_to_send);
243	}
244
245
246	if (form_name == 'agile_lead_playground_en') {
247		next_action = 'redirect';
248		redirect_to = "https://playground.myagileprivacy.com/_______start_eng_new.html";
249
250		event_to_track = 'lead';
251
252		// var $frm_first_name = jQuery( ':input[name="frm_first_name"]', form );
253		// var frm_first_name = $frm_first_name.val();
254
255		var $frm_email = jQuery(':input[name="frm_email"]', form);
256		var frm_email = $frm_email.val();
257
258		var $frm_tipologia_utente = jQuery(':input[name="frm_tipologia_utente"]', form);
259		var frm_tipologia_utente = $frm_tipologia_utente.val();
260
261		var $frm_consenso_marketing = jQuery(':input[name="frm_consenso_marketing"]', form);
262		var frm_consenso_marketing = ($frm_consenso_marketing.prop('checked') ? 1 : 0);
263
264		url_to_send_to = 'https://www.robomarketing.it/automation/myagileprivacy.com/form/submit?formId=105';
265		formId = '105';
266		returnString = '';
267		formName = 'leadplaygrounden';
268
269		var object_to_send = new Object();
270
271		//object_to_send['mauticform[first_name]'] = frm_first_name;
272		object_to_send['mauticform[customer_lang]'] = customer_lang;
273		object_to_send['mauticform[email]'] = frm_email;
274		object_to_send['mauticform[tipologia_utente]'] = frm_tipologia_utente;
275
276
277		object_to_send['mauticform[privacy][]'] = 1;
278		object_to_send['mauticform[formId]'] = formId;
279		object_to_send['mauticform[return]'] = returnString;
280		object_to_send['mauticform[formName]'] = formName;
281		object_to_send['mauticform[consenso_marketing]'] = frm_consenso_marketing;
282		object_to_send['mauticform[messenger]'] = 1;
283
284		var data_to_send = jQuery.param(object_to_send);
285	}
286
287
288	// console.log( data_to_send );
289
290	/**/
291	jQuery.ajax({
292		url: url_to_send_to,
293		beforeSend: function () {
294			jQuery('.default_loading', form).css('display', 'block').slideDown('slow');
295
296			if (!form.hasClass('nohide')) jQuery('.all_fields', form).slideUp('slow');
297		},
298		dataType: 'html',
299		data: data_to_send,
300		type: 'POST',
301		error: function () {
302			jQuery('.default_loading', form).slideUp('slow');
303			jQuery('.thanks', form).slideDown('slow');
304
305			if (afterfinish_func) {
306				window[afterfinish_func](form, response);
307			}
308			if (afterfinish_func2) {
309				window[afterfinish_func2](form, response);
310			}
311			if (afterfinish_func3) {
312				window[afterfinish_func3](form, response);
313			}
314
315			console.log('formSend --> error during sending :');
316			return false;
317		},
318		success: function (response) {
319			jQuery('.default_loading', form).slideUp('slow');
320			jQuery('.thanks', form).slideDown('slow');
321
322
323			console.log(response);
324
325			// GA and Meta event tracking
326			if (event_to_track) {
327				switch (event_to_track) {
328					case 'lead':
329
330						try {
331							fbq('track', 'Lead');
332						} catch (e) {
333							console.error(e);
334						}
335
336						try {
337							gtag('event', 'sign_up');
338						} catch (e) {
339							console.error(e);
340						}
341						try {
342							_paq.push(['trackEvent', 'Lead', 'sign_up']);
343						} catch (e) {
344							console.error(e);
345						}
346
347						break;
348					default:
349
350						try {
351							fbq('track', 'Lead');
352						} catch (e) {
353							console.error(e);
354						}
355
356						try {
357							gtag('event', 'sign_up');
358						} catch (e) {
359							console.error(e);
360						}
361						try {
362							_paq.push(['trackEvent', 'Lead', 'sign_up']);
363						} catch (e) {
364							console.error(e);
365						}
366						break;
367				}
368			}
369
370
371			if (afterfinish_func) {
372				window[afterfinish_func](form, response);
373			}
374			if (afterfinish_func2) {
375				window[afterfinish_func2](form, response);
376			}
377			if (afterfinish_func3) {
378				window[afterfinish_func3](form, response);
379			}
380
381			return false;
382		}
383	});
384
385	/**/
386
387	if (next_action == 'redirect') {
388		setTimeout(function () {
389			window.location.href = redirect_to;
390		}, 5000);
391	} else if (next_action != '') {
392		var data = {
393			'action': next_action,
394			'email': frm_email,
395			'form_name': form_name
396		};
397
398		jQuery.post('/wp-admin/admin-ajax.php', data, function (response) { });
399	}
400
401	return false;
402}
403
404
405// Hooks the fluentform_submission_success event on every .frm-fluent-form.
406// On success, sends a Lead event to Meta (with SHA-256 email if available), a generate_lead
407// event to GA4 (with user_data), a Matomo trackEvent, and a push to window.gdmEvents.
408function trackFluentFormEvents() {
409
410	var fluentForms = jQuery('form.frm-fluent-form');
411	fluentForms.each(function () {
412		var $form = jQuery(this);
413		var formId = $form.attr('data-form_id');
414
415
416		$form.on('fluentform_submission_success', function () {
417			console.log('form sent with success');
418
419			// Enhanced conversion
420			var userdata = {};
421			userdata.email = jQuery(':input[name="email"]', $form).val();
422
423			try {
424
425				if (typeof window.sha256_calc !== 'undefined') {
426					var fb_data = {
427						email: userdata.email,
428					};
429
430					fbq('track', 'Lead', fb_data);
431				}
432				else {
433
434					fbq('track', 'Lead');
435				}
436
437
438			} catch (e) {
439				console.error(e);
440			}
441
442			try {
443
444				if (typeof window.sha256_calc !== 'undefined') {
445					var user_data_details = {
446						"sha256_email_address": sha256_calc(userdata?.email),
447					};
448
449					console.table(user_data_details);
450					gtag("set", "user_data", user_data_details);
451				}
452
453				gtag('event', 'generate_lead');
454
455			} catch (e) {
456				console.error(e);
457			}
458
459			try {
460				_paq.push(['trackEvent', 'Lead', 'form_contatti']);
461			} catch (e) {
462				console.error(e);
463			}
464
465			try {
466				window.gdmEvents.push("contact-sales");
467			} catch (e) {
468				console.error(e);
469			}
470
471		});
472
473	});
474}
475
476
477
478// Fires a custom 30_sec_pageview event to GA4 and Matomo 30 seconds after the page loads.
479function trackGAEvents() {
480	console.log('trackGAEvents');
481
482	setTimeout(function () {
483
484		console.log('sending 30_sec_pageview event');
485
486		try {
487			gtag('event', '30_sec_pageview');
488
489			_paq.push(['trackEvent', '30_sec_pageview', '30_sec_pageview']);
490
491		} catch (e) {
492			console.error(e);
493		}
494
495	}, 30000);
496}
497
498/**
499 * ComplianceCheck365 — removal confirmation modal + public API.
500 *
501 * WHEN IT TRIGGERS
502 *   The modal appears automatically when the user DESELECTS a
503 *   `.map-cc365-checkbox` checkbox inside a `.map-plan-card`,
504 *   OR when a non-CC365 radio is selected in a group managed
505 *   by bindProductSelector().
506 *   Selecting (checking/switching to CC365) does NOT open any modal.
507 *
508 * FLOW
509 *   1. user unchecks / switches radio → event `cc365:remove-attempt`
510 *   2a. clicks "Keep"    → element restored, event `cc365:remove-cancelled`
511 *   2b. clicks "Proceed" → element left deselected, event `cc365:remove-confirmed`
512 *
513 * CUSTOM EVENTS (bubbling, dispatched on the CC365 element)
514 *   - 'cc365:remove-attempt'
515 *   - 'cc365:remove-cancelled'
516 *   - 'cc365:remove-confirmed'
517 *   detail = { el, planId, planName }
518 *
519 * PUBLIC API (window.MapCC365)
520 *   window.MapCC365.confirmRemoval(el)
521 *   window.MapCC365.bindProductSelector(wrapper [, opts])
522 *   window.MapCC365.modal.open({ onConfirm, onCancel })
523 *   window.MapCC365.modal.close([action])
524 *   window.MapCC365.debugGroups()
525 *
526 * DEBUG
527 *   Set CC365_DEBUG = true to enable console logs.
528 *
529 * I18N
530 *   IT/EN strings chosen from document.documentElement.lang. Defaults to IT.
531 */
532
533/* ─────────────────────────────────────────────────────────────────────────────
534   PART 1 — MapCC365Modal (original, unchanged)
535───────────────────────────────────────────────────────────────────────────── */
536// Manages the modal lifecycle: lazy DOM build, open/close, ESC key, focus restore, IT/EN i18n.
537var MapCC365Modal = (function () {
538	var overlay = null;
539	var current = null;
540	var lastFocus = null;
541
542	var i18n = {
543		it: {
544			title: 'Stai rimuovendo la verifica umana',
545			body: 'Senza ComplianceCheck365, il tuo sito potrebbe risultare non conforme al GDPR senza che tu lo sappia — e una sanzione può arrivare fino al 4% del fatturato annuo. I nostri esperti controllano il tuo sito ogni 6 mesi e ti avvisano prima che sia troppo tardi.',
546			question: 'Vuoi davvero procedere senza protezione?',
547			cancel: 'Mantieni ComplianceCheck365',
548			confirm: 'Sì, procedi senza protezione'
549		},
550		en: {
551			title: 'You are removing human verification',
552			body: 'Without ComplianceCheck365, your site could be non-compliant with the GDPR without you knowing — and a fine can reach up to 4% of annual turnover. Our experts check your site every 6 months and warn you before it is too late.',
553			question: 'Do you really want to proceed without protection?',
554			cancel: 'Keep ComplianceCheck365',
555			confirm: 'Yes, proceed without protection'
556		}
557	};
558
559	function getLang() {
560		var l = (document.documentElement.lang || 'it').toLowerCase();
561		return (l.indexOf('en') === 0) ? 'en' : 'it';
562	}
563
564	function build() {
565		var t = i18n[getLang()];
566		overlay = document.createElement('div');
567		overlay.className = 'map-cc365-modal-overlay';
568		overlay.setAttribute('hidden', '');
569		overlay.innerHTML =
570			'<div class="map-cc365-modal" role="dialog" aria-modal="true" aria-labelledby="map-cc365-modal-title">' +
571				'<button type="button" class="map-cc365-modal-close" data-action="cancel" aria-label="Close">' +
572					'<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true"><path fill="currentColor" d="M18.3 5.71L12 12l6.3 6.29-1.41 1.42L10.59 13.4 4.29 19.7 2.88 18.3 9.17 12 2.88 5.71 4.29 4.29l6.3 6.3 6.29-6.3z"/></svg>' +
573				'</button>' +
574				'<div class="map-cc365-modal-icon" aria-hidden="true">' +
575					'<svg viewBox="0 0 24 24" width="34" height="34" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' +
576						'<path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z"/>' +
577						'<line x1="12" y1="9" x2="12" y2="13"/>' +
578						'<line x1="12" y1="17" x2="12.01" y2="17"/>' +
579					'</svg>' +
580				'</div>' +
581				'<h3 id="map-cc365-modal-title" class="map-cc365-modal-title"></h3>' +
582				'<p class="map-cc365-modal-body"></p>' +
583				'<div class="map-cc365-modal-question-box">' +
584					'<p class="map-cc365-modal-question"></p>' +
585				'</div>' +
586				'<div class="map-cc365-modal-actions">' +
587					'<button type="button" class="map-cc365-modal-btn map-cc365-modal-btn--primary" data-action="cancel">' +
588						'<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z"/></svg>' +
589						'<span class="map-cc365-modal-btn-label"></span>' +
590					'</button>' +
591					'<button type="button" class="map-cc365-modal-btn map-cc365-modal-btn--secondary" data-action="confirm"></button>' +
592				'</div>' +
593			'</div>';
594
595		overlay.querySelector('.map-cc365-modal-title').textContent = t.title;
596		overlay.querySelector('.map-cc365-modal-body').textContent = t.body;
597		overlay.querySelector('.map-cc365-modal-question').textContent = t.question;
598		overlay.querySelector('.map-cc365-modal-btn-label').textContent = t.cancel;
599		overlay.querySelector('.map-cc365-modal-btn--secondary').textContent = t.confirm;
600
601		document.body.appendChild(overlay);
602
603		overlay.addEventListener('click', function (e) {
604			if (e.target === overlay) { close('cancel'); return; }
605			var btn = e.target.closest('[data-action]');
606			if (btn) close(btn.dataset.action);
607		});
608
609		document.addEventListener('keydown', function (e) {
610			if (e.key === 'Escape' && overlay && !overlay.hasAttribute('hidden')) {
611				close('cancel');
612			}
613		});
614	}
615
616	function open(opts) {
617		// Build DOM lazily on first open.
618		if (!overlay) build();
619		current = opts || {};
620		lastFocus = document.activeElement;
621		overlay.removeAttribute('hidden');
622		overlay.classList.add('is-open');
623		document.body.classList.add('map-cc365-modal-open');
624		var primaryBtn = overlay.querySelector('.map-cc365-modal-btn--primary');
625		if (primaryBtn) setTimeout(function () { primaryBtn.focus(); }, 30);
626	}
627
628	function close(action) {
629		if (!overlay) return;
630		overlay.classList.remove('is-open');
631		overlay.setAttribute('hidden', '');
632		document.body.classList.remove('map-cc365-modal-open');
633		var cb = current;
634		current = null;
635		// Restore focus to the previously active element.
636		if (lastFocus && typeof lastFocus.focus === 'function') {
637			try { lastFocus.focus(); } catch (e) { }
638		}
639		lastFocus = null;
640		if (!cb) return;
641		if (action === 'confirm' && typeof cb.onConfirm === 'function') cb.onConfirm();
642		else if (typeof cb.onCancel === 'function') cb.onCancel();
643	}
644
645	return { open: open, close: close };
646})();
647
648
649/* ─────────────────────────────────────────────────────────────────────────────
650   PART 2 — window.MapCC365 (public API for radios/checkboxes)
651   Activates only if the page contains .AbF-container.
652───────────────────────────────────────────────────────────────────────────── */
653(function (Modal) {
654
655	/* ── Debug ──────────────────────────────────────────────────────────────── */
656
657	var CC365_DEBUG = false; // true = print logs to console, false = silent
658
659	function log()  { if (CC365_DEBUG) { console.log.apply(console,  arguments); } }
660	function warn() { if (CC365_DEBUG) { console.warn.apply(console, arguments); } }
661
662	/* ── Guard + DOMContentLoaded ───────────────────────────────────────────── */
663
664	function init() {
665		if (!document.querySelector('.AbF-container')) {
666			log('[CC365] .AbF-container non trovato — API radio disattivata');
667			return;
668		}
669		log('[CC365] .AbF-container trovato — API radio attiva');
670		run();
671	}
672
673	if (document.readyState === 'loading') {
674		document.addEventListener('DOMContentLoaded', init);
675	} else {
676		init();
677	}
678
679	/* ── Main logic ──────────────────────────────────────────────────────────── */
680
681	function run() {
682
683		var WRAPPER_SELECTOR = '.abf-product-selector-wrapper';
684		var CC365_SELECTOR   = '.abf-product-option-upgrade input[type="radio"]';
685
686		var groups          = {};
687		var pendingWrappers = [];
688
689		/* ── helpers ──────────────────────────────────────────────────────── */
690
691		function dispatch(el, name, detail) {
692			log('[CC365] dispatch "' + name + '"', detail);
693			var ev;
694			try {
695				ev = new CustomEvent(name, { bubbles: true, cancelable: false, detail: detail });
696			} catch (e) {
697				ev = document.createEvent('CustomEvent');
698				ev.initCustomEvent(name, true, false, detail);
699			}
700			el.dispatchEvent(ev);
701		}
702
703		function fireChange(el) {
704			log('[CC365] fireChange su', el);
705			var ev;
706			try { ev = new Event('change', { bubbles: true }); }
707			catch (e) { ev = document.createEvent('Event'); ev.initEvent('change', true, false); }
708			el.dispatchEvent(ev);
709		}
710
711		function buildDetail(el) {
712			var container = el.closest('label')               ||
713			                el.closest('.abf-product-option') ||
714			                el.closest('.map-plan-card')      ||
715			                el.parentElement;
716			var heading = container ? container.querySelector('h5, h4, h3, h2') : null;
717			return {
718				el:       el,
719				planId:   el.value || null,
720				planName: (heading ? heading.textContent.trim() : null) ||
721				           el.getAttribute('aria-label') || el.name || ''
722			};
723		}
724
725		/* ── Delegated listener — capture phase ────────────────────────────── */
726
727		document.addEventListener('change', function (e) {
728			var radio = e.target;
729			if (!radio || radio.type !== 'radio' || !radio.checked) { return; }
730
731			var group = groups[radio.name];
732			if (!group) { return; }
733
734			log('[CC365] capture change name="' + radio.name + '" value="' + radio.value + '" | cc365Active:', group.cc365Active);
735
736			if (radio.value === group.cc365Value) {
737				log('[CC365] selezionato CC365 → cc365Active = true');
738				group.cc365Active = true;
739				return;
740			}
741
742			if (group.cc365Active) {
743				log('[CC365] CC365 era attivo → apro modale');
744				group.cc365Active = false;
745				var cc365Radio = document.querySelector(
746					'input[type="radio"][name="' + radio.name + '"][value="' + group.cc365Value + '"]'
747				);
748				if (cc365Radio) {
749					confirmRemoval(cc365Radio, group);
750				} else {
751					warn('[CC365] cc365Radio non trovato nel DOM');
752				}
753			}
754
755		}, true); // capture:true — immune to stopPropagation during bubbling
756
757		/* ── confirmRemoval ───────────────────────────────────────────────── */
758
759		// Opens the modal for a CC365 element; restores the radio on cancel or dispatches confirmed.
760		function confirmRemoval(cc365El, group) {
761			log('[CC365] confirmRemoval', cc365El);
762			var detail = buildDetail(cc365El);
763			dispatch(cc365El, 'cc365:remove-attempt', detail);
764
765			Modal.open({
766				onConfirm: function () {
767					log('[CC365] confermato — CC365 rimosso');
768					dispatch(cc365El, 'cc365:remove-confirmed', detail);
769				},
770				onCancel: function () {
771					log('[CC365] annullato — ripristino CC365');
772					var fresh = document.querySelector(
773						'input[type="radio"][name="' + cc365El.name + '"][value="' + cc365El.value + '"]'
774					);
775					if (fresh) {
776						fresh.checked     = true;
777						group.cc365Active = true;
778						fireChange(fresh);
779					}
780					dispatch(cc365El, 'cc365:remove-cancelled', detail);
781				}
782			});
783		}
784
785		/* ── bindProductSelector ──────────────────────────────────────────── */
786
787		// Registers a wrapper element as a CC365 radio group; queues it if radios are not yet in DOM.
788		function bindProductSelector(wrapper, opts) {
789			if (typeof wrapper === 'string') {
790				wrapper = document.querySelector(wrapper);
791			}
792			if (!wrapper) {
793				warn('[CC365] bindProductSelector — wrapper non trovato');
794				return;
795			}
796
797			var cc365Sel   = (opts && opts.cc365Selector) || CC365_SELECTOR;
798			var cc365Radio = wrapper.querySelector(cc365Sel);
799
800			if (!cc365Radio) {
801				if (pendingWrappers.indexOf(wrapper) === -1) {
802					pendingWrappers.push(wrapper);
803					log('[CC365] bindProductSelector — radio non ancora presenti, in coda');
804				}
805				return;
806			}
807
808			var groupName  = cc365Radio.name;
809			var cc365Value = cc365Radio.value;
810
811			if (groups[groupName]) {
812				groups[groupName].cc365Active = cc365Radio.checked;
813				log('[CC365] bindProductSelector — gruppo "' + groupName + '" aggiornato');
814				return;
815			}
816
817			groups[groupName] = { cc365Value: cc365Value, cc365Active: cc365Radio.checked };
818
819			var idx = pendingWrappers.indexOf(wrapper);
820			if (idx !== -1) { pendingWrappers.splice(idx, 1); }
821
822			log('[CC365] bindProductSelector — ✓ gruppo "' + groupName + '" registrato | value:', cc365Value, '| active:', cc365Radio.checked);
823		}
824
825		/* ── retryPending ─────────────────────────────────────────────────── */
826
827		// Re-attempts bindProductSelector on all queued wrappers (called from MutationObserver).
828		function retryPending() {
829			if (!pendingWrappers.length) { return; }
830			var queue = pendingWrappers.slice();
831			for (var i = 0; i < queue.length; i++) { bindProductSelector(queue[i]); }
832		}
833
834		/* ── auto-init with MutationObserver ──────────────────────────────── */
835
836		// Binds any wrapper selector found in a newly added DOM node or its desce
836ndants.
837		function tryBind(node) {
838			if (!node || node.nodeType !== 1) { return; }
839			if (node.matches && node.matches(WRAPPER_SELECTOR)) { bindProductSelector(node); }
840			var nested = node.querySelectorAll ? node.querySelectorAll(WRAPPER_SELECTOR) : [];
841			for (var k = 0; k < nested.length; k++) { bindProductSelector(nested[k]); }
842		}
843
844		var existing = document.querySelectorAll(WRAPPER_SELECTOR);
845		log('[CC365] autoInit — wrapper già nel DOM:', existing.length);
846		for (var i = 0; i < existing.length; i++) { bindProductSelector(existing[i]); }
847
848		if (window.MutationObserver) {
849			var observer = new MutationObserver(function (mutations) {
850				for (var m = 0; m < mutations.length; m++) {
851					var added = mutations[m].addedNodes;
852					for (var n = 0; n < added.length; n++) { tryBind(added[n]); }
853				}
854				retryPending();
855			});
856			observer.observe(document.body || document.documentElement, {
857				childList: true, subtree: true
858			});
859			log('[CC365] MutationObserver avviato');
860		}
861
862		/* ── Exported API ─────────────────────────────────────────────────── */
863
864		window.MapCC365 = {
865			modal:               Modal,
866			confirmRemoval:      function (el) { confirmRemoval(el, groups[el.name] || {}); },
867			bindProductSelector: bindProductSelector,
868			debugGroups:         function () { return JSON.parse(JSON.stringify(groups)); }
869		};
870
871	} // end run()
872
873})(MapCC365Modal);
874
875
876// Binds .map-cc365-checkbox checkboxes in the pricing section.
877// Toggles CC365 state on each plan card (price, CTA link, CSS class), fires GA4/Matomo/Meta
878// view_item/ViewContent events on change, and opens the removal confirmation modal on uncheck.
879// Exposes window.MapCC365.confirmRemoval and window.MapCC365.modal as public shortcuts.
880function cc365PricingCheckbox() {
881
882	function updateCardState(checkbox) {
883		var card = checkbox.closest('.map-plan-card');
884		var priceEl = card.querySelector('.map-plan-price');
885		var priceValue = card.querySelector('.price-value');
886		var ctaLink = card.querySelector('.map-plan-cta');
887
888		if (checkbox.checked) {
889			card.classList.add('cc365-active');
890			priceValue.textContent = priceEl.dataset.cc;
891
892			var url = new URL(ctaLink.href);
893			url.searchParams.set('alt_product', 'true');
894			ctaLink.href = url.toString();
895		} else {
896			card.classList.remove('cc365-active');
897			priceValue.textContent = priceEl.dataset.base;
898
899			var url = new URL(ctaLink.href);
900			url.searchParams.delete('alt_product');
901			ctaLink.href = url.toString();
902		}
903	}
904
905	function trackCheckboxChange(checkbox) {
906		var card = checkbox.closest('.map-plan-card');
907		var priceEl = card.querySelector('.map-plan-price');
908
909		var idprodotto = checkbox.checked ? card.dataset.idprodottoCc : card.dataset.idprodotto;
910		var nome = card.dataset.planName + (checkbox.checked ? ' + CC365' : '');
911		var prezzo = checkbox.checked ? parseFloat(priceEl.dataset.cc) || 0 : parseFloat(priceEl.dataset.base) || 0;
912
913		try {
914			gtag('event', 'view_item', {
915				currency: 'EUR',
916				value: prezzo,
917				items: [{
918					item_id: String(idprodotto),
919					item_name: nome,
920					price: prezzo,
921					quantity: 1
922				}]
923			});
924			console.log('[GA4] view_item inviato | id:', idprodotto, '| nome:', nome, '| prezzo:', prezzo);
925		} catch (e) { console.error('[GA4] view_item error:', e); }
926
927		try {
928			_paq.push(['trackEvent', 'Ecommerce', 'view_item', nome, prezzo]);
929			console.log('[Matomo] trackEvent inviato | id:', idprodotto, '| nome:', nome, '| prezzo:', prezzo);
930		} catch (e) { console.error('[Matomo] trackEvent error:', e); }
931
932		try {
933			fbq('track', 'ViewContent', {
934				content_name: nome,
935				content_ids: [String(idprodotto)],
936				content_type: 'product',
937				value: prezzo,
938				currency: 'EUR'
939			});
940			console.log('[Meta] ViewContent inviato | id:', idprodotto, '| nome:', nome, '| prezzo:', prezzo);
941		} catch (e) { console.error('[Meta] ViewContent error:', e); }
942	}
943
944	function fireEvent(checkbox, name) {
945		var card = checkbox.closest('.map-plan-card');
946		checkbox.dispatchEvent(new CustomEvent(name, {
947			bubbles: true,
948			detail: {
949				checkbox: checkbox,
950				planId: card ? card.dataset.planId : null,
951				planName: card ? card.dataset.planName : null,
952				idprodotto: card ? card.dataset.idprodotto : null,
953				idprodottoCc: card ? card.dataset.idprodottoCc : null
954			}
955		}));
956	}
957
958	function applyRemoval(checkbox) {
959		updateCardState(checkbox);
960		trackCheckboxChange(checkbox);
961		fireEvent(checkbox, 'cc365:remove-confirmed');
962	}
963
964	function cancelRemoval(checkbox) {
965		checkbox.checked = true;
966		fireEvent(checkbox, 'cc365:remove-cancelled');
967	}
968
969	function openRemovalConfirm(checkbox) {
970		fireEvent(checkbox, 'cc365:remove-attempt');
971		MapCC365Modal.open({
972			onConfirm: function () { applyRemoval(checkbox); },
973			onCancel: function () { cancelRemoval(checkbox); }
974		});
975	}
976
977	document.querySelectorAll('.map-cc365-checkbox').forEach(function (checkbox) {
978
979		if (checkbox.dataset.bound) return;
980		checkbox.dataset.bound = 'true';
981
982		checkbox.addEventListener('change', function () {
983			console.log('[CC365] change triggered');
984			if (this.checked) {
985				updateCardState(this);
986				trackCheckboxChange(this);
987			} else {
988				openRemovalConfirm(this);
989			}
990		});
991
992		// DOM update only, no tracking
993		if (checkbox.checked) {
994			updateCardState(checkbox);
995		}
996	});
997
998	window.MapCC365 = window.MapCC365 || {};
999	window.MapCC365.confirmRemoval = openRemovalConfirm;
1000	window.MapCC365.modal = MapCC365Modal;
1001
1002}
1003
1004// Sends a Meta ViewContent event for each product in the array,
1005// starting with a 10-second initial delay and staggering each subsequent item by 500 ms.
1006function mapTrackProducts(products) {
1007	if (!products || !products.length) return;
1008	var delay = 10000;
1009	products.forEach(function (product) {
1010		setTimeout(function () {
1011			try {
1012				fbq('track', 'ViewContent', {
1013					content_name: product.name,
1014					content_ids: [String(product.idprodotto)],
1015					content_type: 'product',
1016					value: product.price,
1017					currency: 'EUR'
1018				});
1019				console.log('[Meta] ViewContent inviato | id:', product.idprodotto, '| nome:', product.name, '| prezzo:', product.price);
1020			} catch (e) { console.error('[Meta] ViewContent error:', e); }
1021		}, delay);
1022		delay += 500;
1023	});
1024}
1025
1026// Initialises a pills block (languages or regulations).
1027// Detects block type from data-langs-pills / data-regs-pills, picks IT/EN labels from
1028// <html lang>, and wires the toggle button to expand/collapse hidden .is-extra items.
1029function initPillsBlock(block) {
1030  const btn = block.querySelector('.toggle-more');
1031  if (!btn) return;
1032
1033  // Detect block type: langs or regs
1034  const isLangs = block.hasAttribute('data-langs-pills');
1035  const type = isLangs ? 'langs' : 'regs';
1036
1037  // UI language
1038  function detectLang() {
1039	const htmlLang = (document.documentElement.lang || '').toLowerCase();
1040	return htmlLang.startsWith('it') ? 'it' : 'en';
1041  }
1042  const lang = detectLang();
1043
1044  // Labels by type and count
1045  const labels = {
1046	regs: {
1047	  it: (n) => `+${n} ${n === 1 ? 'regolamento' : 'regolamenti'} →`,
1048	  en: (n) => `+${n} ${n === 1 ? 'other regulation' : 'other regulations'} →`
1049	},
1050	langs: {
1051	  it: (n) => `+${n} ${n === 1 ? 'altra lingua' : 'altre lingue'} →`,
1052	  en: (n) => `+${n} ${n === 1 ? 'other language' : 'other languages'} →`
1053	}
1054  };
1055
1056  function labelForCount(count) {
1057	return labels[type][lang](count);
1058  }
1059
1060  const hiddenItems = block.querySelectorAll('.is-extra');
1061  const hiddenCount = hiddenItems.length;
1062
1063  block.dataset.expanded = 'false';
1064  btn.setAttribute('aria-expanded', 'false');
1065  btn.textContent = labelForCount(hiddenCount);
1066
1067  btn.addEventListener('click', function (e) {
1068	const root = e.currentTarget.closest('[data-regs-pills], [data-langs-pills]');
1069	if (!root) return;
1070
1071	const expanded = root.dataset.expanded === 'true';
1072	const next = !expanded;
1073
1074	root.classList.toggle('is-expanded', next);
1075	root.dataset.expanded = String(next);
1076	btn.setAttribute('aria-expanded', String(next));
1077	btn.textContent = next ? '↑' : labelForCount(hiddenCount);
1078  });
1079}
1080// Global click delegate: walks up from the click target and, if any ancestor carries
1081// a "track-ga-event" attribute, sends a GA4 event using the attribute value as event name.
1082// NOTE: this listener is registered twice (here and inside DOMContentLoaded below) — kept as-is.
1083  // Intercepts all elements with the track-ga-event attribute
1084  document.addEventListener('click', function(e) {
1085	// Walk up the DOM from the clicked element looking for track-ga-event
1086	let targetElement = e.target;
1087	let eventName = null;
1088
1089	// Check the clicked element and its ancestors up to the document
1090	while (targetElement && targetElement !== document) {
1091	  if (targetElement.hasAttribute && targetElement.hasAttribute('track-ga-event')) {
1092		eventName = targetElement.getAttribute('track-ga-event');
1093		break;
1094	  }
1095	  targetElement = targetElement.parentElement;
1096	}
1097
1098	// If an element with the attribute was found, track the event
1099	if (eventName) {
1100	  console.log('Elemento trovato:', targetElement);
1101	  console.log('Nome evento:', eventName);
1102
1103	  // Send the event to GA4 via gtag
1104	  try {
1105		gtag('event', eventName, {
1106		  'page_url': window.location.href,
1107		  'page_title': document.title
1108		});
1109		console.log('Track GA Event: ', eventName, window.location.href, document.title);
1110	  } catch (error) {
1111		console.error('Errore tracciamento GA4:', error);
1112	  }
1113	}
1114  });
1115
1116
1117// Main bootstrap: initialises pills blocks, registers the click GA delegate, wires MAP consent/
1118// cookie listeners, pricing checkbox, plugin forms, enemies accordion fix, tippy tooltips,
1119// and starts the countdown, Fluent Forms, and GA event trackers.
1120document.addEventListener("DOMContentLoaded", function () {
1121	console.log('functions.js document ready');
1122
1123	document.querySelectorAll('[data-regs-pills], [data-langs-pills]').forEach(initPillsBlock);
1124
1125  // Intercepts all elements with the track-ga-event attribute
1126  document.addEventListener('click', function(e) {
1127	// Walk up the DOM from the clicked element looking for track-ga-event
1128	let targetElement = e.target;
1129	let eventName = null;
1130
1131	// Check the clicked element and its ancestors up to the document
1132	while (targetElement && targetElement !== document) {
1133	  if (targetElement.hasAttribute && targetElement.hasAttribute('track-ga-event')) {
1134		eventName = targetElement.getAttribute('track-ga-event');
1135		break;
1136	  }
1137	  targetElement = targetElement.parentElement;
1138	}
1139
1140	// If an element with the attribute was found, track the event
1141	if (eventName) {
1142	  console.log('Elemento trovato:', targetElement);
1143	  console.log('Nome evento:', eventName);
1144
1145	  // Send the event to GA4 via gtag
1146	  try {
1147		gtag('event', eventName, {
1148		  'page_url': window.location.href,
1149		  'page_title': document.title
1150		});
1151		console.log('Track GA Event: ', eventName, window.location.href, document.title);
1152	  } catch (error) {
1153		console.error('Errore tracciamento GA4:', error);
1154	  }
1155	}
1156  });
1157
1158
1159	// begin MAP listeners
1160	window.addEventListener('myagileprivacy_gcmode_consent_granted', function (e) {
1161		_paq.push(['trackEvent', 'MAP', 'myagileprivacy_gcmode_consent_granted']);
1162	});
1163
1164	// Consent denied listener
1165	window.addEventListener('myagileprivacy_gcmode_consent_denied', function (e) {
1166		_paq.push(['trackEvent', 'MAP', 'myagileprivacy_gcmode_consent_denied']);
1167	});
1168
1169
1170	window.addEventListener('myagileprivacy_cookie_reject', function (e) {
1171		_paq.push(['trackEvent', 'MAP', 'myagileprivacy_cookie_reject']);
1172	});
1173
1174
1175	window.addEventListener('myagileprivacy_cookie_customize', function (e) {
1176		_paq.push(['trackEvent', 'MAP', 'myagileprivacy_cookie_customize']);
1177	});
1178
1179	window.addEventListener('myagileprivacy_cookie_accept', function (e) {
1180		_paq.push(['trackEvent', 'MAP', 'myagileprivacy_cookie_accept']);
1181	});
1182
1183	window.addEventListener('myagileprivacy_resurface', function (e) {
1184		_paq.push(['trackEvent', 'MAP', 'myagileprivacy_resurface']);
1185	});
1186
1187	window.addEventListener('myagileprivacy_single_cookie_accept', function (e) {
1188		_paq.push(['trackEvent', 'MAP', 'myagileprivacy_single_cookie_accept']);
1189	});
1190
1191	window.addEventListener('myagileprivacy_single_cookie_reject', function (e) {
1192		_paq.push(['trackEvent', 'MAP', 'myagileprivacy_single_cookie_reject']);
1193	});
1194	// end MAP listeners
1195
1196	cc365PricingCheckbox();
1197
1198
1199
1200	jQuery('.pluginForm').each(function () {
1201		jQuery(this).formSend();
1202	});
1203
1204	// Enemies accordion fix — pages 14086 / 14091 only
1205	/**/
1206	setTimeout(function () {
1207		if (document.body.classList.contains('page-id-14086') ||
1208			document.body.classList.contains('page-id-14091')) {
1209
1210			var readMoreSpans = document.querySelectorAll('a.oxy-read-more-link');
1211			readMoreSpans.forEach(function (readMoreSpan) {
1212				readMoreSpan.addEventListener('click', function () {
1213
1214					setTimeout(function () {
1215						console.log('dispatch');
1216						window.dispatchEvent(new Event('resize'));
1217					}, 500);
1218				});
1219			});
1220		}
1221	}, 800);
1222	/**/
1223
1224
1225
1226	// Only on elements with a non-empty data-tippy-content attribute
1227	var elementsWithContent = Array.from(document.querySelectorAll('[data-tippy-content]'))
1228		.filter(element => element.getAttribute('data-tippy-content').trim().length > 0);
1229	jQuery(elementsWithContent).addClass('dotted-underline');
1230	tippy(elementsWithContent, {
1231		animation: 'shift-away'
1232	});
1233
1234	agile_initCountdown();
1235
1236	trackFluentFormEvents();
1237
1238	trackGAEvents();
1239
1240});

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.