1/** 2 * Nexus Client â Consent Banner 3 * 4 * Reads cached consent decision from localStorage, renders the sticky consent 5 * banner if no decision exists, wires accept / decline buttons, persists the 6 * choice to localStorage + POSTs to /tmb-client/v1/tracking/consent, and 7 * updates window.TMBClientBeacon.consent so the beacon starts/stops flushing. 8 */ 9(function () { 10 'use strict'; 11 12 if (typeof document === 'undefined') return; 13 14 var cfg = window.TMBClientBeacon || {}; 15 var i18n = cfg.i18n || {}; 16 var STORAGE_KEY = 'tmb_consent_state'; 17 var CONSENT_URL = (cfg.restRoot || '').replace(/\/+$/, '') + '/tracking/consent'; 18 19 function readStored() { 20 try { return localStorage.getItem(STORAGE_KEY) || ''; } 21 catch (e) { return ''; } 22 } 23 24 function writeStored(state) { 25 try { localStorage.setItem(STORAGE_KEY, state); } catch (e) {} 26 } 27 28 function ensureBannerMarkup() { 29 var existing = document.getElementById('tmb-consent-banner'); 30 if (existing) return existing; 31 32 // Build banner programmatically if the template wasn't injected 33 // server-side. Mirrors templates/tracking/tracking-consent-banner.php. 34 var banner = document.createElement('div'); 35 banner.id = 'tmb-consent-banner'; 36 banner.className = 'tmb-consent-banner'; 37 banner.setAttribute('role', 'dialog'); 38 banner.setAttribute('aria-labelledby', 'tmb-consent-banner-title'); 39 banner.setAttribute('aria-describedby', 'tmb-consent-banner-text'); 40 banner.hidden = true; 41 banner.innerHTML = '' 42 + '<div class="tmb-consent-banner-inner">' 43 + '<div class="tmb-consent-banner-body">' 44 + '<h2 id="tmb-consent-banner-title" class="tmb-consent-banner-title"></h2>' 45 + '<p id="tmb-consent-banner-text" class="tmb-consent-banner-text"></p>' 46 + '</div>' 47 + '<div class="tmb-consent-banner-actions">' 48 + '<button type="button" class="tmb-consent-btn tmb-consent-btn--decline" id="tmb-consent-decline"></button>' 49 + '<button type="button" class="tmb-consent-btn tmb-consent-btn--accept" id="tmb-consent-accept"></button>' 50 + '</div>' 51 + '<button type="button" class="tmb-consent-banner-dismiss" id="tmb-consent-dismiss" aria-label="Dismiss">×</button>' 52 + '</div>'; 53 document.body.appendChild(banner); 54 return banner; 55 } 56 57 function applyI18n(banner) { 58 var titleNode = banner.querySelector('#tmb-consent-banner-title'); 59 var textNode = banner.querySelector('#tmb-consent-banner-text'); 60 var acceptBtn = banner.querySelector('#tmb-consent-accept'); 61 var declineBtn = banner.querySelector('#tmb-consent-decline'); 62 if (titleNode && !titleNode.textContent.trim()) titleNode.textContent = i18n.consentTitle || 'We value your privacy'; 63 if (textNode && !textNode.textContent.trim()) textNode.textContent = i18n.consentText || 'We use cookies to improve your experience and analyze site traffic.'; 64 if (acceptBtn && !acceptBtn.textContent.trim()) acceptBtn.textContent = i18n.accept || 'Accept'; 65 if (declineBtn && !declineBtn.textContent.trim()) declineBtn.textContent = i18n.decline || 'Decline'; 66 } 67 68 function showBanner(banner) { 69 banner.hidden = false; 70 banner.removeAttribute('hidden'); 71 } 72 73 function hideBanner(banner) { 74 banner.hidden = true; 75 banner.setAttribute('hidden', 'hidden'); 76 } 77 78 function postConsent(state) { 79 try { 80 fetch(CONSENT_URL, { 81 method: 'POST', 82 credentials: 'same-origin', 83 headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, 84 body: JSON.stringify({ state: state, visitor_id: cfg.visitorId || '' }) 85 }).catch(function () {}); 86 } catch (e) {} 87 } 88 89 function applyDecision(state) { 90 writeStored(state); 91 cfg.consent = state; 92 if (typeof cfg._setConsent === 'function') { 93 cfg._setConsent(state); 94 } 95 if (typeof cfg.track === 'function') { 96 try { cfg.track('visitor.consent_' + state, {}, {}); } catch (e) {} 97 } 98 postConsent(state); 99 } 100 101 function boot() { 102 var existing = readStored(); 103 if (existing === 'granted' || existing === 'denied') { 104 cfg.consent = existing; 105 if (typeof cfg._setConsent === 'function') cfg._setConsent(existing); 106 return; 107 } 108 109 var banner = ensureBannerMarkup(); 110 applyI18n(banner); 111 112 var acceptBtn = banner.querySelector('#tmb-consent-accept'); 113 var declineBtn = banner.querySelector('#tmb-consent-decline'); 114 var dismissBtn = banner.querySelector('#tmb-consent-dismiss'); 115 116 if (acceptBtn) { 117 acceptBtn.addEventListener('click', function () { 118 applyDecision('granted'); 119 hideBanner(banner); 120 }); 121 } 122 if (declineBtn) { 123 declineBtn.addEventListener('click', function () { 124 applyDecision('denied'); 125 hideBanner(banner); 126 }); 127 } 128 if (dismissBtn) { 129 dismissBtn.addEventListener('click', function () { 130 // Dismiss without a decision â leave state unknown for this session 131 hideBanner(banner); 132 }); 133 } 134 135 showBanner(banner); 136 } 137 138 if (document.readyState === 'loading') { 139 document.addEventListener('DOMContentLoaded', boot, { once: true }); 140 } else { 141 boot(); 142 } 143})();
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.