← Node Graph Editor results

Node Graph Editor

GPT 5.6 Luna Pro · full

Initial view of Node Graph Editor

Browser check: Artifact runtime error. This is a technical check, not a quality review.

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 17,444 bytes · SHA-256 c58b32fb515d
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Logic Loom — Node Graph Editor</title>
<style>
:root{--bg:#080c14;--panel:#101723;--panel2:#151e2d;--line:#26354a;--text:#dce8f7;--muted:#71829a;--cyan:#5ee7ff;--orange:#ffae62;--green:#70f0a0;--red:#ff5f70;--purple:#ae9cff}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:var(--bg);color:var(--text);font:13px Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}#app{height:100%;display:flex;position:relative}.sidebar{width:250px;min-width:250px;background:linear-gradient(180deg,#111a29,#0c121e);border-right:1px solid #26354a;padding:22px 18px;z-index:5;box-shadow:8px 0 30px #0005}.brand{display:flex;align-items:center;gap:10px;margin-bottom:26px}.mark{width:30px;height:30px;border:2px solid var(--cyan);border-radius:9px;position:relative;box-shadow:0 0 18px #5ee7ff44}.mark:after,.mark:before{content:'';position:absolute;background:var(--orange);width:6px;height:6px;border-radius:50%;right:-4px}.mark:after{top:5px}.mark:before{bottom:5px}.brand b{font-size:17px;letter-spacing:.12em}.brand small{display:block;color:var(--muted);font-size:9px;letter-spacing:.2em;margin-top:2px}.section-title{font-size:10px;letter-spacing:.16em;color:#8094ad;text-transform:uppercase;margin:20px 0 10px}.palette{display:grid;grid-template-columns:1fr 1fr;gap:8px}.add{background:#182437;border:1px solid #2b3d57;border-radius:9px;padding:10px 8px;color:#c7d7ea;text-align:left;cursor:pointer;transition:.15s;display:flex;gap:8px;align-items:center}.add:hover{transform:translateY(-2px);border-color:var(--cyan);background:#1e3048}.add i{font-style:normal;width:23px;height:23px;border-radius:6px;display:grid;place-items:center;font-weight:800;font-size:11px;background:#233c50;color:var(--cyan)}.add.gate i{background:#352d51;color:var(--purple)}.add.out i{background:#4a3524;color:var(--orange)}.hint{color:var(--muted);line-height:1.65;font-size:11px;background:#0c1421;border:1px solid #1f2c3f;border-radius:10px;padding:12px;margin-top:20px}.hint kbd{color:#dce8f7;background:#1c293b;padding:2px 5px;border-radius:4px;border:1px solid #34445b}.actions{display:flex;gap:7px;margin-top:14px}.action{flex:1;background:#182437;border:1px solid #2b3d57;border-radius:7px;color:#bcd0e7;padding:8px 4px;cursor:pointer;font-size:11px}.action:hover{border-color:var(--orange);color:white}.status{position:absolute;bottom:16px;left:18px;right:18px;color:#6c819c;font-size:10px;display:flex;align-items:center;gap:7px}.dot{width:6px;height:6px;border-radius:50%;background:var(--green);box-shadow:0 0 9px var(--green)}#view{position:relative;flex:1;overflow:hidden;cursor:grab}#view.dragging{cursor:grabbing}canvas{display:block;width:100%;height:100%}.topbar{position:absolute;top:16px;left:18px;right:18px;display:flex;align-items:center;justify-content:space-between;pointer-events:none}.crumb{color:#7d8fa8;font-size:11px;background:#0e1724dd;border:1px solid #243349;border-radius:8px;padding:8px 11px;backdrop-filter:blur(8px)}.zoom{background:#0e1724dd;border:1px solid #243349;border-radius:8px;padding:8px 11px;color:#a8bbd1;font-size:11px}.toast{position:absolute;top:64px;left:50%;transform:translate(-50%,-10px);opacity:0;transition:.2s;background:#3a1822;border:1px solid var(--red);color:#ffb2bd;padding:9px 15px;border-radius:7px;pointer-events:none}.toast.show{opacity:1;transform:translate(-50%,0)}
</style></head><body><div id="app"><aside class="sidebar"><div class="brand"><div class="mark"></div><div><b>LOGIC LOOM</b><small>LIVE CIRCUIT LAB</small></div></div><div class="section-title">Add component</div><div class="palette"><button class="add" data-add="toggle"><i>◐</i>Toggle</button><button class="add" data-add="clock"><i>◷</i>Clock</button><button class="add gate" data-add="and"><i>&amp;</i>AND</button><button class="add gate" data-add="or"><i>≥1</i>OR</button><button class="add gate" data-add="not"><i>¬</i>NOT</button><button class="add gate" data-add="xor"><i>⊕</i>XOR</button><button class="add gate" data-add="nand"><i>⊼</i>NAND</button><button class="add gate" data-add="nor"><i>⊽</i>NOR</button><button class="add out" data-add="lamp"><i>☼</i>Lamp</button><button class="add out" data-add="display"><i>▦</i>Hex</button></div><div class="section-title">Workspace</div><div class="actions"><button class="action" id="demo">Load demo</button><button class="action" id="clear">Clear</button></div><div class="hint"><b>Quick guide</b><br>Drag empty space to pan · wheel to zoom<br>Drag <span style="color:var(--orange)">output</span> ports into <span style="color:var(--cyan)">input</span> ports<br>Click toggles · <kbd>Delete</kbd> removes selection</div><div class="status"><span class="dot"></span><span id="statusText">SIMULATION LIVE</span></div></aside><main id="view"><canvas id="canvas"></canvas><div class="topbar"><div class="crumb">WORKSPACE / <b>UNTITLED CIRCUIT</b></div><div class="zoom" id="zoomText">100%</div></div><div class="toast" id="toast">Feedback loop rejected</div></main></div>
<script>
/* === TUNABLE CONSTANTS === */
const C={minZoom:.5,maxZoom:2,portR:7,hit:14,nodeW:154,nodeH:78,gap:24,grid:34,colors:{bg:'#080c14',grid:'#1b2a3d',wire:'#50617a',hot:'#ffae62',in:'#5ee7ff',on:'#70f0a0',gate:'#ae9cff',text:'#dce8f7',muted:'#71829a'}};
/* === DATA MODEL === */
const TYPES={toggle:{label:'TOGGLE',inputs:0,outputs:1,kind:'input'},clock:{label:'CLOCK',inputs:0,outputs:1,kind:'input'},and:{label:'AND',inputs:2,outputs:1,kind:'gate'},or:{label:'OR',inputs:2,outputs:1,kind:'gate'},not:{label:'NOT',inputs:1,outputs:1,kind:'gate'},xor:{label:'XOR',inputs:2,outputs:1,kind:'gate'},nand:{label:'NAND',inputs:2,outputs:1,kind:'gate'},nor:{label:'NOR',inputs:2,outputs:1,kind:'gate'},lamp:{label:'LAMP',inputs:1,outputs:0,kind:'output'},display:{label:'HEX DISPLAY',inputs:4,outputs:0,kind:'output'}};
let scene={nodes:[],wires:[]}, selected=null, zoom=1, pan={x:0,y:0}, nextId=1, drag=null, wireDrag=null, pulse=0, flash=0;
const canvas=document.querySelector('#canvas'),ctx=canvas.getContext('2d'),view=document.querySelector('#view');
const id=()=> 'n'+(nextId++); const nodeBy=id=>scene.nodes.find(n=>n.id===id); const typeOf=n=>TYPES[n.type];
function makeNode(type,x,y,state=false){return {id:id(),type,x,y,state,rate: type==='clock'?1:undefined,inputs:Array(TYPES[type].inputs).fill(false),outputs:Array(TYPES[type].outputs).fill(false)}}
function demoScene(){let a=makeNode('toggle',90,145),b=makeNode('toggle',90,315),g=makeNode('and',330,225),n=makeNode('not',570,225),l=makeNode('lamp',800,225); scene={nodes:[a,b,g,n,l],wires:[{from:a.id,fromPort:0,to:g.id,toPort:0},{from:b.id,fromPort:0,to:g.id,toPort:1},{from:g.id,fromPort:0,to:n.id,toPort:0},{from:n.id,fromPort:0,to:l.id,toPort:0}]}; evaluate(); save()}
function save(){localStorage.setItem('logic-loom',JSON.stringify(scene))}function load(){try{let x=JSON.parse(localStorage.getItem('logic-loom'));if(x&&Array.isArray(x.nodes)){scene=x;nextId=scene.nodes.reduce((m,n)=>Math.max(m,parseInt(n.id.slice(1))||0),0)+1;evaluate();return}}catch(e){}demoScene()}
/* === COORDINATES / HIT TEST === */
function resize(){canvas.width=view.clientWidth*devicePixelRatio;canvas.height=view.clientHeight*devicePixelRatio;canvas.style.width=view.clientWidth+'px';canvas.style.height=view.clientHeight+'px';ctx.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);draw()}window.addEventListener('resize',resize);
function screenToWorld(x,y){return{x:(x-pan.x)/zoom,y:(y-pan.y)/zoom}}function worldToScreen(x,y){return{x:x*zoom+pan.x,y:y*zoom+pan.y}}
function ports(n){let t=typeOf(n), ins=[],outs=[];for(let i=0;i<t.inputs;i++)ins.push({x:n.x,y:n.y+29+(i+.5)*((C.nodeH-31)/t.inputs),i,side:'in',n});for(let i=0;i<t.outputs;i++)outs.push({x:n.x+C.nodeW,y:n.y+29+(i+.5)*((C.nodeH-31)/t.outputs),i,side:'out',n});return{ins,outs}}
function portAt(w){for(let n of [...scene.nodes].reverse()){let p=ports(n);for(let q of [...p.ins,...p.outs])if(Math.hypot(w.x-q.x,w.y-q.y)<C.hit/zoom)return q}return null}
function nodeAt(w){return [...scene.nodes].reverse().find(n=>w.x>=n.x&&w.x<=n.x+C.nodeW&&w.y>=n.y&&w.y<=n.y+C.nodeH)}
/* === EVALUATION === */
function evaluate(){scene.nodes.forEach(n=>n.inputs=Array(typeOf(n).inputs).fill(false));let incoming=new Map;scene.wires.forEach(w=>{let s=nodeBy(w.from),t=nodeBy(w.to);if(s&&t) {incoming.set(t.id+':'+w.toPort,w);t.inputs[w.toPort]=!!s.outputs?.[w.fromPort]}});let indeg=new Map(scene.nodes.map(n=>[n.id,0]));scene.wires.forEach(w=>indeg.set(w.to,(indeg.get(w.to)||0)+1));let q=scene.nodes.filter(n=>!indeg.get(n.id)),seen=0;while(q.length){let n=q.shift();seen++;let ins=n.inputs||[];if(n.type==='toggle')n.outputs=[!!n.state];else if(n.type==='clock')n.outputs=[!!n.state];else if(n.type==='and')n.outputs=[!!ins[0]&&!!ins[1]];else if(n.type==='or')n.outputs=[!!ins[0]||!!ins[1]];else if(n.type==='not')n.outputs=[!ins[0]];else if(n.type==='xor')n.outputs=[!!ins[0]!==!!ins[1]];else if(n.type==='nand')n.outputs=[!(!!ins[0]&&!!ins[1])];else if(n.type==='nor')n.outputs=[!(!!ins[0]||!!ins[1])];else n.outputs=[];scene.wires.filter(w=>w.from===n.id).forEach(w=>{let t=nodeBy(w.to);if(t){t.inputs[w.toPort]=!!n.outputs[w.fromPort];indeg.set(t.id,indeg.get(t.id)-1);if(indeg.get(t.id)===0)q.push(t)}})}draw()}
function createsCycle(from,to){let todo=[to],seen=new Set;while(todo.length){let x=todo.pop();if(x===from)return true;if(seen.has(x))continue;seen.add(x);scene.wires.filter(w=>w.from===x).forEach(w=>todo.push(w.to))}return false}
/* === RENDERING === */
function draw(){let w=view.clientWidth,h=view.clientHeight;ctx.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);ctx.clearRect(0,0,w,h);ctx.fillStyle=C.colors.bg;ctx.fillRect(0,0,w,h);let step=C.grid*zoom;ctx.strokeStyle=C.colors.grid;ctx.globalAlpha=.65;ctx.lineWidth=1;let ox=((pan.x%step)+step)%step,oy=((pan.y%step)+step)%step;ctx.beginPath();for(let x=ox;x<w;x+=step){ctx.moveTo(x,0);ctx.lineTo(x,h)}for(let y=oy;y<h;y+=step){ctx.moveTo(0,y);ctx.lineTo(w,y)}ctx.stroke();ctx.globalAlpha=1;scene.wires.forEach(w=>drawWire(w));if(wireDrag)drawPreview(wireDrag);scene.nodes.forEach(drawNode);document.querySelector('#zoomText').textContent=Math.round(zoom*100)+'%'}
function curve(a,b){let A=worldToScreen(a.x,a.y),B=worldToScreen(b.x,b.y),dx=Math.max(45*zoom,Math.abs(B.x-A.x)*.45);return[A,B,dx]}
function drawWire(w){let a=nodeBy(w.from),b=nodeBy(w.to);if(!a||!b)return;let A=ports(a).outs[w.fromPort],B=ports(b).ins[w.toPort];if(!A||!B)return;let [sa,sb,d]=curve(A,B),on=!!a.outputs?.[w.fromPort];ctx.beginPath();ctx.moveTo(sa.x,sa.y);ctx.bezierCurveTo(sa.x+d,sa.y,sb.x-d,sb.y,sb.x,sb.y);ctx.strokeStyle=on?C.colors.on:C.colors.wire;ctx.lineWidth=on?3:2;ctx.shadowColor=on?C.colors.on:'transparent';ctx.shadowBlur=on?10:0;ctx.stroke();ctx.shadowBlur=0}
function drawPreview(w){let A=worldToScreen(w.from.x,w.from.y),B=worldToScreen(w.to.x,w.to.y),d=Math.max(45*zoom,Math.abs(B.x-A.x)*.45);ctx.beginPath();ctx.moveTo(A.x,A.y);ctx.bezierCurveTo(A.x+d,A.y,B.x-d,B.y,B.x,B.y);ctx.strokeStyle=w.valid?C.colors.hot:C.colors.red;ctx.setLineDash([7,5]);ctx.lineWidth=2;ctx.stroke();ctx.setLineDash([])}
function drawNode(n){let p=worldToScreen(n.x,n.y),w=C.nodeW*zoom,h=C.nodeH*zoom,t=typeOf(n),on=n.type==='display'?n.inputs.some(Boolean):n.type==='lamp'?n.inputs[0]:n.outputs?.[0];ctx.save();ctx.shadowColor=selected===n.id?'#5ee7ff66':'#0008';ctx.shadowBlur=selected===n.id?18:10;ctx.shadowOffsetY=selected===n.id?2:4;ctx.fillStyle='#162131';round(p.x,p.y,w,h,10);ctx.fill();ctx.shadowBlur=0;ctx.strokeStyle=selected===n.id?C.colors.in:'#2a3b52';ctx.lineWidth=selected===n.id?2:1;ctx.stroke();ctx.fillStyle=t.kind==='gate'?C.colors.gate:t.kind==='output'?C.colors.hot:C.colors.in;ctx.fillRect(p.x,p.y,w,4*zoom);ctx.fillStyle=C.colors.text;ctx.font=`700 ${Math.max(10,11*zoom)}px system-ui`;ctx.fillText(t.label,p.x+12*zoom,p.y+22*zoom);ctx.fillStyle=on?C.colors.on:C.colors.muted;ctx.font=`600 ${Math.max(9,9*zoom)}px system-ui`;ctx.fillText(on?'● ON':'○ OFF',p.x+12*zoom,p.y+42*zoom);if(n.type==='toggle'){ctx.fillStyle=on?C.colors.on:'#38485d';round(p.x+w-38*zoom,p.y+29*zoom,24*zoom,13*zoom,8);ctx.fill();ctx.fillStyle=on?'#10251c':'#8fa0b5';ctx.beginPath();ctx.arc(p.x+(on? w-21:w-31)*zoom,p.y+35.5*zoom,5*zoom,0,7);ctx.fill()}if(n.type==='clock'){ctx.fillStyle='#91a5bd';ctx.font=`${Math.max(8,9*zoom)}px system-ui`;ctx.fillText((n.rate||1)+' Hz',p.x+w-43*zoom,p.y+42*zoom)}let ps=ports(n);ps.ins.forEach(q=>drawPort(q,C.colors.in));ps.outs.forEach(q=>drawPort(q,C.colors.hot));if(n.type==='lamp'){ctx.beginPath();ctx.arc(p.x+w-26*zoom,p.y+42*zoom,9*zoom,0,7);ctx.fillStyle=on?'#ffd271':'#394657';ctx.shadowColor=on?'#ffb84d':'transparent';ctx.shadowBlur=on?18:0;ctx.fill();ctx.shadowBlur=0}if(n.type==='display'){ctx.fillStyle='#081019';round(p.x+w-45*zoom,p.y+20*zoom,31*zoom,42*zoom,4);ctx.fill();ctx.fillStyle=on?C.colors.orange:'#26384c';ctx.font=`700 ${27*zoom}px monospace`;let v=n.inputs.reduce((x,b,i)=>x+(b?(1<<i):0),0);ctx.fillText(v.toString(16).toUpperCase(),p.x+w-37*zoom,p.y+50*zoom)}ctx.restore()}
function drawPort(q,col){let p=worldToScreen(q.x,q.y),active=q.side==='in'?q.n.inputs?.[q.i]:q.n.outputs?.[q.i];ctx.beginPath();ctx.arc(p.x,p.y,(C.portR+(active?2:0))*zoom,0,7);ctx.fillStyle=active?(q.side==='in'?C.colors.in:C.colors.on):'#182435';ctx.fill();ctx.strokeStyle=col;ctx.lineWidth=2;ctx.stroke()}
function round(x,y,w,h,r){ctx.beginPath();ctx.roundRect(x,y,w,h,r)}
/* === INTERACTION === */
function pos(e){let r=canvas.getBoundingClientRect();return{x:e.clientX-r.left,y:e.clientY-r.top}}
canvas.addEventListener('pointerdown',e=>{let s=pos(e),w=screenToWorld(s.x,s.y),p=portAt(w),n=nodeAt(w);canvas.setPointerCapture(e.pointerId);if(p&&p.side==='out'){wireDrag={from:p,to:{x:w.x,y:w.y},valid:false};return}if(p&&p.side==='in'){let old=scene.wires.find(x=>x.to===p.n.id&&x.toPort===p.i);if(old){scene.wires=scene.wires.filter(x=>x!==old);let src=nodeBy(old.from);wireDrag={from:ports(src).outs[old.fromPort],to:{x:w.x,y:w.y},valid:false};evaluate();return}}if(n){selected=n.id;if(n.type==='toggle'&&w.x>n.x+95&&w.y>n.y+25){n.state=!n.state;evaluate();save();return}if(n.type==='clock'&&w.x>n.x+90&&w.y>n.y+25){n.rate=n.rate>=4?0.5:n.rate+0.5;save();return}drag={n,dx:w.x-n.x,dy:w.y-n.y};draw();return}selected=null;drag={panX:pan.x,panY:pan.y,sx:s.x,sy:s.y};view.classList.add('dragging')});
canvas.addEventListener('pointermove',e=>{let s=pos(e),w=screenToWorld(s.x,s.y);if(wireDrag){wireDrag.to=w;let p=portAt(w);wireDrag.target=p&&p.side==='in'?p:null;wireDrag.valid=!!wireDrag.target&&wireDrag.target.n.id!==wireDrag.from.n.id&&!createsCycle(wireDrag.from.n.id,wireDrag.target.n.id);draw();return}if(drag?.n){drag.n.x=w.x-drag.dx;drag.n.y=w.y-drag.dy;draw()}else if(drag){pan.x=drag.panX+s.x-drag.sx;pan.y=drag.panY+s.y-drag.sy;draw()}});
canvas.addEventListener('pointerup',e=>{if(wireDrag){let p=wireDrag.target;if(wireDrag.valid&&p){scene.wires=scene.wires.filter(w=>!(w.to===p.n.id&&w.toPort===p.i));scene.wires.push({from:wireDrag.from.n.id,fromPort:wireDrag.from.i,to:p.n.id,toPort:p.i});evaluate();save()}else if(p&&createsCycle(wireDrag.from.n.id,p.n.id)){flash=1;showToast('Feedback loop rejected');}wireDrag=null;draw()}if(drag?.n)save();drag=null;view.classList.remove('dragging')});
view.addEventListener('wheel',e=>{e.preventDefault();let s=pos(e),before=screenToWorld(s.x,s.y),factor=e.deltaY<0?1.12:.89, nz=Math.max(C.minZoom,Math.min(C.maxZoom,zoom*factor));pan.x=s.x-before.x*nz;pan.y=s.y-before.y*nz;zoom=nz;draw()},{passive:false});
document.addEventListener('keydown',e=>{if((e.key==='Delete'||e.key==='Backspace')&&selected){scene.nodes=scene.nodes.filter(n=>n.id!==selected);scene.wires=scene.wires.filter(w=>w.from!==selected&&w.to!==selected);selected=null;evaluate();save()} });
/* === UI / CLOCKS === */
document.querySelectorAll('[data-add]').forEach(b=>b.onclick=()=>{let t=b.dataset.add,n=makeNode(t,screenToWorld(view.clientWidth/2,view.clientHeight/2).x-C.nodeW/2,screenToWorld(view.clientWidth/2,view.clientHeight/2).y-C.nodeH/2);scene.nodes.push(n);selected=n.id;evaluate();save()});document.querySelector('#clear').onclick=()=>{scene={nodes:[],wires:[]};selected=null;save();draw()};document.querySelector('#demo').onclick=()=>{demoScene();selected=null};function showToast(s){let t=document.querySelector('#toast');t.textContent=s;t.classList.add('show');setTimeout(()=>t.classList.remove('show'),900)}setInterval(()=>{scene.nodes.filter(n=>n.type==='clock').forEach(n=>{n.state=!n.state});if(scene.nodes.some(n=>n.type==='clock')){evaluate();save()}},500);
/* === BOOT === */
load();resize();window.editor={scene,addNode:(t,x,y)=>{let n=makeNode(t,x,y);scene.nodes.push(n);evaluate();save();return n.id},connect:(a,ap,b,bp)=>{let sn=nodeBy(a),tn=nodeBy(b);if(!sn||!tn||!ports(sn).outs[ap]||!ports(tn).ins[bp]||createsCycle(a,b))return false;scene.wires=scene.wires.filter(w=>!(w.to===b&&w.toPort===bp));scene.wires.push({from:a,fromPort:ap,to:b,toPort:bp});evaluate();save();return true},evaluate,screenToWorld,worldToScreen};
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-5.6-luna-pro","provider":"openrouter","persona":"full","sessionId":"b0d95f34-9a5a-4215-88d1-8598be25d255","tokensIn":718087,"tokensOut":35454,"tokensTotal":753541,"turns":15,"toolCalls":14,"failedToolCalls":0,"timestamp":"2026-07-31T01:24:44.991Z"} -->