1(function () { 2 'use strict'; 3 4 const STORAGE_KEY = 'sotvConsent'; 5 const COOKIE_KEY = 'sotvConsent'; 6 const BANNER_ID = 'sotv-consent-banner'; 7 const COOKIE_MAX_AGE = 60 * 60 * 24 * 365; // 1 Jahr 8 9 const activateDeferredResources = () => { 10 const scriptNodes = document.querySelectorAll('script[type="text/plain"][data-consent]'); 11 scriptNodes.forEach((node) => { 12 const replacement = document.createElement('script'); 13 Array.from(node.attributes).forEach((attr) => { 14 if (attr.name === 'type' || attr.name === 'data-consent') { 15 return; 16 } 17 replacement.setAttribute(attr.name, attr.value); 18 }); 19 replacement.textContent = node.textContent || ''; 20 node.replaceWith(replacement); 21 }); 22 23 const lazyFrames = document.querySelectorAll('[data-consent-src]'); 24 lazyFrames.forEach((el) => { 25 if (el.dataset.consentLoaded === '1') { 26 return; 27 } 28 const src = el.getAttribute('data-consent-src'); 29 if (!src) { 30 return; 31 } 32 el.setAttribute('src', src); 33 el.dataset.consentLoaded = '1'; 34 }); 35 }; 36 37 document.addEventListener('DOMContentLoaded', () => { 38 const banner = document.getElementById(BANNER_ID); 39 const reopen = document.querySelector('[data-consent-open]'); 40 const docEl = document.documentElement; 41 const version = banner?.dataset?.consentVersion || '1'; 42 43 const syncNavColors = () => { 44 const nav = document.querySelector('header nav, .navbar, .site-header, header'); 45 if (!nav || !docEl) return; 46 const styles = window.getComputedStyle(nav); 47 const bg = styles.backgroundColor || ''; 48 const text = styles.color || ''; 49 if (bg) docEl.style.setProperty('--sotv-nav-bg', bg); 50 if (text) docEl.style.setProperty('--sotv-nav-text', text); 51 }; 52 53 const readCookie = () => { 54 try { 55 const match = document.cookie.match(new RegExp(`(?:^|; )${COOKIE_KEY}=([^;]+)`)); 56 if (!match || !match[1]) return null; 57 const parsed = JSON.parse(decodeURIComponent(match[1])); 58 return parsed && parsed.version === version ? parsed : null; 59 } catch (err) { 60 return null; 61 } 62 }; 63 64 const writeCookie = (entry) => { 65 try { 66 const payload = encodeURIComponent(JSON.stringify(entry)); 67 document.cookie = `${COOKIE_KEY}=${payload};path=/;max-age=${COOKIE_MAX_AGE};SameSite=Lax`; 68 } catch (err) { 69 // Ignore cookie errors 70 } 71 }; 72 73 const readStored = () => { 74 try { 75 const raw = localStorage.getItem(STORAGE_KEY); 76 if (!raw) { 77 return readCookie(); 78 } 79 const parsed = JSON.parse(raw); 80 if (!parsed || parsed.version !== version) { 81 return readCookie(); 82 } 83 return parsed; 84 } catch (err) { 85 return readCookie(); 86 } 87 }; 88 89 const setPreference = (state, hideAfter = true) => { 90 const entry = { state, version, ts: Date.now() };
91 try { 92 localStorage.setItem(STORAGE_KEY, JSON.stringify(entry)); 93 } catch (err) { 94 // Ignore storage errors (e.g. private mode) 95 } 96 writeCookie(entry); 97 if (docEl) { 98 docEl.setAttribute('data-sotv-consent', state); 99 } 100 window.dispatchEvent(new CustomEvent('sotvConsentChanged', { detail: entry })); 101 if (state === 'granted') { 102 activateDeferredResources(); 103 } 104 if (hideAfter) { 105 hideBanner(); 106 } 107 return entry; 108 }; 109 110 const hideBanner = () => { 111 banner?.classList.remove('is-visible'); 112 }; 113 114 const showBanner = () => { 115 banner?.classList.add('is-visible'); 116 }; 117 118 if (!banner) { 119 return; 120 } 121 122 const acceptBtn = banner.querySelector('[data-consent-accept]'); 123 const declineBtn = banner.querySelector('[data-consent-decline]'); 124 125 const stored = readStored(); 126 if (stored) { 127 if (docEl) { 128 docEl.setAttribute('data-sotv-consent', stored.state); 129 } 130 if (stored.state === 'granted') { 131 activateDeferredResources(); 132 } 133 // Broadcast stored state so other scripts (z.B. SPA) can react on initial load. 134 window.dispatchEvent(new CustomEvent('sotvConsentChanged', { detail: stored })); 135 hideBanner(); 136 } else { 137 showBanner(); 138 } 139 140 syncNavColors(); 141 142 acceptBtn?.addEventListener('click', () => setPreference('granted')); 143 declineBtn?.addEventListener('click', () => setPreference('denied')); 144 reopen?.addEventListener('click', () => showBanner()); 145 146 window.sotvConsent = { 147 get: readStored, 148 set: setPreference, 149 activateDeferred: activateDeferredResources, 150 open: showBanner, 151 }; 152 }); 153})();
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.