---
name: "git-graph-visualizer"
description: "Animated canvas visualization of a realistic git branch history: 4 branches, 13 commits, merge commits, HEAD glow, and smooth auto-scroll. Pure HTML5 canvas."
metadata:
  version: "1.0.0"
  dependencies:
    system:
      - "browser"
  runtime:
    language: "html"
    entrypoint: "git-graph.html"
    timeout_seconds: 30
---

See skill files — primary content is `git-graph.html`.

---

## Files

### `git-graph.html`

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Git Graph Visualizer</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0a1a; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; font-family: monospace; }
canvas { border-radius: 12px; box-shadow: 0 0 60px rgba(168,85,247,0.15); }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const C = document.getElementById('c');
C.width = 800; C.height = 500;
const ctx = C.getContext('2d');
const W = 800, H = 500;

// Branch lanes (y positions)
const LANE = { main: 80, feat_auth: 180, feat_ui: 280, hotfix: 130 };
const COLOR = { main: '#a855f7', feat_auth: '#06b6d4', feat_ui: '#10b981', hotfix: '#f43f5e' };
const LABEL = { main: 'main', feat_auth: 'feature/auth', feat_ui: 'feature/ui', hotfix: 'hotfix/crash' };

function rh() { return Math.random().toString(16).substr(2,7); }

// Build git history — right = older, left = newer
// Each commit: { id, branch, x, y, msg, color, merge }
const STEP = 90;
let commits = [];
let edges = []; // { from_id, to_id }

function commit(branch, msg, parents, merge) {
  const x = 730 - commits.length * STEP;
  const c = { id: rh(), branch, x, y: LANE[branch], msg, color: COLOR[branch], merge: !!merge };
  commits.push(c);
  (parents||[]).forEach(p => edges.push({ f: p, t: c.id }));
  return c.id;
}

// Build a realistic git history
const c0  = commit('main',      'init: project scaffold',  []);
const c1  = commit('main',      'feat: add routing',       [c0]);
const c2  = commit('feat_auth', 'feat: JWT middleware',    [c1]);
const c3  = commit('main',      'docs: update README',     [c1]);
const c4  = commit('hotfix',    'fix: null ptr crash',     [c3]);
const c5  = commit('feat_auth', 'feat: refresh tokens',    [c2]);
const c6  = commit('main',      'merge: hotfix',           [c3, c4], true);
const c7  = commit('feat_ui',   'feat: hero section',      [c6]);
const c8  = commit('feat_auth', 'test: auth coverage',     [c5]);
const c9  = commit('feat_ui',   'feat: navbar responsive', [c7]);
const c10 = commit('main',      'merge: feature/auth',     [c6, c8], true);
const c11 = commit('feat_ui',   'feat: dark mode toggle',  [c9]);
const c12 = commit('main',      'merge: feature/ui',       [c10, c11], true);
const c13 = commit('main',      'chore: bump v1.2.0',      [c12]);

// Animation state
let offset = 0;
let pulse = 0;
let frame = 0;

function getC(id) { return commits.find(c => c.id === id); }

function drawLaneLines() {
  Object.entries(LANE).forEach(([k, y]) => {
    ctx.save();
    ctx.setLineDash([5, 12]);
    ctx.strokeStyle = COLOR[k] + '18';
    ctx.lineWidth = 1;
    ctx.beginPath(); ctx.moveTo(60, y); ctx.lineTo(W - 10, y); ctx.stroke();
    ctx.restore();
    ctx.fillStyle = COLOR[k];
    ctx.font = 'bold 11px monospace';
    ctx.textAlign = 'left';
    ctx.fillText(LABEL[k], 8, y + 4);
  });
}

