Muse Ember
by MetaReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 29.0 s
- Cost
- $0.0091
- Output tokens
- 18,077
- Spent thinking
- 9,179 tokens
- Speed
- 675 tok/s
- Console errors
- 0
- First token after
- 2.2 s
- First visible text after
- 20.6 s
- Prompt tokens
- 511
- Code size
- 29.3 KB
- Finish reason
- stop
- Served by
- Meta
- Model id
meta/muse-ember- Reasoning sent
model default- 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 · 591 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:#f4f5f8; --surface:#ffffff; --surface2:#eef0f4; --surface3:#f8f9fb;
--border:#e2e5ec; --border2:#d4d9e3; --text:#161a22; --muted:#6b7280; --muted2:#9aa1af;
--accent:#6366f1; --accent2:#8b5cf6; --accent-soft:rgba(99,102,241,.10);
--shadow:0 1px 2px rgba(15,17,23,.05),0 4px 14px rgba(15,17,23,.05);
--shadow-lg:0 18px 50px rgba(15,17,23,.22);
}
[data-theme="dark"]{
--bg:#0d0f13; --surface:#171a21; --surface2:#12141a; --surface3:#1c2029;
--border:#262b36; --border2:#323a49; --text:#f2f4f8; --muted:#9aa3b2; --muted2:#6e7889;
--accent:#818cf8; --accent2:#a78bfa; --accent-soft:rgba(129,140,248,.14);
--shadow:0 1px 2px rgba(0,0,0,.35),0 4px 14px rgba(0,0,0,.25);
--shadow-lg:0 18px 50px rgba(0,0,0,.6);
}
html,body{height:100%}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--text);overflow-x:hidden;-webkit-font-smoothing:antialiased}
button,input{font:inherit}
button{cursor:pointer}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
/* Header */
.topbar{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;padding:16px 28px;background:var(--surface);border-bottom:1px solid var(--border);position:sticky;top:0;z-index:30}
.brand{display:flex;align-items:center;gap:12px;min-width:0}
.logo{width:44px;height:44px;border-radius:13px;background:linear-gradient(135deg,#6366f1,#8b5cf6);display:grid;place-items:center;flex:0 0 auto;box-shadow:0 6px 16px rgba(99,102,241,.35)}
.brand h1{font-size:21px;line-height:1;letter-spacing:-.02em}
.brand p{font-size:12.5px;color:var(--muted);margin-top:4px;white-space:nowrap}
.controls{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.search-wrap{position:relative;flex:1 1 260px;min-width:220px;max-width:420px}
.search-wrap svg{position:absolute;left:13px;top:50%;transform:translateY(-50%);color:var(--muted2);pointer-events:none}
.search-wrap input{width:100%;padding:11px 38px 11px 40px;border:1px solid var(--border);background:var(--surface2);color:var(--text);border-radius:999px;font-size:14px;outline:none;transition:border-color .15s,background .15s,box-shadow .15s}
.search-wrap input::placeholder{color:var(--muted2)}
.search-wrap input:focus{border-color:var(--accent);background:var(--surface);box-shadow:0 0 0 3px var(--accent-soft)}
.clear-search{position:absolute;right:8px;top:50%;transform:translateY(-50%);width:26px;height:26px;border:none;border-radius:50%;background:var(--surface3);color:var(--muted);font-size:18px;line-height:1;display:grid;place-items:center}
.clear-search:hover{background:var(--border);color:var(--text)}
.icon-btn{width:42px;height:42px;border-radius:50%;border:1px solid var(--border);background:var(--surface);color:var(--text);display:grid;place-items:center;flex:0 0 auto;transition:background .15s,border-color .15s,transform .15s}
.icon-btn:hover{background:var(--surface2);border-color:var(--border2);transform:translateY(-1px)}
.icon-btn.small{width:34px;height:34px;font-size:22px;border:none;background:var(--surface2)}
[data-theme="light"] .icon-sun{display:none}
[data-theme="dark"] .icon-moon{display:none}
.btn-primary{border:none;background:linear-gradient(135deg,#6366f1,#7c5cf6);color:#fff;font-weight:700;font-size:14px;padding:11px 19px;border-radius:999px;box-shadow:0 6px 16px rgba(99,102,241,.35);transition:transform .15s,box-shadow .15s,filter .15s;white-space:nowrap}
.btn-primary:hover{transform:translateY(-1px);filter:brightness(1.06);box-shadow:0 9px 22px rgba(99,102,241,.42)}
.btn-primary:active{transform:translateY(0)}
.btn-ghost{border:1px solid var(--border);background:var(--surface);color:var(--text);font-weight:600;font-size:14px;padding:11px 18px;border-radius:999px}
.btn-ghost:hover{background:var(--surface2)}
/* Board meta */
.board-meta{display:flex;align-items:center;gap:16px;flex-wrap:wrap;padding:18px 28px 2px}
.meta-left{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.meta-title{font-size:17px;font-weight:800;letter-spacing:-.02em}
.meta-sub{font-size:13px;color:var(--muted)}
.progress-track{flex:1 1 160px;min-width:140px;height:9px;background:var(--surface2);border:1px solid var(--border);border-radius:999px;overflow:hidden}
.progress-fill{height:100%;width:0%;background:linear-gradient(90deg,#22c55e,#4ade80);border-radius:999px;transition:width .5s ease}
.meta-hint{font-size:12.5px;color:var(--muted2);white-space:nowrap}
/* Board */
.board{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;padding:16px 28px 6px;align-items:start}
.column{background:var(--surface2);border:1px solid var(--border);border-radius:18px;padding:14px;min-height:380px;display:flex;flex-direction:column;transition:border-color .15s,box-shadow .15s}
.column:has(.drag-over){border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
.col-header{display:flex;align-items:center;gap:9px;padding:2px 2px 13px}
.col-dot{width:10px;height:10px;border-radius:50%;flex:0 0 auto}
.dot-todo{background:#3b82f6;box-shadow:0 0 0 3px rgba(59,130,246,.18)}
.dot-progress{background:#f59e0b;box-shadow:0 0 0 3px rgba(245,158,11,.18)}
.dot-done{background:#22c55e;box-shadow:0 0 0 3px rgba(34,197,94,.18)}
.col-header h2{font-size:15px;font-weight:800;letter-spacing:-.01em}
.count{margin-left:2px;background:var(--surface);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:800;min-width:26px;height:24px;padding:0 7px;border-radius:999px;display:grid;place-items:center}
.col-add{margin-left:auto;width:30px;height:30px;border-radius:9px;border:1px solid var(--border);background:var(--surface);color:var(--muted);font-size:19px;line-height:1;display:grid;place-items:center;transition:all .15s}
.col-add:hover{background:var(--accent);border-color:var(--accent);color:#fff}
.cards{display:flex;flex-direction:column;gap:12px;flex:1;border-radius:14px;padding:2px;min-height:120px;transition:background .15s}
.cards.drag-over{background:var(--accent-soft)}
/* Card */
.card{background:var(--surface);border:1px solid var(--border);border-radius:14px;padding:14px;box-shadow:var(--shadow);cursor:grab;user-select:none;transition:transform .16s ease,box-shadow .16s ease,border-color .16s ease,opacity .16s ease;animation:popIn .28s ease both}
.card:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(15,17,23,.10);border-color:var(--border2)}
.card:active{cursor:grabbing}
.card.dragging{opacity:.45;transform:rotate(2deg) scale(.98);box-shadow:var(--shadow-lg);cursor:grabbing}
.card.removing{opacity:0;transform:scale(.94) translateX(12px)}
.card.flash{animation:flash 1.4s ease both}
@keyframes popIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}
@keyframes flash{0%{box-shadow:0 0 0 0 rgba(99,102,241,.55)}35%{box-shadow:0 0 0 6px rgba(99,102,241,.18)}100%{box-shadow:var(--shadow)}}
.card-top{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:9px}
.badge{font-size:11px;letter-spacing:.07em;text-transform:uppercase;font-weight:800;padding:4px 9px;border-radius:999px;display:inline-flex;align-items:center;gap:6px}
.badge::before{content:"";width:6px;height:6px;border-radius:50%;background:currentColor}
.badge-low{background:#dcfce7;color:#166534}
.badge-medium{background:#fef3c7;color:#92400e}
.badge-high{background:#fee2e2;color:#991b1b}
[data-theme="dark"] .badge-low{background:rgba(34,197,94,.18);color:#4ade80}
[data-theme="dark"] .badge-medium{background:rgba(245,158,11,.18);color:#fbbf24}
[data-theme="dark"] .badge-high{background:rgba(239,68,68,.18);color:#f87171}
.delete-btn{width:28px;height:28px;border:none;border-radius:8px;background:transparent;color:var(--muted2);font-size:20px;line-height:1;display:grid;place-items:center;transition:all .15s;flex:0 0 auto}
.delete-btn:hover{background:#fee2e2;color:#dc2626;transform:scale(1.06)}
[data-theme="dark"] .delete-btn:hover{background:rgba(239,68,68,.18);color:#f87171}
.card-title{font-size:15px;line-height:1.35;font-weight:700;letter-spacing:-.01em}
.card-desc{font-size:13px;line-height:1.5;color:var(--muted);margin-top:6px}
.card-meta{display:flex;align-items:center;gap:8px;margin-top:13px;padding-top:11px;border-top:1px solid var(--border)}
.avatar{width:27px;height:27px;border-radius:50%;background:linear-gradient(135deg,#6366f1,#ec4899);color:#fff;font-size:11px;font-weight:800;display:grid;place-items:center;flex:0 0 auto}
.meta-name{font-size:12.5px;color:var(--muted);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.drag-hint{margin-left:auto;font-size:11.5px;color:var(--muted2);white-space:nowrap}
.empty-state{border:1.5px dashed var(--border2);border-radius:12px;padding:20px 14px;text-align:center;color:var(--muted2);font-size:13px;line-height:1.5}
.drop-indicator{height:74px;min-height:74px;border:2px dashed var(--accent);background:var(--accent-soft);border-radius:14px;flex:0 0 auto}
.foot-note{text-align:center;color:var(--muted2);font-size:12.5px;padding:16px 20px 22px}
/* Modal */
.modal-overlay{position:fixed;inset:0;background:rgba(13,15,20,.58);backdrop-filter:blur(6px);display:grid;place-items:center;padding:20px;opacity:0;pointer-events:none;transition:opacity .2s ease;z-index:50}
.modal-overlay.show{opacity:1;pointer-events:auto}
.modal{width:100%;max-width:470px;background:var(--surface);border:1px solid var(--border);border-radius:20px;padding:22px;box-shadow:var(--shadow-lg);transform:translateY(10px) scale(.98);transition:transform .22s ease}
.modal-overlay.show .modal{transform:translateY(0) scale(1)}
.modal-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px}
.modal-header h3{font-size:18px;letter-spacing:-.02em}
.field{display:block;margin-bottom:16px}
.field>span{display:block;font-size:13px;font-weight:700;margin-bottom:8px}
.field>span em{color:#dc2626;font-style:normal}
.field input[type="text"]{width:100%;padding:12px 13px;border:1px solid var(--border);border-radius:12px;background:var(--surface2);color:var(--text);font-size:14px;outline:none;transition:border-color .15s,box-shadow .15s,background .15s}
.field input[type="text"]:focus{border-color:var(--accent);background:var(--surface);box-shadow:0 0 0 3px var(--accent-soft)}
.field input.invalid{border-color:#dc2626;box-shadow:0 0 0 3px rgba(220,38,38,.12)}
.error{display:none;color:#dc2626;font-size:12.5px;margin:-8px 0 14px}
.error.show{display:block}
.segmented{display:flex;gap:8px;flex-wrap:wrap}
.seg-btn{flex:1 1 0;min-width:0;padding:10px 8px;border:1px solid var(--border);border-radius:12px;background:var(--surface2);color:var(--muted);font-weight:700;font-size:13px;display:flex;align-items:center;justify-content:center;gap:7px;transition:all .15s}
.seg-btn:hover{border-color:var(--border2);color:var(--text)}
.seg-btn.active{border-color:var(--accent);background:var(--accent-soft);color:var(--text)}
.p-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto}
.p-low{background:#22c55e}.p-medium{background:#f59e0b}.p-high{background:#ef4444}
.c-dot{width:8px;height:8px;border-radius:50%;flex:0 0 auto}
.modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:22px}
@media (max-width:900px){
.board{grid-template-columns:1fr;padding:14px 18px 6px}
.topbar{padding:14px 18px}
.board-meta{padding:16px 18px 2px}
.column{min-height:0}
.meta-hint{display:none}
}
@media (max-width:560px){
.topbar{align-items:stretch}
.controls{width:100%}
.search-wrap{flex:1 1 100%;max-width:none;order:3}
.brand p{display:none}
#addCardBtn{flex:1}
.board{padding:12px 14px 6px}
.board-meta{padding:14px 14px 2px}
.seg-btn{flex:1 1 100%}
}
</style>
</head>
<body>
<header class="topbar">
<div class="brand">
<div class="logo" aria-hidden="true">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round"><rect x="3" y="3" width="5" height="13" rx="1.5"/><rect x="10" y="3" width="5" height="9" rx="1.5"/><rect x="17" y="3" width="4" height="16" rx="1.5"/></svg>
</div>
<div>
<h1>Flowboard</h1>
<p>Product Launch • Sprint 12</p>
</div>
</div>
<div class="controls">
<div class="search-wrap">
<svg width="16" height="16" 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..." autocomplete="off" aria-label="Search cards by title">
<button id="clearSearch" class="clear-search" type="button" hidden aria-label="Clear search">×</button>
</div>
<button id="themeToggle" class="icon-btn" type="button" aria-label="Toggle light / dark theme" title="Toggle theme">
<svg class="icon-moon" width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
<svg class="icon-sun" width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><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>
<button id="addCardBtn" class="btn-primary" type="button">+ Add card</button>
</div>
</header>
<div class="board-meta">
<div class="meta-left">
<span class="meta-title">Sprint board</span>
<span class="meta-sub" id="progressText">2 of 6 done</span>
</div>
<div class="progress-track" aria-hidden="true"><div class="progress-fill" id="progressFill"></div></div>
<span class="meta-hint">↔ Drag cards between columns</span>
</div>
<main class="board" id="board">
<section class="column" aria-label="To do column">
<header class="col-header">
<span class="col-dot dot-todo"></span><h2>To do</h2><span class="count" id="count-todo">0</span>
<button class="col-add" type="button" data-add="todo" aria-label="Add card to To do">+</button>
</header>
<div class="cards" data-column="todo" id="list-todo"></div>
</section>
<section class="column" aria-label="In progress column">
<header class="col-header">
<span class="col-dot dot-progress"></span><h2>In progress</h2><span class="count" id="count-progress">0</span>
<button class="col-add" type="button" data-add="progress" aria-label="Add card to In progress">+</button>
</header>
<div class="cards" data-column="progress" id="list-progress"></div>
</section>
<section class="column" aria-label="Done column">
<header class="col-header">
<span class="col-dot dot-done"></span><h2>Done</h2><span class="count" id="count-done">0</span>
<button class="col-add" type="button" data-add="done" aria-label="Add card to Done">+</button>
</header>
<div class="cards" data-column="done" id="list-done"></div>
</section>
</main>
<p class="foot-note">Search filters live by title • Click × on a card to delete it • State lives in memory only</p>
<div class="modal-overlay" id="modalOverlay" aria-hidden="true">
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
<div class="modal-header">
<h3 id="modalTitle">Add a new card</h3>
<button class="icon-btn small" id="closeModal" type="button" aria-label="Close dialog">×</button>
</div>
<form id="addForm" novalidate>
<label class="field">
<span>Title <em>*</em></span>
<input id="titleInput" type="text" placeholder="e.g. Design new landing page" maxlength="120" autocomplete="off">
</label>
<p class="error" id="titleError">Please enter a title for the card.</p>
<div class="field">
<span>Priority</span>
<div class="segmented" id="priorityOptions">
<button type="button" class="seg-btn" data-priority="low"><span class="p-dot p-low"></span>Low</button>
<button type="button" class="seg-btn active" data-priority="medium"><span class="p-dot p-medium"></span>Medium</button>
<button type="button" class="seg-btn" data-priority="high"><span class="p-dot p-high"></span>High</button>
</div>
</div>
<div class="field">
<span>Column</span>
<div class="segmented" id="columnOptions">
<button type="button" class="seg-btn active" data-column-val="todo"><span class="c-dot" style="background:#3b82f6"></span>To do</button>
<button type="button" class="seg-btn" data-column-val="progress"><span class="c-dot" style="background:#f59e0b"></span>In progress</button>
<button type="button" class="seg-btn" data-column-val="done"><span class="c-dot" style="background:#22c55e"></span>Done</button>
</div>
</div>
<div class="modal-actions">
<button type="button" class="btn-ghost" id="cancelBtn">Cancel</button>
<button type="submit" class="btn-primary">Add card</button>
</div>
</form>
</div>
</div>
<script>
(function(){
"use strict";
var searchInput = document.getElementById('searchInput');
var clearSearch = document.getElementById('clearSearch');
var themeToggle = document.getElementById('themeToggle');
var addCardBtn = document.getElementById('addCardBtn');
var board = document.getElementById('board');
var progressFill = document.getElementById('progressFill');
var progressText = document.getElementById('progressText');
var modalOverlay = document.getElementById('modalOverlay');
var addForm = document.getElementById('addForm');
var titleInput = document.getElementById('titleInput');
var titleError = document.getElementById('titleError');
var priorityOptions = document.getElementById('priorityOptions');
var columnOptions = document.getElementById('columnOptions');
var cards = [
{id:'c1', title:'Design new onboarding flow', priority:'high', column:'todo', desc:'Create wireframes and an interactive prototype for the 3-step signup.', assignee:'AK', name:'Ava Kim'},
{id:'c2', title:'Write API documentation', priority:'medium', column:'todo', desc:'Document auth endpoints, error codes and rate limits for v2.', assignee:'JM', name:'James Miller'},
{id:'c3', title:'Fix checkout payment bug', priority:'high', column:'progress', desc:'Stripe webhook fails on Safari — investigate the retry logic.', assignee:'SR', name:'Sara Rossi'},
{id:'c4', title:'Build search autocomplete', priority:'medium', column:'progress', desc:'Debounced suggestions with full keyboard navigation support.', assignee:'LT', name:'Leo Turner'},
{id:'c5', title:'Ship v2.1 release notes', priority:'low', column:'done', desc:'Changelog, blog post and in-app announcement for the release.', assignee:'AK', name:'Ava Kim'},
{id:'c6', title:'Refactor auth middleware', priority:'medium', column:'done', desc:'Split into token, session and permissions modules with tests.', assignee:'JM', name:'James Miller'}
];
var filter = '';
var dragId = null;
var selectedPriority = 'medium';
var selectedColumn = 'todo';
var placeholder = document.createElement('div');
placeholder.className = 'drop-indicator';
placeholder.setAttribute('aria-hidden','true');
function createCardElement(card){
var el = document.createElement('article');
el.className = 'card';
el.draggable = true;
el.dataset.id = card.id;
el.dataset.column = card.column;
var top = document.createElement('div');
top.className = 'card-top';
var badge = document.createElement('span');
badge.className = 'badge badge-' + card.priority;
badge.textContent = card.priority;
var del = document.createElement('button');
del.className = 'delete-btn';
del.type = 'button';
del.title = 'Delete card';
del.setAttribute('aria-label','Delete ' + card.title);
del.dataset.id = card.id;
del.textContent = '\u00D7';
top.appendChild(badge);
top.appendChild(del);
var title = document.createElement('h3');
title.className = 'card-title';
title.textContent = card.title;
el.appendChild(top);
el.appendChild(title);
if(card.desc){
var desc = document.createElement('p');
desc.className = 'card-desc';
desc.textContent = card.desc;
el.appendChild(desc);
}
var meta = document.createElement('div');
meta.className = 'card-meta';
var avatar = document.createElement('span');
avatar.className = 'avatar';
avatar.textContent = card.assignee || 'YO';
avatar.title = card.name || 'You';
var name = document.createElement('span');
name.className = 'meta-name';
name.textContent = card.name || 'You';
var hint = document.createElement('span');
hint.className = 'drag-hint';
hint.textContent = '\u2807 Drag';
meta.appendChild(avatar);
meta.appendChild(name);
meta.appendChild(hint);
el.appendChild(meta);
return el;
}
function render(){
var cols = ['todo','progress','done'];
cols.forEach(function(col){
var list = document.getElementById('list-' + col);
var countEl = document.getElementById('count-' + col);
var all = cards.filter(function(c){ return c.column === col; });
countEl.textContent = String(all.length);
list.innerHTML = '';
var visible = all.filter(function(c){
return !filter || c.title.toLowerCase().indexOf(filter) !== -1;
});
if(visible.length === 0){
var empty = document.createElement('div');
empty.className = 'empty-state';
empty.textContent = filter
? 'No cards match "' + searchInput.value + '"'
: 'No cards here — drag one in or add a new card.';
list.appendChild(empty);
} else {
visible.forEach(function(card){ list.appendChild(createCardElement(card)); });
}
});
var total = cards.length;
var done = cards.filter(function(c){ return c.column === 'done'; }).length;
var pct = total ? Math.round(done / total * 100) : 0;
progressFill.style.width = pct + '%';
progressText.textContent = total ? (done + ' of ' + total + ' done \u2022 ' + pct + '% complete') : 'No cards yet';
}
/* ---- Search (live by title) ---- */
searchInput.addEventListener('input', function(){
filter = searchInput.value.trim().toLowerCase();
clearSearch.hidden = !searchInput.value;
render();
});
clearSearch.addEventListener('click', function(){
searchInput.value = '';
filter = '';
clearSearch.hidden = true;
render();
searchInput.focus();
});
/* ---- Theme toggle (memory only) ---- */
themeToggle.addEventListener('click', function(){
var html = document.documentElement;
html.dataset.theme = html.dataset.theme === 'dark' ? 'light' : 'dark';
});
/* ---- Delete (delegated) ---- */
board.addEventListener('click', function(e){
var btn = e.target.closest('.delete-btn');
if(!btn) return;
var id = btn.dataset.id;
var cardEl = btn.closest('.card');
if(cardEl){
cardEl.classList.add('removing');
setTimeout(function(){
cards = cards.filter(function(c){ return c.id !== id; });
render();
}, 180);
} else {
cards = cards.filter(function(c){ return c.id !== id; });
render();
}
});
/* ---- Add buttons in column headers ---- */
document.querySelectorAll('[data-add]').forEach(function(btn){
btn.addEventListener('click', function(){ openModal(btn.dataset.add); });
});
/* ---- 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;
dragId = cardEl.dataset.id;
cardEl.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
try{ e.dataTransfer.setData('text/plain', dragId); }catch(err){}
});
board.addEventListener('dragend', function(e){
var cardEl = e.target.closest('.card');
if(cardEl) cardEl.classList.remove('dragging');
dragId = null;
if(placeholder.parentNode) placeholder.remove();
document.querySelectorAll('.drag-over').forEach(function(el){ el.classList.remove('drag-over'); });
});
document.querySelectorAll('.cards').forEach(function(list){
list.addEventListener('dragover', function(e){
if(!dragId) return;
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
list.classList.add('drag-over');
var after = getDragAfterElement(list, e.clientY);
if(after === null){ list.appendChild(placeholder); }
else { list.insertBefore(placeholder, after); }
});
list.addEventListener('dragleave', function(e){
if(!list.contains(e.relatedTarget)){
list.classList.remove('drag-over');
if(placeholder.parentNode === list) placeholder.remove();
}
});
list.addEventListener('drop', function(e){
e.preventDefault();
list.classList.remove('drag-over');
var targetColumn = list.dataset.column;
if(!dragId){ if(placeholder.parentNode) placeholder.remove(); return; }
var insertIndex = Array.prototype.slice.call(list.querySelectorAll('.card:not(.dragging)')).length;
if(placeholder.parentNode === list){
var count = 0;
for(var i=0;i<list.children.length;i++){
if(list.children[i] === placeholder) break;
if(list.children[i].classList && list.children[i].classList.contains('card')) count++;
}
insertIndex = count;
}
if(placeholder.parentNode) placeholder.remove();
var idx = cards.findIndex(function(c){ return c.id === dragId; });
if(idx === -1){ dragId = null; return; }
var moved = cards.splice(idx,1)[0];
moved.column = targetColumn;
var colCards = cards.filter(function(c){ return c.column === targetColumn; });
if(insertIndex >= colCards.length){
if(colCards.length === 0){ cards.push(moved); }
else {
var lastIdx = cards.findIndex(function(c){ return c.id === colCards[colCards.length-1].id; });
cards.splice(lastIdx + 1, 0, moved);
}
} else {
var beforeIdx = cards.findIndex(function(c){ return c.id === colCards[insertIndex].id; });
cards.splice(beforeIdx, 0, moved);
}
dragId = null;
render();
});
});
/* ---- Modal ---- */
function syncSegUI(){
priorityOptions.querySelectorAll('.seg-btn').forEach(function(b){
b.classList.toggle('active', b.dataset.priority === selectedPriority);
});
columnOptions.querySelectorAll('.seg-btn').forEach(function(b){
b.classList.toggle('active', b.dataset.columnVal === selectedColumn);
});
}
priorityOptions.addEventListener('click', function(e){
var btn = e.target.closest('.seg-btn');
if(!btn) return;
selectedPriority = btn.dataset.priority;
syncSegUI();
});
columnOptions.addEventListener('click', function(e){
var btn = e.target.closest('.seg-btn');
if(!btn) return;
selectedColumn = btn.dataset.columnVal;
syncSegUI();
});
function openModal(col){
selectedColumn = col || 'todo';
syncSegUI();
titleInput.value = '';
titleError.classList.remove('show');
titleInput.classList.remove('invalid');
modalOverlay.classList.add('show');
modalOverlay.setAttribute('aria-hidden','false');
setTimeout(function(){ titleInput.focus(); }, 60);
}
function closeModal(){
modalOverlay.classList.remove('show');
modalOverlay.setAttribute('aria-hidden','true');
}
addCardBtn.addEventListener('click', function(){ openModal('todo'); });
document.getElementById('closeModal').addEventListener('click', closeModal);
document.getElementById('cancelBtn').addEventListener('click', closeModal);
modalOverlay.addEventListener('click', function(e){ if(e.target === modalOverlay) closeModal(); });
document.addEventListener('keydown', function(e){
if(e.key === 'Escape' && modalOverlay.classList.contains('show')) closeModal();
});
titleInput.addEventListener('input', function(){
titleError.classList.remove('show');
titleInput.classList.remove('invalid');
});
addForm.addEventListener('submit', function(e){
e.preventDefault();
var title = titleInput.value.trim();
if(!title){
titleError.classList.add('show');
titleInput.classList.add('invalid');
titleInput.focus();
return;
}
var newCard = {
id: 'c' + Date.now().toString(36) + Math.floor(Math.random()*999),
title: title,
priority: selectedPriority,
column: selectedColumn,
desc: '',
assignee: 'YO',
name: 'You'
};
cards.push(newCard);
if(filter){
searchInput.value = '';
filter = '';
clearSearch.hidden = true;
}
closeModal();
render();
var el = document.querySelector('.card[data-id="' + newCard.id + '"]');
if(el){
el.classList.add('flash');
el.scrollIntoView({behavior:'smooth', block:'center'});
}
});
render();
})();
</script>
</body>
</html>Reasoning trace (9,179 tokens)
Implementing a single-file HTML kanban board with drag-and-drop, filtering, theming, and responsive layout using system fonts and in-memory state.Adding UI layout, modal form, drag-and-drop handling, search filtering, theming, and delete actions with counts and empty states.Extending the UI with theme colors, responsive grid, drag-and-drop ordering, and modal form validation.Adding CSS variables, responsive layout, drag-and-drop styling, and modal form details.Defining color tokens, badge styles, card markup, and initial data with DOM rendering and event delegation.Adding drag-and-drop handlers with placeholder insertion and modal form validation for card creation.Extending UI with progress bar, theme toggle, accessibility labels, and delete animations.Implementing segmented button styling, drag-and-drop handlers, and card rendering with validation and theme logic.Adding document-level add-button listeners, drag-over handling, modal open/close logic, and theme and search event bindings.Defining modal, segmented control, badge, avatar, empty state, and responsive CSS with theme overrides and focus styles.