diff --git a/public/analytics.js b/public/analytics.js
index 7ada19f..e5e4f27 100644
--- a/public/analytics.js
+++ b/public/analytics.js
@@ -753,8 +753,6 @@
async 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; }
// Fetch all nodes for lookup
let allNodes = [];
@@ -763,23 +761,18 @@
allNodes = nd.nodes || [];
} catch {}
- // Build 16x16 grid
- 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;
+ // Build prefix → node count map
+ const prefixNodes = {};
+ for (let i = 0; i < 256; i++) {
+ const hex = i.toString(16).padStart(2, '0').toUpperCase();
+ prefixNodes[hex] = allNodes.filter(n => n.public_key.toUpperCase().startsWith(hex));
}
const nibbles = '0123456789ABCDEF'.split('');
const cellSize = 36;
const headerSize = 24;
- let html = ``;
+ let html = ``;
html += ` | `;
for (const n of nibbles) {
html += `${n} | `;
@@ -789,38 +782,47 @@
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)';
+ const nodes = prefixNodes[hex] || [];
+ const count = nodes.length;
+ let bg, color;
+ if (count === 0) {
+ bg = 'var(--bg-card, #1a1a2e)'; color = 'var(--text-muted)';
+ } else if (count === 1) {
+ bg = '#166534'; color = '#fff'; // green — free, single user
+ } else {
+ // 2+ nodes: interpolate yellow→red based on count
+ const t = Math.min((count - 2) / 4, 1); // 2=yellow, 6+=full red
+ const r = 239;
+ const g = Math.round(180 * (1 - t));
+ bg = `rgb(${r},${g},50)`; color = '#fff';
}
- const nodeCount = allNodes.filter(n => n.public_key.toLowerCase().startsWith(hex.toLowerCase())).length;
- html += `${count || ''} | `;
+ const status = count === 0 ? 'unused' : count === 1 ? `1 node: ${nodes[0].name || nodes[0].public_key.slice(0,12)}` : `${count} nodes — COLLISION`;
+ html += `${hex} | `;
}
html += '
';
}
html += '
';
- html += '
';
+ html += `
+
+ Unused
+ 1 node (free)
+ 2 nodes
+ 3+ nodes (collision)
+
`;
el.innerHTML = html;
// Click handler for cells
el.querySelectorAll('.hash-active').forEach(td => {
td.addEventListener('click', () => {
- const hex = td.dataset.hex.toLowerCase();
- const matches = allNodes.filter(n => n.public_key.toLowerCase().startsWith(hex));
+ const hex = td.dataset.hex.toUpperCase();
+ const matches = prefixNodes[hex] || [];
const detail = document.getElementById('hashDetail');
if (!matches.length) {
- detail.innerHTML = `0x${hex.toUpperCase()}
No known nodes`;
+ detail.innerHTML = `0x${hex}
No known nodes`;
return;
}
- detail.innerHTML = `0x${hex.toUpperCase()} — ${matches.length} node${matches.length !== 1 ? 's' : ''}` +
+ detail.innerHTML = `0x${hex} — ${matches.length} node${matches.length !== 1 ? 's' : ''}` +
`${matches.map(m => {
const coords = (m.lat && m.lon && !(m.lat === 0 && m.lon === 0))
? `
(${m.lat.toFixed(2)}, ${m.lon.toFixed(2)})`
@@ -828,7 +830,6 @@
const role = m.role ? `
${esc(m.role)} ` : '';
return `
`;
}).join('')}
`;
- // Highlight selected cell
el.querySelectorAll('.hash-selected').forEach(c => c.classList.remove('hash-selected'));
td.classList.add('hash-selected');
});