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
+38 -31
View File
@@ -30,6 +30,17 @@
@filters[:device]
) %>
</label>
<label class="admin-filter-field">
<span><%= t("admin.analytics.filters.layer") %></span>
<%= select_tag :layer,
options_for_select(
[
[t("admin.analytics.filters.layer_move"), "move"],
[t("admin.analytics.filters.layer_click"), "click"]
],
@filters[:layer]
) %>
</label>
</div>
<div class="admin-filter-actions">
<%= submit_tag t("admin.analytics.filters.apply"), class: "admin-btn admin-btn--primary admin-btn--sm" %>
@@ -45,9 +56,13 @@
<dt><%= t("admin.analytics.index.table.pageviews") %></dt>
<dd><%= @pageviews %></dd>
</div>
<div>
<dt><%= t("admin.analytics.index.table.moves") %></dt>
<dd><%= @move_total %></dd>
</div>
<div>
<dt><%= t("admin.analytics.index.table.clicks") %></dt>
<dd><%= @cells.values.sum %></dd>
<dd><%= @click_total %></dd>
</div>
<div>
<dt><%= t("admin.analytics.index.table.avg_scroll") %></dt>
@@ -73,43 +88,35 @@
</div>
<section class="panel">
<h3><%= t("admin.analytics.show.heatmap_title") %></h3>
<h3>
<%= @filters[:layer] == "click" ? t("admin.analytics.show.heatmap_clicks") : t("admin.analytics.show.heatmap_moves") %>
</h3>
<p class="muted admin-table-sub"><%= t("admin.analytics.show.heatmap_hint") %></p>
<% if @cells.any? %>
<div class="admin-heatmap-wrap">
<div class="admin-heatmap-stage" id="admin-heatmap-stage">
<% if @preview_url.present? %>
<iframe
class="admin-heatmap-frame"
src="<%= @preview_url %>"
title="<%= t("admin.analytics.show.preview_title") %>"
loading="lazy"
referrerpolicy="no-referrer"
></iframe>
<% else %>
<div class="admin-heatmap-fallback" aria-hidden="true"></div>
<p class="muted admin-heatmap-fallback-note"><%= t("admin.analytics.show.preview_unavailable") %></p>
<% end %>
<canvas
id="admin-heatmap"
class="admin-heatmap"
width="800"
height="1200"
class="admin-heatmap-overlay"
data-grid="<%= @grid %>"
data-max="<%= @max_clicks %>"
data-max="<%= [@max_weight, 1].max %>"
data-cells="<%= @cells.map { |(x, y), c| { x: x, y: y, c: c } }.to_json %>"
></canvas>
</div>
<script>
(function () {
var canvas = document.getElementById("admin-heatmap");
if (!canvas) return;
var grid = parseInt(canvas.getAttribute("data-grid"), 10) || 40;
var max = parseInt(canvas.getAttribute("data-max"), 10) || 1;
var cells = [];
try { cells = JSON.parse(canvas.getAttribute("data-cells") || "[]"); } catch (e) { cells = []; }
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
ctx.fillStyle = "#0d0d12";
ctx.fillRect(0, 0, cw, ch);
var cellW = cw / grid;
var cellH = ch / grid;
cells.forEach(function (cell) {
var intensity = Math.min(1, cell.c / max);
var alpha = 0.15 + intensity * 0.75;
ctx.fillStyle = "rgba(229, 57, 53, " + alpha + ")";
ctx.fillRect(cell.x * cellW, cell.y * cellH, cellW + 0.5, cellH + 0.5);
});
})();
</script>
<script src="/admin-analytics-heatmap.js?v=1" defer></script>
<% else %>
<p class="empty"><%= t("admin.analytics.show.no_clicks") %></p>
<p class="empty"><%= t("admin.analytics.show.no_points") %></p>
<% end %>
</section>