1(function () { 2 'use strict'; 3 4 // Bump this after any policy change to force every visitor to be re-prompted. 5 var CONSENT_VERSION = 1; 6 7 var config = window.ColossalConsentConfig || {}; 8 var COOKIE_NAME = config.cookieName || 'site_consent'; 9 var COOKIE_DAYS = config.cookieDays || 365; 10 var CATEGORIES = config.categories || ['necessary', 'analytics', 'marketing', 'preferences']; 11 12 // Domains that must never load a <script src="..."> unless their category is granted. 13 // Safety net only â the primary mechanism is the consent:<category> comment blocks below. 14 var TRACKER_DOMAIN_CATEGORY = { 15 'googletagmanager.com': 'analytics', 16 'google-analytics.com': 'analytics', 17 'connect.facebook.net': 'marketing', 18 'facebook.net': 'marketing' 19 }; 20 21 function setCookie(name, value, days) { 22 var expires = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toUTCString(); 23 document.cookie = name + '=' + encodeURIComponent(value) + '; expires=' + expires + '; path=/; SameSite=Lax'; 24 } 25 26 function getCookie(name) { 27 var escaped = name.replace(/([.$?*|{}()\[\]\\\/+^])/g, '\\$1'); 28 var match = document.cookie.match(new RegExp('(?:^|; )' + escaped + '=([^;]*)')); 29 return match ? decodeURIComponent(match[1]) : null; 30 } 31 32 function readConsent() { 33 var raw = getCookie(COOKIE_NAME); 34 if (!raw) return null; 35 var parsed; 36 try { 37 parsed = JSON.parse(raw); 38 } catch (e) { 39 return null; 40 } 41 if (!parsed || parsed.version !== CONSENT_VERSION) return null; 42 return parsed; 43 } 44 45 function writeConsent(categories) { 46 var consent = { 47 necessary: true, 48 analytics: !!categories.analytics, 49 marketing: !!categories.marketing, 50 preferences: !!categories.preferences, 51 version: CONSENT_VERSION, 52 timestamp: Date.now() 53 }; 54 setCookie(COOKIE_NAME, JSON.stringify(consent), COOKIE_DAYS); 55 return consent; 56 } 57 58 function rebuildScripts(container) { 59 var scripts = container.querySelectorAll('script'); 60 scripts.forEach(function (oldScript) { 61 var newScript = document.createElement('script'); 62 for (var i = 0; i < oldScript.attributes.length; i++) { 63 var attr = oldScript.attributes[i]; 64 newScript.setAttribute(attr.name, attr.value); 65 } 66 newScript.textContent = oldScript.textContent; 67 newScript.setAttribute('data-consent-injected', 'true'); 68 oldScript.parentNode.replaceChild(newScript, oldScript); 69 }); 70 } 71 72 function findConsentComments() { 73 var walker = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_COMMENT, null); 74 var comments = []; 75 var node; 76 while ((node = walker.nextNode())) { 77 comments.push(node); 78 } 79 return comments; 80 } 81 82 function activateConsentedTrackers(consent) { 83 findConsentComments().forEach(function (comment) { 84 var match = /^\s*consent:([a-z0-9_-]+)/i.exec(comment.nodeValue); 85 if (!match) return; 86 87 var category = match[1].toLowerCase(); 88 if (CATEGORIES.indexOf(category) === -1 || !consent[category]) return; 89 90 var html = comment.nodeValue.slice(match[0].length); 91 var container = document.createElement('div'); 92 container.innerHTML = html; 93 rebuildScripts(container); 94 95 var parent = comment.parentNode; 96 if (!parent) return; 97 98 Array.prototype.slice.call(container.childNodes).forEach(function (node) { 99 parent.insertBefore(node, comment); 100 }); 101 parent.removeChild(comment); 102 }); 103 } 104 105 function watchForRogueTrackers(consent) { 106 if (!('MutationObserver' in window)) return; 107 108 var blockedDomains = Object.keys(TRACKER_DOMAIN_CATEGORY); 109 110 var checkNode = function (node) { 111 if (!node || node.nodeType !== 1 || node.tagName !== 'SCRIPT') return; 112 if (node.getAttribute('data-consent-injected') === 'true') return; 113 114 var src = node.getAttribute('src') || ''; 115 for (var i = 0; i < blockedDomains.length; i++) { 116 var domain = blockedDomains[i]; 117 if (src.indexOf(domain) === -1) continue; 118 119 var category = TRACKER_DOMAIN_CATEGORY[domain]; 120 if (!consent[category]) { 121 node.parentNode && node.parentNode.removeChild(node); 122 } 123 return; 124 } 125 }; 126 127 var observer = new MutationObserver(function (mutations) { 128 mutations.forEach(function (mutation) {
129 mutation.addedNodes.forEach(checkNode); 130 }); 131 }); 132 133 observer.observe(document.head, { childList: true, subtree: true }); 134 observer.observe(document.body, { childList: true, subtree: true }); 135 } 136 137 function initBanner(onAction) { 138 var banner = document.getElementById('consent-banner'); 139 if (!banner) return null; 140 141 var panel = document.getElementById('consent-panel'); 142 var customizeBtn = banner.querySelector('[data-consent-action="customize"]'); 143 144 function show() { 145 banner.hidden = false; 146 requestAnimationFrame(function () { 147 requestAnimationFrame(function () { 148 banner.classList.add('is-visible'); 149 }); 150 }); 151 } 152 153 function hide() { 154 var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; 155 banner.classList.remove('is-visible'); 156 157 if (reduceMotion) { 158 banner.hidden = true; 159 return; 160 } 161 162 var onEnd = function (event) { 163 if (event.target !== banner || event.propertyName !== 'transform') return; 164 banner.removeEventListener('transitionend', onEnd); 165 banner.hidden = true; 166 }; 167 banner.addEventListener('transitionend', onEnd); 168 } 169 170 function categoriesFromToggles() { 171 var categories = {}; 172 banner.querySelectorAll('[data-consent-category]').forEach(function (input) { 173 categories[input.getAttribute('data-consent-category')] = input.checked; 174 }); 175 return categories; 176 } 177 178 customizeBtn && customizeBtn.addEventListener('click', function () { 179 var expanded = customizeBtn.getAttribute('aria-expanded') === 'true'; 180 customizeBtn.setAttribute('aria-expanded', String(!expanded)); 181 panel.hidden = expanded; 182 }); 183 184 banner.querySelector('[data-consent-action="accept-all"]').addEventListener('click', function () { 185 onAction({ analytics: true, marketing: true, preferences: true }); 186 hide(); 187 }); 188 189 banner.querySelector('[data-consent-action="reject"]').addEventListener('click', function () { 190 onAction({ analytics: false, marketing: false, preferences: false }); 191 hide(); 192 }); 193 194 banner.querySelector('[data-consent-action="save"]').addEventListener('click', function () { 195 onAction(categoriesFromToggles()); 196 hide(); 197 }); 198 199 return { show: show, hide: hide }; 200 } 201 202 function init() { 203 var existing = readConsent(); 204 205 if (existing) { 206 activateConsentedTrackers(existing); 207 watchForRogueTrackers(existing); 208 return; 209 } 210 211 // Mutated in place (never reassigned) so the MutationObserver closure 212 // below always sees the latest grants after a banner action. 213 var consentState = { necessary: true, analytics: false, marketing: false, preferences: false }; 214 watchForRogueTrackers(consentState); 215 216 var banner = initBanner(function (categories) { 217 var updated = writeConsent(categories); 218 Object.keys(updated).forEach(function (key) { 219 consentState[key] = updated[key]; 220 }); 221 activateConsentedTrackers(consentState); 222 }); 223 224 if (banner) banner.show(); 225 } 226 227 if (document.readyState === 'loading') { 228 document.addEventListener('DOMContentLoaded', init); 229 } else { 230 init(); 231 } 232})();
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.