- Messskript: data-variant="eigen" zeigt eine eigene Banner-Fassung (Sie-Form, WEBklar misst selbst, BANNER_VERSION_EIGEN 2026-10-17); Kunden-Websites bleiben bei der Fassung 2026-10-02. - config.analyse.eigeneWebsite: fester Schlüssel und Domains von webklar.com; ensureEigeneWebsite zieht den Eintrag im Messdienst täglich nach (aktiv: false schaltet ab). Kein Kunde, kein Abo, keine Tickets. - /api/admin/analyse-eigen (jeder Mitarbeiter): Kennzahlen 7/30/90 Tage und Heatmap-Link auf webklar.com. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
128 lines
6.0 KiB
JavaScript
128 lines
6.0 KiB
JavaScript
// 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.
|
|
|
|
export const CONSENT_KEY = 'wk-consent';
|
|
export const CONSENT_MAX_AGE = 183 * 86400000;
|
|
export const BANNER_VERSION = '2026-10-02';
|
|
export 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: ';
|
|
// Eigene Websites der WEBklar GbR (webklar.com, Skript-Attribut data-variant="eigen"): Sie-Form, WEBklar misst selbst
|
|
// (keine Auftragsverarbeitung). Eigene Fassung, damit eine Textaenderung nur dort neu fragt.
|
|
export const BANNER_VERSION_EIGEN = '2026-10-17';
|
|
export const BANNER_TEXT_EIGEN = 'Um unsere Website zu verbessern, zählen wir Seitenaufrufe mit Land und Gerätetyp, ohne Kennung. Mit Ihrer Zustimmung messen wir zusätzlich, von welcher Website Sie kommen, wie lange Sie bleiben, wie weit Sie scrollen und wo Sie klicken (Heatmap), mit einer täglich wechselnden Kennung. Wir messen selbst auf unserem Server in Deutschland, ohne Cookies und ohne Werbung. Nach „Ablehnen“ zählen wir nur noch, dass Sie abgelehnt haben. Ihre Zustimmung können Sie jederzeit über „Datenschutz-Einstellungen“ mit Wirkung für die Zukunft widerrufen. Details: ';
|
|
export const BANNER_LINK_LABEL = 'Datenschutz';
|
|
export const BANNER_ARIA = 'Einwilligung zur Reichweitenmessung';
|
|
export const SETTINGS_LABEL = 'Datenschutz-Einstellungen';
|
|
|
|
export function vpClass(width) {
|
|
const w = Number(width);
|
|
if (w < 768) return 'm';
|
|
if (w < 1024) return 't';
|
|
return 'd';
|
|
}
|
|
|
|
export 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\./, '');
|
|
|
|
export 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).
|
|
export 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();
|
|
}
|
|
|
|
export function linkHost(a) {
|
|
try { return new URL(parseHref(a)).hostname.toLowerCase(); } catch (e) { return ''; }
|
|
}
|
|
|
|
export 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';
|
|
}
|
|
|
|
// Banner-Fassung je Variante: 'eigen' fuer WEBklars eigene Websites, alles andere ist die Kunden-Fassung.
|
|
export function bannerFor(variant) {
|
|
return variant === 'eigen'
|
|
? { version: BANNER_VERSION_EIGEN, text: BANNER_TEXT_EIGEN }
|
|
: { version: BANNER_VERSION, text: BANNER_TEXT };
|
|
}
|
|
|
|
export function readConsent(storage, now, version = BANNER_VERSION) {
|
|
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 !== 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; }
|
|
}
|
|
|
|
export 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;
|
|
}
|
|
|
|
export function escapeHtml(s) {
|
|
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
|
|
}
|