Spreadsheet
GPT 5.6 Luna Pro · full

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,132 bytes · SHA-256 43f9c032045b
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Gridline Spreadsheet</title>
<style>
/* Tunable visual constants */
:root{--blue:#1a73e8;--blue-soft:#e8f0fe;--line:#dfe3e8;--header:#f8f9fa;--text:#202124;--muted:#687078;--error:#c5221f;--cell-w:106px;--cell-h:28px}
*{box-sizing:border-box}body{margin:0;color:var(--text);font:13px Arial,Helvetica,sans-serif;background:#fff;overflow:hidden}
.app{height:100vh;display:flex;flex-direction:column}.topbar{height:56px;display:flex;align-items:center;padding:0 18px;border-bottom:1px solid var(--line);gap:16px;flex:none}.brand{font-size:19px;font-weight:600;color:#3c4043;letter-spacing:-.2px;white-space:nowrap}.brand b{color:var(--blue)}
.toolbar{height:48px;display:flex;align-items:center;padding:0 14px;gap:6px;border-bottom:1px solid var(--line);background:#fff;flex:none}.toolbar button,.menu button{border:0;background:transparent;color:#3c4043;border-radius:4px;padding:7px 9px;cursor:pointer;font-size:12px}.toolbar button:hover,.menu button:hover{background:#f1f3f4}.toolbar .primary{background:var(--blue);color:#fff}.toolbar .primary:hover{background:#1765cc}.sep{width:1px;height:24px;background:var(--line);margin:0 6px}.hint{margin-left:auto;color:var(--muted);font-size:11px}
.formula-row{height:42px;display:flex;align-items:center;padding:6px 14px;border-bottom:1px solid var(--line);gap:9px;flex:none}.name-box{width:67px;height:29px;border:1px solid #c9cdd1;border-radius:3px;text-align:center;padding:7px 4px;font-weight:600;background:#fff}.fx{font-size:16px;color:#7b8187;font-family:Georgia,serif}.formula-input{height:29px;flex:1;border:1px solid #c9cdd1;border-radius:3px;padding:5px 9px;font-family:Consolas,monospace;font-size:13px;outline:none}.formula-input:focus{border-color:var(--blue);box-shadow:0 0 0 1px var(--blue)}
.sheet-wrap{flex:1;overflow:auto;position:relative}.grid{border-collapse:separate;border-spacing:0;table-layout:fixed}.grid th,.grid td{height:var(--cell-h);min-width:var(--cell-w);width:var(--cell-w);padding:0 8px;border-right:1px solid var(--line);border-bottom:1px solid var(--line);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;position:relative}.grid thead th{position:sticky;top:0;z-index:3;background:var(--header);text-align:center;font-weight:600;color:#5f6368}.grid thead th.corner{left:0;z-index:5;min-width:48px;width:48px}.grid tbody th{position:sticky;left:0;z-index:2;background:var(--header);min-width:48px;width:48px;text-align:center;font-weight:600;color:#5f6368}.grid td{background:#fff;cursor:cell}.grid td.selected{outline:2px solid var(--blue);outline-offset:-2px;z-index:1}.grid td.active{box-shadow:inset 0 0 0 1px #fff}.grid td.numeric{text-align:right}.grid td.error{color:var(--error);font-weight:600}.grid td.editing{padding:0}.cell-editor{border:0;outline:2px solid var(--blue);outline-offset:-2px;width:100%;height:100%;padding:0 8px;font:13px Arial;background:#fff}
.context{position:fixed;background:#fff;border:1px solid #dadce0;box-shadow:0 3px 12px #0002;border-radius:4px;z-index:20;padding:5px 0;min-width:175px}.context button{display:block;width:100%;text-align:left;padding:8px 14px}.status{height:25px;border-top:1px solid var(--line);color:#687078;padding:5px 14px;font-size:11px;flex:none}.toast{position:fixed;bottom:40px;left:50%;transform:translateX(-50%);background:#323232;color:#fff;padding:9px 15px;border-radius:4px;display:none;z-index:30}.toast.show{display:block}
</style>
</head>
<body>
<div class="app">
<div class="topbar"><div class="brand"><b>▦</b> Gridline</div><div style="color:#687078">Untitled spreadsheet</div></div>
<div class="toolbar"><button class="primary" id="newBtn">New</button><button id="clearBtn">Clear</button><span class="sep"></span><button id="insertRow">+ Row</button><button id="deleteRow">− Row</button><button id="insertCol">+ Column</button><button id="deleteCol">− Column</button><span class="sep"></span><button id="saveBtn">Save</button><span class="hint">Enter / Tab to commit · Double-click to edit · Right-click headers for more</span></div>
<div class="formula-row"><div class="name-box" id="nameBox">A1</div><div class="fx">ƒx</div><input class="formula-input" id="formulaInput" spellcheck="false" autocomplete="off"></div>
<div class="sheet-wrap" id="sheetWrap"><table class="grid" id="grid"><thead><tr id="headRow"><th class="corner">◢</th></tr></thead><tbody id="body"></tbody></table></div>
<div class="status" id="status">Ready · 26 columns × 100 rows</div>
</div><div class="context" id="context" hidden></div><div class="toast" id="toast"></div>
<script>
/* ==================== Tunable constants ==================== */
const ROWS=100,COLS=26,STORE_KEY='gridline-sheet-v1';
const ERR={REF:'#REF!',DIV:'#DIV/0!',PARSE:'#ERR!',CYCLE:'#CYCLE!'};
/* ==================== State ==================== */
let cells=Array.from({length:ROWS},()=>Array(COLS).fill('')), values=Array.from({length:ROWS},()=>Array(COLS).fill(''));
let selected={r:0,c:0}, editCell=null, dependency=new Map(), dependents=new Map();
const $=id=>document.getElementById(id), grid=$('grid'), body=$('body'), head=$('headRow'), formula=$('formulaInput'), nameBox=$('nameBox');
function colName(c){let s='';for(c++;c;c=Math.floor((c-1)/26))s=String.fromCharCode(65+(c-1)%26)+s;return s}
function addr(r,c){return colName(c)+(r+1)} function parseAddr(s){let m=/^\$?([A-Z]{1,2})\$?(\d+)$/.exec(s.toUpperCase());if(!m)return null;let c=0;for(const x of m[1])c=c*26+x.charCodeAt(0)-64; c--;let r=+m[2]-1;return r>=0&&r<ROWS&&c>=0&&c<COLS?{r,c}:null}
function esc(s){return String(s).replace(/[&<>"']/g,x=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[x]))}
/* ==================== Rendering ==================== */
function buildGrid(){for(let c=0;c<COLS;c++){let th=document.createElement('th');th.textContent=colName(c);th.dataset.c=c;head.appendChild(th)}for(let r=0;r<ROWS;r++){let tr=document.createElement('tr'),th=document.createElement('th');th.textContent=r+1;th.dataset.r=r;tr.appendChild(th);for(let c=0;c<COLS;c++){let td=document.createElement('td');td.dataset.r=r;td.dataset.c=c;tr.appendChild(td)}body.appendChild(tr)}grid.addEventListener('mousedown',onMouse);grid.addEventListener('dblclick',e=>{let td=e.target.closest('td');if(td)startEdit(+td.dataset.r,+td.dataset.c)});grid.addEventListener('contextmenu',onContext)}
function render(){for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){let td=body.rows[r].cells[c+1],v=values[r][c];td.textContent=v;td.className=(v!==''&&!isNaN(Number(v))?'numeric ':'')+(String(v).startsWith('#')?'error ':'')+((selected.r===r&&selected.c===c)?'selected active':'')}nameBox.textContent=addr(selected.r,selected.c);if(editCell===null)formula.value=cells[selected.r][selected.c]||'';$('status').textContent=`Ready · ${ROWS} rows × ${COLS} columns · ${addr(selected.r,selected.c)} selected`}
function onMouse(e){let td=e.target.closest('td');if(td){select(+td.dataset.r,+td.dataset.c);return}let th=e.target.closest('th');if(th&&!th.classList.contains('corner')){if(th.parentElement.parentElement.tagName==='THEAD')select(0,+th.dataset.c);else select(+th.dataset.r,0)}}
function select(r,c){if(editCell!==null)commitEdit();selected={r:Math.max(0,Math.min(ROWS-1,r)),c:Math.max(0,Math.min(COLS-1,c))};render()}
function startEdit(r=selected.r,c=selected.c){if(editCell!==null)commitEdit();selected={r,c};editCell={r,c};let td=body.rows[r].cells[c+1],raw=cells[r][c]||'';td.classList.add('editing');td.innerHTML='';let inp=document.createElement('input');inp.className='cell-editor';inp.value=raw;td.appendChild(inp);inp.focus();inp.setSelectionRange(inp.value.length,inp.value.length);inp.onkeydown=e=>{if(e.key==='Enter'||e.key==='Tab'){e.preventDefault();let dir=e.key==='Enter'?[1,0]:[0,1];commitEdit();select(r+dir[0],c+dir[1])}else if(e.key==='Escape'){editCell=null;render()}};inp.onblur=()=>{if(editCell)commitEdit()}}
function commitEdit(){if(editCell===null)return;let {r,c}=editCell,inp=body.rows[r].cells[c+1].querySelector('input');if(inp)setRaw(r,c,inp.value);editCell=null;render()}
formula.addEventListener('focus',()=>{});formula.addEventListener('keydown',e=>{if(e.key==='Enter'){e.preventDefault();setRaw(selected.r,selected.c,formula.value);formula.blur();render()}else if(e.key==='Escape'){formula.value=cells[selected.r][selected.c]||'';formula.blur()}});formula.addEventListener('input',()=>{cells[selected.r][selected.c]=formula.value;recalculate();render()});
/* ==================== Formula tokenizer/parser ==================== */
function tokenize(s){let a=[],i=0;while(i<s.length){let ch=s[i];if(/\s/.test(ch)){i++;continue}if(s.slice(i,i+5)==='#REF!'){a.push({t:'badref'});i+=5;continue}if(/[+\-*/(),:]/.test(ch)){a.push({t:ch});i++;continue}let m=s.slice(i).match(/^\$?[A-Za-z]{1,2}\$?\d+/);if(m){a.push({t:'ref',v:m[0]});i+=m[0].length;continue}m=s.slice(i).match(/^\d+(?:\.\d+)?(?:[eE][+\-]?\d+)?/);if(m){a.push({t:'num',v:+m[0]});i+=m[0].length;continue}m=s.slice(i).match(/^[A-Za-z_][A-Za-z_0-9]*/);if(m){a.push({t:'id',v:m[0].toUpperCase()});i+=m[0].length;continue}throw Error('token')};a.push({t:'EOF'});return a}
function parseFormula(src){let ts=tokenize(src.slice(1)),p=0,refs=[];function peek(t){return ts[p].t===t}function eat(t){if(!peek(t))throw Error('parse');return ts[p++]}function expr(){let x=term();while(peek('+')||peek('-')){let op=ts[p++].t,y=term();x={k:'bin',op,a:x,b:y}}return x}function term(){let x=unary();while(peek('*')||peek('/')){let op=ts[p++].t,y=unary();x={k:'bin',op,a:x,b:y}}return x}function unary(){if(peek('-')){eat('-');return{k:'neg',a:unary()}}return primary()}function primary(){if(peek('num'))return{k:'lit',v:eat('num').v};if(peek('badref')){eat('badref');return{k:'ref',a:null}}if(peek('ref')){let x=ref();if(peek(':')){eat(':');let y=ref();return{k:'range',a:x,b:y}}return x}if(peek('id')){let id=eat('id').v;if(!peek('('))throw Error('id');eat('(');let args=[];if(!peek(')')){do{args.push(expr());if(peek(','))eat(',');else break}while(true)}eat(')');if(!['SUM','AVG','AVERAGE','MIN','MAX','COUNT'].includes(id))throw Error('fn');return{k:'fn',id,args}}if(peek('(')){eat('(');let x=expr();eat(')');return x}throw Error('primary')}function ref(){let raw=eat('ref').v,a=parseAddr(raw.replace(/\$/g,''));if(a)refs.push(a);return{k:'ref',a}}let ast=expr();if(!peek('EOF'))throw Error('tail');return{ast,refs}}
function expandRange(a,b){let out=[];let r1=Math.min(a.r,b.r),r2=Math.max(a.r,b.r),c1=Math.min(a.c,b.c),c2=Math.max(a.c,b.c);for(let r=r1;r<=r2;r++)for(let c=c1;c<=c2;c++)out.push({r,c});return out}
/* ==================== Dependency graph + recalculation ==================== */
function setRaw(r,c,raw){cells[r][c]=raw;save();recalculate()}
function rebuildGraph(){dependency=new Map();dependents=new Map();for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){let raw=cells[r][c],key=addr(r,c),refs=[];if(raw&&raw[0]==='='){try{let parsed=parseFormula(raw);refs=parsed.refs; // ranges are added by AST traversal below
let all=[];(function walk(n){if(n.k==='ref'){if(n.a)all.push(n.a)}else if(n.k==='range'){if(n.a&&n.b)all.push(...expandRange(n.a,n.b))}else if(n.k==='bin'){walk(n.a);walk(n.b)}else if(n.k==='neg')walk(n.a);else if(n.k==='fn')n.args.forEach(walk)})(parsed.ast);refs=all}catch(e){}}dependency.set(key,refs);refs.forEach(a=>{let q=addr(a.r,a.c);if(!dependents.has(q))dependents.set(q,new Set);dependents.get(q).add(key)})}}
function recalculate(){rebuildGraph();let state=new Map(),stack=[],cycleKeys=new Set();function calc(r,c){let key=addr(r,c);if(state.get(key)==='doing'){let i=stack.indexOf(key);if(i>=0)stack.slice(i).forEach(k=>cycleKeys.add(k));return ERR.CYCLE}if(state.get(key)==='done')return values[r][c];state.set(key,'doing');stack.push(key);let raw=cells[r][c],out='';if(raw==='')out='';else if(raw[0]!=='=')out=raw;else{try{let {ast}=parseFormula(raw);out=evalNode(ast)}catch(e){out=String(e.message||'').startsWith('#')?e.message:ERR.PARSE}}values[r][c]=out;stack.pop();state.set(key,'done');return out;function evalNode(n){if(n.k==='lit')return n.v;if(n.k==='ref'){if(!n.a)return ERR.REF;let v=calc(n.a.r,n.a.c);if(String(v).startsWith('#'))throw Error(v);return v===''?0:(isNaN(Number(v))?0:Number(v))}if(n.k==='range'){if(!n.a||!n.b)throw Error(ERR.REF);return expandRange(n.a,n.b).map(a=>{let v=calc(a.r,a.c);if(String(v).startsWith('#'))throw Error(v);return v===''?0:(isNaN(Number(v))?null:Number(v))})}if(n.k==='neg')return -Number(evalNode(n.a));if(n.k==='bin'){let a=Number(evalNode(n.a)),b=Number(evalNode(n.b));if(n.op==='/'&&b===0)throw Error(ERR.DIV);return n.op==='+'?a+b:n.op==='-'?a-b:n.op==='*'?a*b:a/b}if(n.k==='fn'){let vals=n.args.flatMap(x=>{let v=evalNode(x);return Array.isArray(v)?v:[v]});let nums=vals.filter(v=>typeof v==='number'&&!isNaN(v));if(n.id==='COUNT')return nums.length;if(n.id==='SUM')return nums.reduce((a,b)=>a+b,0);if(n.id==='AVG'||n.id==='AVERAGE')return nums.length?nums.reduce((a,b)=>a+b,0)/nums.length:0;if(n.id==='MIN')return nums.length?Math.min(...nums):0;if(n.id==='MAX')return nums.length?Math.max(...nums):0}}}
// second pass marks all members of detected cycles, including the initiating cell
for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++)if(state.get(addr(r,c))!=='done')calc(r,c);cycleKeys.forEach(k=>{let a=parseAddr(k);values[a.r][a.c]=ERR.CYCLE});let changed=true;while(changed){changed=false;for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){let k=addr(r,c);if(values[r][c]===ERR.CYCLE||dependents.get(k)?.has(k)){if(values[r][c]!==ERR.CYCLE){values[r][c]=ERR.CYCLE;changed=true}}}}}
/* ==================== Persistence and structural operations ==================== */
function save(){try{localStorage.setItem(STORE_KEY,JSON.stringify(cells))}catch(e){}}
function load(){try{let x=JSON.parse(localStorage.getItem(STORE_KEY));if(Array.isArray(x))for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++)cells[r][c]=x[r]?.[c]??''}catch(e){}}
function rewrite(raw,kind,index){if(!raw||raw[0]!=='=')return raw;return raw.replace(/(\$?)([A-Za-z]{1,2})(\$?)(\d+)/g,(m,ca,letters,ra,num)=>{let a=parseAddr(letters+num);if(!a)return m;let r=a.r,c=a.c;if(kind==='row'){if(index<r)r+=1;else if(index===r&&false)r+=1}else{if(index<c)c+=1}return ca+colName(c)+ra+(r+1)})}
function structural(kind,index,del){let old=cells;cells=Array.from({length:ROWS},()=>Array(COLS).fill(''));for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){let sr=r,sc=c;if(kind==='row'){sr=del?(r>=index?r+1:r):(r<index?r:r-1);if(sr<0||sr>=ROWS)continue}else{sc=del?(c>=index?c+1:c):(c<index?c:c-1);if(sc<0||sc>=COLS)continue}cells[r][c]=rewrite(old[sr][sc],kind,index,del)}save();recalculate();render()}
/* ==================== Interaction ==================== */
function move(dr,dc){select(selected.r+dr,selected.c+dc)}document.addEventListener('keydown',e=>{if(e.target===formula||e.target.tagName==='INPUT')return;if(e.key==='Delete'||e.key==='Backspace'){setRaw(selected.r,selected.c,'');render()}else if(e.key==='Enter'){startEdit()}else if(e.key==='F2'){startEdit()}else if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(e.key)){e.preventDefault();move(e.key==='ArrowDown'?1:e.key==='ArrowUp'?-1:0,e.key==='ArrowRight'?1:e.key==='ArrowLeft'?-1:0)}else if(e.key.length===1&&!e.ctrlKey&&!e.metaKey){startEdit();let inp=body.rows[selected.r].cells[selected.c+1].querySelector('input');inp.value=e.key;inp.setSelectionRange(1,1)}});
function toast(msg){$('toast').textContent=msg;$('toast').classList.add('show');setTimeout(()=>$('toast').classList.remove('show'),1600)}
function onContext(e){let th=e.target.closest('th');if(!th||th.classList.contains('corner'))return;e.preventDefault();let isCol=th.parentElement.parentElement.tagName==='THEAD',idx=+(th.dataset[isCol?'c':'r']);let m=$('context');m.innerHTML=`<button data-act="ins">Insert ${isCol?'column':'row'} before</button><button data-act="del">Delete ${isCol?'column':'row'}</button>`;m.style.left=e.clientX+'px';m.style.top=e.clientY+'px';m.hidden=false;m.onclick=x=>{if(x.target.dataset.act)structural(isCol?'col':'row',idx,x.target.dataset.act==='del');m.hidden=true}}document.addEventListener('click',()=>{$('context').hidden=true});
$('insertRow').onclick=()=>structural('row',selected.r,false);$('deleteRow').onclick=()=>structural('row',selected.r,true);$('insertCol').onclick=()=>structural('col',selected.c,false);$('deleteCol').onclick=()=>structural('col',selected.c,true);$('clearBtn').onclick=()=>{cells=Array.from({length:ROWS},()=>Array(COLS).fill(''));save();recalculate();render();toast('Sheet cleared')};$('newBtn').onclick=()=>{if(confirm('Start a new blank sheet?'))$('clearBtn').click()};$('saveBtn').onclick=()=>{save();toast('Saved to this browser')};
/* ==================== Boot ==================== */
buildGrid();load();recalculate();render();
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-5.6-luna-pro","provider":"openrouter","persona":"full","sessionId":"94404b5c-7f82-4a4c-ae9c-ba14e166ffb0","tokensIn":890007,"tokensOut":32899,"tokensTotal":922906,"turns":15,"toolCalls":21,"failedToolCalls":1,"timestamp":"2026-07-31T00:15:27.503Z"} -->