Models 44

MiniMax M3.1 Flash

by MiniMaxReasoningClean
Open in new window

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

How it went

Total time
44.1 s
Cost
$0.0049
Output tokens
8,160
Spent thinking
1,575 tokens
Speed
187.2 tok/s
Console errors
0
First token after
472 ms
First visible text after
12.8 s
Prompt tokens
678
Code size
21.3 KB
Finish reason
stop
Served by
Fireworks
Model id
minimax/minimax-m3.1-flash
Reasoning sent
{"effort":"low"}
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 · 512 lines

<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Task Board — Kanban</title>
<style>
  :root{
    --bg:#eef0f4;
    --panel:#ffffff;
    --card:#ffffff;
    --text:#1b2230;
    --muted:#6b7280;
    --line:#e3e6ec;
    --accent:#4f46e5;
    --accent-soft:rgba(79,70,229,.12);
    --shadow:0 1px 2px rgba(16,24,40,.06), 0 6px 16px rgba(16,24,40,.06);
    --shadow-lg:0 12px 40px rgba(16,24,40,.18);
    --radius:14px;
  }
  html[data-theme="dark"]{
    --bg:#0d1016;
    --panel:#141821;
    --card:#1a1f2b;
    --text:#e6e9f0;
    --muted:#8b93a7;
    --line:#262c39;
    --accent:#818cf8;
    --accent-soft:rgba(129,140,248,.16);
    --shadow:0 1px 2px rgba(0,0,0,.4), 0 8px 20px rgba(0,0,0,.35);
    --shadow-lg:0 18px 50px rgba(0,0,0,.55);
  }
  *{box-sizing:border-box}
  html,body{margin:0;padding:0}
  body{
    background:var(--bg);color:var(--text);min-height:100vh;
    font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    -webkit-font-smoothing:antialiased;
  }
  .topbar{
    display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;
    padding:16px 24px;background:var(--panel);
    border-bottom:1px solid var(--line);
    position:sticky;top:0;z-index:20;
  }
  .brand{display:flex;align-items:center;gap:12px}
  .logo{
    width:40px;height:40px;border-radius:12px;display:grid;place-items:center;
    background:linear-gradient(135deg,#6366f1,#8b5cf6 60%,#ec4899);
    color:#fff;font-weight:800;font-size:18px;box-shadow:var(--shadow);
  }
  .brand h1{margin:0;font-size:17px;letter-spacing:-.2px}
  .brand p{margin:2px 0 0;font-size:12px;color:var(--muted)}
  .tools{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
  .search{position:relative;display:flex;align-items:center}
  .search svg{position:absolute;left:11px;width:15px;height:15px;stroke:var(--muted);fill:none;stroke-width:2;pointer-events:none}
  #search{
    width:220px;padding:9px 12px 9px 33px;border-radius:10px;border:1px solid var(--line);
    background:var(--bg);color:var(--text);font-size:13.5px;outline:none;transition:.18s;
  }
  #search:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);width:250px}
  button{font-family:inherit;cursor:pointer}
  .icon-btn{
    width:38px;height:38px;border-radius:10px;border:1px solid var(--line);
    background:var(--bg);color:var(--text);display:grid;place-items:center;transition:.18s;
  }
  .icon-btn:hover{border-color:var(--accent);color:var(--accent)}
  .icon-btn svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round}
  html[data-theme="light"] .moon{display:none}
  html[data-theme="dark"] .sun{display:none}
  .primary-btn{
    border:none;border-radius:10px;padding:10px 15px;font-size:13.5px;font-weight:600;
    background:var(--accent);color:#fff;display:inline-flex;align-items:center;gap:6px;transition:.18s;
    box-shadow:0 4px 12px var(--accent-soft);
  }
  .primary-btn:hover{filter:brightness(1.08);transform:translateY(-1px)}
  .primary-btn svg{width:15px;height:15px;stroke:#fff;stroke-width:2.2;fill:none;stroke-linecap:round}

  .board{
    display:grid;grid-template-columns:repeat(3,1fr);gap:18px;
    padding:22px 24px 40px;max-width:1280px;margin:0 auto;align-items:start;
  }
  .column{
    background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);
    box-shadow:var(--shadow);display:flex;flex-direction:column;min-height:240px;transition:.18s;
  }
  .column.drag-over{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
  .col-head{display:flex;align-items:center;gap:9px;padding:14px 16px 10px;border-bottom:1px solid var(--line)}
  .dot{width:9px;height:9px;border-radius:50%}
  .col-head h2{margin:0;font-size:13.5px;font-weight:700;letter-spacing:.3px;text-transform:uppercase;color:var(--muted)}
  .count{
    margin-left:auto;font-size:12px;font-weight:700;color:var(--muted);
    background:var(--bg);border:1px solid var(--line);border-radius:20px;padding:2px 9px;min-width:26px;text-align:center;
  }
  .count b{color:var(--text)}
  .col-add{
    width:26px;height:26px;border-radius:8px;border:1px solid transparent;background:transparent;
    color:var(--muted);font-size:17px;line-height:1;display:grid;place-items:center;transition:.15s;
  }
  .col-add:hover{background:var(--bg);color:var(--accent);border-color:var(--line)}
  .cards{padding:12px;display:flex;flex-direction:column;gap:10px;min-height:70px}
  .card{
    position:relative;background:var(--card);border:1px solid var(--line);border-radius:11px;
    padding:12px 13px 12px 14px;box-shadow:var(--shadow);cursor:grab;touch-action:none;
    transition:box-shadow .18s,transform .12s,border-color .18s;user-select:none;
  }
  .card:hover{transform:translateY(-2px);box-shadow:var(--shadow-lg);border-color:var(--accent)}
  .card::before{content:"";position:absolute;left:0;top:10px;bottom:10px;width:3px;border-radius:0 3px 3px 0}
  .card.p-high::before{background:#ef4444}
  .card.p-medium::before{background:#f59e0b}
  .card.p-low::before{background:#10b981}
  .card-title{margin:0 0 9px;font-size:14px;font-weight:600;line-height:1.35;padding-right:22px;word-break:break-word}
  .card-foot{display:flex;align-items:center;gap:8px}
  .badge{
    font-size:10.5px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;
    padding:3px 8px;border-radius:20px;display:inline-flex;align-items:center;gap:5px;
  }
  .badge .pip{width:6px;height:6px;border-radius:50%;background:currentColor}
  .p-high .badge{color:#dc2626;background:rgba(239,68,68,.13)}
  .p-medium .badge{color:#b45309;background:rgba(245,158,11,.16)}
  .p-low .badge{color:#059669;background:rgba(16,185,129,.14)}
  html[data-theme="dark"] .p-high .badge{color:#f87171}
  html[data-theme="dark"] .p-medium .badge{color:#fbbf24}
  html[data-theme="dark"] .p-low .badge{color:#34d399}
  .del{
    position:absolute;top:9px;right:9px;width:22px;height:22px;border-radius:7px;border:none;
    background:transparent;color:var(--muted);font-size:14px;line-height:1;display:grid;place-items:center;
    opacity:.55;transition:.15s;
  }
  .del:hover{background:rgba(239,68,68,.14);color:#ef4444;opacity:1}
  .card.hidden{display:none}
  .empty{
    border:1px dashed var(--line);border-radius:11px;padding:18px 12px;text-align:center;
    color:var(--muted);font-size:12.5px;
  }
  .empty.hidden{display:none}

  .ghost{
    position:fixed;z-index:999;pointer-events:none;box-shadow:var(--shadow-lg);
    transform:rotate(-2.5deg) scale(1.03);opacity:.96;border-color:var(--accent)!important;
  }
  .drop-placeholder{
    border:2px dashed var(--accent);border-radius:11px;background:var(--accent-soft);
    display:grid;place-items:center;color:var(--accent);font-size:11.5px;font-weight:700;
    letter-spacing:.6px;text-transform:uppercase;
  }
  body.dragging{cursor:grabbing}
  body.dragging .card{cursor:grabbing}

  .overlay{
    position:fixed;inset:0;background:rgba(15,18,26,.55);backdrop-filter:blur(3px);
    display:grid;place-items:center;z-index:100;padding:20px;
  }
  .overlay[hidden]{display:none}
  .modal{
    width:100%;max-width:420px;background:var(--panel);border:1px solid var(--line);
    border-radius:16px;padding:22px;box-shadow:var(--shadow-lg);animation:pop .18s ease-out;
  }
  @keyframes pop{from{transform:translateY(10px) scale(.97);opacity:0}to{transform:none;opacity:1}}
  .modal h2{margin:0 0 16px;font-size:17px}
  .field{margin-bottom:14px}
  .field label{display:block;font-size:12px;font-weight:700;color:var(--muted);margin-bottom:6px;letter-spacing:.3px;text-transform:uppercase}
  .field input[type=text],.field select{
    width:100%;padding:10px 12px;border-radius:10px;border:1px solid var(--line);
    background:var(--bg);color:var(--text);font-size:14px;outline:none;transition:.18s;
  }
  .field input[type=text]:focus,.field select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
  .seg{display:flex;gap:8px}
  .seg button{
    flex:1;padding:9px 6px;border-radius:9px;border:1px solid var(--line);background:var(--bg);
    color:var(--muted);font-size:12.5px;font-weight:700;letter-spacing:.3px;text-transform:capitalize;transition:.15s;
  }
  .seg button.on[data-p="high"]{border-color:#ef4444;color:#ef4444;background:rgba(239,68,68,.12)}
  .seg button.on[data-p="medium"]{border-color:#f59e0b;color:#b45309;background:rgba(245,158,11,.15)}
  .seg button.on[data-p="low"]{border-color:#10b981;color:#059669;background:rgba(16,185,129,.13)}
  .err{color:#ef4444;font-size:12.5px;min-height:16px;margin:-6px 0 8px}
  .modal-actions{display:flex;justify-content:flex-end;gap:9px;margin-top:4px}
  .ghost-btn{border:1px solid var(--line);background:var(--bg);color:var(--text);padding:10px 15px;border-radius:10px;font-size:13.5px;font-weight:600}
  .ghost-btn:hover{border-color:var(--accent);color:var(--accent)}
  .hint{color:var(--muted);font-size:12px;margin:14px 24px 0;text-align:center}
  .hint kbd{border:1px solid var(--line);border-bottom-width:2px;border-radius:5px;padding:1px 6px;font-size:11px;background:var(--panel)}
  @media (max-width:860px){
    .board{grid-template-columns:1fr;padding:16px}
    .topbar{padding:12px 16px}
    #search{width:150px}
    #search:focus{width:170px}
  }
</style>
</head>
<body>

<header class="topbar">
  <div class="brand">
    <div class="logo">K</div>
    <div>
      <h1>Task Board</h1>
      <p>Product sprint · June</p>
    </div>
  </div>
  <div class="tools">
    <div class="search">
      <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M20 20l-3.5-3.5"/></svg>
      <input id="search" type="text" placeholder="Search cards…" aria-label="Search cards by title" autocomplete="off">
    </div>
    <button class="icon-btn" id="themeBtn" title="Toggle light / dark theme" aria-label="Toggle theme">
      <svg class="sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4.2"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
      <svg class="moon" viewBox="0 0 24 24"><path d="M20 14.5A8.5 8.5 0 1 1 9.5 4a7 7 0 0 0 10.5 10.5z"/></svg>
    </button>
    <button class="primary-btn" id="addBtn">
      <svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg> Add card
    </button>
  </div>
</header>

<main class="board" id="board">
  <section class="column" data-col="todo">
    <div class="col-head">
      <span class="dot" style="background:#6366f1"></span>
      <h2>To do</h2>
      <span class="count" data-count="todo"></span>
      <button class="col-add" data-add="todo" title="Add card to To do">+</button>
    </div>
    <div class="cards" data-list="todo"></div>
  </section>

  <section class="column" data-col="doing">
    <div class="col-head">
      <span class="dot" style="background:#f59e0b"></span>
      <h2>In progress</h2>
      <span class="count" data-count="doing"></span>
      <button class="col-add" data-add="doing" title="Add card to In progress">+</button>
    </div>
    <div class="cards" data-list="doing"></div>
  </section>

  <section class="column" data-col="done">
    <div class="col-head">
      <span class="dot" style="background:#10b981"></span>
      <h2>Done</h2>
      <span class="count" data-count="done"></span>
      <button class="col-add" data-add="done" title="Add card to Done">+</button>
    </div>
    <div class="cards" data-list="done"></div>
  </section>
</main>

<p class="hint">Drag a card between columns · <kbd>N</kbd> new card · <kbd>/</kbd> search · <kbd>T</kbd> theme</p>

<div class="overlay" id="overlay" hidden>
  <div class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
    <h2 id="modalTitle">New card</h2>
    <form id="cardForm" novalidate>
      <div class="field">
        <label for="titleInput">Title</label>
        <input id="titleInput" type="text" placeholder="What needs to be done?" maxlength="90" autocomplete="off">
      </div>
      <div class="err" id="formErr"></div>
      <div class="field">
        <label>Priority</label>
        <div class="seg" id="prioSeg">
          <button type="button" data-p="low">Low</button>
          <button type="button" data-p="medium" class="on">Medium</button>
          <button type="button" data-p="high">High</button>
        </div>
      </div>
      <div class="field">
        <label for="colSelect">Column</label>
        <select id="colSelect">
          <option value="todo">To do</option>
          <option value="doing">In progress</option>
          <option value="done">Done</option>
        </select>
      </div>
      <div class="modal-actions">
        <button type="button" class="ghost-btn" id="cancelBtn">Cancel</button>
        <button type="submit" class="primary-btn">Add card</button>
      </div>
    </form>
  </div>
</div>

<script>
(function(){
  "use strict";

  const COLS = ['todo','doing','done'];
  const COL_TITLE = {todo:'To do', doing:'In progress', done:'Done'};
  const PRIO_LABEL = {low:'Low', medium:'Medium', high:'High'};

  let cards = [
    {id:1, title:'Design onboarding flow for new signups', priority:'high',   col:'todo'},
    {id:2, title:'Set up CI pipeline with preview deploys', priority:'medium', col:'todo'},
    {id:3, title:'Write API docs for the public endpoints', priority:'low',    col:'todo'},
    {id:4, title:'Implement drag & drop for the board', priority:'high',      col:'doing'},
    {id:5, title:'Fix login redirect loop on Safari',     priority:'medium',   col:'doing'},
    {id:6, title:'Create project scaffold and lint rules',priority:'low',     col:'done'},
    {id:7, title:'Collect feedback from the beta group',   priority:'medium',  col:'done'}
  ];
  let nextId = 8;
  let query = '';
  let priority = 'medium';

  const board = document.getElementById('board');
  const searchInput = document.getElementById('search');
  const overlay = document.getElementById('overlay');
  const form = document.getElementById('cardForm');
  const titleInput = document.getElementById('titleInput');
  const colSelect = document.getElementById('colSelect');
  const formErr = document.getElementById('formErr');
  const prioSeg = document.getElementById('prioSeg');

  const listFor = c => board.querySelector('[data-list="'+c+'"]');
  const matches = card => card.title.toLowerCase().includes(query);

  function cardEl(card){
    const el = document.createElement('article');
    el.className = 'card p-'+card.priority;
    el.dataset.id = card.id;
    el.innerHTML =
      '<button class="del" title="Delete card" aria-label="Delete card">✕</button>'+
      '<h3 class="card-title"></h3>'+
      '<div class="card-foot"><span class="badge"><span class="pip"></span>'+PRIO_LABEL[card.priority]+'</span></div>';
    el.querySelector('.card-title').textContent = card.title;
    if(!matches(card)) el.classList.add('hidden');
    return el;
  }

  function render(){
    COLS.forEach(col=>{
      const list = listFor(col);
      const mine = cards.filter(c=>c.col===col);
      const visible = mine.filter(matches);
      list.innerHTML = '';
      visible.forEach(c=>list.appendChild(cardEl(c)));
      let empty = list.querySelector('.empty');
      if(!visible.length){
        if(!empty){ empty=document.createElement('div'); empty.className='empty'; list.appendChild(empty); }
        empty.textContent = query ? 'No matching cards' : 'Nothing here yet';
        empty.classList.remove('hidden');
      } else if(empty){ empty.remove(); }

      const counter = board.querySelector('[data-count="'+col+'"]');
      if(query){
        counter.innerHTML = '<b>'+visible.length+'</b>/'+mine.length;
      } else {
        counter.textContent = mine.length;
      }
    });
  }

  /* ---------- add / delete ---------- */
  board.addEventListener('click', e=>{
    const del = e.target.closest('.del');
    if(del){
      const id = +del.closest('.card').dataset.id;
      cards = cards.filter(c=>c.id!==id);
      render();
      return;
    }
    const add = e.target.closest('[data-add]');
    if(add){ openModal(add.dataset.add); }
  });
  document.getElementById('addBtn').addEventListener('click', ()=>openModal('todo'));

  function openModal(col){
    colSelect.value = col || 'todo';
    formErr.textContent='';
    overlay.hidden = false;
    setTimeout(()=>titleInput.focus(), 30);
  }
  function closeModal(){ overlay.hidden = true; }
  document.getElementById('cancelBtn').addEventListener('click', closeModal);
  overlay.addEventListener('mousedown', e=>{ if(e.target===overlay) closeModal(); });

  prioSeg.addEventListener('click', e=>{
    const b = e.target.closest('button'); if(!b) return;
    prioSeg.querySelectorAll('button').forEach(x=>x.classList.remove('on'));
    b.classList.add('on'); priority = b.dataset.p;
  });

  form.addEventListener('submit', e=>{
    e.preventDefault();
    const t = titleInput.value.trim();
    if(!t){ formErr.textContent='Please enter a title for the card.'; titleInput.focus(); return; }
    cards.push({id:nextId++, title:t, priority:priority, col:colSelect.value});
    render();
    form.reset(); priority='medium';
    prioSeg.querySelectorAll('button').forEach(x=>x.classList.toggle('on', x.dataset.p==='medium'));
    closeModal();
  });

  /* ---------- search ---------- */
  searchInput.addEventListener('input', ()=>{ query = searchInput.value.trim().toLowerCase(); render(); });

  /* ---------- theme ---------- */
  const themeBtn = document.getElementById('themeBtn');
  const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
  document.documentElement.setAttribute('data-theme', prefersDark ? 'dark' : 'light');
  themeBtn.addEventListener('click', ()=>{
    const cur = document.documentElement.getAttribute('data-theme');
    document.documentElement.setAttribute('data-theme', cur==='dark'?'light':'dark');
  });

  /* ---------- drag & drop (pointer based) ---------- */
  let drag = null;

  board.addEventListener('pointerdown', e=>{
    if(e.button !== 0) return;
    const card = e.target.closest('.card');
    if(!card || e.target.closest('.del')) return;
    const rect = card.getBoundingClientRect();
    drag = {
      card, col: card.parentElement.dataset.list,
      startX:e.clientX, startY:e.clientY,
      offX: e.clientX - rect.left, offY: e.clientY - rect.top,
      w: rect.width, h: rect.height,
      active:false, ghost:null, placeholder:null, targetCol:null
    };
    window.addEventListener('pointermove', onMove);
    window.addEventListener('pointerup', onUp);
    window.addEventListener('pointercancel', onUp);
  });

  function onMove(e){
    if(!drag) return;
    const dx = e.clientX - drag.startX, dy = e.clientY - drag.startY;
    if(!drag.active){
      if(Math.abs(dx) + Math.abs(dy) < 6) return;
      startDrag(e);
    }
    e.preventDefault();
    drag.ghost.style.left = (e.clientX - drag.offX)+'px';
    drag.ghost.style.top  = (e.clientY - drag.offY)+'px';

    const colEl = columnAt(e.clientX, e.clientY);
    board.querySelectorAll('.column').forEach(c=>c.classList.toggle('drag-over', c===colEl));
    if(!colEl) return;
    const list = colEl.querySelector('.cards');
    const siblings = [...list.querySelectorAll('.card:not(.hidden)')].filter(c=>c!==drag.card);
    let before = null;
    for(const s of siblings){
      const r = s.getBoundingClientRect();
      if(e.clientY < r.top + r.height/2){ before = s; break; }
    }
    if(before) list.insertBefore(drag.placeholder, before);
    else list.appendChild(drag.placeholder);
    drag.targetCol = colEl.dataset.col;
  }

  function columnAt(x,y){
    const els = document.elementsFromPoint ? document.elementsFromPoint(x,y) : [];
    for(const el of els){
      const c = el.closest && el.closest('.column');
      if(c) return c;
    }
    return null;
  }

  function startDrag(e){
    drag.active = true;
    document.body.classList.add('dragging');
    const g = drag.card.cloneNode(true);
    g.classList.add('ghost');
    g.style.width = drag.w+'px';
    g.style.left = (e.clientX - drag.offX)+'px';
    g.style.top  = (e.clientY - drag.offY)+'px';
    document.body.appendChild(g);
    drag.ghost = g;

    const ph = document.createElement('div');
    ph.className = 'drop-placeholder';
    ph.style.height = drag.h+'px';
    ph.textContent = 'Drop here';
    drag.placeholder = ph;
    drag.card.style.display = 'none';
    drag.card.parentElement.insertBefore(ph, drag.card);
  }

  function onUp(){
    window.removeEventListener('pointermove', onMove);
    window.removeEventListener('pointerup', onUp);
    window.removeEventListener('pointercancel', onUp);
    if(!drag) return;
    const d = drag; drag = null;
    document.body.classList.remove('dragging');
    board.querySelectorAll('.column').forEach(c=>c.classList.remove('drag-over'));
    if(!d.active) return;
    d.ghost.remove();
    const list = listFor(d.targetCol || d.col);
    list.insertBefore(d.card, d.placeholder);
    d.placeholder.remove();
    d.card.style.display = '';
    const card = cards.find(c=>c.id === +d.card.dataset.id);
    if(card) card.col = d.targetCol || d.col;
    render();
  }

  /* ---------- keyboard ---------- */
  document.addEventListener('keydown', e=>{
    if(e.key === 'Escape' && !overlay.hidden){ closeModal(); return; }
    const tag = (e.target.tagName||'').toLowerCase();
    if(tag === 'input' || tag === 'select' || tag === 'textarea') return;
    if(e.key === 'n' || e.key === 'N'){ openModal('todo'); e.preventDefault(); }
    if(e.key === '/'){ searchInput.focus(); e.preventDefault(); }
    if(e.key === 't' || e.key === 'T'){ themeBtn.click(); }
  });

  render();
})();
</script>
</body>
</html>
Reasoning trace (1,575 tokens)
Let me build a nice polished Kanban board.

Key features: 3 columns, counts, 6 cards, priority badges, modal add form, delete button, drag & drop with drop indicator, search filter, theme toggle.

Drag and drop: HTML5 DnD with dragover placeholder insertion. Use mouse events? HTML5 DnD is fine but in iframe works. Let me implement custom pointer-based drag for reliability (pointerdown/move/up) — that gives clear drop indicator. I'll do pointer-based since HTML5 DnD screenshots are fine too. Custom pointer drag gives full control.

Let me write it.

Design: modern, subtle shadows, rounded cards, column header with count pill, colored left border for priority.

Implementation details:
- state.cards array {id, title, priority, col}
- render() builds columns.
- Drag: pointerdown on card (not on delete button), after 4px movement start drag: create floating clone following pointer, original gets .dragging opacity, columns highlight. Compute insertion index by comparing pointer Y to card midpoints. Show placeholder line.
- Drop: move card to target column at index.
- Search: filter by title lowercase includes; hide non-matching cards (and their drag disabled).
- Modal: overlay with form; title required, priority select; validation message.
- Theme toggle: data-theme attribute on html, toggle button.

Also keyboard accessible-ish: Enter to open modal.

Let me write the code carefully.

Pointer drag logic:
- on pointerdown on .card: record startY, card element, offset. set pointer capture on document? Use window listeners for pointermove/pointerup.
- if moved > 5px: dragging = true; create clone in body position fixed, width = card width, pointer-events none, class drag-ghost. Hide original via class .is-source (opacity .3? but placeholder line inserted at target). Actually keep original in place as placeholder? Better: remove original from flow and insert a placeholder element in target column. On cancel restore.

Simpler approach: keep original in the DOM but style it as the placeholder (dashed outline, no content)? Let's do: the dragged card element itself stays but we add class 'drag-placeholder' showing dashed with reduced opacity and text. Actually let's create a separate placeholder div.

Plan:
- On drag start: measure card rect. Create ghost = card.cloneNode(true) with class 'ghost', appended to body, positioned fixed at rect, transform translate. Then insert placeholder div (class 'drop-placeholder', height = rect.height) into the column where the card was, and remove card from DOM? Removing loses reference; keep cardEl reference, set display none? Use cardEl.style.display='none' and insert placeholder at same position. On drop, remove placeholder, insert cardEl into target position.

Insertion determination: on pointermove, find column under pointer (elementFromPoint but ghost has pointer-events none so fine). Within column, iterate card children (excluding source card since it's display none), find first whose midpoint Y > pointerY → insert before it, else append.

placeholder is inside column, so skip it in iteration.

Drop indicator: placeholder styled as dashed rounded box with "Drop here" text.

Auto-scroll not needed.

Counts: number of cards in column (all, not filtered? show filtered count maybe). Show total count; if search active show matching/total.

Let's write HTML.

CSS variables for themes.

Also add a footer hint. Add "Add card" button in each column? Just one global button plus per-column add button could be nice — per-column "+" sets target column. Let's do header "Add card" button opens modal with column selector defaulting to clicked column.

Colors: priority high = red, medium = amber, low = green/blue.

Let's write.

Card delete button: "✕" appearing on hover (but always visible? for screenshot niceness, show on hover; but requirement says delete with button on it — visible always is safer). I'll make it visible always but subtle.

Write code now.

Detail: modal focus; escape closes.

Also keyboard start message not needed since no game.

Let me produce final code.

Column cards list scroll with max-height.

Code:

```html
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>...styles...</head>
<body>
<header class="topbar">
  <div class="brand"><div class="logo">K</div><div><h1>Task Board</h1><p>Product sprint · June</p></div></div>
  <div class="tools">
    <div class="search"><svg.../><input id="search" placeholder="Search cards…" aria-label="Search cards"></div>
    <button id="themeBtn" class="icon-btn" title="Toggle theme">...</button>
    <button id="addBtn" class="primary-btn">+ Add card</button>
  </div>
</header>
<main class="board">
 <section class="column" data-col="todo"> header with dot, title, count, "+" </section>
 ...
</main>
```

Render columns via JS: build from state to allow counts. I'll have static column shells in HTML and fill lists + counts via JS.

Let me write render():

```js
const cols = {todo:{title:'To do', color:'#6366f1'}, doing:{...}, done:{...}};
```

Data model columns: 'todo','doing','done'.

Cards seed:
todo: "Design onboarding flow" high; "Set up CI pipeline" medium; "Write API docs" low
doing: "Implement drag & drop" high; "Fix login redirect bug" medium
done: "Create project scaffold" low; "Collect user feedback" medium

That's 6. Good.

Now full code.

For search filter: card hidden with class 'hidden' (display:none). Counts show matching count when searching.

Write CSS carefully for both themes.

I'll go with a soft modern look: light theme bg #f4f5f7, columns white cards; dark theme bg #0f1115, columns #171a21.

Theme toggle persists only in memory (default light). Also respect prefers-color-scheme initial? Set initial theme from media query at load, but toggle overrides.

Now write.

Ghost positioning: ghost fixed, left/top = rect.left/top, width rect.width; transform translate(dx, dy) where dx = pointerX - startX etc.

pointerdown handler on card: ignore if target is delete button.

Use e.preventDefault to avoid text selection; also set touch-action none on cards for touch drag.

Let me write final.

Modal HTML:

```html
<div class="overlay" id="overlay" hidden>
 <div class="modal" role="dialog" aria-modal="true">
   <h2>New card</h2>
   <form id="cardForm">
     <label>Title <input id="titleInput" required maxlength="80"></label>
     <div class="err" id="formErr"></div>
     <label>Priority <div class="seg" id="prioSeg"> buttons low/medium/high </div></label>
     <label>Column <select id="colSelect">...</select></label>
     <div class="modal-actions"><button type="button" id="cancelBtn">Cancel</button><button type="submit" class="primary-btn">Add card</button></div>
   </form>
 </div>
</div>
```

Priority segmented control with radio-like buttons.

Submit: if title empty -> show error, focus.

Now writing the final file.