{"project":{"id":"qesHYUH","userId":"davidyarham@gmail.com","username":null,"userPicture":null,"name":"Neural Network Playground","visible":true,"contributors":"","githubRepo":null,"forkedFrom":null,"isTemplate":false,"tags":"","files":{"folder":"","files":[{"name":"index.html","content":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Untitled</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Neural Network Playground</title>\n<script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js\"></script>\n<script src=\"https://unpkg.com/lucide@latest/dist/umd/lucide.js\"></script>\n<link href=\"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n\n</head>\n<body>\n<div class=\"app\">\n  <!-- Header -->\n  <header class=\"header\">\n    <div class=\"header__logo\">\n      <i data-lucide=\"brain-circuit\"></i>\n      <span>Neural Network Playground</span>\n    </div>\n    <div class=\"header__badge\">XOR Classifier</div>\n  </header>\n\n  <!-- Main Layout -->\n  <main class=\"main\">\n\n    <!-- LEFT: Network Visualizer -->\n    <section class=\"panel panel--network\" aria-label=\"Network Visualizer\">\n      <div class=\"panel__header\">\n        <i data-lucide=\"cpu\"></i>\n        <h2>Network Architecture</h2>\n        <span class=\"panel__sub\">2 → 4 → 1</span>\n      </div>\n      <canvas id=\"network-canvas\" width=\"480\" height=\"340\" aria-label=\"Neural network diagram\"></canvas>\n      <div class=\"activations-row\">\n        <div class=\"activation-pill\" id=\"act-00\">\n          <span class=\"act-label\">x₁=0, x₂=0</span>\n          <span class=\"act-output\" id=\"out-00\">—</span>\n        </div>\n        <div class=\"activation-pill\" id=\"act-01\">\n          <span class=\"act-label\">x₁=0, x₂=1</span>\n          <span class=\"act-output\" id=\"out-01\">—</span>\n        </div>\n        <div class=\"activation-pill\" id=\"act-10\">\n          <span class=\"act-label\">x₁=1, x₂=0</span>\n          <span class=\"act-output\" id=\"out-10\">—</span>\n        </div>\n        <div class=\"activation-pill\" id=\"act-11\">\n          <span class=\"act-label\">x₁=1, x₂=1</span>\n          <span class=\"act-output\" id=\"out-11\">—</span>\n        </div>\n      </div>\n    </section>\n\n    <!-- CENTER: Controls + Decision Boundary -->\n    <section class=\"panel panel--controls\" aria-label=\"Controls\">\n      <div class=\"panel__header\">\n        <i data-lucide=\"sliders-horizontal\"></i>\n        <h2>Training Controls</h2>\n      </div>\n\n      <div class=\"controls-grid\">\n        <div class=\"control-group\">\n          <label for=\"lr-slider\">Learning Rate <span class=\"mono\" id=\"lr-val\">0.1</span></label>\n          <input type=\"range\" id=\"lr-slider\" min=\"0.001\" max=\"1\" step=\"0.001\" value=\"0.1\" aria-label=\"Learning rate\">\n        </div>\n        <div class=\"control-group\">\n          <label for=\"hidden-slider\">Hidden Neurons <span class=\"mono\" id=\"hidden-val\">4</span></label>\n          <input type=\"range\" id=\"hidden-slider\" min=\"2\" max=\"8\" step=\"1\" value=\"4\" aria-label=\"Hidden neurons\">\n        </div>\n        <div class=\"control-group\">\n          <label for=\"activation-select\">Activation</label>\n          <select id=\"activation-select\" aria-label=\"Activation function\">\n            <option value=\"sigmoid\">Sigmoid</option>\n            <option value=\"tanh\">Tanh</option>\n            <option value=\"relu\">ReLU</option>\n          </select>\n        </div>\n      </div>\n\n      <div class=\"btn-row\">\n        <button class=\"btn btn--primary\" id=\"btn-train\" aria-label=\"Start training\">\n          <i data-lucide=\"play\"></i> Train\n        </button>\n        <button class=\"btn btn--ghost\" id=\"btn-step\" aria-label=\"Step one epoch\">\n          <i data-lucide=\"skip-forward\"></i> Step\n        </button>\n        <button class=\"btn btn--danger\" id=\"btn-reset\" aria-label=\"Reset network\">\n          <i data-lucide=\"rotate-ccw\"></i> Reset\n        </button>\n      </div>\n\n      <div class=\"stats-bar\">\n        <div class=\"stat\">\n          <span class=\"stat__label\">Epoch</span>\n          <span class=\"stat__value mono\" id=\"stat-epoch\">0</span>\n        </div>\n        <div class=\"stat\">\n          <span class=\"stat__label\">Loss</span>\n          <span class=\"stat__value mono\" id=\"stat-loss\">—</span>\n        </div>\n        <div class=\"stat\">\n          <span class=\"stat__label\">Accuracy</span>\n          <span class=\"stat__value mono\" id=\"stat-acc\">—</span>\n        </div>\n      </div>\n\n      <!-- Decision Boundary Canvas -->\n      <div class=\"panel__header\" style=\"margin-top:1rem\">\n        <i data-lucide=\"grid-3x3\"></i>\n        <h2>Decision Boundary</h2>\n      </div>\n      <canvas id=\"boundary-canvas\" width=\"260\" height=\"260\" aria-label=\"Decision boundary heatmap\"></canvas>\n      <!-- XOR Points Legend -->\n      <div class=\"legend-row\">\n        <span class=\"legend-dot legend-dot--zero\"></span><span>XOR = 0</span>\n        <span class=\"legend-dot legend-dot--one\"></span><span>XOR = 1</span>\n      </div>\n    </section>\n\n    <!-- RIGHT: Loss Chart -->\n    <section class=\"panel panel--chart\" aria-label=\"Loss Chart\">\n      <div class=\"panel__header\">\n        <i data-lucide=\"trending-down\"></i>\n        <h2>Training Loss</h2>\n      </div>\n      <div class=\"chart-wrap\">\n        <canvas id=\"loss-chart\" aria-label=\"Loss over epochs chart\"></canvas>\n      </div>\n\n      <div class=\"panel__header\" style=\"margin-top:1.25rem\">\n        <i data-lucide=\"table-2\"></i>\n        <h2>Truth Table</h2>\n      </div>\n      <table class=\"truth-table\" aria-label=\"XOR truth table with predictions\">\n        <thead>\n          <tr><th>x₁</th><th>x₂</th><th>Target</th><th>Predicted</th><th></th></tr>\n        </thead>\n        <tbody id=\"truth-table-body\">\n          <tr><td>0</td><td>0</td><td>0</td><td class=\"pred\" id=\"pred-00\">—</td><td class=\"verdict\" id=\"verd-00\"></td></tr>\n          <tr><td>0</td><td>1</td><td>1</td><td class=\"pred\" id=\"pred-01\">—</td><td class=\"verdict\" id=\"verd-01\"></td></tr>\n          <tr><td>1</td><td>0</td><td>1</td><td class=\"pred\" id=\"pred-10\">—</td><td class=\"verdict\" id=\"verd-10\"></td></tr>\n          <tr><td>1</td><td>1</td><td>0</td><td class=\"pred\" id=\"pred-11\">—</td><td class=\"verdict\" id=\"verd-11\"></td></tr>\n        </tbody>\n      </table>\n\n      <div class=\"speed-control\">\n        <label for=\"speed-slider\"><i data-lucide=\"zap\"></i> Speed</label>\n        <input type=\"range\" id=\"speed-slider\" min=\"1\" max=\"50\" step=\"1\" value=\"5\" aria-label=\"Training speed\">\n        <span class=\"mono\" id=\"speed-val\">5×</span>\n      </div>\n    </section>\n  </main>\n</div>\n\n  <script type=\"module\" src=\"main.js\"></script>\n</body>\n</html>"},{"name":"main.js","content":"// ── XOR Dataset ──\nconst XOR_DATA = [\n  { input: [0, 0], target: 0 },\n  { input: [0, 1], target: 1 },\n  { input: [1, 0], target: 1 },\n  { input: [1, 1], target: 0 },\n];\n\n// ── Activation Functions ──\nconst activations = {\n  sigmoid: {\n    fn:   x => 1 / (1 + Math.exp(-x)),\n    deriv: x => { const s = 1 / (1 + Math.exp(-x)); return s * (1 - s); }\n  },\n  tanh: {\n    fn:   x => Math.tanh(x),\n    deriv: x => 1 - Math.tanh(x) ** 2\n  },\n  relu: {\n    fn:   x => Math.max(0, x),\n    deriv: x => x > 0 ? 1 : 0\n  }\n};\n\n// ── Neural Network ──\nclass NeuralNetwork {\n  constructor(hiddenSize, actKey) {\n    this.hiddenSize = hiddenSize;\n    this.actKey = actKey;\n    this.init();\n  }\n\n  init() {\n    const h = this.hiddenSize;\n    // Xavier init\n    const xav = (n) => (Math.random() * 2 - 1) * Math.sqrt(2 / n);\n    this.W1 = Array.from({ length: h }, () => [xav(2), xav(2)]);\n    this.b1 = Array(h).fill(0);\n    this.W2 = Array.from({ length: 1 }, () => Array.from({ length: h }, () => xav(h)));\n    this.b2 = [0];\n    // For animation\n    this.hiddenActivations = Array(h).fill(0);\n    this.outputActivation = 0;\n    this.inputActivations = [0, 0];\n  }\n\n  forward(input) {\n    const act = activations[this.actKey];\n    // Hidden layer\n    this.z1 = this.W1.map((row, i) =>\n      row.reduce((sum, w, j) => sum + w * input[j], 0) + this.b1[i]\n    );\n    this.a1 = this.z1.map(z => act.fn(z));\n\n    // Output layer (always sigmoid for classification)\n    this.z2 = this.W2.map(row =>\n      row.reduce((sum, w, j) => sum + w * this.a1[j], 0)\n    ).map((z, i) => z + this.b2[i]);\n    this.a2 = this.z2.map(z => activations.sigmoid.fn(z));\n\n    return this.a2[0];\n  }\n\n  backward(input, target, lr) {\n    const act = activations[this.actKey];\n    const h = this.hiddenSize;\n    const pred = this.a2[0];\n    // Output error\n    const dOut = pred - target;\n    // W2 gradients\n    const dW2 = this.W2.map((row, i) => row.map((_, j) => dOut * this.a1[j]));\n    const db2 = [dOut];\n    // Hidden error\n    const dHidden = this.a1.map((_, j) =>\n      dOut * this.W2[0][j] * act.deriv(this.z1[j])\n    );\n    // W1 gradients\n    const dW1 = this.W1.map((row, i) => row.map((_, j) => dHidden[i] * input[j]));\n    const db1 = dHidden.slice();\n    // Update\n    this.W2 = this.W2.map((row, i) => row.map((w, j) => w - lr * dW2[i][j]));\n    this.b2 = this.b2.map((b, i) => b - lr * db2[i]);\n    this.W1 = this.W1.map((row, i) => row.map((w, j) => w - lr * dW1[i][j]));\n    this.b1 = this.b1.map((b, i) => b - lr * db1[i]);\n  }\n\n  trainEpoch(lr) {\n    let loss = 0;\n    for (const sample of XOR_DATA) {\n      const pred = this.forward(sample.input);\n      loss += 0.5 * (pred - sample.target) ** 2;\n      this.backward(sample.input, sample.target, lr);\n    }\n    return loss / XOR_DATA.length;\n  }\n\n  predict(input) {\n    return this.forward(input);\n  }\n\n  getPredictions() {\n    return XOR_DATA.map(d => ({\n      input: d.input,\n      target: d.target,\n      pred: this.forward(d.input)\n    }));\n  }\n}\n\n// ── State ──\nlet nn, epoch, losses, isRunning, animFrame, lastForward, lastInput;\nlet trainInterval = null;\n\nfunction getHiddenSize() { return parseInt(document.getElementById('hidden-slider').value); }\nfunction getLR()         { return parseFloat(document.getElementById('lr-slider').value); }\nfunction getActKey()     { return document.getElementById('activation-select').value; }\nfunction getSpeed()      { return parseInt(document.getElementById('speed-slider').value); }\n\nfunction resetNetwork() {\n  stopTraining();\n  epoch = 0;\n  losses = [];\n  nn = new NeuralNetwork(getHiddenSize(), getActKey());\n  lastForward = null;\n  updateStats();\n  updateTruthTable();\n  updateActivationPills();\n  drawNetwork(null);\n  drawBoundary();\n  resetChart();\n  document.getElementById('btn-train').innerHTML = '<i data-lucide=\"play\"></i> Train';\n  document.getElementById('btn-train').classList.remove('running');\n  lucide.createIcons();\n}\n\n// ── Loss Chart ──\nlet lossChart;\nfunction initChart() {\n  const ctx = document.getElementById('loss-chart').getContext('2d');\n  lossChart = new Chart(ctx, {\n    type: 'line',\n    data: {\n      labels: [],\n      datasets: [{\n        label: 'MSE Loss',\n        data: [],\n        borderColor: '#00e5a0',\n        backgroundColor: 'rgba(0,229,160,0.08)',\n        borderWidth: 2,\n        pointRadius: 0,\n        fill: true,\n        tension: 0.4,\n      }]\n    },\n    options: {\n      responsive: true,\n      maintainAspectRatio: false,\n      animation: false,\n      plugins: {\n        legend: { display: false },\n        tooltip: {\n          mode: 'index',\n          intersect: false,\n          backgroundColor: '#1a1f2e',\n          borderColor: '#252b3b',\n          borderWidth: 1,\n          titleColor: '#6b7599',\n          bodyColor: '#e8eaf0',\n          titleFont: { family: 'JetBrains Mono', size: 10 },\n          bodyFont: { family: 'JetBrains Mono', size: 11 },\n        }\n      },\n      scales: {\n        x: {\n          ticks: {\n            color: '#6b7599',\n            font: { family: 'JetBrains Mono', size: 9 },\n            maxTicksLimit: 8\n          },\n          grid: { color: 'rgba(37,43,59,0.7)' }\n        },\n        y: {\n          ticks: {\n            color: '#6b7599',\n            font: { family: 'JetBrains Mono', size: 9 },\n          },\n          grid: { color: 'rgba(37,43,59,0.7)' },\n          min: 0\n        }\n      }\n    }\n  });\n}\n\nfunction resetChart() {\n  lossChart.data.labels = [];\n  lossChart.data.datasets[0].data = [];\n  lossChart.update();\n}\n\nfunction pushLoss(ep, loss) {\n  if (ep % 10 === 0 || ep < 10) {\n    lossChart.data.labels.push(ep);\n    lossChart.data.datasets[0].data.push(loss.toFixed(5));\n    lossChart.update();\n  }\n}\n\n// ── Stats ──\nfunction updateStats(loss) {\n  document.getElementById('stat-epoch').textContent = epoch;\n  document.getElementById('stat-loss').textContent = loss !== undefined ? loss.toFixed(5) : '—';\n  const preds = nn.getPredictions();\n  const correct = preds.filter(p => Math.round(p.pred) === p.target).length;\n  document.getElementById('stat-acc').textContent = loss !== undefined\n    ? `${correct}/4`\n    : '—';\n}\n\n// ── Truth Table ──\nfunction updateTruthTable() {\n  const preds = nn.getPredictions();\n  const ids = ['00', '01', '10', '11'];\n  preds.forEach((p, i) => {\n    const predEl  = document.getElementById(`pred-${ids[i]}`);\n    const verdEl  = document.getElementById(`verd-${ids[i]}`);\n    predEl.textContent = p.pred.toFixed(3);\n    const correct = Math.round(p.pred) === p.target;\n    verdEl.className = 'verdict ' + (correct ? 'ok' : 'bad');\n  });\n}\n\n// ── Activation Pills ──\nfunction updateActivationPills() {\n  const preds = nn.getPredictions();\n  const ids = ['00', '01', '10', '11'];\n  const targets = [0, 1, 1, 0];\n  preds.forEach((p, i) => {\n    const pill = document.getElementById(`act-${ids[i]}`);\n    const out  = document.getElementById(`out-${ids[i]}`);\n    out.textContent = p.pred.toFixed(3);\n    const correct = Math.round(p.pred) === p.target;\n    pill.className = 'activation-pill ' + (correct ? 'correct' : 'wrong');\n  });\n}\n\n// ── Network Canvas ──\nconst NET_CANVAS = document.getElementById('network-canvas');\nconst NET_CTX = NET_CANVAS.getContext('2d');\n\nfunction getNeuronPositions(hiddenSize) {\n  const W = NET_CANVAS.width;\n  const H = NET_CANVAS.height;\n  const cols = [W * 0.13, W * 0.42, W * 0.78];\n  const layers = [2, hiddenSize, 1];\n  return layers.map((count, li) => {\n    const x = cols[li];\n    return Array.from({ length: count }, (_, i) => ({\n      x,\n      y: H / 2 + (i - (count - 1) / 2) * Math.min(68, (H - 40) / count)\n    }));\n  });\n}\n\nfunction clamp01(v) { return Math.max(0, Math.min(1, v)); }\nfunction lerpColor(a, b, t) {\n  const hex = (s) => parseInt(s, 16);\n  const parse = c => ({ r: hex(c.slice(1,3)), g: hex(c.slice(3,5)), b: hex(c.slice(5,7)) });\n  const ca = parse(a), cb = parse(b);\n  const r = Math.round(ca.r + (cb.r - ca.r) * t);\n  const g = Math.round(ca.g + (cb.g - ca.g) * t);\n  const b2 = Math.round(ca.b + (cb.b - ca.b) * t);\n  return `rgb(${r},${g},${b2})`;\n}\n\nfunction drawNetwork(forward) {\n  const ctx = NET_CTX;\n  const W = NET_CANVAS.width;\n  const H = NET_CANVAS.height;\n  ctx.clearRect(0, 0, W, H);\n\n  // Background\n  ctx.fillStyle = '#12151d';\n  ctx.fillRect(0, 0, W, H);\n\n  const hiddenSize = nn.hiddenSize;\n  const positions = getNeuronPositions(hiddenSize);\n\n  // Extract weights for edge color\n  const W1 = nn.W1; // [hidden x 2]\n  const W2 = nn.W2; // [1 x hidden]\n  const maxW1 = W1.flat().reduce((m, v) => Math.max(m, Math.abs(v)), 0.001);\n  const maxW2 = W2.flat().reduce((m, v) => Math.max(m, Math.abs(v)), 0.001);\n\n  // Draw edges input → hidden\n  for (let i = 0; i < hiddenSize; i++) {\n    for (let j = 0; j < 2; j++) {\n      const w = W1[i][j];\n      const t = clamp01((w + maxW1) / (2 * maxW1));\n      const col = w > 0 ? `rgba(0,229,160,${0.15 + 0.55 * Math.abs(w) / maxW1})`\n                        : `rgba(255,107,107,${0.15 + 0.55 * Math.abs(w) / maxW1})`;\n      drawEdge(ctx, positions[0][j], positions[1][i], col, Math.max(0.5, 2.5 * Math.abs(w) / maxW1));\n    }\n  }\n\n  // Draw edges hidden → output\n  for (let j = 0; j < hiddenSize; j++) {\n    const w = W2[0][j];\n    const col = w > 0 ? `rgba(0,229,160,${0.15 + 0.55 * Math.abs(w) / maxW2})`\n                      : `rgba(255,107,107,${0.15 + 0.55 * Math.abs(w) / maxW2})`;\n    drawEdge(ctx, positions[1][j], positions[2][0], col, Math.max(0.5, 2.5 * Math.abs(w) / maxW2));\n  }\n\n  // Get activations for color\n  let inputActs = [0.5, 0.5];\n  let hidActs = Array(hiddenSize).fill(0.5);\n  let outAct = 0.5;\n\n  if (forward) {\n    inputActs = forward.input;\n    hidActs = forward.a1;\n    outAct = forward.a2;\n  }\n\n  // Draw neurons — input\n  const layerLabels = [['x₁', 'x₂'],\n    Array.from({ length: hiddenSize }, (_, i) => `h${i + 1}`),\n    ['ŷ']];\n\n  positions.forEach((layer, li) => {\n    layer.forEach((pos, ni) => {\n      let act = 0.5;\n      if (li === 0) act = inputActs[ni];\n      if (li === 1) act = clamp01(hidActs[ni]);\n      if (li === 2) act = clamp01(outAct);\n\n      drawNeuron(ctx, pos.x, pos.y, act, layerLabels[li][ni], li);\n    });\n  });\n\n  // Layer labels\n  const layerNames = ['Input', 'Hidden', 'Output'];\n  [0, 1, 2].forEach(li => {\n    ctx.font = '10px JetBrains Mono';\n    ctx.fillStyle = '#6b7599';\n    ctx.textAlign = 'center';\n    const x = positions[li][0].x;\n    ctx.fillText(layerNames[li], x, H - 8);\n  });\n}\n\nfunction drawEdge(ctx, from, to, color, width) {\n  ctx.beginPath();\n  ctx.moveTo(from.x, from.y);\n  // Bezier curve\n  const cx = (from.x + to.x) / 2;\n  ctx.bezierCurveTo(cx, from.y, cx, to.y, to.x, to.y);\n  ctx.strokeStyle = color;\n  ctx.lineWidth = width;\n  ctx.stroke();\n}\n\nfunction drawNeuron(ctx, x, y, activation, label, layerIdx) {\n  const r = layerIdx === 2 ? 20 : 16;\n  // Glow\n  const glowColor = activation > 0.5\n    ? `rgba(0,229,160,${0.15 + 0.55 * activation})`\n    : `rgba(76,201,240,${0.1 + 0.3 * (1 - activation)})`;\n  ctx.shadowBlur = 14 * activation;\n  ctx.shadowColor = glowColor;\n\n  // Neuron body\n  const grad = ctx.createRadialGradient(x - r * 0.3, y - r * 0.3, r * 0.1, x, y, r);\n  grad.addColorStop(0, lerpColor('#2a3a4a', '#00e5a0', clamp01(activation)));\n  grad.addColorStop(1, '#12151d');\n\n  ctx.beginPath();\n  ctx.arc(x, y, r, 0, Math.PI * 2);\n  ctx.fillStyle = grad;\n  ctx.fill();\n  ctx.shadowBlur = 0;\n\n  // Border\n  ctx.strokeStyle = activation > 0.5\n    ? `rgba(0,229,160,${0.4 + 0.6 * activation})`\n    : '#252b3b';\n  ctx.lineWidth = 1.5;\n  ctx.stroke();\n\n  // Label\n  ctx.font = `bold ${r > 18 ? 11 : 9}px JetBrains Mono`;\n  ctx.fillStyle = '#e8eaf0';\n  ctx.textAlign = 'center';\n  ctx.textBaseline = 'middle';\n  ctx.fillText(label, x, y);\n}\n\n// ── Decision Boundary ──\nconst BOUND_CANVAS = document.getElementById('boundary-canvas');\nconst BOUND_CTX = BOUND_CANVAS.getContext('2d');\nconst BOUND_SIZE = 80; // internal resolution\n\nfunction drawBoundary() {\n  const size = BOUND_SIZE;\n  const imgData = BOUND_CTX.createImageData(size, size);\n  for (let row = 0; row < size; row++) {\n    for (let col = 0; col < size; col++) {\n      const x1 = col / (size - 1);\n      const x2 = 1 - row / (size - 1);\n      const pred = nn.predict([x1, x2]);\n      const idx = (row * size + col) * 4;\n      if (pred > 0.5) {\n        // Green tint\n        imgData.data[idx]     = Math.round(0  + pred * 0);\n        imgData.data[idx + 1] = Math.round(80 + pred * 100);\n        imgData.data[idx + 2] = Math.round(50 + pred * 50);\n        imgData.data[idx + 3] = Math.round(80 + pred * 120);\n      } else {\n        // Red tint\n        imgData.data[idx]     = Math.round(120 + (1 - pred) * 100);\n        imgData.data[idx + 1] = Math.round(20);\n        imgData.data[idx + 2] = Math.round(20);\n        imgData.data[idx + 3] = Math.round(80 + (1 - pred) * 100);\n      }\n    }\n  }\n\n  // Render image at full canvas size\n  const tmpCanvas = document.createElement('canvas');\n  tmpCanvas.width = size;\n  tmpCanvas.height = size;\n  tmpCanvas.getContext('2d').putImageData(imgData, 0, 0);\n  BOUND_CTX.fillStyle = '#12151d';\n  BOUND_CTX.fillRect(0, 0, BOUND_CANVAS.width, BOUND_CANVAS.height);\n  BOUND_CTX.imageSmoothingEnabled = false;\n  BOUND_CTX.drawImage(tmpCanvas, 0, 0, BOUND_CANVAS.width, BOUND_CANVAS.height);\n\n  // Draw XOR points\n  const pts = [\n    { x: 0, y: 0, t: 0 },\n    { x: 0, y: 1, t: 1 },\n    { x: 1, y: 0, t: 1 },\n    { x: 1, y: 1, t: 0 },\n  ];\n  pts.forEach(p => {\n    const cx = p.x * (BOUND_CANVAS.width - 24) + 12;\n    const cy = (1 - p.y) * (BOUND_CANVAS.height - 24) + 12;\n    BOUND_CTX.beginPath();\n    BOUND_CTX.arc(cx, cy, 7, 0, Math.PI * 2);\n    BOUND_CTX.fillStyle = p.t === 1 ? '#00e5a0' : '#ff6b6b';\n    BOUND_CTX.fill();\n    BOUND_CTX.strokeStyle = '#0c0e13';\n    BOUND_CTX.lineWidth = 2;\n    BOUND_CTX.stroke();\n  });\n}\n\n// ── Training Loop ──\nfunction runEpochs(count) {\n  for (let i = 0; i < count; i++) {\n    const loss = nn.trainEpoch(getLR());\n    epoch++;\n    losses.push(loss);\n    pushLoss(epoch, loss);\n\n    if (i === count - 1) {\n      updateStats(loss);\n      updateTruthTable();\n      updateActivationPills();\n\n      // Do a forward pass on a random XOR sample for animation\n      const sample = XOR_DATA[Math.floor(Math.random() * 4)];\n      nn.forward(sample.input);\n      drawNetwork({ input: sample.input, a1: nn.a1, a2: nn.a2[0] });\n      drawBoundary();\n    }\n  }\n}\n\nfunction startTraining() {\n  if (isRunning) return;\n  isRunning = true;\n  document.getElementById('btn-train').innerHTML = '<i data-lucide=\"pause\"></i> Pause';\n  document.getElementById('btn-train').classList.add('running');\n  lucide.createIcons();\n\n  function loop() {\n    if (!isRunning) return;\n    runEpochs(getSpeed());\n    animFrame = requestAnimationFrame(loop);\n  }\n  animFrame = requestAnimationFrame(loop);\n}\n\nfunction stopTraining() {\n  isRunning = false;\n  if (animFrame) cancelAnimationFrame(animFrame);\n  document.getElementById('btn-train').innerHTML = '<i data-lucide=\"play\"></i> Train';\n  document.getElementById('btn-train').classList.remove('running');\n  lucide.createIcons();\n}\n\n// ── Event Listeners ──\ndocument.getElementById('btn-train').addEventListener('click', () => {\n  if (isRunning) stopTraining(); else startTraining();\n});\n\ndocument.getElementById('btn-step').addEventListener('click', () => {\n  stopTraining();\n  runEpochs(1);\n});\n\ndocument.getElementById('btn-reset').addEventListener('click', resetNetwork);\n\ndocument.getElementById('lr-slider').addEventListener('input', function () {\n  document.getElementById('lr-val').textContent = parseFloat(this.value).toFixed(3);\n});\n\ndocument.getElementById('hidden-slider').addEventListener('input', function () {\n  document.getElementById('hidden-val').textContent = this.value;\n  // Rebuild network immediately\n  resetNetwork();\n  document.getElementById('hidden-val').textContent = this.value;\n});\n\ndocument.getElementById('activation-select').addEventListener('change', () => {\n  resetNetwork();\n});\n\ndocument.getElementById('speed-slider').addEventListener('input', function () {\n  document.getElementById('speed-val').textContent = this.value + '×';\n});\n\n// ── Init ──\ninitChart();\nresetNetwork();\nlucide.createIcons();\n"},{"name":"style.css","content":"/* ── Reset & Base ── */\n*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n:root {\n  --bg: #0c0e13;\n  --surface: #12151d;\n  --surface2: #1a1f2e;\n  --border: #252b3b;\n  --accent: #00e5a0;\n  --accent2: #ff6b6b;\n  --accent3: #ffd166;\n  --accent4: #4cc9f0;\n  --text: #e8eaf0;\n  --text-muted: #8e9bbf;\n  --font-mono: 'JetBrains Mono', monospace;\n  --font-ui: 'Inter', sans-serif;\n  color-scheme: dark;\n}\n\nbody {\n  background: var(--bg);\n  color: var(--text);\n  font-family: var(--font-ui);\n  min-height: 100vh;\n  overflow-x: hidden;\n}\n\n/* ── Header ── */\n.header {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 0.85rem 1.5rem;\n  background: var(--surface);\n  border-bottom: 1px solid var(--border);\n  position: sticky;\n  top: 0;\n  z-index: 10;\n}\n\n.header__logo {\n  display: flex;\n  align-items: center;\n  gap: 0.6rem;\n  font-family: var(--font-mono);\n  font-weight: 700;\n  font-size: 1rem;\n  letter-spacing: -0.01em;\n  color: var(--accent);\n}\n\n.header__logo svg { width: 22px; height: 22px; stroke: var(--accent); }\n\n.header__badge {\n  font-family: var(--font-mono);\n  font-size: 0.7rem;\n  padding: 0.2rem 0.7rem;\n  border-radius: 999px;\n  background: color-mix(in srgb, var(--accent) 12%, transparent);\n  border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);\n  color: var(--accent);\n  letter-spacing: 0.05em;\n}\n\n/* ── Main Layout ── */\n.main {\n  display: grid;\n  grid-template-columns: 1fr 280px 280px;\n  gap: 1rem;\n  padding: 1rem;\n  min-height: calc(100vh - 56px);\n  align-items: start;\n}\n\n/* ── Panel ── */\n.panel {\n  background: var(--surface);\n  border: 1px solid var(--border);\n  border-radius: 14px;\n  padding: 1.1rem 1.1rem;\n  display: flex;\n  flex-direction: column;\n  gap: 0.85rem;\n}\n\n.panel__header {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n  color: var(--text-muted);\n  font-size: 0.8rem;\n  font-weight: 600;\n  text-transform: uppercase;\n  letter-spacing: 0.06em;\n}\n\n.panel__header svg { width: 15px; height: 15px; }\n.panel__header h2 { font-size: 0.8rem; font-weight: 600; color: var(--text-muted); }\n.panel__sub {\n  font-family: var(--font-mono);\n  font-size: 0.7rem;\n  margin-left: auto;\n  color: var(--accent);\n  background: color-mix(in srgb, var(--accent) 10%, transparent);\n  padding: 0.15rem 0.5rem;\n  border-radius: 4px;\n}\n\n/* ── Network Canvas ── */\n#network-canvas {\n  width: 100%;\n  height: auto;\n  border-radius: 10px;\n  background: var(--surface2);\n  border: 1px solid var(--border);\n  display: block;\n}\n\n/* ── Activation Pills ── */\n.activations-row {\n  display: grid;\n  grid-template-columns: 1fr 1fr;\n  gap: 0.5rem;\n}\n\n.activation-pill {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding: 0.4rem 0.7rem;\n  background: var(--surface2);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  font-size: 0.72rem;\n  transition: border-color 0.3s;\n}\n\n.activation-pill.correct { border-color: var(--accent); }\n.activation-pill.wrong   { border-color: var(--accent2); }\n\n.act-label { font-family: var(--font-mono); color: var(--text-muted); }\n.act-output { font-family: var(--font-mono); font-weight: 700; color: var(--accent4); }\n\n/* ── Controls ── */\n.controls-grid {\n  display: flex;\n  flex-direction: column;\n  gap: 0.75rem;\n}\n\n.control-group {\n  display: flex;\n  flex-direction: column;\n  gap: 0.3rem;\n}\n\n.control-group label {\n  font-size: 0.75rem;\n  color: var(--text-muted);\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n.mono { font-family: var(--font-mono); color: var(--text); }\n\ninput[type=\"range\"] {\n  -webkit-appearance: none;\n  appearance: none;\n  width: 100%;\n  height: 4px;\n  border-radius: 99px;\n  background: var(--surface2);\n  outline: none;\n  cursor: pointer;\n}\n\ninput[type=\"range\"]::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  width: 14px;\n  height: 14px;\n  border-radius: 50%;\n  background: var(--accent);\n  border: 2px solid var(--bg);\n  box-shadow: 0 0 8px var(--accent);\n  cursor: pointer;\n  transition: transform 0.15s;\n}\n\ninput[type=\"range\"]::-webkit-slider-thumb:hover { transform: scale(1.25); }\n\nselect {\n  width: 100%;\n  padding: 0.45rem 0.7rem;\n  border-radius: 8px;\n  border: 1px solid var(--border);\n  background: var(--surface2);\n  color: var(--text);\n  font-family: var(--font-mono);\n  font-size: 0.78rem;\n  outline: none;\n  cursor: pointer;\n}\n\nselect:focus { border-color: var(--accent); }\n\n/* ── Buttons ── */\n.btn-row {\n  display: flex;\n  gap: 0.5rem;\n}\n\n.btn {\n  display: flex;\n  align-items: center;\n  gap: 0.4rem;\n  padding: 0.5rem 0.9rem;\n  border-radius: 8px;\n  border: none;\n  font-family: var(--font-mono);\n  font-size: 0.75rem;\n  font-weight: 600;\n  cursor: pointer;\n  transition: all 0.18s;\n  flex: 1;\n  justify-content: center;\n}\n\n.btn svg { width: 13px; height: 13px; }\n\n.btn--primary {\n  background: var(--accent);\n  color: #0c0e13;\n}\n\n.btn--primary:hover { filter: brightness(1.15); box-shadow: 0 0 18px color-mix(in srgb, var(--accent) 50%, transparent); }\n.btn--primary.running { background: var(--accent3); }\n\n.btn--ghost {\n  background: var(--surface2);\n  color: var(--text);\n  border: 1px solid var(--border);\n}\n\n.btn--ghost:hover { border-color: var(--accent4); color: var(--accent4); }\n\n.btn--danger {\n  background: var(--surface2);\n  color: var(--accent2);\n  border: 1px solid color-mix(in srgb, var(--accent2) 30%, transparent);\n}\n\n.btn--danger:hover { background: color-mix(in srgb, var(--accent2) 15%, transparent); }\n\n/* ── Stats Bar ── */\n.stats-bar {\n  display: flex;\n  gap: 0.5rem;\n}\n\n.stat {\n  flex: 1;\n  background: var(--surface2);\n  border: 1px solid var(--border);\n  border-radius: 8px;\n  padding: 0.5rem 0.6rem;\n  text-align: center;\n}\n\n.stat__label {\n  display: block;\n  font-size: 0.65rem;\n  color: var(--text-muted);\n  text-transform: uppercase;\n  letter-spacing: 0.07em;\n  margin-bottom: 0.2rem;\n}\n\n.stat__value {\n  font-size: 0.9rem;\n  font-weight: 700;\n  color: var(--accent);\n}\n\n/* ── Decision Boundary ── */\n#boundary-canvas {\n  width: 100%;\n  height: auto;\n  border-radius: 10px;\n  border: 1px solid var(--border);\n  display: block;\n  image-rendering: pixelated;\n}\n\n.legend-row {\n  display: flex;\n  align-items: center;\n  gap: 0.6rem;\n  font-size: 0.72rem;\n  color: var(--text-muted);\n}\n\n.legend-dot {\n  width: 10px; height: 10px;\n  border-radius: 50%;\n  display: inline-block;\n  margin-left: 0.5rem;\n}\n\n.legend-dot--zero { background: var(--accent2); box-shadow: 0 0 6px var(--accent2); }\n.legend-dot--one  { background: var(--accent); box-shadow: 0 0 6px var(--accent); }\n\n/* ── Loss Chart ── */\n.chart-wrap {\n  background: var(--surface2);\n  border: 1px solid var(--border);\n  border-radius: 10px;\n  padding: 0.8rem;\n  height: 180px;\n  position: relative;\n}\n\n.chart-wrap canvas { width: 100% !important; height: 100% !important; }\n\n/* ── Truth Table ── */\n.truth-table {\n  width: 100%;\n  border-collapse: collapse;\n  font-size: 0.75rem;\n  font-family: var(--font-mono);\n}\n\n.truth-table th {\n  text-align: center;\n  padding: 0.4rem 0.3rem;\n  color: var(--text-muted);\n  font-size: 0.65rem;\n  text-transform: uppercase;\n  letter-spacing: 0.06em;\n  border-bottom: 1px solid var(--border);\n}\n\n.truth-table td {\n  text-align: center;\n  padding: 0.45rem 0.3rem;\n  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);\n  transition: color 0.3s;\n}\n\n.truth-table tr:last-child td { border-bottom: none; }\n\n.truth-table .pred { font-weight: 700; color: var(--accent4); }\n\n.verdict {\n  font-size: 1rem;\n}\n\n.verdict.ok::after  { content: '✓'; color: var(--accent); }\n.verdict.bad::after { content: '✗'; color: var(--accent2); }\n\n/* ── Speed Control ── */\n.speed-control {\n  display: flex;\n  align-items: center;\n  gap: 0.6rem;\n  font-size: 0.72rem;\n  color: var(--text-muted);\n}\n\n.speed-control svg { width: 13px; height: 13px; stroke: var(--accent3); }\n.speed-control label { display: flex; align-items: center; gap: 0.4rem; white-space: nowrap; }\n.speed-control input { flex: 1; }\n.speed-control .mono { font-size: 0.72rem; min-width: 2.5rem; text-align: right; }\n\n/* ── Animations ── */\n@keyframes pulse-glow {\n  0%, 100% { box-shadow: 0 0 6px var(--accent); }\n  50% { box-shadow: 0 0 18px var(--accent); }\n}\n\n.stat__value.animated { animation: pulse-glow 0.6s ease; }\n\n@keyframes fadeSlideIn {\n  from { opacity: 0; transform: translateY(6px); }\n  to   { opacity: 1; transform: translateY(0); }\n}\n\n.panel { animation: fadeSlideIn 0.4s ease both; }\n.panel--controls { animation-delay: 0.05s; }\n.panel--chart    { animation-delay: 0.1s; }\n\n/* ── Responsive ── */\n@media (max-width: 900px) {\n  .main {\n    grid-template-columns: 1fr;\n  }\n}\n"}],"folders":[]},"variants":null,"createdAt":"2026-04-09T01:46:19.943Z","updatedAt":"2026-04-09T01:46:22.897Z","hasThumbnail":true}}