// WEBklar Analyse: Heatmap-Overlay (wird von w.js nur mit gueltigem ?__wk_heat=Token geladen). // data-embed="1" (nur in einem Rahmen): Ansicht im Geraeterahmen des Kundenportals. Dann ohne Leiste, // Links und Formulare gesperrt, und die Klickzahl geht per postMessage nur an das Portal (Herkunft von h.js). 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; let portal = ''; try { portal = new URL(base).origin; } catch (e) { /* ohne Portal keine Einbettung */ } const embed = !!portal && me.getAttribute('data-embed') === '1' && window.parent && window.parent !== window; function tell(state, count) { if (!embed) return; try { window.parent.postMessage({ wk: 'heat', state: state, count: count }, portal); } catch (e) { /* egal */ } } 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; })(); if (embed) { // Nur ansehen: kein Seitenwechsel (er wuerde zaehlen und das Banner zeigen) const block = function (e) { const t = e.target; if (e.type === 'submit' || (t && t.closest && t.closest('a,area'))) { e.preventDefault(); e.stopPropagation(); } }; window.addEventListener('click', block, true); window.addEventListener('submit', block, true); // Handy und Tablet haben ueberlagernde Scrollleisten; so stimmt die Breite wie auf dem Geraet const st = document.createElement('style'); st.textContent = 'html{scrollbar-width:none}html::-webkit-scrollbar{display:none}'; (document.head || de).appendChild(st); start(function (n) { tell('ready', n); }, function () { tell('error', 0); }); return; } 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); 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); start(function (n) { msg.textContent = 'Heatmap – ' + n + ' Klicks (letzte 30 Tage)'; }, function () { msg.textContent = 'Heatmap-Link abgelaufen oder ungültig'; }); // Punkte laden, zeichnen und bei Groessenaenderung neu zeichnen function start(onReady, onError) { 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; }); draw(); timer = setInterval(redrawIfChanged, 1000); onReady(points.length); }) .catch(function () { onError(); }); } } try { main(); } catch (e) { /* nie die Seite stoeren */ }