/* WEBklar Analyse h.js - generiert aus client/core.js + client/h.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) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); } // WEBklar Analyse: Heatmap-Overlay (wird von w.js nur mit gueltigem ?__wk_heat=Token geladen). function main() { const me = document.currentScript; const token = me && me.getAttribute('data-token'); const src = String((me && (me.src || me.getAttribute('src'))) || '').replace(/[?#].*$/, ''); const base = src.replace(/[^/]*$/, ''); if (!token || !base) return; const de = document.documentElement; const host = document.createElement('div'); host.style.position = 'fixed'; host.style.left = '0'; host.style.right = '0'; host.style.top = '0'; host.style.zIndex = '2147483647'; const root = host.attachShadow({ mode: 'open' }); const style = document.createElement('style'); style.textContent = ':host{all:initial}.bar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;background:#111;color:#fff;font:14px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif}button{font:600 14px/1 system-ui,sans-serif;padding:8px 14px;border:2px solid #fff;border-radius:6px;background:#fff;color:#111;cursor:pointer}button:focus-visible{outline:3px solid #1a73e8;outline-offset:2px}'; const bar = document.createElement('div'); bar.className = 'bar'; const msg = document.createElement('span'); msg.textContent = 'Heatmap wird geladen ...'; const close = document.createElement('button'); close.setAttribute('type', 'button'); close.textContent = 'Schließen'; bar.appendChild(msg); bar.appendChild(close); root.appendChild(style); root.appendChild(bar); document.body.appendChild(host); const canvas = document.createElement('canvas'); const cs = canvas.style; cs.position = 'absolute'; cs.left = '0'; cs.top = '0'; cs.pointerEvents = 'none'; cs.zIndex = '2147483646'; document.body.appendChild(canvas); let points = []; let lastW = 0, lastH = 0, timer = 0; // Farbverlauf: transparent -> blau -> gruen -> gelb -> rot const ramp = (function () { const c = document.createElement('canvas'); c.width = 256; c.height = 1; const g = c.getContext('2d'); const lg = g.createLinearGradient(0, 0, 256, 0); lg.addColorStop(0.25, 'rgb(0,0,255)'); lg.addColorStop(0.55, 'rgb(0,255,0)'); lg.addColorStop(0.85, 'rgb(255,255,0)'); lg.addColorStop(1, 'rgb(255,0,0)'); g.fillStyle = lg; g.fillRect(0, 0, 256, 1); return g.getImageData(0, 0, 256, 1).data; })(); function draw() { const w = Math.max(de.scrollWidth, de.clientWidth, 1); const h = Math.max(de.scrollHeight, de.clientHeight, 1); lastW = w; lastH = h; // Zeichenflaeche begrenzen (Speicher): hoechstens ca. 4 Mio. Pixel const scale = Math.min(1, Math.sqrt(4e6 / (w * h))); const cw = Math.max(1, Math.floor(w * scale)), ch = Math.max(1, Math.floor(h * scale)); canvas.width = cw; canvas.height = ch; cs.width = w + 'px'; cs.height = h + 'px'; const g = canvas.getContext('2d'); g.clearRect(0, 0, cw, ch); const r = Math.max(6, 30 * scale); for (const p of points) { const x = p.x * cw, y = p.y * ch; const rg = g.createRadialGradient(x, y, 0, x, y, r); rg.addColorStop(0, 'rgba(0,0,0,0.25)'); rg.addColorStop(1, 'rgba(0,0,0,0)'); g.fillStyle = rg; g.fillRect(x - r, y - r, r * 2, r * 2); } // Deckkraft-Stufen in Farben umsetzen const img = g.getImageData(0, 0, cw, ch); const d = img.data; for (let i = 3; i < d.length; i += 4) { const a = d[i]; if (!a) continue; const k = Math.min(255, a * 3) * 4; d[i - 3] = ramp[k]; d[i - 2] = ramp[k + 1]; d[i - 1] = ramp[k + 2]; d[i] = Math.min(200, 60 + a); } g.putImageData(img, 0, 0); } function redrawIfChanged() { if (Math.max(de.scrollWidth, de.clientWidth) !== lastW || Math.max(de.scrollHeight, de.clientHeight) !== lastH) draw(); } function stop() { clearInterval(timer); window.removeEventListener('resize', draw); host.remove(); canvas.remove(); } close.addEventListener('click', stop); window.addEventListener('resize', draw); fetch(base + 'heat?token=' + encodeURIComponent(token), { credentials: 'omit' }) .then(function (r) { if (!r.ok) throw new Error('heat'); return r.json(); }) .then(function (j) { points = (j && Array.isArray(j.points) ? j.points : []).filter(function (p) { return p && p.x >= 0 && p.x <= 1 && p.y >= 0 && p.y <= 1; }); msg.textContent = 'Heatmap – ' + points.length + ' Klicks (letzte 30 Tage)'; draw(); timer = setInterval(redrawIfChanged, 1000); }) .catch(function () { msg.textContent = 'Heatmap-Link abgelaufen oder ungültig'; }); } try { main(); } catch (e) { /* nie die Seite stoeren */ } })();