Muse Ember
by MetaReasoningClean
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">↔ 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 {"&":"&","<":"<",">":">",'"':""","'":"'"}[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)+'">×</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.