GPT-6 Luna
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 26 s
- Cost
- $0.0051
- Output tokens
- 10,152
- Spent thinking
- 1,598 tokens
- Speed
- 126.3 tok/s
- Console errors
- 0
- First token after
- 5.1 s
- First visible text after
- 24.1 s
- Prompt tokens
- 493
- Code size
- 27.7 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna- Reasoning sent
{"effort":"medium"}- Max output
- 64,000 tokens
- Generated
- Nov 10, 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 · 839 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flowday — Product launch</title>
<style>
:root {
color-scheme: light;
--page: #f6f7fb;
--surface: #ffffff;
--column: #eef1f7;
--ink: #20283a;
--muted: #7b8497;
--line: #e6e9f0;
--accent: #6258db;
--accent-hover: #5147c8;
--shadow: 0 5px 18px rgba(27, 38, 70, .055);
--high: #d94b58;
--high-bg: #fff0f1;
--medium: #c37b16;
--medium-bg: #fff5e5;
--low: #27896b;
--low-bg: #e8f7f1;
}
* { box-sizing: border-box; }
[hidden] { display: none !important; }
body {
margin: 0;
min-width: 320px;
background: var(--page);
color: var(--ink);
font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 14px;
-webkit-font-smoothing: antialiased;
transition: background .2s ease, color .2s ease;
}
body.dark {
color-scheme: dark;
--page: #171a24;
--surface: #222633;
--column: #1d212d;
--ink: #edf0f8;
--muted: #9aa3b6;
--line: #343949;
--accent: #948cff;
--accent-hover: #aaa4ff;
--shadow: 0 6px 18px rgba(0, 0, 0, .18);
--high: #ff858c;
--high-bg: #462b34;
--medium: #f2bd65;
--medium-bg: #433829;
--low: #6fd2ab;
--low-bg: #243f39;
}
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 36px;
gap: 17px;
}
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
font-weight: 750;
font-size: 17px;
letter-spacing: -.45px;
white-space: nowrap;
}
.brand-mark {
width: 31px;
height: 31px;
display: grid;
place-items: center;
color: white;
border-radius: 10px;
background: linear-gradient(145deg, #796cf1, #5148c4);
box-shadow: 0 4px 9px rgba(92, 81, 211, .24);
}
.brand-mark svg { width: 18px; height: 18px; }
.top-divider { width: 1px; height: 25px; background: var(--line); }
.workspace-label { color: var(--muted); font-size: 13px; }
.top-spacer { flex: 1; }
.workspace-pill {
display: flex;
align-items: center;
gap: 8px;
color: var(--muted);
font-size: 12px;
font-weight: 600;
padding: 8px 11px;
border: 1px solid var(--line);
border-radius: 9px;
}
.workspace-pill svg { width: 15px; height: 15px; }
main {
width: min(1192px, calc(100% - 64px));
margin: 0 auto;
padding: 33px 0 42px;
}
.heading-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
margin-bottom: 23px;
}
.eyebrow {
display: flex;
align-items: center;
gap: 8px;
color: var(--muted);
font-size: 12px;
font-weight: 600;
margin-bottom: 9px;
}
.eyebrow svg { width: 14px; height: 14px; }
h1 {
margin: 0;
font-size: 29px;
line-height: 1.15;
letter-spacing: -.9px;
font-weight: 720;
}
.subtitle {
margin: 8px 0 0;
color: var(--muted);
font-size: 13px;
}
.team {
display: flex;
align-items: center;
gap: 10px;
color: var(--muted);
font-size: 12px;
white-space: nowrap;
}
.avatar-stack { display: flex; padding-left: 5px; }
.avatar {
width: 30px;
height: 30px;
border: 2px solid var(--page);
border-radius: 50%;
display: grid;
place-items: center;
margin-left: -6px;
color: #fff;
font-size: 9px;
font-weight: 750;
letter-spacing: -.2px;
}
.av-lilac { background: #9384d8; }
.av-blue { background: #5e9cbd; }
.av-coral { background: #db8978; }
.av-green { background: #5b9a80; }
.av-gold { background: #bd9355; }
.toolbar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.search {
position: relative;
width: 270px;
height: 39px;
}
.search svg {
position: absolute;
left: 12px;
top: 11px;
width: 17px;
height: 17px;
color: var(--muted);
pointer-events: none;
}
.search input {
width: 100%;
height: 100%;
padding: 0 12px 0 38px;
border: 1px solid var(--line);
border-radius: 9px;
color: var(--ink);
background: var(--surface);
outline: none;
transition: border .15s, box-shadow .15s;
font-size: 13px;
}
.search input::placeholder { color: #9aa2b1; }
.search input:focus, .field input:focus, .field select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 15%, transparent);
}
.toolbar-spacer { flex: 1; }
.btn {
height: 39px;
border: 1px solid var(--line);
border-radius: 9px;
background: var(--surface);
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 0 12px;
cursor: pointer;
transition: background .15s, border .15s, transform .15s;
font-size: 12px;
font-weight: 650;
white-space: nowrap;
}
.btn:hover { background: var(--column); }
.btn:active { transform: translateY(1px); }
.btn svg { width: 16px; height: 16px; }
.theme-btn { color: var(--muted); }
.add-btn {
background: var(--accent);
border-color: var(--accent);
color: #fff;
padding: 0 14px;
box-shadow: 0 3px 8px rgba(91, 80, 206, .18);
}
.add-btn:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.board {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
align-items: start;
gap: 17px;
}
.column {
min-width: 0;
min-height: 440px;
padding: 14px 13px 15px;
border: 1px solid transparent;
border-radius: 13px;
background: var(--column);
transition: background .18s, border-color .18s;
}
.column.drag-over {
border: 1px dashed var(--accent);
background: color-mix(in srgb, var(--accent) 7%, var(--column));
}
.column-header {
display: flex;
align-items: center;
gap: 9px;
margin: 1px 2px 13px;
}
.status-dot { width: 9px; height: 9px; border-radius: 50%; flex: none; }
.todo-dot { background: #929bab; }
.progress-dot { background: #7180e8; }
.done-dot { background: #55b18b; }
.column-header h2 {
margin: 0;
font-size: 13px;
letter-spacing: -.1px;
font-weight: 700;
}
.count {
min-width: 21px;
height: 21px;
padding: 0 6px;
display: grid;
place-items: center;
border-radius: 7px;
color: var(--muted);
background: color-mix(in srgb, var(--surface) 72%, transparent);
font-size: 11px;
font-weight: 650;
}
.column-spacer { flex: 1; }
.column-add {
width: 27px;
height: 27px;
display: grid;
place-items: center;
border: 0;
border-radius: 7px;
color: var(--muted);
background: transparent;
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; }
.card {
position: relative;
padding: 14px 14px 12px;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--surface);
box-shadow: var(--shadow);
cursor: grab;
transition: transform .16s, box-shadow .16s, border-color .16s, opacity .15s;
user-select: none;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(27, 38, 70, .09);
border-color: color-mix(in srgb, var(--accent) 28%, var(--line));
}
.card.dragging { opacity: .42; transform: scale(.98); cursor: grabbing; }
.card-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-bottom: 10px;
}
.priority {
display: inline-flex;
align-items: center;
gap: 5px;
border-radius: 5px;
padding: 4px 7px;
font-size: 10px;
font-weight: 700;
line-height: 1;
text-transform: capitalize;
}
.priority::before { content: ""; width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
.priority.high { color: var(--high); background: var(--high-bg); }
.priority.medium { color: var(--medium); background: var(--medium-bg); }
.priority.low { color: var(--low); background: var(--low-bg); }
.delete-btn {
width: 25px;
height: 25px;
border: 0;
border-radius: 6px;
display: grid;
place-items: center;
background: transparent;
color: #a0a7b5;
cursor: pointer;
opacity: .48;
transition: opacity .15s, background .15s, color .15s;
}
.card:hover .delete-btn, .delete-btn:focus-visible { opacity: 1; }
.delete-btn:hover { color: #d94b58; background: var(--high-bg); }
.delete-btn svg { width: 14px; height: 14px; }
.card-title {
margin: 0 0 13px;
font-size: 13px;
line-height: 1.42;
font-weight: 650;
letter-spacing: -.12px;
overflow-wrap: anywhere;
user-select: text;
}
.card-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding-top: 10px;
border-top: 1px solid var(--line);
color: var(--muted);
font-size: 10.5px;
}
.due {
display: inline-flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
.due svg { width: 13px; height: 13px; }
.assignee {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.assignee .avatar {
width: 22px;
height: 22px;
margin: 0;
border: 0;
font-size: 8px;
flex: none;
}
.assignee-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.drop-indicator {
min-height: 40px;
display: none;
align-items: center;
justify-content: center;
border: 1px dashed color-mix(in srgb, var(--accent) 55%, transparent);
border-radius: 9px;
color: var(--accent);
background: color-mix(in srgb, var(--accent) 6%, transparent);
font-size: 11px;
font-weight: 650;
}
.column.drag-over .drop-indicator { display: flex; }
.empty-state {
padding: 25px 12px;
text-align: center;
color: var(--muted);
font-size: 12px;
line-height: 1.5;
border: 1px dashed var(--line);
border-radius: 9px;
background: color-mix(in srgb, var(--surface) 45%, transparent);
}
.modal-backdrop {
position: fixed;
inset: 0;
z-index: 10;
display: grid;
place-items: center;
padding: 20px;
background: rgba(19, 23, 37, .48);
backdrop-filter: blur(3px);
}
.modal {
width: min(420px, 100%);
padding: 24px;
border: 1px solid var(--line);
border-radius: 15px;
background: var(--surface);
box-shadow: 0 20px 70px rgba(8, 12, 30, .26);
animation: pop .16s ease-out;
}
@keyframes pop { from { transform: translateY(7px); opacity: .7; } to { transform: none; opacity: 1; } }
.modal-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 22px;
}
.modal-head h2 { margin: 0; font-size: 19px; letter-spacing: -.4px; }
.modal-head p { margin: 6px 0 0; color: var(--muted); font-size: 12px; }
.close-btn {
width: 30px;
height: 30px;
display: grid;
place-items: center;
border: 0;
border-radius: 8px;
background: transparent;
color: var(--muted);
cursor: pointer;
}
.close-btn:hover { background: var(--column); }
.close-btn svg { width: 17px; height: 17px; }
.field { margin-bottom: 17px; }
.field label {
display: block;
margin: 0 0 7px;
font-size: 12px;
font-weight: 650;
}
.field input, .field select {
width: 100%;
height: 41px;
padding: 0 11px;
outline: none;
border: 1px solid var(--line);
border-radius: 8px;
color: var(--ink);
background: var(--surface);
font-size: 13px;
}
.field input::placeholder { color: #9aa2b1; }
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 9px;
margin-top: 24px;
}
.cancel-btn { padding: 0 14px; }
.submit-btn { padding: 0 15px; }
@media (max-width: 760px) {
.topbar { height: 60px; padding: 0 20px; gap: 12px; }
.workspace-pill { padding: 7px; }
.workspace-pill span { display: none; }
main { width: min(100% - 36px, 560px); padding-top: 26px; }
.heading-row { margin-bottom: 18px; }
.team { display: none; }
.board { grid-template-columns: 1fr; gap: 13px; }
.column { min-height: 0; padding: 13px; }
.column-header { margin-bottom: 11px; }
.toolbar { margin-bottom: 17px; }
.search { flex: 1; min-width: 0; }
}
@media (max-width: 430px) {
.topbar { padding: 0 16px; }
.top-divider, .workspace-label { display: none; }
main { width: calc(100% - 32px); padding-top: 23px; }
h1 { font-size: 25px; }
.subtitle { font-size: 12px; }
.toolbar { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 7px; }
.search { width: auto; grid-column: 1 / -1; }
.toolbar-spacer { display: none; }
.btn { height: 37px; }
.theme-btn { width: 39px; padding: 0; }
.add-btn { padding: 0 11px; }
.column { border-radius: 11px; }
.card { padding: 13px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; }
}
</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.5h5v9H5zM14 4h5v7h-5zM14 15h5v5h-5z" stroke="currentColor" stroke-width="2" stroke-linejoin="round"/></svg>
</span>
<span>flowday</span>
</div>
<span class="top-divider"></span>
<span class="workspace-label">Workspace</span>
<div class="top-spacer"></div>
<div class="workspace-pill">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 5.5A1.5 1.5 0 0 1 5.5 4h13A1.5 1.5 0 0 1 20 5.5v13a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 18.5v-13Z" stroke="currentColor" stroke-width="1.7"/><path d="M4 9h16M9 9v11" stroke="currentColor" stroke-width="1.7"/></svg>
<span>Product team</span>
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="m8 10 4 4 4-4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
</div>
</header>
<main>
<section class="heading-row">
<div>
<div class="eyebrow">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4 5.5A1.5 1.5 0 0 1 5.5 4h13A1.5 1.5 0 0 1 20 5.5v13a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 18.5v-13Z" stroke="currentColor" stroke-width="1.8"/><path d="M4 9h16M9 9v11" stroke="currentColor" stroke-width="1.8"/></svg>
Boards <span aria-hidden="true">/</span> Marketing
</div>
<h1>Product launch</h1>
<p class="subtitle">Keep the launch moving, together.</p>
</div>
<div class="team" aria-label="Board members">
<div class="avatar-stack">
<span class="avatar av-lilac">AM</span>
<span class="avatar av-blue">JL</span>
<span class="avatar av-coral">SK</span>
<span class="avatar av-green">TW</span>
<span class="avatar av-gold">+2</span>
</div>
<span>6 members</span>
</div>
</section>
<section class="toolbar" aria-label="Board tools">
<label class="search">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="10.8" cy="10.8" r="6.6" stroke="currentColor" stroke-width="1.8"/><path d="m16 16 4.2 4.2" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
<input id="searchInput" type="search" placeholder="Search cards…" aria-label="Search cards by title">
</label>
<div class="toolbar-spacer"></div>
<button class="btn theme-btn" id="themeToggle" type="button" aria-label="Switch to dark theme">
<svg id="themeIcon" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="3.7" stroke="currentColor" stroke-width="1.7"/><path d="M12 2.5v2M12 19.5v2M4.93 4.93l1.42 1.42m11.3 11.3 1.42 1.42M2.5 12h2m15 0h2M4.93 19.07l1.42-1.42m11.3-11.3 1.42-1.42" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
<span id="themeLabel">Theme</span>
</button>
<button class="btn add-btn" id="addCardBtn" type="button">
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
Add card
</button>
</section>
<section class="board" id="board" aria-label="Kanban board"></section>
</main>
<div class="modal-backdrop" id="modalBackdrop" hidden>
<section class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
<div class="modal-head">
<div>
<h2 id="modalTitle">Add a card</h2>
<p>Add a task to your launch board.</p>
</div>
<button class="close-btn" id="closeModal" type="button" aria-label="Close dialog">
<svg viewBox="0 0 24 24" fill="none"><path d="m6 6 12 12M18 6 6 18" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
</button>
</div>
<form id="cardForm">
<div class="field">
<label for="cardTitle">Title</label>
<input id="cardTitle" name="title" type="text" maxlength="90" placeholder="What needs to get done?" required>
</div>
<div class="field">
<label for="cardPriority">Priority</label>
<select id="cardPriority" name="priority">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
</select>
</div>
<div class="modal-actions">
<button class="btn cancel-btn" id="cancelModal" type="button">Cancel</button>
<button class="btn add-btn submit-btn" type="submit">Create card</button>
</div>
</form>
</section>
</div>
<script>
const columns = [
{ id: "todo", name: "To do", dot: "todo-dot" },
{ id: "progress", name: "In progress", dot: "progress-dot" },
{ id: "done", name: "Done", dot: "done-dot" }
];
let cards = [
{ id: "c1", title: "Finalize launch email sequence", priority: "high", column: "todo", due: "Jun 12", owner: "Ava Morgan", initials: "AM", avatar: "av-lilac" },
{ id: "c2", title: "Gather customer quotes for landing page", priority: "medium", column: "todo", due: "Jun 14", owner: "Jordan Lee", initials: "JL", avatar: "av-blue" },
{ id: "c3", title: "Build onboarding checklist in-app", priority: "high", column: "progress", due: "Jun 10", owner: "Sam Kim", initials: "SK", avatar: "av-coral" },
{ id: "c4", title: "QA pricing page on mobile", priority: "medium", column: "progress", due: "Jun 11", owner: "Taylor Woods", initials: "TW", avatar: "av-green" },
{ id: "c5", title: "Draft launch announcement post", priority: "low", column: "done", due: "Jun 06", owner: "Ava Morgan", initials: "AM", avatar: "av-lilac" },
{ id: "c6", title: "Confirm webinar speakers & schedule", priority: "medium", column: "done", due: "Jun 05", owner: "Jordan Lee", initials: "JL", avatar: "av-blue" }
];
const board = document.getElementById("board");
const searchInput = document.getElementById("searchInput");
const modalBackdrop = document.getElementById("modalBackdrop");
const titleInput = document.getElementById("cardTitle");
let draggedId = null;
const svg = {
plus: '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
trash: '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M4.5 7h15M9 7V4.8h6V7m3.5 0-.8 12.5H6.3L5.5 7M10 10.5v5.5m4-5.5v5.5" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>',
calendar: '<svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="4" y="5.5" width="16" height="15" rx="2" stroke="currentColor" stroke-width="1.7"/><path d="M8 3.5v4M16 3.5v4M4 10h16" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>'
};
function make(tag, className, text) {
const node = document.createElement(tag);
if (className) node.className = className;
if (text !== undefined) node.textContent = text;
return node;
}
function render() {
const query = searchInput.value.trim().toLowerCase();
board.replaceChildren();
columns.forEach(column => {
const matching = cards.filter(card =>
card.column === column.id && card.title.toLowerCase().includes(query)
);
const section = make("section", "column");
section.dataset.column = column.id;
section.setAttribute("aria-label", column.name);
const header = make("div", "column-header");
header.append(make("span", `status-dot ${column.dot}`));
header.append(make("h2", "", column.name));
header.append(make("span", "count", String(matching.length)));
header.append(make("span", "column-spacer"));
const quickAdd = make("button", "column-add");
quickAdd.type = "button";
quickAdd.setAttribute("aria-label", `Add a card to ${column.name}`);
quickAdd.innerHTML = svg.plus;
quickAdd.addEventListener("click", () => openModal(column.id));
header.append(quickAdd);
section.append(header);
const list = make("div", "card-list");
matching.forEach(card => list.append(createCard(card)));
if (!matching.length) {
const empty = make("div", "empty-state", query ? "No cards match your search." : "Drop a card here to keep things moving.");
list.append(empty);
}
const indicator = make("div", "drop-indicator", "Drop card here");
list.append(indicator);
section.append(list);
section.addEventListener("dragover", event => {
if (!draggedId) return;
event.preventDefault();
event.dataTransfer.dropEffect = "move";
section.classList.add("drag-over");
});
section.addEventListener("dragleave", event => {
if (!section.contains(event.relatedTarget)) section.classList.remove("drag-over");
});
section.addEventListener("drop", event => {
event.preventDefault();
section.classList.remove("drag-over");
if (!draggedId) return;
const card = cards.find(item => item.id === draggedId);
if (card) card.column = column.id;
draggedId = null;
render();
});
board.append(section);
});
}
function createCard(card) {
const article = make("article", "card");
article.draggable = true;
article.dataset.id = card.id;
article.setAttribute("aria-label", `${card.title}, ${card.priority} priority. Drag to move.`);
article.addEventListener("dragstart", event => {
draggedId = card.id;
article.classList.add("dragging");
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/plain", card.id);
});
article.addEventListener("dragend", () => {
draggedId = null;
document.querySelectorAll(".drag-over").forEach(el => el.classList.remove("drag-over"));
article.classList.remove("dragging");
});
const top = make("div", "card-top");
top.append(make("span", `priority ${card.priority}`, card.priority));
const remove = make("button", "delete-btn");
remove.type = "button";
remove.setAttribute("aria-label", `Delete ${card.title}`);
remove.title = "Delete card";
remove.innerHTML = svg.trash;
remove.addEventListener("click", event => {
event.stopPropagation();
cards = cards.filter(item => item.id !== card.id);
render();
});
top.append(remove);
article.append(top);
article.append(make("h3", "card-title", card.title));
const meta = make("div", "card-meta");
const due = make("span", "due");
due.innerHTML = svg.calendar;
due.append(document.createTextNode(card.due));
meta.append(due);
const assignee = make("span", "assignee");
assignee.append(make("span", `avatar ${card.avatar}`, card.initials));
assignee.append(make("span", "assignee-name", card.owner));
meta.append(assignee);
article.append(meta);
return article;
}
function openModal(column = "todo") {
modalBackdrop.dataset.column = column;
modalBackdrop.hidden = false;
titleInput.value = "";
document.getElementById("cardPriority").value = "medium";
requestAnimationFrame(() => titleInput.focus());
}
function closeModal() {
modalBackdrop.hidden = true;
document.getElementById("addCardBtn").focus();
}
document.getElementById("addCardBtn").addEventListener("click", () => openModal());
document.getElementById("closeModal").addEventListener("click", closeModal);
document.getElementById("cancelModal").addEventListener("click", closeModal);
modalBackdrop.addEventListener("click", event => {
if (event.target === modalBackdrop) closeModal();
});
document.addEventListener("keydown", event => {
if (event.key === "Escape" && !modalBackdrop.hidden) closeModal();
});
document.getElementById("cardForm").addEventListener("submit", event => {
event.preventDefault();
const title = titleInput.value.trim();
if (!title) {
titleInput.focus();
return;
}
cards.unshift({
id: `c${Date.now()}${Math.floor(Math.random() * 1000)}`,
title,
priority: document.getElementById("cardPriority").value,
column: modalBackdrop.dataset.column || "todo",
due: "No date",
owner: "You",
initials: "YO",
avatar: "av-lilac"
});
searchInput.value = "";
render();
closeModal();
});
searchInput.addEventListener("input", render);
const themeToggle = document.getElementById("themeToggle");
themeToggle.addEventListener("click", () => {
const dark = document.body.classList.toggle("dark");
document.getElementById("themeLabel").textContent = dark ? "Theme" : "Theme";
themeToggle.setAttribute("aria-label", dark ? "Switch to light theme" : "Switch to dark theme");
document.getElementById("themeIcon").innerHTML = dark
? '<path d="M20.2 15.3A8.2 8.2 0 0 1 8.7 3.8a8.7 8.7 0 1 0 11.5 11.5Z" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>'
: '<circle cx="12" cy="12" r="3.7" stroke="currentColor" stroke-width="1.7"/><path d="M12 2.5v2M12 19.5v2M4.93 4.93l1.42 1.42m11.3 11.3 1.42 1.42M2.5 12h2m15 0h2M4.93 19.07l1.42-1.42m11.3-11.3 1.42-1.42" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/>';
});
render();
</script>
</body>
</html>Reasoning trace (1,598 tokens)
**Designing an Interactive UI** I’m focusing on producing polished HTML for an interactive board UI. It should include features like drag-and-drop functionality, modals, and a responsive design with a theme toggle. I’m thinking of a sidebar and a workspace layout of 1280x800, featuring a header, search bar, and an option to add cards arranged in three columns with realistic priority indicators. I want a light theme with nice colors, supporting various interactions and ensuring smooth usability across devices.**Designing Mobile and Desktop Layout** I’m considering a vertical layout for mobile columns, which seems fine. The app's max width should be 1200, and the header will have a flexible wrap. I’m planning a subtle background for the board columns and cards, with the search functionality in the header. I need to ensure everything looks nice for a screenshot, especially focusing on the overall layout at 1280. The main title, toolbar, and column setups are crucial for a clear and compact design!**Defining Card Attributes** I'm thinking about how each card should include essential elements like a priority pill, a title, and a metadata row with an icon and date. I want to add an avatar for the assigned user at the bottom, and maybe a check indicator for completed tasks. The requirements focus on having distinct titles and priorities. I'll include features like a modal for adding titles and priorities, and a delete button that appears on hover, keeping it subtle. I’ll also consider a drag handle for ease of movement!**Designing drag interactions** I'm working on making cards draggable, setting up data transfer and ensuring the source ID is captured. During drag events, I want to visually indicate where the card can be dropped with a dashed border and placeholder text saying "Drop card here." A clear drop indicator will help users, and I plan to show a drop hint centered at the bottom of the column. I'll manage card movements across columns while ensuring the visual cues are clear and functional!**Optimizing drag-and-drop functionality** I'm thinking about how to handle the drag-and-drop interactions better. During the drag over, I need to prevent default actions if there's a drop indicator at the end. When a card is dropped, it should append to the column and then hide the drop indicator. I can't allow reordering within cards, but I could track target positions when moving between columns. Using textContent for dynamic content helps prevent XSS. I want the search feature to accurately count visible cards. Plus, I should ensure the modal is accessible with close options.**Creating a responsive UI with theme toggle** I'm designing a UI where I can toggle between light and dark themes by modifying the body class or the document root. The icon toggle button will be central to this. For mobile, I'll keep the layout with the header on top and a toolbar grid. The columns need to be stacked and the board cards should have no horizontal overflow. Custom SVG icons will enhance the design, and I must ensure the script executes after the DOM loads while keeping it all responsive!**Considering icon creation** I'm thinking about creating icons manually. It seems like the counts should always reflect totals without filtering, but maybe I could filter counts per column as needed. Searching could help show matching counts, and it's sensible to have a dynamic count for visible board cards. I wonder if an empty search could show helpful tips. For the drag-and-drop functionality, I'll need to consider the effects and mobile usability, including using modals for better visibility.**Coding dark mode toggle** I need to create a button for dark mode that includes SVG and a label saying "Dark mode." Upon toggling, I should set both the text and the icon, perhaps using fixed icons like a sun for light mode and a moon for dark mode. I also need to incorporate a way to close the modal. It looks like I’ll only need a code fence for this task. Let’s get to coding!