1/** 2 * Finance Partners â Cookie Consent Manager 3 * 4 * Compliant with GDPR + Provvedimento Garante Privacy 10/06/2021: 5 * - Third-party scripts (Calendly) are NOT loaded until user opt-in. 6 * - Closing or ignoring the banner = refusal (only technical cookies). 7 * - Explicit "Solo necessari" button rejects non-essential cookies. 8 * - User can revoke/change preferences at any time via footer link. 9 */ 10(function () { 11 'use strict'; 12 13 var STORAGE_KEY = 'fp_cookie_consent'; 14 var CONSENT_VERSION = 1; 15 var CALENDLY_BOOKING_URL = 'https://calendly.com/m-nardini-financepartners/30min?hide_gdpr_banner=1'; 16 17 var I18N = { 18 it: { 19 banner_title: 'Usiamo i cookie', 20 banner_text: 'Questo sito utilizza cookie tecnici necessari al funzionamento e â solo previo tuo consenso â cookie di terze parti (Calendly) per la prenotazione di call. Non usiamo cookie di profilazione o pubblicitari. Maggiori dettagli nella <a href="cookie-policy.html">Cookie Policy</a>.', 21 accept_all: 'Accetta tutti', 22 accept_necessary: 'Solo necessari', 23 customize: 'Personalizza', 24 modal_title: 'Preferenze cookie', 25 modal_intro: 'Scegli quali cookie autorizzare. Puoi modificare la scelta in qualsiasi momento dal link "Gestisci cookie" nel footer.', 26 cat_necessary_title: 'Cookie tecnici (sempre attivi)', 27 cat_necessary_desc: 'Necessari al funzionamento del sito: memorizzazione della lingua, sessione, sicurezza. Non richiedono consenso ai sensi dell\'Art. 122 del Codice Privacy.', 28 cat_necessary_badge: 'Sempre attivi', 29 cat_third_title: 'Cookie di terze parti â Calendly', 30 cat_third_desc: 'Permettono di prenotare una call direttamente dal sito tramite il widget Calendly. Calendly può impostare cookie propri e ricevere il tuo indirizzo IP. Senza il tuo consenso il widget non viene caricato.', 31 save: 'Salva preferenze', 32 booking_blocked_title: 'Servizio bloccato', 33 booking_blocked_text: 'Per prenotare una call online occorre accettare i cookie di terze parti (Calendly). In alternativa puoi scriverci a <a href="mailto:[email protected]">[email protected]</a>.', 34 booking_blocked_accept: 'Accetta e prenota', 35 booking_blocked_cancel: 'Annulla', 36 manage: 'Gestisci cookie' 37 }, 38 en: { 39 banner_title: 'We use cookies', 40 banner_text: 'This site uses strictly necessary technical cookies and â only with your consent â third-party cookies (Calendly) to book a call. We do not use profiling or advertising cookies. Details in our <a href="cookie-policy.html">Cookie Policy</a>.', 41 accept_all: 'Accept all', 42 accept_necessary: 'Necessary only', 43 customize: 'Customize', 44 modal_title: 'Cookie preferences', 45 modal_intro: 'Choose which cookies to allow. You can change your choice at any time using the "Manage cookies" link in the footer.', 46 cat_necessary_title: 'Technical cookies (always on)', 47 cat_necessary_desc: 'Required for the site to function: language storage, session, security. No consent required under Art. 122 of the Italian Privacy Code.', 48 cat_necessary_badge: 'Always on', 49 cat_third_title: 'Third-party cookies â Calendly', 50 cat_third_desc: 'Let you book a call directly through the Calendly widget. Calendly may set its own cookies and receive your IP address. The widget is not loaded without your consent.', 51 save: 'Save preferences', 52 booking_blocked_title: 'Service blocked', 53 booking_blocked_text: 'To book a call online you need to accept third-party cookies (Calendly). Alternatively, email us at <a href="mailto:[email protected]">[email protected]</a>.', 54 booking_blocked_accept: 'Accept and book', 55 booking_blocked_cancel: 'Cancel', 56 manage: 'Manage cookies' 57 } 58 }; 59 60 function currentLang() { 61 try { 62 var stored = localStorage.getItem('fp_lang') || localStorage.getItem('fp_language'); 63 if (stored === 'en') return 'en'; 64 return 'it'; 65 } catch (e) { return 'it'; } 66 } 67 68 function t(key) { 69 var lang = currentLang(); 70 return (I18N[lang] && I18N[lang][key]) || I18N.it[key] || key; 71 } 72 73 function readConsent() { 74 try { 75 var raw = localStorage.getItem(STORAGE_KEY); 76 if (!raw) return null; 77 var parsed = JSON.parse(raw); 78 if (!parsed || parsed.version !== CONSENT_VERSION) return null; 79 return parsed; 80 } catch (e) { return null; } 81 } 82 83 function writeConsent(thirdParty) { 84 var data = { 85 version: CONSENT_VERSION, 86 timestamp: new Date().toISOString(), 87 necessary: true, 88 thirdParty: !!thirdParty 89 }; 90 try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch (e) { /* ignore */ } 91 return data; 92 } 93 94 function hasThirdPartyConsent() { 95 var c = readConsent(); 96 return !!(c && c.thirdParty); 97 } 98 99 /* ââââââââââ Calendly lazy-loader ââââââââââ */ 100 var calendlyLoaded = false;
101 var calendlyLoading = false; 102 var calendlyCallbacks = []; 103 104 function loadCalendly(onReady) { 105 if (calendlyLoaded) { onReady && onReady(); return; } 106 if (onReady) calendlyCallbacks.push(onReady); 107 if (calendlyLoading) return; 108 calendlyLoading = true; 109 110 var css = document.createElement('link'); 111 css.rel = 'stylesheet'; 112 css.href = 'https://assets.calendly.com/assets/external/widget.css'; 113 document.head.appendChild(css); 114 115 var js = document.createElement('script'); 116 js.src = 'https://assets.calendly.com/assets/external/widget.js'; 117 js.async = true; 118 js.onload = function () { 119 calendlyLoaded = true; 120 calendlyLoading = false; 121 calendlyCallbacks.forEach(function (cb) { try { cb(); } catch (e) {} }); 122 calendlyCallbacks = []; 123 }; 124 document.head.appendChild(js); 125 } 126 127 function openCalendly(url) { 128 var target = url || CALENDLY_BOOKING_URL; 129 loadCalendly(function () { 130 if (window.Calendly && typeof window.Calendly.initPopupWidget === 'function') { 131 window.Calendly.initPopupWidget({ url: target }); 132 } 133 }); 134 } 135 136 /* ââââââââââ DOM rendering ââââââââââ */ 137 function el(tag, attrs, children) { 138 var node = document.createElement(tag); 139 if (attrs) { 140 Object.keys(attrs).forEach(function (k) { 141 if (k === 'class') node.className = attrs[k]; 142 else if (k === 'html') node.innerHTML = attrs[k]; 143 else if (k.indexOf('on') === 0 && typeof attrs[k] === 'function') { 144 node.addEventListener(k.slice(2), attrs[k]); 145 } else node.setAttribute(k, attrs[k]); 146 }); 147 } 148 (children || []).forEach(function (c) { 149 if (c == null) return; 150 node.appendChild(typeof c === 'string' ? document.createTextNode(c) : c); 151 }); 152 return node; 153 } 154 155 var bannerNode = null; 156 var modalNode = null; 157 158 function buildBanner() { 159 if (bannerNode) return bannerNode; 160 bannerNode = el('div', { class: 'fp-cc-banner', role: 'dialog', 'aria-live': 'polite', 'aria-label': t('banner_title') }, [ 161 el('h2', { class: 'fp-cc-title' }, [t('banner_title')]), 162 el('p', { class: 'fp-cc-text', html: t('banner_text') }), 163 el('div', { class: 'fp-cc-actions' }, [ 164 el('button', { 165 class: 'fp-cc-btn fp-cc-btn-primary', 166 type: 'button', 167 onclick: function () { acceptAll(); } 168 }, [t('accept_all')]), 169 el('button', { 170 class: 'fp-cc-btn fp-cc-btn-secondary', 171 type: 'button', 172 onclick: function () { acceptNecessary(); } 173 }, [t('accept_necessary')]), 174 el('button', { 175 class: 'fp-cc-btn fp-cc-btn-link', 176 type: 'button', 177 onclick: function () { hideBanner(); openPreferences(); } 178 }, [t('customize')]) 179 ]) 180 ]); 181 document.body.appendChild(bannerNode); 182 return bannerNode; 183 } 184 185 function showBanner() { 186 buildBanner(); 187 requestAnimationFrame(function () { 188 bannerNode.classList.add('fp-cc-visible'); 189 }); 190 } 191 192 function hideBanner() { 193 if (!bannerNode) return; 194 bannerNode.classList.remove('fp-cc-visible'); 195 setTimeout(function () { 196 if (bannerNode && bannerNode.parentNode) bannerNode.parentNode.removeChild(bannerNode); 197 bannerNode = null; 198 }, 400); 199 } 200 201 function buildModal() { 202 if (modalNode) return modalNode; 203 204 var thirdPartyChecked = hasThirdPartyConsent(); 205 206 var toggle = el('label', { class: 'fp-cc-switch' }, []); 207 var input = el('input', { type: 'checkbox' }); 208 if (thirdPartyChecked) input.setAttribute('checked', 'checked'); 209 var slider = el('span', { class: 'fp-cc-switch-slider' }, []); 210 toggle.appendChild(input); 211 toggle.appendChild(slider); 212 213 var modalBox = el('div', { class: 'fp-cc-modal', role: 'dialog', 'aria-modal'
213: 'true', 'aria-label': t('modal_title') }, [ 214 el('h2', null, [t('modal_title')]), 215 el('p', { class: 'fp-cc-modal-intro' }, [t('modal_intro')]), 216 el('div', { class: 'fp-cc-category' }, [ 217 el('div', { class: 'fp-cc-category-head' }, [ 218 el('span', { class: 'fp-cc-category-title' }, [t('cat_necessary_title')]), 219 el('span', { class: 'fp-cc-badge-always' }, [t('cat_necessary_badge')]) 220 ]), 221 el('p', { class: 'fp-cc-category-desc' }, [t('cat_necessary_desc')]) 222 ]), 223 el('div', { class: 'fp-cc-category' }, [ 224 el('div', { class: 'fp-cc-category-head' }, [ 225 el('span', { class: 'fp-cc-category-title' }, [t('cat_third_title')]), 226 toggle 227 ]), 228 el('p', { class: 'fp-cc-category-desc' }, [t('cat_third_desc')]) 229 ]), 230 el('div', { class: 'fp-cc-modal-actions' }, [ 231 el('button', { 232 class: 'fp-cc-btn fp-cc-btn-secondary', 233 type: 'button', 234 onclick: function () { closePreferences(); } 235 }, [t('accept_necessary')]), 236 el('button', { 237 class: 'fp-cc-btn fp-cc-btn-primary', 238 type: 'button', 239 onclick: function () { 240 writeConsent(!!input.checked); 241 closePreferences(); 242 } 243 }, [t('save')]) 244 ]) 245 ]); 246 247 modalNode = el('div', { 248 class: 'fp-cc-modal-overlay', 249 onclick: function (e) { if (e.target === modalNode) closePreferences(); } 250 }, [modalBox]); 251 252 document.body.appendChild(modalNode); 253 return modalNode; 254 } 255 256 function openPreferences() { 257 buildModal(); 258 requestAnimationFrame(function () { 259 modalNode.classList.add('fp-cc-visible'); 260 }); 261 } 262 263 function closePreferences() { 264 if (!modalNode) return; 265 modalNode.classList.remove('fp-cc-visible'); 266 setTimeout(function () { 267 if (modalNode && modalNode.parentNode) modalNode.parentNode.removeChild(modalNode); 268 modalNode = null; 269 }, 250); 270 } 271 272 function acceptAll() { writeConsent(true); hideBanner(); } 273 function acceptNecessary() { writeConsent(false); hideBanner(); } 274 275 /* ââââââââââ Booking gate ââââââââââ */ 276 function bookCall(url) { 277 if (hasThirdPartyConsent()) { 278 openCalendly(url); 279 return; 280 } 281 showBookingGate(url); 282 } 283 284 var gateNode = null; 285 function showBookingGate(url) { 286 if (gateNode) return; 287 var box = el('div', { class: 'fp-cc-modal' }, [ 288 el('h2', null, [t('booking_blocked_title')]), 289 el('p', { class: 'fp-cc-modal-intro', html: t('booking_blocked_text') }), 290 el('div', { class: 'fp-cc-modal-actions' }, [ 291 el('button', { 292 class: 'fp-cc-btn fp-cc-btn-secondary', 293 type: 'button', 294 onclick: function () { closeBookingGate(); } 295 }, [t('booking_blocked_cancel')]), 296 el('button', { 297 class: 'fp-cc-btn fp-cc-btn-primary', 298 type: 'button', 299 onclick: function () { 300 writeConsent(true); 301 closeBookingGate(); 302 openCalendly(url); 303 } 304 }, [t('booking_blocked_accept')]) 305 ]) 306 ]); 307 gateNode = el('div', { 308 class: 'fp-cc-modal-overlay', 309 onclick: function (e) { if (e.target === gateNode) closeBookingGate(); } 310 }, [box]); 311 document.body.appendChild(gateNode); 312 requestAnimationFrame(function () { gateNode.classList.add('fp-cc-visible'); }); 313 } 314 315 function closeBookingGate() { 316 if (!gateNode) return; 317 gateNode.classList.remove('fp-cc-visible'); 318 setTimeout(function () { 319 if (gateNode && gateNode.parentNode) gateNode.parentNode.removeChild(gateNode); 320 gateNode = null; 321 }, 250); 322 } 323 324 /* ââââââââââ Footer link auto-wiring ââââââââââ */ 325 function wireFooterLinks() { 326 var nodes = document.querySelectorAll('[data-fp-cc-open]'); 327 for (var i = 0; i < nodes.length; i++) { 328 (function (node) { 329 if (node.__fpCcWired) return; 330 node.__fpCcWired = true; 331 node.addEventListener('click', function (e) { 332 e.preventDefault(); 333 openPreferences(); 334 }); 335 })(nodes[i]); 336 } 337 } 338 339 /* ââââââââââ Boot ââââââââââ */
340 function init() { 341 wireFooterLinks(); 342 var consent = readConsent(); 343 if (!consent) { 344 showBanner(); 345 } else if (consent.thirdParty) { 346 // Pre-warm Calendly so the popup is instant when clicked 347 loadCalendly(); 348 } 349 } 350 351 if (document.readyState === 'loading') { 352 document.addEventListener('DOMContentLoaded', init); 353 } else { 354 init(); 355 } 356 357 /* ââââââââââ Public API ââââââââââ */ 358 window.FPCookieConsent = { 359 bookCall: bookCall, 360 openPreferences: openPreferences, 361 hasThirdPartyConsent: hasThirdPartyConsent, 362 revoke: function () { 363 try { localStorage.removeItem(STORAGE_KEY); } catch (e) {} 364 showBanner(); 365 } 366 }; 367})();
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.