diff --git a/pygitweb/static/board.js b/pygitweb/static/board.js
new file mode 100644
index 0000000..600dade
--- /dev/null
+++ b/pygitweb/static/board.js
@@ -0,0 +1,87 @@
+(function() {
+  var template = document.getElementById('board-card-template');
+  var addCardButtons = document.querySelectorAll('.board-column-add-card');
+
+  function addDefaultCard(ev) {
+    if (!template || !template.content) return;
+    var column = ev.currentTarget.closest('.board-column');
+    var cardsContainer = column && column.querySelector('.board-column-cards');
+    if (!cardsContainer) return;
+    var clone = template.content.cloneNode(true);
+    var card = clone.querySelector && clone.querySelector('.board-card');
+    if (card) {
+      var titleEl = card.querySelector('.board-card-title');
+      if (titleEl) titleEl.textContent = 'New task';
+    }
+    cardsContainer.appendChild(clone);
+    var badge = column.querySelector('.board-column-add-card');
+    if (badge) {
+      var n = cardsContainer.querySelectorAll('.board-card').length;
+      badge.textContent = n;
+    }
+  }
+
+  addCardButtons.forEach(function(btn) {
+    btn.addEventListener('click', addDefaultCard);
+  });
+
+  var boardColumns = document.querySelector('.board-columns');
+  var dropzones = document.querySelectorAll('.board-column.dropzone');
+  var draggedCard = null;
+
+  function updateColumnBadge(column) {
+    var cardsContainer = column && column.querySelector('.board-column-cards');
+    var badge = column && column.querySelector('.board-column-add-card');
+    if (badge && cardsContainer) {
+      badge.textContent = cardsContainer.querySelectorAll('.board-card').length;
+    }
+  }
+
+  function handleDragStart(e) {
+    var card = e.target.closest && e.target.closest('.board-card');
+    if (!card) return;
+    draggedCard = card;
+    e.dataTransfer.effectAllowed = 'move';
+    e.dataTransfer.setData('text/plain', card.dataset.taskRef || '');
+    card.classList.add('opacity-50');
+  }
+  function handleDragEnd(e) {
+    var card = e.target.closest && e.target.closest('.board-card');
+    if (card) card.classList.remove('opacity-50');
+    draggedCard = null;
+  }
+
+  function handleDragOver(e) {
+    e.preventDefault();
+    e.dataTransfer.dropEffect = 'move';
+    e.currentTarget.classList.add('border-primary');
+  }
+  function handleDragLeave(e) {
+    e.currentTarget.classList.remove('border-primary');
+  }
+  function handleDrop(e) {
+    e.preventDefault();
+    e.currentTarget.classList.remove('border-primary');
+    if (!draggedCard) return;
+    var targetColumn = e.currentTarget.closest && e.currentTarget.closest('.board-column');
+    var targetCards = targetColumn && targetColumn.querySelector('.board-column-cards');
+    if (!targetCards) return;
+    var sourceColumn = draggedCard.closest && draggedCard.closest('.board-column');
+    if (sourceColumn === targetColumn) return;
+    draggedCard.dataset.taskStatus = targetColumn.dataset.status || '';
+    targetCards.appendChild(draggedCard);
+    updateColumnBadge(sourceColumn);
+    updateColumnBadge(targetColumn);
+    // TODO: POST to update task status on server (e.g. /project/.../tasks/update?task=... with body { status })
+  }
+
+  if (boardColumns) {
+    boardColumns.addEventListener('dragstart', handleDragStart);
+    boardColumns.addEventListener('dragend', handleDragEnd);
+  }
+  dropzones.forEach(function(el) {
+    el.addEventListener('dragover', handleDragOver);
+    el.addEventListener('dragleave', handleDragLeave);
+    el.addEventListener('drop', handleDrop);
+  });
+})();
diff --git a/pygitweb/templates/board.html b/pygitweb/templates/board.html
index e48355e..9d3695d 100644
--- a/pygitweb/templates/board.html
+++ b/pygitweb/templates/board.html
@@ -28,92 +28,4 @@
   {% endfor %}
 </div>
 
