Models 44

gpt-oss-120b

by OpenAIReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
2 min 2 s
Cost
$0.0029
Output tokens
15,334
Spent thinking
12,196 tokens
Speed
126.6 tok/s
Console errors
0
First token after
647 ms
First visible text after
1 min 44 s
Prompt tokens
557
Code size
11.2 KB
Finish reason
stop
Served by
AkashML
Model id
openai/gpt-oss-120b
Reasoning sent
{"effort":"high"}
Max output
64,000 tokens
Generated
Nov 11, 2026

What it was asked

The prompt
Build an interactive Kanban board in vanilla HTML, CSS and JavaScript (no framework, no library). Requirements: - Three columns: "To do", "In progress", "Done", each showing its card count. - Pre-populate the board with 6 realistic cards spread across the columns so the first screenshot looks alive. - Each card has a title and a priority (low / medium / high) shown as a colored badge. - Add a card through a small modal form (title required), and delete a card with a button on it. - Drag and drop cards between columns with the mouse, with a clear visual drop indicator. - A search field that filters cards live by title. - A light / dark theme toggle. - State lives in memory only (no storage APIs).
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 418 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Kanban Board Demo</title>
<style>
/* Reset */
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
  font-family:Arial,Helvetica,sans-serif;
  background:var(--bg-color);
  color:var(--text-color);
  margin:0;
}
:root{
  --bg-color:#f5f5f5;
  --text-color:#222;
  --card-bg:#fff;
  --card-shadow:0 2px 4px rgba(0,0,0,.1);
  --badge-low-bg:#d4edda;   --badge-low-color:#155724;
  --badge-medium-bg:#fff3cd;--badge-medium-color:#856404;
  --badge-high-bg:#f8d7da;  --badge-high-color:#721c24;
  --accent-color:#007bff;
}
body.dark{
  --bg-color:#222;
  --text-color:#eee;
  --card-bg:#333;
  --card-shadow:0 2px 4px rgba(0,0,0,.5);
  --badge-low-bg:#155724;   --badge-low-color:#d4edda;
  --badge-medium-bg:#856404;--badge-medium-color:#fff3cd;
  --badge-high-bg:#721c24;  --badge-high-color:#f8d7da;
}
.top-controls{
  display:flex;
  flex-wrap:wrap;
  gap:.5rem;
  align-items:center;
  padding:.5rem;
  background:var(--card-bg);
  box-shadow:var(--card-shadow);
}
.top-controls input[type="search"]{
  flex:1;
  min-width:150px;
  padding:.4rem .6rem;
  border:1px solid #aaa;
  border-radius:4px;
}
.top-controls button{
  padding:.4rem .8rem;
  border:none;
  background:var(--accent-color);
  color:#fff;
  border-radius:4px;
  cursor:pointer;
}
.top-controls button:hover{opacity:.9}
.board{
  display:flex;
  flex-wrap:wrap;
  gap:1rem;
  padding:1rem;
  justify-content:center;
}
.column{
  flex:1 1 260px;
  max-width:350px;
  background:var(--card-bg);
  border-radius:5px;
  padding:.5rem;
  box-shadow:var(--card-shadow);
  display:flex;
  flex-direction:column;
}
.column h2{
  font-size:1.2rem;
  margin:.5rem 0;
  display:flex;
  justify-content:space-between;
  align-items:baseline;
}
.card-list{
  flex-grow:1;
  min-height:50px;
}
.card{
  background:var(--card-bg);
  border:1px solid #ccc;
  border-radius:4px;
  padding:.5rem .75rem;
  margin-bottom:.5rem;
  display:flex;
  justify-content:space-between;
  align-items:center;
  cursor:grab;
  user-select:none;
}
.card.dragging{opacity:.5}
.badge{
  border-radius:.25rem;
  padding:.1rem .4rem;
  font-size:.75rem;
  text-transform:capitalize;
  margin-right:.5rem;
}
.badge.low{background:var(--badge-low-bg);color:var(--badge-low-color)}
.badge.medium{background:var(--badge-medium-bg);color:var(--badge-medium-color)}
.badge.high{background:var(--badge-high-bg);color:var(--badge-high-color)}
.delete-btn{
  background:none;
  border:none;
  color:#999;
  cursor:pointer;
  font-size:1rem;
  line-height:1;
}
.delete-btn:hover{color:#e00}
.card-list.drag-over{
  border:2px dashed var(--accent-color);
  border-radius:5px;
}
.modal{
  position:fixed;
  inset:0;
  display:flex;
  align-items:center;
  justify-content:center;
  background:rgba(0,0,0,.5);
  visibility:hidden;
  opacity:0;
  transition:opacity .2s ease,visibility .2s;
}
.modal.show{
  visibility:visible;
  opacity:1;
}
.modal-content{
  background:var(--card-bg);
  padding:1rem;
  border-radius:5px;
  width:90%;
  max-width:400px;
  box-shadow:var(--card-shadow);
}
.modal-content h2{margin-bottom:.5rem}
.form-group{
  margin-bottom:.75rem;
  display:flex;
  flex-direction:column;
}
.form-group label{
  margin-bottom:.25rem;
  font-size:.9rem;
}
.form-group input,
.form-group select{
  padding:.4rem .6rem;
  border:1px solid #aaa;
  border-radius:4px;
}
.modal-actions{
  display:flex;
  justify-content:flex-end;
  gap:.5rem;
  margin-top:.5rem;
}
.modal-actions button{
  padding:.4rem .8rem;
  border:none;
  border-radius:4px;
  cursor:pointer;
}
.modal-actions button[type="submit"]{
  background:var(--accent-color);
  color:#fff;
}
.modal-actions .cancel-btn{
  background:#ccc;
  color:#222;
}
.hidden{display:none!important}
@media(max-width:600px){
  .column{flex:1 1 100%;max-width:none}
}
</style>
</head>
<body>
<div class="top-controls">
  <input type="search" id="searchInput" placeholder="Search cards…" autocomplete="off">
  <button id="addCardBtn">Add Card</button>
  <button id="themeToggle">🌙</button>
</div>
<div class="board" id="board">
  <div class="column" data-column="todo">
    <h2>To do <span class="count">(0)</span></h2>
    <div class="card-list" data-column="todo"></div>
  </div>
  <div class="column" data-column="inprogress">
    <h2>In progress <span class="count">(0)</span></h2>
    <div class="card-list" data-column="inprogress"></div>
  </div>
  <div class="column" data-column="done">
    <h2>Done <span class="count">(0)</span></h2>
    <div class="card-list" data-column="done"></div>
  </div>
</div>
<div class="modal" id="modal">
  <div class="modal-content">
    <h2>Add Card</h2>
    <form id="addCardForm">
      <div class="form-group">
        <label for="titleInput">Title</label>
        <input type="text" name="title" id="titleInput" required>
      </div>
      <div class="form-group">
        <label for="prioritySelect">Priority</label>
        <select name="priority" id="prioritySelect">
          <option value="low">Low</option>
          <option value="medium" selected>Medium</option>
          <option value="high">High</option>
        </select>
      </div>
      <div class="form-group">
        <label for="columnSelect">Column</label>
        <select name="column" id="columnSelect">
          <option value="todo">To do</option>
          <option value="inprogress">In progress</option>
          <option value="done">Done</option>
        </select>
      </div>
      <div class="modal-actions">
        <button type="submit">Add</button>
        <button type="button" class="cancel-btn">Cancel</button>
      </div>
    </form>
  </div>
</div>
<script>
(() => {
  // State
  let cards = [
    {id:1, title:"Design homepage", priority:"high", column:"todo"},
    {id:2, title:"Write documentation", priority:"medium", column:"inprogress"},
    {id:3, title:"Fix login bug", priority:"high", column:"todo"},
    {id:4, title:"Implement drag and drop", priority:"low", column:"inprogress"},
    {id:5, title:"Deploy to production", priority:"medium", column:"done"},
    {id:6, title:"User testing", priority:"low", column:"todo"}
  ];
  let nextId = 7;

  // Elements
  const searchInput = document.getElementById('searchInput');
  const addCardBtn = document.getElementById('addCardBtn');
  const modal = document.getElementById('modal');
  const addCardForm = document.getElementById('addCardForm');
  const cancelBtn = modal.querySelector('.cancel-btn');
  const themeToggle = document.getElementById('themeToggle');

  // Render board
  function renderBoard() {
    // clear lists
    document.querySelectorAll('.card-list').forEach(list => list.innerHTML = '');
    // create cards
    cards.forEach(card => {
      const cardEl = document.createElement('div');
      cardEl.className = 'card';
      cardEl.draggable = true;
      cardEl.dataset.id = card.id;
      cardEl.dataset.title = card.title;
      // drag events
      cardEl.addEventListener('dragstart', onDragStart);
      cardEl.addEventListener('dragend', onDragEnd);
      // title
      const titleDiv = document.createElement('div');
      titleDiv.className = 'card-title';
      titleDiv.textContent = card.title;
      // badge
      const badge = document.createElement('span');
      badge.className = `badge ${card.priority}`;
      badge.textContent = card.priority;
      // delete button
      const delBtn = document.createElement('button');
      delBtn.type = 'button';
      delBtn.className = 'delete-btn';
      delBtn.title = 'Delete';
      delBtn.innerHTML = '&times;';
      delBtn.addEventListener('click', () => deleteCard(card.id));
      // right container
      const right = document.createElement('div');
      right.style.display = 'flex';
      right.style.alignItems = 'center';
      right.appendChild(badge);
      right.appendChild(delBtn);
      // assemble
      cardEl.appendChild(titleDiv);
      cardEl.appendChild(right);
      // append to column
      const columnList = document.querySelector(`.card-list[data-column="${card.column}"]`);
      if (columnList) columnList.appendChild(cardEl);
    });
    // update counts
    document.querySelectorAll('.column').forEach(col => {
      const count = col.querySelectorAll('.card-list .card').length;
      const countSpan = col.querySelector('.count');
      if (countSpan) countSpan.textContent = `(${count})`;
    });
    // apply filter if any
    filterCards(searchInput.value);
  }

  // Drag handlers
  function onDragStart(e) {
    const cardId = e.currentTarget.dataset.id;
    e.dataTransfer.setData('text/plain', cardId);
    e.dataTransfer.effectAllowed = 'move';
    e.currentTarget.classList.add('dragging');
  }
  function onDragEnd(e) {
    e.currentTarget.classList.remove('dragging');
  }

  // Setup drop zones
  function setupDropZones() {
    document.querySelectorAll('.card-list').forEach(list => {
      list.addEventListener('dragover', e => {
        e.preventDefault();
        list.classList.add('drag-over');
      });
      list.addEventListener('dragleave', e => {
        if (!list.contains(e.relatedTarget)) {
          list.classList.remove('drag-over');
        }
      });
      list.addEventListener('drop', e => {
        e.preventDefault();
        list.classList.remove('drag-over');
        const cardId = parseInt(e.dataTransfer.getData('text/plain'),10);
        const targetColumn = list.dataset.column;
        moveCard(cardId, targetColumn);
      });
    });
  }

  // Move card
  function moveCard(id, newColumn) {
    const card = cards.find(c => c.id === id);
    if (card && card.column !== newColumn) {
      card.column = newColumn;
      renderBoard();
    }
  }

  // Delete card
  function deleteCard(id) {
    cards = cards.filter(c => c.id !== id);
    renderBoard();
  }

  // Add card
  function addCard({title, priority, column}) {
    cards.push({id:nextId++, title, priority, column});
    renderBoard();
  }

  // Filter cards
  function filterCards(query) {
    const q = query.trim().toLowerCase();
    document.querySelectorAll('.card').forEach(cardEl => {
      const title = cardEl.dataset.title.toLowerCase();
      if (q && !title.includes(q)) {
        cardEl.classList.add('hidden');
      } else {
        cardEl.classList.remove('hidden');
      }
    });
  }

  // Modal handling
  function showModal() {
    modal.classList.add('show');
    const titleInput = modal.querySelector('input[name="title"]');
    if (titleInput) titleInput.focus();
  }
  function hideModal() {
    modal.classList.remove('show');
    addCardForm.reset();
  }

  // Event listeners
  searchInput.addEventListener('input', e => filterCards(e.target.value));
  addCardBtn.addEventListener('click', showModal);
  cancelBtn.addEventListener('click', hideModal);
  modal.addEventListener('click', e => { if (e.target === modal) hideModal(); });
  addCardForm.addEventListener('submit', e => {
    e.preventDefault();
    const formData = new FormData(addCardForm);
    const title = formData.get('title').trim();
    if (!title) return;
    const priority = formData.get('priority');
    const column = formData.get('column');
    addCard({title, priority, column});
    hideModal();
  });
  themeToggle.addEventListener('click', () => {
    document.body.classList.toggle('dark');
    const isDark = document.body.classList.contains('dark');
    themeToggle.textContent = isDark ? '☀️' : '🌙';
  });

  // Initialize
  renderBoard();
  setupDropZones();
})();
</script>
</body>
</html>
Reasoning trace (12,196 tokens)
We need to output a single HTML file with all CSS and JS inline. Must satisfy the requirements.

We need a Kanban board with three columns, showing card count. Cards are prepopulated (6 cards across columns). Cards have title and priority (low/medium/high) with colored badge. Should be able to add a card via a modal. Delete button on each card. Drag and drop cards between columns with mouse, visual drop indicator. Search field filter cards live by title. Light/dark theme toggle. State in memory.

We need to make sure everything is visible on first paint, so we need prepopulated cards.

Also: "Games must start in a self-playing demo mode" not applicable. But must follow that first screenshot includes something.

Make sure we respect 1280x800 viewport, responsive down to 375px, no horizontal scroll.

All functions (drag-drop, add, delete, search, theme) should be functional.

Do not use any forbidden APIs.

Use modern JS (ES6). Need to create drag and drop event handlers: using HTML5 Drag and Drop API.

Potential approach: Each column is a <section> with class column, header with column name and count.

List of cards (div with class card) and data attributes for title and priority.

Badge color: Use CSS classes for low (green), medium (orange), high (red).

Modal: using a <dialog> element or a custom modal with overlay. Since not all browsers support <dialog>, but Chrome does. Use overlay <div id="modal"> with form.

Theme toggle: a button toggles a class on <body> (light/dark). CSS variables for colors.

Search: <input type="search"> that filters cards based on title. On input event, hide cards that do not match.

All state lives in memory: we maintain a JS array of cards, each with id, title, priority, column. When changes, re-render the board appropriately. Or we can directly manipulate DOM for simplicity. Since we need live filter, we can hide cards directly.

Better to maintain small array for state if needed.

Implement drag-and-drop: set draggable="true" on each card. On dragstart, set dataTransfer with card id. On dragover on column, preventDefault and add visual indicator (e.g., a dotted border). On drop, get card id, move card to column's container, update counts.

Visual drop indicator: Add class "drag-over" on column's card list container.

Add card: open modal, fill title, select priority (radio or select). On submit, create card with unique id (increment). Add to "To do" column by default? Could also have column selection. Let's keep selection for column.

Delete card: a delete button in each card perhaps an "X" icon.

Search field: filter cards based on title includes search text case-insensitive.

Theme toggle: a button toggles .dark on <html> or body. Provide CSS.

Make sure everything is visible initially: maybe the search field and add card button at top, theme toggle.

Make also responsive: using flex layout, columns wrap on smaller screens.

Let's design.

HTML skeleton:
<!DOCTYPE html><html lang="en"><head> etc.
In <style>: use CSS variables for colors, dark mode.

Design columns: container flex with .board class.

Each column with .column, header with column name and count <span class="count">N</span>.

Cards: <div class="card" draggable="true" data-id="..."><div class="card-title">Title</div><span class="badge priority-high">High</span><button class="delete-btn">✕</button></div>

Modal: <div id="modal" class="modal hidden"><div class="modal-content"><h2>Add Card</h2><form id="addCardForm">...</form></div></div>

Add button: <button id="addCardBtn">Add Card</button>

Search: <input type="search" id="searchInput" placeholder="Search cards...">

Theme toggle: <button id="themeToggle">🌙</button>

JS: Manage array of cards. Provide functions: renderBoard(), updateCounts(), addCard(), deleteCard(id), moveCard(id, column), filterCards(query), toggleTheme().

Implement drag events: set dataTransfer.setData('text/plain', id). On dragenter/dragover for column area, add class "drag-over". On drop, remove drag-over class, call moveCard. Remove dragover on dragleave.

Make board container have .board, each column .column has .card-list <div class="card-list"></div> to hold cards.

When rendering board, we should clear card-list and repopulate with cards in that column. Also update count.

Cards that are filtered out: either not rendered or hidden; simpler: hide cards with style display none based on title. For that, when filter input changes, loop through all cards in DOM, check if title includes query.

Alternatively, the renderBoard can accept filter. Better to hide rather than re-render fully.

For theme, set CSS variables for colors: --bg-color, --text-color, --card-bg, etc. Light: white background, dark: #2e2e2e.

Add CSS transitions for smoothness.

Make sure we add appropriate aria attributes for modal (role, aria-modal). Actually minimal.

Also ensure we have appropriate CSS for drop indicator: .card-list.drag-over { border: 2px dashed var(--accent-color); }

Add margin/padding.

Make sure not exceed viewport. Use width: calc(33.333% - 1rem) for columns.

Potential memory: we should give each card an auto-increment id.

Prepopulate 6 cards: array of objects with id 1-6, set titles and priorities and column.

Spacing: Add some minimal padding.

Now tasks:

1. Provide interactive board with dynamic counts.

2. Modal form: With fields: Title (input required), Priority (select low, medium, high), Column (select column). Add a submit button, cancel button.

3. Delete button on card: user can click to delete the card.

4. Drag and drop: implement as described.

5. Search field: filter live.

6. Theme toggle.

The page must be single file with code block.

Implementation details:

- Use an IIFE to avoid polluting global.

- Use document.querySelector for needed elements.

- Use event delegation for delete button: attach click event on board, but we can also directly attach to each delete button.

Better to attach event listener to each card's delete button when creating.

But for simplicity we can assign a class "delete-btn" and add a click event handler on the board (or on document) that checks if target.matches('.delete-btn').

Implement unique id generation: let nextId = 1; after prepopulate set nextId accordingly.

Define columns: "todo", "inprogress", "done" (lowercase keys). Mapping to column names.

Set column names displayed as "To do", "In progress", "Done".

Function getColumnName(key) returns display string.

We will store column property as 'todo', 'inprogress', 'done'.

For UI counts: The header includes column name and count. Ex: <h2>To do <span class="count">(3)</span></h2>.

Implementation steps:

1. Prepopulate cards array:

[
{id: 1, title: "Design homepage", priority: "high", column: "todo"},
{id: 2, title: "Write documentation", priority: "medium", column: "inprogress"},
{id: 3, title: "Fix login bug", priority: "high", column: "todo"},
{id: 4, title: "Implement drag and drop", priority: "low", column: "inprogress"},
{id: 5, title: "Deploy to production", priority: "medium", column: "done"},
{id: 6, title: "User testing", priority: "low", column: "todo"}
]

that's 6 cards.

Now needed behavior:

- On initial load, render board.

- On search input, filter: if search term is empty, show all; else hide those where title does not include term (case-insensitive).

- On delete, remove from array and re-render.

- On add card modal submit, add card to array and render.

- On drag and drop, update column property of card and render.

- Ensure counts update after each operation.

- Theme toggle: adds class to body 'dark' toggling overlay; also we need to set appropriate colors based on CSS custom properties.

Potential pitfalls:

- HTML drag and drop events need dataTransfer effect.

- Should handle dragging: using setData('text/plain', id). While dragover on column's .card-list we need to prevent default.

- On dragenter/dragover: add .drag-over class; on dragleave: remove.

- On drop: get id, find card, update column, remove .drag-over class, render.

- Also need to handle dragging a card onto column, not onto other cards. But we can allow dropping anywhere on column.

- On dragstart, maybe add class "dragging" to styling.

- For visual feedback when dragging a card, use CSS .dragging { opacity: 0.5; }

Let's plan CSS:

:root {
--bg-color: #f5f5f5;
--text-color: #222;
--card-bg: #fff;
--card-shadow: 0 2px 4px rgba(0,0,0,0.1);
--badge-low-bg: #d4edda;
--badge-low-color: #155724;
--badge-medium-bg: #fff3cd;
--badge-medium-color: #856404;
--badge-high-bg: #f8d7da;
--badge-high-color: #721c24;
--primary-color: #007bff;
--accent-color: #007bff;
}
body.dark {
--bg-color: #222;
--text-color: #eee;
--card-bg: #333;
--card-shadow: 0 2px 4px rgba(0,0,0,0.5);
--badge-low-bg: #155724;
--badge-low-color: #d4edda;
--badge-medium-bg: #856404;
--badge-medium-color: #fff3cd;
--badge-high-bg: #721c24;
--badge-high-color: #f8d7da;
}

Set background: body { background: var(--bg-color); color: var(--text-color); }

Columns: .board { display: flex; gap: 1rem; flex-wrap: wrap; justify-content: center; padding: 1rem; } .column { flex: 1; min-width: 250px; max-width: 350px; background: var(--card-bg); border-radius: 5px; padding: .5rem; box-shadow: var(--card-shadow); } .column h2 { margin: 0 0 .5rem; font-size: 1.2rem; display: flex; justify-content: space-between; align-items: baseline; }

.card-list { min-height: 50px; }

.card { background: var(--card-bg); border: 1px solid #ccc; border-radius: 4px; padding: .5rem .75rem; margin-bottom: .5rem; display: flex; justify-content: space-between; align-items: center; cursor: grab; }

.card.dragging { opacity: .5; }

.badge { border-radius: .25rem; padding: .1rem .4rem; font-size: .8rem; text-transform: capitalize; }

.badge.low { background: var(--badge-low-bg); color: var(--badge-low-color); }
.badge.medium { background: var(--badge-medium-bg); color: var(--badge-medium-color); }
.badge.high { background: var(--badge-high-bg); color: var(--badge-high-color); }

.delete-btn { background: none; border: none; color: #999; cursor: pointer; font-size: 1rem; margin-left: .5rem; }

.delete-btn:hover { color: #e00; }

Set .card-list.drag-over { border: 2px dashed var(--accent-color); padding: .5rem; }

Add top controls: .top-controls { display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin: .5rem; }

Right alignment for theme toggle.

Modal: .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.5); visibility: hidden; opacity: 0; transition: visibility 0s linear .2s, opacity .2s; } .modal.show { visibility: visible; opacity: 1; transition-delay: 0s; } .modal-content { background: var(--card-bg); padding: 1rem; border-radius: 5px; width: 90%; max-width: 400px; }

Form: <label> Title <input type="text" name="title" required> </label> <label> Priority <select name="priority"> ... </select> </label> <label> Column <select name="column">...</select> </label> <div class="modal-actions"> <button type="submit">Add</button> <button type="button" class="cancel-btn">Cancel</button> </div>

Make sure styling for label and inputs.

Add simple responsive adjustments.

Implement event listeners:

document.addEventListener('DOMContentLoaded', () => { ... });

In init: set up DOM elements references, prepopulate array, render board. Add event listeners for search input, add card button, theme toggle, modal form submit, cancel. Also drag events.

Implementation details:

- For drag events: when rendering a card, set attributes draggable=true, dataset.id = id. Add event listeners: addEventListener('dragstart', onDragStart); ondragend maybe remove dragging class.

- For column's card-list element: addEventListener('dragover', onDragOver); addEventListener('dragenter', onDragEnter); addEventListener('dragleave', onDragLeave); addEventListener('drop', onDrop).

- In the dragstart, add class "dragging" to card.

- In dragend, remove class dragging.

- In dragenter? we need to check that the target is the card-list or maybe the column container, but we can add these events to the .card-list container.

- The drop event: identify the card id via dataTransfer.getData('text/plain'), find the card object, update its column = columnKey of that column (maybe store data-key on column element). Then render.

- Visual. .drag-over class on .card-list.

Potential issues: When dragging a card over the column's container, we may get dragenter events for each child; may cause flickering of drag-over. Use dragenter and dragleave carefully with counters.

Simplify: Use dragover to add class "drag-over" if not present, and remove on dragleave and drop.

Better: In dragenter, add class .drag-over, but it's added each time we re-enter a child, may cause removal early; to avoid complexity, we can add class on dragover and drop. Or use setTimeout to remove after leaving. For simplicity, we can just add .drag-over on dragover (which fires repeatedly) and remove on dragleave & drop. But dragleave may fire when entering card elements inside the container, causing flicker. Another approach: Use "dragover" on the container and have a remote element overlay or not worry about flicker.

Simplify: Add class to .card-list when dragover on container; remove after drop.

I think we can manage fine.

Now: Live search filter: As we type, hide cards that don't match. To efficiently filter, we can set a CSS class .hidden { display: none !important; } on .card elements that don't match.

Add listener: searchInput.addEventListener('input', () => { const query = searchInput.value.trim().toLowerCase(); document.querySelectorAll('.card').forEach(card => { const title = card.dataset.title.toLowerCase(); if (title.includes(query)) { card.classList.remove('hidden'); } else { card.classList.add('hidden'); } }); });

We need to store title in data attribute for quick access: set data-title attribute.

Alternative is to read .card-title text.

Now, to update counts: Need to compute count per column ignoring hidden (should count visible? Probably count all cards irrespective of filter). I think counts should reflect all cards in column, not filtered. Maybe hide .count updates after filter? Typically counts counts all cards. May be nice to indicate filtered results, but not required. We'll show counts of total cards per column.

Implement function updateCounts(): iterate over columns definitions, find .column corresponding to column key (maybe using data-key attribute), count cards (including hidden?) Should just count all cards in column's card-list container (including hidden). Simpler: container.querySelectorAll('.card').length.

Now, potentially the filter may hide some cards, but counts remain same.

Add responsive CSS: columns as flex; at narrow viewport (<600px) we can flex column to 100% width.

Add search field at top.

Add theme toggle button with icons: maybe use "☀️" for light, "🌙" for dark.

Implement theme toggle: On toggle click, toggle class 'dark' on body; also update button text accordingly.

Add Modal: when clicking Add Card button, show modal by adding class 'show' to .modal. The modal overlay intercepts click outside form maybe, but we can just have cancel button.

Clicking overlay also close modal? Add event listener to modal with click (if target===modal) close.

Implement form handling:

document.getElementById('addCardForm').addEventListener('submit', e => { e.preventDefault(); const form = e.target; const title = form.title.value.trim(); if (!title) return; const priority = form.priority.value; const column = form.column.value; addCard({title, priority, column}); form.reset(); hideModal(); });

Define addCard function: pushes new card object: {id: nextId++, title, priority, column}, then renderBoard.

Delete: function deleteCard(id) { cards = cards.filter(c=>c.id!==id); renderBoard(); } Attach deletion via event delegation: boardElement.addEventListener('click', e => { if (e.target.matches('.delete-btn')) { const cardEl = e.target.closest('.card'); const id = parseInt(cardEl.dataset.id); deleteCard(id); } });

Now, for drag start: cardElement.addEventListener('dragstart', e => { e.dataTransfer.setData('text/plain', cardEl.dataset.id); e.dataTransfer.effectAllowed = 'move'; cardEl.classList.add('dragging'); });

On dragend: cardEl.classList.remove('dragging');

On column .card-list drop: get id, find card in array, update column to columnKey; then renderBoard.

Or for performance we could just move element in DOM, but easier to update state.

Now, no external network requests; everything inline.

Make sure we don't use setTimeout or other unallowable APIs; those are fine.

Now incorporate all.

Edge Cases: When dragging, we need to identify which column the drop belongs to. Set data-key on column .card-list, e.g., data-column="todo". Then in drop handler, get columnKey = e.currentTarget.dataset.column.

Now plan out HTML structure:

<body>
<div class="top-controls">
 <input type="search" id="searchInput" placeholder="Search cards..." autocomplete="off">
 <button id="addCardBtn">Add Card</button>
 <button id="themeToggle">🌙</button>
</div>
<div class="board" id="board">
 <div class="column" data-column="todo">
   <h2>To do <span class="count">(0)</span></h2>
   <div class="card-list"></div>
 </div>
 <div class="column" data-column="inprogress">
   <h2>In progress <span class="count">(0)</span></h2>
   <div class="card-list"></div>
 </div>
 <div class="column" data-column="done">
   <h2>Done <span class="count">(0)</span></h2>
   <div class="card-list"></div>
 </div>
</div>

<div class="modal" id="modal">
 <div class="modal-content">
   <h2>Add Card</h2>
   <form id="addCardForm">
     <div class="form-group">
       <label>Title</label>
       <input type="text" name="title" required>
     </div>
     <div class="form-group">
       <label>Priority</label>
       <select name="priority">
         <option value="low">Low</option>
         <option value="medium">Medium</option>
         <option value="high">High</option>
       </select>
     </div>
     <div class="form-group">
       <label>Column</label>
       <select name="column">
         <option value="todo">To do</option>
         <option value="inprogress">In progress</option>
         <option value="done">Done</option>
       </select>
     </div>
     <div class="modal-actions">
       <button type="submit">Add</button>
       <button type="button" class="cancel-btn">Cancel</button>
     </div>
   </form>
 </div>
</div>

<script> // we will define the JS here.

All code.

Now we need to consider the requirement: "The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar."

Thus need to ensure overflow hidden or header not too wide. Use flex-wrap and ensure card width is not larger than container.

We'll set .board's max-width: 100%; and columns have min-width 260px ensures at narrow screens each column stacks.

At 375px, maybe only one column visible stacked; user can scroll vertically but no horizontal.

Set body margin: 0; font-family.

Now 'state lives in memory only (no storage APIs)', we don't use localStorage.

Make sure we avoid alert etc.

Now final piece: theme toggle: display emojis.

Implementation: const themeToggle = document.getElementById('themeToggle'); themeToggle.addEventListener('click', () => { document.body.classList.toggle('dark'); const isDark = document.body.classList.contains('dark'); themeToggle.textContent = isDark ? '☀️' : '🌙'; });

Initialize theme to light (no dark class). Set button's initial text to '🌙' indicating click to go dark.

Also we might consider to detect prefers-color-scheme? Not necessary.

Now about visual drop indicator: we can make .card-list.drag-over { background: var(--accent-bg, #e0f7ff); } Could just add dashed border.

Add CSS:

.card-list.drag-over {
 border: 2px dashed var(--accent-color);
 border-radius: 5px;
}

We need to set .card-list a little padding. Already.

Make .card to have width 100% so that it fill container.

Add to .card: display: flex; justify-content: space-between; align-items: center; user-select: none.

Add a small icon for Delete: "<button class='delete-btn' title='Delete'>✕</button>"

Now after we remove card, re-render board.

Also we have to consider drag events: When dragging card over column, we need to show visual indicator. We'll add class to column's .card-list.

Implementation code for dragover: e.preventDefault(); e.currentTarget.classList.add('drag-over'); // Optionally set dropEffect.

For dragleave: Check if relatedTarget is inside the .card-list? Or just remove class after dragover ends? Use dragenter/dragleave but may cause flicker. Maybe use "dragover" to keep indicator; on drop remove; but if we leave without dropping, you might need to detect "dragleave" for the container. We'll add "dragleave" handler that removes class.

dragleave event: e.currentTarget.classList.remove('drag-over');

But due to entering children may cause removal prematurely. To address that, we can check if e.relatedTarget is the .card-list or if it is a child of .card-list; if relatedTarget is null, we may remove. Or we can use counter logic: increment on dragenter, decrement on dragleave, and when counter is zero remove class. Simpler: we can just ignore flicker; it's ok.

Better: use "dragover" to always add, "drop" to remove.

Implement<|reserved_200697|>:

For each .card-list:

cardList.addEventListener('dragover', e => { e.preventDefault(); e.currentTarget.classList.add('drag-over'); });

cardList.addEventListener('dragleave', e => { // if leaving to a child? Use e.currentTarget.contains(e.relatedTarget) to check
if (!e.currentTarget.contains(e.relatedTarget)) {
 e.currentTarget.classList.remove('drag-over');
}
});

On drop: e => { e.preventDefault(); e.currentTarget.classList.remove('drag-over'); const id = e.dataTransfer.getData('text/plain'); const columnKey = e.currentTarget.dataset.column; moveCard(...); }

Make sure to use e.dataTransfer.getData('text') not 'text/plain'? Actually we used setData('text/plain', id). So getData('text/plain').

Now implement moveCard(id, newColumn) updating card's column and calling renderBoard().

Now for renderBoard():

function renderBoard() {
 const board = document.getElementById('board');
 // Clear card lists
 document.querySelectorAll('.card-list').forEach(list => { list.innerHTML = ''; });
 // For each card, create DOM element and add to appropriate column.
 cards.forEach(card => {
   const cardEl = document.createElement('div');
   cardEl.className = 'card';
   cardEl.setAttribute('draggable', 'true');
   cardEl.dataset.id = card.id;
   cardEl.dataset.title = card.title; // for search
   // card handles drag events
   cardEl.addEventListener('dragstart', onDragStart);
   cardEl.addEventListener('dragend', onDragEnd);
   // inner content
   const titleDiv = document.createElement('div');
   titleDiv.className = 'card-title';
   titleDiv.textContent = card.title;

   const badge = document.createElement('span');
   badge.className = `badge ${card.priority}`;
   badge.textContent = card.priority;

   const deleteBtn = document.createElement('button');
   deleteBtn.className = 'delete-btn';
   deleteBtn.title = 'Delete';
   deleteBtn.innerHTML = '&times;'; // ×

   // container for badge and delete
   const rightDiv = document.createElement('div');
   rightDiv.style.display = 'flex';
   rightDiv.style.alignItems = 'center';
   rightDiv.appendChild(badge);
   rightDiv.appendChild(deleteBtn);

   // Append title and rightDiv
   cardEl.appendChild(titleDiv);
   cardEl.appendChild(rightDiv);

   // Append to column
   const columnList = document.querySelector(`.column[data-column="${card.column}"] .card-list`);
   if (columnList) columnList.appendChild(cardEl);
 });
 // Update counts
 document.querySelectorAll('.column').forEach(col => {
   const key = col.dataset.column;
   const count = col.querySelectorAll('.card-list .card').length;
   const countSpan = col.querySelector('.count');
   if (countSpan) countSpan.textContent = `(${count})`;
 });
 // After render, apply current filter if any
 const query = searchInput.value.trim().toLowerCase();
 document.querySelectorAll('.card').forEach(cardEl => {
   const title = cardEl.dataset.title.toLowerCase();
   if (query && !title.includes(query)) {
     cardEl.classList.add('hidden');
   } else {
     cardEl.classList.remove('hidden');
   }
 });
}

But using searchInput is defined later; we need to have reference globally.

Alternatively, after render we can call filterCards().

Will implement filterCards(query) separately.

Now it's efficient enough for small number of cards.

Now the functions:

let cards = [ prepopulated objects ];
let nextId = 7; // after prepopulated

function addCard({title, priority, column}) { cards.push({id: nextId++, title, priority, column}); renderBoard(); }

function deleteCard(id) { cards = cards.filter(c=>c.id !== id); renderBoard(); }

function moveCard(id, newColumn) { const card = cards.find(c=>c.id===id); if (card) { card.column = newColumn; renderBoard(); } }

function filterCards(query) { const lower = query.trim().toLowerCase(); document.querySelectorAll('.card').forEach(cardEl => { const title = cardEl.dataset.title.toLowerCase(); if (lower && !title.includes(lower)) { cardEl.classList.add('hidden'); } else { cardEl.classList.remove('hidden'); } }); }

We'll attach to input event: searchInput.addEventListener('input', e => { filterCards(e.target.value); });

Now, event listeners for adding card: addCardBtn adds show modal.

Now code for modal:

function showModal() { modal.classList.add('show'); modal.querySelector('input[name="title"]').focus(); }

function hideModal() { modal.classList.remove('show'); }

Add events: addCardBtn.addEventListener('click', showModal); modal?.addEventListener('click', e => { if (e.target === modal) hideModal(); }); cancelBtn.addEventListener('click', hideModal);

In the form submit handler, after addCard, hide modal.

Now we need later to include proper focus for accessibility; not necessary.

Now the rest.

Potential CSS for hidden class:

.hidden { display: none !important; }

Now define CSS globally.

Now incorporate everything in a single HTML file.

Make sure we have no external scripts.

Consider using minimal CSS resets.

Now code.

First import:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Kanban Board Demo</title>
<style>
/* Reset */
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
 font-family: Arial,Helvetica,sans-serif;
 background: var(--bg-color);
 color: var(--text-color);
 margin: 0;
}
:root {
 --bg-color: #f5f5f5;
 --text-color: #222;
 --card-bg: #fff;
 --card-shadow: 0 2px 4px rgba(0,0,0,0.1);
 --badge-low-bg: #d4edda;
 --badge-low-color: #155724;
 --badge-medium-bg: #fff3cd;
 --badge-medium-color: #856404;
 --badge-high-bg: #f8d7da;
 --badge-high-color: #721c24;
 --accent-color: #007bff;
}
body.dark {
 --bg-color: #222;
 --text-color: #eee;
 --card-bg: #333;
 --card-shadow: 0 2px 4px rgba(0,0,0,0.5);
 --badge-low-bg: #155724;
 --badge-low-color: #d4edda;
 --badge-medium-bg: #856404;
 --badge-medium-color: #fff3cd;
 --badge-high-bg: #721c24;
 --badge-high-color: #f8d7da;
}
.top-controls {
 display: flex;
 flex-wrap: wrap;
 gap: .5rem;
 align-items: center;
 padding: .5rem;
 background: var(--card-bg);
 box-shadow: var(--card-shadow);
}
.top-controls input[type="search"] {
 flex: 1;
 min-width: 150px;
 padding: .4rem .6rem;
 border: 1px solid #aaa;
 border-radius: 4px;
}
.top-controls button {
 padding: .4rem .8rem;
 border: none;
 background: var(--accent-color);
 color: #fff;
 border-radius: 4px;
 cursor: pointer;
}
.top-controls button:hover {
 opacity: .9;
}
.board {
 display: flex;
 flex-wrap: wrap;
 gap: 1rem;
 padding: 1rem;
 justify-content: center;
}
.column {
 flex: 1 1 260px;
 max-width: 350px;
 background: var(--card-bg);
 border-radius: 5px;
 padding: .5rem;
 box-shadow: var(--card-shadow);
 display: flex;
 flex-direction: column;
}
.column h2 {
 font-size: 1.2rem;
 margin: .5rem 0;
 display: flex;
 justify-content: space-between;
 align-items: baseline;
}
.card-list {
 flex-grow: 1;
 min-height: 50px;
}
.card {
 background: var(--card-bg);
 border: 1px solid #ccc;
 border-radius: 4px;
 padding: .5rem .75rem;
 margin-bottom: .5rem;
 display: flex;
 justify-content: space-between;
 align-items: center;
 cursor: grab;
 user-select: none;
}
.card.dragging {
 opacity: .5;
}
.badge {
 border-radius: .25rem;
 padding: .1rem .4rem;
 font-size: .75rem;
 text-transform: capitalize;
 margin-right: .5rem;
}
.badge.low { background: var(--badge-low-bg); color: var(--badge-low-color); }
.badge.medium { background: var(--badge-medium-bg); color: var(--badge-medium-color); }
.badge.high { background: var(--badge-high-bg); color: var(--badge-high-color); }
.delete-btn {
 background: none;
 border: none;
 color: #999;
 cursor: pointer;
 font-size: 1rem;
 line-height: 1;
}
.delete-btn:hover {
 color: #e00;
}
.card-list.drag-over {
 border: 2px dashed var(--accent-color);
 border-radius: 5px;
}
.modal {
 position: fixed;
 inset: 0;
 display: flex;
 align-items: center;
 justify-content: center;
 background: rgba(0,0,0,0.5);
 visibility: hidden;
 opacity: 0;
 transition: opacity .2s ease, visibility .2s;
}
.modal.show {
 visibility: visible;
 opacity: 1;
}
.modal-content {
 background: var(--card-bg);
 padding: 1rem;
 border-radius: 5px;
 width: 90%;
 max-width: 400px;
 box-shadow: var(--card-shadow);
}
.modal-content h2 {
 margin-bottom: .5rem;
}
.form-group {
 margin-bottom: .75rem;
 display: flex;
 flex-direction: column;
}
.form-group label {
 margin-bottom: .25rem;
 font-size: .9rem;
}
.form-group input,
.form-group select {
 padding: .4rem .6rem;
 border: 1px solid #aaa;
 border-radius: 4px;
}
.modal-actions {
 display: flex;
 justify-content: flex-end;
 gap: .5rem;
 margin-top: .5rem;
}
.modal-actions button {
 padding: .4rem .8rem;
 border: none;
 border-radius: 4px;
 cursor: pointer;
}
.modal-actions button[type="submit"] {
 background: var(--accent-color);
 color: #fff;
}
.modal-actions .cancel-btn {
 background: #ccc;
 color: #222;
}
.hidden { display: none !important; }
@media (max-width: 600px) {
 .column { flex: 1 1 100%; max-width: none; }
}
</style>
</head>
<body>
<div class="top-controls">
 <input type="search" id="searchInput" placeholder="Search cards…" autocomplete="off">
 <button id="addCardBtn">Add Card</button>
 <button id="themeToggle">🌙</button>
</div>
<div class="board" id="board">
 <div class="column" data-column="todo">
   <h2>To do <span class="count">(0)</span></h2>
   <div class="card-list" data-column="todo"></div>
 </div>
 <div class="column" data-column="inprogress">
   <h2>In progress <span class="count">(0)</span></h2>
   <div class="card-list" data-column="inprogress"></div>
 </div>
 <div class="column" data-column="done">
   <h2>Done <span class="count">(0)</span></h2>
   <div class="card-list" data-column="done"></div>
 </div>
</div>
<div class="modal" id="modal">
 <div class="modal-content">
   <h2>Add Card</h2>
   <form id="addCardForm">
     <div class="form-group">
       <label for="titleInput">Title</label>
       <input type="text" name="title" id="titleInput" required>
     </div>
     <div class="form-group">
       <label for="prioritySelect">Priority</label>
       <select name="priority" id="prioritySelect">
         <option value="low">Low</option>
         <option value="medium" selected>Medium</option>
         <option value="high">High</option>
       </select>
     </div>
     <div class="form-group">
       <label for="columnSelect">Column</label>
       <select name="column" id="columnSelect">
         <option value="todo">To do</option>
         <option value="inprogress">In progress</option>
         <option value="done">Done</option>
       </select>
     </div>
     <div class="modal-actions">
       <button type="submit">Add</button>
       <button type="button" class="cancel-btn">Cancel</button>
     </div>
   </form>
 </div>
</div>
<script>
(() => {
 // State
 let cards = [
  {id: 1, title: "Design homepage", priority: "high", column: "todo"},
  {id: 2, title: "Write documentation", priority: "medium", column: "inprogress"},
  {id: 3, title: "Fix login bug", priority: "high", column: "todo"},
  {id: 4, title: "Implement drag and drop", priority: "low", column: "inprogress"},
  {id: 5, title: "Deploy to production", priority: "medium", column: "done"},
  {id: 6, title: "User testing", priority: "low", column: "todo"}
 ];
 let nextId = 7;

 // Elements
 const searchInput = document.getElementById('searchInput');
 const addCardBtn = document.getElementById('addCardBtn');
 const modal = document.getElementById('modal');
 const addCardForm = document.getElementById('addCardForm');
 const cancelBtn = modal.querySelector('.cancel-btn');
 const themeToggle = document.getElementById('themeToggle');

 // Render board
 function renderBoard() {
   // clear lists
   document.querySelectorAll('.card-list').forEach(list => list.innerHTML = '');
   // create cards
   cards.forEach(card => {
     const cardEl = document.createElement('div');
     cardEl.className = 'card';
     cardEl.draggable = true;
     cardEl.dataset.id = card.id;
     cardEl.dataset.title = card.title;
     // drag events
     cardEl.addEventListener('dragstart', onDragStart);
     cardEl.addEventListener('dragend', onDragEnd);
     // title
     const titleDiv = document.createElement('div');
     titleDiv.className = 'card-title';
     titleDiv.textContent = card.title;
     // badge
     const badge = document.createElement('span');
     badge.className = `badge ${card.priority}`;
     badge.textContent = card.priority;
     // delete button
     const delBtn = document.createElement('button');
     delBtn.type = 'button';
     delBtn.className = 'delete-btn';
     delBtn.title = 'Delete';
     delBtn.innerHTML = '&times;';
     delBtn.addEventListener('click', () => deleteCard(card.id));
     // right side container
     const right = document.createElement('div');
     right.style.display = 'flex';
     right.style.alignItems = 'center';
     right.appendChild(badge);
     right.appendChild(delBtn);
     // assemble
     cardEl.appendChild(titleDiv);
     cardEl.appendChild(right);
     // append to column
     const columnList = document.querySelector(`.card-list[data-column="${card.column}"]`);
     if (columnList) columnList.appendChild(cardEl);
   });
   // update counts
   document.querySelectorAll('.column').forEach(col => {
     const key = col.dataset.column;
     const count = col.querySelectorAll('.card-list .card').length;
     const countSpan = col.querySelector('.count');
     if (countSpan) countSpan.textContent = `(${count})`;
   });
   // apply filter if any
   filterCards(searchInput.value);
 }

 // Drag handlers
 function onDragStart(e) {
   const cardId = e.currentTarget.dataset.id;
   e.dataTransfer.setData('text/plain', cardId);
   e.dataTransfer.effectAllowed = 'move';
   e.currentTarget.classList.add('dragging');
 }
 function onDragEnd(e) {
   e.currentTarget.classList.remove('dragging');
 }

 // Setup column drop zones
 function setupDropZones() {
   document.querySelectorAll('.card-list').forEach(list => {
     list.addEventListener('dragover', e => {
       e.preventDefault(); // allow drop
       list.classList.add('drag-over');
     });
     list.addEventListener('dragleave', e => {
       // only remove if leaving the list entirely
       if (!list.contains(e.relatedTarget)) {
         list.classList.remove('drag-over');
       }
     });
     list.addEventListener('drop', e => {
       e.preventDefault();
       list.classList.remove('drag-over');
       const cardId = parseInt(e.dataTransfer.getData('text/plain'), 10);
       const targetColumn = list.dataset.column;
       moveCard(cardId, targetColumn);
     });
   });
 }

 // Move card to new column
 function moveCard(id, newColumn) {
   const card = cards.find(c => c.id === id);
   if (card && card.column !== newColumn) {
     card.column = newColumn;
     renderBoard();
   }
 }

 // Delete card
 function deleteCard(id) {
   cards = cards.filter(c => c.id !== id);
   renderBoard();
 }

 // Add card
 function addCard({title, priority, column}) {
   cards.push({id: nextId++, title, priority, column});
   renderBoard();
 }

 // Filter cards
 function filterCards(query) {
   const q = query.trim().toLowerCase();
   document.querySelectorAll('.card').forEach(cardEl => {
     const title = cardEl.dataset.title.toLowerCase();
     if (q && !title.includes(q)) {
       cardEl.classList.add('hidden');
     } else {
       cardEl.classList.remove('hidden');
     }
   });
 }

 // Modal handling
 function showModal() {
   modal.classList.add('show');
   // focus title input
   const titleInput = modal.querySelector('input[name="title"]');
   if (titleInput) titleInput.focus();
 }
 function hideModal() {
   modal.classList.remove('show');
   addCardForm.reset();
 }

 // Event listeners
 searchInput.addEventListener('input', e => filterCards(e.target.value));
 addCardBtn.addEventListener('click', showModal);
 cancelBtn.addEventListener('click', hideModal);
 // clicking outside modal-content closes
 modal.addEventListener('click', e => {
   if (e.target === modal) hideModal();
 });
 addCardForm.addEventListener('submit', e => {
   e.preventDefault();
   const formData = new FormData(addCardForm);
   const title = formData.get('title').trim();
   if (!title) return;
   const priority = formData.get('priority');
   const column = formData.get('column');
   addCard({title, priority, column});
   hideModal();
 });
 themeToggle.addEventListener('click', () => {
   document.body.classList.toggle('dark');
   const isDark = document.body.classList.contains('dark');
   themeToggle.textContent = isDark ? '☀️' : '🌙';
 });

 // Initialize
 renderBoard();
 setupDropZones();
})();
</script>
</body>
</html>
Now final step: output the HTML within a fenced code block, type as ```html.

Check for any missing elements: ensure all required features. Let's go through requirements and verify:

- Three columns: "To do", "In progress", "Done", each showing its card count. Yes, columns have h2 with count.

- Pre-populate board with 6 realistic cards across columns: Yes, cards array has 6 cards.

- Each card has title and priority (low / medium / high) shown as colored badge: yes.

- Add card through modal form (title required): yes. Form includes title, priority, column. Title required.

- Delete a card with button: yes.

- Drag and drop cards between columns with clear visual drop indicator: using dragover adding class "drag-over" with dashed border.

- Search field filters cards live by title: yes.

- Light/dark theme toggle: button toggles class .dark.

- State lives in memory only: yes.

- Responsive: CSS includes flex-wrap, media query for small width.

- No external resources.

- All visible on first paint: initial render with 6 cards.

Potential improvements: The modal is hidden initially; it's fine. The page shows all components.

Potential issues: The dragover event adds class "drag-over" every time; we need to remove on dragleave. I used condition `if (!list.contains(e.relatedTarget))`. That should work when leaving out of the container entirely. However, as we drag over child elements, the dragenter/begin is proper. There's a small possibility of flicker but acceptable.

The drop target usage: list dataset column is set on each .card-list. Yes.

One more check: In the HTML code, .column data-column set; the .card-list also has data-column attribute. Both are used: .card-list data-column for drop target; .column data-column used for count but we haven't used column dataset except maybe for find.

We used col.dataset.column for count.

All is consistent.

Now check that .card-list dragover has correct style: .card-list.drag-over border.

Make sure .card-list has some padding to show border.

Let's double-check CSS: .card-list.drag-over border:2px dashed var(--accent-color) border-radius:5px; That's visible.

Now see that there is no overflow. .board has padding and flex; .column max-width:350px and min width 260px. On small screens, using media query to set column flex 1 1 100%, and max-width none.

Should be fine.

Check that top-controls area does not overflow on overflow. It's flex-wrap.

The search input will stretch to fill space due to flex:1.

Now check that JavaScript references all elements correctly: searchInput exists; addCardBtn exists; modal etc.

But `cancelBtn` is defined via `modal.querySelector('.cancel-btn')`. In HTML, `<button type="button" class="cancel-btn">Cancel</button>` is indeed inside modal (modal-content). So it's fine.

The `themeToggle` toggles between 🌙 and ☀️.

Potential bug: In the CSS variables for dark mode, we have `--badge-low-bg` and `--badge-low-color` swapped: In dark mode, want low priority badge to be perhaps different; but it's fine.

Nice.

Potential memory: In initial call renderBoard, then setupDropZones, we call renderBoard then use document.querySelectorAll('.card-list') to add event listeners inside setupDropZones. That's fine.

Potential drag events: The card itself is created each render, addEventListener for dragstart,dragend attached each time. This way everything works.

Potential performance: small number of cards.

Now check the fact that we used arrow functions inside IIFE with non-escaped string. Fine.

Check the use of .trim().toLowerCase() for filter.

Now check walkway for theme toggle: The toggle button updates its text after toggling.

Potential missing else for initial theme text: maybe we want to display 🌙 initially meaning click to switch to dark. Already themeToggle text is set to 🌙 in HTML. Good.

Now test the code via mental simulation.

On load:

- Body loads, CSS applied.

- Top controls: search input (empty), add button, theme toggle (🌙). Board with 3 columns with counts (0 initially), but after renderBoard, they will update.

- renderBoard clears card-list: they were empty initially, okay.

- For each of 6 cards, create a .card element. Title etc, set drag events.

- Append each to appropriate column's .card-list.

- After that, update counts per column (6 cards distribution: we have cards: id1 todo; id2 inprogress; id3 todo; id4 inprogress; id5 done; id6 todo. So counts: todo=3, inprogress=2, done=1.

- Then filterCards searchInput.value (empty) => no hide.

Thus initial board shows 3 cards in To do, 2 in In progress, 1 in Done.

- Some cards with priority "high"/"medium"/"low", colored badges.

- Delete buttons appear.

- Dragging each card works.

Now search input uses filterCards on each input.

Add Card button shows modal.

Modal has fields: title (required), priority (select default medium), column (select default todo).

On submit, adds a card with new id, nextId increments.

Now drag and drop with visual indicator.

Now theme toggle toggles dark.

Now responsive: At viewport less than 600px, columns become 100% width; they wrap.

Now check: the dragover event attaches to each .card-list. When you drag a card and go over the list area, the event triggers and adds class "drag-over". On dragleave, we remove class when leaving the list entirely. Implementation: `if (!list.contains(e.relatedTarget))`. If moving from the list to its descendants (like cards), e.relatedTarget may be a child inside the list; but .contains returns true, and we not remove. However, if you move from child to list? Actually dragleave fires when mouse leaves the element or its descendants. So if you move from a child to the list (i.e., moving from a card to the list but still inside, maybe no dragleave event?). But anyway, it's fine.

Potential requirement: "clear visual drop indicator". Our dashed border should suffice.

Potential requirement: "counts update when moving" - we update counts on renderBoard. When moving a card, we simply modify card.column and call renderBoard which updates counts.

Potential requirement: "State lives in memory only": we use variable cards.

Potential requirement: "Search field filters cards live by title": we hide cards in DOM but not remove them, so they remain counted in column count.

Potential requirement: Provide a visual difference for priority: using colored badges.

Potential requirement: "Add a card through a small modal form (title required)" we have small modal.

Potential requirement: "Delete a card with a button on it": done.

Potential requirement: "Drag and drop cards between columns with the mouse, with a clear visual drop indicator": done.

Potential requirement: "Light / dark theme toggle": done.

Potential requirement: "State lives in memory only (no storage APIs)": done.

Now verify that all code is present inside a single HTML file. Good.

Potential improvements: Ensure that "Add Card" button is visible; the theme toggle. The "search" field appears.

Potential issue: Use of nested CSS classes for .card .card-title is okay.

Potential issue: Use of form resetting: after hideModal, we call addCardForm.reset() which clears fields. That's fine.

Potential issue: The `cancelBtn` is defined inside modal; we used `const cancelBtn = modal.querySelector('.cancel-btn');` That's fine.

Potential issue: If user press Escape key to close modal, we haven't implemented but not required.

Potential issue: Not using any external fonts. ok.

Potential issue: Not using any network requests: all resources are internal.

Potential issue: This code uses top-level arrow functions that may be old for some environments; but fine.

Potential issue: Should we use `requestAnimationFrame` for any visual updates? Not needed.

Potential issue: Use of `window` not needed.

Potential issue: Implementation uses `e.currentTarget` vs `e.target`. That's fine.

Potential issue: Ensure that the `draggable` attribute and drag events work across Chrome. In Chrome they work.

Potential issue: For the drag image, no custom image needed, default is fine.

Potential issue: After dragging, we may have leftover `.drag-over` if drop isn't handled properly. Should be cleared on dragend maybe. We could also remove on dragend for all lists, but okay.

Potential issue: The card marking includes `deleteBtn` event that uses deleteCard card.id; that works.

Potential issue: In the board, we used `.card-list .card` counting includes hidden ones (still count). Implementation matches.

Potential issue: Search input placeholder includes Unicode ellipsis (…); fine.

Potential issue: theme toggle: initial state (light) with button text "🌙" indicates "click to go dark". After toggling, we replace with "☀️". Works.

Potential issue: The CSS for `.modal` sets visibility: hidden and opacity:0. When `.show`, set visible and opacity:1. The transition includes visibility .2s and opacity .2s; visible will be set immediate due to .show class. That works.

Potential issue: The modal's background `rgba(0,0,0,0.5)` covers page, used as overlay. Click on overlay hides modal due to event listener.

Potential issue: The submit button in modal is type="submit". That's correct.

Now test mental usage:

Open page. See top controls and board. Screenshot after 3 seconds shows board with cards, counts, maybe toggle button.

Search and drag/dropping works.

Now maybe consider column header showing "To do" but the specification says "To do". Good.

Now check labels: In JS for filterCards, we set data-title attr for each card; we also set .dataset.title. That is used for filter.

Here is `cardEl.dataset.title = card.title;` So .dataset.title holds string; we use that for searching.

Now check `filterCards` uses q = query.trim().toLowerCase(); Then loops over document.querySelectorAll('.card'), for each cardEl, obtains cardEl.dataset.title (lowercasing). That works.

Now verify we don't have any naming collisions: we have `let cards = [...]` global variable; we also have functions named `addCard`, `deleteCard`, `moveCard`, etc.

Now inspect risk of duplicate ids: `nextId` starts at 7, not conflicting with prepopulated. Good.

Potential risk: We have an element with id 'board', but not used except in code for initial rendering maybe not needed.

Now ensure we didn't forget to handle drag events for cards after removal / addition. Since we re-create cards on each render, they get fresh event listeners. Good.

Now check that dragging a card over a column where it's already present still triggers moveCard; but we check inside moveCard that if card.column !== newColumn, only then update and re-render. So dropping onto same column doesn't change anything (still re-renders maybe but we check condition). Actually we optionally still call renderBoard if no change? We only call renderBoard if changed. It's fine.

Now we need to ensure that when we drop, the dragover class is removed for that column. In drop handler we call `list.classList.remove('drag-over');`. So we are fine.

Potential scenario: Dropping onto a column while dragging from same column triggers re-append but not needed. But fine.

Now test potential errors:

- There might be conflict: In the CSS, `.column h2` uses display flex, justify-content: space-between; but the count is inside `<span class="count">(0)</span>` inside h2. That's okay.

- The code for `columnList` selector uses `.card-list[data-column="${card.column}"]`. And we set data-column attribute on .card-list elements accordingly: In HTML we set `<div class="card-list" data-column="todo"></div>` etc. Good.

- The column's `.column` element also has data-column; counts use that.

Now check appears good.

Now consider if any reserved words or problematic. Should be fine.

Now final output: must be a single fenced block with HTML code (no extra text).

Now produce final answer.