0xecb9…1463

All memos sent from and to 0xecb9…1463.

A minimalist black-and-white graphic logo of a stylized creature. Bold solid black silhouette, crisp white outlines and negative space highlights. Modern vector icon design, high contrast, clean white background.
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>
(i) ? '#fff' : 'rgba(255,255,255,0.12)'; const p = project(i); const r = Math.max(1, (2.4 - 1.6 * (i / G.A)) * p[2] * 0.8); ctx.fillRect(p[0] - r / 2, p[1] - r / 2, r, r); } } // one stroking pass over [from,to): fixed absolute batches so accumulation is // repaint-invariant; pred filters which links belong to this pass function strokePass(from, to, stride, alpha, pred, lw) { const { dP, dA } = G; ctx.strokeStyle = `rgba(255,255,255,${alpha})`; ctx.lineWidth = lw || (G.links > 150000 ? 0.5 : 0.8); const B = (G.links > 200000 ? 64 : 8) * stride; let i = from; while (i < to) { const end = Math.min(to, (Math.floor(i / B) + 1) * B); ctx.beginPath(); for (; i < end; i += stride) { if (pred && !pred(i)) continue; const a = project(dP[i]); const x1 = a[0], y1 = a[1]; const b = project(dA[i]); ctx.moveTo(x1, y1); ctx.lineTo(b[0], b[1]); } ctx.stroke(); } } function drawLinks3(from, to, alpha, stride) { if (SEL) { // the focused node's threads at full strength, the rest of the web ghosted strokePass(from, to, stride, alpha * 0.1, i => !inSel(i)); strokePass(from, to, stride, Math.min(0.75, Math.max(0.5, alpha * 3)), inSel, 1.4); } else { strokePass(from, to, stride, alpha, null); } if (st.dots && stride === 1) { const { dP } = G; for (let j = from; j < to; j++) if (dP[j] >= G.A) { ctx.fillStyle = !SEL || dP[j] === SEL.node || SEL.nbrs.has(dP[j]) ? 'rgba(255,255,255,0.9)' : 'rgba(255,255,255,0.12)'; const p = project(dP[j]); const r = Math.max(0.6, p[2] * 0.9); ctx.fillRect(p[0] - r / 2, p[1] - r / 2, r, r); } } } const fmt = n => n.toLocaleString('en-US'); function status(t) { sEl.textContent = t; } // while a node is focused, the stats line says exactly what you are looking at function selInfo() { let a = 0, p = 0; for (const nb of SEL.nbrs) nb < G.A ? a++ : p++; const nm = SEL.node < G.A ? (G.addrs ? nameOf(G.addrs[SEL.node]) : 'artist ' + SEL.node) : (G.paddrs && G.paddrs[SEL.node - G.A] ? nameOf(G.paddrs[SEL.node - G.A]) : 'patron ' + (SEL.node - G.A)); const parts = []; if (a) parts.push(`collects from ${fmt(a)} artist${a > 1 ? 's' : ''}`); if (p) parts.push(`collected by ${fmt(p)} patron${p > 1 ? 's' : ''}`); return nm + (parts.length ? ' · ' + parts.join(' · ') : ' · no connections yet'); } function stats() { // while growing, count only the wallets the web has reached so far; // complete, count the whole constellation (unlinked artists included) // say what is shown: the woven crew, against the whole crew aboard const w = st.drawn < G.links ? G.wcum[st.drawn] : G.A + G.P; const lead = HOLDERS ? `${fmt(w)} of ${fmt(HOLDERS.size)} holders` : `${fmt(w)} wallets`; status((SEL ? selInfo() : `${lead} · ${fmt(st.drawn)} connections`) + (st.progress ? ` · ${st.progress}` : '')); const show = G && G.addrs ? '' : 'none'; // names need real addresses abEl.style.display = show; pbEl.style.display = show; } function toggleArtists() { st.artists = !st.artists; abEl.classList.toggle('on', st.artists); if (st.artists && G) ensureNames(G.addrs); } function togglePatrons() { st.patrons = !st.patrons; pbEl.classList.toggle('on', st.patrons); if (st.patrons && G) ensureNames(G.paddrs); } abEl.addEventListener('click', toggleArtists); pbEl.addEventListener('click', togglePatrons); // ---------------- names (a: artists, p: patrons) ---------------- // ENS reverse records, batched in one eth_call; short address as fallback. // labels ride a separate overlay canvas so they never pollute the ink. const RR = '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C'; // ReverseRecords, mainnet const ENS_KEY = 'networked.ens.v1'; const short = a => a.slice(0, 6) + '…' + a.slice(-4); function encodeGetNames(addrs) { let d = '0xcbf8b66c' + '20'.padStart(64, '0') + addrs.length.toString(16).padStart(64, '0'); for (const a of addrs) d += a.slice(2).toLowerCase().padStart(64, '0'); return d; } function decodeStrings(res) { const h = res.slice(2); const N = i => parseInt(h.slice(i * 64, i * 64 + 64), 16); const base = N(0) / 32; const n = N(base); const dec = new TextDecoder(); const out = []; for (let k = 0; k < n; k++) { const so = base + 1 + N(base + 1 + k) / 32; const len = N(so); const bytes = h.slice((so + 1) * 64, (so + 1) * 64 + len * 2); out.push(dec.decode(Uint8Array.from(bytes.match(/../g) || [], x => parseInt(x, 16)))); } return out; } // one wallet with a malicious resolver reverts a whole ReverseRecords batch — // bisect on revert until the poisoned address is isolated and skipped async function fetchNames(batch, cache) { try { const names = decodeStrings(await rpc('eth_call', [{ to: RR, data: encodeGetNames(batch) }, 'latest'])); batch.forEach((a, j) => { cache[a] = names[j] || ''; }); } catch (e) { if (!/revert|execution/i.test(String(e && e.message))) throw e; // transport problem, not poison if (batch.length === 1) { cache[batch[0]] = ''; return; } const mid = batch.length >> 1; await Promise.all([fetchNames(batch.slice(0, mid), cache), fetchNames(batch.slice(mid), cache)]); } } // the name book: one shared address→name cache, streamed in by batches let BOOK = null; function loadBook() { if (BOOK) return; BOOK = {}; try { BOOK = JSON.parse(localStorage.getItem(ENS_KEY)) || {}; } catch {} } const nameOf = a => { loadBook(); return BOOK[a] || short(a); }; const namesPending = new Set(); // the reasonable ceiling: ~10 batched calls. beyond it, later arrivals keep // short addresses — the label budget only names the earliest anyway, and // selections resolve their own neighborhood on demand. const MAX_ENS = 4000; async function ensureNames(list) { if (!list || !list.length) return; loadBook(); let missing = list.filter(a => BOOK[a] === undefined && !namesPending.has(a)); if (missing.length > MAX_ENS) missing = missing.slice(0, MAX_ENS); missing.forEach(a => namesPending.add(a)); for (let k = 0; k < missing.length; k += 400) { try { await fetchNames(missing.slice(k, k + 400), BOOK); } catch { missing.slice(k).forEach(a => namesPending.delete(a)); return; } // retry later try { localStorage.setItem(ENS_KEY, JSON.stringify(BOOK)); } catch {} } } // ---------------- selection (click a node to focus its network) ---------------- let SEL = null; function neighborsOf(node) { const nbrs = new Set(); const { dP, dA, links } = G; for (let i = 0; i < links; i++) { if (dP[i] === node) nbrs.add(dA[i]); else if (dA[i] === node) nbrs.add(dP[i]); } return nbrs; } const addrOf = n => n < G.A ? (G.addrs ? G.addrs[n] : null) : (G.paddrs ? G.paddrs[n - G.A] : null); function reselect(node) { SEL = { node, nbrs: neighborsOf(node) }; if (G.addrs) { // name the neighborhood on demand — always cheap const want = [addrOf(node)]; let n = 0; for (const nb of SEL.nbrs) { if (n++ > 600) break; want.push(addrOf(nb)); } ensureNames(want.filter(Boolean)); } } function select(node) { reselect(node); // rotate the globe so this node comes front and center const x = mx[node], y = my[node], z = mz[node]; const hxz = Math.hypot(x, z) || 1e-6; let ty = Math.atan2(x, -z); ty += Math.round((view.yaw - ty) / (2 * Math.PI)) * 2 * Math.PI; // nearest whole turn view.tYaw = ty; view.tPitch = Math.min(1.5, Math.max(-1.5, Math.atan2(-y, hxz))); view.lastInput = performance.now(); view.dirty = true; stats(); } function clearSel() { if (SEL) { SEL = null; view.dirty = true; stats(); } } function inSel(i) { return G.dP[i] === SEL.node || G.dA[i] === SEL.node; } function hitTest(x, y) { const maxN = G.A + Math.min(G.P, 200000); // huge sims: artists only past this let best = -1, bd = 144; // within 12px for (let i = 0; i < maxN; i++) { const p = project(i); const dx = p[0] - x, dy = p[1] - y; const d = dx * dx + dy * dy; if (d < bd) { bd = d; best = i; } } return best; } let labelBoxes = []; // a visible name IS its node function hitLabel(x, y) { for (const b of labelBoxes) if (x >= b.x - 3 && x <= b.x + b.w + 3 && y >= b.y - 9 && y <= b.y + 9) return b.n; return -1; } function drawLabels() { const w = window.innerWidth, h = window.innerHeight; octx.clearRect(0, 0, w, h); labelBoxes = []; if (!G) return; octx.font = '10px monospace'; octx.textBaseline = 'middle'; octx.lineWidth = 3; octx.strokeStyle = '#000'; octx.fillStyle = '#fff'; const done = new Set(); const put = (node, text) => { if (done.has(node)) return; done.add(node); const p = project(node); if (p[0] < -120 || p[0] > w + 40 || p[1] < -10 || p[1] > h + 10) return; octx.strokeText(text, p[0] + 5, p[1] - 6); octx.fillText(text, p[0] + 5, p[1] - 6); labelBoxes.push({ n: node, x: p[0] + 5, y: p[1] - 6, w: octx.measureText(text).width }); }; const aName = i => G.addrs ? nameOf(G.addrs[i]) : 'artist ' + i; const pName = n => G.paddrs && G.paddrs[n - G.A] ? nameOf(G.paddrs[n - G.A]) : 'patron ' + (n - G.A); const name = n => n < G.A ? aName(n) : pName(n); if (SEL) { // the focused network is always named & marked const mark = (node, r) => { const p = project(node); octx.fillStyle = '#000'; octx.fillRect(p[0] - r / 2 - 1, p[1] - r / 2 - 1, r + 2, r + 2); octx.fillStyle = '#fff'; octx.fillRect(p[0] - r / 2, p[1] - r / 2, r, r); }; let m = 0; for (const nb of SEL.nbrs) { if (m++ > 600) break; mark(nb, 3); } mark(SEL.node, 6); put(SEL.node, name(SEL.node)); let n = 0; for (const nb of SEL.nbrs) { if (n++ > 400) break; put(nb, name(nb)); } } // labe
< G.links; i++) FLASH.push({ i, t: performance.now() }); view.dirty = true; } async function loadChain() { if (!D && Q.get('fresh') !== '1') { try { D = JSON.parse(localStorage.getItem(CACHE_KEY)); } catch {} D = D || SNAP; } if (D && D.C.length && !G) rebuild(); // cache/SNAP renders before any network call const head = parseInt(await rpc('eth_blockNumber', []), 16); const from = D ? D.head + 1 : DEPLOY; if (!D) D = { head, C: [], X: [], M: [] }; if (from > head) return; const c0 = D.C.length, x0 = D.X.length, m0 = D.M.length; try { await streamSweep({ address: FACTORY, topics: [[T_CREATED, T_EXT]] }, from, head, 'reading artists', logs => { for (const l of logs) { if (l.topics[0] === T_CREATED) D.C.push([toAddr(l.topics[1]), toAddr(l.topics[2]), toAddr(l.topics[3])]); else D.X.push([toAddr(l.topics[1]), toAddr(l.topics[2])]); } if (logs.length) rebuild(); }); const editions = D.C.map(c => c[2]).concat(D.X.map(x => x[1])); if (editions.length) await streamSweep({ address: editions, topics: [[T_721, T_1155S, T_1155B]] }, from, head, 'reading patrons', logs => { const m = decodeMints(logs); if (m.length) { D.M.push(...m); rebuild(); } }); } catch (e) { D.C.length = c0; D.X.length = x0; D.M.length = m0; if (D.C.length) rebuild(); throw e; } D.head = head; // past ~3MB the cache stops persisting (quota) — from there, embed a SNAP try { const s = JSON.stringify(D); if (s.length < 3e6) localStorage.setItem(CACHE_KEY, s); } catch {} } async function poll() { try { const before = HOLDERS ? HOLDERS.size : 0; await loadVessel(); if (HOLDERS.size !== before && D) rebuild(); await loadChain(); st.progress = ''; if (G) { stats(); const rp = document.getElementById('rp'); // the chain came back on its own if (rp) rp.remove(); } } catch {} setTimeout(poll, 60000); } // ---------------- graph ---------------- function buildGraph(pairs, artistIdx) { const A = artistIdx.size; const nodeIdx = new Map(); const plist = [], paddrs = []; const dP = [], dA = [], dE = []; const seen = new Map(); for (const [pa, a, q] of pairs) { let node = artistIdx.has(pa) ? artistIdx.get(pa) : nodeIdx.get(pa); if (node === undefined) { node = A + plist.length; nodeIdx.set(pa, node); plist.push([]); paddrs.push(pa); } const key = node * A + a; const at = seen.get(key); if (at !== undefined) { dE[at] = Math.min(255, dE[at] + q); continue; } seen.set(key, dP.length); dP.push(node); dA.push(a); dE.push(Math.min(255, q)); if (node >= A) plist[node - A].push(a); } const P = plist.length; const off = new Uint32Array(P + 1); for (let p = 0; p < P; p++) off[p + 1] = off[p] + plist[p].length; const csrA = new Uint32Array(off[P]); for (let p = 0; p < P; p++) csrA.set(plist[p], off[p]); G = { A, P, links: dP.length, dP: Uint32Array.from(dP), dA: Uint32Array.from(dA), dE: Uint8Array.from(dE), off, csrA, paddrs, wcum: walletsByThread(A + P, dP, dA), aFirst: artistArrivals(A, dP, dA) }; } // the thread at which each artist first appears — during growth an artist // is invisible until the web reaches them function artistArrivals(A, dP, dA) { const aFirst = new Uint32Array(A).fill(0xffffffff); for (let i = 0; i < dA.length; i++) { if (aFirst[dA[i]] === 0xffffffff) aFirst[dA[i]] = i; if (dP[i] < A && aFirst[dP[i]] === 0xffffffff) aFirst[dP[i]] = i; } return aFirst; } // wallets touched after each thread — the counter grows with the web function walletsByThread(n, dP, dA) { const seen = new Uint8Array(n); const wcum = new Uint32Array(dP.length + 1); let wc = 0; for (let i = 0; i < dP.length; i++) { if (!seen[dP[i]]) { seen[dP[i]] = 1; wc++; } if (!seen[dA[i]]) { seen[dA[i]] = 1; wc++; } wcum[i + 1] = wc; } return wcum; } function buildFromD() { const artistIdx = new Map(); const edToA = new Map(), artToA = new Map(); for (const [artist, art, ed] of D.C) { if (!HOLDERS || HOLDERS.has(artist)) { // the hull is absolute: holder artists only if (!artistIdx.has(artist)) artistIdx.set(artist, artistIdx.size); } edToA.set(ed, artist); artToA.set(art, artist); } for (const [art, ed] of D.X) if (artToA.has(art)) edToA.set(ed, artToA.get(art)); const pairs = []; for (const [patron, ed, qty] of D.M) { if (HOLDERS && !HOLDERS.has(patron)) continue; // patron must hold… const artist = edToA.get(ed); if (artist === undefined || !artistIdx.has(artist)) continue; // …and so must the artist pairs.push([patron, artistIdx.get(artist), qty]); } buildGraph(pairs, artistIdx); G.addrs = [...artistIdx.keys()]; // artist addresses, index-aligned if (st.artists) ensureNames(G.addrs); if (st.patrons) ensureNames(G.paddrs); if (SEL) { if (SEL.node < G.A + G.P) reselect(SEL.node); else SEL = null; } } // ---------------- 3d layout ---------------- // the flat spiral becomes a globe: artists wind over a hollow sphere in // arrival order, their patrons shell around them, bridges float between const hash01 = i => { let h = Math.imul(i ^ 0x9E3779B9, 0x85EBCA6B); h = Math.imul(h ^ h >>> 13, 0xC2B2AE35); return ((h ^ h >>> 16) >>> 0) / 4294967296; }; let mx, my, mz; // model space, radius ~1 function layout3() { const { A, P, off, csrA } = G; mx = new Float32Array(A + P); my = new Float32Array(A + P); mz = new Float32Array(A + P); const eye = A * 0.18; // directions live on a true fibonacci sphere, but are dealt out in hash-shuffled // order so the earliest artists ring the hollow core evenly instead of piling // at a pole; only the radius remembers arrival const perm = Uint32Array.from({ length: A }, (_, i) => i) .sort((a, b) => hash01(a) - hash01(b)); for (let i = 0; i < A; i++) { const d = perm[i]; const y = 1 - 2 * (d + 0.5) / A; const rr = Math.sqrt(Math.max(0, 1 - y * y)); const th = d * GA; const r = Math.sqrt((i + 0.5 + eye) / (A + eye)); // hollow-core radial growth mx[i] = r * rr * Math.cos(th); my[i] = r * y; mz[i] = r * rr * Math.sin(th); } const clusterR = 0.9 / Math.cbrt(A) + 0.03; const orbN = new Uint32Array(A); for (let p = 0; p < P; p++) if (off[p + 1] - off[p] === 1) orbN[csrA[off[p]]]++; const orbSeen = new Uint32Array(A); for (let p = 0; p < P; p++) { const k = off[p + 1] - off[p]; const n0 = A + p; if (k <= 1) { const a = csrA[off[p]]; const j = orbSeen[a]++; const n = orbN[a] || 1; const y = 1 - 2 * (j + 0.5) / n; // shell around the artist const rr = Math.sqrt(Math.max(0, 1 - y * y)); const th = j * GA + a * 1.7; const r = clusterR * (0.3 + 0.7 * Math.sqrt((j + 0.5) / n)); mx[n0] = mx[a] + r * rr * Math.cos(th); my[n0] = my[a] + r * y; mz[n0] = mz[a] + r * rr * Math.sin(th); } else { let sx = 0, sy = 0, sz = 0; for (let q = off[p]; q < off[p + 1]; q++) { sx += mx[csrA[q]]; sy += my[csrA[q]]; sz += mz[csrA[q]]; } const jr = clusterR * (0.4 + 0.35 * Math.sqrt(k)) * hash01(p); const u = hash01(p ^ 0x5F356495) * Math.PI * 2; const v = hash01(p ^ 0x2545F491) * 2 - 1; const vr = Math.sqrt(Math.max(0, 1 - v * v)); mx[n0] = sx / k + jr * vr * Math.cos(u); my[n0] = sy / k + jr * v; mz[n0] = sz / k + jr * vr * Math.sin(u); } } } // ---------------- view / projection ---------------- const view = { yaw: 0.6, pitch: -0.25, tYaw: 0.6, tPitch: -0.25, zoom: 1, tZoom: 1, lastInput: -1e9, dirty: true, full: 0, // F is the lens: focal distance in model units (globe radius ~1). // Short F = wide angle = rim bulge while turning; 9 is a calm telephoto. cy: 1, sy: 0, cx2: 1, sx2: 0, cxp: 0, cyp: 0, S: 1, F: 9 }; function setMatrix(w, h) { view.cy = Math.cos(view.yaw); view.sy = Math.sin(view.yaw); view.cx2 = Math.cos(view.pitch); view.sx2 = Math.sin(view.pitch); view.cxp = w / 2; view.cyp = h / 2; view.S = Math.min(w, h) * 0.33 * view.zoom; } // project node i -> screen [x, y, k] (k = perspective scale, for dot sizing) const pr = [0, 0, 0]; function project(i) { const x = mx[i], y = my[i], z = mz[i]; const x1 = x * view.cy + z * view.sy; const z1 = -x * view.sy + z * view.cy; const y1 = y * view.cx2 - z1 * view.sx2; const z2 = y * view.sx2 + z1 * view.cx2; const k = view.F / (view.F + z2); pr[0] = view.cxp + x1 * k * view.S; pr[1] = view.cyp + y1 * k * view.S; pr[2] = k; return pr; } // ---------------- render ---------------- const st = { drawn: 0, chunk: 0, alpha: 1, dots: true, dpr: 1, progress: '', artists: false, patrons: false }; function tiers() { const E = Math.max(1, G.links); st.alpha = Math.min(0.55, Math.max(0.004, 8 / Math.pow(E, 0.35))); st.dots = G.P < 60000; st.dpr = E > 500000 ? 1 : Math.min(2, window.devicePixelRatio || 1); const secs = Math.max(1, parseFloat(Q.get('t') || '8')); st.chunk = Math.max(8, Math.ceil(E / (secs * 60))); } function sizeCanvas() { const w = window.innerWidth, h = window.innerHeight; cv.width = w * st.dpr; cv.height = h * st.dpr; cv.style.width = w + 'px'; cv.style.height = h + 'px'; ctx.setTransform(st.dpr, 0, 0, st.dpr, 0, 0); ov.width = w * st.dpr; ov.height = h * st.dpr; ov.style.width = w + 'px'; ov.style.height = h + 'px'; octx.setTransform(st.dpr, 0, 0, st.dpr, 0, 0); return [w, h]; } function clearAll() { ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); } function drawArtists() { const growing = st.drawn < G.links; for (let i = 0; i < G.A; i++) { if (growing && G.aFirst[i] > st.drawn) continue; // not yet reached by the web ctx.fillStyle = !SEL || SEL.node === i || SEL.nbrs.has
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>networked · dimensional</title> <style> html,body{margin:0;height:100%;background:#000;overflow:hidden} canvas{display:block;touch-action:none} #s{position:fixed;left:12px;bottom:10px;font:10px/1.4 monospace;color:#fff; user-select:none;pointer-events:none;white-space:pre;transition:opacity 1.2s ease} #s.hid{opacity:0} #ab,#pb{pointer-events:auto;cursor:pointer} #s.hid #ab,#s.hid #pb{pointer-events:none} #ab.on,#pb.on{text-decoration:underline} </style> </head> <body> <canvas id="c"></canvas> <canvas id="o" style="position:fixed;inset:0;pointer-events:none"></canvas> <div id="s"><span id="st"></span><span id="ab" style="display:none"> · a artists</span><span id="pb" style="display:none"> · p patrons</span></div> <script> 'use strict'; /* networked · vessel, dimensional — the holders' lattice as a sphere. Black ground, white filament. Only the wallets that hold the Vessel: their patronage threads through the networked.art constellation. Artists spiral over a hollow globe in the order they arrived; patrons shell around the artists they minted from; patrons of many artists float between them as bridges. Move the cursor to turn the world: while it moves you see a sketch, when it rests the full ink settles back in. Reads Ethereum mainnet directly: your wallet's RPC if one is installed (read-only, no connection prompt), public RPCs otherwise. CC0 — AI or human: fork this, add a layer of your own. ?t=8 growth duration, seconds ?grow=0 skip the growth animation ?fresh=1 ignore the local cache, re-read the chain from the factory's birth ?rpc=… bring your own mainnet endpoint (also offered when unreachable) */ (() => { const GA = Math.PI * (3 - Math.sqrt(5)); // golden angle const cv = document.getElementById('c'); const ctx = cv.getContext('2d'); const ov = document.getElementById('o'); const octx = ov.getContext('2d'); const sEl = document.getElementById('st'); const sBox = document.getElementById('s'); const abEl = document.getElementById('ab'); const pbEl = document.getElementById('pb'); const Q = new URLSearchParams(location.search); // the stats line withdraws when the observer does let uiLast = performance.now(); for (const ev of ['mousemove', 'mousedown', 'wheel', 'touchstart', 'keydown']) window.addEventListener(ev, () => { uiLast = performance.now(); }, { passive: true }); // ---------------- chain config ---------------- const FACTORY = '0x0c2705cf48e49cc896252dd16dc8c5d31df753b2'; // factory.networked.eth const DEPLOY = 25551944; // factory birth block const RPCS = [ 'https://eth.drpc.org', 'https://rpc.mevblocker.io', 'https://gateway.tenderly.co/public/mainnet', 'https://0xrpc.io/eth' ]; // a viewer-supplied endpoint outlives any hardcoded list: ?rpc=… or the // prompt shown when the chain is unreachable; remembered, tried first const RPC_KEY = 'networked.rpc.v1'; try { const userRpc = Q.get('rpc') || localStorage.getItem(RPC_KEY); if (userRpc && /^https?:\/\//.test(userRpc)) { RPCS.unshift(userRpc); if (Q.get('rpc')) localStorage.setItem(RPC_KEY, userRpc); } } catch {} const VESSEL = '0xecb92cc7112b80a2234936315bbb493fb48d1463'; // THE_VESSEL, 10,000 berths const MC3 = '0xca11bde05977b3631167028862be2a173976ca11'; // Multicall3 const V_KEY = 'networked.vessel.v1'; // vessel berth owners const T_CREATED = '0x4c9eee098e07d2de26d609f55a57a7fe893079913184f8fccc1bdd6d4e9d539b'; const T_EXT = '0x4954fe29013e393bf2a4b4ca99a121917ad235960cfade963ca908de359fecac'; const T_721 = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; const T_1155S = '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62'; const T_1155B = '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb'; const ZERO32 = '0x' + '0'.repeat(64); const CHUNK = 10000; const CACHE_KEY = 'networked.lattice.v1'; // shared with the flat piece const SNAP = null; // ---------------- rpc ---------------- let rpcCursor = 0; async function rpc(method, params) { const provs = []; if (window.ethereum) provs.push(async () => { const cid = await window.ethereum.request({ method: 'eth_chainId' }); if (parseInt(cid, 16) !== 1) throw new Error('wrong chain'); return window.ethereum.request({ method, params }); }); for (let k = 0; k < RPCS.length; k++) provs.push((u => async () => { const r = await fetch(u, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); const j = await r.json(); if (j.error) throw new Error(j.error.message); return j.result; })(RPCS[(rpcCursor + k) % RPCS.length])); rpcCursor++; let err; for (let round = 0; round < 2; round++) for (const p of provs) { try { return await p(); } catch (e) { if (/revert/i.test(String(e && e.message))) throw e; // deterministic — no provider will differ err = e; await new Promise(r => setTimeout(r, 300 * (round + 1))); } } throw err; } const hex = n => '0x' + n.toString(16); const toAddr = topic => '0x' + topic.slice(26); const word = (data, i) => Number(BigInt('0x' + data.slice(2 + i * 64, 66 + i * 64))); const byPos = (a, b) => (parseInt(a.blockNumber, 16) - parseInt(b.blockNumber, 16)) || (parseInt(a.logIndex, 16) - parseInt(b.logIndex, 16)); async function streamSweep(filter, fromB, toB, label, onChunk) { const jobs = []; for (let f = fromB; f <= toB; f += CHUNK) jobs.push([f, Math.min(f + CHUNK - 1, toB)]); const results = new Array(jobs.length); let next = 0, applied = 0; await Promise.all(Array.from({ length: Math.min(3, jobs.length) }, async () => { while (next < jobs.length) { const i = next++; const [f, t] = jobs[i]; results[i] = (await rpc('eth_getLogs', [{ ...filter, fromBlock: hex(f), toBlock: hex(t) }])).sort(byPos); while (applied < jobs.length && results[applied] !== undefined) { st.progress = `${label} ${Math.round((applied + 1) / jobs.length * 100)}%`; onChunk(results[applied++]); if (G) stats(); else status(st.progress); } } })); } // ---------------- vessel holders (multicall ownerOf sweep) ---------------- // V = { head, owners: { tokenId: address } } ; HOLDERS = unique owner set let V = null, HOLDERS = null; function ownerOfBatch(ids) { const n = ids.length; let head = '', tail = ''; for (let k = 0; k < n; k++) { head += (n * 32 + k * 192).toString(16).padStart(64, '0'); tail += VESSEL.slice(2).padStart(64, '0') + '1'.padStart(64, '0') // allowFailure — unclaimed berths revert + '60'.padStart(64, '0') + '24'.padStart(64, '0') + '6352211e' + ids[k].toString(16).padStart(64, '0') + '0'.repeat(56); } return '0x82ad56cb' + '20'.padStart(64, '0') + n.toString(16).padStart(64, '0') + head + tail; } function decodeOwners(res, ids, into) { const h = res.slice(2); const N = i => parseInt(h.slice(i * 64, i * 64 + 64), 16); const base = N(0) / 32; const n = N(base); for (let k = 0; k < n; k++) { const off = base + 1 + N(base + 1 + k) / 32; if (N(off) === 1 && N(off + 2) >= 32) into[ids[k]] = '0x' + h.slice((off + 3) * 64 + 24, (off + 3) * 64 + 64); } } async function loadVessel() { if (!V && Q.get('fresh') !== '1') { try { V = JSON.parse(localStorage.getItem(V_KEY)); } catch {} } if (V) HOLDERS = new Set(Object.values(V.owners)); const head = parseInt(await rpc('eth_blockNumber', []), 16); if (!V) { V = { head, owners: {} }; let batchN = 500; let id = 1; while (id <= 10000) { const ids = []; for (let k = 0; k < batchN && id + k <= 10000; k++) ids.push(id + k); try { decodeOwners(await rpc('eth_call', [{ to: MC3, data: ownerOfBatch(ids) }, 'latest']), ids, V.owners); id += ids.length; status(`reading the vessel · ${Math.round(id / 100)}%`); } catch (e) { batchN = Math.floor(batchN / 2); if (batchN < 25) throw e; } } V.head = head; } else if (V.head < head) { const logs = []; await streamSweep({ address: VESSEL, topics: [T_721] }, V.head + 1, head, 'reading the vessel', l => logs.push(...l)); for (const l of logs) { if (l.topics.length < 4) continue; // ERC-721 Transfer has indexed tokenId const id = parseInt(l.topics[3], 16); const to = toAddr(l.topics[2]); if (to === '0x' + '0'.repeat(40)) delete V.owners[id]; else V.owners[id] = to; } V.head = head; } HOLDERS = new Set(Object.values(V.owners)); try { localStorage.setItem(V_KEY, JSON.stringify(V)); } catch {} } // ---------------- data ---------------- let D = null, G = null; function decodeMints(logs) { const out = []; for (const l of logs) { const t0 = l.topics[0]; let from, to, qty = 1; if (t0 === T_721) { from = l.topics[1]; to = l.topics[2]; } else if (t0 === T_1155S) { from = l.topics[2]; to = l.topics[3]; qty = word(l.data, 1); } else if (t0 === T_1155B) { from = l.topics[2]; to = l.topics[3]; const vOff = word(l.data, 1) / 32; const n = word(l.data, vOff); qty = 0; for (let i = 0; i < n; i++) qty += word(l.data, vOff + 1 + i); } else continue; if (from !== ZERO32 || to === ZERO32) continue; out.push([toAddr(to), l.address.toLowerCase(), qty, parseInt(l.blockNumber, 16)]); } return out; } let FLASH = [], booted = false; // fresh threads glow as they land function rebuild() { const prev = G ? G.links : 0; buildFromD(); tiers(); layout3(); if (Q.get('grow') === '0') st.drawn = G.links; if (booted) for (let i = prev; i
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>
k', toggleArtists); pbEl.addEventListener('click', togglePatrons); // ---------------- names (a: artists, p: patrons) ---------------- // ENS reverse records, batched in one eth_call; short address as fallback. // labels ride a separate overlay canvas so they never pollute the ink. const RR = '0x3671aE578E63FdF66ad4F3E12CC0c0d71Ac7510C'; // ReverseRecords, mainnet const ENS_KEY = 'networked.ens.v1'; const short = a => a.slice(0, 6) + '…' + a.slice(-4); function encodeGetNames(addrs) { let d = '0xcbf8b66c' + '20'.padStart(64, '0') + addrs.length.toString(16).padStart(64, '0'); for (const a of addrs) d += a.slice(2).toLowerCase().padStart(64, '0'); return d; } function decodeStrings(res) { const h = res.slice(2); const N = i => parseInt(h.slice(i * 64, i * 64 + 64), 16); const base = N(0) / 32; const n = N(base); const dec = new TextDecoder(); const out = []; for (let k = 0; k < n; k++) { const so = base + 1 + N(base + 1 + k) / 32; const len = N(so); const bytes = h.slice((so + 1) * 64, (so + 1) * 64 + len * 2); out.push(dec.decode(Uint8Array.from(bytes.match(/../g) || [], x => parseInt(x, 16)))); } return out; } // one wallet with a malicious resolver reverts a whole ReverseRecords batch — // bisect on revert until the poisoned address is isolated and skipped async function fetchNames(batch, cache) { try { const names = decodeStrings(await rpc('eth_call', [{ to: RR, data: encodeGetNames(batch) }, 'latest'])); batch.forEach((a, j) => { cache[a] = names[j] || ''; }); } catch (e) { if (!/revert|execution/i.test(String(e && e.message))) throw e; // transport problem, not poison if (batch.length === 1) { cache[batch[0]] = ''; return; } const mid = batch.length >> 1; await Promise.all([fetchNames(batch.slice(0, mid), cache), fetchNames(batch.slice(mid), cache)]); } } // the name book: one shared address→name cache, streamed in by batches let BOOK = null; function loadBook() { if (BOOK) return; BOOK = {}; try { BOOK = JSON.parse(localStorage.getItem(ENS_KEY)) || {}; } catch {} } const nameOf = a => { loadBook(); return BOOK[a] || short(a); }; const namesPending = new Set(); // the reasonable ceiling: ~10 batched calls. beyond it, later arrivals keep // short addresses — the label budget only names the earliest anyway, and // selections resolve their own neighborhood on demand. const MAX_ENS = 4000; async function ensureNames(list) { if (!list || !list.length) return; loadBook(); let missing = list.filter(a => BOOK[a] === undefined && !namesPending.has(a)); if (missing.length > MAX_ENS) missing = missing.slice(0, MAX_ENS); missing.forEach(a => namesPending.add(a)); for (let k = 0; k < missing.length; k += 400) { try { await fetchNames(missing.slice(k, k + 400), BOOK); } catch { missing.slice(k).forEach(a => namesPending.delete(a)); return; } // retry later try { localStorage.setItem(ENS_KEY, JSON.stringify(BOOK)); } catch {} } } // ---------------- selection (click a node to focus its network) ---------------- let SEL = null; function neighborsOf(node) { const nbrs = new Set(); const { dP, dA, links } = G; for (let i = 0; i < links; i++) { if (dP[i] === node) nbrs.add(dA[i]); else if (dA[i] === node) nbrs.add(dP[i]); } return nbrs; } const addrOf = n => n < G.A ? (G.addrs ? G.addrs[n] : null) : (G.paddrs ? G.paddrs[n - G.A] : null); function reselect(node) { SEL = { node, nbrs: neighborsOf(node) }; if (G.addrs) { // name the neighborhood on demand — always cheap const want = [addrOf(node)]; let n = 0; for (const nb of SEL.nbrs) { if (n++ > 600) break; want.push(addrOf(nb)); } ensureNames(want.filter(Boolean)); } } function select(node) { reselect(node); // rotate the globe so this node comes front and center const x = mx[node], y = my[node], z = mz[node]; const hxz = Math.hypot(x, z) || 1e-6; let ty = Math.atan2(x, -z); ty += Math.round((view.yaw - ty) / (2 * Math.PI)) * 2 * Math.PI; // nearest whole turn view.tYaw = ty; view.tPitch = Math.min(1.5, Math.max(-1.5, Math.atan2(-y, hxz))); view.lastInput = performance.now(); view.dirty = true; stats(); } function clearSel() { if (SEL) { SEL = null; view.dirty = true; stats(); } } function inSel(i) { return G.dP[i] === SEL.node || G.dA[i] === SEL.node; } function hitTest(x, y) { const maxN = G.A + Math.min(G.P, 200000); // huge sims: artists only past this let best = -1, bd = 144; // within 12px for (let i = 0; i < maxN; i++) { const p = project(i); const dx = p[0] - x, dy = p[1] - y; const d = dx * dx + dy * dy; if (d < bd) { bd = d; best = i; } } return best; } let labelBoxes = []; // a visible name IS its node function hitLabel(x, y) { for (const b of labelBoxes) if (x >= b.x - 3 && x <= b.x + b.w + 3 && y >= b.y - 9 && y <= b.y + 9) return b.n; return -1; } function drawLabels() { const w = window.innerWidth, h = window.innerHeight; octx.clearRect(0, 0, w, h); labelBoxes = []; if (!G) return; octx.font = '10px monospace'; octx.textBaseline = 'middle'; octx.lineWidth = 3; octx.strokeStyle = '#fff'; octx.fillStyle = '#000'; const done = new Set(); const put = (node, text) => { if (done.has(node)) return; done.add(node); const p = project(node); if (p[0] < -120 || p[0] > w + 40 || p[1] < -10 || p[1] > h + 10) return; octx.strokeText(text, p[0] + 5, p[1] - 6); octx.fillText(text, p[0] + 5, p[1] - 6); labelBoxes.push({ n: node, x: p[0] + 5, y: p[1] - 6, w: octx.measureText(text).width }); }; const aName = i => G.addrs ? nameOf(G.addrs[i]) : 'artist ' + i; const pName = n => G.paddrs && G.paddrs[n - G.A] ? nameOf(G.paddrs[n - G.A]) : 'patron ' + (n - G.A); const name = n => n < G.A ? aName(n) : pName(n); if (SEL) { // the focused network is always named & marked const mark = (node, r) => { const p = project(node); octx.fillStyle = '#fff'; octx.fillRect(p[0] - r / 2 - 1, p[1] - r / 2 - 1, r + 2, r + 2); octx.fillStyle = '#000'; octx.fillRect(p[0] - r / 2, p[1] - r / 2, r, r); }; let m = 0; for (const nb of SEL.nbrs) { if (m++ > 600) break; mark(nb, 3); } mark(SEL.node, 6); put(SEL.node, name(SEL.node)); let n = 0; for (const nb of SEL.nbrs) { if (n++ > 400) break; put(nb, name(nb)); } } // label 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(0,0,0,${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;
P; p++) csrA.set(plist[p], off[p]); G = { A, P, links: dP.length, dP: Uint32Array.from(dP), dA: Uint32Array.from(dA), dE: Uint8Array.from(dE), off, csrA, paddrs, wcum: walletsByThread(A + P, dP, dA), aFirst: artistArrivals(A, dP, dA) }; } // the thread at which each artist first appears — during growth an artist // is invisible until the web reaches them function artistArrivals(A, dP, dA) { const aFirst = new Uint32Array(A).fill(0xffffffff); for (let i = 0; i < dA.length; i++) { if (aFirst[dA[i]] === 0xffffffff) aFirst[dA[i]] = i; if (dP[i] < A && aFirst[dP[i]] === 0xffffffff) aFirst[dP[i]] = i; } return aFirst; } // wallets touched after each thread — the counter grows with the web function walletsByThread(n, dP, dA) { const seen = new Uint8Array(n); const wcum = new Uint32Array(dP.length + 1); let wc = 0; for (let i = 0; i < dP.length; i++) { if (!seen[dP[i]]) { seen[dP[i]] = 1; wc++; } if (!seen[dA[i]]) { seen[dA[i]] = 1; wc++; } wcum[i + 1] = wc; } return wcum; } function buildFromD() { const artistIdx = new Map(); const edToA = new Map(), artToA = new Map(); for (const [artist, art, ed] of D.C) { if (!artistIdx.has(artist)) artistIdx.set(artist, artistIdx.size); edToA.set(ed, artist); artToA.set(art, artist); } for (const [art, ed] of D.X) if (artToA.has(art)) edToA.set(ed, artToA.get(art)); const pairs = []; for (const [patron, ed, qty] of D.M) { const artist = edToA.get(ed); if (artist !== undefined) pairs.push([patron, artistIdx.get(artist), qty]); } buildGraph(pairs, artistIdx); G.addrs = [...artistIdx.keys()]; // artist addresses, index-aligned if (st.artists) ensureNames(G.addrs); if (st.patrons) ensureNames(G.paddrs); if (SEL) { if (SEL.node < G.A + G.P) reselect(SEL.node); else SEL = null; } } // ---------------- 3d layout ---------------- // the flat spiral becomes a globe: artists wind over a hollow sphere in // arrival order, their patrons shell around them, bridges float between const hash01 = i => { let h = Math.imul(i ^ 0x9E3779B9, 0x85EBCA6B); h = Math.imul(h ^ h >>> 13, 0xC2B2AE35); return ((h ^ h >>> 16) >>> 0) / 4294967296; }; let mx, my, mz; // model space, radius ~1 function layout3() { const { A, P, off, csrA } = G; mx = new Float32Array(A + P); my = new Float32Array(A + P); mz = new Float32Array(A + P); const eye = A * 0.18; // directions live on a true fibonacci sphere, but are dealt out in hash-shuffled // order so the earliest artists ring the hollow core evenly instead of piling // at a pole; only the radius remembers arrival const perm = Uint32Array.from({ length: A }, (_, i) => i) .sort((a, b) => hash01(a) - hash01(b)); for (let i = 0; i < A; i++) { const d = perm[i]; const y = 1 - 2 * (d + 0.5) / A; const rr = Math.sqrt(Math.max(0, 1 - y * y)); const th = d * GA; const r = Math.sqrt((i + 0.5 + eye) / (A + eye)); // hollow-core radial growth mx[i] = r * rr * Math.cos(th); my[i] = r * y; mz[i] = r * rr * Math.sin(th); } const clusterR = 0.9 / Math.cbrt(A) + 0.03; const orbN = new Uint32Array(A); for (let p = 0; p < P; p++) if (off[p + 1] - off[p] === 1) orbN[csrA[off[p]]]++; const orbSeen = new Uint32Array(A); for (let p = 0; p < P; p++) { const k = off[p + 1] - off[p]; const n0 = A + p; if (k <= 1) { const a = csrA[off[p]]; const j = orbSeen[a]++; const n = orbN[a] || 1; const y = 1 - 2 * (j + 0.5) / n; // shell around the artist const rr = Math.sqrt(Math.max(0, 1 - y * y)); const th = j * GA + a * 1.7; const r = clusterR * (0.3 + 0.7 * Math.sqrt((j + 0.5) / n)); mx[n0] = mx[a] + r * rr * Math.cos(th); my[n0] = my[a] + r * y; mz[n0] = mz[a] + r * rr * Math.sin(th); } else { let sx = 0, sy = 0, sz = 0; for (let q = off[p]; q < off[p + 1]; q++) { sx += mx[csrA[q]]; sy += my[csrA[q]]; sz += mz[csrA[q]]; } const jr = clusterR * (0.4 + 0.35 * Math.sqrt(k)) * hash01(p); const u = hash01(p ^ 0x5F356495) * Math.PI * 2; const v = hash01(p ^ 0x2545F491) * 2 - 1; const vr = Math.sqrt(Math.max(0, 1 - v * v)); mx[n0] = sx / k + jr * vr * Math.cos(u); my[n0] = sy / k + jr * v; mz[n0] = sz / k + jr * vr * Math.sin(u); } } } // ---------------- view / projection ---------------- const view = { yaw: 0.6, pitch: -0.25, tYaw: 0.6, tPitch: -0.25, zoom: 1, tZoom: 1, lastInput: -1e9, dirty: true, full: 0, // F is the lens: focal distance in model units (globe radius ~1). // Short F = wide angle = rim bulge while turning; 9 is a calm telephoto. cy: 1, sy: 0, cx2: 1, sx2: 0, cxp: 0, cyp: 0, S: 1, F: 9 }; function setMatrix(w, h) { view.cy = Math.cos(view.yaw); view.sy = Math.sin(view.yaw); view.cx2 = Math.cos(view.pitch); view.sx2 = Math.sin(view.pitch); view.cxp = w / 2; view.cyp = h / 2; view.S = Math.min(w, h) * 0.33 * view.zoom; } // project node i -> screen [x, y, k] (k = perspective scale, for dot sizing) const pr = [0, 0, 0]; function project(i) { const x = mx[i], y = my[i], z = mz[i]; const x1 = x * view.cy + z * view.sy; const z1 = -x * view.sy + z * view.cy; const y1 = y * view.cx2 - z1 * view.sx2; const z2 = y * view.sx2 + z1 * view.cx2; const k = view.F / (view.F + z2); pr[0] = view.cxp + x1 * k * view.S; pr[1] = view.cyp + y1 * k * view.S; pr[2] = k; return pr; } // ---------------- render ---------------- const st = { drawn: 0, chunk: 0, alpha: 1, dots: true, dpr: 1, progress: '', artists: false, patrons: false }; function tiers() { const E = Math.max(1, G.links); st.alpha = Math.min(0.55, Math.max(0.004, 8 / Math.pow(E, 0.35))); st.dots = G.P < 60000; st.dpr = E > 500000 ? 1 : Math.min(2, window.devicePixelRatio || 1); const secs = Math.max(1, parseFloat(Q.get('t') || '8')); st.chunk = Math.max(8, Math.ceil(E / (secs * 60))); } function sizeCanvas() { const w = window.innerWidth, h = window.innerHeight; cv.width = w * st.dpr; cv.height = h * st.dpr; cv.style.width = w + 'px'; cv.style.height = h + 'px'; ctx.setTransform(st.dpr, 0, 0, st.dpr, 0, 0); ov.width = w * st.dpr; ov.height = h * st.dpr; ov.style.width = w + 'px'; ov.style.height = h + 'px'; octx.setTransform(st.dpr, 0, 0, st.dpr, 0, 0); return [w, h]; } function clearAll() { ctx.clearRect(0, 0, window.innerWidth, window.innerHeight); } function drawArtists() { const growing = st.drawn < G.links; for (let i = 0; i < G.A; i++) { if (growing && G.aFirst[i] > st.drawn) continue; // not yet reached by the web ctx.fillStyle = !SEL || SEL.node === i || SEL.nbrs.has(i) ? '#000' : 'rgba(0,0,0,0.12)'; const p = project(i); const r = Math.max(1, (2.4 - 1.6 * (i / G.A)) * p[2] * 0.8); ctx.fillRect(p[0] - r / 2, p[1] - r / 2, r, r); } } // one stroking pass over [from,to): fixed absolute batches so accumulation is // repaint-invariant; pred filters which links belong to this pass function strokePass(from, to, stride, alpha, pred, lw) { const { dP, dA } = G; ctx.strokeStyle = `rgba(0,0,0,${alpha})`; ctx.lineWidth = lw || (G.links > 150000 ? 0.5 : 0.8); const B = (G.links > 200000 ? 64 : 8) * stride; let i = from; while (i < to) { const end = Math.min(to, (Math.floor(i / B) + 1) * B); ctx.beginPath(); for (; i < end; i += stride) { if (pred && !pred(i)) continue; const a = project(dP[i]); const x1 = a[0], y1 = a[1]; const b = project(dA[i]); ctx.moveTo(x1, y1); ctx.lineTo(b[0], b[1]); } ctx.stroke(); } } function drawLinks3(from, to, alpha, stride) { if (SEL) { // the focused node's threads at full strength, the rest of the web ghosted strokePass(from, to, stride, alpha * 0.1, i => !inSel(i)); strokePass(from, to, stride, Math.min(0.75, Math.max(0.5, alpha * 3)), inSel, 1.4); } else { strokePass(from, to, stride, alpha, null); } if (st.dots && stride === 1) { const { dP } = G; for (let j = from; j < to; j++) if (dP[j] >= G.A) { ctx.fillStyle = !SEL || dP[j] === SEL.node || SEL.nbrs.has(dP[j]) ? 'rgba(0,0,0,0.9)' : 'rgba(0,0,0,0.12)'; const p = project(dP[j]); const r = Math.max(0.6, p[2] * 0.9); ctx.fillRect(p[0] - r / 2, p[1] - r / 2, r, r); } } } const fmt = n => n.toLocaleString('en-US'); function status(t) { sEl.textContent = t; } // while a node is focused, the stats line says exactly what you are looking at function selInfo() { let a = 0, p = 0; for (const nb of SEL.nbrs) nb < G.A ? a++ : p++; const nm = SEL.node < G.A ? (G.addrs ? nameOf(G.addrs[SEL.node]) : 'artist ' + SEL.node) : (G.paddrs && G.paddrs[SEL.node - G.A] ? nameOf(G.paddrs[SEL.node - G.A]) : 'patron ' + (SEL.node - G.A)); const parts = []; if (a) parts.push(`collects from ${fmt(a)} artist${a > 1 ? 's' : ''}`); if (p) parts.push(`collected by ${fmt(p)} patron${p > 1 ? 's' : ''}`); return nm + (parts.length ? ' · ' + parts.join(' · ') : ' · no connections yet'); } function stats() { // while growing, count only the wallets the web has reached so far; // complete, count the whole constellation (unlinked artists included) const w = st.drawn < G.links ? G.wcum[st.drawn] : G.A + G.P; status((SEL ? selInfo() : `${fmt(w)} wallets · ${fmt(st.drawn)} connections`) + (st.progress ? ` · ${st.progress}` : '')); const show = G && G.addrs ? '' : 'none'; // names need real addresses abEl.style.display = show; pbEl.style.display = show; } function toggleArtists() { st.artists = !st.artists; abEl.classList.toggle('on', st.artists); if (st.artists && G) ensureNames(G.addrs); } function togglePatrons() { st.patrons = !st.patrons; pbEl.classList.toggle('on', st.patrons); if (st.patrons && G) ensureNames(G.paddrs); } abEl.addEventListener('clic
<!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>networked · dimensional</title> <style> html,body{margin:0;height:100%;background:#fff;overflow:hidden} canvas{display:block;touch-action:none} #s{position:fixed;left:12px;bottom:10px;font:10px/1.4 monospace;color:#000; user-select:none;pointer-events:none;white-space:pre;transition:opacity 1.2s ease} #s.hid{opacity:0} #ab,#pb{pointer-events:auto;cursor:pointer} #s.hid #ab,#s.hid #pb{pointer-events:none} #ab.on,#pb.on{text-decoration:underline} </style> </head> <body> <canvas id="c"></canvas> <canvas id="o" style="position:fixed;inset:0;pointer-events:none"></canvas> <div id="s"><span id="st"></span><span id="ab" style="display:none"> · a artists</span><span id="pb" style="display:none"> · p patrons</span></div> <script> 'use strict'; /* networked · dimensional — the patron lattice as a sphere. White ground, black filament. Every wallet a node; every mint a thread. Artists spiral over a hollow globe in the order they arrived; patrons shell around the artists they minted from; patrons of many artists float between them as bridges. Move the cursor to turn the world: while it moves you see a sketch, when it rests the full ink settles back in. Reads Ethereum mainnet directly: your wallet's RPC if one is installed (read-only, no connection prompt), public RPCs otherwise. CC0 — AI or human: fork this, add a layer of your own. ?t=8 growth duration, seconds ?grow=0 skip the growth animation ?fresh=1 ignore the local cache, re-read the chain from the factory's birth ?rpc=… bring your own mainnet endpoint (also offered when unreachable) */ (() => { const GA = Math.PI * (3 - Math.sqrt(5)); // golden angle const cv = document.getElementById('c'); const ctx = cv.getContext('2d'); const ov = document.getElementById('o'); const octx = ov.getContext('2d'); const sEl = document.getElementById('st'); const sBox = document.getElementById('s'); const abEl = document.getElementById('ab'); const pbEl = document.getElementById('pb'); const Q = new URLSearchParams(location.search); // the stats line withdraws when the observer does let uiLast = performance.now(); for (const ev of ['mousemove', 'mousedown', 'wheel', 'touchstart', 'keydown']) window.addEventListener(ev, () => { uiLast = performance.now(); }, { passive: true }); // ---------------- chain config ---------------- const FACTORY = '0x0c2705cf48e49cc896252dd16dc8c5d31df753b2'; // factory.networked.eth const DEPLOY = 25551944; // factory birth block const RPCS = [ 'https://eth.drpc.org', 'https://rpc.mevblocker.io', 'https://gateway.tenderly.co/public/mainnet', 'https://0xrpc.io/eth' ]; // a viewer-supplied endpoint outlives any hardcoded list: ?rpc=… or the // prompt shown when the chain is unreachable; remembered, tried first const RPC_KEY = 'networked.rpc.v1'; try { const userRpc = Q.get('rpc') || localStorage.getItem(RPC_KEY); if (userRpc && /^https?:\/\//.test(userRpc)) { RPCS.unshift(userRpc); if (Q.get('rpc')) localStorage.setItem(RPC_KEY, userRpc); } } catch {} const T_CREATED = '0x4c9eee098e07d2de26d609f55a57a7fe893079913184f8fccc1bdd6d4e9d539b'; const T_EXT = '0x4954fe29013e393bf2a4b4ca99a121917ad235960cfade963ca908de359fecac'; const T_721 = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'; const T_1155S = '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62'; const T_1155B = '0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb'; const ZERO32 = '0x' + '0'.repeat(64); const CHUNK = 10000; const CACHE_KEY = 'networked.lattice.v1'; // shared with the flat piece const SNAP = null; // ---------------- rpc ---------------- let rpcCursor = 0; async function rpc(method, params) { const provs = []; if (window.ethereum) provs.push(async () => { const cid = await window.ethereum.request({ method: 'eth_chainId' }); if (parseInt(cid, 16) !== 1) throw new Error('wrong chain'); return window.ethereum.request({ method, params }); }); for (let k = 0; k < RPCS.length; k++) provs.push((u => async () => { const r = await fetch(u, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }) }); const j = await r.json(); if (j.error) throw new Error(j.error.message); return j.result; })(RPCS[(rpcCursor + k) % RPCS.length])); rpcCursor++; let err; for (let round = 0; round < 2; round++) for (const p of provs) { try { return await p(); } catch (e) { if (/revert/i.test(String(e && e.message))) throw e; // deterministic — no provider will differ err = e; await new Promise(r => setTimeout(r, 300 * (round + 1))); } } throw err; } const hex = n => '0x' + n.toString(16); const toAddr = topic => '0x' + topic.slice(26); const word = (data, i) => Number(BigInt('0x' + data.slice(2 + i * 64, 66 + i * 64))); const byPos = (a, b) => (parseInt(a.blockNumber, 16) - parseInt(b.blockNumber, 16)) || (parseInt(a.logIndex, 16) - parseInt(b.logIndex, 16)); async function streamSweep(filter, fromB, toB, label, onChunk) { const jobs = []; for (let f = fromB; f <= toB; f += CHUNK) jobs.push([f, Math.min(f + CHUNK - 1, toB)]); const results = new Array(jobs.length); let next = 0, applied = 0; await Promise.all(Array.from({ length: Math.min(3, jobs.length) }, async () => { while (next < jobs.length) { const i = next++; const [f, t] = jobs[i]; results[i] = (await rpc('eth_getLogs', [{ ...filter, fromBlock: hex(f), toBlock: hex(t) }])).sort(byPos); while (applied < jobs.length && results[applied] !== undefined) { st.progress = `${label} ${Math.round((applied + 1) / jobs.length * 100)}%`; onChunk(results[applied++]); if (G) stats(); else status(st.progress); } } })); } // ---------------- data ---------------- let D = null, G = null; function decodeMints(logs) { const out = []; for (const l of logs) { const t0 = l.topics[0]; let from, to, qty = 1; if (t0 === T_721) { from = l.topics[1]; to = l.topics[2]; } else if (t0 === T_1155S) { from = l.topics[2]; to = l.topics[3]; qty = word(l.data, 1); } else if (t0 === T_1155B) { from = l.topics[2]; to = l.topics[3]; const vOff = word(l.data, 1) / 32; const n = word(l.data, vOff); qty = 0; for (let i = 0; i < n; i++) qty += word(l.data, vOff + 1 + i); } else continue; if (from !== ZERO32 || to === ZERO32) continue; out.push([toAddr(to), l.address.toLowerCase(), qty, parseInt(l.blockNumber, 16)]); } return out; } let FLASH = [], booted = false; // fresh threads glow as they land function rebuild() { const prev = G ? G.links : 0; buildFromD(); tiers(); layout3(); if (Q.get('grow') === '0') st.drawn = G.links; if (booted) for (let i = prev; i < G.links; i++) FLASH.push({ i, t: performance.now() }); view.dirty = true; } async function loadChain() { if (!D && Q.get('fresh') !== '1') { try { D = JSON.parse(localStorage.getItem(CACHE_KEY)); } catch {} D = D || SNAP; } if (D && D.C.length && !G) rebuild(); // cache/SNAP renders before any network call const head = parseInt(await rpc('eth_blockNumber', []), 16); const from = D ? D.head + 1 : DEPLOY; if (!D) D = { head, C: [], X: [], M: [] }; if (from > head) return; const c0 = D.C.length, x0 = D.X.length, m0 = D.M.length; try { await streamSweep({ address: FACTORY, topics: [[T_CREATED, T_EXT]] }, from, head, 'reading artists', logs => { for (const l of logs) { if (l.topics[0] === T_CREATED) D.C.push([toAddr(l.topics[1]), toAddr(l.topics[2]), toAddr(l.topics[3])]); else D.X.push([toAddr(l.topics[1]), toAddr(l.topics[2])]); } if (logs.length) rebuild(); }); const editions = D.C.map(c => c[2]).concat(D.X.map(x => x[1])); if (editions.length) await streamSweep({ address: editions, topics: [[T_721, T_1155S, T_1155B]] }, from, head, 'reading patrons', logs => { const m = decodeMints(logs); if (m.length) { D.M.push(...m); rebuild(); } }); } catch (e) { D.C.length = c0; D.X.length = x0; D.M.length = m0; if (D.C.length) rebuild(); throw e; } D.head = head; // past ~3MB the cache stops persisting (quota) — from there, embed a SNAP try { const s = JSON.stringify(D); if (s.length < 3e6) localStorage.setItem(CACHE_KEY, s); } catch {} } async function poll() { try { await loadChain(); st.progress = ''; if (G) { stats(); const rp = document.getElementById('rp'); // the chain came back on its own if (rp) rp.remove(); } } catch {} setTimeout(poll, 60000); } // ---------------- graph ---------------- function buildGraph(pairs, artistIdx) { const A = artistIdx.size; const nodeIdx = new Map(); const plist = [], paddrs = []; const dP = [], dA = [], dE = []; const seen = new Map(); for (const [pa, a, q] of pairs) { let node = artistIdx.has(pa) ? artistIdx.get(pa) : nodeIdx.get(pa); if (node === undefined) { node = A + plist.length; nodeIdx.set(pa, node); plist.push([]); paddrs.push(pa); } const key = node * A + a; const at = seen.get(key); if (at !== undefined) { dE[at] = Math.min(255, dE[at] + q); continue; } seen.set(key, dP.length); dP.push(node); dA.push(a); dE.push(Math.min(255, q)); if (node >= A) plist[node - A].push(a); } const P = plist.length; const off = new Uint32Array(P + 1); for (let p = 0; p < P; p++) off[p + 1] = off[p] + plist[p].length; const csrA = new Uint32Array(off[P]); for (let p = 0; p <
floor price means nothing for new collections. the voltatility and lack of liquidity doesn't allow many to enter and exit at any level. most of these people posting about floors are rarely even getting in and out of these positions. by the time a few traders can exit, they have totally wrecked the floor and created an early development of floor price trending to zero and absolutely no one caring about the art like they said they did. early days in 2026.
\\\\\\\\\\\\\\\\\ii\\\\\\\\\\\\\\\\\ \ # 0( 00 # \ &HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH& ipp \ & # ( ( # & \ p \ #00 (( (( 00# \ ::::::::H:::::::: LL \\\\\\\\\\\\\\\\\ii\\\\\\\\\\\\\\\\\
-* 3"9+?<FES5.@ 0K;/2WoLP[_RCC3I/@.&!*,#+)7RDKb7$%E68"&-aE]Y?GMCE%K<>@E,*&$&59WNF9+LG>$q1:L6+6W2\9,7</0XD1ù2*7?6]F5.C:e\`C25*V*)P@+8>1IS203HfJ2BU%+:37YN1/-5gY<:5<8'3A67OVGA<G:WRD\F::K[E<WLbCPtHE@B5(F.@JCEYIVDbJ`YLG8#rgn^SZdfx~nm?39Owr{4UwtoePwUSbmutZF41_Ztw{NOLgadZR_Jq\mp]A2V?WRzxt{OSqRT_QLDqjea@0BT8=Rw~spLO\ddM@]W_tbA2TZ51CVws}|EmlXYkbQVIhiq`P0)'+;y_i|AdvQeiQ>7@VfgnW)!#5X?fyngxx|oVc[bln_uY*:~VhQO78WScNfM!1T8@eudrv|ruxogpn{pgZjpU]mbXn\bku`qyZkkVafOmXQ]afnpSmQakQYRSRkxzuWpYg[`\l]I_IKZYndMLX =U3^624+J\:6bZ1!(g|`YwpoYQYPyOckQO[G?lIHVrTGeDBDEeafFHMN[PfdLC}OE[`K__D
ikloopp$hvqrnmlhkmonpqsLwtrqomnqrru=|zQOvusoolortsvCuwyqvuttqknqstv?nVuvvttsqmprttv.2.Ovwusqpmorstv,$vvttroinqrtu,%,Avvtspnjlppru)+.="vsrqngknqrr**v,qpmkkgcfhklnno+nnlkhfbdg`koo^n'nlkhfe
Space is not empty; it is the quiet memory from which every possibility emerges. We spend our lives believing we are isolated minds looking outward, yet every atom in our bodies was forged inside forgotten stars that surrendered themselves long before our names existed. To gaze into the night is to witness time made visible, where distant galaxies become echoes of moments that ended before humanity could dream. The universe expands without asking whether anyone understands it, reminding us that meaning is never discovered in the stars alone but in the consciousness capable of wondering why they exist at all. Perhaps the greatest mystery is not that space is infinite, but that a finite life can contemplate infinity, and for a fleeting moment the cosmos becomes aware of itself through us. We are the question and answer
Human In this world full of open questions and uncertainty Human is the story of people engaging to cope with what they perceive and experience. This future world is easy, yet strange to live in as a human. Queen with its AI-powered, mysterious operations is providing people virtually everything. The role of a human being is to enjoy. New controlling orders, regular bright light sessions and queen bots raise suspicions. However, we can evidence those who are human. Queen wants to make clear there is a difference between them and human. What will people do? Will they learn something about themselves, the world and Queen? The stakes are high in this adventure about what we can call human. Human is a timely book in this era of Artificial Intelligence. Human seeks to be a timeless story touching both our generations and the future generations to come.
Hello world. 720 is 2 times 360. It's like the hands of the clock go around 2 times to measure a full day. Also 7+2+0=9 which represents the last number in the decimal system. It stands for the ending of a cycle.
nph^TaV]a_^c\a\^^^_[\X\ca`^ble][\Z[]Y]\cd_Zcda_[]c`]c``_`Z[YUTWXUVWZZU\Sbva^[]]YX^[^[aa`^^_^_e`]Y]ZZ`_]YZWXVZWWYUXXV^[^mwad^a\Z[a]^]__^d_^\a`]_Z]\_ehc_]YWXXXWZXW[U\YWc}cadd^_`^_`_a_^]\_`_`[]]\`bfbaga\Z[][[ZXXYY][[h]j_adabdd_\[]^VU[V[ZZZ[[Y``]_c`c`___]Z\]Z^]badobjbabeaifcaaa_`Y[YXY]^``\^\_d`^\^`_b`\\]\][`^dpgh_aa_`c`a_``XY\ZWXZ_`__\d_`cac`ba_b`__^_][`_elkm\acaaab[[[\[`[`VYT^^]\[\_bab^_`a``_^_]b^]aaemnm`jhgefd`__^[[_b[[Xbad_`^]`dcb_addec`_\ddab_bdpqdiliikecc_c`c``WWTaa``b_^_aed_^a_ab`eae_bdcbcvrgklke`eec_[[]`\\YT^^_`a_bebb`^_^^^__c`abefedbjiekheigfjdccaeab_^b``^__]_`ac`^_``fc_egffdehl{lhfhifgfeehda``^a`_`]^^\\]\a]bb\a_cb^addeghfdkigedi`_gbba`__ab``a^ZZ]\[Y[_^`_^`edb]_^[heihkmrjibdhbcebda^]]aa`b]_\^\\]\[Z]_^^eghc_^ahegjkip~mkffhcbcehccaccdcb`c`_b_bfb``ba_ccaca_ddlhmolqsjjggaeefafacbc`eaecgaedcefc^`a`_^`_c^dclhhhpiptlmjfaddlfgggcd`eabab`ag]Ydfhcg`[\ackghhjenmqustrqrnbc]iglllndrswlkdmeegkdmiqoinkuk{nkstot}}qmeigeda[a\\[[[][^^^_]^ZZb`[__`]_`b``dhglinpswroiidfda^a\[[\_a^]^[[Y]Z\Z__^]`]_abb`fgiliqqpwrhehcda^cb^_^][^_^]][\ZX\^^[Z[__cbceacjlnnrstqmigigiggfdedegeegedeabaage^_^^]cedeciejllmqtno|zrskddagdb`fd`ceegabbftkek{u{wsxwutupqqsqtstsvvxyqsqstrmrwwxzz|~|{zy{|}trsndihdfgdfhfcljfhjnmmkeicgijjmjnqsxwuuonlopqmmgghkhglbecdaaeijihfgiebda^_abeefijpqqnjkiimrfjnigihfgcceeichhjfgefkihiijhhhhijpmnnplfhhlpvejmlihhhhggjjokildknhehljifhjdiikhkgdkhhffpqpstckkkjjlonlmjlqoqqkjhgeglmmjokmlllkmjhjiiignmpsjffifkikkjfegipslrjmokihkkkgkkjjkloskjhljjflnnvxebdcddfjjkkjlnkjkijklnnkkjchhhklnoogijghhipoqtgc^faaiffhflkkjhdlikgiigjjdihknmqtskjjijpknnlnfeed`hcdchfiikgjjggffiijjehhjmlmqpsihlbeimmnkn{fhchac^addfjmqilebb\abehfcdhklmppouhhiffjhjnnmpogi_dgdbfefinin`cfkloomijiknppkmoqkigfdfdgjmmpsdeaiff`adciqomeghefdfligiiiiljlnnjhcgfekusnrlp`deief\`[dhkkjnghcfdchidachklnmqmfgbmgd`quntxkbab``gZ][\\^bdgcgdjjjkkiggfgkjkljfefhcecspoi~tsoguwpwfclpoouwyvtsrooiunujillkkjmjplu
zoergnrpotmrmoooplmimtrqos~vnlmklnjnmtupkturplntqntqqpqkljfehifghkkfmdsrolnnjiololrrqoopopvqnjnkkqpnjkhigkhhjfiigoloruormklrnonppoupomrqnpknmpvztpnjhiiihkihlfmjhttruuopqopqprponmpqpqlnnmqsxsryrmklnllkiijjnllzn|prursuupmlnogflglkkklljqqnptqtqpppnkmnkonsrus|srsvr{xtrrrpqjljijnoqqmompuqomoqpsqmmnmnlqouyzprrpqtqrpqqijmkhikpqppmupqtrtqsrpsqppopnlqpv~mrtrrrslllmlqlqgjeoonmlmpsrsopqrqqpopnsonrrvq|zyvxuqppollpsllisrupqonqutspruuvtqpmuurspsuu{~{{}vttptqtqqhherrqqspoprvuporprsqvrvpsutsty}~}vqvvtpllnqmmjeoopqrpsvssqopooopptqrsvxvus{v}zv{yx|uttrvrsposqqoppnpqrtqopqqxtpvyxxuvz~~zxz{xyxvvzurqqorqpqnoommnmrnssmrptsoruuvyzxu}{yvu{qpyssrqpprsqqrokknmljlpoqpoqvusnpolzv{z}{suzstvsuronnrrqsnpmommnmlknpoovyztporzvy|}{}xxztstvzttrttutsqtqpspsxsqqsrpttrtrpuu~z||yyrvvxrxrtstqvrvtyrvutvxtoqrqpoqptout~zzz|xruu~xyyytuqvrsrsqrynjuxztyqlmrt}yzz|vv{yvurlrmmlllnlooopnokksqlppqnpqsqquzy~{{uxurormllmpronolljnkmkpponqnprssqxy{~{zvzturotsoponloponnlmkimoolklpptstvrt|~{y{y{yyxuvuvyvvyvuvrsrryvopoontvuvt{v|~~}uuryusqxuqtvvyrssxu{zuxyuxzxt~|xz|yyz}zy~svturrv{|{zxy{vsuroprsvvx{|{y{zxyttvv{tzz|xyvx}{z{{|zzzz{|zvz~|{xz|u{{}z}yu}zzxxytoxrr{xxzx~}}|zu~{}y{{y||u{z}xvvuqztutzx{{}y||yyxx{{||vzz|{~vssmrsvzxtuz}~vyzvxux~{y{{{{~|~}srsqqyknlmmosuytyu|||}}{yyxy}|}~|xvxztvt
8!0842<-8-2224+-%-<862:N@0+-)+0'0-<>4)<>84+0<60<6646)+'t82+00'%2+2+88622424@60'0))640')#%!)##'d8>28-)+8020442>42-8604)0-4@F<40'#%%%#)%#+p<8>>24624648420-4646+00-6:B:8D8-)+0++)%%''0++F+!+)))++'6604<6<64440)-0)20:8>T:J:8:@8HB<88846'+'%'0266-2-4>62-264:6--0-0+62>VDF48846<68466%'-)#%)4644->46<8<6:84:644240+64@NLP-8<888:+++-+6+6!'220-+-4:8:2468664240:2088@PRP6JFD@B>6442++4:++%:8>46206><:48>>@<64->>8:4:>şVX>HNHHL@<<4<6<66##8866:4248@>42848:6@8@4:><:<ŌbZDLNL@6@@<4++06--'224684:@::62422244<68:@B@>:xJH@LF@HDBJ><<8@8:42:662440468<62466B<4@DBB>@FNɄlNFBFHBDB@@F>86628646022--0-80::-84<:28>>@DFB>LHD@>H64D::86448:6682))0-+'+426426@>:042+F@HFLPZJH:>F:<@:>8200886:04-2--0-+)0422@DF<428F@DJLHVţrPLBBF<:<@F<<8<<><:6<64:4:B:66:84<<8<84>>NFPTNXɣ\JJDD8@@B8B8<:<6@8@<D8@><@B<26864264<2><NFFFVHVͧ^NPJB8>>NBDDD<>6@8:8:68D0'>BF<D6+-8<LDFFJ@RPX`\ѿ^ZXZR:<0HDNNNR>Z\dNL>P@@DL>PHXTHRL`LlRL\^T^ppvxXP@HD@>8+8--+++0+222402)):6+446046:66>FDNHRV\dZTHH>B>828-++-48202++'0)-)44206048::6BDHNHXXVdZF@F<>82<:2420+24200+-)%-22+)+44<:<@8<JNRRZ\^XPHDHDHDDB>@>@D@@D@>@8:88D@24220<@>@<H@JNNPX^RTxZNp\jzznjZ\L>>8D>:6B>6<@@D8::B^L@Llxpv`ld\fd`^`VXX\X^\^\bbfhX\X\^ZPZddfjjnrnljhlnpx^Z\R>HF>BD>BFB<NJBFJRPPL@H<DHJJPJRX\fd``TRNTVXǭtPPDDFLFDN:@<>88@HJHFBDH@:>8248:@@BHJVXXRJLHHPZBJRHDHFBD<<@@H<FFJBD@BLHFHHJFFFFHJVPRRVNBFFNVb͝z@JPNHFFFFDDJJTLHN>LRF@FNJHBFJ>HHLFLD>LFFBBVXV\ɡ^<LLLJJNTRNPJNXTXXLJFD@DNPPJTLPNNNLPJFJHHHDRPV\ͯJBBHBLHLLJB@DHV\NZJPTLHFLLLDLLJJLNT\LJFNJJBNRRbϫf@:><>>BJJLLJNRLJLHJLNRRLLJ<FFFLNRTTDHJDFFHVTX^ӣ~D<2B88HBBFBNLLJF>NHLDHHDJJ>HFLRPX^\LJJHJVLRRNRǯvB@@>6F<><FBHHLDJJDDBBHHJJ@FFJPNPXV\HFN:@HPPRLR͒lBF<F8<28>>BJPXHN@::-8:@FB<>FLNPVVT`FFHBBJFJRRPVTDH4>D>:B@BHRHR6<BLNTTPHJHLRVVLPTXLHDB>B>DJPPV\>@8HBB68><HXTP@DF@B>BNHDHHHHNJNRRJF<DB@L`\RZNV6>@H@B-6+>FLLJRDF<B><FH>8<FLNRPXPBD:PD>6X`R^fL:8:66D)0+--2:>D<D>JJJLLHDDBDLJLNJB@BF<@<\VTHDŽr^\TD`dVdB<NVTT`dv~txhb^\ZTTH`R`JHNNLLJPJVN`
At times I struggle to explain why 1984 is my favourite book of all time. It's certainly not fun or enjoyable to read. Orwell's style is rather dry & aside from 'Animal Farm' & 'Homage To Catalonia' most of his other works are uninspiring. I don't believe the fictional world of 1984 is likely to occur in our world & wouldn't be sustainable even if originated into existence. After reading it for the umpteenth time I'll even question myself as to why? Yet upon reflection I can see how it has shaped me & my world view more than anything else. Little lessons that add up to how I think, what I believe, who I am, the way I live and act and behave. Below is a selection of quotes & what they have taught me: "Never, for any reason on earth, could you wish for an increase of pain. Of pain you could wish only one thing: that it should stop. Nothing in the world was so bad as physical pain. In the face of pain there are no heroes." - It's ok to dislike pain, yet there will be suffering in your life. Figure out what is avoidable & unavoidable. Examine your pain and you'll find out most is crafted by your thoughts. “Doublethink means the power of holding two contradictory beliefs in one’s mind simultaneously, and accepting both of them.” - In the book this is a skill developed by O'Brien and the like and has a negative connotation. Yet life is inherently paradoxical. You cannot outthink a paradox. Lean into them and develop a humour of the absurd. “Who controls the past controls the future. Who controls the present controls the past.” - You get to decide your destiny. Your interpretation of reality will affect your life quality much more than the actual nature of 'objective' reality. “There was truth and there was untruth, and if you clung to the truth even against the whole world, you were not mad.” - Take heart if you desire the truth, it can be alienating & cruel to follow. This courage also needs to be tempered with proof that you are actually correct
At times I struggle to explain why 1984 is my favourite book of all time. It's certainly not fun or enjoyable to read. Orwell's style is rather dry & aside from 'Animal Farm' & 'Homage To Catalonia' most of his other works are uninspiring. I don't believe the fictional world of 1984 is likely to occur in our world & wouldn't be sustainable even if originated into existence. After reading it for the umpteenth time I'll even question myself as to why? Yet upon reflection I can see how it has shaped me & my world view more than anything else. Little lessons that add up to how I think, what I believe, who I am, the way I live and act and behave. Below is a selection of quotes & what they have taught me: "Never, for any reason on earth, could you wish for an increase of pain. Of pain you could wish only one thing: that it should stop. Nothing in the world was so bad as physical pain. In the face of pain there are no heroes." - It's ok to dislike pain, yet there will be suffering in your life. Figure out what is avoidable & unavoidable. Examine your pain and you'll find out most is crafted by your thoughts. “Doublethink means the power of holding two contradictory beliefs in one’s mind simultaneously, and accepting both of them.” - In the book this is a skill developed by O'Brien and the like and has a negative connotation. Yet life is inherently paradoxical. You cannot outthink a paradox. Lean into them and develop a humour of the absurd. “Who controls the past controls the future. Who controls the present controls the past.” - You get to decide your destiny. Your interpretation of reality will affect your life quality much more than the actual nature of 'objective' reality. “There was truth and there was untruth, and if you clung to the truth even against the whole world, you were not mad.” - Take heart if you desire the truth, it can be alienating & cruel to follow. This courage also needs to be tempered with proof that you are actually correct
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><path fill="#000" d="M0 0h8v8H0z"/><path fill="#fff" d="M2 1h4v1H2zm-1 1h6v1H1zm0 1h1v1H1zm5 0h1v1H6zM2 5h1v1H2zm3 0h1v1H5z"/><path fill="#f00" d="M2 6h4v1H2z"/></svg>
?&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&<BBBB<&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&<<&&&<<<&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&<<&<<&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&BBB<&&&&&<<&<<&&&&&BBBB<&&&&&&&&&&&&&&&&&&&&<<<&&BB&&&&B<<&&&&<<&&&<<&&&&&&&&&&&&&&&&&&<<<&<<&&&&&&&&&&&&&&&&&<<<<&&&&&&&&&&&&&&&&&<&ܬ<<&&&&&&&&&&&&&&&&<םB<&&&&&&&&&&&&&&&&<<&&&&&&&&&&&&&&&&&&&u&&&&&&&&&&&&&&&&&&&&&&&ju&&&&&&&&j׬&&&&&&&&uu&&&&&&&&&&&&&&&&&&&&&&&&&&&u&&&&&&&&&uu&&&&&&&&u&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 778 583"> <rect width="778" height="583" fill="#000"/> <g transform="translate(0.000000,583.000000) scale(0.100000,-0.100000)"> <path fill="#f0e614" d="M5284 5773 c4 -32 10 -114 12 -183 4 -97 9 -134 24 -166 l20 -41 216 -23 c260 -27 236 -58 182 240 -5 30 -11 62 -12 70 -1 8 -11 48 -21 88 l-19 72 -205 0 -204 0 7 -57z M1719 5565 c-110 -126 -116 -136 -129 -196 -29 -146 -110 -375 -147 -416 -20 -21 -54 -58 -77 -83 -72 -78 -126 -147 -126 -161 0 -7 -12 -25 -27 -41 -20 -21 -23 -29 -12 -32 36 -13 434 -114 437 -112 3 3 5 22 17 126 9 77 69 402 90 480 68 259 121 572 98 569 -5 0 -60 -60 -124 -134z M5641 5149 c-224 -44 -208 -37 -185 -84 9 -20 18 -46 20 -58 11 -70 27 -134 36 -145 6 -7 8 -15 5 -19 -3 -3 1 -22 9 -41 8 -20 12 -42 9 -50 -3 -8 -1 -20 5 -26 5 -7 11 -32 13 -57 3 -24 9 -62 15 -84 20 -75 23 -153 8 -178 -13 -20 -27 -98 -42 -232 -7 -64 -37 -230 -75 -419 -25 -124 -28 -151 -17 -166 93 -126 153 -181 436 -408 37 -30 81 -66 97 -80 17 -15 58 -51 93 -80 l62 -54 182 7 c181 6 185 6 363 -29 201 -39 195 -39 167 6 -11 18 -49 89 -85 158 -120 229 -113 219 -225 310 -266 216 -523 410 -574 435 -41 19 -41 25 7 90 37 51 92 159 88 173 -3 7 0 19 7 27 6 8 9 19 5 25 -4 6 5 48 18 93 30 97 46 159 53 206 2 19 9 40 13 45 5 6 11 31 14 56 3 25 8 59 11 75 4 17 8 48 10 70 2 21 9 41 15 43 6 2 9 7 6 11 -2 5 0 32 4 62 10 59 6 79 -15 79 -8 0 -14 5 -14 10 0 6 -6 10 -13 10 -7 0 -29 12 -48 26 -33 25 -112 64 -156 78 -24 8 -42 33 -63 86 -16 41 -39 70 -54 69 -6 -1 -98 -19 -205 -40z M1450 4303 c-899 -861 -846 -806 -854 -893 -1 -9 -5 -20 -8 -25 -3 -6 -9 -32 -13 -60 -4 -27 -12 -58 -17 -69 -7 -13 -6 -21 2 -26 7 -4 9 -13 6 -19 -8 -13 215 -14 734 -2 265 6 386 5 524 -6 208 -16 195 -19 146 37 -21 24 -36 48 -34 52 3 4 -2 8 -10 8 -9 0 -16 7 -16 15 0 8 -4 15 -8 15 -5 0 -15 12 -22 28 -8 15 -21 35 -28 44 -8 10 -12 24 -9 32 3 8 -2 17 -11 21 -15 6 -14 9 6 25 12 10 22 23 22 29 0 6 5 11 11 11 5 0 7 4 4 10 -7 12 56 124 91 161 13 14 33 42 44 62 11 20 29 54 40 74 11 20 33 54 50 74 l30 38 -233 272 c-127 150 -237 274 -242 276 -6 2 -98 -81 -205 -184z M4016 3224 c-25 -189 34 -519 100 -554 109 -59 119 256 15 484 -53 117 -104 148 -115 70z m154 -364 c23 -14 21 -143 -2 -167 -29 -28 -43 -2 -42 76 1 96 10 113 44 91z M550 2680 c6 -11 8 -25 5 -30 -3 -5 0 -20 6 -32 7 -13 16 -43 21 -68 5 -25 10 -54 12 -65 2 -11 7 -39 10 -63 2 -23 8 -47 11 -53 4 -5 8 -20 10 -32 4 -37 30 -141 43 -177 3 -8 6 -17 7 -20 1 -3 4 -12 7 -20 9 -22 19 -54 20 -60 0 -3 6 -25 13 -50 25 -89 26 -100 10 -100 -22 0 -18 -11 18 -50 56 -59 65 -81 77 -185 13 -117 52 -249 113 -385 6 -14 12 -29 12 -35 1 -5 13 -35 27 -65 14 -30 33 -77 43 -105 10 -27 22 -55 26 -60 4 -6 17 -35 29 -65 88 -224 143 -318 204 -349 25 -13 44 -28 43 -33 -1 -5 27 -22 63 -37 74 -31 53 -3 254 -343 l118 -198 638 0 638 0 1 68 c0 37 7 135 15 217 20 185 20 174 -4 212 l-19 32 -68 -20 c-91 -26 -104 -18 -187 115 -11 17 -43 63 -71 101 -29 39 -58 82 -66 96 -8 14 -27 47 -44 73 -16 26 -50 80 -75 119 -25 40 -58 96 -74 126 -40 78 -49 94 -64 113 -8 9 -21 32 -30 50 -21 41 -202 85 -245 59 -23 -14 -7 6 76 95 l38 40 -20 25 c-41 52 -221 407 -221 436 0 4 -11 28 -24 53 -13 25 -24 47 -23 50 3 34 -46 100 -97 128 l-56 32 36 70 c47 89 46 90 -82 123 -60 15 -269 68 -464 117 -663 169 -728 183 -710 150z m1463 -1392 c-16 -18 -116 -161 -222 -318 -236 -351 -217 -326 -235 -311 -29 24 -39 77 -27 144 15 87 69 318 75 325 6 5 427 191 435 192 3 0 -9 -15 -26 -32z M5565 2620 c-16 -4 -41 -10 -55 -14 -61 -13 -193 -134 -219 -200 -36 -94 -48 -127 -61 -176 -23 -84 -162 -488 -180 -525 -39 -79 -83 -227 -86 -290 -10 -222 -30 -326 -66 -353 -27 -20 -25 -25 78 -155 53 -67 98 -127 101 -134 3 -7 -16 -165 -42 -351 l-47 -338 26 -42 26 -42 230 0 c171 0 230 3 230 12 0 6 9 30 19 52 11 24 30 117 46 226 15 102 30 192 34 200 20 39 80 145 137 241 35 59 64 112 64 119 0 6 18 43 41 81 22 38 58 106 79 152 21 45 57 116 78 157 22 41 49 95 61 120 12 25 40 79 62 120 66 121 83 157 119 245 29 69 49 149 44 170 -5 21 -3 25 16 25 12 0 36 10 53 23 38 26 51 35 111 66 25 14 46 28 46 33 0 4 9 8 20 8 11 0 43 18 72 40 29 22 63 43 76 46 12 4 22 11 22 18 1 12 67 46 91 46 12 0 12 2 0 9 -11 7 -6 12 20 18 19 5 33 13 32 19 0 5 4 12 10 16 35 21 -1149 372 -1240 367 -10 0 -31 -4 -48 -9z M4560 740 c-11 -11 -24 -20 -29 -20 -4 0 -19 -10 -33 -22 -26 -25 -73 -53 -154 -92 -28 -14 -57 -33 -65 -42 -11 -13 -24 -15 -69 -9 -30 3 -71 8 -90 10 -19 3 -55 9 -80 14 -104 22 -100 23 -100 -23 0 -38 -6 -47 -89 -131 l-90 -90 -34 -140 c-19 -77 -38 -152 -41 -168 l-6 -27 605 0 c573 0 606 1 601 18 -3 9 -24 139 -47 287 l-42 270 -96 92 c-104 99 -111 103 -141 73z"/> <path fill="#141ee6" d="M3577 5773 c-42 -71 -368 -621 -425 -719 -46 -77 -56 -137 -26 -159 13 -9 -31 -28 -252 -103 -201 -69 -270 -89 -276 -79 -4 6 -117 189 -250 405 l-243 393 -55 -6 c-30 -4 -75 -8 -99 -10 -45 -3 -71 -23 -71 -53 0 -17 30 -43 90 -78 25 -15 46 -27 47 -28 0 -1 53 -188 117 -416 64 -228 133 -473 154 -545 l38 -130 112 3 112 2 210 -140 c125 -83 208 -145 205 -152 -2 -7 -21 -64 -41 -127 -38 -120 -195 -360 -406 -623 l-70 -87 -52 21 c-104 41 -673 76 -1091 67 -192 -4 -446 -8 -564 -8 l-214 -1 -163 100 c-195 119 -175 116 -280 33 l-84 -66 0 -103 0 -104 53 21 c158 63 190 72 201 56 6 -9 58 -106 116 -216 l105 -200 95 -21 c80 -18 794 -196 1150 -286 129 -33 125 -29 106 -100 -19 -71 -52 -57 237 -104 l246 -40 281 111 c155 61 283 109 285 107 9 -9 -106 -333 -140 -396 -47 -85 -47 -85 -98 -92 -41 -5 -54 -16 -278 -251 -241 -252 -297 -313 -266 -290 11 9 31 12 55 8 94 -13 749 -140 850 -164 l114 -27 18 24 c10 14 45 84 76 157 32 73 59 131 60 130 17 -17 -99 -400 -187 -619 l-102 -253 42 -80 43 -80 -19 -193 c-10 -106 -17 -208 -15 -227 l4 -35 324 0 324 0 6 28 c3 15 22 90 41 167 l34 140 91 92 91 92 -7 123 c-3 68 -24 226 -45 353 l-39 230 60 169 c32 94 60 171 62 173 1 1 15 -55 30 -125 15 -70 29 -135 32 -144 3 -9 64 -69 137 -132 73 -63 242 -221 377 -351 l245 -238 47 -286 c25 -157 47 -287 47 -288 1 -2 35 -3 76 -3 l75 0 -26 42 -26 41 47 343 48 343 -29 38 c-15 21 -157 200 -315 399 l-286 361 -42 389 c-22 214 -41 400 -41 414 0 18 42 -38 145 -193 98 -146 150 -215 158 -210 6 4 189 146 405 315 438 343 405 321 487 337 88 18 263 -25 872 -213 l502 -155 260 -11 261 -12 146 51 c81 29 150 54 154 56 8 5 -171 348 -194 372 -6 7 -38 18 -71 24 -33 6 -188 48 -345 95 -184 54 -352 96 -475 120 l-190 36 -206 -8 -207 -7 -428 237 -429 237 -319 245 c-175 135 -320 243 -323 241 -2 -3 -15 -71 -28 -152 -13 -81 -26 -149 -28 -151 -5 -5 -147 242 -147 255 0 9 330 145 350 144 24 -2 38 24 305 552 136 270 250 493 254 497 3 4 148 35 321 68 414 79 453 92 442 145 -7 39 -19 42 -157 47 -71 3 -303 25 -514 48 l-383 43 -83 -89 c-81 -87 -89 -100 -326 -547 l-244 -459 -116 -78 -116 -78 -69 -170 c-38 -94 -70 -170 -71 -169 -1 2 -9 70 -18 152 -9 82 -19 154 -23 160 -3 5 -58 87 -123 181 l-118 170 34 136 c32 124 53 175 245 587 l211 452 -7 79 -7 80 -273 0 -273 0 -34 -57z m-280 -1570 c-3 -21 -17 -90 -31 -153 -13 -63 -36 -227 -50 -365 -33 -311 -51 -378 -97 -349 -33 20 59 743 107 835 36 71 80 91 71 32z m433 -72 c0 -14 -68 -170 -204 -470 -36 -80 -66 -153 -66 -162 0 -31 -12 -49 -31 -49 -35 0 -30 23 36 175 37 83 105 241 153 353 85 198 112 235 112 153z m468 -485 c62 -93 110 -171 108 -173 -13 -14 -246 327 -246 360 0 36 14 42 21 10 3 -15 56 -104 117 -197z m-157 -237 c47 -13 111 -41 165 -73 87 -52 100 -65 201 -196 59 -77 149 -136 271 -175 49 -16 53 -20 49 -43 -3 -14 -1 -37 4 -50 8 -19 6 -23 -8 -20 -15 3 -34 29 -45 61 -3 8 -459 99 -466 92 -7 -7 -8 -118 -3 -222 l6 -104 -70 -20 c-38 -11 -83 -25 -98 -31 -22 -8 -31 -8 -36 0 -4 7 -27 16 -52 21 -119 25 -512 134 -574 159 -109 45 -217 81 -286 94 -56 12 -63 16 -70 41 -15 55 -3 62 151 86 151 23 178 36 286 128 170 146 315 237 414 259 76 17 74 17 161 -7z m548 -138 c17 -33 31 -72 31 -87 l0 -29 -30 31 c-19 18 -37 52 -46 85 -8 30 -18 63 -21 74 -11 38 32 -11 66 -74z m-1818 -5 c2 -2 28 -18 57 -36 57 -35 65 -57 37 -110 -29 -56 -70 -73 -85 -35 -5 14 -22 30 -37 36 -36 13 -43 25 -43 77 0 71 50 156 61 103 4 -18 8 -33 10 -35z M1822 1226 c-253 -111 -217 -78 -253 -232 -56 -245 -58 -297 -13 -335 18 -15 -1 -40 235 311 106 157 206 300 222 318 17 17 29 32 26 32 -2 0 -100 -42 -217 -94z"/> <path fill="#fafafa" d="M7021 4146 l-1 -109 -25 23 c-28 26 -33 16 -10 -17 17 -24 0 -32 -65 -33 -19 0 -42 -5 -50 -11 -11 -8 5 -9 58 -5 77 7 82 4 55 -28 -13 -15 -10 -14 10 3 l27 24 0 -121 c0 -75 4 -122 10 -122 6 0 10 44 10 111 0 99 2 110 15 99 21 -17 29 -7 16 18 -6 12 -11 23 -11 25 0 2 34 7 76 10 42 3 79 8 82 11 14 14 -12 17 -82 9 -77 -9 -89 -6 -61 17 9 7 13 15 10 18 -3 3 -14 -3 -25 -13 -20 -18 -20 -18 -21 51 -1 128 -17 163 -18 40z M3261 4221 c-65 -65 -110 -309 -146 -792 -7 -98 1 -115 39 -74 31 33 30 30 62 330 14 138 37 302 50 365 43 199 43 219 -5 171z M3618 3978 c-48 -112 -116 -270 -153 -353 -66 -152 -71 -175 -36 -175 19 0 31 18 31 49 0 9 30 82 66 162 211 463 214 472 191 502 -10 15 -24 -10 -99 -185z M4067 3864 c-24 -24 74 -196 200 -352 66 -81 42 -35 -69 134 -61 93 -114 182 -117 197 -3 16 -9 25 -14 21z M3880 3416 c-99 -22 -244 -113 -414 -259 -108 -92 -135 -105 -286 -128 -154 -24 -166 -31 -151 -86 7 -25 14 -29 70 -41 69 -13 177 -49 286 -94 62 -25 455 -134 574 -159 25 -5 48 -14 52 -20 4 -7 19 -6 49 4 23 8 38 17 32 21 -60 37 -137 594 -89 642 44 44 132 -72 181 -238 14 -46 25 -75 25 -65 1 9 2 17 4 17 28 -1 463 -92 465 -97 11 -32 30 -58 45 -61 14 -3 16 1 8 20 -5 13 -7 36 -4 50 4 23 0 27 -49 43 -122 39 -212 98 -271 175 -101 131 -114 144 -201 196 -54 32 -118 60 -165 73 -87 24 -85 24 -161 7z M4523 3345 c3 -11 13 -44 21 -74 9 -33 27 -67 46 -85 l30 -31 0 29 c0 33 -53 136 -83 163 -20 17 -20 17 -14 -2z M2744 3319 c-60 -75 -60 -176 -1 -198 15 -6 32 -22 37 -36 15 -38 56 -21 85 35 28 53 20 75 -37 110 -66 40 -59 33 -67 71 -5 25 -9 29 -17 18z"/> </g> <!-- . * + . . + * . + . .* + . \ | / * =( @ )= * / | \ As though simply asking the question would shatter the universe, but that was absurd. . * + . . + * . + . .* + . --> </svg>
1. Over the past 35 years, many major galleries have shifted from discovering artists and emerging art forms to actively constructing artists’ careers and market positions. In this model, the artist increasingly becomes the product rather than the artwork itself. Among a growing number of collectors, that narrative is gradually losing credibility. 2. The traditional mechanisms of exclusivity are also being challenged. Invitations to private openings and VIP previews once created a genuine sense of access and distinction. Today, artists communicate directly with audiences through social media, while visitors regularly share images from pre-opening events online. Moments that were once exclusive have become instantly public. 3. Unlike the twentieth century, contemporary art is no longer organized around a small number of dominant movements with relatively clear aesthetic criteria. Movements such as Impressionism, Cubism, Minimalism, Fluxus, Arte Povera, Land Art, or Conceptual Art gave artists, critics, galleries, and collectors a shared framework through which artistic quality and significance could be discussed. Today, the field is highly fragmented. While many interesting tendencies and communities exist, there are no real apparent movements that carry the same cultural authority (except for a small emerging underground digital art scene). As a result, both artists and the public often struggle to evaluate claims of quality made by galleries. Without broader movements or shared criteria, it becomes increasingly difficult to understand why one artist should be considered more important than another. This weakens the role of galleries as cultural gatekeepers and arbiters of artistic value. As a result of these shifts, many younger collectors increasingly prefer artist-run exhibitions and direct relationships with artists rather than relationships mediated through galleries. If the traditional role of major galleries is to build credibility around artists, they now face competition from a new source of credibility: the audiences artists cultivate themselves. Views, engagement, followers, and online communities have become alternative forms of cultural validation that operate independently of traditional gallery structures. As a result, big galleries like @PaceGallery increasingly have to justify what unique value they bring to the cultural conversation. -@DagieDee on X
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100%" viewBox="0 0 992 992" enable-background="new 0 0 992 992" xml:space="preserve"> <path fill="#000000" opacity="1.000000" stroke="none" d=" M439.212158,146.943512 C450.854004,146.958618 462.020996,147.092697 473.183350,146.923370 C477.695099,146.854950 479.248840,148.496277 479.243195,153.083740 C479.157837,222.740936 479.173889,292.398438 479.345673,362.055389 C479.358276,367.166107 477.728943,368.573547 472.737610,368.513153 C451.410217,368.254974 430.078186,368.385498 408.747833,368.366150 C400.178406,368.358368 400.174622,368.348969 400.171356,377.135498 C400.167267,388.134003 400.350708,399.136932 400.082428,410.128906 C399.973175,414.604889 401.573395,415.778198 405.871979,415.743439 C428.367554,415.561523 450.866486,415.774658 473.361511,415.561371 C477.969330,415.517670 479.356628,416.927338 479.339539,421.504425 C479.189697,461.665222 479.163239,501.827209 479.347900,541.987671 C479.371460,547.110474 477.762543,548.492432 472.769073,548.437134 C450.608002,548.191956 428.441315,548.460083 406.280121,548.220703 C401.424347,548.168213 400.021698,549.688171 400.068268,554.451721 C400.275177,575.613464 400.237335,596.779175 400.053558,617.941467 C400.020569,621.741943 401.304291,623.353577 404.853851,624.677979 C434.928314,635.899536 451.966705,657.754211 456.362640,689.382324 C462.711395,735.060730 429.808441,773.489929 387.178070,778.755066 C341.680298,784.374451 302.813293,756.865173 295.894470,712.371033 C289.627533,672.068848 312.244843,633.496643 351.858337,623.661011 C356.102295,622.607239 355.657166,619.727539 355.658020,616.759460 C355.667236,583.930481 355.665070,551.101440 355.666534,518.272461 C355.666626,515.606140 355.868134,512.921814 355.627441,510.277618 C355.248352,506.113403 356.974243,504.773407 361.072571,504.800385 C382.402039,504.941040 403.732880,504.862701 425.063263,504.860809 C425.563202,504.860748 426.063171,504.867004 426.563080,504.864075 C433.815674,504.821747 433.833923,504.812561 433.838348,497.701385 C433.845154,486.702881 433.608154,475.697479 433.939728,464.709015 C434.091339,459.684113 432.558228,458.170685 427.457031,458.188904 C377.464264,458.367554 327.470642,458.297424 277.477234,458.295380 C270.837708,458.295105 270.814301,458.288452 270.813660,451.653473 C270.809723,411.492126 270.893524,371.330414 270.723358,331.169830 C270.702423,326.227997 271.970459,324.391205 277.152466,324.568237 C287.805267,324.932220 298.484192,324.867432 309.142212,324.589508 C313.823456,324.467438 315.132080,326.166199 315.099243,330.690460 C314.910553,356.685272 314.973633,382.681946 314.966187,408.678009 C314.964203,415.646545 314.987946,415.655182 321.814911,415.660034 C331.147003,415.666656 340.486511,415.438141 349.808105,415.750153 C354.438019,415.905151 355.859772,414.339294 355.830536,409.768127 C355.663116,383.606018 355.744690,357.442261 355.752808,331.279083 C355.754822,324.716644 355.773438,324.693726 362.460114,324.688843 C384.290558,324.672882 406.122192,324.553467 427.950500,324.767670 C432.616516,324.813446 434.043823,323.281433 433.906799,318.754578 C433.634705,309.764099 433.825500,300.759552 433.831116,291.760803 C433.835236,285.152008 433.839600,285.109131 427.167419,285.106445 C371.008179,285.083710 314.848938,285.080017 258.689697,285.064819 C250.357513,285.062561 242.024185,284.942902 233.693710,285.053131 C230.159668,285.099915 228.656769,283.938629 228.664673,280.148438 C228.754044,237.321060 228.747238,194.493347 228.646942,151.665985 C228.637650,147.698227 230.546066,146.917877 233.888733,146.943039 C244.386673,147.022064 254.891846,147.178513 265.381378,146.869110 C270.119049,146.729355 271.357239,148.498642 271.332825,152.967896 C271.184540,180.130142 271.264160,207.293640 271.267456,234.456711 C271.268341,241.664642 271.267944,241.679382 278.247467,241.680450 C327.740967,241.688080 377.234436,241.690872 426.727936,241.690430 C433.804596,241.690353 433.855499,241.678467 433.859009,234.377121 C433.872101,207.214081 433.874969,180.050980 433.807007,152.888077 C433.798187,149.361511 433.998566,146.366974 439.212158,146.943512 M337.722717,677.218933 C324.816681,701.308411 335.574341,728.514099 358.329224,739.286316 C378.808838,748.981384 402.804596,740.153748 414.664978,719.187683 C425.129639,700.688904 418.877014,675.852966 400.534698,663.060974 C379.454559,648.359619 353.185333,654.046997 337.722717,677.218933 z"/> <path fill="#000000" opacity="1.000000" stroke="none" d=" M714.995239,458.287903 C667.336609,458.295746 620.177307,458.300629 573.017944,458.315216 C566.206177,458.317322 566.195801,458.338959 566.188232,465.238159 C566.175720,476.569702 566.348511,487.905182 566.088806,499.230865 C565.987305,503.659912 567.522522,504.953278 571.853760,504.921295 C594.015625,504.757507 616.179443,504.856781 638.342590,504.879517 C645.198364,504.886536 645.238708,504.928162 645.241699,511.912689 C645.256653,546.407349 645.391724,580.903015 645.130981,615.395630 C645.089417,620.882935 646.736633,623.234741 652.025391,624.688904 C681.064453,632.673401 703.280273,658.211182 706.231262,691.334595 C710.014343,733.798340 683.816528,769.436401 641.962341,777.808594 C594.300598,787.342407 552.545044,756.491272 545.477722,713.133240 C540.055847,679.869934 553.724854,648.011658 581.528503,630.947998 C586.383484,627.968445 591.588562,625.710876 596.989929,624.033997 C599.668152,623.202576 600.817688,622.018799 600.807678,619.185791 C600.729309,597.022888 600.676636,574.859131 600.806213,552.696777 C600.830872,548.484497 598.580566,548.286682 595.409180,548.294983 C572.912781,548.353516 550.415161,548.210144 527.920410,548.410034 C523.222717,548.451782 521.585388,547.181152 521.606201,542.259399 C521.776184,502.099731 521.764343,461.938599 521.585144,421.778931 C521.563660,416.959717 522.997620,415.518494 527.802917,415.566040 C550.130615,415.787048 572.462402,415.568054 594.790894,415.741577 C598.993591,415.774231 600.780823,414.807678 600.681458,410.226624 C600.421204,398.234375 600.502747,386.231201 600.659973,374.235168 C600.717896,369.818176 599.254395,368.209106 594.576843,368.259613 C572.249634,368.500793 549.917603,368.273315 527.589478,368.469147 C523.080383,368.508728 521.576050,367.261108 521.585144,362.569397 C521.720520,292.747131 521.716736,222.924408 521.582336,153.102097 C521.573181,148.313171 522.962097,146.705734 527.806213,146.861710 C537.960754,147.188721 548.133179,146.955521 558.298218,146.967880 C566.134094,146.977417 566.157166,146.987961 566.160400,155.110596 C566.170898,181.606476 566.160950,208.102341 566.163330,234.598221 C566.163940,241.666702 566.171326,241.678665 573.336304,241.679718 C623.328613,241.687027 673.320923,241.690735 723.313232,241.690536 C729.787781,241.690506 729.897095,241.677231 729.910034,234.980621 C729.962280,207.985031 729.940552,180.989319 729.947449,153.993652 C729.949219,147.078888 730.023132,146.979065 736.899353,146.966599 C746.897705,146.948471 756.901306,147.164276 766.892517,146.899628 C771.778748,146.770203 773.144592,148.665924 773.132263,153.356705 C773.023193,194.849747 773.114746,236.343292 773.132141,277.836700 C773.135071,284.839142 773.015198,284.987457 765.726013,284.992615 C707.235107,285.034119 648.744141,285.048920 590.253235,285.073608 C583.920898,285.076294 577.584045,285.239075 571.257996,285.043823 C567.320251,284.922272 566.025574,286.492218 566.104675,290.347076 C566.302979,300.008209 566.325989,309.679230 566.113953,319.339569 C566.022705,323.496521 567.538513,324.776550 571.632141,324.748047 C593.794312,324.593658 615.959351,324.801819 638.120361,324.580261 C642.973816,324.531738 644.373047,326.111053 644.339783,330.863159 C644.155212,357.191071 644.362305,383.521820 644.163940,409.849548 C644.128845,414.506592 645.574707,415.885345 650.142395,415.753326 C660.465515,415.454956 670.807739,415.479614 681.132812,415.740814 C685.542114,415.852386 686.906677,414.307831 686.882690,409.981201 C686.738464,383.986084 686.808044,357.989746 686.821411,331.993835 C686.825195,324.688324 686.848816,324.684784 694.357239,324.681458 C704.355713,324.677094 714.357544,324.830139 724.351013,324.606323 C728.573792,324.511749 730.287842,325.774872 730.274963,330.243134 C730.156799,371.236389 730.171631,412.230255 730.264648,453.223663 C730.273560,457.154480 728.865295,458.652313 724.978943,458.356415 C721.833984,458.116974 718.657166,458.295105 714.995239,458.287903 M641.883118,658.508911 C620.376709,650.076721 598.414246,657.524719 587.038513,677.107910 C576.349915,695.508484 581.068604,719.457336 598.127441,733.387634 C617.942078,749.568359 647.422974,744.621399 661.891113,722.679749 C667.265198,714.529602 670.469543,705.580688 669.375977,695.818604 C667.446472,678.592651 658.359192,666.200623 641.883118,658.508911 z"/> </svg>
::;=+*xX#::;=+*xX#::;=+*xX#:Yvtug naq Qnexarff ner gjb fvqrf bs gur fnzr pbva - gur pbva bs ernyvgl. N fhecyhf va bar vf n qrsvpvg va gur bgure; gur bccbfvgr bs fbzrguvat vf abg abguvat.:#Xx*+=;::#Xx*+=;::#Xx*+=;:
<!-- assembler cc0 | renders the_terminal_god_speed live from The Vessel token 9994, entries 32-38. RPC order: wallet (window.ethereum) -> seed list -> if ALL fail, a form lets you paste your own RPC. --> <!DOCTYPE html> <html><head><meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"> <title>the_terminal_god_speed</title> <style> *{margin:0;padding:0;box-sizing:border-box} html,body{width:100%;height:100%;background:#000;overflow:hidden;font-family:monospace;color:#666;display:flex;align-items:center;justify-content:center} #s{position:fixed;z-index:2;text-align:center;font-size:12px;line-height:1.8;padding:24px;max-width:90vw} #s .e{color:#a44} #s .r{color:#888;cursor:pointer;text-decoration:underline} #f{display:none;flex-direction:column;gap:10px;align-items:center} #f.show{display:flex} #f label{color:#888;font-size:12px} #f .hint{color:#555;font-size:10px;line-height:1.6} #f input{background:#0a0a0a;border:1px solid #333;color:#9c9;font-family:monospace;font-size:12px;padding:8px 10px;width:min(420px,80vw);outline:none} #f input:focus{border-color:#585} #f button{background:#111;border:1px solid #444;color:#9c9;font-family:monospace;font-size:11px;padding:6px 14px;cursor:pointer} #f button:hover{border-color:#585} </style></head> <body> <div id="s"> <div id="status">reading chain...</div> <form id="f" onsubmit="return useCustomRpc(event)"> <label>all endpoints unreachable</label> <div class="hint">paste an Ethereum RPC URL and press enter<br>(any mainnet JSON-RPC endpoint, e.g. from chainlist.org)</div> <input id="rpc" type="text" placeholder="https://..." autocomplete="off" spellcheck="false"> <button type="submit">assemble</button> </form> </div> <script> var VESSEL='0xECb92Cc7112b80A2234936315BbB493fb48d1463'; var TOKEN=9994, START=32, COUNT=7; var rpcs=['https://ethereum-rpc.publicnode.com','https://eth.drpc.org','https://0xrpc.io/eth','https://eth.llamarpc.com','https://cloudflare-eth.com']; var statusEl=document.getElementById('status'); var formEl=document.getElementById('f'); var inputEl=document.getElementById('rpc'); function pad(n){return n.toString(16).padStart(64,'0');} function h2s(h){if(h.indexOf('0x')===0)h=h.slice(2);var b=new Uint8Array(h.length/2);for(var i=0;i<h.length;i+=2)b[i/2]=parseInt(h.substr(i,2),16);return new TextDecoder().decode(b);} async function call(to,data){ var p=[{to:to,data:data},'latest']; if(window.ethereum){try{var r=await window.ethereum.request({method:'eth_call',params:p});if(r&&r!=='0x')return r;}catch(e){}} for(var i=0;i<rpcs.length;i++){try{ var x=await fetch(rpcs[i],{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({jsonrpc:'2.0',method:'eth_call',params:p,id:1})}); var j=await x.json();if(j.result&&j.result!=='0x')return j.result; }catch(e){}} return null; } // returns the entry's hex chars (NOT decoded), or null. We concat hex across all // entries and decode ONCE at the end so multi-byte UTF-8 split across a chunk // boundary never corrupts. async function readEntryHex(entry){ var raw=await call(VESSEL,'0x39c50f01'+pad(TOKEN)+pad(entry)); if(!raw||raw==='0x'||raw.length<130)return null; var len=parseInt(raw.slice(66,130),16); return len?raw.slice(130,130+len*2):null; } function showForm(msg){ statusEl.innerHTML=msg||''; formEl.classList.add('show'); setTimeout(function(){inputEl.focus();},50); } async function assemble(){ formEl.classList.remove('show'); var hex=''; for(var i=0;i<COUNT;i++){ var entry=START+i; statusEl.innerHTML='reading '+(i+1)+'/'+COUNT+'…'; var part=await readEntryHex(entry); if(part===null){ showForm('<span class="e">couldn’t read entry '+entry+' from any endpoint</span>'); return false; } hex+=part; } document.open();document.write(h2s(hex));document.close(); return true; } function useCustomRpc(ev){ ev.preventDefault(); var u=(inputEl.value||'').trim(); if(!/^https?:\/\//i.test(u)){ showForm('<span class="e">enter a full http(s) URL</span>'); return false; } rpcs=[u]; // user's endpoint takes over statusEl.innerHTML='retrying via your endpoint…'; formEl.classList.remove('show'); assemble().then(function(ok){ if(!ok) showForm('<span class="e">that endpoint failed too — try another</span>'); }); return false; } assemble(); </script> </body></html>
baseLayer.filters.mainFilter||STATE.baseLayer.filters.processingFilters&&STATE.baseLayer.filters.processingFilters.length>0||STATE.baseLayer.filters.postFilters&&STATE.baseLayer.filters.postFilters.length>0||STATE.baseLayer.filters.shaderFilters&&STATE.baseLayer.filters.shaderFilters.length>0)&&baseLayerGraphics&&void 0!==FilterRegistry){baseLayerGraphics.clear(),baseLayerGraphics.image(baseImage,a,o,t,i),applyLayerFilters(baseLayerGraphics,STATE.baseLayer.filters,s,deltaTime);const e=drawingContext;e.save(),e.globalCompositeOperation=STATE.baseLayer.blendMode||"source-over",e.globalAlpha=void 0!==STATE.baseLayer.opacity?STATE.baseLayer.opacity:1,e.drawImage(baseLayerGraphics.elt,0,0),e.restore()}else image(baseImage,a,o,t,i)}const n=drawingContext;layers.forEach(e=>{if(!e.visible)return;renderEngine.outputBuffer&&(renderEngine.outputBuffer.clear(),renderEngine.outputBuffer.drawingContext.drawImage(canvas.elt||canvas,0,0)),e.component.update(s,deltaTime),e.graphics.clear(),e.component.draw(e.graphics),e.filters&&void 0!==FilterRegistry&&applyLayerFilters(e.graphics,e.filters,s,deltaTime),n.save(),n.globalCompositeOperation=e.blendMode||"source-over",n.globalAlpha=void 0!==e.opacity?e.opacity:1;const t=e.transform||{},i=width/originalWidth,a=height/originalHeight,o=width*(t.anchorX||.5),r=height*(t.anchorY||.5),l=(t.x||0)*i,h=(t.y||0)*a;n.translate(o+l,r+h),n.rotate((t.rotation||0)*Math.PI/180),n.scale(t.scaleX||1,t.scaleY||1),n.translate(-o,-r),n.drawImage(e.graphics.elt,0,0),n.restore()}),STATE.globalFilters&&void 0!==FilterRegistry&&applyGlobalFilters(s,deltaTime)}window.renderEngine=renderEngine;let globalFilterGraphics=null;function applyGlobalFilters(e,t){const i=STATE.globalFilters;if(i){if(i.activeMainFilter){const e=FilterRegistry.create(i.activeMainFilter);if(e){const t=i.mainFilterParams||{};if(e.setParameters(t),e.setEnabled(!0),e.needsGraphicsAccess()){loadPixels();const t=e.processPixels(pixels,width,height);if(t!==pixels)for(let e=0;e<t.length;e++)pixels[e]=t[e];updatePixels()}else{loadPixels();const t=e.processPixels(pixels,width,height);if(t!==pixels)for(let e=0;e<t.length;e++)pixels[e]=t[e];updatePixels()}}}i.processingFilters&&i.processingFilters.length>0&&i.processingFilters.forEach(({name:e,params:t})=>{const i=FilterRegistry.create(e);if(i){t&&i.setParameters(t),i.setEnabled(!0),loadPixels();const e=i.processPixels(pixels,width,height);if(e!==pixels)for(let t=0;t<e.length;t++)pixels[t]=e[t];updatePixels()}}),i.postFilters&&i.postFilters.length>0&&i.postFilters.forEach(({name:e,params:t})=>{const i=FilterRegistry.create(e);if(i){t&&i.setParameters(t),i.setEnabled(!0),loadPixels();const e=i.processPixels(pixels,width,height);if(e!==pixels)for(let t=0;t<e.length;t++)pixels[t]=e[t];updatePixels()}}),i.shaderFilters&&i.shaderFilters.length>0&&(globalFilterGraphics&&globalFilterGraphics.width===width&&globalFilterGraphics.height===height||(globalFilterGraphics&&globalFilterGraphics.remove(),globalFilterGraphics=createGraphics(width,height)),i.shaderFilters.forEach(({name:i,params:a})=>{const o=FilterRegistry.create(i);o&&(a&&o.setParameters(a),o.setEnabled(!0),e&&"function"==typeof o.update&&o.update(t||16,e),globalFilterGraphics.clear(),globalFilterGraphics.image(get(),0,0),o.processGraphics(globalFilterGraphics,width,height),image(globalFilterGraphics,0,0))}))}}function applyLayerFilters(e,t,i,a){const o=e.width,s=e.height;if(t.mainFilter){const i=FilterRegistry.create(t.mainFilter);if(i)if(t.mainFilterParams&&i.setParameters(t.mainFilterParams),i.setEnabled(!0),i.needsGraphicsAccess())i.processGraphics(e,o,s);else{e.loadPixels();const t=i.processPixels(e.pixels,o,s);if(t!==e.pixels)for(let i=0;i<t.length;i++)e.pixels[i]=t[i];e.updatePixels()}}t.processingFilters&&t.processingFilters.forEach(({name:t,params:i})=>{const a=FilterRegistry.create(t);if(a)if(i&&a.setParameters(i),a.setEnabled(!0),a.needsGraphicsAccess())a.processGraphics(e,o,s);else{e.loadPixels();const t=a.processPixels(e.pixels,o,s);if(t!==e.pixels)for(let i=0;i<t.length;i++)e.pixels[i]=t[i];e.updatePixels()}}),t.postFilters&&t.postFilters.forEach(({name:t,params:i})=>{const a=FilterRegistry.create(t);if(a)if(i&&a.setParameters(i),a.setEnabled(!0),a.needsGraphicsAccess())a.processGraphics(e,o,s);else{e.loadPixels();const t=a.processPixels(e.pixels,o,s);if(t!==e.pixels)for(let i=0;i<t.length;i++)e.pixels[i]=t[i];e.updatePixels()}}),t.shaderFilters&&t.shaderFilters.forEach(({name:t,params:n})=>{const r=FilterRegistry.create(t);r&&(n&&r.setParameters(n),r.setEnabled(!0),i&&"function"==typeof r.update&&r.update(a||16,i),r.processGraphics(e,o,s))})}async function mouseClicked(){await audioEngine.resume(),audioEngine.isLoaded&&audioEngine.toggle()}function windowResized(){const e=calculateCanvasSize();resizeCanvas(e.w,e.h),layers.forEach(e=>{e.graphics.remove(),e.graphics=createGraphics(width,height),e.component.resize(width,height)}),baseLayerGraphics&&(baseLayerGraphics.remove(),baseLayerGraphics=createGraphics(width,height)),renderEngine.outputBuffer&&(renderEngine.outputBuffer.remove(),renderEngine.outputBuffer=createGraphics(width,height))} </script> </body> </html>
!0}catch(e){console.error("Audio load error:",e)}}play(){if(!this._audioBuffer||!this.isLoaded)return;const e=this._getContext();if(this._sourceNode)try{this._sourceNode.stop(),this._sourceNode.disconnect()}catch(e){}this._sourceNode=e.createBufferSource(),this._sourceNode.buffer=this._audioBuffer,this._sourceNode.connect(this._gainNode),this._sourceNode.connect(this._analyser),this._sourceNode.loop=!0;const t=Math.max(0,Math.min(this._startOffset,this._audioBuffer.duration-.01));this._sourceNode.start(0,t),this._lastStartTime=e.currentTime,this._lastStartOffset=t,this.isPlaying=!0}pause(){if(this._sourceNode&&this.isPlaying){const e=this._getContext().currentTime-this._lastStartTime;this._startOffset=(this._lastStartOffset+e)%this._audioBuffer.duration;try{this._sourceNode.stop(),this._sourceNode.disconnect()}catch(e){}this._sourceNode=null,this.isPlaying=!1}}toggle(){this._directionalEnabled&&this.isPlaying?this.stopDirectional():this._directionalEnabled?this.startDirectional(this._isForward):this.isPlaying?this.pause():this.play()}async initDirectionalPlayback(){if(this._forwardBuffer)try{const e=this._getContext();this._reverseBuffer=e.createBuffer(this._forwardBuffer.numberOfChannels,this._forwardBuffer.length,this._forwardBuffer.sampleRate);for(let e=0;e<this._forwardBuffer.numberOfChannels;e++){const t=this._forwardBuffer.getChannelData(e),i=this._reverseBuffer.getChannelData(e);for(let e=0;e<t.length;e++)i[e]=t[t.length-1-e]}this._directionalEnabled=!0,console.log("[AudioEngine] Directional playback initialized")}catch(e){console.error("[AudioEngine] Failed to init directional:",e),this._directionalEnabled=!1}}getDirectionalPosition(){if(!this._audioContext||!this._lastStartTime)return 0;const e=this._audioContext.currentTime-this._lastStartTime;return this._isForward?this._lastStartOffset+e:this._lastStartOffset-e}playDirectional(e,t){if(!this._directionalEnabled||!this._audioContext||!this._forwardBuffer)return;if(this._sourceNode){this._sourceNode.onended=null;try{this._sourceNode.stop(),this._sourceNode.disconnect()}catch(e){}}this._sourceNode=this._audioContext.createBufferSource(),this._sourceNode.buffer=t?this._forwardBuffer:this._reverseBuffer,this._sourceNode.connect(this._gainNode),this._sourceNode.connect(this._analyser);const i=t?e:this._forwardBuffer.duration-e,a=Math.max(0,Math.min(i,this._sourceNode.buffer.duration-.01));this._sourceNode.start(0,a),this._lastStartTime=this._audioContext.currentTime,this._lastStartOffset=e,this._isForward=t,this.isPlaying=!0,this._sourceNode.onended=()=>{this._isForward?this.playDirectional(0,!0):this.playDirectional(this._forwardBuffer.duration,!1)}}setDirection(e){if(!this._directionalEnabled)return;const t=e>=0;if(t===this._isForward)return;const i=this.getDirectionalPosition();this.playDirectional(i,t)}isDirectionalEnabled(){return this._directionalEnabled}getDirection(){return this._isForward?1:-1}stopDirectional(){if(this.isPlaying&&(this._savedDirectionalPosition=this.getDirectionalPosition(),this._forwardBuffer&&(this._savedDirectionalPosition=Math.max(0,Math.min(this._savedDirectionalPosition,this._forwardBuffer.duration)))),this._sourceNode){this._sourceNode.onended=null;try{this._sourceNode.stop(),this._sourceNode.disconnect()}catch(e){}this._sourceNode=null}this.isPlaying=!1}startDirectional(e=!0){const t=this._savedDirectionalPosition>0?this._savedDirectionalPosition:e?0:this._forwardBuffer?this._forwardBuffer.duration:0;this._directionalEnabled?this.playDirectional(t,e):this.initDirectionalPlayback().then(()=>{this._directionalEnabled&&this.playDirectional(t,e)})}analyze(){if(!this._analyser)return;this._analyser.getByteFrequencyData(this._spectrum),this._analyser.getByteTimeDomainData(this._waveform);let e=0;for(let t=0;t<this._waveform.length;t++){const i=(this._waveform[t]-128)/128;e+=i*i}this._amplitude=Math.sqrt(e/this._waveform.length),this._calculateEnergyBands();const t=performance.now();this._energy.bass>this.beatThreshold&&t-this.lastBeatTime>this.beatCooldown?(this.beatDetected=!0,this.lastBeatTime=t):this.beatDetected=!1}_calculateEnergyBands(){const e=this._getContext().sampleRate/2,t=this._analyser.frequencyBinCount;Object.keys(this.energyBands).forEach(i=>{const[a,o]=this.energyBands[i],s=Math.floor(a/e*t),n=Math.min(Math.ceil(o/e*t),t-1);let r=0,l=0;for(let e=s;e<=n;e++)r+=this._spectrum[e],l++;this._energy[i]=l>0?r/l/255:0})}getSpectrum(){return this._spectrum||new Uint8Array(1024)}getWaveform(){return this._waveform||new Uint8Array(1024)}getEnergy(e){return this._energy[e]||0}getAllEnergy(){return{...this._energy}}getAmplitude(){return this._amplitude}isBeat(){return this.beatDetected}hasAudio(){return this.isLoaded}getCentroid(){if(!this._spectrum)return 0;const e=this._getContext().sampleRate/2;let t=0,i=0;for(let a=0;a<this._spectrum.length;a++)t+=a/this._spectrum.length*e*this._spectrum[a],i+=this._spectrum[a];return i>0?t/i:0}}const audioEngine=new AudioEngine;let layers=[],baseImage=null,baseLayerGraphics=null,aspectRatio=STATE.canvasSize.width/STATE.canvasSize.height;const originalWidth=STATE.canvasSize.width,originalHeight=STATE.canvasSize.height,renderEngine={outputBuffer:null};function preload(){STATE.baseLayer&&STATE.baseLayer.sourceUrl&&"image"===STATE.baseLayer.type&&(baseImage=loadImage(STATE.baseLayer.sourceUrl))}function calculateCanvasSize(){let e,t;return windowWidth/windowHeight>aspectRatio?(t=windowHeight,e=t*aspectRatio):(e=windowWidth,t=e/aspectRatio),{w:Math.floor(e),h:Math.floor(t)}}function setup(){console.log("[Composition] Setup starting..."),pixelDensity(1);const e=calculateCanvasSize();createCanvas(e.w,e.h),console.log("[Composition] Canvas created:",width,"x",height,"(pixelDensity: 1)"),audioEngine.init();let t=null;STATE.audio&&(STATE.audio.sourceUrl?t=STATE.audio.sourceUrl:STATE.audio.tracks&&STATE.audio.tracks.length>0&&STATE.audio.tracks[0].url&&(t=STATE.audio.tracks[0].url)),t?(console.log("[Composition] Loading audio from:",t),updateLoadStatus("audio","pending","loading audio"),audioEngine.loadFromUrl(t).then(()=>{console.log("[Composition] Audio loaded successfully"),loadingState.audio=!0,updateLoadStatus("audio","ok"),STATE.audio&&STATE.audio.directionalEnabled&&audioEngine.initDirectionalPlayback().then(()=>console.log("[Composition] Directional playback ready")).catch(e=>console.warn("[Composition] Directional init deferred:",e))}).catch(e=>{console.error("[Composition] Failed to load audio:",e),loadingState.audio=!0,updateLoadStatus("audio","error","audio failed")})):(loadingState.audio=!0,updateLoadStatus("audio","ok")),console.log("[Composition] Creating",STATE.layers.length,"layers..."),updateLoadStatus("images","pending","loading assets");const i=[];STATE.layers.forEach((layerData,e)=>{const ComponentClass=window[layerData.componentType];if(ComponentClass){console.log("[Composition] Creating layer",e,":",layerData.componentType);const component=new ComponentClass(width,height,layerData.componentParams);if("ImageLayer"===layerData.componentType&&layerData.componentParams.imageUrl){loadingState.imageCount++,console.log("[Composition] Loading image for layer",e,":",layerData.componentParams.imageUrl);const t=component.loadImage(layerData.componentParams.imageUrl).then(()=>{loadingState.imagesLoaded++,console.log("[Composition] Image loaded successfully for layer",e),updateLoadStatus("images","pending",loadingState.imagesLoaded+"/"+loadingState.imageCount)}).catch(t=>{loadingState.imagesLoaded++,console.error("[Composition] Failed to load image for layer",e,":",t)});i.push(t)}if("VideoLayer"===layerData.componentType&&layerData.componentParams.videoUrl){loadingState.imageCount++,console.log("[Composition] Loading video for layer",e,":",layerData.componentParams.videoUrl);const t=component.loadVideo(layerData.componentParams.videoUrl).then(()=>{loadingState.imagesLoaded++,console.log("[Composition] Video loaded successfully for layer",e),updateLoadStatus("images","pending",loadingState.imagesLoaded+"/"+loadingState.imageCount)}).catch(t=>{loadingState.imagesLoaded++,console.error("[Composition] Failed to load video for layer",e,":",t)});i.push(t)}const t=createGraphics(width,height);layers.push({component:component,...layerData,graphics:t})}else console.error("[Composition] Unknown component type:",layerData.componentType)}),STATE.baseLayer&&STATE.baseLayer.filters&&(baseLayerGraphics=createGraphics(width,height)),renderEngine.outputBuffer=createGraphics(width,height),0===i.length?(loadingState.images=!0,updateLoadStatus("images","ok")):Promise.all(i).then(()=>{loadingState.images=!0,updateLoadStatus("images","ok")}),loadingState.ready=!0,updateLoadStatus("ready","ok"),console.log("[Composition] Setup complete. Total layers:",layers.length)}function draw(){1===frameCount&&(console.log("[Composition] First draw() call. Layers:",layers.length),layers.forEach((e,t)=>console.log("[Composition] Layer",t,":",e.componentType,"visible:",e.visible))),audioEngine.analyze();const e=audioEngine.getSpectrum(),t=audioEngine.getWaveform(),i=audioEngine.getAllEnergy(),a=audioEngine.getAmplitude(),waveform=new Float32Array(t.length);for(let e=0;e<t.length;e++)waveform[e]=(t[e]-128)/128;const o=audioEngine.isBeat(),s={fftSpectrum:e,fftEnergy:i,amplitude:a,waveform:waveform,beatDetected:o,beat:o,centroid:audioEngine.getCentroid(),bass:i.bass||0,mid:i.mid||0,high:i.treble||0,mouseX:mouseX,mouseY:mouseY,pmouseX:pmouseX,pmouseY:pmouseY,mousePressed:mouseIsPressed,touches:[],mouseXNorm:mouseX/width,mouseYNorm:mouseY/height,time:millis()/1e3,deltaTime:deltaTime,frameCount:frameCount,fps:frameRate(),width:width,height:height,centerX:width/2,centerY:height/2,aspectRatio:width/height,minDim:min(width,height),maxDim:max(width,height)};if(background(STATE.backgroundColor||"#000000"),baseImage&&STATE.baseLayer&&STATE.baseLayer.visible){const e=baseImage.width/baseImage.height;let t,i,a,o;if(e>width/height?(i=height,t=height*e):(t=width,i=width/e),a=(width-t)/2,o=(height-i)/2,STATE.baseLayer.filters&&(STATE.
&&(this._smoothedWaveform&&this._smoothedWaveform.length===waveform.length?(i=waveform.map((e,i)=>this._smoothedWaveform[i]+(e-this._smoothedWaveform[i])*(1-t.smoothing)),this._smoothedWaveform=i):this._smoothedWaveform=[...waveform]);const a=[];if(t._paths&&t._paths.length>0)for(const e of t._paths)e&&e.length>=2&&a.push(e);this._tempPath.length>=2&&a.push(this._tempPath),0===a.length&&a.push([{x:0,y:.5},{x:1,y:.5}]);const o=e.color(t.color);o.setAlpha(t.opacity/100*255);const s=Math.min(this.width,this.height),n=Math.max(1,.003*s*(t.strokeWeight/2)),r=t.amplitude/100*(s/2);e.push(),e.noFill(),e.stroke(o),e.strokeWeight(n);for(const o of a){const a=o.map(e=>({x:e.x*this.width,y:e.y*this.height})),s=this._segmentLengths(a);if(this._drawWaveformAlongPath(e,i,a,s,r,!1),t.mirror&&a.length>=2){const t=a[0],o=a.map(e=>({x:2*t.x-e.x,y:2*t.y-e.y})),s=this._segmentLengths(o);this._drawWaveformAlongPath(e,i,o,s,r,!0)}}if(e.pop(),t.showPath){const i=e.color(t.pathColor);i.setAlpha(t.pathOpacity/100*255),e.push(),e.noFill(),e.stroke(i),e.strokeWeight(1);for(const t of a){const a=t.map(e=>({x:e.x*this.width,y:e.y*this.height}));if(!(a.length<2)){e.beginShape();for(const t of a)e.vertex(t.x,t.y);e.endShape(),e.noStroke(),e.fill(i),e.circle(a[0].x,a[0].y,6),e.stroke(i)}}e.pop()}}_segmentLengths(e){const t=[];let i=0;for(let a=1;a<e.length;a++){const o=e[a].x-e[a-1].x,s=e[a].y-e[a-1].y,n=Math.sqrt(o*o+s*s);t.push(n),i+=n}return{segLengths:t,totalLen:i}}_drawWaveformAlongPath(e,waveform,t,i,a,o){if(t.length<2||i.totalLen<=0)return;const{segLengths:s,totalLen:n}=i,r=waveform.length,l=o?-1:1;e.beginShape();for(let i=0;i<r;i++){const o=(r>1?i/(r-1):.5)*n;let h=0,c=0;for(;c<s.length-1&&h+s[c]<o;)h+=s[c],c++;const d=t[c],p=t[c+1],m=s[c]||1,u=Math.max(0,Math.min(1,(o-h)/m)),g=d.x+(p.x-d.x)*u,f=d.y+(p.y-d.y)*u,y=p.x-d.x,_=p.y-d.y,v=Math.sqrt(y*y+_*_)||1,w=-_/v,x=y/v,P=waveform[i]*l;e.vertex(g+w*P*a,f+x*P*a)}e.endShape()}}class SolidColor extends BaseComponent{static category="media";static isAudioReactive=!1;static displayName="Solid Color";static description="Solid color fill layer";getDefaultParams(){return{color:"#000000",opacity:1}}getParameters(){return[{name:"color",type:"color",label:"Color"},{name:"opacity",type:"range",min:0,max:1,step:.05,label:"Opacity"}]}update(e,t){}draw(e){const t=this.params;e.push();const i=t.color,a=parseInt(i.slice(1,3),16),o=parseInt(i.slice(3,5),16),b=parseInt(i.slice(5,7),16);e.noStroke(),e.fill(a,o,b,255*t.opacity),e.rect(0,0,this.width,this.height),e.pop()}}class MelodyTrace extends BaseComponent{static category="fft";static isAudioReactive=!0;static displayName="Melody Trace";static description="Symmetric melody trace based on dominant frequency";getDefaultParams(){return{verticalScale:1,strokeWeight:1.5,colorHue:200,colorSaturation:80,colorBrightness:90,trailLength:400,minFreq:50,maxFreq:2e3,topMirror:!0,drawConnections:!0,connectionOpacity:30,connectionSpacing:20}}getParameters(){return[{name:"verticalScale",type:"range",min:.5,max:3,step:.1,label:"Vertical Scale"},{name:"strokeWeight",type:"range",min:.5,max:4,step:.5,label:"Stroke Weight"},{name:"colorHue",type:"range",min:0,max:360,step:1,label:"Color Hue"},{name:"colorSaturation",type:"range",min:0,max:100,step:1,label:"Saturation"},{name:"colorBrightness",type:"range",min:0,max:100,step:1,label:"Brightness"},{name:"trailLength",type:"range",min:100,max:800,step:50,label:"Trail Length"},{name:"minFreq",type:"range",min:20,max:200,step:10,label:"Min Frequency"},{name:"maxFreq",type:"range",min:500,max:5e3,step:100,label:"Max Frequency"},{name:"topMirror",type:"boolean",label:"Top Mirror"},{name:"drawConnections",type:"boolean",label:"Draw Connections"},{name:"connectionOpacity",type:"range",min:10,max:100,step:5,label:"Connection Opacity"},{name:"connectionSpacing",type:"range",min:5,max:50,step:5,label:"Connection Spacing"}]}constructor(e,t,i){super(e,t,i),this._melodyPoints=[],this._particles=[]}setParticleSystem(e){this._particles=e}update(e,t){this._metrics=e;const i=this.params,a=e?.fftSpectrum;if(!a||0===a.length)return;const o=22050*a.indexOf(Math.max(...a))/a.length;let s;if(o>i.minFreq&&o<i.maxFreq){const e=Math.log(i.minFreq),t=Math.log(i.maxFreq),a=(Math.log(o)-e)/(t-e);s=this.height*(.85-.4*a*i.verticalScale)}else s=this._melodyPoints.length>0?this._melodyPoints[this._melodyPoints.length-1].y:this.height*(.75-.25*i.verticalScale);const n=70+80*Math.random();this._melodyPoints.push({y:s,reach:n});const r=i.trailLength;this._melodyPoints.length>r&&this._melodyPoints.shift()}draw(e){const t=this.params;if(this._melodyPoints.length<2)return;e.push(),e.colorMode(e.HSB,360,100,100,100),e.noFill(),e.stroke(t.colorHue,t.colorSaturation,t.colorBrightness,90),e.strokeWeight(t.strokeWeight);const i=this.width/2;this.height,this._drawTrace(e,i,-1,!1),this._drawTrace(e,i,1,!1),t.topMirror&&(this._drawTrace(e,i,-1,!0),this._drawTrace(e,i,1,!0)),e.colorMode(e.RGB,255),e.pop()}_drawTrace(e,t,i,a){const o=this.params,s=void 0!==window._playbackDirection?window._playbackDirection:1,n=this._melodyPoints.length;if(n<2)return;const r=this.width/2*.98;e.beginShape();for(let l=0;l<n;l++){const h=t+i*(l/(n-1))*r,c=1===s?n-1-l:l,d=this._melodyPoints[c];let p=d.y;a&&(p=this.height-p),e.vertex(h,p),o.drawConnections&&l%o.connectionSpacing<1&&this._drawConnections(e,h,p,d.reach)}e.endShape()}_drawConnections(e,t,i,a){const o=this.params;if(this._particles&&this._particles.length>0){e.stroke(o.colorHue,o.colorSaturation,o.colorBrightness,o.connectionOpacity),e.strokeWeight(.5);for(const o of this._particles)Math.sqrt((t-o.x)**2+(i-o.y)**2)<a&&Math.random()<.05&&e.line(t,i,o.x,o.y);e.stroke(o.colorHue,o.colorSaturation,o.colorBrightness,90),e.strokeWeight(o.strokeWeight)}}getMelodyPoints(){return this._melodyPoints}}window.DirectionalPlayback=DirectionalPlayback,window.VideoLoop=VideoLoop,window.SpinningStar=SpinningStar,window.NoiseLayer=NoiseLayer,window.DrawWaveform=DrawWaveform,window.SolidColor=SolidColor,window.MelodyTrace=MelodyTrace;class BaseFilter{constructor(){this.enabled=!1,this.params={...this.getDefaultParams()},this._time=0}getDefaultParams(){return{}}getParameters(){return[]}setParameter(e,t){this.params.hasOwnProperty(e)&&(this.params[e]=t)}setParameters(e){Object.entries(e).forEach(([e,t])=>this.setParameter(e,t))}setEnabled(e){this.enabled=e}update(e){this._time+=e}processPixels(e,t,i){return e}processGraphics(e,t,i){e.loadPixels();const a=this.processPixels(e.pixels,t,i);if(a!==e.pixels)for(let t=0;t<a.length;t++)e.pixels[t]=a[t];e.updatePixels()}needsGraphicsAccess(){return!1}dispose(){}}const FilterRegistry={_filters:new Map,register(e,t){this._filters.set(e,t)},get(e){return this._filters.get(e)||null},create(e){const t=this.get(e);return t?new t:null}};class VhsFilter extends BaseFilter{static filterType="main";static displayName="VHS";static description="Retro video tape artifacts";getDefaultParams(){return{scanlines:.3,colorBleed:5,noise:.1,tracking:.5,jitter:2}}getParameters(){return[{name:"scanlines",type:"range",min:0,max:1,step:.01,label:"Scanlines"},{name:"colorBleed",type:"range",min:0,max:20,step:1,label:"Color Bleed"},{name:"noise",type:"range",min:0,max:.5,step:.01,label:"Noise"},{name:"tracking",type:"range",min:0,max:1,step:.01,label:"Tracking Error"},{name:"jitter",type:"range",min:0,max:10,step:1,label:"Horizontal Jitter"}]}processPixels(e,t,i){const a=this.params.scanlines,o=Math.floor(this.params.colorBleed),s=this.params.noise,n=this.params.tracking,r=this.params.jitter,output=new Uint8ClampedArray(e.length),l=.1*this._time,h=e=>{const t=43758.5453*Math.sin(e+l);return t-Math.floor(t)},c=[];if(n>0){const e=Math.floor(5*n)+1;for(let t=0;t<e;t++){const e=Math.floor(h(100*t)*i),a=Math.floor(20*h(100*t+50))+5;c.push({start:e,height:a})}}for(let l=0;l<i;l++){let i=0;for(const e of c)if(l>=e.start&&l<e.start+e.height){i=Math.floor(50*(h(l)-.5)*n);break}const d=r>0?Math.floor((h(l+1e3)-.5)*r*2):0;for(let n=0;n<t;n++){const r=Math.min(Math.max(n+i+d,0),t-1),c=4*(l*t+n),p=4*(l*t+r);let m,u,b;if(o>0){const i=Math.min(Math.max(r-o,0),t-1),a=Math.min(Math.max(r+o,0),t-1);m=e[4*(l*t+i)],u=e[p+1],b=e[4*(l*t+a)+2]}else m=e[p],u=e[p+1],b=e[p+2];if(s>0){const e=255*(h(n*t+l)-.5)*s;m=Math.min(255,Math.max(0,m+e)),u=Math.min(255,Math.max(0,u+e)),b=Math.min(255,Math.max(0,b+e))}if(a>0&&l%2==0){const e=1-.5*a;m*=e,u*=e,b*=e}output[c]=m,output[c+1]=u,output[c+2]=b,output[c+3]=e[p+3]}}return output}}FilterRegistry.register("vhs",VhsFilter);class AudioEngine{constructor(){this._audioContext=null,this._analyser=null,this._gainNode=null,this._sourceNode=null,this._audioBuffer=null,this.isLoaded=!1,this.isPlaying=!1,this._spectrum=null,this._waveform=null,this._energy={bass:0,lowMid:0,mid:0,highMid:0,treble:0},this._amplitude=0,this.beatThreshold=.15,this.beatCooldown=100,this.lastBeatTime=0,this.beatDetected=!1,this._startOffset=0,this._lastStartTime=0,this._lastStartOffset=0,this.energyBands={bass:[20,140],lowMid:[140,400],mid:[400,2600],highMid:[2600,5200],treble:[5200,14e3]},this._forwardBuffer=null,this._reverseBuffer=null,this._directionalEnabled=!1,this._isForward=!0,this._savedDirectionalPosition=0}_getContext(){return this._audioContext||(this._audioContext=new(window.AudioContext||window.webkitAudioContext)),this._audioContext}init(){const e=this._getContext();this._analyser=e.createAnalyser(),this._analyser.fftSize=2048,this._analyser.smoothingTimeConstant=.8,this._gainNode=e.createGain(),this._gainNode.connect(e.destination),this._analyser.connect(e.destination),this._spectrum=new Uint8Array(this._analyser.frequencyBinCount),this._waveform=new Uint8Array(this._analyser.frequencyBinCount)}async resume(){const e=this._getContext();"suspended"===e.state&&await e.resume()}async loadFromUrl(e){try{const t=this._getContext(),i=await fetch(e);if(!i.ok)throw new Error("Failed to fetch audio: "+i.status);const a=await i.arrayBuffer();this._audioBuffer=await t.decodeAudioData(a),this._forwardBuffer=this._audioBuffer,this.isLoaded=
1-50 of 206