1/** 2 * stt-consent-gating.js â Consent-Gating für schwedentor.de 3 * Lädt GA4, Facebook-Plugin und Embeds erst nach Einwilligung: 4 * CMP (TCF, Purpose 1) ODER 2-Klick-Platzhalter je Inhalt. 5 * Seit 2026-08-06 zusaetzlich: Google Consent Mode v2. Die Signale 6 * ad_storage / ad_user_data / ad_personalization / analytics_storage 7 * starten auf 'denied' und werden aus den TCF-Zwecken abgeleitet. 8 * Ohne diese Kommandos hat gtag ad_personalization als nicht erteilt 9 * behandelt und npa=1 gesendet (Befund 2026-08-06). 10 * Doku: WORK_Schwedentor\consent-gating\README_Consent-Gating.md 11 * Stand: 2026-08-06 (Sven) 12 */ 13(function () { 14 'use strict'; 15 16 var CFG = window.sttConsentConfig || {}; 17 var GA4_IDS = CFG.ga4 || []; 18 var LS_PREFIX = 'sttConsent_'; 19 var tcfUnlocked = false; 20 21 function onReady(fn) { 22 if (document.readyState !== 'loading') { fn(); } 23 else { document.addEventListener('DOMContentLoaded', fn); } 24 } 25 function remember(service) { 26 try { localStorage.setItem(LS_PREFIX + service, '1'); } catch (e) {} 27 } 28 function remembered(service) { 29 try { return localStorage.getItem(LS_PREFIX + service) === '1'; } catch (e) { return false; } 30 } 31 32 function injectStyles() { 33 var css = 34 '.stt-embed-ph{border:1px solid #d5e2ee;border-radius:8px;background:#f4f7fa;' + 35 'padding:18px 16px;text-align:center;font-family:inherit;color:#33404a}' + 36 '.stt-embed-ph p{margin:0 0 10px;font-size:.9em;line-height:1.45}' + 37 '.stt-embed-ph button{background:#005293;color:#fff;border:0;border-radius:5px;' + 38 'padding:8px 18px;font-size:.95em;cursor:pointer}' + 39 '.stt-embed-ph button:hover{background:#003e70}' + 40 '.stt-embed-ph label{display:block;margin-top:8px;font-size:.8em;color:#667}' + 41 '.stt-embed-16x9{position:relative;width:100%;padding-top:56.25%}' + 42 '.stt-embed-16x9 iframe{position:absolute;top:0;left:0;width:100%;height:100%;border:0}'; 43 var s = document.createElement('style'); 44 s.textContent = css; 45 document.head.appendChild(s); 46 } 47 48 /* ---------- Google Consent Mode v2 ---------- */ 49 50 function ensureGtag() { 51 window.dataLayer = window.dataLayer || []; 52 window.gtag = window.gtag || function () { window.dataLayer.push(arguments); }; 53 } 54 55 var defaultsSet = false; 56 function setConsentDefaults() { 57 if (defaultsSet) { return; } 58 defaultsSet = true; 59 ensureGtag(); 60 window.gtag('consent', 'default', { 61 ad_storage: 'denied', 62 ad_user_data: 'denied', 63 ad_personalization: 'denied', 64 analytics_storage: 'denied', 65 wait_for_update: 500 66 }); 67 } 68 69 /* TCF-Zwecke -> Consent-Mode-Signale. 70 GVL-Vendor 755 = Google Advertising Products. 71 Zweck 1 = Speichern/Zugriff Endgeraet, 3 = personalisiertes Anzeigenprofil, 72 4 = personalisierte Anzeigen auswaehlen, 7 = Anzeigenleistung messen. */ 73 function deriveConsent(tcData) { 74 if (tcData.gdprApplies === false) { 75 return { ad_storage: 'granted', ad_user_data: 'granted', 76 ad_personalization: 'granted', analytics_storage: 'granted' }; 77 } 78 var p = (tcData.purpose && tcData.purpose.consents) || {}; 79 var v = (tcData.vendor && tcData.vendor.consents) || {}; 80 var google = v[755] === true; 81 var store = p[1] === true; 82 function g(ok) { return ok ? 'granted' : 'denied'; } 83 return { 84 ad_storage: g(store && google), 85 ad_user_data: g(store && p[7] === true && google), 86 ad_personalization: g(p[3] === true && p[4] === true && google), 87 analytics_storage: g(store) 88 }; 89 } 90 91 var lastConsent = ''; 92 function applyConsent(tcData) { 93 var state = deriveConsent(tcData); 94 var sig = state.ad_storage + state.ad_user_data + 95 state.ad_personalization + state.analytics_storage; 96 if (sig === lastConsent) { return; } 97 lastConsent = sig; 98 ensureGtag(); 99 window.gtag('consent', 'update', state); 100 } 101 102 var gaLoaded = false; 103 function loadGA4() { 104 if (gaLoaded || !GA4_IDS.length) { return; } 105 gaLoaded = true; 106 ensureGtag(); 107 window.gtag('js', new Date()); 108 for (var i = 0; i < GA4_IDS.length; i++) { 109 window.gtag('config', GA4_IDS[i], { anonymize_ip: true }); 110 } 111 var s = document.createElement('script'); 112 s.async = true;
113 s.src = 'https://www.googletagmanager.com/gtag/js?id=' + encodeURIComponent(GA4_IDS[0]); 114 document.head.appendChild(s); 115 } 116 117 var fbLoaded = false; 118 function loadFbSdk(cb) { 119 if (fbLoaded) { cb(); return; } 120 fbLoaded = true; 121 if (!document.getElementById('fb-root')) { 122 var r = document.createElement('div'); 123 r.id = 'fb-root'; 124 document.body.appendChild(r); 125 } 126 var s = document.createElement('script'); 127 s.async = true; s.defer = true; s.crossOrigin = 'anonymous'; 128 s.src = 'https://connect.facebook.net/de_DE/sdk.js#xfbml=1&version=v19.0'; 129 s.onload = cb; 130 document.head.appendChild(s); 131 } 132 133 function renderEmbed(el) { 134 var type = el.getAttribute('data-stt-type') || 'iframe'; 135 var src = el.getAttribute('data-stt-src') || ''; 136 el.innerHTML = ''; 137 138 if (type === 'iframe') { 139 var ratio = el.getAttribute('data-stt-ratio'); 140 var ifr = document.createElement('iframe'); 141 ifr.src = src; 142 ifr.setAttribute('allowfullscreen', ''); 143 ifr.setAttribute('loading', 'lazy'); 144 ifr.setAttribute('referrerpolicy', 'no-referrer-when-downgrade'); 145 if (ratio === '16x9') { 146 var wrap = document.createElement('div'); 147 wrap.className = 'stt-embed-16x9'; 148 wrap.appendChild(ifr); 149 el.appendChild(wrap); 150 } else { 151 ifr.width = el.getAttribute('data-stt-width') || '100%'; 152 ifr.height = el.getAttribute('data-stt-height') || '300'; 153 ifr.style.border = '0'; 154 el.appendChild(ifr); 155 } 156 } else if (type === 'script') { 157 var sc = document.createElement('script'); 158 sc.src = src; 159 sc.async = true; 160 el.appendChild(sc); 161 } else if (type === 'fbpage') { 162 var href = el.getAttribute('data-stt-href') || ''; 163 var fb = document.createElement('div'); 164 fb.className = 'fb-page'; 165 fb.setAttribute('data-href', href); 166 fb.setAttribute('data-width', el.getAttribute('data-stt-width') || '340'); 167 fb.setAttribute('data-small-header', 'true'); 168 fb.setAttribute('data-hide-cover', 'true'); 169 el.appendChild(fb); 170 loadFbSdk(function () { 171 if (window.FB && window.FB.XFBML) { window.FB.XFBML.parse(el); } 172 }); 173 } 174 el.setAttribute('data-stt-loaded', '1'); 175 } 176 177 function renderPlaceholder(el) { 178 if (el.getAttribute('data-stt-ph')) { return; } 179 el.setAttribute('data-stt-ph', '1'); 180 var service = el.getAttribute('data-stt-service') || 'einem externen Anbieter'; 181 var ph = document.createElement('div'); 182 ph.className = 'stt-embed-ph'; 183 var p = document.createElement('p'); 184 p.textContent = 'Externer Inhalt von ' + service + '. Beim Laden werden Daten ' + 185 '(u. a. Ihre IP-Adresse) an den Anbieter übertragen. Details: Datenschutzerklärung.'; 186 var btn = document.createElement('button'); 187 btn.type = 'button'; 188 btn.textContent = 'Inhalt laden'; 189 var lbl = document.createElement('label'); 190 var chk = document.createElement('input'); 191 chk.type = 'checkbox'; 192 lbl.appendChild(chk); 193 lbl.appendChild(document.createTextNode(' Inhalte von ' + service + ' immer laden')); 194 btn.addEventListener('click', function () { 195 if (chk.checked) { remember(service); } 196 renderEmbed(el); 197 }); 198 ph.appendChild(p); 199 ph.appendChild(btn); 200 ph.appendChild(lbl); 201 el.innerHTML = ''; 202 el.appendChild(ph); 203 } 204 205 function processEmbeds() { 206 var list = document.querySelectorAll('.stt-embed:not([data-stt-loaded])'); 207 for (var i = 0; i < list.length; i++) { 208 var el = list[i]; 209 var service = el.getAttribute('data-stt-service') || ''; 210 if (tcfUnlocked || remembered(service)) { renderEmbed(el); } 211 else { renderPlaceholder(el); } 212 } 213 } 214 215 function unlockAll() { 216 tcfUnlocked = true; 217 loadGA4(); 218 processEmbeds(); 219 } 220 221 function bindTcf() { 222 if (typeof window.__tcfapi !== 'function') { return false; } 223 window.__tcfapi('addEventListener', 2, function (tcData, ok) { 224 if (!ok || !tcData) { return; } 225 if (tcData.eventStatus === 'tcloaded' || tcData.eventStatus === 'useractioncomplete') { 226 applyConsent(tcData); 227 if (tcData.gdprApplies === false) { unlockAll(); return; } 228 var p = tcData.purpose && tcData.purpose.consents; 229 if (p && p[1]) { unlockAll(); } 230 } 231 }); 232 return true; 233 } 234 235 setConsentDefaults(); 236 237 onReady(function () { 238 injectStyles(); 239 processEmbeds(); 240 if (!bindTcf()) { 241 var tries = 0; 242 var t = setInterval(function () { 243 tries++; 244 if (bindTcf() || tries >= 20) { clearInterval(t); } 245 }, 500); 246 } 247 }); 248})();
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.