Upgrade heatmaps stile Hotjar: movimenti mouse e anteprima pagina live.

Traccia i movimenti aggregati, overlay a gradienti sull’iframe della pagina e toggle click/move in admin.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-20 23:03:12 +02:00
co-authored by Cursor
parent 8a07c5d569
commit b84321346f
26 changed files with 361 additions and 96 deletions
+59 -12
View File
@@ -1,17 +1,21 @@
/*! Match Live TV first-party site analytics (click + scroll). Requires analytics cookie consent. */
/*! Match Live TV first-party site analytics (click + mouse move + scroll). Requires analytics cookie consent. */
(function () {
"use strict";
var ENDPOINT = "/analytics/events";
var STORAGE_KEY = "mltv_cookie_consent";
var COOKIE_NAME = "mltv_cookie_consent";
var FLUSH_MS = 8000;
var MAX_QUEUE = 40;
var FLUSH_MS = 6000;
var MAX_QUEUE = 60;
var MOVE_THROTTLE_MS = 180;
var MOVE_GRID = 40;
var queue = [];
var moveBuckets = {};
var maxScroll = 0;
var flushTimer = null;
var started = false;
var lastMoveAt = 0;
function excludedPath(pathname) {
var p = pathname || "/";
@@ -44,6 +48,15 @@
return "desktop";
}
function pageSize() {
var doc = document.documentElement;
var body = document.body;
return {
width: Math.max(body.scrollWidth || 0, doc.scrollWidth || 0, window.innerWidth || 1),
height: Math.max(body.scrollHeight || 0, doc.scrollHeight || 0, window.innerHeight || 1)
};
}
function scrollPct() {
var doc = document.documentElement;
var body = document.body;
@@ -56,8 +69,7 @@
doc.clientHeight || 0
);
var view = window.innerHeight || doc.clientHeight || 0;
var maxScrollable = Math.max(height - view, 1);
var pct = ((scrollTop + view) / height) * 100;
var pct = ((scrollTop + view) / Math.max(height, 1)) * 100;
if (scrollTop <= 0 && view >= height) return 100;
return Math.max(0, Math.min(100, Math.round(pct)));
}
@@ -65,10 +77,31 @@
function pushEvent(evt) {
if (!started) return;
queue.push(evt);
if (queue.length >= MAX_QUEUE) flush(true);
if (queue.length >= MAX_QUEUE) flush(false);
}
function flushMovesIntoQueue() {
var keys = Object.keys(moveBuckets);
if (!keys.length) return;
var device = deviceBucket();
var path = location.pathname;
var ts = Date.now();
keys.forEach(function (key) {
var parts = key.split(",");
var cx = parseInt(parts[0], 10);
var cy = parseInt(parts[1], 10);
var n = moveBuckets[key];
if (!n) return;
// cell center in %
var x = ((cx + 0.5) / MOVE_GRID) * 100;
var y = ((cy + 0.5) / MOVE_GRID) * 100;
pushEvent({ type: "move", path: path, device: device, x: x, y: y, n: n, ts: ts });
});
moveBuckets = {};
}
function flush(useBeacon) {
flushMovesIntoQueue();
if (!queue.length || !hasAnalyticsConsent()) {
queue = [];
return;
@@ -107,10 +140,7 @@
if (!(target instanceof Element)) return;
if (target.closest("input, textarea, select, [contenteditable='true']")) return;
var doc = document.documentElement;
var body = document.body;
var width = Math.max(body.scrollWidth || 0, doc.scrollWidth || 0, window.innerWidth || 1);
var height = Math.max(body.scrollHeight || 0, doc.scrollHeight || 0, window.innerHeight || 1);
var size = pageSize();
var x = event.pageX;
var y = event.pageY;
if (typeof x !== "number" || typeof y !== "number") return;
@@ -119,12 +149,28 @@
type: "click",
path: location.pathname,
device: deviceBucket(),
x: Math.max(0, Math.min(100, (x / width) * 100)),
y: Math.max(0, Math.min(100, (y / height) * 100)),
x: Math.max(0, Math.min(100, (x / size.width) * 100)),
y: Math.max(0, Math.min(100, (y / size.height) * 100)),
ts: Date.now()
});
}
function onMouseMove(event) {
var now = Date.now();
if (now - lastMoveAt < MOVE_THROTTLE_MS) return;
lastMoveAt = now;
var size = pageSize();
var x = event.pageX;
var y = event.pageY;
if (typeof x !== "number" || typeof y !== "number") return;
var xPct = Math.max(0, Math.min(100, (x / size.width) * 100));
var yPct = Math.max(0, Math.min(100, (y / size.height) * 100));
var cx = Math.min(MOVE_GRID - 1, Math.max(0, Math.floor((xPct / 100) * MOVE_GRID)));
var cy = Math.min(MOVE_GRID - 1, Math.max(0, Math.floor((yPct / 100) * MOVE_GRID)));
var key = cx + "," + cy;
moveBuckets[key] = (moveBuckets[key] || 0) + 1;
}
function onScroll() {
var pct = scrollPct();
if (pct > maxScroll) maxScroll = pct;
@@ -157,6 +203,7 @@
maxScroll = scrollPct();
trackPageview();
document.addEventListener("click", onClick, true);
document.addEventListener("mousemove", onMouseMove, { passive: true });
window.addEventListener("scroll", onScroll, { passive: true });
document.addEventListener("visibilitychange", function () {
if (document.visibilityState === "hidden") {