Files
Webklar-Kundenbereich/analytics/client/dist/h.js
T

215 lines
8.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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;
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;
const age = now - c.t;
if (age < 0 || age >= CONSENT_MAX_AGE) return null;
return { v: 1, a: c.a, t: c.t };
} 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: 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 */ }
})();