Muse Ember
by MetaReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 24.4 s
- Cost
- $0.0069
- Output tokens
- 13,683
- Spent thinking
- 3,887 tokens
- Speed
- 616.5 tok/s
- Console errors
- 0
- First token after
- 2.2 s
- First visible text after
- 11.4 s
- Prompt tokens
- 511
- Code size
- 32.2 KB
- Finish reason
- stop
- Served by
- Meta
- Model id
meta/muse-ember- Reasoning sent
{"effort":"xhigh"}- 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 · 680 lines
<!DOCTYPE html>
<html lang="en" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FlowBoard — Kanban Board</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{
--bg:#f3f5fb;--bg2:#eef1f9;--surface:#ffffff;--surface2:#f8fafc;--surface3:#eef2f7;
--text:#0f172a;--muted:#64748b;--muted2:#94a3b8;--border:#e2e8f0;--border2:#cbd5e1;
--accent:#6366f1;--accent2:#8b5cf6;--accent-soft:#eef2ff;
--low-bg:#dcfce7;--low-text:#166534;--low-dot:#22c55e;
--med-bg:#fef3c7;--med-text:#92400e;--med-dot:#f59e0b;
--high-bg:#fee2e2;--high-text:#991b1b;--high-dot:#ef4444;
--shadow:0 1px 2px rgba(15,23,42,.05),0 8px 24px rgba(15,23,42,.06);
--shadow2:0 12px 32px rgba(15,23,42,.12);
--header-bg:rgba(255,255,255,.82);
}
[data-theme="dark"]{
--bg:#0b1220;--bg2:#0f172a;--surface:#131c30;--surface2:#0f172a;--surface3:#1a2742;
--text:#e5e7eb;--muted:#94a3b8;--muted2:#64748b;--border:#1f2a44;--border2:#2a3a5c;
--accent:#818cf8;--accent2:#a78bfa;--accent-soft:#1e1b4b;
--low-bg:rgba(34,197,94,.18);--low-text:#86efac;--low-dot:#22c55e;
--med-bg:rgba(245,158,11,.18);--med-text:#fcd34d;--med-dot:#f59e0b;
--high-bg:rgba(239,68,68,.18);--high-text:#fca5a5;--high-dot:#ef4444;
--shadow:0 1px 2px rgba(0,0,0,.3),0 8px 24px rgba(0,0,0,.25);
--shadow2:0 12px 32px rgba(0,0,0,.45);
--header-bg:rgba(11,18,32,.82);
}
html{scroll-behavior:smooth}
body{
font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Inter,Roboto,Helvetica,Arial,sans-serif;
background:
radial-gradient(700px 400px at 15% -10%, rgba(99,102,241,.14), transparent 60%),
radial-gradient(700px 400px at 90% 0%, rgba(139,92,246,.10), transparent 55%),
var(--bg);
color:var(--text);min-height:100vh;overflow-x:hidden;transition:background .3s,color .3s;
-webkit-font-smoothing:antialiased;
}
button{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}
input,textarea,select{font-family:inherit;color:inherit}
/* HEADER */
header{
position:sticky;top:0;z-index:40;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);
background:var(--header-bg);border-bottom:1px solid var(--border);
}
.header-inner{max-width:1280px;margin:0 auto;padding:14px 22px;display:flex;align-items:center;gap:16px;flex-wrap:wrap}
.logo{display:flex;align-items:center;gap:11px;min-width:0}
.logo-tile{
width:40px;height:40px;border-radius:11px;flex:0 0 40px;display:grid;place-items:center;color:#fff;
background:linear-gradient(135deg,#6366f1,#8b5cf6 55%,#ec4899);
box-shadow:0 6px 16px rgba(99,102,241,.35);font-size:19px;font-weight:800;
}
.logo h1{font-size:20px;letter-spacing:-.03em;line-height:1}
.logo p{font-size:11.5px;color:var(--muted);margin-top:3px;font-weight:500}
.search-wrap{position:relative;flex:1 1 280px;max-width:460px;margin-left:auto}
.search-wrap svg{position:absolute;left:12px;top:50%;transform:translateY(-50%);width:17px;height:17px;color:var(--muted2);pointer-events:none}
#searchInput{
width:100%;height:42px;border-radius:999px;border:1px solid var(--border);background:var(--surface);
padding:0 38px 0 40px;font-size:14px;outline:none;transition:.2s;box-shadow:0 1px 2px rgba(0,0,0,.03);
}
#searchInput::placeholder{color:var(--muted2)}
#searchInput:focus{border-color:var(--accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 16%,transparent)}
#clearSearch{
position:absolute;right:6px;top:50%;transform:translateY(-50%);width:28px;height:28px;border-radius:50%;
display:none;place-items:center;color:var(--muted);font-size:16px;line-height:1;
}
#clearSearch:hover{background:var(--surface3);color:var(--text)}
#clearSearch.show{display:grid}
.icon-btn{
width:42px;height:42px;border-radius:50%;border:1px solid var(--border);background:var(--surface);
display:grid;place-items:center;font-size:17px;flex:0 0 42px;transition:.2s;box-shadow:var(--shadow);
}
.icon-btn:hover{transform:translateY(-1px);border-color:var(--border2)}
.btn-primary{
height:42px;padding:0 18px;border-radius:999px;color:#fff;font-weight:700;font-size:14px;
background:linear-gradient(135deg,#6366f1,#8b5cf6);box-shadow:0 8px 20px rgba(99,102,241,.32);
display:inline-flex;align-items:center;gap:7px;transition:.2s;white-space:nowrap;
}
.btn-primary:hover{transform:translateY(-1px);box-shadow:0 10px 24px rgba(99,102,241,.4)}
.btn-primary:active{transform:translateY(0)}
/* MAIN */
main{max-width:1280px;margin:0 auto;padding:24px 22px 40px}
.hero{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;flex-wrap:wrap;margin-bottom:20px}
.hero h2{font-size:30px;letter-spacing:-.04em;line-height:1.05}
.hero h2 span{background:linear-gradient(90deg,var(--accent),var(--accent2));-webkit-background-clip:text;background-clip:text;color:transparent}
.hero-sub{color:var(--muted);font-size:14px;margin-top:7px}
.progress-card{
background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:14px 16px;
box-shadow:var(--shadow);min-width:280px;flex:0 1 340px;
}
.progress-card-row{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:9px}
.progress-card strong{font-size:13px;letter-spacing:-.01em}
.progress-card span{font-size:12px;color:var(--muted);font-weight:600}
.progress-track{height:8px;border-radius:999px;background:var(--surface3);overflow:hidden}
.progress-fill{height:100%;width:0%;border-radius:999px;background:linear-gradient(90deg,#22c55e,#10b981);transition:width .5s cubic-bezier(.2,.8,.2,1)}
.progress-meta{display:flex;gap:14px;margin-top:9px;font-size:12px;color:var(--muted);font-weight:500}
.progress-meta b{color:var(--text)}
#searchMeta{font-size:12.5px;color:var(--muted);margin:-6px 0 14px;display:none}
#searchMeta.show{display:block}
#searchMeta b{color:var(--accent)}
/* BOARD */
.board{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;align-items:start}
.column{
background:color-mix(in srgb,var(--surface) 72%,transparent);border:1px solid var(--border);border-radius:18px;
padding:14px;box-shadow:var(--shadow);transition:.2s;min-width:0;
}
.column.drag-active{border-color:var(--accent);background:color-mix(in srgb,var(--accent-soft) 55%,var(--surface));box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 12%,transparent),var(--shadow2)}
.col-head{display:flex;align-items:center;gap:9px;padding:2px 4px 13px;border-bottom:1px dashed var(--border);margin-bottom:12px}
.col-dot{width:9px;height:9px;border-radius:50%;flex:0 0 9px}
.col-todo .col-dot{background:#3b82f6;box-shadow:0 0 0 4px rgba(59,130,246,.16)}
.col-progress .col-dot{background:#f59e0b;box-shadow:0 0 0 4px rgba(245,158,11,.16)}
.col-done .col-dot{background:#22c55e;box-shadow:0 0 0 4px rgba(34,197,94,.16)}
.col-head h3{font-size:15px;letter-spacing:-.02em}
.count-pill{
margin-left:auto;min-width:26px;height:24px;padding:0 8px;border-radius:999px;display:grid;place-items:center;
background:var(--surface3);border:1px solid var(--border);font-size:12.5px;font-weight:800;color:var(--muted);
}
.col-add{width:28px;height:28px;border-radius:50%;display:grid;place-items:center;font-size:18px;color:var(--muted);transition:.15s;line-height:1}
.col-add:hover{background:var(--accent);color:#fff;transform:rotate(90deg)}
.cards-list{min-height:150px;display:flex;flex-direction:column;gap:11px}
.cards-list.drag-over{background:color-mix(in srgb,var(--accent) 5%,transparent);border-radius:10px}
/* CARD */
.card{
background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:14px 14px 12px;
box-shadow:var(--shadow);cursor:grab;position:relative;transition:transform .18s,box-shadow .18s,border-color .18s;
animation:fadeUp .45s both;
}
@keyframes fadeUp{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
.card:hover{transform:translateY(-2px);box-shadow:var(--shadow2);border-color:var(--border2)}
.card:active{cursor:grabbing}
.card.dragging{opacity:.38;transform:rotate(2deg) scale(.98);box-shadow:none;border-style:dashed}
.card-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:9px}
.badge{display:inline-flex;align-items:center;gap:6px;height:22px;padding:0 9px;border-radius:999px;font-size:10.5px;font-weight:800;letter-spacing:.06em;text-transform:uppercase}
.badge::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
.badge-low{background:var(--low-bg);color:var(--low-text)}
.badge-medium{background:var(--med-bg);color:var(--med-text)}
.badge-high{background:var(--high-bg);color:var(--high-text)}
.delete-btn{
width:26px;height:26px;border-radius:50%;display:grid;place-items:center;color:var(--muted2);font-size:15px;line-height:1;
opacity:0;transition:.15s;flex:0 0 26px;
}
.card:hover .delete-btn,.card:focus-within .delete-btn{opacity:1}
.delete-btn:hover{background:#fee2e2;color:#dc2626;transform:scale(1.08)}
[data-theme="dark"] .delete-btn:hover{background:rgba(239,68,68,.2);color:#fca5a5}
.card h4{font-size:15px;line-height:1.3;letter-spacing:-.015em;margin-bottom:6px;padding-right:4px}
.card p{font-size:12.8px;line-height:1.5;color:var(--muted);margin-bottom:12px}
.card-bottom{display:flex;align-items:center;justify-content:space-between;gap:8px}
.assignee{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--muted);font-weight:600;min-width:0}
.avatar{width:26px;height:26px;border-radius:50%;display:grid;place-items:center;color:#fff;font-size:10px;font-weight:800;flex:0 0 26px;letter-spacing:.02em}
.due{font-size:11.5px;color:var(--muted2);font-weight:600;white-space:nowrap;display:flex;align-items:center;gap:4px}
.empty-state{
border:1.5px dashed var(--border2);border-radius:14px;padding:22px 14px;text-align:center;color:var(--muted2);
font-size:13px;line-height:1.5;
}
.empty-state .big{font-size:20px;display:block;margin-bottom:5px;opacity:.7}
.drop-indicator{
height:4px;border-radius:999px;background:linear-gradient(90deg,var(--accent),var(--accent2));
box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 18%,transparent),0 4px 14px rgba(99,102,241,.45);
margin:2px 2px;position:relative;flex:0 0 4px;animation:pulse 1s ease-in-out infinite;
}
@keyframes pulse{0%,100%{transform:scaleX(1)}50%{transform:scaleX(.96)}}
.col-foot{width:100%;margin-top:12px;height:38px;border-radius:10px;color:var(--muted);font-size:13px;font-weight:600;transition:.15s}
.col-foot:hover{background:var(--surface3);color:var(--accent)}
/* MODAL */
.modal-overlay{
position:fixed;inset:0;background:rgba(15,23,42,.48);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
display:none;align-items:center;justify-content:center;padding:18px;z-index:100;
}
.modal-overlay.open{display:flex}
.modal{
width:100%;max-width:460px;background:var(--surface);border:1px solid var(--border);border-radius:20px;
box-shadow:0 24px 60px rgba(0,0,0,.28);padding:22px;animation:pop .25s cubic-bezier(.2,.9,.3,1.2);
max-height:calc(100vh - 36px);overflow-y:auto;
}
@keyframes pop{from{opacity:0;transform:translateY(14px) scale(.97)}to{opacity:1;transform:translateY(0) scale(1)}}
.modal-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:4px}
.modal-head h3{font-size:19px;letter-spacing:-.03em}
.modal-head p{font-size:13px;color:var(--muted);margin-top:4px}
.modal-close{width:32px;height:32px;border-radius:50%;display:grid;place-items:center;color:var(--muted);font-size:16px;flex:0 0 32px}
.modal-close:hover{background:var(--surface3);color:var(--text)}
.field{margin-top:16px}
.field label{display:block;font-size:12px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);margin-bottom:7px}
.field input[type="text"],.field textarea{
width:100%;border:1px solid var(--border);background:var(--surface2);border-radius:11px;padding:11px 13px;font-size:14px;outline:none;transition:.2s;resize:vertical;
}
.field input:focus,.field textarea:focus{border-color:var(--accent);box-shadow:0 0 0 4px color-mix(in srgb,var(--accent) 14%,transparent);background:var(--surface)}
.field input.error{border-color:#ef4444;box-shadow:0 0 0 4px rgba(239,68,68,.14);animation:shake .3s}
@keyframes shake{0%,100%{transform:translateX(0)}25%{transform:translateX(-5px)}75%{transform:translateX(5px)}}
.error-msg{display:none;color:#dc2626;font-size:12.5px;font-weight:600;margin-top:6px}
.error-msg.show{display:block}
.pills{display:flex;gap:8px;flex-wrap:wrap}
.pill{
height:36px;padding:0 15px;border-radius:999px;border:1px solid var(--border);background:var(--surface2);
font-size:13px;font-weight:700;color:var(--muted);display:inline-flex;align-items:center;gap:7px;transition:.15s;
}
.pill::before{content:"";width:8px;height:8px;border-radius:50%}
.pill[data-priority="low"]::before{background:var(--low-dot)}
.pill[data-priority="medium"]::before{background:var(--med-dot)}
.pill[data-priority="high"]::before{background:var(--high-dot)}
.pill.active[data-priority="low"]{background:var(--low-bg);color:var(--low-text);border-color:var(--low-dot)}
.pill.active[data-priority="medium"]{background:var(--med-bg);color:var(--med-text);border-color:var(--med-dot)}
.pill.active[data-priority="high"]{background:var(--high-bg);color:var(--high-text);border-color:var(--high-dot)}
.pill.col-pill::before{display:none}
.pill.col-pill.active{background:var(--accent-soft);color:var(--accent);border-color:var(--accent)}
.modal-actions{display:flex;gap:10px;margin-top:22px}
.btn-cancel{flex:1;height:44px;border-radius:11px;border:1px solid var(--border);background:var(--surface2);font-weight:700;font-size:14px;transition:.15s}
.btn-cancel:hover{background:var(--surface3)}
.btn-submit{flex:1.4;height:44px;border-radius:11px;color:#fff;font-weight:700;font-size:14px;background:linear-gradient(135deg,#6366f1,#8b5cf6);box-shadow:0 8px 20px rgba(99,102,241,.3);transition:.15s}
.btn-submit:hover{transform:translateY(-1px)}
/* TOAST */
.toast{
position:fixed;left:50%;bottom:22px;transform:translate(-50%,20px);background:#0f172a;color:#fff;
padding:11px 18px;border-radius:999px;font-size:13.5px;font-weight:600;box-shadow:0 14px 34px rgba(0,0,0,.3);
opacity:0;pointer-events:none;transition:.3s;z-index:200;display:flex;align-items:center;gap:8px;max-width:calc(100vw - 32px);text-align:center;
}
.toast.show{opacity:1;transform:translate(-50%,0)}
footer{text-align:center;color:var(--muted2);font-size:12px;padding:6px 20px 26px}
@media(max-width:900px){
.board{grid-template-columns:1fr}
.search-wrap{margin-left:0;max-width:none;order:3;flex-basis:100%}
.hero h2{font-size:25px}
.progress-card{flex-basis:100%}
}
@media(max-width:480px){
.header-inner{padding:12px 14px;gap:10px}
main{padding:18px 14px 30px}
.logo p{display:none}
.btn-primary{padding:0 14px;font-size:13px}
.modal{padding:18px}
.modal-actions{flex-direction:column}
}
</style>
</head>
<body>
<header>
<div class="header-inner">
<div class="logo">
<div class="logo-tile">◧</div>
<div>
<h1>FlowBoard</h1>
<p>Kanban · Sprint 24</p>
</div>
</div>
<div class="search-wrap">
<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 21l-4.3-4.3"/></svg>
<input id="searchInput" type="text" placeholder="Search cards by title…" aria-label="Search cards by title" autocomplete="off">
<button id="clearSearch" aria-label="Clear search">×</button>
</div>
<button class="icon-btn" id="themeToggle" aria-label="Toggle light / dark theme" title="Toggle theme">🌙</button>
<button class="btn-primary" id="addCardBtn"><span style="font-size:17px;line-height:1">+</span> Add card</button>
</div>
</header>
<main>
<div class="hero">
<div>
<h2>Project <span>Board</span></h2>
<p class="hero-sub">Drag cards between columns to update progress. Click + to add, × to delete.</p>
</div>
<div class="progress-card">
<div class="progress-card-row"><strong>Sprint progress</strong><span id="progressLabel">0 of 0 done</span></div>
<div class="progress-track"><div class="progress-fill" id="progressFill"></div></div>
<div class="progress-meta"><span><b id="statTodo">0</b> to do</span><span><b id="statProg">0</b> in progress</span><span><b id="statDone">0</b> done</span></div>
</div>
</div>
<p id="searchMeta"></p>
<section class="board" id="board">
<div class="column col-todo" data-col="todo">
<div class="col-head"><span class="col-dot"></span><h3>To do</h3><span class="count-pill" id="count-todo">0</span><button class="col-add" data-add-col="todo" aria-label="Add card to To do">+</button></div>
<div class="cards-list" data-col="todo"></div>
<button class="col-foot" data-add-col="todo">+ Add a card</button>
</div>
<div class="column col-progress" data-col="progress">
<div class="col-head"><span class="col-dot"></span><h3>In progress</h3><span class="count-pill" id="count-progress">0</span><button class="col-add" data-add-col="progress" aria-label="Add card to In progress">+</button></div>
<div class="cards-list" data-col="progress"></div>
<button class="col-foot" data-add-col="progress">+ Add a card</button>
</div>
<div class="column col-done" data-col="done">
<div class="col-head"><span class="col-dot"></span><h3>Done</h3><span class="count-pill" id="count-done">0</span><button class="col-add" data-add-col="done" aria-label="Add card to Done">+</button></div>
<div class="cards-list" data-col="done"></div>
<button class="col-foot" data-add-col="done">+ Add a card</button>
</div>
</section>
</main>
<footer>FlowBoard · Built with vanilla HTML, CSS & JavaScript · State in memory only</footer>
<!-- MODAL -->
<div class="modal-overlay" id="modalOverlay" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
<form class="modal" id="cardForm" novalidate>
<div class="modal-head">
<div><h3 id="modalTitle">Add a new card</h3><p>Fill in the details — title is required.</p></div>
<button type="button" class="modal-close" id="modalClose" aria-label="Close">✕</button>
</div>
<div class="field">
<label for="cardTitle">Title *</label>
<input type="text" id="cardTitle" placeholder="e.g. Design onboarding flow" maxlength="80">
<div class="error-msg" id="titleError">Please enter a title for this card.</div>
</div>
<div class="field">
<label for="cardDesc">Description <span style="text-transform:none;letter-spacing:0;font-weight:500">(optional)</span></label>
<textarea id="cardDesc" rows="2" placeholder="Short description…" maxlength="140"></textarea>
</div>
<div class="field">
<label>Priority</label>
<div class="pills" id="priorityPills">
<button type="button" class="pill" data-priority="low">Low</button>
<button type="button" class="pill active" data-priority="medium">Medium</button>
<button type="button" class="pill" data-priority="high">High</button>
</div>
</div>
<div class="field">
<label>Column</label>
<div class="pills" id="columnPills">
<button type="button" class="pill col-pill active" data-col="todo">To do</button>
<button type="button" class="pill col-pill" data-col="progress">In progress</button>
<button type="button" class="pill col-pill" data-col="done">Done</button>
</div>
</div>
<div class="modal-actions">
<button type="button" class="btn-cancel" id="modalCancel">Cancel</button>
<button type="submit" class="btn-submit">Add card</button>
</div>
</form>
</div>
<div class="toast" id="toast"></div>
<script>
(function(){
"use strict";
var COLUMNS = [
{id:"todo", name:"To do"},
{id:"progress", name:"In progress"},
{id:"done", name:"Done"}
];
var AVATAR_COLORS = ["#6366f1","#ec4899","#f59e0b","#10b981","#3b82f6","#8b5cf6","#ef4444","#14b8a6"];
var cards = [
{id:1, title:"Design new landing page hero", desc:"Create 3 concepts and pick one for the spring campaign.", priority:"high", column:"todo", assignee:"Maya Chen", due:"Mar 14"},
{id:2, title:"Write API documentation", desc:"Document the new billing endpoints with request examples.", priority:"medium", column:"todo", assignee:"Alex Rivera", due:"Mar 18"},
{id:3, title:"Refactor authentication flow", desc:"Migrate to OAuth 2.0 and add login rate-limiting.", priority:"medium", column:"progress", assignee:"Sara Kim", due:"Mar 12"},
{id:4, title:"Build onboarding email sequence", desc:"5-step drip for trial users — first draft ready for review.", priority:"low", column:"progress", assignee:"Jordan Lee", due:"Mar 20"},
{id:5, title:"Set up CI/CD pipeline", desc:"GitHub Actions for staging deploys on every merge.", priority:"high", column:"done", assignee:"Priya Nair", due:"Mar 5"},
{id:6, title:"Update brand color palette", desc:"Refreshed tokens across the design system and docs.", priority:"low", column:"done", assignee:"Leo Martins", due:"Mar 2"}
];
var searchQuery = "";
var draggedId = null;
var dropTarget = {col:null, index:0};
var selectedPriority = "medium";
var selectedColumn = "todo";
var board = document.getElementById("board");
var searchInput = document.getElementById("searchInput");
var clearSearch = document.getElementById("clearSearch");
var searchMeta = document.getElementById("searchMeta");
var themeToggle = document.getElementById("themeToggle");
var modalOverlay = document.getElementById("modalOverlay");
var cardForm = document.getElementById("cardForm");
var cardTitle = document.getElementById("cardTitle");
var cardDesc = document.getElementById("cardDesc");
var titleError = document.getElementById("titleError");
var toastEl = document.getElementById("toast");
var indicator = document.createElement("div");
indicator.className = "drop-indicator";
var toastTimer = null;
function showToast(msg){
toastEl.textContent = "✓ " + msg;
toastEl.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function(){ toastEl.classList.remove("show"); }, 2400);
}
function esc(str){
return String(str).replace(/[&<>"']/g, function(c){
return {"&":"&","<":"<",">":">",'"':""","'":"'"}[c];
});
}
function initials(name){
return name.split(" ").map(function(w){return w[0];}).join("").slice(0,2).toUpperCase();
}
function avatarColor(name){
var n = 0;
for(var i=0;i<name.length;i++) n += name.charCodeAt(i);
return AVATAR_COLORS[n % AVATAR_COLORS.length];
}
function colName(id){
for(var i=0;i<COLUMNS.length;i++) if(COLUMNS[i].id===id) return COLUMNS[i].name;
return id;
}
function render(){
var q = searchQuery.trim().toLowerCase();
var visibleTotal = 0;
COLUMNS.forEach(function(col){
var list = board.querySelector('[data-col="'+col.id+'"].cards-list');
var visible = cards.filter(function(c){
return c.column === col.id && c.title.toLowerCase().indexOf(q) !== -1;
});
visibleTotal += visible.length;
document.getElementById("count-" + col.id).textContent = visible.length;
if(visible.length === 0){
list.innerHTML = '<div class="empty-state"><span class="big">' +
(q ? "🔍" : "📭") + '</span>' +
(q ? 'No cards match “' + esc(searchQuery.trim()) + '”.' : "No cards here yet.<br>Drag one in or add a new card.") +
'</div>';
} else {
list.innerHTML = visible.map(function(c, i){
return '' +
'<article class="card" draggable="true" data-id="' + c.id + '" style="animation-delay:' + (i*55) + 'ms" tabindex="0">' +
'<div class="card-top">' +
'<span class="badge badge-' + c.priority + '">' + c.priority + '</span>' +
'<button class="delete-btn" data-delete="' + c.id + '" aria-label="Delete card: ' + esc(c.title) + '" title="Delete card">×</button>' +
'</div>' +
'<h4>' + esc(c.title) + '</h4>' +
(c.desc ? '<p>' + esc(c.desc) + '</p>' : '') +
'<div class="card-bottom">' +
'<span class="assignee"><span class="avatar" style="background:' + avatarColor(c.assignee) + '">' + initials(c.assignee) + '</span>' + esc(c.assignee) + '</span>' +
'<span class="due">📅 ' + esc(c.due) + '</span>' +
'</div>' +
'</article>';
}).join("");
}
});
// Stats + progress (based on all cards, not filtered)
var total = cards.length;
var done = cards.filter(function(c){return c.column==="done";}).length;
var prog = cards.filter(function(c){return c.column==="progress";}).length;
var todo = cards.filter(function(c){return c.column==="todo";}).length;
document.getElementById("statTodo").textContent = todo;
document.getElementById("statProg").textContent = prog;
document.getElementById("statDone").textContent = done;
document.getElementById("progressLabel").textContent = done + " of " + total + " done";
document.getElementById("progressFill").style.width = (total ? Math.round(done/total*100) : 0) + "%";
if(q){
searchMeta.innerHTML = "Showing <b>" + visibleTotal + "</b> of <b>" + total + "</b> cards matching “" + esc(searchQuery.trim()) + "”";
searchMeta.classList.add("show");
} else {
searchMeta.classList.remove("show");
}
clearSearch.classList.toggle("show", searchInput.value.length > 0);
}
/* SEARCH — live filter by title */
searchInput.addEventListener("input", function(){
searchQuery = searchInput.value;
render();
});
clearSearch.addEventListener("click", function(){
searchInput.value = "";
searchQuery = "";
render();
searchInput.focus();
});
/* DELETE — delegated */
board.addEventListener("click", function(e){
var del = e.target.closest("[data-delete]");
if(del){
var id = Number(del.getAttribute("data-delete"));
var card = null;
for(var i=0;i<cards.length;i++) if(cards[i].id===id) card = cards[i];
cards = cards.filter(function(c){return c.id!==id;});
render();
if(card) showToast("Deleted “" + card.title + "”");
return;
}
var add = e.target.closest("[data-add-col]");
if(add){ openModal(add.getAttribute("data-add-col")); }
});
/* DRAG & DROP */
function getDragAfterElement(list, y){
var els = Array.prototype.slice.call(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;
}
board.addEventListener("dragstart", function(e){
var cardEl = e.target.closest(".card");
if(!cardEl) return;
draggedId = Number(cardEl.getAttribute("data-id"));
cardEl.classList.add("dragging");
try{ e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", String(draggedId)); }catch(err){}
setTimeout(function(){ cardEl.classList.add("dragging"); }, 0);
});
board.addEventListener("dragover", function(e){
var list = e.target.closest(".cards-list");
if(!list || draggedId === null) return;
e.preventDefault();
try{ e.dataTransfer.dropEffect = "move"; }catch(err){}
document.querySelectorAll(".column").forEach(function(c){ c.classList.remove("drag-active"); });
list.closest(".column").classList.add("drag-active");
list.classList.add("drag-over");
var empty = list.querySelector(".empty-state");
if(empty) empty.style.display = "none";
var after = getDragAfterElement(list, e.clientY);
var cardsEls = Array.prototype.slice.call(list.querySelectorAll(".card:not(.dragging)"));
if(after === null){
list.appendChild(indicator);
dropTarget = {col:list.getAttribute("data-col"), index:cardsEls.length};
} else {
list.insertBefore(indicator, after);
dropTarget = {col:list.getAttribute("data-col"), index:cardsEls.indexOf(after)};
}
});
board.addEventListener("dragleave", function(e){
var list = e.target.closest(".cards-list");
if(list && !list.contains(e.relatedTarget)){
list.classList.remove("drag-over");
var col = list.closest(".column");
if(col) col.classList.remove("drag-active");
}
});
board.addEventListener("drop", function(e){
var list = e.target.closest(".cards-list");
if(!list || draggedId === null) return;
e.preventDefault();
var col = list.getAttribute("data-col");
var index = dropTarget.index;
moveCard(draggedId, col, index);
cleanupDrag();
render();
showToast("Moved to " + colName(col));
});
board.addEventListener("dragend", function(){
cleanupDrag();
});
function cleanupDrag(){
draggedId = null;
indicator.remove();
document.querySelectorAll(".column").forEach(function(c){ c.classList.remove("drag-active"); });
document.querySelectorAll(".cards-list").forEach(function(l){ l.classList.remove("drag-over"); });
document.querySelectorAll(".card.dragging").forEach(function(c){ c.classList.remove("dragging"); });
}
function moveCard(id, col, index){
var moving = null, i;
for(i=0;i<cards.length;i++) if(cards[i].id===id){ moving = cards[i]; break; }
if(!moving) return;
cards = cards.filter(function(c){return c.id!==id;});
moving.column = col;
var colCards = cards.filter(function(c){return c.column===col;});
if(index >= colCards.length){
if(colCards.length){
var lastId = colCards[colCards.length-1].id;
var pos = -1;
for(i=0;i<cards.length;i++) if(cards[i].id===lastId) pos = i;
cards.splice(pos+1, 0, moving);
} else {
cards.push(moving);
}
} else {
var beforeId = colCards[index].id;
var pos2 = -1;
for(i=0;i<cards.length;i++) if(cards[i].id===beforeId) pos2 = i;
cards.splice(pos2, 0, moving);
}
}
/* MODAL */
function setPills(containerId, attr, value){
var pills = document.querySelectorAll("#" + containerId + " .pill");
pills.forEach(function(p){ p.classList.toggle("active", p.getAttribute(attr) === value); });
}
function openModal(col){
selectedColumn = col || "todo";
selectedPriority = "medium";
setPills("priorityPills", "data-priority", selectedPriority);
setPills("columnPills", "data-col", selectedColumn);
cardTitle.value = "";
cardDesc.value = "";
cardTitle.classList.remove("error");
titleError.classList.remove("show");
modalOverlay.classList.add("open");
setTimeout(function(){ cardTitle.focus(); }, 60);
}
function closeModal(){ modalOverlay.classList.remove("open"); }
document.getElementById("addCardBtn").addEventListener("click", function(){ openModal("todo"); });
document.getElementById("modalClose").addEventListener("click", closeModal);
document.getElementById("modalCancel").addEventListener("click", closeModal);
modalOverlay.addEventListener("click", function(e){ if(e.target === modalOverlay) closeModal(); });
document.addEventListener("keydown", function(e){ if(e.key === "Escape") closeModal(); });
document.getElementById("priorityPills").addEventListener("click", function(e){
var p = e.target.closest(".pill"); if(!p) return;
selectedPriority = p.getAttribute("data-priority");
setPills("priorityPills", "data-priority", selectedPriority);
});
document.getElementById("columnPills").addEventListener("click", function(e){
var p = e.target.closest(".pill"); if(!p) return;
selectedColumn = p.getAttribute("data-col");
setPills("columnPills", "data-col", selectedColumn);
});
cardTitle.addEventListener("input", function(){
cardTitle.classList.remove("error");
titleError.classList.remove("show");
});
cardForm.addEventListener("submit", function(e){
e.preventDefault();
var title = cardTitle.value.trim();
if(!title){
cardTitle.classList.add("error");
titleError.classList.add("show");
cardTitle.focus();
return;
}
cards.push({
id: Date.now(),
title: title,
desc: cardDesc.value.trim(),
priority: selectedPriority,
column: selectedColumn,
assignee: "You",
due: "Today"
});
closeModal();
render();
showToast("Added “" + title + "” to " + colName(selectedColumn));
});
/* THEME TOGGLE — in-memory only */
themeToggle.addEventListener("click", function(){
var html = document.documentElement;
var isDark = html.getAttribute("data-theme") === "dark";
html.setAttribute("data-theme", isDark ? "light" : "dark");
themeToggle.textContent = isDark ? "🌙" : "☀️";
themeToggle.setAttribute("aria-label", isDark ? "Switch to dark theme" : "Switch to light theme");
});
render();
})();
</script>
</body>
</html>Reasoning trace (3,887 tokens)
Implementing a vanilla HTML CSS JS Kanban board with drag-and-drop, modal form, search, theme toggle, and in-memory state.Extending the layout with responsive constraints, drag-and-drop indicators, modal validation, and live search filtering.Adding visible count badges, theme toggle, and drag-and-drop logic with modal and state rendering.Implementing drag-and-drop reordering with DOM insertion logic and state array updates.Adding delegation for drag events and rendering UI states for search, empty, and theme without storage.