0xa89f3db4…071asent to0xecb92cc7…1463·#25,767,123·view on Etherscan
drag.y = drag.y0 = e.clientY;
drag.vx = drag.vy = 0; cv.style.cursor = 'grabbing';
});
window.addEventListener('mousemove', e => {
if (!drag.on) {
cv.style.cursor = hitLabel(e.clientX, e.clientY) >= 0 ? 'pointer' : 'grab';
return;
}
dragBy(e.clientX - drag.x, e.clientY - drag.y);
drag.x = e.clientX; drag.y = e.clientY;
});
window.addEventListener('mouseup', e => {
drag.on = false; cv.style.cursor = 'grab';
if (Math.hypot(e.clientX - drag.x0, e.clientY - drag.y0) > 5) {
view.tYaw += drag.vx * 12; // a real drag: the throw carries
view.tPitch = clampPitch(view.tPitch + drag.vy * 12);
} else if (G && e.target === cv) { // a click: jump to that node's network
let hit = hitLabel(e.clientX, e.clientY); // a name counts as its node
if (hit < 0) hit = hitTest(e.clientX, e.clientY);
if (hit >= 0) select(hit); else clearSel();
}
});
window.addEventListener('mouseleave', () => { drag.on = false; cv.style.cursor = 'grab'; });
// zoom: wheel dollies exponentially (trackpad pinch arrives as ctrl+wheel),
// two-finger pinch on touch, double-click resets — the orbit-view conventions
function zoomBy(f) {
view.tZoom = Math.min(10, Math.max(0.4, view.tZoom * f));
view.lastInput = performance.now();
}
window.addEventListener('wheel', e => {
e.preventDefault();
zoomBy(Math.exp(-e.deltaY * (e.ctrlKey ? 0.01 : 0.0015)));
}, { passive: false });
window.addEventListener('dblclick', () => { view.tZoom = 1; view.lastInput = performance.now(); });
let pinchD = 0;
window.addEventListener('touchstart', e => {
if (e.touches.length === 2)
pinchD = Math.hypot(e.touches[0].clientX - e.touches[1].clientX,
e.touches[0].clientY - e.touches[1].clientY);
});
window.addEventListener('touchstart', e => {
if (e.touches.length === 1) {
drag.x = drag.x0 = e.touches[0].clientX;
drag.y = drag.y0 = e.touches[0].clientY;
}
});
window.addEventListener('touchmove', e => {
e.preventDefault();
if (e.touches.length === 2) {
const d = Math.hypot(e.touches[0].clientX - e.touches[1].clientX,
e.touches[0].clientY - e.touches[1].clientY);
if (pinchD) zoomBy(d / pinchD);
pinchD = d;
} else {
const t = e.touches[0];
dragBy(t.clientX - drag.x, t.clientY - drag.y);
drag.x = t.clientX; drag.y = t.clientY;
}
}, { passive: false });
window.addEventListener('touchend', e => {
const wasPinch = pinchD !== 0;
pinchD = 0;
if (wasPinch || !G || !e.changedTouches.length) return;
const t = e.changedTouches[0];
if (Math.hypot(t.clientX - drag.x0, t.clientY - drag.y0) <= 8 && e.target === cv) {
let hit = hitLabel(t.clientX, t.clientY); // a tap on a name counts as its node
if (hit < 0) hit = hitTest(t.clientX, t.clientY);
if (hit >= 0) select(hit); else clearSel();
}
});
window.addEventListener('keydown', e => {
const el = document.activeElement;
if (el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' ||
el.tagName === 'SELECT' || el.isContentEditable)) return;
const k = e.key.toLowerCase();
if (k === 'a') toggleArtists();
else if (k === 'p') togglePatrons();
else if (k === 'escape') clearSel();
});
let rT = 0;
window.addEventListener('resize', () => {
clearTimeout(rT);
rT = setTimeout(() => { if (G) { tiers(); sizeCanvas(); view.dirty = true; } }, 100);
});
// ---------------- boot ----------------
async function boot() {
sizeCanvas();
rafId = requestAnimationFrame(tick);
status('reading the chain…');
try {
await loadChain();
st.progress = '';
if (G) stats();
} catch (e) {
st.progress = '';
if (G) stats();
else {
status('the chain is unreachable');
ctx.fillStyle = '#000';
ctx.fillRect(window.innerWidth / 2 - 1, window.innerHeight / 2 - 1, 2, 2);
rpcPrompt();
}
}
booted = true; // from here on, new threads flash as they land
setTimeout(poll, 60000);
}
// last resort: let the viewer bring their own endpoint. heals in memory and
// in place — on-chain contexts (data: URIs, sandboxed iframes) have no
// storage and no query string, so persistence is a bonus, never a dependency
function rpcPrompt() {
if (document.getElementById('rp')) return;
const inp = document.createElement('input');
inp.id = 'rp';
inp.placeholder = 'paste a mainnet rpc url and press enter…';
inp.style.cssText = 'position:fixed;left:12px;bottom:28px;width:300px;' +
'font:10px monospace;color:#000;background:#fff;border:1px solid #000;' +
'padding:3px 5px;outline:none';
inp.addEventListener('keydown', async e => {
if (e.key !== 'Enter') return;
const v = inp.value.trim();
if (!/^https?:\/\//.test(v)) return;
try { localStorage.setItem(RPC_KEY, v); } catch {} // remembered where origins allow
RPCS.unshift(v); // healed right now, regardless
inp.disabled = true;
status('reading the chain…');
try {
await loadChain();
st.progress = '';
stats();
inp.remove();
} catch (err) {
status('the chain is unreachable');
inp.disabled = false;
inp.select();
}
});
document.body.appendChild(inp);
}
boot();
})();
</script>
</body>
</html>