Diff -- /var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/assets/javascript/admin_visitors_hover.js
Diff

/var/www/vhosts/3dshawn.com/ptmatrix.3dshawn.com/assets/javascript/admin_visitors_hover.js

added on local at 2026-07-01 12:34:02

Added
+130
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to c5344ab19cca
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1/*======================================================================
2 * TaskForge - admin_visitors hover wiring.
3 *
4 * Two IIFEs:
5 * 1. Daily line chart hover -- snap a vertical marker + colored
6 * dots on each series to the nearest day; floating tooltip
7 * shows the date and all three counts.
8 * 2. Section-row hover -- floating popover with the section's
9 * full path, view count, unique sessions, and bar share.
10 *
11 * Lives as a static file (not inline in the template) so the
12 * MODS::Template engine never touches it -- $/ regex anchors, \d
13 * shorthand, $-prefixed JS values etc. all work without escape
14 * acrobatics.
15 *====================================================================*/
16(function () {
17 'use strict';
18
19 // ---- Daily line chart hover ----------------------------------------
20 function wireLineChart() {
21 var card = document.querySelector('[data-main-chart-data]');
22 var svg = document.querySelector('[data-main-chart-svg]');
23 var overlay = document.querySelector('[data-main-chart-overlay]');
24 var line = document.querySelector('[data-main-chart-marker-line]');
25 var dotT = document.querySelector('[data-main-chart-dot-total]');
26 var dotU = document.querySelector('[data-main-chart-dot-unique]');
27 var dotS = document.querySelector('[data-main-chart-dot-users]');
28 var tip = document.querySelector('[data-main-chart-tip]');
29 if (!card || !svg || !overlay || !line || !dotT || !dotU || !dotS || !tip) return;
30
31 var pts;
32 try { pts = JSON.parse(card.textContent || '[]'); } catch (e) { pts = []; }
33 if (!pts.length) return;
34
35 var MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
36 function fmtDate(ymd) {
37 if (!ymd) return '';
38 var m = ymd.match(/^(\d{4})-(\d{2})-(\d{2})$/);
39 if (!m) return ymd;
40 return MONTHS[parseInt(m[2], 10) - 1] + ' ' + parseInt(m[3], 10) + ', ' + m[1];
41 }
42 function nearest(sx) {
43 var best = 0, bestD = Infinity;
44 for (var i = 0; i < pts.length; i++) {
45 var d = Math.abs(pts[i].x - sx);
46 if (d < bestD) { bestD = d; best = i; }
47 }
48 return pts[best];
49 }
50
51 overlay.addEventListener('mousemove', function (e) {
52 var rect = svg.getBoundingClientRect();
53 var vbW = svg.viewBox && svg.viewBox.baseVal ? svg.viewBox.baseVal.width : rect.width;
54 var sx = ((e.clientX - rect.left) / rect.width) * vbW;
55 var p = nearest(sx);
56 if (!p) return;
57 line.setAttribute('x1', p.x); line.setAttribute('x2', p.x);
58 line.style.display = '';
59 dotT.setAttribute('cx', p.x); dotT.setAttribute('cy', p.y_total); dotT.style.display = '';
60 dotU.setAttribute('cx', p.x); dotU.setAttribute('cy', p.y_unique); dotU.style.display = '';
61 dotS.setAttribute('cx', p.x); dotS.setAttribute('cy', p.y_users); dotS.style.display = '';
62 tip.innerHTML =
63 '<div style="color:#f0a3aa;font-size:11px;letter-spacing:1px;text-transform:uppercase;font-weight:700;margin-bottom:6px">' + fmtDate(p.date) + '</div>' +
64 '<div style="display:grid;grid-template-columns:auto auto;gap:6px 14px">' +
65 '<span style="color:#b94854">&#9679; Total visits</span><span style="text-align:right;font-family:ui-monospace,SFMono-Regular,monospace">' + p.total + '</span>' +
66 '<span style="color:#60a5fa">&#9679; Unique</span>' + '<span style="text-align:right;font-family:ui-monospace,SFMono-Regular,monospace">' + p.unique + '</span>' +
67 '<span style="color:#4ade80">&#9679; Logged-in</span>' + '<span style="text-align:right;font-family:ui-monospace,SFMono-Regular,monospace">' + p.users + '</span>' +
68 '</div>';
69 var wrapRect = tip.parentNode.getBoundingClientRect();
70 tip.style.left = (e.clientX - wrapRect.left) + 'px';
71 tip.style.top = (e.clientY - wrapRect.top) + 'px';
72 tip.style.display = 'block';
73 });
74 overlay.addEventListener('mouseleave', function () {
75 line.style.display = 'none';
76 dotT.style.display = 'none';
77 dotU.style.display = 'none';
78 dotS.style.display = 'none';
79 tip.style.display = 'none';
80 });
81 }
82
83 // ---- Section-row hover popover -------------------------------------
84 function wireSectionRows() {
85 var rows = document.querySelectorAll('.sec-row');
86 var stip = document.querySelector('[data-sec-tip]');
87 if (!rows.length || !stip) return;
88
89 function show(e) {
90 var row = e.currentTarget;
91 var section = row.getAttribute('data-section') || '';
92 var views = row.getAttribute('data-views') || '0';
93 var uniques = row.getAttribute('data-uniques') || '0';
94 var pct = row.getAttribute('data-pct') || '0';
95 stip.innerHTML =
96 '<div style="color:#f0a3aa;font-size:11px;letter-spacing:1px;text-transform:uppercase;font-weight:700;margin-bottom:6px">Site section</div>' +
97 '<div style="color:#fff;font-family:ui-monospace,SFMono-Regular,monospace;font-size:13px;margin-bottom:8px;word-break:break-all;white-space:normal">' + section + '</div>' +
98 '<div style="display:grid;grid-template-columns:auto auto;gap:4px 14px;font-size:12px">' +
99 '<span style="color:var(--col-text-3)">Page views</span>' + '<span style="text-align:right;color:#fff;font-family:ui-monospace,SFMono-Regular,monospace;font-weight:700">' + views + '</span>' +
100 '<span style="color:var(--col-text-3)">Unique sessions</span>' + '<span style="text-align:right;color:#fff;font-family:ui-monospace,SFMono-Regular,monospace;font-weight:700">' + uniques + '</span>' +
101 '<span style="color:var(--col-text-3)">Bar share</span>' + '<span style="text-align:right;color:#f0a3aa;font-family:ui-monospace,SFMono-Regular,monospace;font-weight:700">' + pct + '%</span>' +
102 '</div>';
103 stip.style.display = 'block';
104 move(e);
105 }
106 function move(e) {
107 // position:fixed -- coords are viewport pixels.
108 stip.style.left = e.clientX + 'px';
109 stip.style.top = e.clientY + 'px';
110 }
111 function hide() { stip.style.display = 'none'; }
112
113 for (var i = 0; i < rows.length; i++) {
114 rows[i].addEventListener('mouseenter', show);
115 rows[i].addEventListener('mousemove', move);
116 rows[i].addEventListener('mouseleave', hide);
117 }
118 }
119
120 // Wait for DOM if needed, then wire both handlers.
121 function init() {
122 wireLineChart();
123 wireSectionRows();
124 }
125 if (document.readyState === 'loading') {
126 document.addEventListener('DOMContentLoaded', init);
127 } else {
128 init();
129 }
130})();
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help