Diff -- /var/www/vhosts/3dshawn.com/abforge.3dshawn.com/TEMPLATES/abforge_heatmaps.html

O Operator
Diff

/var/www/vhosts/3dshawn.com/abforge.3dshawn.com/TEMPLATES/abforge_heatmaps.html

added on local at 2026-07-01 21:00:04

Added
+112
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to e85aacf6236f
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1<div class="page-header">
2 <div><h1>Heatmaps</h1><p>$site_name &middot; click density overlay.</p></div>
3</div>
4
5[if:!$has_pages]
6<div class="card">
7 <p class="muted">No heatmap data yet. Once visitors start clicking, the most popular pages will show up here.</p>
8</div>
9[/if]
10[if:$has_pages]
11<div class="row-2">
12 <div class="card">
13 <div class="card-header"><h3 class="card-title">Pages with click data</h3></div>
14 <table class="table">
15 <thead><tr><th>Page</th><th class="right">Clicks</th><th></th></tr></thead>
16 <tbody>
17 [loop:@pages]
18 <tr><td style="max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">$page_path</td><td class="num">$clicks</td><td class="right"><a class="btn btn-sm btn-ghost" href="/heatmaps.cgi?site_id=$site_id&p=$page_path">View</a></td></tr>
19 [/loop]
20 </tbody>
21 </table>
22 </div>
23
24 <div class="card">
25 <div class="card-header"><h3 class="card-title">Heatmap &mdash; $page_path</h3><span class="badge">$n_clicks clicks</span></div>
26 <p class="muted text-sm">Live page underneath, click density overlaid on top. Hot zones = most clicks.</p>
27 <div style="position:relative; background: linear-gradient(135deg, #1f263a 0%, #161c2d 100%); border-radius:8px; overflow:hidden; aspect-ratio: 16/10;">
28 <!-- The customer's actual page rendered underneath. If their site
29 sends X-Frame-Options: DENY / SAMEORIGIN, the iframe stays
30 blank and only the fallback background + overlay is shown. -->
31 <iframe id="hm-page"
32 src="https://$domain$page_path"
33 style="position:absolute; inset:0; width:100%; height:100%; border:0; background:#0e1420; filter: brightness(0.35) contrast(0.85) saturate(0.7);"
34 sandbox="allow-same-origin"
35 referrerpolicy="no-referrer"
36 loading="lazy"></iframe>
37 <div aria-hidden="true"
38 style="position:absolute; inset:0; background: rgba(10,14,22,0.35); pointer-events:none;"></div>
39 <canvas id="hm-canvas"
40 style="position:absolute; inset:0; width:100%; height:100%; display:block; pointer-events:none; mix-blend-mode:screen;"></canvas>
41 </div>
42 <div id="hm-blocked-hint" class="muted text-sm" style="display:none; margin-top:8px;">
43 Couldn&rsquo;t embed <code>$domain</code> in a preview frame (the site blocks iframing). The click overlay is still accurate. To see it on top of the real page, install the ABForge browser preview extension.
44 </div>
45 </div>
46</div>
47
48<script>
49(function() {
50 var pts = $points;
51 var c = document.getElementById('hm-canvas');
52 if (!c) return;
53 var W = c.clientWidth, H = c.clientHeight;
54 c.width = W; c.height = H;
55 var ctx = c.getContext('2d');
56 // Detect iframe load failure (X-Frame-Options / CSP) — chrome fires
57 // 'load' either way, so we probe by reading document.title of the
58 // frame; if same-origin blocked or cross-origin, we can't read it.
59 var iframe = document.getElementById('hm-page');
60 if (iframe) {
61 var hint = document.getElementById('hm-blocked-hint');
62 iframe.addEventListener('error', function(){ if(hint) hint.style.display='block'; });
63 // Give the browser a beat to attempt the load, then check state.
64 setTimeout(function(){
65 try {
66 // Cross-origin frame throws on doc access — that's actually the
67 // NORMAL case for external customer sites. Only show the hint
68 // when the frame is visibly empty (contentDocument is same-
69 // origin AND has no head/body loaded).
70 var doc = iframe.contentDocument;
71 if (doc && (!doc.body || !doc.body.childNodes.length)) {
72 if(hint) hint.style.display='block';
73 }
74 } catch(e) { /* cross-origin: page probably loaded fine */ }
75 }, 2500);
76 }
77 if (!pts.length) {
78 ctx.fillStyle = '#7a8499'; ctx.font = '13px Inter';
79 ctx.fillText('No clicks on this page yet.', 24, 24);
80 return;
81 }
82 // Off-screen accumulation buffer
83 var buf = document.createElement('canvas'); buf.width = W; buf.height = H;
84 var bctx = buf.getContext('2d');
85 for (var i = 0; i < pts.length; i++) {
86 var x = pts[i][0] / 100 * W;
87 var y = pts[i][1] / 100 * H;
88 var g = bctx.createRadialGradient(x, y, 0, x, y, 28);
89 g.addColorStop(0, 'rgba(255,255,255,0.25)');
90 g.addColorStop(0.5, 'rgba(255,255,255,0.1)');
91 g.addColorStop(1, 'rgba(255,255,255,0)');
92 bctx.fillStyle = g;
93 bctx.beginPath(); bctx.arc(x, y, 28, 0, Math.PI*2); bctx.fill();
94 }
95 // Colorize alpha into a hot gradient
96 var img = bctx.getImageData(0, 0, W, H);
97 var d = img.data;
98 for (var p = 0; p < d.length; p += 4) {
99 var a = d[p+3];
100 if (a > 0) {
101 // sunset gradient
102 var t = a / 255;
103 d[p] = Math.round(255 * Math.min(1, 0.5 + t));
104 d[p+1] = Math.round(255 * Math.min(1, t * 0.6));
105 d[p+2] = Math.round(80 * (1 - t));
106 d[p+3] = Math.round(200 * t);
107 }
108 }
109 ctx.putImageData(img, 0, 0);
110})();
111</script>
112[/if]