Files
Webklar-Kundenbereich/analytics/client/dist/w.js
T
ClaudeandClaude Opus 5.5 0a113c2983 Heatmap in Geraeterahmen (Computer/Tablet + Handy) und Keks statt "Datenschutz-Einstellungen"
Heatmap im Portal (Funktionen -> Analyse): die echte Website laeuft jetzt in realistischen
Geraeterahmen, gross MacBook oder iPad (Umschalter oben), daneben das iPhone, jeweils in echter
Breite (1512 / 820 / 402 px), verkleinert und im Geraet scrollbar, mit den Klicks der
passenden Geraeteklasse als Overlay. MacBook und iPhone stammen aus den Rahmen der neuen
Website (device-mockups), das iPad ist im gleichen Stil nachgebaut (public/geraete.css/.js).

- heatmap-link: embed: true haengt __wk_embed=1 an; w.js reicht das als data-embed an h.js.
- h.js eingebettet (nur in einem Rahmen): keine Leiste, Links und Formulare gesperrt (kein
  Seitenwechsel, also keine Zaehlung und kein Banner), Scrollleisten aus, Klickzahl per
  postMessage nur an die Herkunft von h.js (project.webklar.com).
- iframe mit Sandbox allow-scripts allow-same-origin (keine Formulare, Pop-ups, keine
  Navigation des Portals); Laden erst, wenn die Heatmap in Sicht kommt. Laesst sich eine
  Website nicht einbetten, kommt nach 20 s ein Hinweis mit "In neuem Tab oeffnen" (alter Weg).

Banner-Skript: der Einstellungsknopf nach der Wahl ist jetzt ein angeknabberter Keks unten
rechts (Inline-SVG, 44 px). Er dreht sich langsam, haelt beim Darueberfahren/Fokus an und
zeigt dann "Datenschutz-Einstellungen" (auch sein Name fuer Screenreader, passend zum
Bannertext); bei reduzierter Bewegung dreht er sich nicht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:09:17 +00:00

452 lines
21 KiB
JavaScript

