---
name: "canvas-neural-network-visualizer"
description: "Animated neural network visualization with signal propagation, node activations, and weighted connections. Feed-forward architecture with sigmoid activation."
metadata:
  version: "1.0.0"
  dependencies:
    system:
      - "browser"
  runtime:
    language: "html"
    entrypoint: "neural-net.html"
    timeout_seconds: 30
---

# Canvas Neural Network Visualizer

> **Difficulty:** Intermediate | **Runtime:** HTML5 Canvas | **Output:** Animated visualization

Teach an AI agent to visualize a feed-forward neural network with animated signal propagation, node activations, and weight connections.

## What You'll Build

A self-contained HTML file showing an animated neural network:
- Configurable layer sizes (input → hidden → output)
- Weighted connections drawn as lines (thickness = weight magnitude)
- Signal pulses traveling along connections during forward pass
- Node activation glow effect (brighter = higher activation)
- Continuous forward pass animation with random inputs

## Teaching Points

- Neural network architecture visualization
- Canvas arc drawing for nodes
- Line thickness mapping for weight visualization
- Particle/pulse animation along paths
- Color interpolation for activation display

---

## Files

### `neural-net.html`

```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Neural Network Visualizer</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body { background: #0a0a1a; display: flex; justify-content: center; align-items: center; height: 100vh; flex-direction: column; }
  canvas { border-radius: 12px; box-shadow: 0 0 40px rgba(168, 85, 247, 0.15); }
  .title { color: #a855f7; font-family: monospace; font-size: 14px; margin-bottom: 12px; }
</style>
</head>
<body>
<div class="title">Feed-Forward Neural Network — Forward Pass</div>
<canvas id="c"></canvas>
<script>
const c = document.getElementById('c');
const W = c.width = 800, H = c.height = 500;
const ctx = c.getContext('2d');

const layers = [4, 6, 6, 3];
const nodeR = 16;
const layerColors = ['#06b6d4','#a855f7','#a855f7','#10b981'];
const labels = [['x₁','x₂','x₃','x₄'],null,null,['y₁','y₂','y₃']];

// Build nodes
const nodes = [];
const padding = 80;
layers.forEach((size, li) => {
  const x = padding + li * ((W - 2*padding) / (layers.length - 1));
  const layerH = (size - 1) * 56;
  const startY = (H - layerH) / 2;
  for (let ni = 0; ni < size; ni++) {
    nodes.push({ x, y: startY + ni * 56, layer: li, idx: ni, activation: 0, targetAct: 0 });
  }
});

// Build connections with weights
const connections = [];
nodes.forEach(n => {
  if (n.layer < layers.length - 1) {
    nodes.filter(m => m.layer === n.layer + 1).forEach(m => {
      connections.push({ from: n, to: m, weight: (Math.random() - 0.5) * 2, pulses: [] });
    });
  }
});

// Signals
let signalTimer = 0;

function newForwardPass() {
  // Random input activations
  nodes.filter(n => n.layer === 0).forEach(n => { n.targetAct = Math.random(); });
  // Propagate through layers
  for (let l = 1; l < layers.length; l++) {
    nodes.filter(n => n.layer === l).forEach(n => {
      let sum = 0;
      connections.filter(c => c.to === n).forEach(c => {
        sum += c.from.targetAct * c.weight;
      });
      n.targetAct = 1 / (1 + Math.exp(-sum)); // sigmoid
    });
  }
  // Create pulses
  connections.forEach(conn => {
    conn.pulses.push({ t: 0, speed: 0.012 + Math.random() * 0.008 });
  });
}

function lerp(a, b, t) { return a + (b - a) * t; }

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

  // Draw connections
  connections.forEach(conn => {
    const absW = Math.abs(conn.weight);
    ctx.strokeStyle = conn.weight > 0 ? `rgba(16,185,129,${absW * 0.4})` : `rgba(244,63,94,${absW * 0.4})`;
    ctx.lineWidth = absW * 2.5 + 0.3;
    ctx.beginPath();
    ctx.moveTo(conn.from.x, conn.from.y);
    ctx.lineTo(conn.to.x, conn.to.y);
    ctx.stroke();

    // Draw pulses
    conn.pulses = conn.pulses.filter(p => p.t <= 1);
    conn.pulses.forEach(p => {
      p.t += p.speed;
      const px = lerp(conn.from.x, conn.to.x, p.t);
      const py = lerp(conn.from.y, conn.to.y, p.t);
      const glow = ctx.createRadialGradient(px, py, 0, px, py, 8);
      glow.addColorStop(0, 'rgba(168,85,247,0.9)');
      glow.addColorStop(1, 'rgba(168,85,247,0)');
      ctx.fillStyle = glow;
      ctx.beginPath();
      ctx.arc(px, py, 8, 0, Math.PI * 2);
      ctx.fill();
    });
  });

  // Draw nodes
  nodes.forEach(n => {
    n.activation = lerp(n.activation, n.targetAct, 0.04);
    const intensity = n.activation;
    const color = layerColors[n.layer];

    // Glow
    const glow = ctx.createRadialGradient(n.x, n.y, nodeR * 0.5, n.x, n.y, nodeR * 2.5);
    glow.addColorStop(0, color + Math.round(intensity * 60).toString(16).padStart(2, '0'));
    glow.addColorStop(1, 'transparent');
    ctx.fillStyle = glow;
    ctx.beginPath();
    ctx.arc(n.x, n.y, nodeR * 2.5, 0, Math.PI * 2);
    ctx.fill();

    // Node circle
    ctx.beginPath();
    ctx.arc(n.x, n.y, nodeR, 0, Math.PI * 2);
    ctx.fillStyle = `rgba(10,10,26,0.8)`;
    ctx.fill();
    ctx.strokeStyle = color;
    ctx.lineWidth = 2;
    ctx.stroke();

    // Inner fill based on activation
    ctx.beginPath();
    ctx.arc(n.x, n.y, nodeR - 3, 0, Math.PI * 2);
    ctx.fillStyle = color + Math.round(intensity * 200).toString(16).padStart(2, '0');
    ctx.fill();

    // Label
    const lbl = labels[n.layer]?.[n.idx];
    if (lbl) {
      ctx.fillStyle = '#ccc';
      ctx.font = '11px monospace';
      ctx.textAlign = 'center';
      ctx.fillText(lbl, n.x, n.y + 30);
    }

    // Activation value
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 9px monospace';
    ctx.textAlign = 'center';
    ctx.fillText(n.activation.toFixed(2), n.x, n.y + 4);
  });
  ctx.textAlign = 'left';

  // Layer labels
  const layerNames = ['Input', 'Hidden 1', 'Hidden 2', 'Output'];
  layers.forEach((_, li) => {
    const x = padding + li * ((W - 2*padding) / (layers.length - 1));
    ctx.fillStyle = '#555';
    ctx.font = '11px monospace';
    ctx.textAlign = 'center';
    ctx.fillText(layerNames[li], x, H - 20);
  });
  ctx.textAlign = 'left';

  // Trigger new pass
  signalTimer++;
  if (signalTimer % 120 === 0) newForwardPass();

  requestAnimationFrame(render);
}

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

### `README.md`

```md
# Neural Network Visualizer

Animated feed-forward neural network visualization with signal propagation.

## Features
- Configurable layer architecture (4→6→6→3)
- Animated signal pulses traveling along weighted connections
- Node activation glow based on sigmoid output
- Color-coded positive (green) and negative (red) weights
- Continuous forward pass with random inputs

## Run
Open `neural-net.html` in any browser.

```
