diff --git a/public/analytics.js b/public/analytics.js
index 15b3d5ae..0f55c697 100644
--- a/public/analytics.js
+++ b/public/analytics.js
@@ -706,11 +706,18 @@
+
+
1-Byte Hash Usage Matrix
+
Each cell shows how many packets used that 1-byte hop prefix (row=high nibble, col=low nibble). Darker = more traffic.
+
+
+
`;
+ renderHashMatrix(data.topHops);
renderCollisions(data.topHops);
}
@@ -738,6 +745,58 @@
return svg;
}
+ function renderHashMatrix(topHops) {
+ const el = document.getElementById('hashMatrix');
+ const oneByteHops = topHops.filter(h => h.size === 1);
+ if (!oneByteHops.length) { el.innerHTML = 'No 1-byte hops
'; return; }
+
+ // Build 16x16 grid: row = high nibble, col = low nibble
+ const grid = Array.from({ length: 16 }, () => Array(16).fill(0));
+ let maxCount = 0;
+ for (const hop of oneByteHops) {
+ const byte = parseInt(hop.hex, 16);
+ if (isNaN(byte)) continue;
+ const hi = (byte >> 4) & 0xF;
+ const lo = byte & 0xF;
+ grid[hi][lo] = hop.count;
+ if (hop.count > maxCount) maxCount = hop.count;
+ }
+
+ const nibbles = '0123456789ABCDEF'.split('');
+ const cellSize = 36;
+ const headerSize = 24;
+
+ let html = ``;
+ // Header row
+ html += ` | `;
+ for (const n of nibbles) {
+ html += `${n} | `;
+ }
+ html += '
';
+
+ for (let hi = 0; hi < 16; hi++) {
+ html += `| ${nibbles[hi]} | `;
+ for (let lo = 0; lo < 16; lo++) {
+ const count = grid[hi][lo];
+ const hex = nibbles[hi] + nibbles[lo];
+ let bg = 'transparent';
+ let color = 'var(--text-muted)';
+ if (count > 0) {
+ const intensity = Math.log(count + 1) / Math.log(maxCount + 1);
+ const r = Math.round(34 + intensity * (239 - 34));
+ const g = Math.round(197 - intensity * (197 - 68));
+ const b = Math.round(94 - intensity * (94 - 68));
+ bg = `rgb(${r},${g},${b})`;
+ color = intensity > 0.5 ? '#fff' : 'var(--text)';
+ }
+ html += `${count || ''} | `;
+ }
+ html += '
';
+ }
+ html += '
';
+ el.innerHTML = html;
+ }
+
async function renderCollisions(topHops) {
const el = document.getElementById('collisionList');
const oneByteHops = topHops.filter(h => h.size === 1);