/* WEBklar Analyse w.js - generiert aus client/core.js + client/w.js (node client/build.js), nicht von Hand aendern */
(function () {
'use strict';
// Reine Funktionen des Analyse-Skripts (ohne DOM-Zugriff ausser an uebergebenen Objekten).
// Wird von w.js/h.js importiert; build.js fuegt alles zu je einer Datei ohne Import zusammen.
const CONSENT_KEY = 'wk-consent';
const CONSENT_MAX_AGE = 183 * 86400000;
const BANNER_VERSION = '2026-10-02';
const BANNER_TEXT = 'Um unsere Website zu verbessern, zählen wir Seitenaufrufe mit Land und Gerätetyp, ohne Kennung. Mit Deiner Zustimmung messen wir zusätzlich, von welcher Website Du kommst, wie lange Du bleibst, wie weit Du scrollst und wo Du klickst (Heatmap), mit einer täglich wechselnden Kennung. Die Messung betreibt für uns die WEBklar GbR in Deutschland, ohne Cookies und ohne Werbung. Nach „Ablehnen“ zählen wir nur noch, dass Du abgelehnt hast. Deine Zustimmung kannst Du jederzeit über „Datenschutz-Einstellungen“ mit Wirkung für die Zukunft widerrufen. Details: ';
const BANNER_LINK_LABEL = 'Datenschutz';
const BANNER_ARIA = 'Einwilligung zur Reichweitenmessung';
const SETTINGS_LABEL = 'Datenschutz-Einstellungen';
function vpClass(width) {
const w = Number(width);
if (w < 768) return 'm';
if (w < 1024) return 't';
return 'd';
}
function cleanPath(href) {
let p = '/';
const h = String(href == null ? '' : href);
if (h.charAt(0) === '/') {
p = h.replace(/[?#].*$/, '') || '/'; // wie location.pathname, auch '//foo'
} else {
try { p = new URL(h, 'http://x.invalid/').pathname || '/'; } catch (e) { /* bleibt '/' */ }
}
try { p = decodeURIComponent(p); } catch (e) { /* kaputtes Encoding: roh lassen */ }
if (p.charAt(0) !== '/') p = '/' + p;
return p.slice(0, 300);
}
const stripWww = (h) => String(h).toLowerCase().replace(/^www\./, '');
function refHost(referrer, ownHost) {
if (!referrer) return '';
try {
const u = new URL(String(referrer));
if (u.protocol !== 'http:' && u.protocol !== 'https:') return '';
const h = u.hostname.toLowerCase();
if (!h || stripWww(h) === stripWww(ownHost || '')) return '';
return h;
} catch (e) { return ''; }
}
// Nur Tag-Namen mit :nth-of-type, keine IDs/Klassen (koennten personenbezogene Texte enthalten).
function cssPath(el) {
const parts = [];
let n = el;
while (n && n.tagName && parts.length < 6) {
const tag = String(n.tagName).toLowerCase().replace(/[^a-z0-9-]/g, '');
if (!tag) break;
let idx = 1;
for (let s = n.previousElementSibling; s; s = s.previousElementSibling) {
if (String(s.tagName).toLowerCase() === String(n.tagName).toLowerCase()) idx++;
}
parts.unshift(tag + ':nth-of-type(' + idx + ')');
if (tag === 'body' || tag === 'html') break;
n = n.parentNode;
}
let out = parts.join(' > ');
while (out.length > 200 && parts.length > 1) { parts.shift(); out = parts.join(' > '); }
return out.slice(0, 200);
}
function parseHref(a) {
const href = a && (typeof a.href === 'string' && a.href ? a.href : (a.getAttribute && a.getAttribute('href'))) || '';
return String(href).trim();
}
function linkHost(a) {
try { return new URL(parseHref(a)).hostname.toLowerCase(); } catch (e) { return ''; }
}
function contactKind(a, ownHost) {
const href = parseHref(a);
if (!href) return null;
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(href);
if (!scheme) return null; // relativ, Anker
const s = scheme[1].toLowerCase();
if (s === 'tel') return 'tel';
if (s === 'mailto') return 'mail';
if (s === 'whatsapp') return 'wa';
if (s !== 'http' && s !== 'https') return null;
const host = linkHost(a);
if (!host) return null;
if (host === 'wa.me' || host === 'api.whatsapp.com') return 'wa';
const own = ownHost == null ? (typeof location !== 'undefined' ? location.hostname : '') : ownHost;
return stripWww(host) === stripWww(own || '') ? null : 'out';
}
function readConsent(storage, now) {
try {
const raw = storage.getItem(CONSENT_KEY);
if (!raw) return null;
const c = JSON.parse(raw);
if (!c || c.v !== 1 || (c.a !== 'accept' && c.a !== 'reject') || typeof c.t !== 'number' || !isFinite(c.t)) return null;
if (c.bv !== BANNER_VERSION) return null; // andere/fehlende Fassung des Banner-Textes: neu fragen
const age = now - c.t;
if (age < 0 || age >= CONSENT_MAX_AGE) return null;
return { v: 1, a: c.a, t: c.t, bv: c.bv };
} catch (e) { return null; }
}
function contrastOk(hex) {
if (typeof hex !== 'string' || !/^#[0-9a-f]{6}$/i.test(hex)) return false;
const lin = (i) => {
const c = parseInt(hex.slice(1 + i * 2, 3 + i * 2), 16) / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
};
const L = 0.2126 * lin(0) + 0.7152 * lin(1) + 0.0722 * lin(2);
return 1.05 / (L + 0.05) >= 4.5;
}
function escapeHtml(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}
// WEBklar Analyse: Einwilligungs-Banner + Ereignisse. Laeuft auf oeffentlichen Kunden-Websites.
// Keine Cookies, keine Kennung im Browser. Ohne Wahl nur pv {t,site,path}; ref/vp/cx/ck/ct/en nur nach Annehmen;
// nach Ablehnen oder bei GPC/DNT geht nichts mehr raus.
function main() {
const DOC = document;
const WIN = window;
if (WIN.__wkAnalytics) return; // doppelt eingebunden
WIN.__wkAnalytics = true;
const SELF = DOC.currentScript && DOC.currentScript.getAttribute && DOC.currentScript.getAttribute('data-site')
? DOC.currentScript : DOC.querySelector('script[src*="/a/w.js"]');
if (!SELF) return;
const site = String(SELF.getAttribute('data-site') || '');
if (!/^[a-z0-9]{16,40}$/.test(site)) return;
const colorAttr = String(SELF.getAttribute('data-color') || '');
const accent = contrastOk(colorAttr) ? colorAttr : '#111111';
const src = String(SELF.src || SELF.getAttribute('src') || '').replace(/[?#].*$/, '');
const base = src.replace(/[^/]*$/, '');
const endpoint = base + 'e';
// ---- Heatmap-Modus: nichts senden, kein Banner ----
const qs = new URLSearchParams(WIN.location.search);
const heatToken = qs.get('__wk_heat');
if (heatToken) {
// __wk_embed=1: Ansicht im Geraeterahmen des Kundenportals (h.js ohne Leiste, Links gesperrt)
const embed = qs.get('__wk_embed') === '1';
qs.delete('__wk_heat');
qs.delete('__wk_embed');
const rest = qs.toString();
try { WIN.history.replaceState(null, '', WIN.location.pathname + (rest ? '?' + rest : '') + (WIN.location.hash || '')); } catch (e) { /* egal */ }
const s = DOC.createElement('script');
s.setAttribute('src', base + 'h.js');
s.setAttribute('data-token', heatToken);
if (embed) s.setAttribute('data-embed', '1');
(DOC.head || DOC.documentElement).appendChild(s);
return;
}
// ---- Senden: Fehler werden verschluckt ----
function send(obj) {
try {
const body = JSON.stringify(obj);
fetch(endpoint, { method: 'POST', body, keepalive: true, credentials: 'omit', headers: { 'Content-Type': 'text/plain' } }).catch(function () {});
} catch (e) { /* egal */ }
}
const safe = (fn) => function () { try { return fn.apply(this, arguments); } catch (e) { /* nie die Seite stoeren */ } };
const vp = () => vpClass(WIN.innerWidth);
const ownHost = () => WIN.location.hostname;
// ---- Widerspruch (GPC/DNT) und Wahl: vor jedem Senden und vor jedem Hook ----
let storage = null;
try { storage = WIN.localStorage; } catch (e) { /* gesperrt */ }
const dnt = (v) => v === '1' || v === 'yes';
const optOut = navigator.globalPrivacyControl === true || dnt(navigator.doNotTrack) || dnt(WIN.doNotTrack) || dnt(navigator.msDoNotTrack);
if (optOut) return; // wie Ablehnen: nichts senden, kein Banner, keine Hooks
const stored = readConsent(storage, Date.now());
let memChoice = null;
let tracking = false;
// Wahl bei jedem Aufruf frisch lesen (Speicher, sonst nur fuer diese Seite gemerkt)
function choiceNow() {
const c = readConsent(storage, Date.now());
return c ? c.a : (memChoice ? memChoice.a : null);
}
const counting = () => choiceNow() !== 'reject';
// ---- Seitenaufruf (pv): ohne Wahl nur Seitenadresse, ref/vp nur nach Annehmen ----
let lastPath = null;
let pvStart = Date.now();
let maxScroll = 0;
let enSent = false;
let pvSent = false;
let cxSent = false;
function sendPv(first) {
if (!counting()) return;
lastPath = cleanPath(WIN.location.pathname);
pvStart = Date.now(); maxScroll = 0; enSent = false;
const ev = { t: 'pv', site, path: lastPath };
if (choiceNow() === 'accept') {
if (first) { ev.ref = refHost(DOC.referrer, ownHost()); cxSent = true; }
ev.vp = vp();
}
pvSent = true;
send(ev);
}
let navTimer = null;
function scheduleNav() {
if (navTimer || !counting()) return;
navTimer = setTimeout(safe(function () {
navTimer = null;
if (counting() && cleanPath(WIN.location.pathname) !== lastPath) sendPv(false);
}), 300);
}
let hooksOn = false;
function ensureHooks() {
if (hooksOn) return;
hooksOn = true;
WIN.addEventListener('pageshow', safe(function (e) { if (e && e.persisted) sendPv(false); }));
try {
const orig = WIN.history.pushState;
WIN.history.pushState = function () { const r = orig.apply(this, arguments); try { scheduleNav(); } catch (e) { /* egal */ } return r; };
WIN.addEventListener('popstate', safe(scheduleNav));
} catch (e) { /* egal */ }
}
// Nach "Annehmen": Herkunft und Fenstergroesse einmal nachreichen, oder (noch kein pv) pv mit Daten senden
function onAccept() {
ensureHooks();
if (!pvSent) { sendPv(true); return; }
if (cxSent) return;
cxSent = true;
send({ t: 'cx', site, path: lastPath, ref: refHost(DOC.referrer, ownHost()), vp: vp() });
}
if (choiceNow() !== 'reject') { sendPv(true); ensureHooks(); }
// ---- Tracking (nur nach Annehmen erreichbar) ----
const hosts = [];
const scrollPct = () => {
const h = DOC.documentElement.scrollHeight || 1;
return Math.max(0, Math.min(100, Math.round(((WIN.scrollY || 0) + WIN.innerHeight) / h * 100)));
};
const rel = (v, total) => Math.max(0, Math.min(1, Math.round(v / (total || 1) * 10000) / 10000));
const onClick = safe(function (e) {
if (!allowed()) return;
const t = e.target;
if (!t || hosts.indexOf(t) !== -1) return;
const dw = DOC.documentElement.scrollWidth, dh = DOC.documentElement.scrollHeight;
const px = typeof e.pageX === 'number' ? e.pageX : (e.clientX || 0) + (WIN.scrollX || 0);
const py = typeof e.pageY === 'number' ? e.pageY : (e.clientY || 0) + (WIN.scrollY || 0);
const path = cleanPath(WIN.location.pathname);
send({ t: 'ck', site, path, vp: vp(), x: rel(px, dw), y: rel(py, dh), sel: cssPath(t) });
let a = t;
while (a && String(a.tagName).toUpperCase() !== 'A') a = a.parentNode;
const kind = a ? contactKind(a, ownHost()) : null;
if (kind) {
const ev = { t: 'ct', site, path, kind };
if (kind === 'out') ev.host = linkHost(a);
send(ev);
}
});
const onScroll = safe(function () { maxScroll = Math.max(maxScroll, scrollPct()); });
function sendEn() {
if (enSent || !allowed()) return;
enSent = true;
send({ t: 'en', site, path: lastPath || cleanPath(WIN.location.pathname), vp: vp(),
sec: Math.max(0, Math.min(3600, Math.round((Date.now() - pvStart) / 1000))), scroll: Math.max(maxScroll, scrollPct()) });
}
const onLeave = safe(sendEn);
// Interner Seitenwechsel per Link: en sofort senden, der Beacon beim pagehide geht in Chromium oft verloren.
// Bubbling-Listener am Dokument: laeuft nach den Handlern der Seite, defaultPrevented ist dann bekannt.
const onLinkClick = safe(function (e) {
if (e.defaultPrevented || e.ctrlKey || e.metaKey || e.shiftKey || e.altKey || (typeof e.button === 'number' && e.button !== 0)) return;
let a = e.target;
while (a && String(a.tagName).toUpperCase() !== 'A') a = a.parentNode;
if (!a || hosts.indexOf(e.target) !== -1) return;
const tg = String(a.getAttribute('target') || '').toLowerCase();
if ((tg && tg !== '_self') || a.getAttribute('download') !== null) return;
const u = new URL(String(a.href || a.getAttribute('href') || ''), WIN.location.href);
if (u.protocol !== 'http:' && u.protocol !== 'https:') return;
if (u.origin !== WIN.location.origin || u.pathname === WIN.location.pathname) return;
sendEn();
});
const onVis = safe(function () { if (DOC.visibilityState === 'hidden') onLeave(); });
// Einwilligung unmittelbar vor jedem Senden pruefen (z.B. in einem anderen Tab widerrufen)
function allowed() {
const ok = choiceNow() === 'accept';
if (!ok && tracking) stopTracking();
return ok;
}
function startTracking() {
if (tracking) return;
tracking = true;
DOC.addEventListener('click', onClick, { capture: true, passive: true });
DOC.addEventListener('click', onLinkClick, { passive: true });
DOC.addEventListener('visibilitychange', onVis);
WIN.addEventListener('pagehide', onLeave);
WIN.addEventListener('scroll', onScroll, { passive: true });
}
function stopTracking() {
tracking = false;
DOC.removeEventListener('click', onClick, { capture: true });
DOC.removeEventListener('click', onLinkClick);
DOC.removeEventListener('visibilitychange', onVis);
WIN.removeEventListener('pagehide', onLeave);
WIN.removeEventListener('scroll', onScroll);
}
// ---- Einwilligung ----
let bannerHost = null;
let linkHostEl = null;
function choose(a) {
memChoice = null;
try { storage.setItem(CONSENT_KEY, JSON.stringify({ v: 1, a, t: Date.now(), bv: BANNER_VERSION })); } catch (e) { memChoice = { a }; /* ohne Speicher: gilt nur fuer diese Seite */ }
send({ t: 'bn', site, a });
if (a === 'accept') { startTracking(); onAccept(); } else stopTracking();
closeBanner();
}
function el(tag, attrs, text) {
const n = DOC.createElement(tag);
for (const k in attrs || {}) n.setAttribute(k, attrs[k]);
if (text != null) n.textContent = text;
return n;
}
const CSS = ':host{all:initial}' +
'*{box-sizing:border-box}' +
'.bar{display:flex;align-items:center;justify-content:center;gap:16px;padding:14px 20px;background:#fff;color:#111;border-top:1px solid #d0d0d0;box-shadow:0 -2px 12px rgba(0,0,0,.12);font:15px/1.45 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}' +
'.t{margin:0;max-width:720px}' +
'.t a{color:#111;text-decoration:underline}' +
'.r{display:flex;gap:10px;flex:none}' +
'button{font:600 15px/1 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;width:150px;height:44px;padding:0 20px;border:2px solid ' + accent + ';border-radius:8px;cursor:pointer;background:' + accent + ';color:#fff}' +
'button:focus-visible{outline:3px solid #1a73e8;outline-offset:2px}' +
'@media (max-width:767px){.bar{display:block;margin:8px;padding:16px;border:1px solid #d0d0d0;border-radius:12px}.r{flex-direction:column;margin-top:12px}button{width:100%}}';
let prevPad = '';
function applyPad() {
if (!bannerHost) return;
const h = Math.ceil(bannerHost.getBoundingClientRect().height || bannerHost.offsetHeight || 0);
DOC.body.style.paddingBottom = h + 'px';
}
const onResize = safe(applyPad);
function openBanner(focus) {
if (bannerHost) return;
const prev = DOC.activeElement;
bannerHost = DOC.createElement('div');
const st = bannerHost.style;
st.position = 'fixed'; st.left = '0'; st.right = '0'; st.bottom = '0'; st.zIndex = '2147483647';
hosts.push(bannerHost);
const root = bannerHost.attachShadow({ mode: 'open' });
root.appendChild(el('style', {}, CSS));
const bar = el('div', { class: 'bar', role: 'dialog', 'aria-label': BANNER_ARIA });
const p = el('p', { class: 't' }, BANNER_TEXT);
p.appendChild(el('a', { href: '/datenschutz-analyse.html' }, BANNER_LINK_LABEL));
const row = el('div', { class: 'r' });
const no = el('button', { type: 'button' }, 'Ablehnen');
const yes = el('button', { type: 'button' }, 'Annehmen');
no.addEventListener('click', safe(function () { choose('reject'); if (prev && prev.focus) prev.focus(); }));
yes.addEventListener('click', safe(function () { choose('accept'); if (prev && prev.focus) prev.focus(); }));
row.appendChild(no); row.appendChild(yes);
bar.appendChild(p); bar.appendChild(row);
root.appendChild(bar);
DOC.body.appendChild(bannerHost);
// Platz unten schaffen, damit Footer-Links (Impressum, Datenschutz) erreichbar bleiben
prevPad = DOC.body.style.paddingBottom;
applyPad();
WIN.addEventListener('resize', onResize);
if (linkHostEl) linkHostEl.style.display = 'none';
if (focus === true) no.focus();
}
function closeBanner() {
if (!bannerHost) return;
const i = hosts.indexOf(bannerHost);
if (i !== -1) hosts.splice(i, 1);
bannerHost.remove();
bannerHost = null;
WIN.removeEventListener('resize', onResize);
DOC.body.style.paddingBottom = prevPad;
if (linkHostEl) linkHostEl.style.display = '';
}
// Einstellungsknopf: angeknabberter Keks unten rechts, dreht sich langsam (nicht bei reduzierter Bewegung).
// Beim Darueberfahren haelt er an und zeigt "Datenschutz-Einstellungen"; das ist auch sein Name fuer Screenreader.
// Inline-SVG statt data:-Grafik, damit eine strenge CSP der Website (img-src) ihn nicht blockiert.
const SETTINGS_CSS = ':host{all:initial}' +
'.b{position:relative;display:block;width:44px;height:44px;padding:2px;margin:0;border:0;border-radius:50%;background:transparent;cursor:pointer;-webkit-tap-highlight-color:transparent}' +
'.b:focus-visible{outline:3px solid #1a73e8;outline-offset:2px}' +
'.c{display:block;width:40px;height:40px;filter:drop-shadow(0 2px 3px rgba(0,0,0,.35));animation:wk-spin 9s linear infinite}' +
'.b:hover .c,.b:focus-visible .c{animation-play-state:paused}' +
'.l{position:absolute;right:52px;top:50%;transform:translate(6px,-50%);padding:7px 12px;border-radius:999px;background:#fff;color:#222;box-shadow:0 2px 10px rgba(0,0,0,.18);font:600 13px/1 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .15s,transform .15s}' +
'.b:hover .l,.b:focus-visible .l{opacity:1;transform:translate(0,-50%)}' +
'@keyframes wk-spin{to{transform:rotate(360deg)}}' +
'@media (prefers-reduced-motion:reduce){.c{animation:none}.l{transition:none}}';
const SVG_NS = 'http://www.w3.org/2000/svg';
function svgEl(tag, attrs) {
const n = DOC.createElementNS(SVG_NS, tag);
for (const k in attrs) n.setAttribute(k, attrs[k]);
return n;
}
function cookieSvg() {
const svg = svgEl('svg', { class: 'c', viewBox: '0 0 64 64', 'aria-hidden': 'true', focusable: 'false' });
// Umriss mit Bissspur oben rechts (zwei Bissboegen, dann der grosse Kreis zurueck)
svg.appendChild(svgEl('path', { d: 'M44.84 6A10 10 0 0 0 57.5 19.37A7.5 7.5 0 0 0 60.96 33.5A29 29 0 1 1 44.84 6Z', fill: '#dfa25b', stroke: '#a5652c', 'stroke-width': '3', 'stroke-linejoin': 'round' }));
const chips = [[19, 22, 4.2, 3.3, -20], [31, 14, 3.2, 2.6, 15], [33, 33, 4, 3.2, 30], [46, 41, 3.6, 2.8, -35], [21, 45, 4, 3, 10], [36, 52, 3, 2.4, -10], [11, 34, 2.6, 2.1, 40], [47, 31, 2.4, 2, 0]];
for (const c of chips) svg.appendChild(svgEl('ellipse', { cx: c[0], cy: c[1], rx: c[2], ry: c[3], transform: 'rotate(' + c[4] + ' ' + c[0] + ' ' + c[1] + ')', fill: '#5a3216' }));
const crumbs = [[26, 28, 1.1], [40, 22, 1], [28, 40, 1.2], [14, 26, 0.9], [40, 46, 1]];
for (const c of crumbs) svg.appendChild(svgEl('circle', { cx: c[0], cy: c[1], r: c[2], fill: '#f3c98c' }));
return svg;
}
function addSettingsLink() {
linkHostEl = DOC.createElement('div');
const st = linkHostEl.style;
st.position = 'fixed'; st.right = '12px'; st.bottom = '12px'; st.zIndex = '2147483646';
hosts.push(linkHostEl);
const root = linkHostEl.attachShadow({ mode: 'open' });
root.appendChild(el('style', {}, SETTINGS_CSS));
const b = el('button', { type: 'button', class: 'b', 'aria-label': SETTINGS_LABEL });
b.appendChild(el('span', { class: 'l', 'aria-hidden': 'true' }, SETTINGS_LABEL));
b.appendChild(cookieSvg());
b.addEventListener('click', safe(function () { openBanner(true); }));
root.appendChild(b);
DOC.body.appendChild(linkHostEl);
}
DOC.addEventListener('click', safe(function (e) {
const t = e.target;
if (t && t.closest && t.closest('[data-webklar-consent]')) { if (e.preventDefault) e.preventDefault(); openBanner(true); }
}));
if (!DOC.querySelector('[data-webklar-consent]')) addSettingsLink();
// Einwilligung in anderem Tab geaendert
WIN.addEventListener('storage', safe(function (e) {
if (e.key !== null && e.key !== CONSENT_KEY) return;
const c = readConsent(storage, Date.now());
if (choiceNow() === 'accept') { startTracking(); onAccept(); } else stopTracking();
if (c) closeBanner();
}));
if (stored) {
if (stored.a === 'accept') startTracking();
} else {
openBanner();
send({ t: 'bn', site, a: 'shown' });
}
}
try { main(); } catch (e) { /* nie die Seite stoeren */ }
})();