Analytics: Banner-Skript und Heatmap-Overlay
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
305939fdbd
commit
5930f052d7
7 files changed
+1379
No files matched your search
@@ -0,0 +1,120 @@
|
||||
// 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 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 = '/';
|
||||
try {
|
||||
p = new URL(String(href == null ? '' : href), '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 ''; }
|
||||
}
|
||||
|
||||
// Erzeugt wirkende Namen (css-1a2b3c, ember12345, 9f8e7d6c5b4a ...) erkennen.
|
||||
function looksGenerated(s) {
|
||||
if (/\d{4,}/.test(s)) return true;
|
||||
const mixed = (t, min) => t.length >= min && /\d/.test(t) && /[a-z]/i.test(t);
|
||||
if (mixed(s, 10) && !/[-_]/.test(s)) return true;
|
||||
if (/^[a-f0-9]{8,}$/i.test(s) && /\d/.test(s)) return true;
|
||||
return s.split(/[-_]/).some((p) => mixed(p, 6));
|
||||
}
|
||||
|
||||
const safeName = (s) => String(s).replace(/[^\w-]/g, '');
|
||||
|
||||
export function cssPath(el) {
|
||||
const parts = [];
|
||||
let n = el;
|
||||
while (n && n.tagName && parts.length < 5) {
|
||||
const tag = String(n.tagName).toLowerCase();
|
||||
let seg = tag;
|
||||
const id = n.id ? safeName(n.id) : '';
|
||||
if (id && !looksGenerated(id)) {
|
||||
parts.unshift(seg + '#' + id);
|
||||
break;
|
||||
}
|
||||
const raw = n.getAttribute ? n.getAttribute('class') : '';
|
||||
const cls = String(raw || '').split(/\s+/).map(safeName).filter((c) => c && !looksGenerated(c)).slice(0, 2);
|
||||
if (cls.length) seg += '.' + cls.join('.');
|
||||
parts.unshift(seg);
|
||||
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';
|
||||
}
|
||||
|
||||
export 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; }
|
||||
}
|
||||
|
||||
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]));
|
||||
}
|
||||
Reference in new issue
Block a user