← Git Explorer results

Git Explorer

GPT 6 Luna · full

Initial view of Git Explorer

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 41,850 bytes · SHA-256 fe8c4aa1b73e
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Git Atlas — Structure & Context</title>
<style>
:root{--bg:#0b1020;--panel:#11182b;--panel2:#151e34;--line:#27334d;--muted:#8d9bb8;--text:#edf2ff;--white:#fff;--cyan:#56d5d0;--blue:#7b9cff;--violet:#c19aff;--orange:#ffb56b;--green:#69d8a5;--red:#ff7185;--shadow:0 16px 50px #02061188}*{box-sizing:border-box}body{margin:0;background:radial-gradient(ellipse at 50% -20%,#1c2b4b 0,transparent 55%),var(--bg);color:var(--text);font:14px/1.45 Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif;height:100vh;overflow:hidden}button,input,select,textarea{font:inherit}button{cursor:pointer;color:inherit}.app{height:100%;display:flex;flex-direction:column}.topbar{height:68px;flex:none;display:flex;align-items:center;padding:0 24px;border-bottom:1px solid #ffffff10;background:#0b1020c9;gap:24px}.brand{display:flex;align-items:center;gap:11px;min-width:220px}.logo{width:34px;height:34px;border-radius:10px;background:linear-gradient(135deg,#63ddd1,#798cff);display:grid;place-items:center;color:#10182b;font-weight:900;font-size:18px;box-shadow:0 5px 20px #4dc4cd33}.brand strong{font-size:16px;letter-spacing:-.4px}.brand small{display:block;color:#8190ac;font-size:10px;letter-spacing:1.6px;text-transform:uppercase;margin-top:1px}.repo{border-left:1px solid var(--line);padding-left:22px;color:#bcc8df;font-size:13px}.repo b{color:#fff;font-weight:600}.top-actions{margin-left:auto;display:flex;gap:9px;align-items:center}.btn{border:1px solid #33415d;background:#182239;border-radius:8px;padding:8px 12px;color:#dbe5fa;transition:.16s;display:inline-flex;gap:8px;align-items:center;font-size:12px;font-weight:600}.btn:hover{border-color:#6f85ad;background:#202c47;transform:translateY(-1px)}.btn.primary{background:#667ef0;border-color:#8296ff;color:white;box-shadow:0 4px 16px #576fe044}.btn.primary:hover{background:#7c91ff}.btn.quiet{background:transparent;border-color:transparent;color:#a9b5ce}.btn.danger{color:#ff929f;border-color:#663943;background:#2a1c2a}.iconbtn{border:0;background:none;color:#8492ae;padding:5px;border-radius:6px}.iconbtn:hover{background:#ffffff12;color:white}.layout{flex:1;min-height:0;display:grid;grid-template-columns:minmax(0,1fr) 355px}.main{min-width:0;display:flex;flex-direction:column;border-right:1px solid #ffffff10}.toolbar{height:66px;border-bottom:1px solid #ffffff10;display:flex;align-items:center;padding:0 24px;gap:18px}.tabs{display:flex;gap:4px;background:#10182a;padding:4px;border-radius:9px}.tab{border:0;background:transparent;color:#91a0bb;padding:7px 12px;border-radius:6px;font-size:12px;font-weight:600}.tab.active{background:#273451;color:white;box-shadow:0 2px 8px #0004}.crumb{color:#72819e;font-size:12px;margin-left:5px}.toolbar-right{margin-left:auto;display:flex;gap:9px;align-items:center}.search{position:relative}.search input{width:205px;background:#10182a;border:1px solid #2a3650;color:#eaf0ff;border-radius:8px;padding:8px 10px 8px 30px;outline:none;font-size:12px}.search input:focus{border-color:#7186de}.search span{position:absolute;left:10px;top:7px;color:#70809d}.stage{flex:1;min-height:0;position:relative;background-image:radial-gradient(#33415d66 .8px,transparent .8px);background-size:24px 24px;overflow:hidden}.graph-wrap{position:absolute;inset:0;overflow:hidden}.graph-svg{width:100%;height:100%;display:block;touch-action:none}.graph-head{position:absolute;top:20px;left:24px;pointer-events:none}.graph-head .eyebrow{color:#91a1bf;text-transform:uppercase;font-size:10px;letter-spacing:1.8px;font-weight:700}.graph-head h1{font-size:19px;margin:4px 0 0;letter-spacing:-.5px}.graph-head p{margin:4px 0;color:#8b9ab5;font-size:12px}.legend{position:absolute;bottom:20px;left:24px;display:flex;gap:14px;align-items:center;color:#98a7c1;font-size:11px;background:#11182bd9;border:1px solid #283550;border-radius:8px;padding:9px 12px}.leg{display:flex;align-items:center;gap:6px}.dot{width:8px;height:8px;border-radius:50%;background:var(--cyan)}.dot.event{background:var(--orange);box-shadow:0 0 8px #ffb56b}.legend .line{height:2px;width:15px;background:#647391}.graph-controls{position:absolute;right:20px;bottom:20px;display:flex;gap:6px}.graph-controls button{width:31px;height:31px;border:1px solid #30405e;background:#121b30d9;border-radius:7px;color:#a6b3cc;font-size:17px}.empty{display:grid;place-items:center;height:100%;color:#8c9ab4;text-align:center}.side{background:#0e1527;display:flex;flex-direction:column;min-height:0}.side-head{height:66px;display:flex;align-items:center;padding:0 20px;border-bottom:1px solid #ffffff10}.side-head h2{font-size:14px;margin:0}.count{margin-left:8px;background:#25324b;color:#aebddb;padding:2px 7px;border-radius:12px;font-size:10px}.side-head .btn{margin-left:auto;padding:7px 9px}.event-list{overflow:auto;padding:14px 14px 20px;flex:1}.event-card{position:relative;border:1px solid #27334b;background:linear-gradient(135deg,#151e32,#11192b);border-radius:10px;padding:13px 13px 12px 16px;margin-bottom:9px;cursor:pointer;transition:.16s}.event-card:hover,.event-card.selected{border-color:#d19a62;transform:translateY(-1px);background:#202337}.event-card.selected{box-shadow:0 0 0 1px #d19a6255}.event-card:before{content:'';position:absolute;left:0;top:12px;bottom:12px;width:2px;background:var(--event-color,#ffb56b);border-radius:2px}.event-top{display:flex;align-items:center;gap:7px}.event-icon{font-size:13px}.event-title{font-weight:700;font-size:13px;flex:1}.event-date{color:#7786a2;font-size:10px;white-space:nowrap}.event-desc{font-size:11px;color:#a3afc5;margin:7px 0 10px;line-height:1.5}.event-link{border-top:1px solid #ffffff0c;padding-top:8px;display:flex;align-items:center;gap:7px;color:#8e9eb9;font-size:10px}.sha{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;color:#a9b9db}.branch-pill{border:1px solid #364765;border-radius:20px;padding:2px 7px;color:#8fa6cf;font-size:9px}.side-foot{padding:11px 18px;border-top:1px solid #ffffff10;color:#7786a2;font-size:10px;display:flex;justify-content:space-between}.status-dot{display:inline-block;width:6px;height:6px;background:#6ddba7;border-radius:50%;margin-right:6px}.inspector{border-top:1px solid #ffffff10;padding:15px 18px;display:none;max-height:255px;overflow:auto}.inspector.show{display:block}.inspector-top{display:flex;align-items:center;justify-content:space-between}.inspector h3{font-size:12px;margin:0}.inspector p{color:#96a4bd;font-size:11px;margin:10px 0}.detail-row{display:flex;justify-content:space-between;font-size:10px;color:#7e8eaa;padding:5px 0;border-bottom:1px solid #ffffff0a}.detail-row b{color:#d3dced;font-weight:500}.inspect-actions{display:flex;gap:7px;margin-top:10px}.inspect-actions .btn{padding:6px 9px;font-size:10px}.branch-label{font-size:10px;font-weight:700;letter-spacing:.2px}.node-label{font:10px ui-monospace,monospace;fill:#95a4c0}.node-title{font:11px system-ui,sans-serif;fill:#dbe5fa}.event-anchor{cursor:pointer}.event-anchor circle{fill:#ffb56b;stroke:#fff0ce;stroke-width:2}.event-anchor text{fill:#ffc98f;font-size:10px;font-weight:700}.commit-node{cursor:pointer}.commit-node .halo{fill:transparent;stroke:transparent;stroke-width:2}.commit-node.selected .halo{stroke:#fff;filter:drop-shadow(0 0 7px #a7baff)}.commit-node circle.core{stroke:#10182b;stroke-width:3}.commit-node:hover circle.core{stroke:white;stroke-width:3}.tooltip{position:fixed;z-index:20;pointer-events:none;background:#080e1d;border:1px solid #52617c;border-radius:8px;padding:10px 12px;max-width:250px;box-shadow:var(--shadow);display:none;color:#e9efff;font-size:11px}.tooltip strong{display:block;font-size:12px;margin-bottom:4px}.tooltip span{display:block;color:#9facc4;margin-top:2px}.modal-backdrop{position:fixed;inset:0;background:#030610bb;backdrop-filter:blur(5px);z-index:50;display:none;place-items:center;padding:20px}.modal-backdrop.open{display:grid}.modal{width:min(510px,100%);max-height:90vh;overflow:auto;background:#141d31;border:1px solid #374561;border-radius:14px;box-shadow:0 25px 90px #000a}.modal-head{padding:18px 20px;border-bottom:1px solid #ffffff12;display:flex;align-items:center}.modal-head h2{font-size:16px;margin:0;flex:1}.modal-body{padding:19px 20px}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:13px}.field{display:flex;flex-direction:column;gap:6px}.field.full{grid-column:1/-1}.field label{font-size:10px;color:#a8b5cc;font-weight:700;letter-spacing:.5px}.field input,.field select,.field textarea{background:#0d1527;border:1px solid #34415b;border-radius:7px;color:#eef3ff;padding:9px 10px;outline:none;width:100%;font-size:12px}.field textarea{min-height:75px;resize:vertical}.field input:focus,.field select:focus,.field textarea:focus{border-color:#8499f3}.field select option{background:#111a2d}.helper{font-size:10px;color:#8190aa}.modal-foot{padding:13px 20px;border-top:1px solid #ffffff12;display:flex;justify-content:flex-end;gap:8px}.toast{position:fixed;bottom:24px;left:50%;transform:translate(-50%,20px);opacity:0;z-index:90;background:#1e2c42;border:1px solid #4a5c79;color:#ecf4ff;padding:10px 16px;border-radius:8px;transition:.25s;box-shadow:var(--shadow);font-size:12px}.toast.show{opacity:1;transform:translate(-50%,0)}.event-view{position:absolute;inset:0;overflow:auto;padding:90px 30px 35px;display:none}.event-view.show{display:block}.event-view-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px;max-width:1050px}.event-view .event-card{min-height:145px}.branch-select{max-width:120px}.hidden{display:none!important}@media(max-width:900px){.layout{grid-template-columns:minmax(0,1fr) 300px}.brand{min-width:auto}.repo{display:none}.toolbar{padding:0 14px}.graph-head{left:15px}.side-head{padding:0 14px}}@media(max-width:680px){body{overflow:auto;height:auto;min-height:100vh}.app{min-height:100vh;height:auto}.topbar{padding:0 12px;height:58px}.brand small{display:none}.brand{min-width:0}.top-actions .btn:not(.primary){display:none}.top-actions{gap:5px}.layout{display:flex;flex-direction:column;min-height:0}.main{height:58vh;min-height:400px;border-right:0}.toolbar{height:58px;padding:0 10px;gap:6px}.crumb,.search{display:none}.toolbar-right{gap:4px}.toolbar-right .btn{padding:7px 8px}.stage{min-height:0}.side{min-height:380px}.side-head{height:55px}.event-list{max-height:400px}.graph-head{top:13px}.graph-head h1{font-size:16px}.graph-head p{font-size:10px}.legend{bottom:10px;left:10px;gap:8px;padding:7px;font-size:9px}.graph-controls{bottom:10px;right:10px}.graph-controls button{width:28px;height:28px}.event-view{padding:75px 12px 20px}.form-grid{grid-template-columns:1fr}.field.full{grid-column:auto}}
</style>
</head>
<body><div class="app">
<header class="topbar"><div class="brand"><div class="logo">⑂</div><div><strong>Git Atlas</strong><small>structure × context</small></div></div><div class="repo"><b>northstar / platform</b> <span style="color:#6f7f9b">·</span> main</div><div class="top-actions"><button class="btn quiet" id="exportBtn" title="Download unified JSON">↧ <span>Export</span></button><button class="btn quiet" id="importBtn">↥ <span>Import</span></button><input id="importFile" type="file" accept="application/json,.json" hidden><button class="btn" id="manageBranches">⑂ <span>Branches</span></button><button class="btn" id="manageTags">◇ <span>Tags</span></button><button class="btn" id="resetBtn">↺ <span>Reset demo</span></button><button class="btn primary" id="newCommitBtn">+ New commit</button></div></header>
<div class="layout"><main class="main"><div class="toolbar"><div class="tabs"><button class="tab active" data-view="graph">⑂ &nbsp;Git graph</button><button class="tab" data-view="events">◉ &nbsp;Event flow</button></div><div class="crumb">/ &nbsp;history</div><div class="toolbar-right"><div class="search"><span>⌕</span><input id="search" placeholder="Find a commit…" autocomplete="off"></div><select id="branchFilter" class="btn branch-select" aria-label="Filter branch"><option value="all">All branches</option></select><button class="btn" id="newBranchBtn">+ Branch</button><button class="btn" id="newEventBtn">+ Event</button></div></div>
<section class="stage"><div class="graph-wrap" id="graphWrap"><div class="graph-head"><div class="eyebrow">Repository history</div><h1>One history, many signals.</h1><p>Commits are the structure. Events are the moments that matter.</p></div><svg class="graph-svg" id="graph" role="img" aria-label="Interactive Git commit graph"></svg><div class="legend"><span class="leg"><i class="dot"></i> Commit</span><span class="leg"><i class="line"></i> Parent link</span><span class="leg"><i class="dot event"></i> Context event</span></div><div class="graph-controls"><button id="zoomOut" title="Zoom out">−</button><button id="zoomHome" title="Fit graph">⌂</button><button id="zoomIn" title="Zoom in">+</button></div></div><div class="event-view" id="eventView"><div class="graph-head"><div class="eyebrow">Contextual flow</div><h1>Milestones along the way.</h1><p>Every signal resolves to the exact commit where it happened.</p></div><div class="event-view-grid" id="eventGrid"></div></div></section>
</main><aside class="side"><div class="side-head"><h2>Contextual events</h2><span class="count" id="eventCount">0</span><button class="btn" id="sideAddEvent">+ Add event</button></div><div class="event-list" id="eventList"></div><div class="inspector" id="inspector"></div><div class="side-foot"><span><i class="status-dot"></i><span id="savedState">All changes saved</span></span><span id="commitCount">0 commits</span></div></aside></div></div>
<div class="tooltip" id="tooltip"></div><div class="toast" id="toast"></div><div class="modal-backdrop" id="modalBackdrop"><div class="modal"><div class="modal-head"><h2 id="modalTitle"></h2><button class="iconbtn" id="modalClose">✕</button></div><div class="modal-body" id="modalBody"></div><div class="modal-foot" id="modalFoot"></div></div></div>
<script>
(()=>{'use strict';
const STORE='git-atlas-state-v1';
const palette={main:'#65d8d1',feature:'#9b8cff',release:'#ffb76d',hotfix:'#ff7185',design:'#69d8a5'};
const initial={commits:[
{id:'a01f9c2',message:'Initialize project structure',author:'Maya Chen',date:'2024-04-02',parents:[],branch:'main',anchor:{x:110,y:0}},
{id:'b72d5e1',message:'Add tokenized color system',author:'Maya Chen',date:'2024-04-04',parents:['a01f9c2'],branch:'main',anchor:{x:0,y:0}},
{id:'c84a2b7',message:'Build responsive shell',author:'Noah Williams',date:'2024-04-06',parents:['b72d5e1'],branch:'main',anchor:{x:0,y:0}},
{id:'d31e6f4',message:'Create navigation primitives',author:'Noah Williams',date:'2024-04-09',parents:['c84a2b7'],branch:'main',anchor:{x:0,y:0}},
{id:'e18b4a9',message:'Prototype command palette',author:'Iris Park',date:'2024-04-10',parents:['c84a2b7'],branch:'feature/command-menu',anchor:{x:0,y:0}},
{id:'f90c2d3',message:'Add keyboard navigation',author:'Iris Park',date:'2024-04-12',parents:['e18b4a9'],branch:'feature/command-menu',anchor:{x:0,y:0}},
{id:'0ac73e8',message:'Merge command menu',author:'Maya Chen',date:'2024-04-15',parents:['d31e6f4','f90c2d3'],branch:'main',anchor:{x:0,y:0}},
{id:'128fe61',message:'Polish empty states',author:'Noah Williams',date:'2024-04-17',parents:['0ac73e8'],branch:'main',anchor:{x:0,y:0}},
{id:'26bd5c0',message:'Stabilize release build',author:'Maya Chen',date:'2024-04-19',parents:['128fe61'],branch:'main',anchor:{x:0,y:0}},
{id:'35aa91d',message:'Patch session timeout',author:'Iris Park',date:'2024-04-20',parents:['26bd5c0'],branch:'hotfix/session',anchor:{x:0,y:0}},
{id:'4c8e2f1',message:'Ship v2.4.0',author:'Maya Chen',date:'2024-04-22',parents:['26bd5c0','35aa91d'],branch:'main',anchor:{x:0,y:0}}],
branches:[{name:'main',commit:'4c8e2f1',color:palette.main},{name:'feature/command-menu',commit:'f90c2d3',color:palette.feature},{name:'hotfix/session',commit:'35aa91d',color:palette.hotfix}],
tags:[{name:'v2.3.0',commit:'d31e6f4'},{name:'v2.4.0',commit:'4c8e2f1'}],events:[
{id:'ev-freeze',title:'Code freeze',type:'Release',date:'2024-04-18',description:'Feature work paused; only release blockers accepted from this point.',commitId:'26bd5c0',branch:'main'},
{id:'ev-feature',title:'Command menu complete',type:'Milestone',date:'2024-04-12',description:'Keyboard-first command menu is ready for integration.',commitId:'f90c2d3',branch:'feature/command-menu'},
{id:'ev-review',title:'Design review',type:'Review',date:'2024-04-08',description:'Navigation patterns approved by the product design team.',commitId:'c84a2b7',branch:'main'},
{id:'ev-ship',title:'v2.4.0 shipped',type:'Launch',date:'2024-04-22',description:'Release deployed to production across all regions.',commitId:'4c8e2f1',branch:'main'},
{id:'ev-incident',title:'Session incident',type:'Incident',date:'2024-04-20',description:'A timeout regression was identified and patched before launch.',commitId:'35aa91d',branch:'hotfix/session'}]};
let state=load(),selectedCommit=null,selectedEvent=null,activeView='graph',searchTerm='',filterBranch='all',transform={x:0,y:0,k:1},drag=null,pan=null;
const $=id=>document.getElementById(id), svg=$('graph'), NS='http://www.w3.org/2000/svg';
function clone(v){return JSON.parse(JSON.stringify(v))}function load(){try{let d=JSON.parse(localStorage.getItem(STORE));if(d&&Array.isArray(d.commits)&&Array.isArray(d.events))return d}catch(e){}return clone(initial)}
function persist(){localStorage.setItem(STORE,JSON.stringify(state));$('savedState').textContent='Saved just now';setTimeout(()=>{if($('savedState'))$('savedState').textContent='All changes saved'},1000)}
function uid(prefix='c'){return prefix+Math.random().toString(36).slice(2,8)}function commit(id){return state.commits.find(c=>c.id===id)}function branch(name){return state.branches.find(b=>b.name===name)}function esc(s=''){return String(s).replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]))}
function colorForBranch(name){return (branch(name)||{}).color||palette[name]||'#8fa3c8'}
function notify(text){let t=$('toast');t.textContent=text;t.classList.add('show');clearTimeout(notify.timer);notify.timer=setTimeout(()=>t.classList.remove('show'),2400)}
function dateFmt(s){if(!s)return '';let d=new Date(s+'T12:00:00');return Number.isNaN(+d)?s:d.toLocaleDateString('en-US',{month:'short',day:'numeric'})}
function svgEl(tag,attrs={},parent=svg){let el=document.createElementNS(NS,tag);for(let k in attrs)el.setAttribute(k,attrs[k]);parent.appendChild(el);return el}
function layoutGraph(){let w=svg.clientWidth||900,h=svg.clientHeight||600;svg.setAttribute('viewBox',`0 0 ${w} ${h}`);svg.innerHTML='';let defs=svgEl('defs');let filter=svgEl('filter',{id:'glow',x:'-100%',y:'-100%',width:'300%',height:'300%'},defs);svgEl('feGaussianBlur',{stdDeviation:'3',result:'b'},filter);let ordered=[...state.commits].sort((a,b)=>a.date.localeCompare(b.date));let rank=new Map();ordered.forEach(c=>rank.set(c.id,Math.max(0,...c.parents.map(p=>(rank.get(p)??-1)+1))));
// Prefer lane ownership; merge nodes stay on main. Branches get stable lanes.
let laneNames=['main',...state.branches.map(b=>b.name).filter(n=>n!=='main')];let laneX={};let left=95,right=w-80,span=Math.max(250,right-left);laneNames.forEach((n,i)=>laneX[n]=left+(i/(Math.max(1,laneNames.length-1)))*span);if(laneNames.length===1)laneX.main=left+span*.5;
let maxRank=Math.max(1,...rank.values());let top=150,bottom=Math.max(top+100,h-170),rowGap=Math.max(46,(bottom-top)/maxRank);let pos={};ordered.forEach((c,i)=>{let x=laneX[c.branch]??laneX.main;let y=top+(rank.get(c.id)||0)*rowGap;if(c.parents.length>1)x=laneX.main; if(c.anchor&&c.anchor.x>0&&c.anchor.y>0&&c.anchor.manual){x=c.anchor.x;y=c.anchor.y}pos[c.id]={x,y}});
// If many nodes share lane/rank, offset gently to keep them legible
let seen={};ordered.forEach(c=>{let p=pos[c.id],key=Math.round(p.x)+'-'+Math.round(p.y);if(seen[key])p.y+=seen[key]*18;seen[key]=(seen[key]||0)+1});
let g=svgEl('g',{transform:`translate(${transform.x},${transform.y}) scale(${transform.k})`});
// branch guide rails
laneNames.forEach(n=>{let x=laneX[n];svgEl('line',{x1:x,y1:top-22,x2:x,y2:bottom+30,stroke:colorForBranch(n), 'stroke-opacity':'.09','stroke-width':'1','stroke-dasharray':'3 8'},g);let t=svgEl('text',{x:x,y:top-36,'text-anchor':'middle',class:'branch-label',fill:colorForBranch(n)},g);t.textContent=n});
// edges draw parents behind nodes
ordered.forEach(c=>c.parents.forEach(pid=>{let a=pos[pid],b=pos[c.id];if(!a||!b)return;let curve=`M ${a.x} ${a.y} C ${a.x} ${(a.y+b.y)/2}, ${b.x} ${(a.y+b.y)/2}, ${b.x} ${b.y}`;svgEl('path',{d:curve,fill:'none',stroke:colorForBranch(c.branch),'stroke-width':c.parents.length>1?'2':'1.8','stroke-opacity':'.58'},g)}));
// tags labels
state.tags.forEach(t=>{let p=pos[t.commit];if(!p)return;let tag=svgEl('g',{transform:`translate(${p.x+22},${p.y-22})`},g);svgEl('rect',{x:0,y:-9,width:54,height:18,rx:5,fill:'#222d46',stroke:'#526481','stroke-width':'.7'},tag);let tx=svgEl('text',{x:27,y:3,'text-anchor':'middle',fill:'#bdc9e0','font-size':'9','font-weight':'700'},tag);tx.textContent=t.name});
// commits
ordered.forEach(c=>{let p=pos[c.id];if(!p)return;let match=!searchTerm||`${c.id} ${c.message} ${c.author}`.toLowerCase().includes(searchTerm.toLowerCase());let grp=svgEl('g',{class:`commit-node ${selectedCommit===c.id?'selected':''}`,transform:`translate(${p.x},${p.y})`,opacity:match?'1':'.22','data-id':c.id},g);svgEl('circle',{class:'halo',r:13},grp);svgEl('circle',{class:'core',r:7,fill:colorForBranch(c.branch)},grp);let sh=svgEl('text',{class:'node-label',x:15,y:3},grp);sh.textContent=c.id.slice(0,7);let title=svgEl('text',{class:'node-title',x:15,y:18},grp);title.textContent=c.message.length>25?c.message.slice(0,24)+'…':c.message;grp.addEventListener('click',e=>{e.stopPropagation();selectCommit(c.id)});grp.addEventListener('pointerdown',e=>{if(e.button!==0)return;drag={id:c.id,startX:e.clientX,startY:e.clientY,orig:{...p},moved:false};grp.setPointerCapture?.(e.pointerId)});grp.addEventListener('pointermove',e=>{if(!drag||drag.id!==c.id)return;let dx=(e.clientX-drag.startX)/transform.k,dy=(e.clientY-drag.startY)/transform.k;if(Math.abs(dx)+Math.abs(dy)>3)drag.moved=true;if(drag.moved){c.anchor={x:drag.orig.x+dx,y:drag.orig.y+dy,manual:true};layoutGraph()}});grp.addEventListener('pointerup',()=>{if(drag?.id===c.id){if(drag.moved){persist();notify('Commit anchor updated')}drag=null}});grp.addEventListener('pointerenter',e=>showCommitTooltip(e,c));grp.addEventListener('pointermove',moveTooltip);grp.addEventListener('pointerleave',hideTooltip);});
// event anchors around target commit, distinct positions
let counts={};state.events.forEach(ev=>{let p=pos[ev.commitId];if(!p)return;let idx=counts[ev.commitId]||0;counts[ev.commitId]=idx+1;let angle=-Math.PI/2+idx*1.35,rad=idx?29:22;let ax=p.x+Math.cos(angle)*rad,ay=p.y+Math.sin(angle)*rad;let eg=svgEl('g',{class:'event-anchor','data-event':ev.id,transform:`translate(${ax},${ay})`},g);svgEl('line',{x1:p.x-ax,y1:p.y-ay,x2:0,y2:0,stroke:'#ffb56b','stroke-opacity':'.54','stroke-width':'1','stroke-dasharray':'2 3',transform:`translate(${-ax},${-ay})`},eg);svgEl('circle',{r:selectedEvent===ev.id?7:5,filter:selectedEvent===ev.id?'url(#glow)':''},eg);let txt=svgEl('text',{x:8,y:3},eg);txt.textContent=ev.title.length>18?ev.title.slice(0,17)+'…':ev.title;eg.addEventListener('click',e=>{e.stopPropagation();selectEvent(ev.id)});eg.addEventListener('pointerenter',e=>showEventTooltip(e,ev));eg.addEventListener('pointermove',moveTooltip);eg.addEventListener('pointerleave',hideTooltip)});
// branches pointer badges
state.branches.forEach(b=>{let p=pos[b.commit];if(!p)return;let label=svgEl('g',{transform:`translate(${p.x-12},${p.y-25})`,'pointer-events':'none'},g);let width=Math.max(44,b.name.length*6+13);svgEl('rect',{x:-width,y:-9,width,height:17,rx:7,fill:b.color,opacity:'.92'},label);let tx=svgEl('text',{x:-width/2,y:3,'text-anchor':'middle',fill:'#08111e','font-size':'9','font-weight':'800'},label);tx.textContent=b.name});
renderInspector();}
function selectCommit(id){selectedCommit=id;selectedEvent=null;renderAll();let c=commit(id);if(c)notify(`Selected ${c.id.slice(0,7)} · ${c.message}`)}function selectEvent(id){let ev=state.events.find(e=>e.id===id);if(!ev)return;selectedEvent=id;selectedCommit=ev.commitId;renderAll();showInspector();}
function showCommitTooltip(e,c){let events=state.events.filter(x=>x.commitId===c.id);let t=$('tooltip');t.innerHTML=`<strong>${esc(c.message)}</strong><span>${esc(c.id)} · ${esc(c.author)} · ${dateFmt(c.date)}</span><span>Branch: ${esc(c.branch||'unassigned')}</span>${events.length?`<span>◉ ${events.length} contextual event${events.length>1?'s':''}</span>`:''}`;t.style.display='block';moveTooltip(e)}function showEventTooltip(e,ev){let c=commit(ev.commitId);let t=$('tooltip');t.innerHTML=`<strong>◉ ${esc(ev.title)}</strong><span>${esc(ev.type)} · ${dateFmt(ev.date)}</span><span>${esc(ev.description)}</span><span>Linked commit: ${esc(c?.id||ev.commitId)} — ${esc(c?.message||'')}</span><span>Branch: ${esc(ev.branch||c?.branch||'')}</span>`;t.style.display='block';moveTooltip(e)}function moveTooltip(e){let t=$('tooltip');t.style.left=Math.min(innerWidth-270,e.clientX+14)+'px';t.style.top=Math.min(innerHeight-130,e.clientY+13)+'px'}function hideTooltip(){$('tooltip').style.display='none'}
function renderAll(){renderBranchFilter();renderEvents();layoutGraph();$('eventCount').textContent=state.events.length;$('commitCount').textContent=`${state.commits.length} commits`;if(activeView==='events')renderEventGrid()}
function renderBranchFilter(){let el=$('branchFilter'),old=filterBranch;el.innerHTML='<option value="all">All branches</option>'+state.branches.map(b=>`<option value="${esc(b.name)}">${esc(b.name)}</option>`).join('');el.value=state.branches.some(b=>b.name===old)?old:'all';filterBranch=el.value}
function eventCard(ev){let c=commit(ev.commitId),color=({Release:'#ffb56b',Milestone:'#9b8cff',Review:'#65d8d1',Launch:'#69d8a5',Incident:'#ff7185'})[ev.type]||'#ffb56b';return `<article class="event-card ${selectedEvent===ev.id?'selected':''}" style="--event-color:${color}" data-event-card="${esc(ev.id)}"><div class="event-top"><span class="event-icon">${ev.type==='Incident'?'⚠':ev.type==='Launch'?'✦':ev.type==='Review'?'◇':ev.type==='Milestone'?'◆':'◷'}</span><span class="event-title">${esc(ev.title)}</span><span class="event-date">${dateFmt(ev.date)}</span></div><p class="event-desc">${esc(ev.description||'No description provided.')}</p><div class="event-link"><span class="sha">${esc(c?.id||ev.commitId)}</span><span>·</span><span>${esc(c?.message||'Unknown commit')}</span><span class="branch-pill">${esc(ev.branch||c?.branch||'')}</span></div></article>`}
function renderEvents(){let evs=state.events.slice().sort((a,b)=>b.date.localeCompare(a.date));$('eventList').innerHTML=evs.length?evs.map(eventCard).join():'<div class="empty" style="height:180px">No context yet.<br>Add an event to anchor it to history.</div>';document.querySelectorAll('[data-event-card]').forEach(el=>{el.onclick=()=>selectEvent(el.dataset.eventCard);el.ondblclick=()=>openEvent(el.dataset.eventCard)});}
function renderEventGrid(){let arr=state.events.slice().sort((a,b)=>a.date.localeCompare(b.date));$('eventGrid').innerHTML=arr.length?arr.map(eventCard).join():'<div class="empty">No events yet. Add one to start a contextual timeline.</div>';document.querySelectorAll('#eventGrid [data-event-card]').forEach(el=>{el.onclick=()=>selectEvent(el.dataset.eventCard);el.ondblclick=()=>openEvent(el.dataset.eventCard)})}
function renderInspector(){let box=$('inspector');if(!selectedCommit&&!selectedEvent){box.classList.remove('show');box.innerHTML='';return}box.classList.add('show');let c=commit(selectedCommit);if(!c){box.classList.remove('show');return}let evs=state.events.filter(e=>e.commitId===c.id);box.innerHTML=`<div class="inspector-top"><h3>Commit details</h3><button class="iconbtn" id="clearInspect">✕</button></div><p><b style="color:#eef3ff">${esc(c.message)}</b></p><div class="detail-row"><span>Commit</span><b class="sha">${esc(c.id)}</b></div><div class="detail-row"><span>Author · date</span><b>${esc(c.author)} · ${dateFmt(c.date)}</b></div><div class="detail-row"><span>Parents</span><b>${c.parents.length?c.parents.map(p=>esc(p.slice(0,7))).join(', '):'Root commit'}</b></div><div class="detail-row"><span>Anchored events</span><b>${evs.length}</b></div><div class="inspect-actions"><button class="btn" id="editSelected">Edit commit</button><button class="btn" id="deleteSelected">Delete</button>${evs[0]?`<button class="btn" id="editLinkedEvent">Edit event</button>`:''}</div>`;$('clearInspect').onclick=()=>{selectedCommit=selectedEvent=null;renderAll()};$('editSelected').onclick=()=>openCommit(c.id);$('deleteSelected').onclick=()=>deleteCommit(c.id);if(evs[0])$('editLinkedEvent').onclick=()=>openEvent(evs[0].id)}
function showInspector(){renderInspector()}
function modal(title,body,buttons){$('modalTitle').textContent=title;$('modalBody').innerHTML=body;$('modalFoot').innerHTML=buttons;$('modalBackdrop').classList.add('open');$('modalClose').onclick=closeModal;$('modalBackdrop').onclick=e=>{if(e.target===$('modalBackdrop'))closeModal()}}
function closeModal(){$('modalBackdrop').classList.remove('open')}
function parentOptions(selected=[]){return state.commits.map(c=>`<option value="${esc(c.id)}" ${selected.includes(c.id)?'selected':''}>${esc(c.id)} · ${esc(c.message)}</option>`).join('')}
function branchOptions(selected){return state.branches.map(b=>`<option value="${esc(b.name)}" ${selected===b.name?'selected':''}>${esc(b.name)}</option>`).join('')}
function openCommit(id=null){let c=id?commit(id):null;let parents=c?c.parents:state.commits.filter(x=>x.id===currentBranchCommit()).slice(0,1).map(x=>x.id);let title=c?'Edit commit':'Create commit';modal(title,`<div class="form-grid"><div class="field full"><label>COMMIT MESSAGE</label><input id="fMessage" value="${esc(c?.message||'')}" placeholder="What changed?" autofocus></div><div class="field"><label>AUTHOR</label><input id="fAuthor" value="${esc(c?.author||'Maya Chen')}"></div><div class="field"><label>DATE</label><input id="fDate" type="date" value="${esc(c?.date||new Date().toISOString().slice(0,10))}"></div><div class="field"><label>BRANCH</label><select id="fBranch">${branchOptions(c?.branch||(filterBranch!=='all'?filterBranch:'main'))}</select></div><div class="field"><label>PARENTS · MULTI-SELECT</label><select id="fParents" multiple size="4">${parentOptions(parents)}</select><span class="helper">Use Ctrl / ⌘ to select multiple parents (merge commit).</span></div></div>`, `<button class="btn" id="cancelModal">Cancel</button><button class="btn primary" id="saveCommit">${c?'Save changes':'Create commit'}</button>`);$('cancelModal').onclick=closeModal;$('saveCommit').onclick=()=>{let message=$('fMessage').value.trim(),author=$('fAuthor').value.trim(),date=$('fDate').value||new Date().toISOString().slice(0,10),br=$('fBranch').value,pars=[...$('fParents').selectedOptions].map(o=>o.value);if(!message||!author){notify('Message and author are required');return}if(c&&pars.includes(c.id)){notify('A commit cannot parent itself');return}if(c){Object.assign(c,{message,author,date,branch:br,parents:pars});state.branches.forEach(b=>{if(b.commit===c.id)b.name===br?null:null});state.events.filter(e=>e.commitId===c.id).forEach(e=>e.branch=br)}else{let nc={id:uid(),message,author,date,parents:pars,branch:br,anchor:{x:0,y:0}};state.commits.push(nc);let b=branch(br);if(b)b.commit=nc.id;else state.branches.push({name:br,commit:nc.id,color:palette[br]||'#9b8cff'})}persist();closeModal();renderAll();notify(c?'Commit updated':'Commit created')};}
function currentBranchCommit(){return branch(filterBranch==='all'?'main':filterBranch)?.commit}
function openBranch(name=null){let b=name?branch(name):null;modal(b?'Edit branch':'Create branch',`<div class="form-grid"><div class="field full"><label>BRANCH NAME</label><input id="bName" value="${esc(b?.name||'')}" placeholder="feature/my-feature"></div><div class="field full"><label>POINTER COMMIT</label><select id="bCommit">${parentOptions(b?[b.commit]:[currentBranchCommit()].filter(Boolean))}</select></div><div class="field"><label>COLOR</label><input id="bColor" type="color" value="${b?.color||'#9b8cff'}"></div></div>`, `<button class="btn" id="cancelModal">Cancel</button>${b?'<button class="btn danger" id="deleteBranch">Delete branch</button>':''}<button class="btn primary" id="saveBranch">${b?'Save branch':'Create branch'}</button>`);$('cancelModal').onclick=closeModal;if(b)$('deleteBranch').onclick=()=>{if(state.branches.length<2){notify('Keep at least one branch');return}if(!confirm(`Delete branch “${b.name}”?`))return;state.branches=state.branches.filter(x=>x!==b);state.events.forEach(e=>{if(e.branch===b.name)e.branch=commit(e.commitId)?.branch||'main'});closeModal();persist();renderAll();notify('Branch deleted')};$('saveBranch').onclick=()=>{let n=$('bName').value.trim(),ptr=$('bCommit').value;if(!n||!ptr){notify('Branch name and pointer are required');return}if(state.branches.some(x=>x.name===n&&x!==b)){notify('That branch name already exists');return}if(b){let old=b.name;b.name=n;b.commit=ptr;b.color=$('bColor').value;state.events.forEach(e=>{if(e.branch===old)e.branch=n})}else state.branches.push({name:n,commit:ptr,color:$('bColor').value});persist();closeModal();renderAll();notify(b?'Branch updated':'Branch created')};}
function openEvent(id=null){let ev=id?state.events.find(e=>e.id===id):null;let selected=ev?.commitId||selectedCommit||currentBranchCommit();let c=commit(selected);modal(ev?'Edit contextual event':'Add contextual event',`<div class="form-grid"><div class="field full"><label>EVENT TITLE</label><input id="eTitle" value="${esc(ev?.title||'')}" placeholder="e.g. Feature completion"></div><div class="field"><label>TYPE</label><select id="eType">${['Milestone','Release','Review','Incident','Launch','Decision','Other'].map(t=>`<option ${ev?.type===t?'selected':''}>${t}</option>`).join('')}</select></div><div class="field"><label>DATE</label><input id="eDate" type="date" value="${esc(ev?.date||new Date().toISOString().slice(0,10))}"></div><div class="field full"><label>ASSOCIATED COMMIT · REQUIRED</label><select id="eCommit">${parentOptions(selected?[selected]:[])}</select><span class="helper">Selecting a branch below resolves its current pointer commit.</span></div><div class="field"><label>BRANCH CONTEXT</label><select id="eBranch"><option value="">Use commit branch</option>${branchOptions(ev?.branch||c?.branch)}</select></div><div class="field full"><label>DESCRIPTION</label><textarea id="eDescription" placeholder="Why does this moment matter?">${esc(ev?.description||'')}</textarea></div></div>`, `<button class="btn" id="cancelModal">Cancel</button>${ev?'<button class="btn danger" id="deleteEvent">Delete event</button>':''}<button class="btn primary" id="saveEvent">${ev?'Save event':'Anchor event'}</button>`);$('cancelModal').onclick=closeModal;$('eBranch').onchange=()=>{let b=branch($('eBranch').value);if(b)$('eCommit').value=b.commit};$('eCommit').onchange=()=>{let cm=commit($('eCommit').value);if(cm&&$('eBranch').value&&branch($('eBranch').value).commit!==cm.id)$('eBranch').value=''};if(ev)$('deleteEvent').onclick=()=>deleteEvent(ev.id);$('saveEvent').onclick=()=>{let title=$('eTitle').value.trim(),commitId=$('eCommit').value;if(!title||!commitId){notify('A title and linked commit are required');return}let obj={id:ev?.id||uid('ev-'),title,type:$('eType').value,date:$('eDate').value||new Date().toISOString().slice(0,10),description:$('eDescription').value.trim(),commitId,branch:$('eBranch').value||commit(commitId)?.branch||''};if(ev)Object.assign(ev,obj);else state.events.push(obj);selectedEvent=obj.id;selectedCommit=commitId;persist();closeModal();renderAll();notify(ev?'Event updated':'Event anchored to '+commitId.slice(0,7))};}
function manageTags(){let rows=state.tags.map((t,i)=>`<div class="detail-row"><span><span class="sha">${esc(t.name)}</span> → ${esc(commit(t.commit)?.message||t.commit)}</span><button class="iconbtn" data-remove-tag="${i}">✕</button></div>`).join('');modal('Repository tags',`<div>${rows||'<p class="helper">No tags yet.</p>'}</div><div class="form-grid" style="margin-top:16px"><div class="field"><label>TAG NAME</label><input id="tagName" placeholder="v2.5.0"></div><div class="field"><label>POINTS TO COMMIT</label><select id="tagCommit">${parentOptions(selectedCommit?[selectedCommit]:[currentBranchCommit()].filter(Boolean))}</select></div></div>`,`<button class="btn" id="cancelModal">Done</button><button class="btn primary" id="addTag">+ Add tag</button>`);$('cancelModal').onclick=closeModal;document.querySelectorAll('[data-remove-tag]').forEach(x=>x.onclick=()=>{state.tags.splice(Number(x.dataset.removeTag),1);persist();manageTags()});$('addTag').onclick=()=>{let name=$('tagName').value.trim(),target=$('tagCommit').value;if(!name||!target){notify('Tag name and target commit are required');return}if(state.tags.some(t=>t.name===name)){notify('Tag name already exists');return}state.tags.push({name,commit:target});persist();manageTags();notify('Tag added')};}
function deleteEvent(id){let e=state.events.find(x=>x.id===id);if(!e)return;if(!confirm(`Delete event “${e.title}”?`))return;state.events=state.events.filter(x=>x.id!==id);if(selectedEvent===id)selectedEvent=null;persist();closeModal();renderAll();notify('Event deleted')}
function deleteCommit(id){let c=commit(id);if(!c)return;if(!confirm(`Delete ${id.slice(0,7)}? Child commits will connect to its parent(s); anchored events will be removed.`))return;state.commits=state.commits.filter(x=>x.id!==id);state.commits.forEach(x=>{if(x.parents.includes(id))x.parents=[...new Set(x.parents.flatMap(p=>p===id?c.parents:[p]))]});state.events=state.events.filter(e=>e.commitId!==id);state.branches.forEach(b=>{if(b.commit===id)b.commit=c.parents[0]||state.commits[0]?.id});state.tags=state.tags.filter(t=>t.commit!==id);selectedCommit=null;selectedEvent=null;persist();renderAll();notify('Commit removed; history reconnected')}
function exportState(){let blob=new Blob([JSON.stringify(state,null,2)],{type:'application/json'}),a=document.createElement('a');a.href=URL.createObjectURL(blob);a.download='git-atlas-history.json';a.click();URL.revokeObjectURL(a.href);notify('Unified history exported')}
function importState(data){if(!data||!Array.isArray(data.commits)||!Array.isArray(data.events)||!Array.isArray(data.branches)){notify('Invalid file: expected commits, branches, and events');return}let ids=new Set(data.commits.map(c=>c.id));if(data.events.some(e=>!e.commitId||!ids.has(e.commitId))){notify('Import rejected: every event must link to an existing commit');return}if(data.commits.some(c=>!c.id||!Array.isArray(c.parents)||c.parents.some(p=>!ids.has(p)))){notify('Import rejected: invalid commit or parent reference');return}state={commits:data.commits,branches:data.branches,tags:Array.isArray(data.tags)?data.tags:[],events:data.events};selectedCommit=selectedEvent=null;persist();renderAll();notify('History and context imported')}
function onResize(){layoutGraph()}window.addEventListener('resize',onResize);
// canvas navigation wheel zoom and background drag
svg.addEventListener('wheel',e=>{e.preventDefault();let k=transform.k*(e.deltaY<0?1.1:.9);transform.k=Math.min(2.2,Math.max(.55,k));layoutGraph()},{passive:false});svg.addEventListener('pointerdown',e=>{if(e.target!==svg&&e.target.tagName!=='path'&&e.target.tagName!=='line')return;pan={x:e.clientX-transform.x,y:e.clientY-transform.y};svg.setPointerCapture(e.pointerId)});svg.addEventListener('pointermove',e=>{if(!pan)return;transform.x=e.clientX-pan.x;transform.y=e.clientY-pan.y;layoutGraph()});svg.addEventListener('pointerup',()=>{pan=null});$('zoomIn').onclick=()=>{transform.k=Math.min(2.2,transform.k*1.2);layoutGraph()};$('zoomOut').onclick=()=>{transform.k=Math.max(.55,transform.k/1.2);layoutGraph()};$('zoomHome').onclick=()=>{transform={x:0,y:0,k:1};layoutGraph()};
document.querySelectorAll('.tab').forEach(t=>t.onclick=()=>{activeView=t.dataset.view;document.querySelectorAll('.tab').forEach(x=>x.classList.toggle('active',x===t));$('graphWrap').classList.toggle('hidden',activeView!=='graph');$('eventView').classList.toggle('show',activeView==='events');if(activeView==='events')renderEventGrid()});$('search').oninput=e=>{searchTerm=e.target.value;layoutGraph()};$('branchFilter').onchange=e=>{filterBranch=e.target.value;layoutGraph()};$('newCommitBtn').onclick=()=>openCommit();$('newBranchBtn').onclick=()=>openBranch();$('newEventBtn').onclick=$('sideAddEvent').onclick=()=>openEvent();$('exportBtn').onclick=exportState;$('importBtn').onclick=()=>$('importFile').click();$('importFile').onchange=e=>{let f=e.target.files[0];if(!f)return;let reader=new FileReader();reader.onload=()=>{try{importState(JSON.parse(reader.result))}catch(err){notify('Could not read JSON file')}};reader.readAsText(f);e.target.value=''};$('manageBranches').onclick=()=>openBranch();$('manageTags').onclick=manageTags;$('resetBtn').onclick=()=>{if(confirm('Reset Git Atlas to the sample repository? This replaces saved local data.')){state=clone(initial);selectedCommit=selectedEvent=null;transform={x:0,y:0,k:1};persist();renderAll();notify('Demo repository restored')}};
renderAll();
})();
</script></body></html>
<!-- agent-meta {"model":"openai/gpt-6-luna","provider":"openrouter","persona":"full","sessionId":"08e85ba6-3018-4b8e-bbbc-5cbbf02d98cc","tokensIn":316670,"tokensOut":16993,"tokensTotal":333663,"cost":0.014655409999999999,"turns":15,"toolCalls":14,"failedToolCalls":1,"timestamp":"2026-09-23T02:39:33.590Z"} -->