aboutsummaryrefslogtreecommitdiff
path: root/downloads/memgraph-today.html
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-11-09 15:36:23 +0100
committerAgando <roduccomm@gmail.com>2026-11-09 15:36:23 +0100
commita065a4c30be748b0c8fc6dd0b5ee1dc4d3de4b1c (patch)
tree8a84adabfccee9994937cd71888170ab205b9361 /downloads/memgraph-today.html
parenta46ebee30cfbbac86abb45068411bc28e1c65503 (diff)
chore(newos4): commit accumulated maintenance outputHEADv4.6.156main
The daily collector and the feedback pipeline had left 57 modified files uncommitted (skills, modules-index, CHANGELOG, feedback decisions, debug counters). The public mirror publishes git HEAD, so without this the published repo stayed behind the tree - and it blocked the publisher outright once uncommitted subtrees started failing loudly.
Diffstat (limited to 'downloads/memgraph-today.html')
-rw-r--r--downloads/memgraph-today.html243
1 files changed, 243 insertions, 0 deletions
diff --git a/downloads/memgraph-today.html b/downloads/memgraph-today.html
new file mode 100644
index 0000000..c30a24b
--- /dev/null
+++ b/downloads/memgraph-today.html
@@ -0,0 +1,243 @@
+<!DOCTYPE html>
+<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<meta name="color-scheme" content="dark"><title>🗺️ Today's Map — Memory Graph</title>
+<style>*{box-sizing:border-box;margin:0;padding:0}
+html,body{height:100%;overflow:hidden;background:#070b14;color:#e2e8f0;font-family:system-ui,-apple-system,'Segoe UI',Roboto,sans-serif}
+#cv{position:fixed;inset:0;cursor:grab;touch-action:none}
+.panel{position:fixed;background:rgba(13,20,36,.82);border:1px solid rgba(148,163,184,.15);border-radius:14px;backdrop-filter:blur(12px);box-shadow:0 12px 40px rgba(0,0,0,.45)}
+#top{top:10px;left:10px;right:10px;height:50px;display:flex;align-items:center;gap:10px;padding:0 12px;z-index:20}
+#brand{font-weight:700;font-size:15px;white-space:nowrap}#brand small{display:block;font-size:9.5px;color:#7c8db0;letter-spacing:.08em;text-transform:uppercase}
+#counts{font-size:12px;color:#67e8f9;border:1px solid rgba(34,211,238,.3);background:rgba(8,47,73,.45);padding:4px 10px;border-radius:999px;white-space:nowrap}
+#src{font-size:11px;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:280px}
+#btns{margin-left:auto;display:flex;gap:6px}
+.btn{height:30px;padding:0 10px;border-radius:8px;border:1px solid rgba(148,163,184,.18);background:rgba(30,41,59,.6);color:#cbd5e1;font-size:12.5px;cursor:pointer;display:inline-flex;align-items:center;white-space:nowrap}
+.btn:hover{background:rgba(51,65,85,.9);color:#fff;border-color:rgba(34,211,238,.4)}
+#side{top:70px;left:10px;bottom:96px;width:300px;padding:13px;overflow-y:auto;z-index:15}
+h3{font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:#7c8db0;margin:14px 0 7px}h3:first-child{margin-top:0}
+.grid{display:grid;grid-template-columns:1fr 1fr;gap:7px}
+.st{background:rgba(30,41,59,.45);border:1px solid rgba(148,163,184,.12);border-radius:9px;padding:7px 9px}
+.st b{font-size:15px;display:block}.st i{font-size:9.5px;color:#7c8db0;text-transform:uppercase;letter-spacing:.06em;font-style:normal}
+.chip{display:inline-flex;align-items:center;gap:6px;padding:3px 8px;margin:0 5px 5px 0;border-radius:999px;border:1px solid rgba(148,163,184,.16);background:rgba(30,41,59,.5);font-size:11.5px;cursor:pointer;color:#cbd5e1}
+.chip .d{width:8px;height:8px;border-radius:50%}.chip.off{opacity:.32;filter:saturate(.2)}
+.row{display:flex;align-items:center;gap:7px;font-size:12px;padding:4px 6px;border-radius:7px;color:#cbd5e1;min-width:0}
+.row:hover{background:rgba(34,211,238,.08)}.row .d{width:7px;height:7px;border-radius:50%;flex-shrink:0}
+.row .l{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.row .v{color:#7c8db0;font-size:11px}
+.leg{display:flex;align-items:center;gap:7px;font-size:11px;color:#94a3b8;padding:2px 4px}
+.leg .ln{width:18px;height:3px;border-radius:2px;flex-shrink:0}
+#det{top:70px;right:10px;width:320px;max-height:calc(100% - 116px);padding:13px;overflow-y:auto;z-index:16;display:none}
+#det.on{display:block}#dt{font-size:14px;font-weight:700;word-break:break-word;margin-bottom:6px}
+.kv{display:flex;gap:8px;font-size:12px;padding:3px 0;border-bottom:1px dashed rgba(148,163,184,.1)}
+.kv i{color:#7c8db0;width:74px;flex-shrink:0;font-style:normal}.kv b{font-weight:400;word-break:break-all}
+.er{display:flex;gap:6px;align-items:center;font-size:11px;padding:4px 6px;border-radius:6px;color:#cbd5e1;min-width:0;cursor:pointer}
+.er:hover{background:rgba(34,211,238,.08)}.er .t{color:#67e8f9;font-size:9.5px;background:rgba(8,47,73,.5);padding:1px 5px;border-radius:5px;flex-shrink:0}
+.er .l{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
+#tl{left:10px;right:10px;bottom:10px;height:78px;padding:8px 12px;z-index:20;display:flex;gap:10px;align-items:center}
+#tlcv{flex:1;height:58px;cursor:ew-resize;border-radius:8px}#tread{font-size:11px;color:#94a3b8;white-space:nowrap;width:200px;line-height:1.5}
+#tip{position:fixed;pointer-events:none;background:rgba(2,6,17,.95);border:1px solid rgba(148,163,184,.28);border-radius:8px;padding:7px 9px;font-size:12px;z-index:50;display:none;max-width:260px}
+#ov{position:fixed;inset:0;background:rgba(4,7,14,.85);display:flex;align-items:center;justify-content:center;z-index:100}
+#ov .c{background:rgba(13,20,36,.95);border:1px solid rgba(148,163,184,.2);border-radius:16px;padding:26px;max-width:440px;text-align:center}
+#ov .c h2{font-size:16px;margin-bottom:8px}#ov .c p{font-size:12px;color:#94a3b8;line-height:1.6}</style></head>
+<body><canvas id="cv"></canvas>
+<header id="top" class="panel"><div id="brand">🗺️ Today's Map<small id="date"></small></div>
+<span id="counts">…</span><span id="src"></span>
+<div id="btns"><button class="btn" id="bPlay">▶ Replay</button><button class="btn" id="bSpd">1×</button>
+<button class="btn" id="bNow">Now</button><button class="btn" id="bFit">Fit</button><button class="btn" id="bPng">PNG</button></div></header>
+<aside id="side" class="panel"><h3>Today</h3><div class="grid" id="stats"></div><h3>Types</h3><div id="types"></div>
+<h3>Top today</h3><div id="tops"></div><h3>Edges</h3><div id="legend"></div></aside>
+<div id="det" class="panel"><div id="dt"></div><div id="dk"></div><div id="de"></div></div>
+<footer id="tl" class="panel"><canvas id="tlcv"></canvas><div id="tread"></div></footer>
+<div id="tip"></div><div id="ov"><div class="c"><h2>Loading today's map…</h2><p id="ovm">Reading the live memory graph.</p></div></div>
+<script>'use strict';
+const $=id=>document.getElementById(id);
+const COLORS={session:'#fbbf24',task:'#a78bfa',tool:'#34d399',file:'#38bdf8',type:'#fb7185',concept:'#f97316',fact:'#f472b6'};
+const EC={failed:'#ef4444',has_fact:'#f472b6',executed:'#a78bfa',used:'#38bdf8',accessed:'#38bdf8',maps_to:'#94a3b8',tagged_as:'#94a3b8',involves:'#f97316',has_type:'#94a3b8'};
+const EW={failed:2,has_fact:2,executed:1.6,used:1.4,accessed:1.4,maps_to:1.2,tagged_as:1.2,involves:1,has_type:1};
+const S={nodes:[],edges:[],src:'',t0:0,t1:0,t:Infinity,playing:false,speed:1,pt:0,filt:{},sel:null,hover:null,view:{x:0,y:0,k:1},bk:[],fails:[]};
+let cv,ctx,tlc,tx,dpr=1,W=0,H=0,TLW=0,TLH=0;
+const gunzip=async b=>{const s=new Blob([b]).stream().pipeThrough(new DecompressionStream('gzip'));return new Uint8Array(await new Response(s).arrayBuffer());};
+const b64u=s=>{const x=atob(s),u=new Uint8Array(x.length);for(let i=0;i<x.length;i++)u[i]=x.charCodeAt(i);return u;};
+const isN=a=>Array.isArray(a)&&a.length&&a[0]&&typeof a[0]==='object'&&'id'in a[0]&&'type'in a[0]&&!('source'in a[0]);
+const isE=a=>Array.isArray(a)&&a.length&&a[0]&&typeof a[0]==='object'&&'source'in a[0]&&'target'in a[0];
+function hunt(o,pred,d){d=d||0;if(o==null||d>5)return null;
+ if(typeof o==='string'){if(o.length>50&&(o[0]==='['||o[0]==='{')){try{return hunt(JSON.parse(o),pred,d+1);}catch(e){}}return null;}
+ if(Array.isArray(o)){if(pred(o))return o;for(const x of o){const r=hunt(x,pred,d+1);if(r)return r;}return null;}
+ if(typeof o==='object'){const ks=Object.keys(o);
+ for(const k of ks){if(/nodes\.json$|edges\.json$/.test(k)){const r=hunt(o[k],pred,d+1);if(r)return r;}}
+ for(const k of ks){const r=hunt(o[k],pred,d+1);if(r)return r;}}
+ return null;}
+async function loadModule(){const c=[];
+ try{if(window.MemoryGraphModule)c.push(window.MemoryGraphModule);}catch(e){}
+ try{if(window.modules&&window.modules['MemoryGraphModule'])c.push(window.modules['MemoryGraphModule']);}catch(e){}
+ for(const M of c){try{if(typeof M.getGraph==='function'){const g=M.getGraph();if(g&&g.nodes&&g.nodes.length)return{nodes:g.nodes,edges:g.edges||[],src:'live module'};}
+ if(M.nodes&&M.nodes.length)return{nodes:M.nodes,edges:M.edges||[],src:'live module'};}catch(e){}}
+ return null;}
+async function loadLS(){for(const k of['webx11_filesystem_v2','webx11_filesystem']){let v=null;try{v=localStorage.getItem(k);}catch(e){}
+ if(!v||v.length<100)continue;let txt=v;try{txt=new TextDecoder().decode(await gunzip(b64u(v)));}catch(e){}
+ let j=null;try{j=JSON.parse(txt);}catch(e){}if(!j)continue;
+ const n=hunt(j,isN),e2=hunt(j,isE);if(n)return{nodes:n,edges:e2||[],src:'VFS · '+k};}return null;}
+async function loadIDB(){const open=n=>new Promise((res,rej)=>{const q=indexedDB.open(n);q.onsuccess=()=>res(q.result);q.onerror=()=>rej(q.error||new Error('open'));});
+ const kOf=(db,sn)=>new Promise(res=>{const ks=[];try{const tx=db.transaction(sn,'readonly');const q=tx.objectStore(sn).openKeyCursor();q.onsuccess=()=>{const c=q.result;c?(ks.push(c.key),c.continue()):res(ks);};q.onerror=()=>res(ks);}catch(e){res(ks);}});
+ const get=(db,sn,k)=>new Promise(res=>{try{const tx=db.transaction(sn,'readonly');const q=tx.objectStore(sn).get(k);q.onsuccess=()=>res(q.result);q.onerror=()=>res(null);}catch(e){res(null);}});
+ const dec=async rec=>{if(!rec)return null;
+ for(const f of['content','data','body','value','text','blob']){let v=rec[f];if(v==null)continue;
+ if(typeof v==='string'){try{return JSON.parse(v);}catch(e){}continue;}
+ let b=v instanceof Uint8Array?v:(v instanceof ArrayBuffer?new Uint8Array(v):(v instanceof Blob?new Uint8Array(await v.arrayBuffer()):null));
+ if(!b)continue;try{return JSON.parse(new TextDecoder().decode(b));}catch(e){}
+ try{return JSON.parse(new TextDecoder().decode(await gunzip(b)));}catch(e){}}
+ return null;};
+ try{const db=await open('webx11_vfs');
+ for(const sn of[...db.objectStoreNames]){const ks=await kOf(db,sn);
+ const nh=ks.filter(k=>String(k).endsWith('nodes.json')),eh=ks.filter(k=>String(k).endsWith('edges.json'));
+ if(nh.length){const nn=await dec(await get(db,sn,nh[nh.length-1]));
+ const n=nn&&(Array.isArray(nn)?nn:nn.nodes)||null;let e2=null;
+ if(eh.length){const ee=await dec(await get(db,sn,eh[eh.length-1]));e2=ee&&(Array.isArray(ee)?ee:ee.edges)||null;}
+ if(n)return{nodes:n,edges:e2||[],src:'IndexedDB · webx11_vfs'};}}}catch(e){}
+ return null;}
+function normalize(){const now=Date.now();
+ S.nodes.forEach((n,i)=>{n.acc=n.access_count||n.accessCount||0;n.cre=n.created||n.createdAt||now;
+ n.deg=0;n.vz={x:0,y:0};n.vis=null;n.lab=n.label||n.id||'';
+ const a=i*2.39996,r=40+9*Math.sqrt(i+1);n.px=W/2+r*Math.cos(a);n.py=H/2+r*Math.sin(a);
+ n.rad=4+Math.log2(1+n.acc)*2.2;
+ if(n.type==='session'){n.px=W/2;n.py=H/2;n.fix=true;n.rad=13;}});
+ const nm=new Map(S.nodes.map(n=>[n.id,n]));
+ S.edges.forEach(e=>{e.s=nm.get(e.source)||null;e.tt=nm.get(e.target)||null;
+ e.born=e.created||e.last_used||Math.max(e.s?e.s.cre:0,e.tt?e.tt.cre:0)||now;
+ if(e.s)e.s.deg++;if(e.tt)e.tt.deg++;});
+ const ts=S.nodes.map(n=>n.cre);S.t0=Math.min(...ts);S.t1=Math.max(...ts);S.t=S.t1+1;}
+function computeStats(){const bs=15*60*1000,arr=[];const b0=Math.floor(S.t0/bs)*bs,b1=Math.ceil((S.t1+1)/bs)*bs;
+ for(let t=b0;t<b1;t+=bs)arr.push({t,c:{}});
+ for(const n of S.nodes){const i=Math.floor((n.cre-b0)/bs);if(i>=0&&i<arr.length)arr[i].c[n.type]=(arr[i].c[n.type]||0)+1;}
+ S.bk=arr;S.fails=[...new Set(S.edges.filter(e=>e.type==='failed').map(e=>e.born))];
+ const hrs={};S.nodes.forEach(n=>{const h=new Date(n.cre).getHours();hrs[h]=(hrs[h]||0)+1;});
+ S.busy=Object.entries(hrs).sort((a,b)=>b[1]-a[1])[0]||['-',0];
+ S.sumA=S.nodes.reduce((s,n)=>s+n.acc,0);S.hours=Math.max(0.1,(S.t1-S.t0)/3600000);}
+const esc=s=>String(s).replace(/[<>&]/g,c=>({'<':'&lt;','>':'&gt;','&':'&amp;'}[c]));
+const fmt=ms=>{const d=new Date(ms);return String(d.getHours()).padStart(2,'0')+':'+String(d.getMinutes()).padStart(2,'0');};
+const fmtS=ms=>{const d=new Date(ms);return fmt(ms)+':'+String(d.getSeconds()).padStart(2,'0');};
+const kv=(k,v)=>`<div class="kv"><i>${k}</i><b>${v}</b></div>`;
+const stH=(k,v)=>`<div class="st"><b>${v}</b><i>${k}</i></div>`;
+function buildUI(){$('date').textContent=new Date(S.t0).toDateString();
+ $('counts').textContent=S.nodes.length+' nodes · '+S.edges.length+' edges';$('src').textContent=S.src;
+ $('stats').innerHTML=stH('Nodes',S.nodes.length)+stH('Edges',S.edges.length)+stH('First event',fmt(S.t0))+
+ stH('Span',S.hours.toFixed(1)+'h')+stH('Busiest hour',S.busy[0]+':00 ('+S.busy[1]+')')+stH('Accesses',S.sumA)+
+ stH('Growth',(S.nodes.length/S.hours).toFixed(1)+' n/h');
+ const tc={};S.nodes.forEach(n=>tc[n.type]=(tc[n.type]||0)+1);
+ $('types').innerHTML=Object.entries(tc).sort((a,b)=>b[1]-a[1]).map(([t,c])=>`<span class="chip" data-t="${t}"><span class="d" style="background:${COLORS[t]||'#94a3b8'}"></span>${t} · ${c}</span>`).join('');
+ [...$('types').children].forEach(ch=>ch.onclick=()=>{const t=ch.dataset.t;S.filt[t]=!S.filt[t];ch.classList.toggle('off');});
+ $('tops').innerHTML=[...S.nodes].sort((a,b)=>b.acc-a.acc).slice(0,8).map(n=>`<div class="row"><span class="d" style="background:${COLORS[n.type]||'#94a3b8'}"></span><span class="l">${esc(n.lab)}</span><span class="v">${n.acc}x</span></div>`).join('');
+ const ec={};S.edges.forEach(e=>ec[e.type]=(ec[e.type]||0)+1);
+ $('legend').innerHTML=Object.entries(ec).sort((a,b)=>b[1]-a[1]).map(([t,c])=>`<div class="leg"><span class="ln" style="background:${EC[t]||'#64748b'}"></span>${t} · ${c}${t==='failed'?' ⚠':''}</div>`).join('');}
+const vis=n=>S.t>=n.cre&&!S.filt[n.type];
+const evis=e=>e.s&&e.tt&&S.t>=e.born&&!S.filt[e.s.type]&&!S.filt[e.tt.type];
+function step(){const N=S.nodes;
+ for(let i=0;i<N.length;i++){const a=N[i];
+ for(let j=i+1;j<N.length;j++){const b=N[j];
+ let dx=b.px-a.px,dy=b.py-a.py;const d2=dx*dx+dy*dy+0.01,d=Math.sqrt(d2),f=2600/d2;dx/=d;dy/=d;
+ a.vz.x-=dx*f;a.vz.y-=dy*f;b.vz.x+=dx*f;b.vz.y+=dy*f;}}
+ for(const e of S.edges){if(!e.s||!e.tt)continue;const rest=110/(0.4+(e.weight||0.5));
+ let dx=e.tt.px-e.s.px,dy=e.tt.py-e.s.py;const d=Math.sqrt(dx*dx+dy*dy)||0.01,f=(d-rest)*0.02;dx/=d;dy/=d;
+ e.s.vz.x+=dx*f;e.s.vz.y+=dy*f;e.tt.vz.x-=dx*f;e.tt.vz.y-=dy*f;}
+ for(const n of N){if(n.fix){n.vz.x=n.vz.y=0;continue;}
+ n.vz.x+=(W/2-n.px)*0.0035;n.vz.y+=(H/2-n.py)*0.0035;
+ n.px+=n.vz.x*0.5;n.py+=n.vz.y*0.5;n.vz.x*=0.82;n.vz.y*=0.82;}}
+const shortLab=n=>{let s=String(n.lab);return s.length>26?s.slice(0,25)+'…':s;};
+function draw(){ctx.setTransform(dpr,0,0,dpr,0,0);ctx.fillStyle='#070b14';ctx.fillRect(0,0,W,H);
+ ctx.translate(S.view.x,S.view.y);ctx.scale(S.view.k,S.view.k);
+ const tn=performance.now();
+ for(const e of S.edges){if(!evis(e))continue;
+ ctx.strokeStyle=EC[e.type]||'#64748b';ctx.globalAlpha=0.18+0.6*(e.weight||0.5);
+ ctx.lineWidth=Math.max(0.6,(EW[e.type]||1)*Math.min(1,S.view.k+0.4));
+ ctx.setLineDash(e.type==='failed'?[6,5]:[]);
+ ctx.beginPath();ctx.moveTo(e.s.px,e.s.py);ctx.lineTo(e.tt.px,e.tt.py);ctx.stroke();}
+ ctx.setLineDash([]);ctx.globalAlpha=1;
+ for(const n of S.nodes){if(!vis(n)){n.vis=null;continue;}
+ if(n.vis===null)n.vis=tn;const r=n.rad*Math.min(1,(tn-n.vis)/350);
+ if(n.type==='session'){const g=ctx.createRadialGradient(n.px,n.py,2,n.px,n.py,r*4.5);
+ g.addColorStop(0,'rgba(251,191,36,.5)');g.addColorStop(1,'rgba(251,191,36,0)');
+ ctx.fillStyle=g;ctx.beginPath();ctx.arc(n.px,n.py,r*4.5,0,7);ctx.fill();}
+ ctx.fillStyle=COLORS[n.type]||'#94a3b8';
+ ctx.beginPath();ctx.arc(n.px,n.py,Math.max(0.5,r),0,7);ctx.fill();
+ if(n===S.sel||n===S.hover){ctx.strokeStyle='#e2e8f0';ctx.lineWidth=1.6;ctx.beginPath();ctx.arc(n.px,n.py,r+3,0,7);ctx.stroke();}}
+ for(const n of S.nodes){if(!vis(n))continue;
+ if(n.type==='session'||n.deg>=7||n===S.hover||n===S.sel||n.acc>=10){
+ ctx.font=(n.type==='session'?12:10.5)+'px system-ui';ctx.textAlign='center';
+ ctx.fillStyle='rgba(226,232,240,.85)';ctx.fillText(shortLab(n),n.px,n.py-n.rad-5);}}}
+function drawTL(){const w=TLW,h=TLH,pad=26;tx.setTransform(dpr,0,0,dpr,0,0);tx.clearRect(0,0,w,h);
+ const span=Math.max(1,S.t1-S.t0),X=t=>pad+(t-S.t0)/span*(w-2*pad);
+ const nb=S.bk.length,maxC=Math.max(1,...S.bk.map(b=>Object.values(b.c).reduce((a,b)=>a+b,0)));
+ const bw=Math.max(2,(w-2*pad)/nb-2);
+ for(const b of S.bk){let y=h-14;for(const t in b.c){const c=b.c[t],bh=c/maxC*(h-26);
+ tx.fillStyle=COLORS[t]||'#94a3b8';tx.globalAlpha=.75;tx.fillRect(X(b.t)-bw/2,y-bh,bw,bh);y-=bh;}}
+ tx.globalAlpha=1;tx.font='9px system-ui';tx.fillStyle='#7c8db0';tx.textAlign='center';
+ for(const b of S.bk){if(new Date(b.t).getMinutes()===0)tx.fillText(fmt(b.t),X(b.t),h-3);}
+ tx.fillStyle='#ef4444';tx.font='10px system-ui';for(const ft of S.fails)tx.fillText('✕',X(ft),10);
+ const mt=Math.min(S.t,S.t1);tx.strokeStyle='#22d3ee';tx.lineWidth=2;
+ tx.beginPath();tx.moveTo(X(mt),4);tx.lineTo(X(mt),h-14);tx.stroke();
+ tx.fillStyle='#22d3ee';tx.beginPath();tx.arc(X(mt),6,4,0,7);tx.fill();
+ let nv=0,ev=0;for(const n of S.nodes)if(vis(n))nv++;for(const e of S.edges)if(evis(e))ev++;
+ $('tread').innerHTML=`<b style="color:#67e8f9">${S.t>=S.t1?'now':fmt(mt)}</b><br>${nv}/${S.nodes.length} nodes · ${ev}/${S.edges.length} edges`;}
+function advancePlay(){const dur=20000/S.speed;
+ if(!S.pt)S.pt=performance.now();const el=(performance.now()-S.pt)/dur;
+ if(el>=1){S.playing=false;S.pt=0;S.t=S.t1+1;$('bPlay').textContent='▶ Replay';return;}
+ S.t=S.t0+el*(S.t1-S.t0);}
+function loop(){step();draw();drawTL();if(S.playing)advancePlay();requestAnimationFrame(loop);}
+function fit(){let x0=1e9,y0=1e9,x1=-1e9,y1=-1e9,c=0;
+ for(const n of S.nodes){if(!vis(n))continue;c++;
+ x0=Math.min(x0,n.px-n.rad);x1=Math.max(x1,n.px+n.rad);y0=Math.min(y0,n.py-n.rad);y1=Math.max(y1,n.py+n.rad);}
+ if(!c)return;S.view.k=Math.max(0.25,Math.min(2.5,Math.min((W-80)/(x1-x0),(H-160)/(y1-y0))));
+ S.view.x=W/2-(x0+x1)/2*S.view.k;S.view.y=H/2-(y0+y1)/2*S.view.k+20;}
+const toWorld=e=>({x:(e.clientX-S.view.x)/S.view.k,y:(e.clientY-S.view.y)/S.view.k});
+function hit(x,y){let best=null,bd=1e9;
+ for(const n of S.nodes){if(!vis(n))continue;const dx=n.px-x,dy=n.py-y,d=dx*dx+dy*dy;
+ const rr=(n.rad+5)*(n.rad+5);if(d<rr&&d<bd){bd=d;best=n;}}return best;}
+function tip(n,e){const t=$('tip');
+ if(!n){t.style.display='none';return;}
+ t.innerHTML=`<b>${esc(shortLab(n))}</b><br><span style="color:#7c8db0">${n.type} · ${fmtS(n.cre)}</span><br>accesses ${n.acc} · degree ${n.deg}`;
+ t.style.display='block';t.style.left=Math.min(innerWidth-270,e.clientX+14)+'px';t.style.top=(e.clientY+14)+'px';}
+function showDet(n){S.sel=n;$('det').classList.add('on');$('dt').textContent=n.lab;
+ $('dk').innerHTML=kv('type',n.type)+kv('id',esc(n.id))+kv('created',fmtS(n.cre))+kv('accesses',n.acc)+kv('degree',n.deg)+
+ kv('decay score',(1-((Date.now()-n.cre)/86400000)*0.9/30).toFixed(3));
+ $('de').innerHTML='';S.edges.filter(e=>e.s===n||e.tt===n).slice(0,14).forEach(e=>{const o=e.s===n?e.tt:e.s;
+ const d=document.createElement('div');d.className='er';
+ d.innerHTML=`<span class="t">${e.type}</span><span class="l">${esc(o?o.lab:'?')}</span><span style="color:#7c8db0">${e.weight||''} ${e.use_count!=null?'·'+e.use_count+'x':''}</span>`;
+ d.onclick=()=>{if(o)showDet(o);};$('de').appendChild(d);});}
+function sizeCanvases(){dpr=window.devicePixelRatio||1;W=innerWidth;H=innerHeight;
+ cv.width=W*dpr;cv.height=H*dpr;cv.style.width=W+'px';cv.style.height=H+'px';
+ const r=tlc.getBoundingClientRect();TLW=r.width;TLH=r.height;
+ tlc.width=TLW*dpr;tlc.height=TLH*dpr;tlc.style.width=TLW+'px';tlc.style.height=TLH+'px';}
+function initUI(){let pd=null;
+ cv.addEventListener('pointerdown',e=>{cv.setPointerCapture(e.pointerId);
+ const w=toWorld(e);pd={x0:e.clientX,y0:e.clientY,vx:S.view.x,vy:S.view.y,n:hit(w.x,w.y),moved:0,lx:e.clientX,ly:e.clientY};});
+ cv.addEventListener('pointermove',e=>{if(pd){pd.moved+=Math.abs(e.clientX-pd.lx)+Math.abs(e.clientY-pd.ly);pd.lx=e.clientX;pd.ly=e.clientY;
+ const w=toWorld(e);
+ if(pd.n){pd.n.px=w.x;pd.n.py=w.y;pd.n.vz.x=pd.n.vz.y=0;}
+ else{S.view.x=pd.vx+(e.clientX-pd.x0);S.view.y=pd.vy+(e.clientY-pd.y0);}}
+ else{const w=toWorld(e);S.hover=hit(w.x,w.y);tip(S.hover,e);}});
+ cv.addEventListener('pointerup',()=>{if(pd&&pd.moved<4){if(pd.n)showDet(pd.n);else{S.sel=null;$('det').classList.remove('on');}}pd=null;});
+ cv.addEventListener('wheel',e=>{e.preventDefault();
+ const k2=Math.max(0.15,Math.min(4,S.view.k*Math.exp(-e.deltaY*0.0012)));
+ const wx=(e.clientX-S.view.x)/S.view.k,wy=(e.clientY-S.view.y)/S.view.k;
+ S.view.k=k2;S.view.x=e.clientX-wx*k2;S.view.y=e.clientY-wy*k2;},{passive:false});
+ let ts=false;
+ tlc.addEventListener('pointerdown',e=>{ts=true;tlc.setPointerCapture(e.pointerId);scrub(e);});
+ tlc.addEventListener('pointermove',e=>{if(ts)scrub(e);});
+ tlc.addEventListener('pointerup',()=>ts=false);
+ $('bPlay').onclick=()=>{if(S.playing){S.playing=false;$('bPlay').textContent='▶ Replay';}
+ else{S.playing=true;S.pt=0;S.t=S.t0;$('bPlay').textContent='⏸ Pause';}};
+ $('bSpd').onclick=()=>{S.speed=S.speed===1?2:S.speed===2?4:1;$('bSpd').textContent=S.speed+'×';};
+ $('bNow').onclick=()=>{S.playing=false;S.pt=0;S.t=S.t1+1;$('bPlay').textContent='▶ Replay';};
+ $('bFit').onclick=()=>fit();
+ $('bPng').onclick=()=>{const a=document.createElement('a');a.download='memgraph-today.png';a.href=cv.toDataURL('image/png');a.click();};
+ addEventListener('resize',()=>{sizeCanvases();fit();});}
+function scrub(e){const r=tlc.getBoundingClientRect(),pad=26;
+ const x=Math.max(pad,Math.min(r.width-pad,e.clientX-r.left));
+ S.t=S.t0+(x-pad)/(r.width-2*pad)*(S.t1-S.t0);S.playing=false;$('bPlay').textContent='▶ Replay';}
+async function init(){cv=$('cv');ctx=cv.getContext('2d');tlc=$('tlcv');tx=tlc.getContext('2d');
+ sizeCanvases();initUI();
+ const d=(await loadModule())||(await loadLS())||(await loadIDB());
+ if(!d||!d.nodes||!d.nodes.length){
+ $('ovm').innerHTML='Could not reach the memory graph.<br>Tried: MemoryGraphModule → VFS (localStorage) → IndexedDB.<br>Open this file inside newOS4 (preview) so it can read <code>/.memory_graph/</code>.';
+ return;}
+ S.nodes=d.nodes;S.edges=d.edges;S.src=d.src;
+ normalize();computeStats();buildUI();fit();loop();}
+init();</script></body></html> \ No newline at end of file