GPT-6 Luna Pro
by OpenAIreasoning defaultClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 44 s
- Cost
- $0.014
- Output tokens
- 23,038
- Spent thinking
- 4,203 tokens
- Speed
- 8484.5 tok/s
- Console errors
- 0
- First token after
- 1 min 41 s
- First visible text after
- 1 min 43 s
- Prompt tokens
- 23,904
- Code size
- 30.2 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna-pro- Reasoning sent
model default- Max output
- 128,000 tokens
- Generated
- Nov 13, 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 · 660 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Orbit — Product launch board</title>
<style>
:root {
color-scheme: light;
--page: #f5f6fa;
--surface: #ffffff;
--surface-2: #f8f9fc;
--text: #202336;
--muted: #83889b;
--line: #e9eaf0;
--accent: #6558df;
--accent-soft: #f0efff;
--shadow: 0 2px 8px rgba(34, 39, 68, .045), 0 12px 30px rgba(34, 39, 68, .025);
}
* { box-sizing: border-box; }
body {
margin: 0;
min-width: 320px;
background: var(--page);
color: var(--text);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 14px;
transition: background .2s, color .2s;
}
body.dark {
color-scheme: dark;
--page: #171923;
--surface: #20222f;
--surface-2: #252735;
--text: #eef0fa;
--muted: #a1a5b8;
--line: #343746;
--accent: #9b90ff;
--accent-soft: #302d4c;
--shadow: 0 3px 12px rgba(0, 0, 0, .15);
}
button, input, select { font: inherit; }
button { color: inherit; }
.topbar {
height: 68px;
background: var(--surface);
border-bottom: 1px solid var(--line);
display: flex;
align-items: center;
padding: 0 max(28px, calc((100vw - 1190px) / 2));
gap: 34px;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
font-size: 17px;
font-weight: 750;
letter-spacing: -.5px;
white-space: nowrap;
}
.brand-mark {
width: 29px;
height: 29px;
border-radius: 9px;
display: grid;
place-items: center;
color: white;
background: linear-gradient(145deg, #8275fa, #5b4bd4);
box-shadow: 0 4px 10px rgba(101, 88, 223, .24);
}
.brand-mark svg { width: 17px; height: 17px; }
.top-nav { display: flex; align-items: center; gap: 28px; height: 100%; }
.top-nav a {
height: 100%;
display: flex;
align-items: center;
position: relative;
text-decoration: none;
color: var(--muted);
font-weight: 550;
}
.top-nav a.active { color: var(--text); }
.top-nav a.active::after {
content: "";
position: absolute;
left: 0; right: 0; bottom: -1px;
height: 2px;
background: var(--accent);
border-radius: 2px 2px 0 0;
}
.top-right { margin-left: auto; display: flex; align-items: center; gap: 14px; }
.team-avatars { display: flex; padding-left: 4px; }
.avatar {
width: 29px;
height: 29px;
margin-left: -5px;
display: inline-grid;
place-items: center;
border-radius: 50%;
border: 2px solid var(--surface);
color: #fff;
font-size: 9px;
font-weight: 750;
letter-spacing: .15px;
}
.av-lilac { background: #8a79df; }
.av-coral { background: #e28472; }
.av-teal { background: #51a99c; }
.av-blue { background: #5486c8; }
.avatar.more { background: var(--surface-2); color: var(--muted); border-color: var(--surface); font-size: 9px; }
.top-divider { height: 24px; width: 1px; background: var(--line); }
.icon-btn {
width: 35px; height: 35px;
display: grid; place-items: center;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--surface);
cursor: pointer;
transition: background .15s, border .15s, transform .15s;
}
.icon-btn:hover { background: var(--surface-2); border-color: #d9d9e8; }
.icon-btn:active { transform: scale(.96); }
.icon-btn svg { width: 17px; height: 17px; color: var(--muted); }
main { max-width: 1190px; margin: 0 auto; padding: 30px 24px 44px; }
.crumbs {
display: flex; align-items: center; gap: 9px;
color: var(--muted); font-size: 12px; font-weight: 550;
margin-bottom: 17px;
}
.crumb-dot { width: 7px; height: 7px; border-radius: 2px; background: #e9a655; }
.crumb-sep { color: #c2c4d0; font-size: 16px; font-weight: 400; }
.project-heading {
display: flex; align-items: flex-start; justify-content: space-between; gap: 20px;
margin-bottom: 23px;
}
h1 { margin: 0; font-size: 27px; line-height: 1.22; letter-spacing: -.8px; font-weight: 730; }
.subtitle { color: var(--muted); margin-top: 7px; font-size: 13px; }
.heading-actions { display: flex; align-items: center; gap: 9px; padding-top: 3px; }
.button {
height: 37px;
border-radius: 9px;
padding: 0 13px;
display: inline-flex; align-items: center; justify-content: center; gap: 8px;
font-size: 12px; font-weight: 650;
cursor: pointer;
transition: transform .15s, box-shadow .15s, background .15s;
}
.button:active { transform: translateY(1px); }
.button svg { width: 15px; height: 15px; }
.button.secondary { border: 1px solid var(--line); background: var(--surface); color: var(--text); }
.button.secondary:hover { background: var(--surface-2); }
.button.primary {
border: 1px solid var(--accent);
color: white;
background: #6558df;
box-shadow: 0 3px 7px rgba(101, 88, 223, .18);
}
.button.primary:hover { background: #584bcf; box-shadow: 0 5px 12px rgba(101, 88, 223, .25); }
body.dark .button.primary { background: #6b60d8; border-color: #6b60d8; }
.board-toolbar {
display: flex; align-items: center; justify-content: space-between; gap: 14px;
margin-bottom: 17px;
}
.board-tabs { display: flex; align-items: center; gap: 22px; height: 35px; }
.board-tab {
border: 0; background: none; padding: 0;
color: var(--muted); font-size: 12px; font-weight: 600;
cursor: pointer; height: 100%; position: relative;
}
.board-tab.selected { color: var(--accent); }
.board-tab.selected::after {
content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
height: 2px; border-radius: 2px; background: var(--accent);
}
.toolbar-tools { display: flex; align-items: center; gap: 9px; }
.search-wrap {
width: 218px; height: 35px; display: flex; align-items: center; gap: 8px;
padding: 0 10px; background: var(--surface);
border: 1px solid var(--line); border-radius: 9px;
}
.search-wrap svg { width: 15px; height: 15px; color: #9a9daf; flex: 0 0 auto; }
.search-wrap input {
width: 100%; min-width: 0; background: transparent; border: 0; outline: 0;
color: var(--text); font-size: 12px;
}
.search-wrap input::placeholder { color: #a2a5b5; }
.filter-button { height: 35px; padding: 0 10px; font-size: 12px; }
.filter-button svg { width: 14px; height: 14px; }
.board {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
align-items: start;
}
.column {
min-width: 0;
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: 12px;
padding: 13px 12px 12px;
transition: background .15s, border-color .15s, box-shadow .15s;
}
.column.drag-over {
border-color: var(--accent);
background: var(--accent-soft);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 14%, transparent);
}
.column-head {
height: 25px; display: flex; align-items: center; gap: 8px;
padding: 0 1px; margin-bottom: 11px;
}
.status-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.todo-dot { background: #999eae; }
.progress-dot { background: #edae4e; }
.done-dot { background: #56b392; }
.column-title { font-size: 12px; font-weight: 700; letter-spacing: .05px; }
.count {
display: inline-grid; place-items: center;
min-width: 19px; height: 19px; padding: 0 5px;
color: var(--muted); background: var(--surface);
border: 1px solid var(--line); border-radius: 6px;
font-size: 10px; font-weight: 650;
}
.column-add {
margin-left: auto; width: 26px; height: 26px;
border: 0; border-radius: 7px; background: transparent; color: #9a9daf;
display: grid; place-items: center; cursor: pointer;
}
.column-add:hover { background: var(--surface); color: var(--accent); }
.column-add svg { width: 16px; height: 16px; }
.card-list { display: flex; flex-direction: column; gap: 10px; min-height: 105px; }
.card {
position: relative;
padding: 13px 13px 11px;
background: var(--surface);
border: 1px solid var(--line);
border-radius: 10px;
box-shadow: var(--shadow);
cursor: grab;
transition: border-color .15s, box-shadow .15s, opacity .15s, transform .15s;
}
.card:hover { border-color: #d8d9e6; box-shadow: 0 4px 13px rgba(38, 40, 72, .075); }
body.dark .card:hover { border-color: #4a4d60; }
.card.dragging { opacity: .42; transform: rotate(-1deg); cursor: grabbing; }
.card-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 9px; }
.category {
color: #8a8da0; font-size: 9px; font-weight: 750; letter-spacing: .65px;
text-transform: uppercase;
}
.priority {
display: inline-flex; align-items: center; gap: 5px;
border-radius: 5px; padding: 4px 7px;
font-size: 9px; line-height: 1; font-weight: 700;
text-transform: capitalize;
}
.priority::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.priority.high { color: #d76565; background: #fff0ef; }
.priority.medium { color: #c88b2d; background: #fff6e6; }
.priority.low { color: #579578; background: #eaf7f0; }
body.dark .priority.high { background: #432b31; color: #f08a8a; }
body.dark .priority.medium { background: #453823; color: #e7b45e; }
body.dark .priority.low { background: #243e35; color: #75c7a0; }
.card h3 { font-size: 13px; line-height: 1.42; letter-spacing: -.1px; margin: 0 0 6px; font-weight: 680; }
.card-desc { color: var(--muted); font-size: 10.5px; line-height: 1.45; margin: 0 0 11px; }
.card-bottom {
border-top: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
padding-top: 9px;
display: flex; align-items: center; justify-content: space-between; gap: 6px;
}
.due { display: flex; align-items: center; gap: 5px; color: var(--muted); font-size: 10px; white-space: nowrap; }
.due svg { width: 12px; height: 12px; color: #a3a6b4; }
.card-people { display: flex; align-items: center; }
.card-people .avatar { width: 22px; height: 22px; font-size: 8px; border-color: var(--surface); }
.delete-card {
position: absolute; right: 8px; top: 36px; width: 25px; height: 25px;
display: grid; place-items: center; border: 0; border-radius: 6px;
background: var(--surface); color: #a1a4b4; opacity: 0;
cursor: pointer; transition: opacity .15s, color .15s, background .15s;
}
.card:hover .delete-card, .delete-card:focus-visible { opacity: 1; }
.delete-card:hover { color: #d85d68; background: #fff0f0; }
.delete-card svg { width: 14px; height: 14px; }
.drop-marker {
height: 5px; min-height: 5px; border-radius: 5px;
background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent);
margin: 2px 2px;
}
.empty-state {
color: var(--muted); font-size: 11px; text-align: center; padding: 18px 8px;
border: 1px dashed var(--line); border-radius: 9px;
}
.board-foot {
margin-top: 15px; color: var(--muted); font-size: 10px;
display: flex; align-items: center; gap: 6px;
}
.board-foot svg { width: 13px; height: 13px; }
.modal-backdrop {
position: fixed; inset: 0; z-index: 10;
display: none; align-items: center; justify-content: center;
padding: 18px; background: rgba(22, 24, 40, .44);
backdrop-filter: blur(3px);
}
.modal-backdrop.open { display: flex; }
.modal {
width: min(100%, 410px); background: var(--surface);
border: 1px solid var(--line); border-radius: 15px;
padding: 23px; box-shadow: 0 22px 70px rgba(18, 20, 42, .24);
animation: modal-in .18s ease-out;
}
@keyframes modal-in { from { opacity: 0; transform: translateY(8px) scale(.985); } to { opacity: 1; transform: none; } }
.modal-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 19px; }
.modal h2 { font-size: 18px; margin: 0 0 5px; letter-spacing: -.35px; }
.modal-copy { margin: 0; font-size: 12px; color: var(--muted); }
.modal-close {
border: 0; background: transparent; color: var(--muted); cursor: pointer;
width: 28px; height: 28px; border-radius: 7px; display: grid; place-items: center;
}
.modal-close:hover { background: var(--surface-2); }
.modal-close svg { width: 16px; height: 16px; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; font-size: 11px; font-weight: 650; margin-bottom: 7px; }
.form-group input, .form-group select {
width: 100%; height: 39px; border-radius: 8px;
border: 1px solid var(--line); background: var(--surface);
color: var(--text); padding: 0 11px; outline: 0; font-size: 12px;
}
.form-group input:focus, .form-group select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 13%, transparent); }
.form-group input::placeholder { color: #a3a6b4; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 21px; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
@media (max-width: 760px) {
.topbar { height: 60px; padding: 0 16px; gap: 22px; }
.top-nav { gap: 18px; }
.top-nav a { font-size: 12px; }
.top-right { gap: 9px; }
.team-avatars, .top-divider { display: none; }
main { padding: 24px 16px 35px; }
.board { grid-template-columns: 1fr; gap: 12px; }
.column { padding: 12px; }
.card-list { min-height: 0; }
.delete-card { opacity: 1; }
.board-toolbar { align-items: flex-end; }
.search-wrap { width: 185px; }
}
@media (max-width: 480px) {
.topbar { gap: 15px; }
.brand { font-size: 15px; }
.brand-mark { width: 27px; height: 27px; }
.top-nav { gap: 13px; }
.top-nav a { font-size: 11px; }
.top-nav a:nth-child(n+2) { display: none; }
.top-right { margin-left: auto; }
main { padding: 20px 13px 32px; }
.project-heading { margin-bottom: 19px; }
h1 { font-size: 24px; }
.heading-actions { gap: 7px; }
.heading-actions .button.secondary { padding: 0 9px; }
.heading-actions .button.primary { padding: 0 10px; }
.board-toolbar { flex-direction: column; align-items: stretch; gap: 9px; margin-bottom: 13px; }
.board-tabs { height: 30px; }
.toolbar-tools { width: 100%; }
.search-wrap { flex: 1; width: auto; }
.filter-button { flex: 0 0 auto; }
}
</style>
</head>
<body>
<header class="topbar">
<div class="brand">
<span class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none"><path d="M5 7.5 12 4l7 3.5v9L12 20l-7-3.5v-9Z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="m5.5 7.8 6.5 3.4 6.5-3.4M12 11.3V19" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>
</span>
orbit
</div>
<nav class="top-nav" aria-label="Main navigation">
<a href="#" class="active">Workspace</a>
<a href="#">Projects</a>
<a href="#">Inbox</a>
</nav>
<div class="top-right">
<div class="team-avatars" aria-label="Project team">
<span class="avatar av-lilac">ML</span><span class="avatar av-coral">JR</span><span class="avatar av-teal">SK</span><span class="avatar more">+2</span>
</div>
<span class="top-divider"></span>
<button class="icon-btn" id="theme-toggle" aria-label="Switch to dark theme" title="Toggle theme">
<svg id="theme-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M20.2 15.1A8.5 8.5 0 0 1 8.9 3.8 8.5 8.5 0 1 0 20.2 15.1Z"/></svg>
</button>
</div>
</header>
<main>
<div class="crumbs"><span class="crumb-dot"></span><span>Acme Studio</span><span class="crumb-sep">›</span><span>Projects</span><span class="crumb-sep">›</span><span>Product launch</span></div>
<section class="project-heading">
<div>
<h1>Product launch</h1>
<div class="subtitle">Plan, track, and ship something great together.</div>
</div>
<div class="heading-actions">
<button class="button secondary" id="invite-btn" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M16 21v-2a4 4 0 0 0-4-4H7a4 4 0 0 0-4 4v2M9.5 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM19 8v6M22 11h-6"/></svg>
<span>Invite</span>
</button>
<button class="button primary" id="add-task" type="button">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg>
Add task
</button>
</div>
</section>
<div class="board-toolbar">
<div class="board-tabs" aria-label="Project views">
<button class="board-tab selected" type="button">Board</button>
<button class="board-tab" type="button">List</button>
<button class="board-tab" type="button">Calendar</button>
</div>
<div class="toolbar-tools">
<label class="search-wrap">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><circle cx="11" cy="11" r="7"/><path d="m20 20-4-4"/></svg>
<input id="search" type="search" placeholder="Search tasks…" aria-label="Search tasks by title">
</label>
<button class="button secondary filter-button" type="button" id="filter-btn" title="Priority filter">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M7 12h10m-7 6h4"/></svg>
Filter
</button>
</div>
</div>
<section class="board" aria-label="Kanban board">
<section class="column" data-column="todo" aria-label="To do">
<header class="column-head">
<span class="status-dot todo-dot"></span><span class="column-title">To do</span><span class="count" id="count-todo">0</span>
<button class="column-add" data-add-column="todo" aria-label="Add task to To do"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg></button>
</header>
<div class="card-list" id="list-todo"></div>
</section>
<section class="column" data-column="progress" aria-label="In progress">
<header class="column-head">
<span class="status-dot progress-dot"></span><span class="column-title">In progress</span><span class="count" id="count-progress">0</span>
<button class="column-add" data-add-column="progress" aria-label="Add task to In progress"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg></button>
</header>
<div class="card-list" id="list-progress"></div>
</section>
<section class="column" data-column="done" aria-label="Done">
<header class="column-head">
<span class="status-dot done-dot"></span><span class="column-title">Done</span><span class="count" id="count-done">0</span>
<button class="column-add" data-add-column="done" aria-label="Add task to Done"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg></button>
</header>
<div class="card-list" id="list-done"></div>
</section>
</section>
<div class="board-foot">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8v4l2.5 2.5"/><circle cx="12" cy="12" r="9"/></svg>
Last updated just now <span>·</span> Drag a card to move it between columns
</div>
</main>
<div class="modal-backdrop" id="modal-backdrop" role="presentation">
<section class="modal" role="dialog" aria-modal="true" aria-labelledby="modal-title">
<div class="modal-head">
<div><h2 id="modal-title">Add a task</h2><p class="modal-copy">Add a new card to your project board.</p></div>
<button class="modal-close" id="modal-close" type="button" aria-label="Close dialog"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"><path d="m18 6-12 12M6 6l12 12"/></svg></button>
</div>
<form id="task-form">
<div class="form-group">
<label for="task-title">Task title <span aria-hidden="true" style="color:#d76565">*</span></label>
<input id="task-title" name="title" type="text" maxlength="90" placeholder="e.g. Review launch checklist" required>
</div>
<div class="form-group">
<label for="task-priority">Priority</label>
<select id="task-priority" name="priority">
<option value="low">Low</option><option value="medium" selected>Medium</option><option value="high">High</option>
</select>
</div>
<div class="form-group">
<label for="task-column">Add to column</label>
<select id="task-column" name="column">
<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="button secondary" id="cancel-modal" type="button">Cancel</button>
<button class="button primary" type="submit">Create task</button>
</div>
</form>
</section>
</div>
<script>
(() => {
let tasks = [
{ id: 1, title: "Review onboarding flow", priority: "high", column: "todo", category: "Design · UX", desc: "Walk through the first-run experience and flag any friction.", due: "Due today", initials: "ML", color: "lilac" },
{ id: 2, title: "Write release notes", priority: "medium", column: "todo", category: "Content · Docs", desc: "Summarize the customer-facing updates for v2.4.", due: "Mar 28", initials: "JR", color: "coral" },
{ id: 3, title: "Build billing settings page", priority: "high", column: "progress", category: "Product · Web", desc: "Finish the subscription and payment method screens.", due: "Due tomorrow", initials: "SK", color: "teal" },
{ id: 4, title: "QA mobile navigation", priority: "medium", column: "progress", category: "Engineering", desc: "Check menu states across the supported device sizes.", due: "Mar 29", initials: "AP", color: "blue" },
{ id: 5, title: "Ship analytics dashboard", priority: "low", column: "done", category: "Product · Data", desc: "Core usage metrics are ready for the launch review.", due: "Completed Mar 25", initials: "JR", color: "coral" },
{ id: 6, title: "Update component library", priority: "low", column: "done", category: "Design system", desc: "Buttons, inputs, and tokens are synced with the new UI.", due: "Completed Mar 24", initials: "ML", color: "lilac" }
];
const labels = { todo: "To do", progress: "In progress", done: "Done" };
const listIds = { todo: "list-todo", progress: "list-progress", done: "list-done" };
const search = document.getElementById("search");
const backdrop = document.getElementById("modal-backdrop");
const form = document.getElementById("task-form");
const titleInput = document.getElementById("task-title");
let nextId = 7;
let draggedId = null;
let marker = null;
const escapeHTML = value => String(value).replace(/[&<>"']/g, ch => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[ch]));
function cardHTML(task) {
return `<article class="card" draggable="true" data-id="${task.id}" aria-label="${escapeHTML(task.title)}">
<div class="card-top"><span class="category">${escapeHTML(task.category || "General")}</span><span class="priority ${task.priority}">${task.priority}</span></div>
<h3>${escapeHTML(task.title)}</h3>
${task.desc ? `<p class="card-desc">${escapeHTML(task.desc)}</p>` : ""}
<div class="card-bottom">
<span class="due"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><rect x="3.5" y="5" width="17" height="16" rx="2"/><path d="M7.5 3v4M16.5 3v4M3.5 10h17"/></svg>${escapeHTML(task.due || "No due date")}</span>
<span class="card-people" aria-label="Assigned to ${escapeHTML(task.initials || "You")}"><span class="avatar av-${escapeHTML(task.color || "lilac")}">${escapeHTML(task.initials || "YO")}</span></span>
</div>
<button class="delete-card" type="button" aria-label="Delete ${escapeHTML(task.title)}" title="Delete task"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16M10 11v6m4-6v6M5.5 7l1 14h11l1-14M9 7V4h6v3"/></svg></button>
</article>`;
}
function render() {
const query = search.value.trim().toLowerCase();
Object.keys(listIds).forEach(column => {
const all = tasks.filter(task => task.column === column);
const list = document.getElementById(listIds[column]);
document.getElementById(`count-${column}`).textContent = all.length;
const shown = all.filter(task => task.title.toLowerCase().includes(query));
list.innerHTML = shown.length ? shown.map(cardHTML).join("") : (query ? '<div class="empty-state">No matching tasks</div>' : "");
});
}
function openModal(column = "todo") {
document.getElementById("task-column").value = column;
backdrop.classList.add("open");
titleInput.value = "";
setTimeout(() => titleInput.focus(), 30);
}
function closeModal() { backdrop.classList.remove("open"); }
document.getElementById("add-task").addEventListener("click", () => openModal());
document.querySelectorAll("[data-add-column]").forEach(button => button.addEventListener("click", () => openModal(button.dataset.addColumn)));
document.getElementById("modal-close").addEventListener("click", closeModal);
document.getElementById("cancel-modal").addEventListener("click", closeModal);
backdrop.addEventListener("click", event => { if (event.target === backdrop) closeModal(); });
document.addEventListener("keydown", event => { if (event.key === "Escape" && backdrop.classList.contains("open")) closeModal(); });
form.addEventListener("submit", event => {
event.preventDefault();
const title = titleInput.value.trim();
if (!title) { titleInput.focus(); return; }
tasks.push({
id: nextId++, title, priority: document.getElementById("task-priority").value,
column: document.getElementById("task-column").value,
category: "New task", desc: "", due: "No due date", initials: "YO", color: "lilac"
});
render();
closeModal();
});
document.getElementById("theme-toggle").addEventListener("click", () => {
const isDark = document.body.classList.toggle("dark");
document.getElementById("theme-toggle").setAttribute("aria-label", isDark ? "Switch to light theme" : "Switch to dark theme");
document.getElementById("theme-icon").innerHTML = isDark
? '<circle cx="12" cy="12" r="4"/><path d="M12 2v2m0 16v2M4.93 4.93l1.42 1.42m11.3 11.3 1.42 1.42M2 12h2m16 0h2M4.93 19.07l1.42-1.42m11.3-11.3 1.42-1.42"/>'
: '<path d="M20.2 15.1A8.5 8.5 0 0 1 8.9 3.8 8.5 8.5 0 1 0 20.2 15.1Z"/>';
});
search.addEventListener("input", render);
document.getElementById("filter-btn").addEventListener("click", event => {
const current = event.currentTarget.dataset.filter || "";
const next = current === "" ? "high" : current === "high" ? "medium" : current === "medium" ? "low" : "";
event.currentTarget.dataset.filter = next;
event.currentTarget.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6h16M7 12h10m-7 6h4"/></svg>${next ? ` ${next[0].toUpperCase()}${next.slice(1)} ×` : " Filter"}`;
const query = search.value.trim().toLowerCase();
Object.keys(listIds).forEach(column => {
const list = document.getElementById(listIds[column]);
const shown = tasks.filter(task => task.column === column && task.title.toLowerCase().includes(query) && (!next || task.priority === next));
list.innerHTML = shown.length ? shown.map(cardHTML).join("") : ((query || next) ? '<div class="empty-state">No matching tasks</div>' : "");
});
});
document.getElementById("invite-btn").addEventListener("click", event => {
const button = event.currentTarget;
const label = button.querySelector("span");
label.textContent = label.textContent === "Invite" ? "Invited ✓" : "Invite";
});
document.addEventListener("click", event => {
const deleteButton = event.target.closest(".delete-card");
if (!deleteButton) return;
const card = deleteButton.closest(".card");
tasks = tasks.filter(task => task.id !== Number(card.dataset.id));
render();
});
function removeMarker() {
if (marker && marker.parentNode) marker.parentNode.removeChild(marker);
marker = null;
document.querySelectorAll(".column.drag-over").forEach(col => col.classList.remove("drag-over"));
}
document.addEventListener("dragstart", event => {
const card = event.target.closest(".card");
if (!card) return;
draggedId = Number(card.dataset.id);
card.classList.add("dragging");
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", String(draggedId));
marker = document.createElement("div");
marker.className = "drop-marker";
});
document.addEventListener("dragend", event => {
const card = event.target.closest(".card");
if (card) card.classList.remove("dragging");
removeMarker();
});
document.querySelectorAll(".column").forEach(column => {
const list = column.querySelector(".card-list");
column.addEventListener("dragover", event => {
if (draggedId === null) return;
event.preventDefault();
column.classList.add("drag-over");
const targetCard = event.target.closest(".card");
if (targetCard && targetCard.dataset.id !== String(draggedId)) {
const rect = targetCard.getBoundingClientRect();
const after = event.clientY > rect.top + rect.height / 2;
list.insertBefore(marker, after ? targetCard.nextSibling : targetCard);
} else if (!targetCard) {
list.appendChild(marker);
}
});
column.addEventListener("dragleave", event => {
if (!column.contains(event.relatedTarget)) column.classList.remove("drag-over");
});
column.addEventListener("drop", event => {
event.preventDefault();
if (draggedId === null) return;
const task = tasks.find(item => item.id === draggedId);
if (task) task.column = column.dataset.column;
removeMarker();
render();
});
});
render();
})();
</script>
</body>
</html>Reasoning trace (4,203 tokens)
**Implementing drag-and-drop features** I'm thinking about adding a marker as an element within the .card-list. It needs a CSS height of 5px, a blue background, some margin, and rounded borders. When dragging, I need to target the closest card for marker insertion on dragover, and then replace the marker with the dragged card on drop. I'll also add a .dragging class and make sure to clean up the markers afterward. I wonder if I need to consider touch events, but mouse requirements should be enough.