memoscan
← all memos

Memo 0x82d41ea9…dffa2b on Ethereum

l budget: at future scale a full sweep would be soup — the earliest // arrivals (the founders) get named first, zoom + click reach the rest if (st.artists) for (let i = 0; i < G.A && done.size < 900; i++) put(i, aName(i)); if (st.patrons) for (let p = 0; p < G.P && done.size < 1800; p++) put(G.A + p, pName(G.A + p)); } // ---------------- main loop ---------------- // moving: cleared sketch each frame (sampled threads, boosted alpha) // at rest: one clear, then the full ink settles back in over frames const SKETCH_MAX = 30000; let rafId = 0; function tick(now) { if (!G) { rafId = requestAnimationFrame(tick); return; } const w = window.innerWidth, h = window.innerHeight; // idle: the globe turns, slowly, always — the ink gets its 3s to settle first if (now - view.lastInput > 3000 && !drag.on) view.tYaw += 0.0008; // stats fade with the observer's attention; never while the chain is streaming sBox.classList.toggle('hid', now - uiLast > 4000 && !st.progress && !!G && st.drawn >= G.links); const d = Math.abs(view.tYaw - view.yaw) + Math.abs(view.tPitch - view.pitch) + Math.abs(view.tZoom - view.zoom); const moving = d > 0.0006; if (moving) { view.yaw += (view.tYaw - view.yaw) * 0.12; view.pitch += (view.tPitch - view.pitch) * 0.12; view.zoom += (view.tZoom - view.zoom) * 0.18; } setMatrix(w, h); // growth advances regardless of rotation if (st.drawn < G.links) { st.drawn = Math.min(G.links, st.drawn + st.chunk); stats(); } if (moving) { clearAll(); const stride = Math.max(1, Math.ceil(st.drawn / SKETCH_MAX)); if (st.drawn) drawLinks3(0, st.drawn, Math.min(0.7, st.alpha * Math.sqrt(stride)), stride); drawArtists(); view.dirty = true; view.full = 0; } else { if (view.dirty) { // first still frame: restart the ink clearAll(); drawArtists(); view.full = 0; view.dirty = false; } if (view.full < st.drawn) { const fast = Math.max(st.chunk * 12, 20000); const to = Math.min(st.drawn, view.full + fast); drawLinks3(view.full, to, st.alpha, 1); view.full = to; } } drawLabels(); if (FLASH.length) { // fresh threads glow, then fade into the web FLASH = FLASH.filter(f => now - f.t < 5000); for (const f of FLASH) { const a = 0.9 * (1 - (now - f.t) / 5000); const p1 = project(G.dP[f.i]); const x1 = p1[0], y1 = p1[1]; const p2 = project(G.dA[f.i]); octx.strokeStyle = `rgba(255,255,255,${a})`; octx.lineWidth = 1.6; octx.beginPath(); octx.moveTo(x1, y1); octx.lineTo(p2[0], p2[1]); octx.stroke(); } } rafId = requestAnimationFrame(tick); } // ---------------- input ---------------- // grab the globe: drag rotates so the face under the cursor follows the hand, // release keeps a little inertia const drag = { on: false, x: 0, y: 0, x0: 0, y0: 0, vx: 0, vy: 0 }; const clampPitch = v => Math.min(1.4, Math.max(-1.4, v)); function dragBy(dx, dy) { const k = Math.PI / Math.min(window.innerWidth, window.innerHeight); drag.vx = -dx * k; drag.vy = dy * k; view.tYaw += drag.vx; view.tPitch = clampPitch(view.tPitch + drag.vy); view.lastInput = performance.now(); } cv.style.cursor = 'grab'; window.addEventListener('mousedown', e => { drag.on = true; drag.x = drag.x0 = e.clientX; 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 vessel…'); try { await loadVessel(); await loadChain(); st.progress = ''; if (G) stats(); } catch (e) { st.progress = ''; if (G) stats(); else { status('the chain is unreachable'); ctx.fillStyle = '#fff'; 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:#fff;background:#000;border:1px solid #fff;' + '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>