-<script>
-(function() {
-  var template = document.getElementById('board-card-template');
-  var addCardButtons = document.querySelectorAll('.board-column-add-card');
-
-  function addDefaultCard(ev) {
-    if (!template || !template.content) return;
-    var column = ev.currentTarget.closest('.board-column');
-    var cardsContainer = column && column.querySelector('.board-column-cards');
-    if (!cardsContainer) return;
-    var clone = template.content.cloneNode(true);
-    var card = clone.querySelector && clone.querySelector('.board-card');
-    if (card) {
-      var titleEl = card.querySelector('.board-card-title');
-      if (titleEl) titleEl.textContent = 'New task';
-    }
-    cardsContainer.appendChild(clone);
-    var badge = column.querySelector('.board-column-add-card');
-    if (badge) {
-      var n = cardsContainer.querySelectorAll('.board-card').length;
-      badge.textContent = n;
-    }
-  }
-
-  addCardButtons.forEach(function(btn) {
-    btn.addEventListener('click', addDefaultCard);
-  });
-
-  var boardColumns = document.querySelector('.board-columns');
-  var dropzones = document.querySelectorAll('.board-column.dropzone');
-  var draggedCard = null;
-
-  function updateColumnBadge(column) {
-    var cardsContainer = column && column.querySelector('.board-column-cards');
-    var badge = column && column.querySelector('.board-column-add-card');
-    if (badge && cardsContainer) {
-      badge.textContent = cardsContainer.querySelectorAll('.board-card').length;
-    }
-  }
-
-  function handleDragStart(e) {
-    var card = e.target.closest && e.target.closest('.board-card');
-    if (!card) return;
-    draggedCard = card;
-    e.dataTransfer.effectAllowed = 'move';
-    e.dataTransfer.setData('text/plain', card.dataset.taskRef || '');
-    card.classList.add('opacity-50');
-  }
-  function handleDragEnd(e) {
-    var card = e.target.closest && e.target.closest('.board-card');
-    if (card) card.classList.remove('opacity-50');
-    draggedCard = null;
-  }
-
-  function handleDragOver(e) {
-    e.preventDefault();
-    e.dataTransfer.dropEffect = 'move';
-    e.currentTarget.classList.add('border-primary');
-  }
-  function handleDragLeave(e) {
-    e.currentTarget.classList.remove('border-primary');
-  }
-  function handleDrop(e) {
-    e.preventDefault();
-    e.currentTarget.classList.remove('border-primary');
-    if (!draggedCard) return;
-    var targetColumn = e.currentTarget.closest && e.currentTarget.closest('.board-column');
-    var targetCards = targetColumn && targetColumn.querySelector('.board-column-cards');
-    if (!targetCards) return;
-    var sourceColumn = draggedCard.closest && draggedCard.closest('.board-column');
-    if (sourceColumn === targetColumn) return;
-    draggedCard.dataset.taskStatus = targetColumn.dataset.status || '';
-    targetCards.appendChild(draggedCard);
-    updateColumnBadge(sourceColumn);
-    updateColumnBadge(targetColumn);
-    // TODO: POST to update task status on server (e.g. /project/.../tasks/update?task=... with body { status })
-  }
-
-  if (boardColumns) {
-    boardColumns.addEventListener('dragstart', handleDragStart);
-    boardColumns.addEventListener('dragend', handleDragEnd);
-  }
-  dropzones.forEach(function(el) {
-    el.addEventListener('dragover', handleDragOver);
-    el.addEventListener('dragleave', handleDragLeave);
-    el.addEventListener('drop', handleDrop);
-  });
-})();
-</script>
+<script src="/static/board.js"></script>