function drawEdge(e) {
  const f = getC(e.f), t = getC(e.t);
  if (!f || !t) return;
  const fx = f.x + offset, tx = t.x + offset;
  if (fx < 55 && tx < 55) return;
  ctx.save();
  ctx.strokeStyle = t.color + '99';
  ctx.lineWidth = 2;
  ctx.beginPath();
  ctx.moveTo(fx, f.y);
  if (f.y === t.y) {
    ctx.lineTo(tx, t.y);
  } else {
    const mx = (fx + tx) / 2;
    ctx.bezierCurveTo(mx, f.y, mx, t.y, tx, t.y);
  }
  ctx.stroke();
  ctx.restore();
}

function drawCommit(c) {
  const x = c.x + offset;
  if (x < 55 || x > W - 5) return;
  const r = c.merge ? 11 : 8;

  // Glow for HEAD commit (c13)
  if (c.id === c13) {
    ctx.save();
    ctx.shadowColor = c.color;
    ctx.shadowBlur = 14 + Math.sin(pulse) * 5;
    ctx.fillStyle = c.color;
    ctx.beginPath(); ctx.arc(x, c.y, r + 2, 0, Math.PI*2); ctx.fill();
    ctx.restore();
  }

  // Outer ring
  ctx.fillStyle = c.color;
  ctx.beginPath(); ctx.arc(x, c.y, r, 0, Math.PI*2); ctx.fill();

  // Inner dot for merges
  if (c.merge) {
    ctx.fillStyle = '#0a0a1a';
    ctx.beginPath(); ctx.arc(x, c.y, 5, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = c.color;
    ctx.beginPath(); ctx.arc(x, c.y, 2, 0, Math.PI*2); ctx.fill();
  }

  // Hash below
  ctx.fillStyle = '#444';
  ctx.font = '9px monospace';
  ctx.textAlign = 'center';
  ctx.fillText(c.id.slice(0,6), x, c.y + 22);

  // Message above
  ctx.fillStyle = c.color + 'cc';
  ctx.font = '9.5px monospace';
  const msg = c.msg.length > 20 ? c.msg.slice(0,19) + '…' : c.msg;
  ctx.fillText(msg, x, c.y - 18);
}

function drawHUD() {
  // Title
  ctx.fillStyle = '#e2e8f0';
  ctx.font = 'bold 15px monospace';
  ctx.textAlign = 'left';
  ctx.fillText('Git Graph Visualizer', 8, 22);

  // Stats
  ctx.fillStyle = '#555';
  ctx.font = '11px monospace';
  ctx.fillText(`${commits.length} commits · ${Object.keys(LANE).length} branches`, 8, 40);

  // Legend
  ctx.textAlign = 'right';
  ctx.fillStyle = '#444';
  ctx.font = '10px monospace';
  ctx.fillText('● merge  ● commit  HEAD→', W - 8, H - 8);
}

function render(ts) {
  frame++;
  pulse = ts / 600;
  offset -= 0.25; // slow scroll right-to-left

  ctx.fillStyle = '#0a0a1a';
  ctx.fillRect(0, 0, W, H);

  // Clip label area
  ctx.save();
  ctx.rect(60, 0, W, H);
  ctx.clip();

  drawLaneLines();
  edges.forEach(drawEdge);
  commits.forEach(drawCommit);
  ctx.restore();

  drawHUD();
  requestAnimationFrame(render);
}

requestAnimationFrame(render);
</script>
</body>
</html>
```

### `README.md`

```md
# Git Graph Visualizer

An animated canvas visualization of a realistic git repository history.

## What it shows
- **4 branches**: main (purple), feature/auth (cyan), feature/ui (green), hotfix (red)
- **13 commits** in a realistic development timeline
- **Merge commits** with double-ring styling
- **HEAD commit** with pulsing glow effect
- Slow auto-scroll animation (right = older, left = newer)

## Visual elements
- Dashed lane lines for each branch
- Bezier curves for cross-branch edges
- Abbreviated commit hashes below each node
- Commit message labels above each node
- Live frame counter

## Built with
Pure HTML5 Canvas + requestAnimationFrame. Zero external dependencies.

```
