0x0000…5b01

All memos sent from and to 0x0000…5b01.

<!doctype html><html><head><meta name="title" content="PUSH4"><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><style>*{margin:0;padding:0;box-sizing:border-box}body,html{width:100%;height:100%;overflow:hidden;font-family:monospace}#container{width:100%;height:100%;display:flex;flex-direction:column;align-items:center}#artwork{width:100%;height:calc(100% - 30px);display:flex;align-items:flex-start;justify-content:center}#artwork iframe,#artwork img{height:100%;width:auto;max-width:100%;image-rendering:pixelated}#info{height:30px;display:flex;flex-direction:column;justify-content:flex-end;gap:5px;visibility:hidden;opacity:0;margin:0 auto}#info.loaded{visibility:visible;opacity:1}#info-text{display:flex;justify-content:space-between;padding:0 0 6px;font-size:11px;color:#666;letter-spacing:.3px}#progress-bar{height:2px;background:#555}#progress{height:100%;background:#222;width:100%}</style></head><body><div id="container"><div id="artwork"><div id="loading">Loading...</div></div><div id="info"><div id="info-text"><span><span id="title">--</span> by <span id="creator">--</span></span> <span id="proxy">--</span></div><div id="progress-bar"><div id="progress"></div></div></div></div><script>const RPC_URLS=[{{FILE_URIS}}],BLOCK_INTERVAL={{BLOCK_INTERVAL}},CONTRACT_ADDRESS="{{CORE_ADDRESS}}",GAS_LIMIT="0x20C85580";let currentRpcIndex=0,lastBlock=0,lastProxyIdx=-1,proxyAddress=null,proxyCount=0,rendererAddress=null;function showError(t){document.getElementById("artwork").innerHTML='<div style="color: #ff5555; padding: 20px; text-align: center;">'+t+"</div>"}async function fetchWithFallback(t,e){const r=[];for(let n=0;n<RPC_URLS.length;n++){const o=(currentRpcIndex+n)%RPC_URLS.length,a=RPC_URLS[o];try{const n=await fetch(a,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:t,params:e})});if(!n.ok){const t=`HTTP ${n.status}: ${n.statusText}`;r.push({rpc:a,error:t});continue}const c=await n.text();let l;try{l=JSON.parse(c)}catch(t){const e=`JSON parse error: ${t.message} (response length: ${c.length})`;r.push({rpc:a,error:e});continue}if(l.error){const t=`RPC error: ${JSON.stringify(l.error)}`;r.push({rpc:a,error:t});continue}return currentRpcIndex=o,l.result}catch(t){const e=t.message||String(t);r.push({rpc:a,error:e});continue}}throw console.error("[RPC] All RPCs failed:",r),showError("Connection Error: All RPC nodes are unresponsive."),new Error("All RPCs failed: "+JSON.stringify(r))}async function getBlockNumber(){const t=await fetchWithFallback("eth_blockNumber",[]);return parseInt(t,16)}async function discoverProxy(){try{const t="0x"+(await fetchWithFallback("eth_call",[{to:CONTRACT_ADDRESS,data:"0xec556889"},"latest"])).slice(26,66);return"0x0000000000000000000000000000000000000000"===t?null:t}catch(t){return console.error("[Proxy] Failed to discover proxy:",t),null}}async function getProxyCount(t){if(!t)return 0;try{const e=await fetchWithFallback("eth_call",[{to:t,data:"0x37c954d8"},"latest"]);return parseInt(e,16)}catch(t){return console.error("[ProxyCount] Failed to get proxy count:",t),0}}async function getProxyAt(t,e){if(!t)return null;const r="0xcc935efe"+e.toString(16).padStart(64,"0");try{const e=await fetchWithFallback("eth_call",[{to:t,data:r},"latest"]);return"0x"+e.slice(26,66)}catch(t){return console.error("[ProxyAt] Failed to get proxy at index:",t),null}}async function getCurrentProxy(t){if(!t)return null;try{const e=await fetchWithFallback("eth_call",[{to:t,data:"0x3f2d9d88"},"latest"]);return"0x"+e.slice(26,66)}catch(t){return console.error("[CurrentProxy] Failed to get current proxy:",t),null}}async function getCurrentProxyIndex(t){if(!t)return 0;try{const e=await fetchWithFallback("eth_call",[{to:t,data:"0x81ef9a43"},"latest"]);return parseInt(e,16)}catch(t){return console.error("[CurrentProxyIndex] Failed to get current proxy index:",t),0}}function decodeString(t){const e=t.slice(2),r=2*parseInt(e.slice(0,64),16),n=2*parseInt(e.slice(r,r+64),16);return e.slice(r+64,r+64+n).match(/.{2}/g).map(t=>String.fromCharCode(parseInt(t,16))).join("")}async function getProxyTitle(t){if(!t)return"--";try{const e=await fetchWithFallback("eth_call",[{to:t,data:"0x4a79d50c"},"latest"]);return decodeString(e)}catch(t){return console.error("[ProxyTitle] Failed to get title:",t),"--"}}async function getProxyCreator(t){if(!t)return"--";try{const e=(await fetchWithFallback("eth_call",[{to:t,data:"0x02d05d3f"},"latest"])).slice(2),r=2*parseInt(e.slice(0,64),16),n=r+2*parseInt(e.slice(r,r+64),16),o=2*parseInt(e.slice(n,n+64),16),a=e.slice(n+64,n+64+o);return a.match(/.{2}/g).map(t=>String.fromCharCode(parseInt(t,16))).join("")}catch(t){return console.error("[ProxyCreator] Failed to get creator:",t),"--"}}async function discoverRenderer(){try{return"0x"+(await fetchWithFallback("eth_call",[{to:CONTRACT_ADDRESS,data:"0x8ada6b0f"},"latest"])).slice(26,66)}catch(t){return console.error("[Renderer] Failed to discover renderer:",t),null}}async function getSvgDataUri(){return decodeString(await fetchWithFallback("eth_call",[{to:rendererAddress,data:"0xbfec768f",gas:GAS_LIMIT},"latest"]))}function syncInfoWidth(){const t=document.querySelector("#artwork img"),e=document.getElementById("info");t&&(e.style.width=t.clientWidth+"px")}function renderArtwork(t){const e=document.getElementById("artwork");e.innerHTML='<img src="'+t+'" alt="PUSH4">',e.querySelector("img").onload=syncInfoWidth}function updateProgress(t){const e=document.getElementById("progress"),r=t/BLOCK_INTERVAL*100,n=Math.min((t+1)/BLOCK_INTERVAL*100,100);e.style.transition="none",e.style.width=r+"%",requestAnimationFrame(function(){requestAnimationFrame(function(){e.style.transition="width 12s linear",e.style.width=n+"%"})})}async function pollBlock(){try{const t=await getBlockNumber();if(t!==lastBlock){const e=await getCurrentProxyIndex(proxyAddress);if(e!==lastProxyIdx){const r=await getCurrentProxy(proxyAddress),[n,o,a]=await Promise.all([getProxyTitle(r),getProxyCreator(r),getSvgDataUri()]);if(!n||"--"===n||!o||"--"===o||!a)return void console.warn("[Poll] Incomplete data, skipping update");lastBlock=t,lastProxyIdx=e,updateProgress(t%BLOCK_INTERVAL),document.getElementById("title").textContent=n,document.getElementById("creator").textContent=o,document.getElementById("proxy").textContent=e+1+"/"+proxyCount,renderArtwork(a)}else lastBlock=t,updateProgress(t%BLOCK_INTERVAL)}}catch(t){console.error("[Poll] Poll failed, keeping previous state:",t)}}async function init(){[proxyAddress,rendererAddress]=await Promise.all([discoverProxy(),discoverRenderer()]),proxyCount=await getProxyCount(proxyAddress);const[t,e]=await Promise.all([getCurrentProxy(proxyAddress),getCurrentProxyIndex(proxyAddress)]);lastProxyIdx=e;const[r,n,o]=await Promise.all([getProxyTitle(t),getProxyCreator(t),getSvgDataUri()]),a=await getBlockNumber();lastBlock=a,document.getElementById("title").textContent=r,document.getElementById("creator").textContent=n,document.getElementById("proxy").textContent=e+1+"/"+proxyCount,renderArtwork(o),updateProgress(a%BLOCK_INTERVAL),document.getElementById("info").classList.add("loaded"),window.addEventListener("resize",syncInfoWidth),setInterval(pollBlock,3e3)}init().catch(t=>console.error("[Init] Fatal error:",t))</script></body></html>
<!doctype html> <html><head> <meta name="title" content="PUSH4"/><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/> <style>*{margin:0;padding:0;box-sizing:border-box}html,body{width:100%;height:100%;overflow:hidden;font-family:monospace}#container{width:100%;height:100%;display:flex;flex-direction:column;align-items:center}#artwork{width:100%;height:calc(100% - 30px);display:flex;align-items:flex-start;justify-content:center}#artwork img,#artwork iframe{height:100%;width:auto;max-width:100%;image-rendering:pixelated}#info{height:30px;display:flex;flex-direction:column;justify-content:flex-end;gap:5px;visibility:hidden;opacity:0;margin:0 auto}#info.loaded{visibility:visible;opacity:1}#info-text{display:flex;justify-content:space-between;padding:0 0 6px;font-size:11px;color:#666;letter-spacing:.3px}#progress-bar{height:2px;background:#222}#progress{height:100%;background:#555;width:0%}</style> </head><body> <div id="container"><div id="artwork"><div id="loading">Loading...</div></div><div id="info"><div id="info-text"><span><span id="title">--</span> by <span id="creator">--</span></span><span id="proxy">--</span></div><div id="progress-bar"><div id="progress"></div></div></div></div> <script> const R=[{{FILE_URIS}}],B={{BLOCK_INTERVAL}},C='{{CORE_ADDRESS}}',G='0x20C85580'; let rIdx=0,lB=0,lP=-1,pA=null,pC=0,rA=null; function sE(m){document.getElementById('artwork').innerHTML='<div style="color:#ff5555;padding:20px;text-align:center">'+m+'</div>'} async function f(m,p){const e=[];for(let i=0;i<R.length;i++){const x=(rIdx+i)%R.length,u=R[x];try{const r=await fetch(u,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({jsonrpc:'2.0',id:1,method:m,params:p})});if(!r.ok){e.push({r:u,e:`HTTP ${r.status}`});continue}const t=await r.text();let j;try{j=JSON.parse(t)}catch(pe){e.push({r:u,e:'Parse error'});continue}if(j.error){e.push({r:u,e:'RPC error'});continue}rIdx=x;return j.result}catch(ex){e.push({r:u,e:ex.message});continue}}sE('Connection Error');throw new Error('RPCs failed')} async function gB(){return parseInt(await f('eth_blockNumber',[]),16)} async function dP(){try{const r=await f('eth_call',[{to:C,data:'0xec556889'},'latest']);const a='0x'+r.slice(26,66);return a==='0x0000000000000000000000000000000000000000'?null:a}catch(e){return null}} async function gPC(o){if(!o)return 0;try{return parseInt(await f('eth_call',[{to:o,data:'0x37c954d8'},'latest']),16)}catch(e){return 0}} async function gCP(o){if(!o)return null;try{return'0x'+(await f('eth_call',[{to:o,data:'0x3f2d9d88'},'latest'])).slice(26,66)}catch(e){return null}} async function gCI(o){if(!o)return 0;try{return parseInt(await f('eth_call',[{to:o,data:'0x81ef9a43'},'latest']),16)}catch(e){return 0}} function dS(h){const d=h.slice(2),o=parseInt(d.slice(0,64),16)*2,l=parseInt(d.slice(o,o+64),16)*2;return d.slice(o+64,o+64+l).match(/.{2}/g).map(x=>String.fromCharCode(parseInt(x,16))).join('')} async function gT(p){if(!p)return'--';try{return dS(await f('eth_call',[{to:p,data:'0x4a79d50c'},'latest']))}catch(e){return'--'}} async function gCr(p){if(!p)return'--';try{const r=await f('eth_call',[{to:p,data:'0x02d05d3f'},'latest']),d=r.slice(2),so=parseInt(d.slice(0,64),16)*2,no=parseInt(d.slice(so,so+64),16)*2,ns=so+no,nl=parseInt(d.slice(ns,ns+64),16)*2;return d.slice(ns+64,ns+64+nl).match(/.{2}/g).map(x=>String.fromCharCode(parseInt(x,16))).join('')}catch(e){return'--'}} async function dR(){try{return'0x'+(await f('eth_call',[{to:C,data:'0x8ada6b0f'},'latest'])).slice(26,66)}catch(e){return null}} async function gS(){return dS(await f('eth_call',[{to:rA,data:'0xbfec768f',gas:G},'latest']))} function sW(){const i=document.querySelector('#artwork img'),n=document.getElementById('info');if(i)n.style.width=i.clientWidth+'px'} function rAw(s){const a=document.getElementById('artwork');a.innerHTML='<img src="'+s+'" alt="PUSH4">';a.querySelector('img').onload=sW} function uP(e){const b=document.getElementById('progress'),c=(e/B)*100,n=Math.min(((e+1)/B)*100,100);b.style.transition='none';b.style.width=c+'%';requestAnimationFrame(()=>requestAnimationFrame(()=>{b.style.transition='width 12s linear';b.style.width=n+'%'}))} async function poll(){try{const b=await gB();if(b!==lB){const pI=await gCI(pA);if(pI!==lP){const cp=await gCP(pA),[t,cr,sv]=await Promise.all([gT(cp),gCr(cp),gS()]);if(!t||t==='--'||!cr||cr==='--'||!sv)return;lB=b;lP=pI;uP(b%B);document.getElementById('title').textContent=t;document.getElementById('creator').textContent=cr;document.getElementById('proxy').textContent=(pI+1)+'/'+pC;rAw(sv)}else{lB=b;uP(b%B)}}}catch(e){}} async function init(){[pA,rA]=await Promise.all([dP(),dR()]);pC=await gPC(pA);const[cp,pI]=await Promise.all([gCP(pA),gCI(pA)]);lP=pI;const[t,cr,sv]=await Promise.all([gT(cp),gCr(cp),gS()]);const b=await gB();lB=b;document.getElementById('title').textContent=t;document.getElementById('creator').textContent=cr;document.getElementById('proxy').textContent=(pI+1)+'/'+pC;rAw(sv);uP(b%B);document.getElementById('info').classList.add('loaded');window.addEventListener('resize',sW);setInterval(poll,3000)} init().catch(e=>console.error('Init error:',e)); </script></body></html>
4k<!doctype html> <html> <head> <meta name="title" content="PUSH4" /> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <style> * { margin: 0; padding: 0; box-sizing: border-box; } html, body { width: 100%; height: 100%; overflow: hidden; } #artwork { width: 100%; height: calc(100% - 30px); display: flex; align-items: flex-start; justify-content: flex-start; } #artwork img, #artwork iframe { height: 100%; width: auto; max-width: 100%; image-rendering: pixelated; } #info { height: 30px; display: flex; flex-direction: column; justify-content: flex-end; gap: 5px; } #info-text { display: flex; justify-content: space-between; padding: 0 0 6px; font-family: monospace; font-size: 11px; color: #666; letter-spacing: 0.3px; } #progress-bar { height: 2px; background: #222; } #progress { height: 100%; background: #555; width: 0%; } </style> </head> <body> <div id="artwork"><div id="loading">Loading...</div></div> <div id="info"> <div id="info-text"> <span><span id="title">--</span> by <span id="creator">--</span></span> <span id="proxy">--</span> </div> <div id="progress-bar"><div id="progress"></div></div> </div> <script> // Injected values (replaced at runtime by the contract) const RPC_URLS = [{{FILE_URIS}}]; const BLOCK_INTERVAL = {{BLOCK_INTERVAL}}; const CONTRACT_ADDRESS = '{{CORE_ADDRESS}}'; const GAS_LIMIT = '0x20C85580'; let currentRpcIndex = 0; let lastBlock = 0; let lastProxyIdx = -1; let proxyAddress = null; let proxyCount = 0; let rendererAddress = null; function showError(msg) { const artwork = document.getElementById('artwork'); // Simple error styling, centering it in the container artwork.innerHTML = '<div style="color: #ff5555; font-family: monospace; padding: 20px; text-align: center;">' + msg + '</div>'; } async function fetchWithFallback(method, params) { const errors = []; for (let i = 0; i < RPC_URLS.length; i++) { const idx = (currentRpcIndex + i) % RPC_URLS.length; const rpcUrl = RPC_URLS[idx]; try { const res = await fetch(rpcUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: method, params: params }) }); if (!res.ok) { const err = `HTTP ${res.status}: ${res.statusText}`; errors.push({ rpc: rpcUrl, error: err }); continue; } // Use text() first to avoid JSON parse errors on large responses const text = await res.text(); let json; try { json = JSON.parse(text); } catch (parseErr) { const err = `JSON parse error: ${parseErr.message} (response length: ${text.length})`; errors.push({ rpc: rpcUrl, error: err }); continue; } if (json.error) { const err = `RPC error: ${JSON.stringify(json.error)}`; errors.push({ rpc: rpcUrl, error: err }); continue; } currentRpcIndex = idx; return json.result; } catch (e) { const err = e.message || String(e); errors.push({ rpc: rpcUrl, error: err }); continue; } } console.error('[RPC] All RPCs failed:', errors); showError('Connection Error: All RPC nodes are unresponsive.'); throw new Error('All RPCs failed: ' + JSON.stringify(errors)); } async function getBlockNumber() { const hex = await fetchWithFallback('eth_blockNumber', []); const blockNum = parseInt(hex, 16); return blockNum; } // Discover the proxy address from PUSH4Core.proxy() async function discoverProxy() { // proxy() selector: 0xec556889 const data = '0xec556889'; try { const res = await fetchWithFallback('eth_call', [{ to: CONTRACT_ADDRESS, data: data }, 'latest']); // Result is an address (32 bytes, right-padded) const addr = '0x' + res.slice(26, 66); if (addr === '0x0000000000000000000000000000000000000000') { return null; } return addr; } catch (e) { console.error('[Proxy] Failed to discover proxy:', e); return null; } } // Get proxy count from orchestrator.proxyCount() async function getProxyCount(orchestratorAddr) { if (!orchestratorAddr) { return 0; } // proxyCount() selector: 0x37c954d8 const data = '0x37c954d8'; try { const res = await fetchWithFallback('eth_call', [{ to: orchestratorAddr, data: data }, 'latest']); const count = parseInt(res, 16); return count; } catch (e) { console.error('[ProxyCount] Failed to get proxy count:', e); return 0; } } // Get proxy at index from orchestrator.getProxyAt(uint256) async function getProxyAt(orchestratorAddr, index) { if (!orchestratorAddr) return null; // getProxyAt(uint256) selector: 0xcc935efe const data = '0xcc935efe' + index.toString(16).padStart(64, '0'); try { const res = await fetchWithFallback('eth_call', [{ to: orchestratorAddr, data: data }, 'latest']); const addr = '0x' + res.slice(26, 66); return addr; } catch (e) { console.error('[ProxyAt] Failed to get proxy at index:', e); return null; } } // Decode a string from ABI-encoded response function decodeString(hex) { const data = hex.slice(2); const offset = parseInt(data.slice(0, 64), 16) * 2; const len = parseInt(data.slice(offset, offset + 64), 16) * 2; const strHex = data.slice(offset + 64, offset + 64 + len); return strHex.match(/.{2}/g).map(x => String.fromCharCode(parseInt(x, 16))).join(''); } // Get title from proxy.title() async function getProxyTitle(proxyAddr) { if (!proxyAddr) return '--'; // title() selector: 0x4a79d50c try { const res = await fetchWithFallback('eth_call', [{ to: proxyAddr, data: '0x4a79d50c' }, 'latest']); const title = decodeString(res); return title; } catch (e) { console.error('[ProxyTitle] Failed to get title:', e); return '--'; } } // Get creator from proxy.creator() - returns (string name, address wallet) async function getProxyCreator(proxyAddr) { if (!proxyAddr) return '--'; // creator() selector: 0x02d05d3f try { const res = await fetchWithFallback('eth_call', [{ to: proxyAddr, data: '0x02d05d3f' }, 'latest']); // Creator struct: (string name, address wallet) // First 32 bytes: offset to tuple, then offset to string, then address (right-padded) const data = res.slice(2); // Offset to the struct const structOffset = parseInt(data.slice(0, 64), 16) * 2; // At structOffset: offset to name string (relative to struct start) const nameOffsetRel = parseInt(data.slice(structOffset, structOffset + 64), 16) * 2; // Name string is at structOffset + nameOffsetRel const nameStart = structOffset + nameOffsetRel; const nameLen = parseInt(data.slice(nameStart, nameStart + 64), 16) * 2; const nameHex = data.slice(nameStart + 64, nameStart + 64 + nameLen); const name = nameHex.match(/.{2}/g).map(x => String.fromCharCode(parseInt(x, 16))).join(''); return name; } catch (e) { console.error('[ProxyCreator] Failed to get creator:', e); return '--'; } } // Discover the renderer address from PUSH4Core.renderer() async function discoverRenderer() { // renderer() selector: 0x8ada6b0f try { const res = await fetchWithFallback('eth_call', [{ to: CONTRACT_ADDRESS, data: '0x8ada6b0f' }, 'latest']); return '0x' + res.slice(26, 66); } catch (e) { console.error('[Renderer] Failed to discover renderer:', e); return null; } } async function getSvgDataUri() { // getSvgDataUri() selector: 0xbfec768f const res = await fetchWithFallback('eth_call', [{ to: rendererAddress, data: '0xbfec768f', gas: GAS_LIMIT }, 'latest']); return decodeString(res); } function syncInfoWidth() { const img = document.querySelector('#artwork img'); const info = document.getElementById('info'); if (img) info.style.width = img.clientWidth + 'px'; } function renderArtwork(svgDataUri) { const artwork = document.getElementById('artwork'); artwork.innerHTML = '<img src="' + svgDataUri + '" alt="PUSH4">'; artwork.querySelector('img').onload = syncInfoWidth; } function updateProgress(elapsed) { const bar = document.getElementById('progress'); const currentPct = (elapsed / BLOCK_INTERVAL) * 100; const nextPct = Math.min(((elapsed + 1) / BLOCK_INTERVAL) * 100, 100); // Jump to current position immediately bar.style.transition = 'none'; bar.style.width = currentPct + '%'; // Double-rAF ensures the jump is painted before starting the transition requestAnimationFrame(function() { requestAnimationFrame(function() { bar.style.transition = 'width 12s linear'; bar.style.width = nextPct + '%'; }); }); } async function pollBlock() { try { const block = await getBlockNumber(); const proxyIdx = proxyCount > 0 ? Math.floor(block / BLOCK_INTERVAL) % proxyCount : 0; document.getElementById('proxy').textContent = (proxyIdx + 1) + '/' + proxyCount; if (block !== lastBlock) { lastBlock = block; updateProgress(block % BLOCK_INTERVAL); if (proxyIdx !== lastProxyIdx) { lastProxyIdx = proxyIdx; // Get the current proxy, then fetch title/creator/artwork in parallel const currentProxy = await getProxyAt(proxyAddress, proxyIdx); const [title, creator, svgDataUri] = await Promise.all([ getProxyTitle(currentProxy), getProxyCreator(currentProxy), getSvgDataUri() ]); document.getElementById('title').textContent = title; document.getElementById('creator').textContent = creator; renderArtwork(svgDataUri); } } } catch (e) { console.error('[Poll] Poll failed:', e); } } async function init() { // Discover proxy, renderer and get count [proxyAddress, rendererAddress] = await Promise.all([ discoverProxy(), discoverRenderer() ]); proxyCount = await getProxyCount(proxyAddress); // Get initial proxy index and fetch title/creator/svg in parallel const block = await getBlockNumber(); const proxyIdx = proxyCount > 0 ? Math.floor(block / BLOCK_INTERVAL) % proxyCount : 0; lastProxyIdx = proxyIdx; const currentProxy = await getProxyAt(proxyAddress, proxyIdx); const [title, creator, svgDataUri] = await Promise.all([ getProxyTitle(currentProxy), getProxyCreator(currentProxy), getSvgDataUri() ]); document.getElementById('title').textContent = title; document.getElementById('creator').textContent = creator; renderArtwork(svgDataUri); lastBlock = block; updateProgress(block % BLOCK_INTERVAL); window.addEventListener('resize', syncInfoWidth); // Start polling setInterval(pollBlock, 3000); } init().catch(e => console.error('[Init] Fatal error:', e)); </script> </body> </html>
<!doctype html><html lang="en"><head><meta charset="UTF-8"/><meta name="viewport" content="width=device-width,initial-scale=1.0"/><title>MURI Protocol Resolver</title><style>body{margin:0;padding:0;display:flex;justify-content:center;align-items:center;min-height:100vh;background:#000;font-family:Arial,sans-serif;}#content{color:white;}#debug{position:fixed;top:10px;left:10px;color:white;font-family:monospace;font-size:12px;max-width:300px;word-break:break-all;z-index:1000;}.stage{position:fixed;inset:0;overflow:hidden;background:#000;touch-action:none;cursor:grab;}.stage.grabbing{cursor:grabbing;}.panzoom{transform-origin:0 0;visibility:hidden;}.panzoom img{display:block;user-select:none;-webkit-user-drag:none;-khtml-user-drag:none;-moz-user-drag:none;-o-user-drag:none;user-drag:none;}.media-container{position:fixed;inset:0;display:flex;justify-content:center;align-items:center;background:#000;overflow:auto;}.video-player,.audio-player{max-width:90vw;max-height:90vh;}.audio-player{width:400px;}.threejs-container{position:fixed;inset:0;background:#000;}.threejs-controls{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);padding:10px;border-radius:5px;color:white;font-family:monospace;font-size:12px;}.text-viewer{position:fixed;inset:20px;background:#1e1e1e;color:#d4d4d4;font-family:"Courier New",monospace;font-size:14px;line-height:1.5;overflow:auto;padding:20px;border-radius:5px;}.json-viewer{white-space:pre-wrap;}.json-key{color:#9cdcfe;}.json-string{color:#ce9178;}.json-number{color:#b5cea8;}.json-boolean{color:#569cd6;}.json-null{color:#569cd6;}.html-viewer{position:fixed;inset:0;border:none;background:white;width:100vw;height:100vh;z-index:999;}.loading{display:flex;justify-content:center;align-items:center;color:white;font-size:18px;}.error{color:#ff6b6b;text-align:center;padding:20px;}</style></head><body><div id="content">Loading...</div><div id="debug"></div><script>async function sha256(buffer){const data=new Uint8Array(buffer);const totalBlocks=Math.ceil((data.length+9)/64);let processedBlocks=0;function rightRotate(value,amount){return(value >>> amount)|(value <<(32-amount));}const k=[0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2];let h=[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19];const len=data.length*8;const padding=new Uint8Array(64-((data.length+9)% 64));const padded=new Uint8Array(data.length+1+padding.length+8);padded.set(data);padded[data.length]=0x80;const view=new DataView(padded.buffer);view.setUint32(padded.length-4,len,false);for(let i=0;i < padded.length;i+=64){processedBlocks++;if(processedBlocks % 4096===0){const progress=Math.floor((processedBlocks/totalBlocks)*100);debug.innerHTML=debug.innerHTML.split('<br>Hashing:')[0]+'<br>Hashing:'+progress+'%';await new Promise(resolve=> setTimeout(resolve,0));}const w=new Array(64);for(let j=0;j < 16;j++){w[j]=view.getUint32(i+j*4,false);}for(let j=16;j < 64;j++){const s0=rightRotate(w[j-15],7)^ rightRotate(w[j-15],18)^(w[j-15]>>> 3);const s1=rightRotate(w[j-2],17)^ rightRotate(w[j-2],19)^(w[j-2]>>> 10);w[j]=(w[j-16]+s0+w[j-7]+s1)>>> 0;}let[a,b,c,d,e,f,g,h0]=h;for(let j=0;j < 64;j++){const S1=rightRotate(e,6)^ rightRotate(e,11)^ rightRotate(e,25);const ch=(e & f)^(~e & g);const temp1=(h0+S1+ch+k[j]+w[j])>>> 0;const S0=rightRotate(a,2)^ rightRotate(a,13)^ rightRotate(a,22);const maj=(a & b)^(a & c)^(b & c);const temp2=(S0+maj)>>> 0;h0=g;g=f;f=e;e=(d+temp1)>>> 0;d=c;c=b;b=a;a=(temp1+temp2)>>> 0;}h=h.map((x,i)=>(x+[a,b,c,d,e,f,g,h0][i])>>> 0);}return h.map(x=> x.toString(16).padStart(8,'0')).join('');}function rewriteUri(uri){if(uri.startsWith('ar://')){return 'https://arweave.net/'+uri.slice(5);}if(uri.startsWith('ipfs://')){return 'https://ipfs.io/ipfs/'+uri.slice(7);}return uri;}const mediaUris=[{{FILE_URIS}}].map(rewriteUri);const expectedHash='{{FILE_HASH}}';const threejsLibraries={core:{uris:["https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js","https://cdn.jsdelivr.net/npm/three@0.128.0/build/three.min.js","https://unpkg.com/three@0.128.0/build/three.min.js","https://gateway.lighthouse.storage/ipfs/bafybeidkvmhsidcrkq45bncf2lfyiesszy4d3cbgsargqcyvss5hmmkzhy/three.min.js","https://hgivmajycxdg74kebrr7vef5ivgatdbawtg5k4qed2oa3ey23nka.arweave.net/OZFWATgVxm_xRAxj-pC9RUwJjCC0zdVyBB6cDZMa21Q"],expectedHash:"9274bbcec8d96168626c732b5d31c775aa8cfb7eaa0599bec0c175908a2c1ce2"},gltfLoader:{uris:["https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js","https://unpkg.com/three@0.128.0/examples/js/loaders/GLTFLoader.js","https://gateway.lighthouse.storage/ipfs/bafybeidkvmhsidcrkq45bncf2lfyiesszy4d3cbgsargqcyvss5hmmkzhy/GLTFLoader.js","https://dcve6f4lbyjp5gutxfp7fo5lvu4qcxhilnet2hfbuk5ayd4xxwqq.arweave.net/GKpPF4sOEv6ak7lf8rurrTkBXOhbST0coaK6DA-XvaE"],expectedHash:"5c15967ba830918a9caea6338712c994c354bccd4edc4569bde411c3ec06a3e6"},orbitControls:{uris:["https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js","https://unpkg.com/three@0.128.0/examples/js/controls/OrbitControls.js","https://gateway.lighthouse.storage/ipfs/bafybeidkvmhsidcrkq45bncf2lfyiesszy4d3cbgsargqcyvss5hmmkzhy/OrbitControls.js","https://c4y7vlkxilcx7p6zsy36yszscriicdr6zdj5wepbhx4l7ysw3nva.arweave.net/FzH6rVdCxX-_2ZY37EsyFFCBDj7I09sR4T34v-JW22o"],expectedHash:"02bb4ade710f3e607329e37a21f098bc3ac70eb6e33daf8a65e79f4db785e7b2"}};const debug=document.getElementById('debug');debug.innerHTML='Expected:'+expectedHash+'<br>URIs:'+mediaUris.length+'<br>Checking available media...';let threejsLoaded=false;async function loadScriptFromURIs(uris,scriptName,expectedScriptHash){for(let i=0;i < uris.length;i++){const uri=uris[i];try{debug.innerHTML+='<br>Loading '+scriptName+' from:'+uri.substring(0,30)+'...';const response=await fetch(uri,{mode:'cors'});if(response.status===200){const scriptText=await response.text();if(expectedScriptHash && expectedScriptHash.length > 0){debug.innerHTML+='<br>Verifying '+scriptName+' hash...';const encoder=new TextEncoder();const scriptBuffer=encoder.encode(scriptText);const hashHex=await sha256(scriptBuffer);const cleanExpected=expectedScriptHash.replace('0x','');if(hashHex !==cleanExpected){debug.innerHTML+='<br>'+scriptName+' hash mismatch,trying next URI';continue;}debug.innerHTML+='<br>'+scriptName+' hash verified!';}const script=document.createElement('script');script.textContent=scriptText;document.head.appendChild(script);debug.innerHTML+='<br>'+scriptName+' loaded successfully';return true;}}catch(e){debug.innerHTML+='<br>'+scriptName+' failed from this URI:'+e.message;}}debug.innerHTML+='<br>ERROR:Could not load '+scriptName;return false;}async function loadThreeJS(){if(threejsLoaded)return true;debug.innerHTML+='<br>Loading Three.js libraries...';const coreLoaded=await loadScriptFromURIs(threejsLibraries.core.uris,'Three.js Core',threejsLibraries.core.expectedHash);if(!coreLoaded)return false;const gltfLoaded=await loadScriptFromURIs(threejsLibraries.gltfLoader.uris,'GLTFLoader',threejsLibraries.gltfLoader.expectedHash);if(!gltfLoaded)return false;const controlsLoaded=await loadScriptFromURIs(threejsLibraries.orbitControls.uris,'OrbitControls',threejsLibraries.orbitControls.expectedHash);if(!controlsLoaded)return false;threejsLoaded=true;debug.innerHTML+='<br>All Three.js libraries loaded!';return true;}function detectMediaType(arrayBuffer,uri){const uint8Array=new Uint8Array(arrayBuffer);const header=Array.from(uint8Array.slice(0,16)).map(b=> b.toString(16).padStart(2,'0')).join('');if(uri.startsWith('data:')){const mimeMatch=uri.match(/data:([^;]+)/);if(mimeMatch){const mimeType=mimeMatch[1];if(mimeType.startsWith('image/'))return 'image';if(mimeType.startsWith('video/'))return 'video';if(mimeType.startsWith('audio/'))return 'audio';if(mimeType==='text/html')return 'html';if(mimeType==='application/json')return 'json';if(mimeType.startsWith('text/'))return 'text';if(mimeType==='model/gltf+json')return '3d';}}if(header.startsWith('ffd8ff'))return 'image';if(header.startsWith('89504e47')){return 'image';}if(header.startsWith('47494638'))return 'image';if(header.startsWith('52494646')&& uint8Array[8]===0x57 && uint8Array[9]===0x45 && uint8Array[10]===0x42 && uint8Array[11]===0x50)return 'image';if(header.startsWith('00000020667479706176696601')|| header.startsWith('0000001c667479706176696601'))return 'image';if(header.startsWith('424d'))return 'image';if(header.startsWith('00000100')|| header.startsWith('00000200'))return 'image';if(header.startsWith('49492a00')|| header.startsWith('4d4d002a'))return 'image';if(header.startsWith('3c3f786d6c')|| header.startsWith('3c737667'))return 'image';if(header.includes('667479706d703432')|| header.includes('667479706d703431')|| header.includes('6674797069736f6d'))return 'video';if(header.startsWith('464c5601'))return 'video';if(header.startsWith('1a45dfa3')){const extendedHeader=Array.from(uint8Array.slice(0,200)).map(b=> b.toString(16).padStart(2,'0')).join('');if(extendedHeader.includes('56503830')|| extendedHeader.includes('56503930')|| extendedHeader.includes('415631')){return 'video';}else if(extendedHeader.includes('566f72626973')|| extendedHeader.includes('4f707573')){return 'audio';}return 'video';}if(header.startsWith('52494646')&& uint8Array[8]===0x41 && uint8Array[9]===0x56 && uint8Array[10]===0x49 && uint8Array[11]===0x20)return 'video';if(header.includes('6674797071742020')|| header.includes('6d6f6f76'))return 'video';if(header.includes('667479703367702')|| header.includes('667479703367703'))return 'video';if(header.startsWith('000001ba')|| header.startsWith('000001b3'))return 'video';if(header.startsWith('4f676753')&& uint8Array.slice(28,35).includes(0x74,0x68,0x65,0x6f,0x72,0x61))return 'video';if(header.startsWith('494433')|| header.startsWith('fff3')|| header.startsWith('fff2'))return 'audio';if(header.startsWith('4f676753'))return 'audio';if(header.startsWith('52494646')&& uint8Array[8]===0x57 && uint8Array[9]===0x41 && uint8Array[10]===0x56 && uint8Array[11]===0x45)return 'audio';if(header.startsWith('664c6143'))return 'audio';if(header.includes('6674797')&&(header.includes('4d344120')|| header.includes('4d344120')|| header.includes('69736f6d'))){const fullHeader=Array.from(uint8Array.slice(0,64)).map(b=> b.toString(16).padStart(2,'0')).join('');if(fullHeader.includes('6d703461')|| fullHeader.includes('4d344120')|| !fullHeader.includes('61766331')){return 'audio';}}if(header.startsWith('676c5446'))return '3d';const textContent=new TextDecoder().decode(uint8Array.slice(0,200));if(textContent.includes('"asset"')&& textContent.includes('"version"')&& textContent.includes('"generator"'))return '3d';try{const text=new TextDecoder().decode(arrayBuffer);if(text.trim().startsWith('{')&& text.trim().endsWith('}')){JSON.parse(text);return 'json';}if(text.includes('<html')|| text.includes('<!DOCTYPE'))return 'html';return 'text';}catch(e){}return 'unknown';}async function calculateHash(arrayBuffer){try{return await sha256(arrayBuffer);}catch(e){debug.innerHTML+='<br>Hash error:'+e.message;return null;}}function renderImage(uri,arrayBuffer){const content=document.getElementById('content');const stage=document.createElement('div');stage.className='stage';const panzoom=document.createElement('div');panzoom.className='panzoom';const img=document.createElement('img');img.src=uri;let scale=1,minScale=1,x=0,y=0;const zoomFactors=[1,2,3];let zoomIndex=0;function clampPosition(){const sw=stage.clientWidth,sh=stage.clientHeight;const iw=img.naturalWidth || 0,ih=img.naturalHeight || 0;const dw=iw*scale,dh=ih*scale;if(dw <=sw){x=Math.round((sw-dw)/2);}else{if(x > 0)x=0;if(x < sw-dw)x=Math.round(sw-dw);}if(dh <=sh){y=Math.round((sh-dh)/2);}else{if(y > 0)y=0;if(y < sh-dh)y=Math.round(sh-dh);}}function applyTransform(){clampPosition();panzoom.style.transform='translate('+x+'px,'+y+'px)scale('+scale+')';}function setZoomAt(factor,cx,cy){const newScale=Math.max(minScale,Math.min(10,minScale*factor));const k=newScale/scale;x=cx-(cx-x)*k;y=cy-(cy-y)*k;scale=newScale;applyTransform();}img.onload=()=>{const sw=stage.clientWidth,sh=stage.clientHeight;const iw=img.naturalWidth,ih=img.naturalHeight;minScale=Math.min(sw/iw,sh/ih);scale=minScale;x=Math.round((sw-iw*scale)/2);y=Math.round((sh-ih*scale)/2);applyTransform();panzoom.style.visibility='visible';debug.style.display='none';};let isPanning=false,startX=0,startY=0,startTX=0,startTY=0,moved=false;stage.addEventListener('pointerdown',(e)=>{isPanning=true;moved=false;startX=e.clientX;startY=e.clientY;startTX=x;startTY=y;stage.setPointerCapture(e.pointerId);stage.classList.add('grabbing');});stage.addEventListener('pointermove',(e)=>{if(!isPanning)return;const dx=e.clientX-startX;const dy=e.clientY-startY;if(Math.abs(dx)> 2 || Math.abs(dy)> 2)moved=true;x=startTX+dx;y=startTY+dy;applyTransform();});const endPan=(e)=>{if(!isPanning)return;isPanning=false;stage.releasePointerCapture(e.pointerId);stage.classList.remove('grabbing');};stage.addEventListener('pointerup',(e)=>{const rect=stage.getBoundingClientRect();const cx=e.clientX-rect.left;const cy=e.clientY-rect.top;const wasMoved=moved;endPan(e);if(!wasMoved){zoomIndex=(zoomIndex+1)% zoomFactors.length;setZoomAt(zoomFactors[zoomIndex],cx,cy);}});stage.addEventListener('pointercancel',endPan);stage.addEventListener('wheel',(e)=>{e.preventDefault();const rect=stage.getBoundingClientRect();const cx=e.clientX-rect.left;const cy=e.clientY-rect.top;const zoom=Math.exp(-e.deltaY*0.0015);const newScale=Math.max(minScale,Math.min(10,scale*zoom));const k=newScale/scale;x=cx-(cx-x)*k;y=cy-(cy-y)*k;scale=newScale;applyTransform();},{passive:false});window.addEventListener('resize',()=>{const sw=stage.clientWidth,sh=stage.clientHeight;const iw=img.naturalWidth,ih=img.naturalHeight;const newMin=Math.min(sw/iw,sh/ih);minScale=newMin;if(scale < minScale){scale=minScale;}applyTransform();});content.innerHTML='';content.appendChild(stage);stage.appendChild(panzoom);panzoom.appendChild(img);}function renderVideo(uri){const content=document.getElementById('content');const container=document.createElement('div');container.className='media-container';const video=document.createElement('video');video.className='video-player';video.src=uri;video.controls=true;video.autoplay=false;video.style.maxWidth='100%';video.style.maxHeight='100%';container.appendChild(video);content.innerHTML='';content.appendChild(container);debug.style.display='none';}function renderAudio(uri){const content=document.getElementById('content');const container=document.createElement('div');container.className='media-container';const audio=document.createElement('audio');audio.className='audio-player';audio.src=uri;audio.controls=true;audio.style.width='100%';container.appendChild(audio);content.innerHTML='';content.appendChild(container);debug.style.display='none';}async function render3D(uri,arrayBuffer){const content=document.getElementById('content');const container=document.createElement('div');container.className='threejs-container';container.innerHTML='<div class="loading">Loading 3D model...</div>';const controls=document.createElement('div');controls.className='threejs-controls';controls.innerHTML='Loading Three.js libraries...';content.innerHTML='';content.appendChild(container);content.appendChild(controls);const loaded=await loadThreeJS();if(!loaded){container.innerHTML='<div class="error">Failed to load Three.js libraries<br>Check console for details</div>';return;}controls.innerHTML='Mouse:Rotate | Wheel:Zoom | Right-click:Pan';try{const scene=new THREE.Scene();scene.background=new THREE.Color(0x000000);const camera=new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,0.1,1000);const renderer=new THREE.WebGLRenderer({antialias:true});renderer.setSize(window.innerWidth,window.innerHeight);renderer.shadowMap.enabled=true;renderer.shadowMap.type=THREE.PCFSoftShadowMap;container.innerHTML='';container.appendChild(renderer.domElement);const ambientLight=new THREE.AmbientLight(0x404040,0.6);scene.add(ambientLight);const directionalLight=new THREE.DirectionalLight(0xffffff,0.8);directionalLight.position.set(10,10,5);directionalLight.castShadow=true;scene.add(directionalLight);const orbitControls=new THREE.OrbitControls(camera,renderer.domElement);orbitControls.enableDamping=true;orbitControls.dampingFactor=0.05;const uint8Array=new Uint8Array(arrayBuffer);const isGLB=uint8Array[0]===0x67 && uint8Array[1]===0x6C && uint8Array[2]===0x54 && uint8Array[3]===0x46;if(!isGLB){try{const gltfText=new TextDecoder().decode(arrayBuffer);const gltfJson=JSON.parse(gltfText);if(gltfJson.buffers && gltfJson.buffers.some(buffer=> buffer.uri && !buffer.uri.startsWith('data:'))){container.innerHTML='<div class="error">GLTF with external files not supported<br>Please use GLB(binary)format or embedded GLTF</div>';return;}}catch(e){container.innerHTML='<div class="error">Invalid GLTF file format</div>';return;}}const loader=new THREE.GLTFLoader();const blob=new Blob([arrayBuffer],{type:isGLB ? 'model/gltf-binary':'model/gltf+json'});const blobUrl=URL.createObjectURL(blob);loader.load(blobUrl,function(gltf){const model=gltf.scene;if(!model || model.children.length===0){container.innerHTML='<div class="error">No 3D geometry found in model</div>';URL.revokeObjectURL(blobUrl);return;}scene.add(model);const box=new THREE.Box3().setFromObject(model);const center=box.getCenter(new THREE.Vector3());const size=box.getSize(new THREE.Vector3());const maxDim=Math.max(size.x,size.y,size.z);if(maxDim > 0){const scale=2/maxDim;model.scale.setScalar(scale);model.position.sub(center.multiplyScalar(scale));}camera.position.set(0,0,3);camera.lookAt(0,0,0);URL.revokeObjectURL(blobUrl);controls.innerHTML='Mouse:Rotate | Wheel:Zoom | Right-click:Pan';},function(progress){if(progress.total > 0){const percent=Math.round((progress.loaded/progress.total)*100);controls.innerHTML='Loading 3D model:'+percent+'%';}},function(error){console.error('Error loading 3D model:',error);URL.revokeObjectURL(blobUrl);let errorMessage='Error loading 3D model';if(error.message.includes('buffer')){errorMessage='Model references external files<br>Use GLB format for self-contained models';}else if(error.message.includes('JSON')){errorMessage='Invalid GLTF format<br>Check file structure';}container.innerHTML='<div class="error">'+errorMessage+'</div>';});function animate(){requestAnimationFrame(animate);orbitControls.update();renderer.render(scene,camera);}animate();window.addEventListener('resize',()=>{camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);});}catch(error){console.error('Three.js initialization error:',error);container.innerHTML='<div class="error">Three.js not available<br>3D models require Three.js library</div>';}debug.style.display='none';}function renderHTML(arrayBuffer){const content=document.getElementById('content');const htmlContent=new TextDecoder().decode(arrayBuffer);debug.style.display='none';const iframe=document.createElement('iframe');iframe.className='html-viewer';iframe.srcdoc=htmlContent;iframe.sandbox='allow-scripts allow-same-origin allow-forms allow-popups allow-modals';iframe.style.position='fixed';iframe.style.top='0';iframe.style.left='0';iframe.style.width='100vw';iframe.style.height='100vh';iframe.style.border='none';iframe.style.zIndex='9999';iframe.style.background='white';document.body.innerHTML='';document.body.appendChild(iframe);document.body.style.margin='0';document.body.style.padding='0';document.body.style.overflow='hidden';}function renderText(arrayBuffer){const content=document.getElementById('content');const textContent=new TextDecoder().decode(arrayBuffer);const viewer=document.createElement('div');viewer.className='text-viewer';viewer.textContent=textContent;content.innerHTML='';content.appendChild(viewer);debug.style.display='none';}function renderJSON(arrayBuffer){const content=document.getElementById('content');const jsonText=new TextDecoder().decode(arrayBuffer);try{const jsonObj=JSON.parse(jsonText);const formatted=JSON.stringify(jsonObj,null,2);const viewer=document.createElement('div');viewer.className='text-viewer json-viewer';const highlighted=formatted.replace(/"([^"]+)":/g,'<span class="json-key">"$1"</span>:').replace(/:"([^"]*)"/g,':<span class="json-string">"$1"</span>').replace(/:(\d+)/g,':<span class="json-number">$1</span>').replace(/:(true|false)/g,':<span class="json-boolean">$1</span>').replace(/:null/g,':<span class="json-null">null</span>');viewer.innerHTML=highlighted;content.innerHTML='';content.appendChild(viewer);}catch(e){renderText(arrayBuffer);}debug.style.display='none';}async function loadMedia(){const content=document.getElementById('content');for(let i=0;i < mediaUris.length;i++){const uri=mediaUris[i];try{debug.innerHTML+='<br>Trying:'+uri.substring(0,30)+'...';const response=await fetch(uri,{mode:'cors'});debug.innerHTML+='<br>Status:'+response.status;if(response.status===200){const arrayBuffer=await response.arrayBuffer();debug.innerHTML+='<br>Size:'+arrayBuffer.byteLength;if(!expectedHash || expectedHash.length===0){debug.innerHTML+='<br>NO EXPECTED HASH-Cannot verify';continue;}const fileSizeMB=(arrayBuffer.byteLength/(1024*1024)).toFixed(1);debug.innerHTML+='<br>Hashing '+fileSizeMB+'MB file...';const hashHex=await calculateHash(arrayBuffer);if(!hashHex){debug.innerHTML+='<br>Hash calculation failed';continue;}debug.innerHTML+='<br>Hash:'+hashHex.substring(0,16)+'...';const cleanExpected=expectedHash.replace('0x','');if(hashHex===cleanExpected){debug.innerHTML+='<br>HASH MATCH! Loading...';const mediaType=detectMediaType(arrayBuffer,uri);debug.innerHTML+='<br>Media type:'+mediaType;switch(mediaType){case 'image':renderImage(uri,arrayBuffer);break;case 'video':renderVideo(uri);break;case 'audio':renderAudio(uri);break;case '3d':render3D(uri,arrayBuffer);break;case 'html':renderHTML(arrayBuffer);break;case 'json':renderJSON(arrayBuffer);break;case 'text':renderText(arrayBuffer);break;default:content.innerHTML='<div class="error">Unsupported media type:'+mediaType+'</div>';debug.style.display='none';}return;}else{debug.innerHTML+='<br>Hash mismatch';}}}catch(e){debug.innerHTML+='<br>Error:'+e.message;}}content.innerHTML='No media with matching hash found. All media failed hash verification.';}loadMedia();</script></body></html>