1 2/** 3 * Legab cookie consent: catalogo JSON, categorie GDPR, script/iframe bloccati fino al consenso. 4 */ 5(function () { 6 'use strict'; 7 8 var STORAGE_KEY = 'legab_cookie_consent'; 9 var STORAGE_SCHEMA = 1; 10 var EVENT_NAME = 'legab-consent-changed'; 11 var HOST_ID = 'legab-cookie-mount'; 12 var observerStarted = false; 13 var observerQueued = false; 14 var autoBlockInProgress = false; 15 var activeCatalog = null; 16 var blockedNodeSeq = 0; 17 // Captured at load: later consent activation must re-apply this nonce (CSP blocks bare inline scripts). 18 var pageCspNonce = 19 (document.currentScript && document.currentScript.nonce) || ''; 20 var EMBED_PROVIDERS = [ 21 { serviceId: 'youtube-embed', categoryId: 'marketing', hosts: ['youtube.com', 'youtu.be', 'youtube-nocookie.com', 'googlevideo.com'] }, 22 { serviceId: 'facebook-embed', categoryId: 'marketing', hosts: ['facebook.com', 'facebook.net', 'fbcdn.net'] }, 23 { serviceId: 'instagram-embed', categoryId: 'marketing', hosts: ['instagram.com', 'cdninstagram.com'] }, 24 { serviceId: 'x-embed', categoryId: 'marketing', hosts: ['x.com', 'twitter.com', 'twimg.com'] }, 25 { serviceId: 'tiktok-embed', categoryId: 'marketing', hosts: ['tiktok.com', 'tiktokcdn.com'] }, 26 { serviceId: 'spotify-embed', categoryId: 'marketing', hosts: ['spotify.com', 'scdn.co'] }, 27 ]; 28 29 function getMount() { 30 return document.getElementById(HOST_ID); 31 } 32 33 function readPageConfig() { 34 var el = getMount(); 35 if (!el) return { catalogUrl: '', privacyUrl: '' }; 36 // privacyUrl arriva da TYPO3: constants(privacyPageUid) -> setup typolink -> data-privacy-url. 37 var privacyUrl = (el.getAttribute('data-privacy-url') || '').trim(); 38 return { 39 catalogUrl: el.getAttribute('data-catalog-url') || '', 40 privacyUrl: privacyUrl, 41 }; 42 } 43 44 function loadStored() { 45 try { 46 var raw = localStorage.getItem(STORAGE_KEY); 47 if (!raw) return null; 48 return JSON.parse(raw); 49 } catch (e) { 50 return null; 51 } 52 } 53 54 function saveStored(payload) { 55 localStorage.setItem(STORAGE_KEY, JSON.stringify(payload)); 56 } 57 58 function cloneConsentState(state) { 59 return { 60 categories: Object.assign({}, state.categories || {}), 61 services: Object.assign({}, state.services || {}), 62 }; 63 } 64 65 function defaultConsentFromCatalog(catalog) { 66 var out = { categories: {}, services: {} }; 67 if (!catalog || !catalog.categories) return out; 68 catalog.categories.forEach(function (c) { 69 out.categories[c.id] = !!c.required; 70 }); 71 (catalog.services || []).forEach(function (s) { 72 out.services[s.id] = !!out.categories[s.categoryId]; 73 }); 74 return out; 75 } 76 77 function normalizeConsent(catalog, stored) { 78 var normalized = defaultConsentFromCatalog(catalog); 79 if (!stored || !stored.categories) { 80 return normalized; 81 } 82 83 Object.keys(normalized.categories).forEach(function (catId) { 84 if (Object.prototype.hasOwnProperty.call(stored.categories, catId)) { 85 normalized.categories[catId] = !!stored.categories[catId]; 86 } 87 }); 88 89 if (stored.services) { 90 Object.keys(normalized.services).forEach(function (serviceId) { 91 if (Object.prototype.hasOwnProperty.call(stored.services, serviceId)) { 92 normalized.services[serviceId] = !!stored.services[serviceId]; 93 } 94 }); 95 } else { 96 (catalog.services || []).forEach(function (svc) { 97 normalized.services[svc.id] = !!normalized.categories[svc.categoryId]; 98 }); 99 } 100 return normalized; 101 } 102 103 function isAllowed(categoryId, serviceId, consentState) { 104 if (serviceId) { 105 return !!(consentState.services && consentState.services[serviceId]); 106 } 107 if (!categoryId) return true; 108 return !!(consentState.categories && consentState.categories[categoryId]); 109 } 110 111 function getCspNonce() { 112 if (pageCspNonce) return pageCspNonce; 113 var mount = getMount(); 114 if (mount) { 115 var fromMount = (mount.getAttribute('data-csp-nonce') || '').trim(); 116 if (fromMount) { 117 pageCspNonce = fromMount; 118 return pageCspNonce; 119 } 120 }
121 var scripts = document.querySelectorAll('script[nonce]'); 122 for (var i = 0; i < scripts.length; i++) { 123 if (scripts[i].nonce) { 124 pageCspNonce = scripts[i].nonce; 125 return pageCspNonce; 126 } 127 } 128 return ''; 129 } 130 131 function activateScripts(consentState) { 132 var nodes = document.querySelectorAll('script[type="text/plain"][data-cookie-category]'); 133 var nonce = getCspNonce(); 134 nodes.forEach(function (oldScript) { 135 var cat = oldScript.getAttribute('data-cookie-category'); 136 var serviceId = oldScript.getAttribute('data-cookie-service'); 137 if (!isAllowed(cat, serviceId, consentState)) return; 138 139 var nu = document.createElement('script'); 140 for (var i = 0; i < oldScript.attributes.length; i++) { 141 var a = oldScript.attributes[i]; 142 // Skip type (kept as text/plain while blocked), category marker, and nonce 143 // (getAttribute('nonce') is empty in browsers â would wipe a valid nonce). 144 if (a.name === 'type' || a.name === 'data-cookie-category' || a.name === 'nonce') continue; 145 nu.setAttribute(a.name, a.value); 146 } 147 nu.setAttribute('data-cookie-unblocked', '1'); 148 var inline = oldScript.textContent; 149 if (inline && inline.trim()) { 150 nu.textContent = inline; 151 } 152 // CSP script-src-elem requires nonce/hash for dynamically inserted inline scripts. 153 if (nonce) { 154 nu.setAttribute('nonce', nonce); 155 nu.nonce = nonce; 156 } 157 oldScript.parentNode.replaceChild(nu, oldScript); 158 }); 159 } 160 161 function activateIframes(consentState) { 162 document.querySelectorAll('iframe[data-cookie-src][data-cookie-category]').forEach(function (iframe) { 163 var cat = iframe.getAttribute('data-cookie-category'); 164 var serviceId = iframe.getAttribute('data-cookie-service'); 165 if (!isAllowed(cat, serviceId, consentState)) return; 166 var src = iframe.getAttribute('data-cookie-src'); 167 if (src) { 168 iframe.setAttribute('src', src); 169 iframe.setAttribute('data-cookie-unblocked', '1'); 170 } 171 }); 172 } 173 174 function applyTechnical(consentState) { 175 activateScripts(consentState); 176 activateIframes(consentState); 177 syncBlockedEmbedClasses(consentState); 178 } 179 180 function persistAndNotify(catalogVersion, consentState) { 181 saveStored({ 182 schema: STORAGE_SCHEMA, 183 catalogVersion: catalogVersion, 184 categories: consentState.categories, 185 services: consentState.services, 186 updatedAt: Date.now(), 187 }); 188 window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: cloneConsentState(consentState) })); 189 } 190 191 function applyConsent(catalogVersion, consentState) { 192 applyTechnical(consentState); 193 persistAndNotify(catalogVersion, consentState); 194 } 195 196 function legalBasisLabel(ui, key) { 197 if (key === 'consent') return ui.legalBasisLabel + ': Consenso'; 198 if (key === 'legitimate_interest') return ui.legalBasisLabel + ': Interesse legittimo'; 199 return ui.legalBasisLabel + ': ' + (key || 'â'); 200 } 201 202 function cookieTypeLabel(t) { 203 if (t === 'first_party') return 'First party'; 204 if (t === 'third_party') return 'Terza parte'; 205 return t || 'â'; 206 } 207 208 function cookieClassLabel(v) { 209 if (v === 'technical') return 'Tecnico'; 210 if (v === 'analytics') return 'Analytics'; 211 if (v === 'marketing') return 'Marketing'; 212 return v || 'â'; 213 } 214 215 function sourceLabel(ui, source) { 216 if (source === 'observed') { 217 return ui.sourceObservedLabel || 'Rilevato nei nostri test'; 218 } 219 if (source === 'provider_declared') { 220 return ui.sourceProviderDeclaredLabel || 'Possibile secondo il fornitore'; 221 } 222 return source || 'â'; 223 } 224 225 function mandatoryLabel(v) { 226 if (v === 'required') return 'Richiesto'; 227 if (v === 'optional') return 'Opzionale'; 228 return v || 'â'; 229 } 230 231 function buildConsentToggleButton(baseClass, label, enabled, disabled, attrs) { 232 var checked = enabled ? 'true' : 'false'; 233 var disabledAttr = disabled ? ' disabled' : ''; 234 return ( 235 '<button type="button" class="' + 236 baseClass + 237 ' legab-cookie-toggle-btn" aria-pressed="' + 238 checked + 239 '" data-checked="' + 240 checked + 241 '"' + 242 (attrs || '') + 243 disabledAttr + 244 '>' + 245 '<span class="legab-cookie-toggle-btn__label">' + 246 esc(label) + 247 '</span>' + 248 '<span class="legab-cookie-toggle-btn__switch" aria-hidden="true"><span class="legab-cookie-toggle-btn__knob"></span></span>' + 249 '</button>' 250 ); 251 } 252 253 function transferOutsideEuLabel(ui, transferObj) { 254 if (!transferObj) return 'â'; 255 var base = transferObj.possible ? 'Si' : 'No'; 256 if (transferObj.notes) { 257 return ui.transferOutsideEuLabel + ': ' + base + ' (' + transferObj.notes + ')'; 258 } 259 return ui.transferOutsideEuLabel + ': ' + base; 260 } 261 262 function esc(s) { 263 if (s == null) return ''; 264 var d = document.createElement('div'); 265 d.textContent = s; 266 return d.innerHTML; 267 } 268 269 function detectProviderByHost(hostname) { 270 if (!hostname) return null; 271 var host = String(hostname).toLowerCase(); 272 for (var i = 0; i < EMBED_PROVIDERS.length; i++) { 273 var provider = EMBED_PROVIDERS[i]; 274 for (var j = 0; j < provider.hosts.length; j++) { 275 var providerHost = provider.hosts[j]; 276 if (host === providerHost || host.slice(-(providerHost.length + 1)) === '.' + providerHost) { 277 return provider; 278 } 279 } 280 } 281 return null; 282 } 283 284 function detectProviderByUrl(url) { 285 if (!url) return null; 286 try { 287 var parsed = new URL(url, window.location.origin); 288 return detectProviderByHost(parsed.hostname); 289 } catch (e) { 290 return null; 291 } 292 } 293 294 function nextBlockedNodeId() { 295 blockedNodeSeq += 1; 296 return 'legab-cookie-blocked-' + String(blockedNodeSeq); 297 } 298 299 function ensureBlockedNodeId(node) { 300 var id = node.getAttribute('data-cookie-blocked-id'); 301 if (!id) { 302 id = nextBlockedNodeId(); 303 node.setAttribute('data-cookie-blocked-id', id); 304 } 305 return id; 306 } 307 308 function getServiceLabel(serviceId) { 309 if (!activeCatalog || !activeCatalog.services) return ''; 310 for (var i = 0; i < activeCatalog.services.length; i++) { 311 if (activeCatalog.services[i].id === serviceId) { 312 return activeCatalog.services[i].name || ''; 313 } 314 } 315 return ''; 316 } 317 318 function buildEmbedPlaceholder(node) { 319 var serviceId = node.getAttribute('data-cookie-service') || ''; 320 var serviceLabel = getServiceLabel(serviceId) || 'contenuto esterno'; 321 var acceptServiceBtn = ''; 322 if (serviceId) { 323 acceptServiceBtn = 324 '<button type="button" class="btn btn-sm btn-success legab-cookie-accept-service" data-cookie-accept-service="' + 325 esc(serviceId) + 326 '">Accetta questo servizio</button>'; 327 } 328 return ( 329 '<div class="legab-cookie-placeholder__title">Contenuto bloccato per preferenze cookie</div>' + 330 '<div class="legab-cookie-placeholder__text">Per visualizzare questo ' + 331 esc(serviceLabel) + 332 ', abilita la categoria e il consenso specifico servizio.</div>' + 333 '<div class="legab-cookie-placeholder__actions">' + 334 acceptServiceBtn + 335 '<button type="button" class="btn btn-sm btn-primary legab-cookie-open-settings">Apri impostazioni cookie</button>' + 336 '</div>' 337 ); 338 } 339 340 function ensureEmbedPlaceholder(node) { 341 if (!node || !node.parentNode) return; 342 if (node.tagName === 'SCRIPT') return; 343 var wrapper = node.parentNode; 344 if (!wrapper.classList || !wrapper.classList.contains('legab-cookie-embed-wrapper')) { 345 wrapper = document.createElement('div'); 346 wrapper.className = 'legab-cookie-embed-wrapper'; 347 node.parentNode.insertBefore(wrapper, node); 348 wrapper.appendChild(node); 349 } 350 var blockedId = ensureBlockedNodeId(node); 351 var existing = wrapper.querySelector('[data-cookie-placeholder-for="' + blockedId + '"]'); 352 if (existing) return; 353 var placeholder = document.createElement('div'); 354 placeholder.className = 'legab-cookie-placeholder'; 355 placeholder.setAttribute('data-cookie-placeholder-for', blockedId); 356 placeholder.innerHTML = buildEmbedPlaceholder(node); 357 wrapper.appendChild(placeholder); 358 } 359 360 function removeEmbedPlaceholder(node) { 361 if (!node || !node.parentNode) return; 362 var blockedId = node.getAttribute('data-cookie-blocked-id'); 363 if (!blockedId) return; 364 var wrapper = node.parentNode; 365 var existing = wrapper.querySelector('[data-cookie-placeholder-for="' + blockedId + '"]'); 366 if (existing) existing.remove(); 367 } 368 369 function markBlocked(node, provider) { 370 if (!node || !provider) return; 371 if (!node.getAttribute('data-cookie-category')) { 372 node.setAttribute('data-cookie-category', provider.categoryId); 373 } 374 if (!node.getAttribute('data-cookie-service')) { 375 node.setAttribute('data-cookie-service', provider.serviceId); 376 } 377 node.setAttribute('data-cookie-auto-blocked', '1'); 378 } 379 380 function shouldAutoBlockNode(node) { 381 if (!node || !node.closest) return false;
382 if (node.closest('header, footer, .footer-legal, .footer-social, nav')) { 383 return false; 384 } 385 return true; 386 } 387 388 function detectProviderByBlockquote(blockquote) { 389 var cls = (blockquote.className || '').toLowerCase(); 390 if (cls.indexOf('twitter-tweet') !== -1) { 391 return detectProviderByHost('twitter.com'); 392 } 393 if (cls.indexOf('instagram-media') !== -1 || blockquote.getAttribute('data-instgrm-permalink')) { 394 return detectProviderByHost('instagram.com'); 395 } 396 if (cls.indexOf('fb-post') !== -1 || cls.indexOf('fb-video') !== -1 || cls.indexOf('fb-xfbml-parse-ignore') !== -1) { 397 return detectProviderByHost('facebook.com'); 398 } 399 if (cls.indexOf('tiktok-embed') !== -1) { 400 return detectProviderByHost('tiktok.com'); 401 } 402 var citeProvider = detectProviderByUrl(blockquote.getAttribute('cite') || ''); 403 return citeProvider; 404 } 405 406 function getAutoBlockRoots() { 407 var explicitRoots = document.querySelectorAll('[data-cookie-autoblock-root]'); 408 if (explicitRoots.length) return explicitRoots; 409 var mainRoots = document.querySelectorAll('main'); 410 if (mainRoots.length) return mainRoots; 411 return []; 412 } 413 414 function forEachInAutoBlockRoots(selector, callback) { 415 var roots = getAutoBlockRoots(); 416 if (!roots.length) return; 417 roots.forEach(function (root) { 418 root.querySelectorAll(selector).forEach(callback); 419 }); 420 } 421 422 function autoBlockEmbeds() { 423 forEachInAutoBlockRoots('iframe[src]:not([data-cookie-src]):not([data-cookie-unblocked="1"])', function (iframe) { 424 if (!shouldAutoBlockNode(iframe)) return; 425 var src = iframe.getAttribute('src'); 426 var provider = detectProviderByUrl(src); 427 if (!provider) return; 428 iframe.setAttribute('data-cookie-src', src); 429 iframe.removeAttribute('src'); 430 markBlocked(iframe, provider); 431 iframe.classList.add('legab-cookie-embed-blocked'); 432 }); 433 434 forEachInAutoBlockRoots('script[src]:not([data-cookie-category]):not([data-cookie-unblocked="1"])', function (script) { 435 if (!shouldAutoBlockNode(script)) return; 436 if (script.type === 'text/plain') return; 437 var src = script.getAttribute('src'); 438 var provider = detectProviderByUrl(src); 439 if (!provider) return; 440 script.setAttribute('type', 'text/plain'); 441 markBlocked(script, provider); 442 }); 443 444 forEachInAutoBlockRoots('blockquote:not([data-cookie-category])', function (blockquote) { 445 if (!shouldAutoBlockNode(blockquote)) return; 446 var provider = detectProviderByBlockquote(blockquote); 447 if (!provider) return; 448 markBlocked(blockquote, provider); 449 blockquote.classList.add('legab-cookie-embed-blocked'); 450 }); 451 } 452 453 function startEmbedAutoBlockObserver() { 454 if (observerStarted) return; 455 observerStarted = true; 456 var target = document.documentElement; 457 if (!target) return; 458 var observer = new MutationObserver(function () { 459 if (observerQueued || autoBlockInProgress) return; 460 observerQueued = true; 461 window.requestAnimationFrame(function () { 462 observerQueued = false; 463 if (autoBlockInProgress) return; 464 autoBlockInProgress = true; 465 try { 466 autoBlockEmbeds(); 467 var stored = loadStored(); 468 if (activeCatalog && stored && stored.categories) { 469 applyTechnical(normalizeConsent(activeCatalog, stored)); 470 } 471 } finally { 472 autoBlockInProgress = false; 473 } 474 }); 475 }); 476 observer.observe(target, { childList: true, subtree: true }); 477 } 478 479 function syncBlockedEmbedClasses(consentState) { 480 document.querySelectorAll('[data-cookie-auto-blocked="1"]').forEach(function (node) { 481 var cat = node.getAttribute('data-cookie-category'); 482 var serviceId = node.getAttribute('data-cookie-service'); 483 var allowed = isAllowed(cat, serviceId, consentState); 484 if (node.tagName === 'IFRAME') { 485 var blockedSrc = node.getAttribute('data-cookie-src'); 486 if (allowed && blockedSrc && !node.getAttribute('src')) { 487 node.setAttribute('src', blockedSrc); 488 } 489 if (!allowed && blockedSrc && node.getAttribute('src')) { 490 node.removeAttribute('src'); 491 } 492 } 493 node.classList.toggle('legab-cookie-embed-blocked', !allowed); 494 if (allowed) { 495 node.removeAttribute('aria-hidden'); 496 removeEmbedPlaceholder(node); 497 } else { 498 node.setAttribute('aria-hidden', 'true'); 499 ensureEmbedPlaceholder(node); 500 } 501 }); 502 } 503 504 function buildServiceBlock(ui, svc, consentState, categoryRequired) { 505 var rows = '';
506 (svc.cookies || []).forEach(function (ck) { 507 rows += 508 '<tr><td>' + 509 esc(ck.name) + 510 '</td><td>' + 511 esc(ck.duration) + 512 '</td><td>' + 513 esc(cookieTypeLabel(ck.type)) + 514 '</td><td>' + 515 esc(ck.domain || 'â') + 516 '</td><td>' + 517 esc(ck.purpose || 'â') + 518 '</td><td>' + 519 esc(cookieClassLabel(ck.cookieClass)) + 520 '</td><td>' + 521 esc(mandatoryLabel(ck.mandatory)) + 522 '</td></tr>'; 523 }); 524 var privacy = 525 svc.providerPrivacyUrl && svc.providerPrivacyUrl.length 526 ? '<p class="legab-cookie-service__row"><a href="' + 527 esc(svc.providerPrivacyUrl) + 528 '" target="_blank" rel="noopener noreferrer">' + 529 esc(ui.privacyLinkLabel) + 530 '</a></p>' 531 : ''; 532 var notes = 533 svc.notes && svc.notes.length 534 ? '<p class="legab-cookie-service__notes"><strong>' + 535 esc(ui.notesLabel) + 536 '</strong> ' + 537 esc(svc.notes) + 538 '</p>' 539 : ''; 540 var recipients = 541 svc.dataRecipients && svc.dataRecipients.length 542 ? '<p class="legab-cookie-service__row"><strong>' + 543 esc(ui.dataRecipientsLabel || 'Destinatari') + 544 '</strong> ' + 545 esc(svc.dataRecipients) + 546 '</p>' 547 : ''; 548 var retention = 549 svc.retentionPolicy && svc.retentionPolicy.length 550 ? '<p class="legab-cookie-service__row"><strong>' + 551 esc(ui.retentionPolicyLabel || 'Conservazione') + 552 '</strong> ' + 553 esc(svc.retentionPolicy) + 554 '</p>' 555 : ''; 556 var transferOutsideEu = ''; 557 if (svc.transferOutsideEU) { 558 transferOutsideEu = 559 '<p class="legab-cookie-service__row">' + 560 esc(transferOutsideEuLabel(ui, svc.transferOutsideEU)) + 561 '</p>'; 562 } 563 var serviceEnabled = !!(consentState.services && consentState.services[svc.id]); 564 var serviceConsentControl = ''; 565 if (!categoryRequired) { 566 serviceConsentControl = 567 '<div class="legab-cookie-service-consent">' + 568 buildConsentToggleButton( 569 'legab-cookie-service-switch', 570 'Consenso specifico servizio', 571 serviceEnabled, 572 false, 573 ' data-service="' + esc(svc.id) + '" data-category="' + esc(svc.categoryId) + '"' 574 ) + 575 '</div>'; 576 } 577 return ( 578 '<article class="legab-cookie-service" data-service-id="' + 579 esc(svc.id) + 580 '">' + 581 '<button type="button" class="legab-cookie-service__toggle" aria-expanded="false">' + 582 '<span class="legab-cookie-service__name">' + 583 esc(svc.name) + 584 '</span>' + 585 '<span class="legab-cookie-service__chev" aria-hidden="true"></span>' + 586 '</button>' + 587 serviceConsentControl + 588 '<div class="legab-cookie-service__panel" hidden>' + 589 '<p class="legab-cookie-service__row"><strong>' + 590 esc(ui.providerLabel) + 591 '</strong> ' + 592 esc(svc.provider) + 593 '</p>' + 594 '<p class="legab-cookie-service__row"><strong>' + 595 esc(ui.purposeLabel) + 596 '</strong> ' + 597 esc(svc.purpose) + 598 '</p>' + 599 '<p class="legab-cookie-service__row">' + 600 esc(legalBasisLabel(ui, svc.legalBasis)) + 601 '</p>' + 602 recipients + 603 transferOutsideEu + 604 retention + 605 privacy + 606 notes + 607 '<table class="legab-cookie-table"><caption class="visually-hidden">' + 608 esc(svc.name) + 609 '</caption><thead><tr><th>' + 610 esc(ui.cookieTableName) + 611 '</th><th>' + 612 esc(ui.cookieTableDuration) + 613 '</th><th>' + 614 esc(ui.cookieTableType) + 615 '</th><th>' + 616 esc(ui.cookieTableDomain) + 617 '</th><th>' + 618 esc(ui.cookieTablePurpose || 'Finalita') + 619 '</th><th>' + 620 esc(ui.cookieTableClass || 'Classe cookie') + 621 '</th><th>' + 622 esc(ui.cookieTableMandatory || 'Obbligatorieta') + 623 '</th></tr></thead><tbody>' + 624 rows + 625 '</tbody></table>' + 626 '</div></article>' 627 ); 628 } 629 630 function buildSettingsPanel(catalog, ui, consentDraft, privacyUrl) { 631 var catBlocks = '';
632 catalog.categories.forEach(function (cat) { 633 var services = catalog.services.filter(function (s) { 634 return s.categoryId === cat.id; 635 }); 636 var svcHtml = services.map(function (s) { 637 return buildServiceBlock(ui, s, consentDraft, !!cat.required); 638 }).join(''); 639 640 var toggle = ''; 641 if (!cat.required) { 642 toggle = 643 '<div class="legab-cookie-cat-toggle">' + 644 buildConsentToggleButton( 645 'legab-cookie-category-switch', 646 ui.categoryOptionalHint, 647 !!consentDraft.categories[cat.id], 648 false, 649 ' data-category="' + esc(cat.id) + '"' 650 ) + 651 '</div>'; 652 } else { 653 toggle = '<p class="legab-cookie-cat-required">' + esc(ui.categoryRequiredHint) + '</p>'; 654 } 655 656 catBlocks += 657 '<section class="legab-cookie-category" data-category-section="' + 658 esc(cat.id) + 659 '">' + 660 '<h3 class="legab-cookie-category__title">' + 661 esc(cat.label) + 662 '</h3>' + 663 '<p class="legab-cookie-category__desc">' + 664 esc(cat.description) + 665 '</p>' + 666 toggle + 667 '<div class="legab-cookie-category__services">' + 668 svcHtml + 669 '</div></section>'; 670 }); 671 672 var normalizedPrivacyUrl = (privacyUrl || '').trim(); 673 var privacy = 674 normalizedPrivacyUrl && normalizedPrivacyUrl.length 675 ? '<p class="legab-cookie-privacy">Scopri di più sui cookie e su come li usiamo nella nostra <a href="' + 676 esc(normalizedPrivacyUrl) + 677 '">Cookie Policy</a>.</p>' 678 : '<p class="legab-cookie-privacy">Scopri di più sui cookie e su come li usiamo nella nostra Cookie Policy.</p>'; 679 var sourceIntro = 680 ui.sourceIntro && ui.sourceIntro.length 681 ? '<p class="legab-cookie-source-intro">' + esc(ui.sourceIntro) + '</p>' 682 : ''; 683 684 return ( 685 '<div class="legab-cookie-settings" role="dialog" aria-modal="true" aria-labelledby="legab-cookie-settings-title">' + 686 '<div class="legab-cookie-settings__inner">' + 687 '<header class="legab-cookie-settings__head">' + 688 '<h2 id="legab-cookie-settings-title">' + 689 esc(ui.settingsTitle) + 690 '</h2>' + 691 '<button type="button" class="legab-cookie-settings__close" aria-label="' + 692 esc(ui.close) + 693 '">×</button>' + 694 '</header>' + 695 '<p class="legab-cookie-settings__intro">' + 696 esc(ui.settingsIntro) + 697 '</p>' + 698 sourceIntro + 699 privacy + 700 '<div class="legab-cookie-settings__body">' + 701 catBlocks + 702 '</div>' + 703 '<footer class="legab-cookie-settings__foot">' + 704 '<button type="button" class="btn btn-outline-secondary legab-cookie-btn-reject">' + 705 esc(ui.rejectOptional) + 706 '</button>' + 707 '<button type="button" class="btn btn-primary legab-cookie-btn-save">' + 708 esc(ui.save) + 709 '</button>' + 710 '</footer></div></div>' 711 ); 712 } 713 714 function buildBanner(ui, privacyUrl) { 715 var normalizedPrivacyUrl = (privacyUrl || '').trim(); 716 var policyTail = 717 normalizedPrivacyUrl && normalizedPrivacyUrl.length 718 ? ' Scopri di più sui cookie e su come li usiamo nella nostra <a class="legab-cookie-bar__policy-link" href="' + 719 esc(normalizedPrivacyUrl) + 720 '">' + 721 'Cookie Policy</a>.' 722 : ' Scopri di più sui cookie e su come li usiamo nella nostra Cookie Policy.'; 723 return ( 724 '<div class="legab-cookie-bar" role="region" aria-label="' + 725 esc(ui.bannerTitle) + 726 '">' + 727 '<div class="legab-cookie-bar__inner">' + 728 '<p class="legab-cookie-bar__text"><strong>' + 729 esc(ui.bannerTitle) + 730 '</strong> â ' + 731 esc(ui.bannerText) + 732 policyTail + 733 '</p>' + 734 '<div class="legab-cookie-bar__actions">' + 735 '<button type="button" class="btn btn-outline-light legab-cookie-btn-customize">' + 736 esc(ui.customize) + 737 '</button>' + 738 '<button type="button" class="btn btn-outline-light legab-cookie-btn-reject">' + 739 esc(ui.rejectOptional) + 740 '</button>' + 741 '<button type="button" class="btn btn-light legab-cookie-btn-accept">' + 742 esc(ui.acceptAll) + 743 '</button>' + 744 '</div></div></div>' 745 ); 746 } 747 748 function wireServiceToggles(root) { 749 root.querySelectorAll('.legab-cookie-service__toggle').forEach(function (btn) { 750 btn.addEventListener('click', function () { 751 var art = btn.closest('.legab-cookie-service'); 752 var panel = art && art.querySelector('.legab-cookie-service__panel'); 753 if (!panel) return; 754 var open = panel.hasAttribute('hidden'); 755 panel.toggleAttribute('hidden', !open); 756 btn.setAttribute('aria-expanded', open ? 'true' : 'false'); 757 }); 758 }); 759 } 760 761 function refreshServiceInputs(root, consentDraft) { 762 root.querySelectorAll('.legab-cookie-service-switch').forEach(function (btn) { 763 var serviceId = btn.getAttribute('data-service'); 764 var enabled = !!consentDraft.services[serviceId]; 765 btn.disabled = false;
766 btn.setAttribute('aria-pressed', enabled ? 'true' : 'false'); 767 btn.setAttribute('data-checked', enabled ? 'true' : 'false'); 768 }); 769 770 root.querySelectorAll('.legab-cookie-category-switch').forEach(function (btn) { 771 var categoryId = btn.getAttribute('data-category'); 772 var enabled = !!consentDraft.categories[categoryId]; 773 btn.setAttribute('aria-pressed', enabled ? 'true' : 'false'); 774 btn.setAttribute('data-checked', enabled ? 'true' : 'false'); 775 }); 776 } 777 778 function openSettings(host, catalog, ui, consentDraft, privacyUrl, onSaveAll) { 779 var existing = host.querySelector('.legab-cookie-settings'); 780 if (existing) existing.remove(); 781 782 var wrap = document.createElement('div'); 783 wrap.className = 'legab-cookie-settings-wrap'; 784 wrap.innerHTML = buildSettingsPanel(catalog, ui, consentDraft, privacyUrl); 785 host.appendChild(wrap); 786 787 var panel = wrap.querySelector('.legab-cookie-settings'); 788 var closeBtn = wrap.querySelector('.legab-cookie-settings__close'); 789 790 function close() { 791 wrap.remove(); 792 document.body.classList.remove('legab-cookie-no-scroll'); 793 } 794 795 closeBtn.addEventListener('click', close); 796 panel.addEventListener('click', function (e) { 797 if (e.target === panel) close(); 798 }); 799 800 wrap.querySelectorAll('.legab-cookie-category-switch').forEach(function (btn) { 801 btn.addEventListener('click', function () { 802 var id = btn.getAttribute('data-category'); 803 if (id) { 804 var nextValue = !(consentDraft.categories[id] === true); 805 consentDraft.categories[id] = nextValue; 806 // Categoria come azione bulk: allinea i servizi della categoria 807 // mantenendo comunque la possibilità di override singolo subito dopo. 808 (catalog.services || []).forEach(function (service) { 809 if (service.categoryId !== id) return; 810 consentDraft.services[service.id] = nextValue; 811 }); 812 refreshServiceInputs(wrap, consentDraft); 813 } 814 }); 815 }); 816 817 wrap.querySelectorAll('.legab-cookie-service-switch').forEach(function (btn) { 818 btn.addEventListener('click', function () { 819 var serviceId = btn.getAttribute('data-service'); 820 if (!serviceId) return; 821 consentDraft.services[serviceId] = !(consentDraft.services[serviceId] === true); 822 refreshServiceInputs(wrap, consentDraft); 823 }); 824 }); 825 826 wrap.querySelector('.legab-cookie-btn-save').addEventListener('click', function () { 827 onSaveAll(consentDraft); 828 close(); 829 }); 830 wrap.querySelector('.legab-cookie-btn-reject').addEventListener('click', function () { 831 var minimal = defaultConsentFromCatalog(catalog); 832 onSaveAll(minimal); 833 close(); 834 }); 835 836 wireServiceToggles(wrap); 837 refreshServiceInputs(wrap, consentDraft); 838 document.body.classList.add('legab-cookie-no-scroll'); 839 } 840 841 function showBanner(host, catalog, ui, privacyUrl, callbacks) { 842 var barWrap = document.createElement('div'); 843 barWrap.className = 'legab-cookie-bar-wrap'; 844 barWrap.innerHTML = buildBanner(ui, privacyUrl); 845 host.appendChild(barWrap); 846 847 var consentDraft = defaultConsentFromCatalog(catalog); 848 catalog.categories.forEach(function (c) { 849 if (!c.required) consentDraft.categories[c.id] = false; 850 }); 851 852 function acceptAll() { 853 catalog.categories.forEach(function (c) { 854 consentDraft.categories[c.id] = true; 855 }); 856 (catalog.services || []).forEach(function (s) { 857 consentDraft.services[s.id] = true; 858 }); 859 callbacks.onApply(catalog.catalogVersion, consentDraft); 860 barWrap.remove(); 861 } 862 863 function rejectMinimal() { 864 consentDraft = defaultConsentFromCatalog(catalog); 865 callbacks.onApply(catalog.catalogVersion, consentDraft); 866 barWrap.remove(); 867 } 868 869 barWrap.querySelector('.legab-cookie-btn-accept').addEventListener('click', acceptAll); 870 barWrap.querySelector('.legab-cookie-btn-reject').addEventListener('click', rejectMinimal); 871 barWrap.querySelector('.legab-cookie-btn-customize').addEventListener('click', function () { 872 var draft = cloneConsentState(consentDraft);
873 catalog.categories.forEach(function (c) { 874 if (!c.required && draft.categories[c.id] === undefined) draft.categories[c.id] = false; 875 }); 876 openSettings(host, catalog, ui, draft, privacyUrl, function (map) { 877 callbacks.onApply(catalog.catalogVersion, map); 878 barWrap.remove(); 879 }); 880 }); 881 } 882 883 function exposeApi(catalog, getConsentState) { 884 window.LegabCookieConsent = { 885 getState: function () { 886 return cloneConsentState(getConsentState()); 887 }, 888 setServiceConsent: function (serviceId, enabled) { 889 if (!serviceId || !catalog || !catalog.services) return false; 890 var exists = false; 891 catalog.services.forEach(function (service) { 892 if (service.id === serviceId) { 893 exists = true; 894 } 895 }); 896 if (!exists) return false; 897 var stored = loadStored(); 898 var nextState = normalizeConsent(catalog, stored); 899 nextState.services[serviceId] = !!enabled; 900 applyConsent(catalog.catalogVersion, nextState); 901 return true; 902 }, 903 openSettings: function () { 904 var cfg = readPageConfig(); 905 if (!catalog) return; 906 var stored = loadStored(); 907 var draft = normalizeConsent(catalog, stored); 908 var host = getMount() || document.body; 909 openSettings(host, catalog, catalog.ui, draft, cfg.privacyUrl, function (map) { 910 applyConsent(catalog.catalogVersion, map); 911 }); 912 }, 913 getCatalogVersion: function () { 914 return catalog ? catalog.catalogVersion : null; 915 }, 916 }; 917 } 918 919 function init() { 920 startEmbedAutoBlockObserver(); 921 var cfg = readPageConfig(); 922 if (!cfg.catalogUrl) { 923 return; 924 } 925 926 fetch(cfg.catalogUrl, { credentials: 'same-origin' }) 927 .then(function (r) { 928 if (!r.ok) throw new Error('catalog'); 929 return r.json(); 930 }) 931 .then(function (catalog) { 932 activeCatalog = catalog; 933 var ui = catalog.ui || {}; 934 var stored = loadStored(); 935 var needBanner = true; 936 var consentState = defaultConsentFromCatalog(catalog); 937 938 autoBlockEmbeds(); 939 940 if ( 941 stored && 942 stored.schema === STORAGE_SCHEMA && 943 stored.catalogVersion === catalog.catalogVersion && 944 stored.categories 945 ) { 946 needBanner = false; 947 consentState = normalizeConsent(catalog, stored); 948 } else { 949 consentState = defaultConsentFromCatalog(catalog); 950 } 951 applyTechnical(consentState); 952 953 var host = getMount(); 954 if (!host) return; 955 956 exposeApi(catalog, function () { 957 var s = loadStored(); 958 return s && s.categories ? normalizeConsent(catalog, s) : consentState; 959 }); 960 961 document.addEventListener('click', function (e) { 962 var t = e.target; 963 if (t && t.closest && t.closest('.legab-cookie-open-settings')) { 964 e.preventDefault(); 965 window.LegabCookieConsent.openSettings(); 966 return; 967 } 968 if (t && t.closest && t.closest('.legab-cookie-accept-service')) { 969 e.preventDefault(); 970 var btn = t.closest('.legab-cookie-accept-service'); 971 var serviceId = btn.getAttribute('data-cookie-accept-service'); 972 if (serviceId && window.LegabCookieConsent && window.LegabCookieConsent.setServiceConsent) { 973 window.LegabCookieConsent.setServiceConsent(serviceId, true); 974 } 975 } 976 }); 977 978 if (needBanner) { 979 showBanner(host, catalog, ui, cfg.privacyUrl, { 980 onApply: function (version, map) { 981 applyConsent(version, map); 982 }, 983 }); 984 } 985 }) 986 .catch(function () { 987 /* catalog fetch failed: block optional scripts remains */ 988 }); 989 } 990 991 if (document.readyState === 'loading') { 992 document.addEventListener('DOMContentLoaded', init); 993 } else { 994 init(); 995 } 996})(); 997 998document.addEventListener('DOMContentLoaded', function() { 999 const navBtn = document.getElementById('responsiveNav'); 1000 // Selezioniamo anche il div .top-fade 1001 const topFade = document.querySelector('.top-fade'); 1002 1003 if (!navBtn) return; 1004 1005 let ticking = false; 1006 1007 function updateButton() { 1008 const scrollStep = window.scrollY; 1009 const width = window.innerWidth; 1010 1011 const themeDiv = document.getElementById('bannerSkin'); 1012 const hasSkin = themeDiv && themeDiv.classList.contains('show-skin'); 1013 1014 let threshold; 1015 1016 if (hasSkin) { 1017 threshold = 16; 1018 } else { 1019 if (width < 768) { 1020 threshold = 16; 1021 } else { 1022 threshold = 150; 1023 } 1024 } 1025 1026 // Applichiamo la logica a entrambi gli elementi 1027 if (scrollStep >= threshold) { 1028 navBtn.classList.add('issticky'); 1029 if (topFade) topFade.classList.add('issticky'); 1030 } else { 1031 navBtn.classList.remove('issticky'); 1032 if (topFade) topFade.classList.remove('issticky'); 1033 } 1034 1035 ticking = false; 1036 } 1037 1038 updateButton(); 1039 1040 window.addEventListener('scroll', function() { 1041 if (!ticking) { 1042 window.requestAnimationFrame(updateButton); 1043 ticking = true; 1044 } 1045 }); 1046 1047 window.addEventListener('resize', function() { 1048 updateButton(); 1049 }); 1050}); 1051 1052// luminanza background card 1053 1054document.querySelectorAll('.card').forEach(card => { 1055 const bgColor = window.getComputedStyle(card).backgroundColor; 1056 1057 if (bgColor) { 1058 const rgb = bgColor.match(/\d+/g).map(Number); 1059 1060 // Conversione dei valori RGB in sRGB lineare 1061 const rescaled = rgb.map(v => {
1062 v /= 255; 1063 return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); 1064 }); 1065 1066 // Calcolo della luminanza (Standard WCAG) 1067 const luminance = 0.2126 * rescaled[0] + 0.7152 * rescaled[1] + 0.0722 * rescaled[2]; 1068 1069 // Gestione delle classi in base alla soglia 1070 if (luminance < 0.179) { 1071 card.classList.add('is-dark'); 1072 card.classList.remove('is-light'); 1073 } else { 1074 card.classList.add('is-light'); 1075 card.classList.remove('is-dark'); 1076 } 1077 } 1078}); 1079 1080 1081 1082 1083// luminanza background page club e player 1084 1085document.querySelectorAll('.luminance').forEach(card => { 1086 const bgColor = window.getComputedStyle(card).backgroundColor; 1087 1088 if (bgColor) { 1089 1090 const rgb = bgColor.match(/\d+/g).map(Number); 1091 1092 const rescaled = rgb.map(v => { 1093 v /= 255; 1094 return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); 1095 }); 1096 1097 const luminance = 0.2126 * rescaled[0] + 0.7152 * rescaled[1] + 0.0722 * rescaled[2]; 1098 1099 if (luminance < 0.179) { 1100 card.classList.add('is-dark'); 1101 } else { 1102 card.classList.remove('is-dark'); 1103 } 1104 } 1105}); 1106 1107document.addEventListener('DOMContentLoaded', () => { 1108 const tabButtons = document.querySelectorAll('button[data-bs-toggle="pill"]'); 1109 1110 tabButtons.forEach(button => { 1111 button.addEventListener('shown.bs.tab', (event) => { 1112 const targetId = event.target.getAttribute('data-bs-target'); 1113 const targetPane = document.querySelector(targetId); 1114 1115 if (targetPane) { 1116 // Troviamo il genitore specifico "classifiche-container" 1117 const container = targetPane.closest('.classifiche-container'); 1118 1119 // Se lo trova, scrolliamo verso il contenitore, altrimenti verso il pane 1120 const elementToScroll = container || targetPane; 1121 1122 const elementPosition = elementToScroll.getBoundingClientRect().top + window.scrollY; 1123 const offsetPosition = elementPosition - 140; 1124 1125 window.scrollTo({ 1126 top: offsetPosition, 1127 behavior: 'smooth' 1128 }); 1129 } 1130 }); 1131 }); 1132}); 1133 1134 1135document.addEventListener('DOMContentLoaded', function() { 1136 const dropdownItems = document.querySelectorAll('.dropdown-item'); 1137 const label = document.getElementById('active-tab-label'); 1138 1139 dropdownItems.forEach(item => { 1140 item.addEventListener('click', function(e) { 1141 e.preventDefault(); 1142 1143 // 1. Recupera l'ID della tab reale da attivare 1144 const targetId = this.getAttribute('data-target-tab'); 1145 const targetTab = document.getElementById(targetId); 1146 1147 if (targetTab) { 1148 // 2. Attiva la tab tramite l'istanza Bootstrap 1149 const tabInstance = new bootstrap.Tab(targetTab); 1150 tabInstance.show(); 1151 1152 // 3. Aggiorna l'interfaccia del dropdown 1153 label.innerText = this.innerText; 1154 1155 // Rimuove active da altri item e aggiunge a questo 1156 dropdownItems.forEach(el => el.classList.remove('active')); 1157 this.classList.add('active'); 1158 } 1159 }); 1160 }); 1161 1162 // Sincronizzazione inversa: se clicco su desktop, aggiorno il testo mobile 1163 const allTabs = document.querySelectorAll('[data-bs-toggle="pill"]'); 1164 allTabs.forEach(tab => { 1165 tab.addEventListener('shown.bs.tab', function (event) { 1166 label.innerText = event.target.innerText; 1167 }); 1168 }); 1169}); 1170 1171document.addEventListener( 'DOMContentLoaded', function () { 1172 // Seleziona tutti gli elementi con la classe .splide 1173 var elms = document.querySelectorAll( '.splide' ); 1174 1175 // Cicla su ogni elemento e inizializzalo 1176 elms.forEach( function( splideElement ) { 1177 try { 1178 splideElement.splide = new Splide( splideElement, 1179 { 1180 "type":"slide", 1181 "perPage":1, 1182 "gap":"1rem", 1183 "arrows":true, 1184 "pagination":true, 1185 "autoplay":false, 1186 "i18n": { 1187 "prev": "Slide precedente", 1188 "next": "Slide successiva", 1189 "first": "Vai alla prima slide", 1190 "last": "Vai all'ultima slide", 1191 "slideX": "Vai alla slide %s", 1192 "pageX": "Vai alla pagina %s", 1193 "play": "Avvia riproduzione automatica", 1194 "pause": "Sospendi riproduzione automatica", 1195 "carousel": "Galleria immagini", 1196 "select": "Seleziona una slide da visualizzare" 1197 } 1198 } 1199 ).mount(); 1200 } catch (error) {
1201 console.error('Errore durante l\'inizializzazione di Splide:', error); 1202 } 1203 } ); 1204} ); 1205 1206 1207// Scroll verso l'alto al drag calendario primavera 2 1208 1209window.addEventListener('load', function() { 1210 var el = document.querySelector('#p2-slider'); 1211 if (!el) return; 1212 1213el.querySelectorAll('.splide__arrow').forEach(function(btn) { 1214 btn.addEventListener('click', function() { 1215 var top = el.getBoundingClientRect().top + window.scrollY - 120; 1216 window.scrollTo({ top: top, behavior: 'smooth' }); 1217 }); 1218}); 1219 1220 var startX = 0; 1221 var startY = 0; 1222 1223 el.querySelector('.splide__track').addEventListener('touchstart', function(e) { 1224 startX = e.touches[0].clientX; 1225 startY = e.touches[0].clientY; 1226 }); 1227 1228 el.querySelector('.splide__track').addEventListener('touchend', function(e) { 1229 var diffX = Math.abs(e.changedTouches[0].clientX - startX); 1230 var diffY = Math.abs(e.changedTouches[0].clientY - startY); 1231 if (diffX > diffY && diffX > 60) { 1232setTimeout(function() { 1233 var top = el.getBoundingClientRect().top + window.scrollY - 120; 1234 window.scrollTo({ top: top, behavior: 'smooth' }); 1235}, 400); 1236 } 1237 }); 1238}); 1239 1240 1241// home calendar 1242 1243document.addEventListener('DOMContentLoaded', function() { 1244 const slider = document.querySelector('.calendar-carousel'); 1245 const nextBtn = document.getElementById('nextBtn'); 1246 const prevBtn = document.getElementById('prevBtn'); 1247 1248 if (slider) { 1249 let isDown = false; 1250 let startX; 1251 let scrollLeft; 1252 1253 function getScrollAmount() { 1254 const card = document.querySelector('.match-card'); 1255 const gap = 15; 1256 return card ? card.offsetWidth + gap : 300; 1257 } 1258 1259 if (nextBtn) { 1260 nextBtn.addEventListener('click', () => { 1261 slider.scrollBy({ left: getScrollAmount(), behavior: 'smooth' }); 1262 }); 1263 } 1264 1265 if (prevBtn) { 1266 prevBtn.addEventListener('click', () => { 1267 slider.scrollBy({ left: -getScrollAmount(), behavior: 'smooth' }); 1268 }); 1269 } 1270 1271 slider.addEventListener('mousedown', (e) => { 1272 isDown = true; 1273 slider.classList.add('active'); 1274 startX = e.pageX - slider.offsetLeft; 1275 scrollLeft = slider.scrollLeft; 1276 }); 1277 1278 window.addEventListener('mouseup', () => { 1279 isDown = false; 1280 slider.classList.remove('active'); 1281 }); 1282 1283 slider.addEventListener('mouseleave', () => { 1284 isDown = false; 1285 }); 1286 1287 slider.addEventListener('mousemove', (e) => { 1288 if (!isDown) return; 1289 e.preventDefault(); 1290 const x = e.pageX - slider.offsetLeft; 1291 const walk = (x - startX) * 1.2; 1292 slider.scrollLeft = scrollLeft - walk; 1293 }); 1294 1295 function updateArrowState() { 1296 const isAtStart = slider.scrollLeft <= 10; 1297 const isAtEnd = slider.scrollLeft + slider.offsetWidth >= slider.scrollWidth - 10; 1298 1299 if (prevBtn) { 1300 prevBtn.disabled = isAtStart; 1301 prevBtn.setAttribute('aria-disabled', isAtStart); 1302 } 1303 1304 if (nextBtn) { 1305 nextBtn.disabled = isAtEnd; 1306 nextBtn.setAttribute('aria-disabled', isAtEnd); 1307 } 1308 } 1309 1310 slider.addEventListener('scroll', updateArrowState); 1311 1312 updateArrowState(); 1313 } 1314 1315}); 1316 1317// news share 1318 1319document.addEventListener('DOMContentLoaded', function() { 1320 const shareBtn = document.getElementById('shareBtn'); 1321 if (shareBtn) { 1322 shareBtn.addEventListener('click', async () => { 1323 1324 const shareData = { 1325 title: shareBtn.getAttribute('data-title'), 1326 text: shareBtn.getAttribute('data-text'), 1327 url: window.location.href 1328 }; 1329 1330 if (navigator.share) { 1331 try { 1332 await navigator.share(shareData); 1333 } catch (err) { 1334 console.log('Errore:', err); 1335 } 1336 } else { 1337 alert("Copia l'URL: " + shareData.url); 1338 } 1339 });
1340 } 1341 1342}); 1343 1344 1345// tooltip 1346 1347const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]') 1348const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)) 1349 1350 1351 1352// navscroll 1353document.addEventListener('DOMContentLoaded', function () { 1354 const targetDesktop = '#navbar-partita'; 1355 const targetMobile = '#navbar-partita-mobile'; 1356 const EXTRA_OFFSET = 30; 1357 1358 const allLinks = document.querySelectorAll(`${targetDesktop} .nav-link, ${targetMobile} .nav-link`); 1359 1360 1361 const sections = []; 1362 allLinks.forEach(link => { 1363 const id = link.getAttribute('href'); 1364 if (id && id.startsWith('#')) { 1365 const section = document.querySelector(id); 1366 if (section) sections.push(section); 1367 } 1368 }); 1369 1370 1371 const observerOptions = { 1372 root: null, 1373 1374 rootMargin: '-100px 0px -70% 0px', 1375 threshold: 0 1376 }; 1377 1378 const observer = new IntersectionObserver((entries) => { 1379 entries.forEach(entry => { 1380 if (entry.isIntersecting) { 1381 const id = entry.target.getAttribute('id'); 1382 allLinks.forEach(link => { 1383 link.classList.toggle('active', link.getAttribute('href') === `#${id}`); 1384 }); 1385 } 1386 }); 1387 }, observerOptions); 1388 1389 sections.forEach(section => observer.observe(section)); 1390 1391 1392 allLinks.forEach(anchor => { 1393 anchor.addEventListener('click', function (e) { 1394 const targetId = this.getAttribute('href'); 1395 if (targetId && targetId.startsWith('#')) { 1396 e.preventDefault(); 1397 const targetElement = document.querySelector(targetId); 1398 1399 if (targetElement) { 1400 1401 const activeNav = document.querySelector('.onepage-navbar.sticky-top'); 1402 const navHeight = activeNav ? activeNav.offsetHeight : 0; 1403 const totalOffset = navHeight + EXTRA_OFFSET; 1404 1405 const elementPosition = targetElement.getBoundingClientRect().top; 1406 const offsetPosition = elementPosition + window.pageYOffset - totalOffset; 1407 1408 window.scrollTo({ 1409 top: offsetPosition, 1410 behavior: 'smooth' 1411 }); 1412 } 1413 } 1414 }); 1415 }); 1416}); 1417 1418// photoswipe add caption to image 1419document.addEventListener("DOMContentLoaded", function() { 1420 if (window.imageLightbox) { 1421 window.imageLightbox.on('uiRegister', function() { 1422 window.imageLightbox.pswp.ui.registerElement({ 1423 name: 'caption-content', 1424 order: 9, 1425 isButton: false, 1426 appendTo: 'root', 1427 html: 'Caption text', 1428 onInit: (el, pswp) => { 1429 window.imageLightbox.pswp.on('change', () => { 1430 const currSlideElement = window.imageLightbox.pswp.currSlide.data.element; 1431 let captionHTML = ''; 1432 if (currSlideElement) { 1433 const hiddenCaption = currSlideElement.querySelector('.hidden-caption-content'); 1434 console.log(currSlideElement); 1435 if (hiddenCaption) { 1436 // get caption from element with class hidden-caption-content 1437 captionHTML = hiddenCaption.innerHTML; 1438 } else { 1439 // get caption from alt attribute 1440 captionHTML = currSlideElement.querySelector('img').getAttribute('alt'); 1441 } 1442 } 1443 el.innerHTML = captionHTML || ''; 1444 }); 1445 } 1446 }); 1447 }); 1448 } 1449}); 1450 1451 1452document.addEventListener('DOMContentLoaded', function() { 1453 sal({ 1454 threshold: 0.1, 1455 once: true 1456 }); 1457}); 1458 1459 1460 1461 1462function calcolaMasonry() { 1463 const container = document.getElementById('masonryFooter'); 1464 if (!container) return; 1465 1466 const items = container.querySelectorAll('.masonry-item'); 1467 const containerWidth = container.offsetWidth; 1468 1469
1470 let columnsCount = 4; 1471 const gap = 24; 1472 1473 if (containerWidth < 576) { 1474 columnsCount = 2; 1475 } else if (containerWidth < 992) { 1476 columnsCount = 2; 1477 } else if (containerWidth < 1200) { 1478 columnsCount = 3; 1479 } 1480 1481 1482 const totalGapWidth = gap * (columnsCount - 1); 1483 const columnWidth = (containerWidth - totalGapWidth) / columnsCount; 1484 1485 1486 const columnHeights = Array(columnsCount).fill(0); 1487 1488 1489 items.forEach(item => { 1490 1491 item.style.width = `${columnWidth}px`; 1492 1493 1494 const minHeightIndex = columnHeights.indexOf(Math.min(...columnHeights)); 1495 1496 1497 const posX = minHeightIndex * (columnWidth + gap); 1498 const posY = columnHeights[minHeightIndex]; 1499 1500 1501 item.style.left = `${posX}px`; 1502 item.style.top = `${posY}px`; 1503 1504 1505 columnHeights[minHeightIndex] += item.offsetHeight + gap; 1506 }); 1507 1508 1509 container.style.height = `${Math.max(...columnHeights)}px`; 1510} 1511 1512 1513window.addEventListener('load', calcolaMasonry); 1514 1515window.addEventListener('resize', calcolaMasonry);
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.