diff options
| author | Agando <roduccomm@gmail.com> | 2026-11-09 15:36:23 +0100 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-11-09 15:36:23 +0100 |
| commit | a065a4c30be748b0c8fc6dd0b5ee1dc4d3de4b1c (patch) | |
| tree | 8a84adabfccee9994937cd71888170ab205b9361 /downloads/memgraph-today.html | |
| parent | a46ebee30cfbbac86abb45068411bc28e1c65503 (diff) | |
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.html | 243 |
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=>({'<':'<','>':'>','&':'&'}[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 |