1(function (window, document) { 2 'use strict'; 3 4 var script = document.currentScript; 5 var COOKIE_SCHEMA_VERSION = 2; 6 var DEFAULT_COOKIE_NAME = 'swimaholic_consent'; 7 var DEFAULT_POLICY_REVISION = '1'; 8 var DEFAULT_MAX_AGE_DAYS = 180; 9 var MAX_CLOCK_SKEW_MS = 5 * 60 * 1000; 10 var CONSENT_ACTIVATED_EVENT = 'swimaholic:consent-activated'; 11 var bannerId = 'swim-consent-banner'; 12 var preferencesId = 'swim-consent-preferences'; 13 var analyticsCheckboxId = 'consent-analytics'; 14 var targetingCheckboxId = 'consent-targeting'; 15 var activationQueue = []; 16 var consentListeners = []; 17 var activationRunning = false; 18 var preferencesOpen = false; 19 var pendingOpen = false; 20 var previousFocus = null; 21 var observer = null; 22 var pendingConsentEvent = null; 23 var lastAppliedGoogleConsent = null; 24 25 function scriptAttribute(name, fallbackName) { 26 if (!script || !script.getAttribute) { 27 return ''; 28 } 29 30 return script.getAttribute(name) || (fallbackName ? script.getAttribute(fallbackName) : '') || ''; 31 } 32 33 function validCookieName(value) { 34 return /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/.test(value); 35 } 36 37 function maxAgeDays(value) { 38 var days = parseInt(value, 10); 39 40 if (!isFinite(days) || days < 1 || days > 730) { 41 return DEFAULT_MAX_AGE_DAYS; 42 } 43 44 return days; 45 } 46 47 var configuredCookieName = scriptAttribute('data-consent-cookie-name', 'data-consent-cookie'); 48 var cookieName = validCookieName(configuredCookieName) ? configuredCookieName : DEFAULT_COOKIE_NAME; 49 var policyRevision = scriptAttribute('data-consent-policy-revision', 'data-consent-revision') || DEFAULT_POLICY_REVISION; 50 var configuredMaxAge = scriptAttribute('data-consent-max-age-days', 'data-consent-days'); 51 var statisticsEndpoint = scriptAttribute('data-consent-statistics-endpoint'); 52 var vendorInventoryFingerprint = scriptAttribute('data-consent-vendor-inventory-fingerprint'); 53 var vendorInventoryComplete = scriptAttribute('data-consent-vendor-inventory-complete') === 'true' && 54 /^[0-9a-f]{64}$/.test(vendorInventoryFingerprint); 55 var gtmConsentBridgeEnabled = scriptAttribute('data-consent-gtm-bridge-enabled') === 'true'; 56 var cookieMaxAgeDays = maxAgeDays(configuredMaxAge); 57 var cookieMaxAgeSeconds = cookieMaxAgeDays * 24 * 60 * 60; 58 var cookieMaxAgeMs = cookieMaxAgeSeconds * 1000; 59 60 function undecidedState() { 61 return { 62 v: COOKIE_SCHEMA_VERSION, 63 revision: policyRevision, 64 inventory: vendorInventoryFingerprint, 65 decisionId: null, 66 timestamp: null, 67 analytics: false, 68 targeting: false, 69 decided: false 70 }; 71 } 72 73 function trim(value) { 74 return String(value || '').replace(/^\s+|\s+$/g, ''); 75 } 76 77 function readCookie(name) { 78 var cookies = document.cookie ? document.cookie.split(';') : []; 79 var prefix = name + '='; 80 var i; 81 var item; 82 83 for (i = 0; i < cookies.length; i += 1) { 84 item = trim(cookies[i]); 85 if (item.indexOf(prefix) === 0) { 86 return item.substring(prefix.length); 87 } 88 } 89 90 return null; 91 } 92 93 function isStrictStoredState(value) { 94 var allowedKeys = { 95 v: true, 96 revision: true, 97 inventory: true, 98 decisionId: true, 99 timestamp: true, 100 analytics: true, 101 targeting: true 102 }; 103 var keys; 104 var now = new Date().getTime(); 105 var i; 106 107 if (!value || Object.prototype.toString.call(value) !== '[object Object]') { 108 return false; 109 } 110 111 keys = Object.keys(value); 112 if (keys.length !== 7) { 113 return false; 114 } 115 116 for (i = 0; i < keys.length; i += 1) { 117 if (!allowedKeys[keys[i]]) { 118 return false; 119 } 120 } 121 122 if (value.v !== COOKIE_SCHEMA_VERSION 123 || value.revision !== policyRevision 124 || value.inventory !== vendorInventoryFingerprint 125 ) { 126 return false; 127 } 128 129 if (typeof value.decisionId !== 'string' || !/^[0-9a-f]{32}$/.test(value.decisionId)) { 130 return false; 131 } 132 133 if (typeof value.timestamp !== 'number' || !isFinite(value.timestamp) || Math.floor(value.timestamp) !== value.timestamp) { 134 return false; 135 } 136 137 if (value.timestamp <= 0 || value.timestamp > now + MAX_CLOCK_SKEW_MS || now - value.timestamp > cookieMaxAgeMs) { 138 return false; 139 } 140 141 return typeof value.analytics === 'boolean' && typeof value.targeting === 'boolean'; 142 } 143 144 function createDecisionId() { 145 var bytes; 146 var result = ''; 147 var i; 148 149 if (window.crypto && typeof window.crypto.getRandomValues === 'function') { 150 bytes = new Uint8Array(16); 151 window.crypto.getRandomValues(bytes); 152 for (i = 0; i < bytes.length; i += 1) { 153 result += ('0' + bytes[i].toString(16)).slice(-2); 154 } 155 return result; 156 } 157 158 // The identifier is only a correlation value, never a secret or token. 159 while (result.length < 32) { 160 result += Math.floor(Math.random() * 0x100000000).toString(16); 161 } 162 return (result + new Date().getTime().toString(16)).slice(0, 32); 163 } 164 165 function expireHostCookie(name) { 166 document.cookie = name + '=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; SameSite=Lax'; 167 } 168 169 function restoreState() { 170 var encoded = readCookie(cookieName); 171 var parsed; 172 173 if (!vendorInventoryComplete) { 174 expireHostCookie(cookieName); 175 return undecidedState(); 176 } 177 178 if (encoded === null) { 179 return undecidedState(); 180 } 181 182 try { 183 parsed = JSON.parse(decodeURIComponent(encoded)); 184 } catch (error) { 185 expireHostCookie(cookieName); 186 return undecidedState(); 187 } 188 189 if (!isStrictStoredState(parsed)) { 190 expireHostCookie(cookieName); 191 return undecidedState(); 192 } 193 194 parsed.decided = true; 195 return parsed; 196 } 197 198 var consentState = restoreState(); 199 200 window.dataLayer = window.dataLayer && typeof window.dataLayer.push === 'function' ? window.dataLayer : []; 201 window.gtag = typeof window.gtag === 'function' ? window.gtag : function () { 202 window.dataLayer.push(arguments); 203 }; 204 205 function googleConsentPayload(state) { 206 return { 207 analytics_storage: state.analytics ? 'granted' : 'denied', 208 ad_storage: state.targeting ? 'granted' : 'denied', 209 ad_user_data: state.targeting ? 'granted' : 'denied', 210 ad_personalization: state.targeting ? 'granted' : 'denied', 211 functionality_storage: 'denied', 212 security_storage: 'granted', 213 personalization_storage: 'denied' 214 }; 215 } 216 217 function copyGoogleConsentPayload(payload) { 218 if (!payload) { 219 return null; 220 } 221 222 // Only fixed Consent Mode enums are exposed. This deliberately excludes 223 // decision identifiers, timestamps and any callback-provided values. 224 return { 225 analytics_storage: payload.analytics_storage === 'granted' ? 'granted' : 'denied', 226 ad_storage: payload.ad_storage === 'granted' ? 'granted' : 'denied', 227 ad_user_data: payload.ad_user_data === 'granted' ? 'granted' : 'denied', 228 ad_personalization: payload.ad_personalization === 'granted' ? 'granted' : 'denied', 229 functionality_storage: payload.functionality_storage === 'granted' ? 'granted' : 'denied', 230 security_storage: payload.security_storage === 'granted' ? 'granted' : 'denied', 231 personalization_storage: payload.personalization_storage === 'granted' ? 'granted' : 'denied' 232 }; 233 } 234 235 function publicBridgeState() { 236 return { 237 enabled: gtmConsentBridgeEnabled, 238 listenerRegistered: consentListeners.length === 1, 239 pendingEvent: pendingConsentEvent !== null, 240 lastApplied: copyGoogleConsentPayload(lastAppliedGoogleConsent) 241 }; 242 } 243 244 function applyGoogleConsent(state) { 245 // When audited GTM is active, its Consent Initialization custom template 246 // owns updateConsentState() through the synchronous listener below. 247 // Page-level gtag updates are deliberately not used as a substitute because 248 // GTM can process them after the next event. 249 if (gtmConsentBridgeEnabled) { 250 return notifyConsentListeners(state); 251 } 252 window.gtag('consent', 'update', googleConsentPayload(state)); 253 return true; 254 } 255 256 function notifyConsentListeners(state) { 257 var payload = googleConsentPayload(state); 258 259 if (consentListeners.length !== 1) { 260 return false;
261 } 262 try { 263 consentListeners[0](payload); 264 lastAppliedGoogleConsent = copyGoogleConsentPayload(payload); 265 return true; 266 } catch (error) { 267 // Suppress the following measurement event when GTM could not apply the 268 // new state. This keeps grants and especially withdrawals fail-closed. 269 return false; 270 } 271 } 272 273 function addConsentListener(callback) { 274 if (typeof callback !== 'function' || consentListeners.length !== 0) { 275 return false; 276 } 277 consentListeners.push(callback); 278 if (consentState.decided) { 279 try { 280 var payload = googleConsentPayload(consentState); 281 callback(payload); 282 lastAppliedGoogleConsent = copyGoogleConsentPayload(payload); 283 if (pendingConsentEvent) { 284 var pending = pendingConsentEvent; 285 pendingConsentEvent = null; 286 emitPendingConsentEvent(pending); 287 } 288 } catch (error) { 289 consentListeners.pop(); 290 return false; 291 } 292 } 293 return true; 294 } 295 296 function emitConsentEvents(state, source, previous) { 297 var retainedTransitions = arguments.length > 3 && arguments[3] ? arguments[3] : {}; 298 var analyticsGrantSource = state.analytics 299 ? (retainedTransitions.analytics || ((!previous || !previous.analytics) ? source : null)) 300 : null; 301 var targetingGrantSource = state.targeting 302 ? (retainedTransitions.targeting || ((!previous || !previous.targeting) ? source : null)) 303 : null; 304 window.dataLayer.push({ 305 event: 'swimaholic_consent_update', 306 consent_source: source, 307 consent_revision: policyRevision, 308 consent_analytics: state.analytics ? 'granted' : 'denied', 309 consent_targeting: state.targeting ? 'granted' : 'denied' 310 }); 311 312 if (analyticsGrantSource) { 313 window.dataLayer.push({ 314 event: 'CookieScriptCategory-performance', 315 consent_source: analyticsGrantSource 316 }); 317 } 318 319 if (targetingGrantSource) { 320 window.dataLayer.push({ 321 event: 'CookieScriptCategory-targeting', 322 consent_source: targetingGrantSource 323 }); 324 } 325 } 326 327 function consentEventFor(state, source, previous, retained) { 328 retained = retained || {}; 329 var retainedTransitions = retained.transitions || {}; 330 return { 331 state: state, 332 source: source, 333 previous: previous, 334 transitions: { 335 analytics: state.analytics 336 ? (retainedTransitions.analytics || ((!previous || !previous.analytics) ? source : null)) 337 : null, 338 targeting: state.targeting 339 ? (retainedTransitions.targeting || ((!previous || !previous.targeting) ? source : null)) 340 : null 341 } 342 }; 343 } 344 345 function emitPendingConsentEvent(pending) { 346 emitConsentEvents(pending.state, pending.source, pending.previous, pending.transitions); 347 } 348 349 if (consentState.decided) { 350 var restoredConsentEvent = consentEventFor(consentState, 'restore', null, null); 351 if (applyGoogleConsent(consentState)) { 352 emitPendingConsentEvent(restoredConsentEvent); 353 } else { 354 pendingConsentEvent = restoredConsentEvent; 355 } 356 } 357 358 function publicState() { 359 return { 360 v: consentState.v, 361 revision: consentState.revision, 362 inventory: consentState.inventory, 363 decisionId: consentState.decisionId, 364 timestamp: consentState.timestamp, 365 analytics: consentState.analytics, 366 targeting: consentState.targeting, 367 decided: consentState.decided 368 }; 369 } 370 371 function hasConsent(category) { 372 category = String(category || '').toLowerCase(); 373 374 if (category === 'necessary' || category === 'essential' || category === 'security') { 375 return true; 376 } 377 378 // There is no functionality preference in this CMP revision. It must 379 // remain fail-closed until one is exposed to the visitor and persisted. 380 if (category === 'functionality') { 381 return false; 382 } 383 384 if (!consentState.decided) { 385 return false; 386 } 387 388 if (category === 'analytics' || category === 'performance') { 389 return consentState.analytics; 390 } 391 392 if (category === 'targeting' || category === 'marketing' || category === 'advertising') { 393 return consentState.targeting; 394 } 395 396 return false; 397 } 398 399 function normalizeScriptCategory(node) { 400 var category = node.getAttribute('data-consent-category') || 401 node.getAttribute('data-measuring-script-category') || 402 node.getAttribute('data-cookiecategory') || ''; 403 404 category = String(category).toLowerCase(); 405 if (category === 'analytics' || category === 'performance') { 406 return 'analytics'; 407 } 408 409 if (category === 'targeting' || category === 'marketing' || category === 'advertising') { 410 return 'targeting'; 411 } 412 413 return ''; 414 } 415 416 function isInertConsentScript(node) { 417 var category; 418 419 if (!node || String(node.tagName || '').toLowerCase() !== 'script') { 420 return false;
421 } 422 423 category = normalizeScriptCategory(node); 424 return category !== '' && String(node.getAttribute('type') || '').toLowerCase() === 'text/plain'; 425 } 426 427 function matchesSelector(node, selector) { 428 var matches = node && (node.matches || node.matchesSelector || node.msMatchesSelector || node.webkitMatchesSelector); 429 430 return matches ? matches.call(node, selector) : false; 431 } 432 433 function enqueueScript(node) { 434 var category; 435 436 if (!isInertConsentScript(node) || node.getAttribute('data-consent-activated') === 'true' || node.getAttribute('data-consent-queued') === 'true') { 437 return; 438 } 439 440 category = normalizeScriptCategory(node); 441 if (!hasConsent(category)) { 442 return; 443 } 444 445 node.setAttribute('data-consent-queued', 'true'); 446 activationQueue.push(node); 447 } 448 449 function enqueueScripts(root) { 450 var selector = 'script[type="text/plain"][data-consent-category], ' + 451 'script[type="text/plain"][data-measuring-script-category], ' + 452 'script[type="text/plain"][data-cookiecategory]'; 453 var nodes; 454 var i; 455 456 if (!root) { 457 return; 458 } 459 460 if (matchesSelector(root, selector)) { 461 enqueueScript(root); 462 } 463 464 if (!root.querySelectorAll) { 465 return; 466 } 467 468 nodes = root.querySelectorAll(selector); 469 for (i = 0; i < nodes.length; i += 1) { 470 enqueueScript(nodes[i]); 471 } 472 } 473 474 function copyScriptAttributes(source, target) { 475 var ignored = { 476 type: true, 477 async: true, 478 defer: true, 479 'data-consent-category': true, 480 'data-measuring-script-category': true, 481 'data-cookiecategory': true, 482 'data-cookiescript': true, 483 'data-consent-activated': true, 484 'data-consent-queued': true 485 }; 486 var attributes = source.attributes || []; 487 var i; 488 var attribute; 489 490 for (i = 0; i < attributes.length; i += 1) { 491 attribute = attributes[i]; 492 if (!ignored[String(attribute.name).toLowerCase()]) { 493 target.setAttribute(attribute.name, attribute.value); 494 } 495 } 496 } 497 498 function processActivationQueue() { 499 var source; 500 var category; 501 var active; 502 var nonBlocking; 503 var finish; 504 505 if (activationRunning || activationQueue.length === 0) { 506 return; 507 } 508 509 source = activationQueue.shift(); 510 category = normalizeScriptCategory(source); 511 if (!source.parentNode || !hasConsent(category)) { 512 source.removeAttribute('data-consent-queued'); 513 processActivationQueue(); 514 return; 515 } 516 517 activationRunning = true; 518 source.setAttribute('data-consent-activated', 'true'); 519 source.removeAttribute('data-consent-queued'); 520 active = document.createElement('script'); 521 copyScriptAttributes(source, active); 522 nonBlocking = source.hasAttribute('async') || source.hasAttribute('defer'); 523 if (source.hasAttribute('async')) { 524 active.async = true; 525 } else { 526 active.async = false; 527 } 528 529 if (source.hasAttribute('defer')) { 530 active.defer = true; 531 } 532 533 finish = function () { 534 active.onload = null; 535 active.onerror = null; 536 activationRunning = false; 537 processActivationQueue(); 538 }; 539 540 if (source.getAttribute('src')) { 541 if (!nonBlocking) { 542 active.onload = finish; 543 active.onerror = finish; 544 } 545 source.parentNode.insertBefore(active, source); 546 if (nonBlocking) { 547 finish(); 548 } 549 return; 550 } 551 552 active.text = source.text || source.textContent || source.innerHTML || ''; 553 source.parentNode.insertBefore(active, source); 554 finish(); 555 } 556 557 function activateAllowedScripts(root) { 558 enqueueScripts(root || document); 559 processActivationQueue(); 560 } 561 562 function observeInertScripts() { 563 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver; 564 565 if (!MutationObserver || !document.documentElement || observer) { 566 return; 567 } 568 569 observer = new MutationObserver(function (mutations) { 570 var i; 571 var j; 572 573 for (i = 0; i < mutations.length; i += 1) { 574 for (j = 0; j < mutations[i].addedNodes.length; j += 1) { 575 enqueueScripts(mutations[i].addedNodes[j]); 576 } 577 } 578 579 processActivationQueue(); 580 }); 581 582 observer.observe(document.documentElement, {childList: true, subtree: true}); 583 } 584 585 function secureCookieSuffix() { 586 return window.location && window.location.protocol === 'https:' ? '; Secure' : ''; 587 } 588 589 function persistState(state) { 590 var stored = { 591 v: COOKIE_SCHEMA_VERSION, 592 revision: policyRevision, 593 inventory: vendorInventoryFingerprint, 594 decisionId: state.decisionId,
595 timestamp: state.timestamp, 596 analytics: state.analytics, 597 targeting: state.targeting 598 }; 599 var expires = new Date(state.timestamp + cookieMaxAgeMs).toUTCString(); 600 601 document.cookie = cookieName + '=' + encodeURIComponent(JSON.stringify(stored)) + 602 '; Max-Age=' + cookieMaxAgeSeconds + '; Expires=' + expires + '; Path=/; SameSite=Lax' + secureCookieSuffix(); 603 } 604 605 function normalizeDecisionAction(action, state) { 606 action = String(action || 'custom').toLowerCase(); 607 if (action === 'accept_all' && state.analytics && state.targeting) { 608 return action; 609 } 610 if (action === 'reject_all' && !state.analytics && !state.targeting) { 611 return action; 612 } 613 return 'custom'; 614 } 615 616 function reportDecision(state, phase, action) { 617 var body; 618 var request; 619 620 if (!statisticsEndpoint || !state || !state.decided) { 621 return; 622 } 623 624 body = 'phase=' + encodeURIComponent(phase) + '&action=' + encodeURIComponent(action); 625 try { 626 if (window.navigator && typeof window.navigator.sendBeacon === 'function' && 627 window.navigator.sendBeacon(statisticsEndpoint, body) 628 ) { 629 return; 630 } 631 } catch (beaconError) { 632 // Reporting is best-effort and must never affect the consent choice. 633 } 634 635 if (typeof window.fetch === 'function') { 636 try { 637 window.fetch(statisticsEndpoint, { 638 method: 'POST', 639 headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}, 640 body: body, 641 credentials: 'same-origin', 642 keepalive: true 643 })['catch'](function () {}); 644 return; 645 } catch (fetchError) { 646 // Older browsers can expose fetch without supporting keepalive. 647 } 648 } 649 650 if (typeof window.XMLHttpRequest === 'function') { 651 try { 652 request = new window.XMLHttpRequest(); 653 request.open('POST', statisticsEndpoint, true); 654 request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8'); 655 request.send(body); 656 } catch (requestError) { 657 // A statistics outage must stay invisible to the visitor. 658 } 659 } 660 } 661 662 function expireCookieEverywhere(name) { 663 var hostname = window.location && window.location.hostname ? String(window.location.hostname) : ''; 664 var labels = hostname.split('.'); 665 var base = name + '=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; SameSite=Lax' + secureCookieSuffix(); 666 var i; 667 668 document.cookie = base; 669 if (!hostname || /^\d{1,3}(\.\d{1,3}){3}$/.test(hostname) || hostname.indexOf(':') !== -1) { 670 return; 671 } 672 673 for (i = 0; i <= labels.length - 2; i += 1) { 674 document.cookie = base + '; Domain=.' + labels.slice(i).join('.'); 675 } 676 } 677 678 function cookieNames() { 679 var cookies = document.cookie ? document.cookie.split(';') : []; 680 var result = []; 681 var i; 682 var separator; 683 var name; 684 685 for (i = 0; i < cookies.length; i += 1) { 686 separator = cookies[i].indexOf('='); 687 name = trim(separator === -1 ? cookies[i] : cookies[i].substring(0, separator)); 688 if (name) { 689 result.push(name); 690 } 691 } 692 693 return result; 694 } 695 696 function clearCookies(patterns) { 697 var names = cookieNames(); 698 var i; 699 var j; 700 701 for (i = 0; i < names.length; i += 1) { 702 for (j = 0; j < patterns.length; j += 1) { 703 if (patterns[j].test(names[i])) { 704 expireCookieEverywhere(names[i]); 705 break; 706 } 707 } 708 } 709 } 710 711 function removeStorageItem(storageName, key) { 712 try { 713 if (window[storageName]) { 714 window[storageName].removeItem(key); 715 } 716 } catch (error) { 717 // Storage can be unavailable in private mode or when the browser blocks it. 718 } 719 } 720 721 function removeStorageKeysMatching(storageName, patterns) { 722 var storage; 723 var keys = []; 724 var i; 725 var j; 726 var key; 727 728 try { 729 storage = window[storageName]; 730 if (!storage || typeof storage.key !== 'function') { 731 return; 732 } 733 734 for (i = 0; i < storage.length; i += 1) { 735 key = storage.key(i); 736 for (j = 0; key !== null && j < patterns.length; j += 1) { 737 if (patterns[j].test(key)) { 738 keys.push(key); 739 break; 740 } 741 } 742 } 743 744 for (i = 0; i < keys.length; i += 1) { 745 storage.removeItem(keys[i]); 746 } 747 } catch (error) { 748 // Storage enumeration can be blocked independently of cookie access. 749 } 750 } 751 752 function clearRevokedData(revokedAnalytics, revokedTargeting) { 753 if (revokedAnalytics) { 754 clearCookies([/^_ga(?:_|$)/, /^_gid$/, /^_gat(?:_|$)/]); 755 } 756 757 if (revokedTargeting) { 758 clearCookies([ 759 /^_gcl_/, 760 /^_fbp$/,
761 /^_fbc$/, 762 /^_ttp$/, 763 /^_tt_enable_cookie$/, 764 /^ttcsid(?:_|$)/, 765 /^_sp_(?:id|ses)(?:\.|$)/, 766 /^gp_(?:e|s)$/, 767 /^__oppref$/, 768 /^__obref$/ 769 ]); 770 removeStorageItem('localStorage', 'swimaholic.ecomail.sportex.basket.v1'); 771 removeStorageItem('sessionStorage', 'swimaholic.ecomail.sportex.basket.v1'); 772 removeStorageItem('localStorage', 'swimaholic_ecomail_popup_59_hidden_until'); 773 removeStorageKeysMatching('localStorage', [/^snowplowOutQueue_(?:[A-Za-z0-9]+_)*cf_/]); 774 removeStorageKeysMatching('sessionStorage', [/^snowplowOutQueue_(?:[A-Za-z0-9]+_)*cf_/]); 775 } 776 } 777 778 function clearUnsupportedOptionalData() { 779 clearCookies([ 780 /^recent_products(?:\[\d+\]|%5b\d+%5d)$/i, 781 /^news-read$/, 782 /^refId$/i, 783 /^last_basket$/, 784 /^hideBasket$/ 785 ]); 786 } 787 788 // Fresh, expired-policy and malformed decisions all restore as denied. Purge 789 // known vendor state immediately so an old identifier cannot outlive the 790 // decision that previously allowed it. 791 clearRevokedData(!consentState.analytics, !consentState.targeting); 792 clearUnsupportedOptionalData(); 793 794 function element(id) { 795 return document.getElementById(id); 796 } 797 798 function setHidden(node, hidden) { 799 if (!node) { 800 return; 801 } 802 803 if (hidden) { 804 node.setAttribute('hidden', 'hidden'); 805 node.setAttribute('aria-hidden', 'true'); 806 } else { 807 node.removeAttribute('hidden'); 808 node.setAttribute('aria-hidden', 'false'); 809 } 810 } 811 812 function setModalBodyState(open) { 813 var body = document.body; 814 815 if (!body) { 816 return; 817 } 818 819 if (body.classList) { 820 body.classList[open ? 'add' : 'remove']('swim-consent-modal-open'); 821 return; 822 } 823 824 if (open && body.className.indexOf('swim-consent-modal-open') === -1) { 825 body.className += ' swim-consent-modal-open'; 826 } else if (!open) { 827 body.className = trim(body.className.replace(/(?:^|\s)swim-consent-modal-open(?:\s|$)/g, ' ')); 828 } 829 } 830 831 function syncPreferenceInputs() { 832 var analytics = element(analyticsCheckboxId); 833 var targeting = element(targetingCheckboxId); 834 835 if (analytics) { 836 analytics.checked = consentState.decided && consentState.analytics; 837 } 838 839 if (targeting) { 840 targeting.checked = consentState.decided && consentState.targeting; 841 } 842 } 843 844 function focusableElements(container) { 845 var nodes; 846 var result = []; 847 var i; 848 849 if (!container || !container.querySelectorAll) { 850 return result; 851 } 852 853 nodes = container.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'); 854 for (i = 0; i < nodes.length; i += 1) { 855 if (nodes[i].getAttribute('aria-hidden') !== 'true' && !nodes[i].hasAttribute('hidden')) { 856 result.push(nodes[i]); 857 } 858 } 859 860 return result; 861 } 862 863 function focusPreferences() { 864 var preferences = element(preferencesId); 865 var autofocus = preferences && preferences.querySelector('[data-consent-initial-focus]'); 866 var focusable = focusableElements(preferences); 867 var target = autofocus || focusable[0]; 868 869 if (target && target.focus) { 870 target.focus(); 871 } 872 } 873 874 function openPreferences(trigger) { 875 var preferences = element(preferencesId); 876 var dialog; 877 878 if (!preferences || !document.body) { 879 pendingOpen = true; 880 return; 881 } 882 883 pendingOpen = false; 884 previousFocus = trigger && trigger.focus ? trigger : document.activeElement; 885 syncPreferenceInputs(); 886 setHidden(element(bannerId), true); 887 setHidden(preferences, false); 888 dialog = preferences.querySelector('[role="dialog"]'); 889 if (!dialog) { 890 preferences.setAttribute('role', 'dialog'); 891 preferences.setAttribute('aria-modal', 'true'); 892 } 893 preferencesOpen = true; 894 setModalBodyState(true); 895 window.setTimeout(focusPreferences, 0); 896 } 897 898 function closePreferences() { 899 var focusTarget = previousFocus; 900 901 setHidden(element(preferencesId), true); 902 preferencesOpen = false; 903 setModalBodyState(false); 904 if (!consentState.decided) { 905 setHidden(element(bannerId), false); 906 } 907 908 previousFocus = null; 909 if (focusTarget && document.documentElement.contains(focusTarget) && focusTarget.focus) { 910 focusTarget.focus(); 911 } 912 } 913 914 function updateUiAfterDecision() { 915 var focusTarget = preferencesOpen ? previousFocus : null; 916 917 setHidden(element(bannerId), true); 918 setHidden(element(preferencesId), true); 919 preferencesOpen = false;
920 setModalBodyState(false); 921 previousFocus = null; 922 if (focusTarget && document.documentElement.contains(focusTarget) && focusTarget.focus) { 923 focusTarget.focus(); 924 } 925 } 926 927 function dismissBanner() { 928 setHidden(element(bannerId), true); 929 } 930 931 function dispatchConsentActivatedEvent() { 932 if (!document.createEvent || !document.dispatchEvent) { 933 return; 934 } 935 936 // This wake-up signal deliberately carries no consent values, identity or 937 // other detail. Consumers must read only their own allowed category state. 938 var event = document.createEvent('Event'); 939 event.initEvent(CONSENT_ACTIVATED_EVENT, false, false); 940 document.dispatchEvent(event); 941 } 942 943 function saveConsent(choice, action) { 944 var previous = consentState; 945 var phase = previous.decided ? 'change' : 'initial'; 946 // A DB/vendor inventory error is a real runtime release gate. Never 947 // activate optional integrations from a stale decision while the current 948 // page cannot prove every active recipient. 949 if (!vendorInventoryComplete) { 950 choice = {analytics: false, targeting: false}; 951 } 952 var next = { 953 v: COOKIE_SCHEMA_VERSION, 954 revision: policyRevision, 955 inventory: vendorInventoryFingerprint, 956 decisionId: createDecisionId(), 957 timestamp: new Date().getTime(), 958 analytics: choice && choice.analytics === true, 959 targeting: choice && choice.targeting === true, 960 decided: true 961 }; 962 var revokedAnalytics = previous.decided && previous.analytics && !next.analytics; 963 var revokedTargeting = previous.decided && previous.targeting && !next.targeting; 964 965 consentState = next; 966 persistState(next); 967 action = normalizeDecisionAction(action, next); 968 if (vendorInventoryComplete) { 969 reportDecision(next, phase, action); 970 } 971 var retainedConsentEvent = pendingConsentEvent; 972 var currentConsentEvent = consentEventFor( 973 next, 974 previous.decided ? 'change' : 'choice', 975 previous.decided ? previous : null, 976 retainedConsentEvent 977 ); 978 var googleConsentApplied = applyGoogleConsent(next); 979 if (googleConsentApplied) { 980 pendingConsentEvent = null; 981 emitPendingConsentEvent(currentConsentEvent); 982 } else { 983 pendingConsentEvent = currentConsentEvent; 984 } 985 expireCookieEverywhere('CookieScriptConsent'); 986 clearUnsupportedOptionalData(); 987 updateUiAfterDecision(); 988 clearRevokedData(!next.analytics, !next.targeting); 989 990 if (revokedAnalytics || revokedTargeting) { 991 window.setTimeout(function () { 992 window.location.reload(); 993 }, googleConsentApplied ? 100 : 0); 994 return publicState(); 995 } 996 997 activateAllowedScripts(document); 998 dispatchConsentActivatedEvent(); 999 return publicState(); 1000 } 1001 1002 function readPreferencesAndSave() { 1003 var analytics = element(analyticsCheckboxId); 1004 var targeting = element(targetingCheckboxId); 1005 1006 return saveConsent({ 1007 analytics: !!(analytics && analytics.checked), 1008 targeting: !!(targeting && targeting.checked) 1009 }, 'custom'); 1010 } 1011 1012 function actionElement(target) { 1013 while (target && target !== document) { 1014 if (target.getAttribute && (target.getAttribute('data-consent-action') || target.hasAttribute('data-consent-open'))) { 1015 return target; 1016 } 1017 target = target.parentNode; 1018 } 1019 1020 return null; 1021 } 1022 1023 function handleClick(event) { 1024 var target = actionElement(event.target || event.srcElement); 1025 var action; 1026 1027 if (!target) { 1028 return; 1029 } 1030 1031 action = target.getAttribute('data-consent-action') || (target.hasAttribute('data-consent-open') ? 'open' : ''); 1032 if (action === 'settings') { 1033 action = 'open'; 1034 } 1035 1036 if (action !== 'accept-all' && action !== 'reject-all' && action !== 'open' && action !== 'save' && action !== 'close' && action !== 'dismiss') { 1037 return; 1038 } 1039 1040 if (event.preventDefault) { 1041 event.preventDefault(); 1042 } else { 1043 event.returnValue = false; 1044 } 1045 1046 if (action === 'accept-all') { 1047 saveConsent({analytics: true, targeting: true}, 'accept_all'); 1048 } else if (action === 'reject-all') { 1049 saveConsent({analytics: false, targeting: false}, 'reject_all'); 1050 } else if (action === 'open') { 1051 openPreferences(target); 1052 } else if (action === 'save') { 1053 readPreferencesAndSave(); 1054 } else if (action === 'dismiss') { 1055 dismissBanner(); 1056 } else { 1057 closePreferences(); 1058 } 1059 } 1060 1061 function handleSubmit(event) { 1062 var form = event.target || event.srcElement; 1063 var preferences = element(preferencesId); 1064 1065 if (!preferences || !form || (form !== preferences && !preferences.contains(form))) { 1066 return; 1067 } 1068 1069 if (event.preventDefault) { 1070 event.preventDefault(); 1071 } else { 1072 event.returnValue = false;
1073 } 1074 1075 readPreferencesAndSave(); 1076 } 1077 1078 function handleModalKeyboard(event) { 1079 var key = event.key || event.keyCode; 1080 var preferences; 1081 var focusable; 1082 var first; 1083 var last; 1084 1085 if (!preferencesOpen) { 1086 return; 1087 } 1088 1089 if (key === 'Escape' || key === 'Esc' || key === 27) { 1090 if (event.preventDefault) { 1091 event.preventDefault(); 1092 } 1093 closePreferences(); 1094 return; 1095 } 1096 1097 if (key !== 'Tab' && key !== 9) { 1098 return; 1099 } 1100 1101 preferences = element(preferencesId); 1102 focusable = focusableElements(preferences); 1103 if (focusable.length === 0) { 1104 if (event.preventDefault) { 1105 event.preventDefault(); 1106 } 1107 return; 1108 } 1109 1110 first = focusable[0]; 1111 last = focusable[focusable.length - 1]; 1112 if (event.shiftKey && document.activeElement === first) { 1113 event.preventDefault(); 1114 last.focus(); 1115 } else if (!event.shiftKey && document.activeElement === last) { 1116 event.preventDefault(); 1117 first.focus(); 1118 } 1119 } 1120 1121 function initializeUi() { 1122 var banner = element(bannerId); 1123 var preferences = element(preferencesId); 1124 var dialog = preferences && preferences.querySelector('[role="dialog"]'); 1125 1126 if (banner) { 1127 banner.setAttribute('role', 'dialog'); 1128 banner.setAttribute('aria-modal', 'false'); 1129 } 1130 1131 if (preferences && !dialog) { 1132 preferences.setAttribute('role', 'dialog'); 1133 preferences.setAttribute('aria-modal', 'true'); 1134 } 1135 1136 setHidden(preferences, true); 1137 setHidden(banner, consentState.decided); 1138 syncPreferenceInputs(); 1139 1140 if (pendingOpen) { 1141 openPreferences(); 1142 } 1143 1144 activateAllowedScripts(document); 1145 } 1146 1147 window.SwimaholicConsent = { 1148 has: hasConsent, 1149 state: publicState, 1150 googleState: function () { 1151 return googleConsentPayload(consentState); 1152 }, 1153 bridgeState: publicBridgeState, 1154 addListener: addConsentListener, 1155 open: function () { 1156 openPreferences(); 1157 }, 1158 save: saveConsent 1159 }; 1160 1161 document.addEventListener('click', handleClick, false); 1162 document.addEventListener('submit', handleSubmit, false); 1163 document.addEventListener('keydown', handleModalKeyboard, false); 1164 observeInertScripts(); 1165 1166 if (document.readyState === 'loading') { 1167 document.addEventListener('DOMContentLoaded', initializeUi, false); 1168 } else { 1169 initializeUi(); 1170 } 1171}(window, document));
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.