Models 44

Muse Ember

Open in new window

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

How it went

Total time
15.4 s
Cost
$0.0051
Output tokens
10,000
Spent thinking
2,108 tokens
Speed
767.7 tok/s
Console errors
0
First token after
2.4 s
First visible text after
6.2 s
Prompt tokens
511
Code size
26.3 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"medium"}
Max output
943,718 tokens
Generated
Nov 12, 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 · 545 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flowboard — Kanban</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{
  --bg:#eef1f6; --bg2:#e6eaf2; --card:#ffffff; --card2:#f8fafc; --text:#0f172a; --muted:#64748b; --muted2:#94a3b8;
  --border:#e2e8f0; --border2:#cbd5e1; --shadow:0 4px 18px rgba(15,23,42,.07); --shadow2:0 12px 32px rgba(15,23,42,.14);
  --header:#ffffff; --input:#ffffff; --primary:#4f46e5; --primary2:#4338ca; --primarySoft:#eef2ff;
  --colTodo:#64748b; --colProgress:#f59e0b; --colDone:#22c55e;
  --lowBg:#dcfce7; --lowText:#166534; --medBg:#fef3c7; --medText:#92400e; --highBg:#fee2e2; --highText:#991b1b;
  --placeholder:#c7d2fe; --overlay:rgba(15,23,42,.52);
}
body.dark{
  --bg:#0b1120; --bg2:#0f172a; --card:#151f33; --card2:#1a2740; --text:#e5edff; --muted:#94a3b8; --muted2:#64748b;
  --border:#24324d; --border2:#33435f; --shadow:0 4px 18px rgba(0,0,0,.28); --shadow2:0 14px 36px rgba(0,0,0,.45);
  --header:#0f172a; --input:#151f33; --primary:#6366f1; --primary2:#818cf8; --primarySoft:#1e1b4b;
  --lowBg:#052e16; --lowText:#86efac; --medBg:#451a03; --medText:#fcd34d; --highBg:#450a0a; --highText:#fca5a5;
  --placeholder:#3730a3; --overlay:rgba(2,6,23,.72);
}
html,body{height:100%}
body{
  font-family:ui-sans-system,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
  background:radial-gradient(900px 500px at 85% -10%, rgba(99,102,241,.16), transparent 60%), linear-gradient(180deg,var(--bg),var(--bg2));
  color:var(--text); min-height:100vh; overflow-x:hidden; transition:background .3s,color .3s;
}
/* Header */
.topbar{
  background:var(--header); border-bottom:1px solid var(--border);
  padding:14px 22px; display:flex; align-items:center; justify-content:space-between; gap:18px; flex-wrap:wrap;
  position:sticky; top:0; z-index:30; box-shadow:0 1px 0 rgba(0,0,0,.02);
}
.brand{display:flex;align-items:center;gap:13px;min-width:0}
.logo{
  width:42px;height:42px;border-radius:11px;background:linear-gradient(135deg,#4f46e5,#7c3aed);color:#fff;
  display:grid;place-items:center;font-weight:800;font-size:19px;box-shadow:0 8px 18px rgba(79,70,229,.35);flex:0 0 auto;
}
.brand h1{font-size:20px;letter-spacing:-.4px;line-height:1.1}
.brand p{font-size:12.5px;color:var(--muted);margin-top:2px}
.actions{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-left:auto}
.search{
  display:flex;align-items:center;gap:8px;background:var(--input);border:1.5px solid var(--border);border-radius:10px;
  padding:0 11px;min-width:270px;height:40px;transition:border-color .2s, box-shadow .2s;
}
.search:focus-within{border-color:var(--primary);box-shadow:0 0 0 3px rgba(99,102,241,.16)}
.search svg{width:17px;height:17px;color:var(--muted2);flex:0 0 auto}
.search input{border:none;outline:none;background:transparent;color:var(--text);font-size:14px;width:100%;font-family:inherit}
.search input::placeholder{color:var(--muted2)}
.search kbd{font-size:11px;color:var(--muted);border:1px solid var(--border2);border-bottom-width:2px;border-radius:5px;padding:1px 6px;background:var(--card2)}
.btn{
  border:none;font-family:inherit;font-size:14px;font-weight:600;border-radius:10px;height:40px;padding:0 16px;cursor:pointer;
  display:inline-flex;align-items:center;gap:7px;transition:transform .15s,background .2s,box-shadow .2s;white-space:nowrap;
}
.btn:active{transform:scale(.97)}
.btn-primary{background:var(--primary);color:#fff;box-shadow:0 6px 16px rgba(79,70,229,.32)}
.btn-primary:hover{background:var(--primary2)}
.icon-btn{
  width:40px;height:40px;border-radius:10px;border:1.5px solid var(--border);background:var(--input);color:var(--text);
  display:grid;place-items:center;cursor:pointer;transition:border-color .2s,transform .15s;
}
.icon-btn:hover{border-color:var(--primary);transform:rotate(8deg)}
.icon-btn svg{width:19px;height:19px}
/* Summary */
.summary{max-width:1280px;margin:0 auto;padding:20px 22px 4px;display:flex;align-items:center;gap:22px;flex-wrap:wrap}
.summary h2{font-size:22px;letter-spacing:-.5px}
.summary .sub{color:var(--muted);font-size:13.5px;margin-top:3px}
.stat{margin-left:auto;display:flex;gap:18px;flex-wrap:wrap}
.stat-item{display:flex;align-items:center;gap:9px;font-size:13px;color:var(--muted)}
.stat-item b{font-size:19px;color:var(--text)}
.progress-wrap{display:flex;align-items:center;gap:9px}
.progress{width:110px;height:7px;background:var(--border);border-radius:99px;overflow:hidden}
.progress i{display:block;height:100%;background:linear-gradient(90deg,#22c55e,#4f46e5);border-radius:99px;transition:width .5s ease}
/* Board */
.board{
  max-width:1280px;margin:0 auto;padding:16px 22px 28px;
  display:grid;grid-template-columns:repeat(3,1fr);gap:18px;align-items:start;
}
.column{
  background:var(--card2);border:1.5px solid var(--border);border-radius:16px;padding:14px;
  min-height:420px;display:flex;flex-direction:column;transition:border-color .2s,box-shadow .2s,background .2s;
}
.column.drag-over{border-color:var(--primary);box-shadow:0 0 0 3px rgba(99,102,241,.18);background:var(--primarySoft)}
.col-head{display:flex;align-items:center;gap:9px;padding:2px 4px 13px;border-bottom:1px solid var(--border);margin-bottom:13px}
.dot{width:10px;height:10px;border-radius:50%;flex:0 0 auto}
.dot.todo{background:var(--colTodo)} .dot.progress{background:var(--colProgress)} .dot.done{background:var(--colDone)}
.col-head h3{font-size:14.5px;letter-spacing:.2px;text-transform:uppercase}
.count{
  margin-left:auto;background:var(--card);border:1px solid var(--border);color:var(--muted);
  font-size:12.5px;font-weight:700;min-width:28px;height:24px;padding:0 8px;border-radius:99px;display:grid;place-items:center;
}
.cards{display:flex;flex-direction:column;gap:11px;flex:1;min-height:60px}
.card{
  background:var(--card);border:1px solid var(--border);border-radius:13px;padding:13px 13px 11px;box-shadow:var(--shadow);
  cursor:grab;position:relative;animation:pop .28s ease;transition:transform .15s,box-shadow .2s,opacity .2s,border-color .2s;
}
.card:hover{transform:translateY(-2px);box-shadow:var(--shadow2);border-color:var(--border2)}
.card:active{cursor:grabbing}
.card.dragging{opacity:.45;transform:rotate(1.5deg) scale(.98)}
.card.removing{opacity:0;transform:translateX(18px) scale(.96)}
.card-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:9px}
.badge{font-size:10.5px;font-weight:800;letter-spacing:.6px;text-transform:uppercase;padding:4px 8px;border-radius:99px;display:inline-flex;align-items:center;gap:5px}
.badge::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
.badge.low{background:var(--lowBg);color:var(--lowText)}
.badge.medium{background:var(--medBg);color:var(--medText)}
.badge.high{background:var(--highBg);color:var(--highText)}
.del{
  width:26px;height:26px;border-radius:7px;border:none;background:transparent;color:var(--muted2);font-size:17px;line-height:1;cursor:pointer;
  display:grid;place-items:center;opacity:0;transition:opacity .15s,background .15s,color .15s;flex:0 0 auto;
}
.card:hover .del,.card:focus-within .del{opacity:1}
.del:hover{background:var(--highBg);color:var(--highText)}
.card h4{font-size:15px;line-height:1.35;letter-spacing:-.1px;padding-right:4px}
.card-foot{display:flex;align-items:center;justify-content:space-between;margin-top:13px;color:var(--muted2);font-size:12px}
.avatar{width:25px;height:25px;border-radius:50%;display:grid;place-items:center;font-size:10.5px;font-weight:800;color:#fff;background:linear-gradient(135deg,#6366f1,#a855f7)}
.avatar.alt{background:linear-gradient(135deg,#0ea5e9,#22c55e)} .avatar.alt2{background:linear-gradient(135deg,#f59e0b,#ef4444)}
.foot-right{display:flex;align-items:center;gap:7px}
.drop-placeholder{
  border:2.5px dashed var(--primary);background:rgba(99,102,241,.13);border-radius:13px;
  display:grid;place-items:center;color:var(--primary);font-size:12.5px;font-weight:700;letter-spacing:.3px;
}
.empty{border:1.5px dashed var(--border2);border-radius:13px;padding:22px 14px;text-align:center;color:var(--muted2);font-size:13px;line-height:1.5}
.empty b{display:block;color:var(--muted);font-size:14px;margin-bottom:3px}
.add-inline{
  margin-top:12px;width:100%;height:38px;border-radius:10px;border:1.5px dashed var(--border2);background:transparent;color:var(--muted);
  font-family:inherit;font-size:13.5px;font-weight:600;cursor:pointer;transition:all .2s;
}
.add-inline:hover{border-color:var(--primary);color:var(--primary);background:rgba(99,102,241,.07)}
.hint{max-width:1280px;margin:0 auto;padding:0 22px 22px;color:var(--muted2);font-size:12.5px;display:flex;gap:6px;align-items:center}
/* Modal */
.overlay{
  position:fixed;inset:0;background:var(--overlay);backdrop-filter:blur(4px);z-index:50;
  display:none;place-items:center;padding:18px;
}
.overlay.open{display:grid}
.modal{
  background:var(--card);border:1px solid var(--border);border-radius:18px;width:100%;max-width:430px;padding:24px;
  box-shadow:var(--shadow2);animation:modalIn .25s cubic-bezier(.2,.9,.3,1.2);
}
.modal h3{font-size:19px;letter-spacing:-.3px}
.modal .m-sub{color:var(--muted);font-size:13.5px;margin:5px 0 18px}
.field{margin-bottom:15px}
.field label{display:block;font-size:12.5px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:var(--muted);margin-bottom:7px}
.field input[type="text"],.field select{
  width:100%;height:42px;border:1.5px solid var(--border2);border-radius:10px;background:var(--input);color:var(--text);
  padding:0 12px;font-size:14.5px;font-family:inherit;outline:none;transition:border-color .2s,box-shadow .2s;
}
.field input:focus,.field select:focus{border-color:var(--primary);box-shadow:0 0 0 3px rgba(99,102,241,.16)}
.field input.invalid{border-color:#ef4444;box-shadow:0 0 0 3px rgba(239,68,68,.15)}
.error{display:none;color:#ef4444;font-size:12.5px;margin-top:6px;font-weight:600}
.error.show{display:block}
.pills{display:flex;gap:8px}
.pill{flex:1}
.pill input{position:absolute;opacity:0;pointer-events:none}
.pill span{
  display:block;text-align:center;padding:9px 4px;border-radius:10px;border:1.5px solid var(--border2);cursor:pointer;
  font-size:13px;font-weight:700;text-transform:capitalize;transition:all .15s;color:var(--muted);
}
.pill input:checked + span.low{background:var(--lowBg);color:var(--lowText);border-color:#22c55e}
.pill input:checked + span.medium{background:var(--medBg);color:var(--medText);border-color:#f59e0b}
.pill input:checked + span.high{background:var(--highBg);color:var(--highText);border-color:#ef4444}
.pill input:focus-visible + span{box-shadow:0 0 0 3px rgba(99,102,241,.3)}
.modal-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:20px}
.btn-ghost{background:var(--card2);color:var(--muted);border:1px solid var(--border)}
.btn-ghost:hover{color:var(--text);border-color:var(--border2)}
@keyframes pop{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes modalIn{from{opacity:0;transform:translateY(14px) scale(.97)}to{opacity:1;transform:none}}
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-6px)}75%{transform:translateX(6px)}}
.shake{animation:shake .25s ease 2}
@media(max-width:900px){
  .board{grid-template-columns:1fr}
  .column{min-height:0}
  .stat{margin-left:0}
  .search{min-width:200px;flex:1}
}
@media(max-width:560px){
  .topbar{padding:13px 14px} .summary,.board,.hint{padding-left:14px;padding-right:14px}
  .search{order:3;flex-basis:100%;min-width:0}
  .actions{width:100%} .brand p{display:none}
  .summary h2{font-size:19px}
}
</style>
</head>
<body>

<header class="topbar">
  <div class="brand">
    <div class="logo" aria-hidden="true">✓</div>
    <div>
      <h1>Flowboard</h1>
      <p>Kanban workspace · Team Sprint</p>
    </div>
  </div>
  <div class="actions">
    <label class="search" aria-label="Search cards">
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
      <input id="searchInput" type="text" placeholder="Search cards by title…" autocomplete="off">
      <kbd>/</kbd>
    </label>
    <button class="btn btn-primary" id="addBtn" type="button">
      <span style="font-size:17px;line-height:1">+</span> Add Card
    </button>
    <button class="icon-btn" id="themeBtn" type="button" aria-label="Toggle light / dark theme" title="Toggle theme">
      <svg id="moonIcon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8Z"/></svg>
      <svg id="sunIcon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" style="display:none"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></svg>
    </button>
  </div>
</header>

<div class="summary">
  <div>
    <h2>Sprint board</h2>
    <div class="sub">Drag cards between columns to update progress.</div>
  </div>
  <div class="stat">
    <div class="stat-item"><b id="statTotal">6</b> total cards</div>
    <div class="stat-item"><b id="statProgress">2</b> in progress</div>
    <div class="progress-wrap">
      <div class="progress"><i id="progressBar" style="width:33%"></i></div>
      <span><b id="statDonePct" style="font-size:14px">33%</b> done</span>
    </div>
  </div>
</div>

<main class="board" id="board">
  <section class="column" data-col="todo" aria-label="To do column">
    <div class="col-head"><span class="dot todo"></span><h3>To do</h3><span class="count" id="count-todo">0</span></div>
    <div class="cards" data-col="todo"></div>
    <button class="add-inline" type="button" data-add-col="todo">+ Add card</button>
  </section>

  <section class="column" data-col="progress" aria-label="In progress column">
    <div class="col-head"><span class="dot progress"></span><h3>In progress</h3><span class="count" id="count-progress">0</span></div>
    <div class="cards" data-col="progress"></div>
    <button class="add-inline" type="button" data-add-col="progress">+ Add card</button>
  </section>

  <section class="column" data-col="done" aria-label="Done column">
    <div class="col-head"><span class="dot done"></span><h3>Done</h3><span class="count" id="count-done">0</span></div>
    <div class="cards" data-col="done"></div>
    <button class="add-inline" type="button" data-add-col="done">+ Add card</button>
  </section>
</main>

<p class="hint">↔ &nbsp;Drag a card by its body and drop it in another column. Press <b>/</b> to search, <b>Esc</b> to close the form.</p>

<!-- Modal -->
<div class="overlay" id="overlay" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
  <form class="modal" id="cardForm" novalidate>
    <h3 id="modalTitle">Add a new card</h3>
    <p class="m-sub">Fill in the details and it will appear on the board instantly.</p>

    <div class="field">
      <label for="titleInput">Card title *</label>
      <input id="titleInput" type="text" placeholder="e.g. Design pricing page" maxlength="90">
      <div class="error" id="titleError">Please enter a title for the card.</div>
    </div>

    <div class="field">
      <label>Priority</label>
      <div class="pills">
        <label class="pill"><input type="radio" name="priority" value="low"><span class="low">Low</span></label>
        <label class="pill"><input type="radio" name="priority" value="medium" checked><span class="medium">Medium</span></label>
        <label class="pill"><input type="radio" name="priority" value="high"><span class="high">High</span></label>
      </div>
    </div>

    <div class="field">
      <label for="colSelect">Column</label>
      <select id="colSelect">
        <option value="todo">To do</option>
        <option value="progress">In progress</option>
        <option value="done">Done</option>
      </select>
    </div>

    <div class="modal-actions">
      <button class="btn btn-ghost" type="button" id="cancelBtn">Cancel</button>
      <button class="btn btn-primary" type="submit">Add card</button>
    </div>
  </form>
</div>

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

  /* ---------- State (in memory only) ---------- */
  var nextId = 107;
  var cards = [
    { id:101, title:"Refactor authentication flow", priority:"high",   column:"todo",     initials:"AR", cls:"" },
    { id:102, title:"Write onboarding email sequence", priority:"low", column:"todo",     initials:"JM", cls:"alt" },
    { id:103, title:"Build payment checkout UI",    priority:"high",   column:"progress", initials:"SK", cls:"alt2" },
    { id:104, title:"Fix mobile navigation lag",    priority:"medium", column:"progress", initials:"AR", cls:"" },
    { id:105, title:"Set up CI/CD pipeline",        priority:"medium", column:"done",     initials:"TL", cls:"alt" },
    { id:106, title:"Update brand guidelines",      priority:"low",    column:"done",     initials:"JM", cls:"alt2" }
  ];
  var searchTerm = "";
  var draggedId = null;
  var placeholder = null;

  /* ---------- Elements ---------- */
  var board       = document.getElementById("board");
  var searchInput = document.getElementById("searchInput");
  var overlay     = document.getElementById("overlay");
  var form        = document.getElementById("cardForm");
  var titleInput  = document.getElementById("titleInput");
  var colSelect   = document.getElementById("colSelect");
  var titleError  = document.getElementById("titleError");
  var themeBtn    = document.getElementById("themeBtn");

  function esc(s){ return s.replace(/[&<>"']/g, function(c){ return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]; }); }
  function colCards(col){ return cards.filter(function(c){ return c.column===col; }); }
  function visibleCards(col){
    var q = searchTerm.trim().toLowerCase();
    return colCards(col).filter(function(c){ return !q || c.title.toLowerCase().indexOf(q) !== -1; });
  }

  /* ---------- Render ---------- */
  function render(){
    ["todo","progress","done"].forEach(function(col){
      var list = document.querySelector('.cards[data-col="'+col+'"]');
      var all  = colCards(col);
      var vis  = visibleCards(col);
      document.getElementById("count-"+col).textContent = all.length;

      if (vis.length === 0){
        list.innerHTML = searchTerm.trim()
          ? '<div class="empty"><b>No matches</b>No cards match your search in this column.</div>'
          : '<div class="empty"><b>Nothing here yet</b>Drop a card here or add a new one.</div>';
        return;
      }
      list.innerHTML = vis.map(function(c){
        return ''+
        '<article class="card" draggable="true" data-id="'+c.id+'" tabindex="0" aria-label="'+esc(c.title)+'">'+
          '<div class="card-top">'+
            '<span class="badge '+c.priority+'">'+c.priority+'</span>'+
            '<button class="del" type="button" data-delete="'+c.id+'" title="Delete card" aria-label="Delete '+esc(c.title)+'">&times;</button>'+
          '</div>'+
          '<h4>'+esc(c.title)+'</h4>'+
          '<div class="card-foot"><span>#K-'+c.id+'</span>'+
          '<span class="foot-right"><span class="avatar '+c.cls+'">'+c.initials+'</span></span></div>'+
        '</article>';
      }).join("");
    });
    updateStats();
  }

  function updateStats(){
    var total = cards.length;
    var prog  = colCards("progress").length;
    var done  = colCards("done").length;
    var pct   = total ? Math.round(done/total*100) : 0;
    document.getElementById("statTotal").textContent = total;
    document.getElementById("statProgress").textContent = prog;
    document.getElementById("statDonePct").textContent = pct + "%";
    document.getElementById("progressBar").style.width = pct + "%";
  }

  /* ---------- Search (live) ---------- */
  searchInput.addEventListener("input", function(){
    searchTerm = searchInput.value;
    render();
  });
  document.addEventListener("keydown", function(e){
    if (e.key === "/" && document.activeElement !== searchInput && document.activeElement !== titleInput){
      e.preventDefault(); searchInput.focus();
    }
    if (e.key === "Escape" && overlay.classList.contains("open")) closeModal();
  });

  /* ---------- Delete ---------- */
  board.addEventListener("click", function(e){
    var btn = e.target.closest("[data-delete]");
    if (!btn) return;
    var id = +btn.getAttribute("data-delete");
    var el = btn.closest(".card");
    if (el){ el.classList.add("removing"); }
    setTimeout(function(){
      cards = cards.filter(function(c){ return c.id !== id; });
      render();
    }, 180);
  });

  /* ---------- Modal ---------- */
  function openModal(col){
    overlay.classList.add("open");
    titleInput.value = ""; titleInput.classList.remove("invalid");
    titleError.classList.remove("show");
    if (col) colSelect.value = col;
    setTimeout(function(){ titleInput.focus(); }, 60);
  }
  function closeModal(){ overlay.classList.remove("open"); }

  document.getElementById("addBtn").addEventListener("click", function(){ openModal(); });
  document.querySelectorAll("[data-add-col]").forEach(function(b){
    b.addEventListener("click", function(){ openModal(b.getAttribute("data-add-col")); });
  });
  document.getElementById("cancelBtn").addEventListener("click", closeModal);
  overlay.addEventListener("click", function(e){ if (e.target === overlay) closeModal(); });
  titleInput.addEventListener("input", function(){
    titleInput.classList.remove("invalid"); titleError.classList.remove("show");
  });

  form.addEventListener("submit", function(e){
    e.preventDefault();
    var title = titleInput.value.trim();
    if (!title){
      titleInput.classList.add("invalid");
      titleError.classList.add("show");
      form.querySelector(".modal").classList.remove("shake");
      void form.querySelector(".modal").offsetWidth;
      form.querySelector(".modal").classList.add("shake");
      titleInput.focus();
      return;
    }
    var priority = (form.querySelector('input[name="priority"]:checked') || {}).value || "medium";
    cards.push({
      id: nextId++, title: title, priority: priority,
      column: colSelect.value, initials: "YO", cls: ""
    });
    closeModal();
    render();
  });

  /* ---------- Theme toggle ---------- */
  themeBtn.addEventListener("click", function(){
    var dark = document.body.classList.toggle("dark");
    document.getElementById("moonIcon").style.display = dark ? "none" : "block";
    document.getElementById("sunIcon").style.display  = dark ? "block" : "none";
  });

  /* ---------- Drag & drop (mouse) ---------- */
  function ensurePlaceholder(height){
    if (!placeholder){
      placeholder = document.createElement("div");
      placeholder.className = "drop-placeholder";
      placeholder.textContent = "Drop here";
    }
    placeholder.style.height = (height || 76) + "px";
    return placeholder;
  }
  function clearDropUI(){
    if (placeholder && placeholder.parentNode) placeholder.parentNode.removeChild(placeholder);
    document.querySelectorAll(".column.drag-over").forEach(function(c){ c.classList.remove("drag-over"); });
  }
  function afterElement(list, y){
    var els = list.querySelectorAll(".card:not(.dragging)");
    for (var i=0;i<els.length;i++){
      var r = els[i].getBoundingClientRect();
      if (y < r.top + r.height/2) return els[i];
    }
    return null;
  }
  function insertIndex(list, after){
    if (!after) return list.querySelectorAll(".card:not(.dragging)").length;
    var els = Array.prototype.slice.call(list.querySelectorAll(".card:not(.dragging)"));
    return els.indexOf(after);
  }

  board.addEventListener("dragstart", function(e){
    var card = e.target.closest(".card");
    if (!card) return;
    draggedId = +card.getAttribute("data-id");
    card.classList.add("dragging");
    card.style.height = "";
    e.dataTransfer.effectAllowed = "move";
    try { e.dataTransfer.setData("text/plain", String(draggedId)); } catch(_){}
    ensurePlaceholder(card.offsetHeight);
  });

  board.addEventListener("dragend", function(){
    draggedId = null;
    clearDropUI();
    document.querySelectorAll(".card.dragging").forEach(function(c){ c.classList.remove("dragging"); });
  });

  board.addEventListener("dragover", function(e){
    if (draggedId === null) return;
    var colEl = e.target.closest(".column");
    if (!colEl) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";

    document.querySelectorAll(".column.drag-over").forEach(function(c){
      if (c !== colEl) c.classList.remove("drag-over");
    });
    colEl.classList.add("drag-over");

    var list = colEl.querySelector(".cards");
    var after = afterElement(list, e.clientY);
    var ph = ensurePlaceholder();
    if (after) list.insertBefore(ph, after); else list.appendChild(ph);
  });

  board.addEventListener("dragleave", function(e){
    var colEl = e.target.closest(".column");
    if (!colEl) return;
    if (!colEl.contains(e.relatedTarget)) colEl.classList.remove("drag-over");
  });

  board.addEventListener("drop", function(e){
    var colEl = e.target.closest(".column");
    if (!colEl || draggedId === null) return;
    e.preventDefault();

    var targetCol = colEl.getAttribute("data-col");
    var list = colEl.querySelector(".cards");
    var idx = insertIndex(list, (placeholder && placeholder.parentNode === list) ? (placeholder.nextElementSibling && placeholder.nextElementSibling.classList.contains("card") ? placeholder.nextElementSibling : null) : null);
    // If placeholder sits before a card, that card is the "after" element.
    if (placeholder && placeholder.parentNode === list){
      var nxt = placeholder.nextElementSibling;
      idx = nxt && nxt.classList.contains("card") && !nxt.classList.contains("dragging")
        ? Array.prototype.slice.call(list.querySelectorAll(".card:not(.dragging)")).indexOf(nxt)
        : list.querySelectorAll(".card:not(.dragging)").length;
    } else {
      idx = list.querySelectorAll(".card:not(.dragging)").length;
    }

    var moved = null;
    cards = cards.filter(function(c){
      if (c.id === draggedId){ moved = c; return false; }
      return true;
    });
    if (moved){
      moved.column = targetCol;
      // Rebuild: keep other columns, then target column with card inserted at idx.
      var others = cards.filter(function(c){ return c.column !== targetCol; });
      var mine   = cards.filter(function(c){ return c.column === targetCol; });
      mine.splice(Math.max(0, Math.min(idx, mine.length)), 0, moved);
      cards = others.concat(mine);
    }
    draggedId = null;
    clearDropUI();
    render();
  });

  /* ---------- Init ---------- */
  render();
})();
</script>
</body>
</html>
Reasoning trace (2,108 tokens)
Building a single-file vanilla HTML/CSS/JS Kanban board with drag-and-drop, filtering, theming, and responsive layout while keeping state in memory.Extending the board with sample cards, modal validation, drop indicators, and theme persistence.Adding search filtering, dynamic counts, theme toggle, and drag placeholder handling.