DeepSeek V4.1 Flash 1017
by DeepSeekReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 39.0 s
- Cost
- $0.0046
- Output tokens
- 7,606
- Spent thinking
- 0 tokens
- Speed
- 196.5 tok/s
- Console errors
- 0
- First token after
- 304 ms
- First visible text after
- 304 ms
- Prompt tokens
- 494
- Code size
- 23.7 KB
- Finish reason
- stop
- Served by
- InferenceNet
- Model id
deepseek/deepseek-v4.1-flash-1017- Reasoning sent
{"effort":"none"}- Max output
- 64,000 tokens
- Generated
- Nov 11, 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 · 858 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kanban Board</title>
<style>
:root {
--bg: #f0f2f5;
--panel: #ffffff;
--col-bg: #e8ebf0;
--text: #1a1d21;
--text-muted: #6b7280;
--border: #d1d5db;
--accent: #4f46e5;
--accent-hover: #4338ca;
--shadow: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.06);
--shadow-lg: 0 10px 25px rgba(0,0,0,0.16), 0 6px 10px rgba(0,0,0,0.1);
--drop-bg: rgba(79, 70, 229, 0.07);
--drop-border: #4f46e5;
--low-bg: #dbeafe; --low-text: #1d4ed8;
--med-bg: #fef3c7; --med-text: #b45309;
--high-bg: #fee2e2; --high-text: #b91c1c;
--danger: #ef4444;
--modal-overlay: rgba(0,0,0,0.45);
--input-bg: #ffffff;
--card-hover: #f9fafb;
}
body.dark {
--bg: #0f1115;
--panel: #1a1d23;
--col-bg: #14171c;
--text: #e5e7eb;
--text-muted: #9ca3af;
--border: #2d3138;
--accent: #6366f1;
--accent-hover: #818cf8;
--shadow: 0 1px 3px rgba(0,0,0,0.4), 0 1px 2px rgba(0,0,0,0.3);
--shadow-lg: 0 10px 25px rgba(0,0,0,0.6), 0 6px 10px rgba(0,0,0,0.4);
--drop-bg: rgba(99, 102, 241, 0.12);
--drop-border: #6366f1;
--low-bg: #1e3a5f; --low-text: #93c5fd;
--med-bg: #422006; --med-text: #fcd34d;
--high-bg: #451a1a; --high-text: #fca5a5;
--danger: #f87171;
--modal-overlay: rgba(0,0,0,0.65);
--input-bg: #14171c;
--card-hover: #22262e;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
transition: background 0.25s, color 0.25s;
-webkit-font-smoothing: antialiased;
}
.app {
max-width: 1240px;
margin: 0 auto;
padding: 20px 20px 40px;
}
header {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
margin-bottom: 20px;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
font-size: 22px;
font-weight: 800;
letter-spacing: -0.4px;
}
.logo-icon {
width: 32px; height: 32px;
border-radius: 9px;
background: linear-gradient(135deg, var(--accent), #a855f7);
display: grid;
place-items: center;
color: #fff;
font-size: 17px;
box-shadow: 0 2px 8px rgba(79,70,229,0.35);
flex-shrink: 0;
}
.header-spacer { flex: 1; }
.search-box {
position: relative;
min-width: 200px;
flex: 0 1 260px;
}
.search-box svg {
position: absolute;
left: 11px;
top: 50%;
transform: translateY(-50%);
width: 16px; height: 16px;
color: var(--text-muted);
pointer-events: none;
}
#search {
width: 100%;
padding: 9px 12px 9px 34px;
border-radius: 9px;
border: 1px solid var(--border);
background: var(--panel);
color: var(--text);
font-size: 14px;
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
}
#search:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(79,70,229,0.15);
}
.btn {
font-family: inherit;
font-size: 14px;
font-weight: 600;
border-radius: 9px;
border: 1px solid transparent;
cursor: pointer;
padding: 9px 16px;
display: inline-flex;
align-items: center;
gap: 7px;
transition: background 0.15s, border-color 0.15s, transform 0.08s;
white-space: nowrap;
}
.btn:active { transform: scale(0.97); }
.btn-primary {
background: var(--accent);
color: #fff;
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-ghost {
background: var(--panel);
border-color: var(--border);
color: var(--text);
}
.btn-ghost:hover { border-color: var(--accent); color: var(--accent); }
.theme-toggle {
width: 40px; height: 40px;
padding: 0;
justify-content: center;
font-size: 18px;
border-radius: 10px;
}
.board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 18px;
align-items: start;
}
.column {
background: var(--col-bg);
border-radius: 14px;
padding: 12px;
display: flex;
flex-direction: column;
min-height: 420px;
border: 2px solid transparent;
transition: background 0.2s, border-color 0.2s;
}
.column.drag-over {
background: var(--drop-bg);
border-color: var(--drop-border);
border-style: dashed;
}
.column-header {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 6px 12px;
user-select: none;
}
.col-dot {
width: 9px; height: 9px;
border-radius: 50%;
flex-shrink: 0;
}
.col-todo .col-dot { background: #6b7280; }
.col-progress .col-dot { background: #f59e0b; }
.col-done .col-dot { background: #10b981; }
.column-title {
font-size: 14px;
font-weight: 700;
letter-spacing: 0.2px;
}
.count {
margin-left: auto;
font-size: 12px;
font-weight: 700;
background: var(--panel);
color: var(--text-muted);
border-radius: 20px;
padding: 2px 9px;
min-width: 24px;
text-align: center;
border: 1px solid var(--border);
}
.card-list {
display: flex;
flex-direction: column;
gap: 10px;
flex: 1;
min-height: 60px;
}
.card {
background: var(--panel);
border-radius: 10px;
padding: 12px 13px;
box-shadow: var(--shadow);
border: 1px solid var(--border);
cursor: grab;
transition: box-shadow 0.15s, transform 0.12s, opacity 0.15s, border-color 0.15s, background 0.15s;
position: relative;
animation: cardIn 0.22s ease;
}
@keyframes cardIn {
from { opacity: 0; transform: translateY(-6px) scale(0.98); }
to { opacity: 1; transform: none; }
}
.card:hover {
background: var(--card-hover);
box-shadow: var(--shadow-lg);
border-color: var(--accent);
}
.card:active { cursor: grabbing; }
.card.dragging {
opacity: 0.4;
transform: scale(0.97);
}
.card-title {
font-size: 14px;
font-weight: 500;
line-height: 1.4;
margin-bottom: 10px;
word-break: break-word;
}
.card-footer {
display: flex;
align-items: center;
gap: 8px;
}
.badge {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 3px 9px;
border-radius: 20px;
white-space: nowrap;
}
.badge.low { background: var(--low-bg); color: var(--low-text); }
.badge.medium { background: var(--med-bg); color: var(--med-text); }
.badge.high { background: var(--high-bg); color: var(--high-text); }
.delete-btn {
margin-left: auto;
width: 26px; height: 26px;
border-radius: 7px;
border: none;
background: transparent;
color: var(--text-muted);
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.15s, color 0.15s;
opacity: 0;
flex-shrink: 0;
}
.card:hover .delete-btn,
.delete-btn:focus-visible { opacity: 1; }
.delete-btn:hover {
background: var(--high-bg);
color: var(--danger);
}
.delete-btn svg { width: 14px; height: 14px; }
.drop-indicator {
height: 3px;
border-radius: 3px;
background: var(--accent);
margin: 2px 0;
animation: pulse 0.9s ease infinite alternate;
pointer-events: none;
}
@keyframes pulse {
from { opacity: 0.5; }
to { opacity: 1; }
}
.empty-msg {
font-size: 13px;
color: var(--text-muted);
text-align: center;
padding: 22px 8px;
border: 1.5px dashed var(--border);
border-radius: 10px;
font-style: italic;
}
.no-results {
grid-column: 1 / -1;
text-align: center;
color: var(--text-muted);
font-size: 14px;
padding: 30px;
display: none;
}
/* Modal */
.modal-overlay {
position: fixed;
inset: 0;
background: var(--modal-overlay);
display: none;
align-items: center;
justify-content: center;
padding: 20px;
z-index: 100;
backdrop-filter: blur(2px);
}
.modal-overlay.open { display: flex; }
.modal {
background: var(--panel);
border-radius: 16px;
width: 100%;
max-width: 420px;
padding: 24px;
box-shadow: var(--shadow-lg);
border: 1px solid var(--border);
animation: modalIn 0.2s ease;
}
@keyframes modalIn {
from { opacity: 0; transform: translateY(12px) scale(0.97); }
to { opacity: 1; transform: none; }
}
.modal h2 {
font-size: 19px;
font-weight: 700;
margin-bottom: 18px;
}
.field { margin-bottom: 16px; }
.field label {
display: block;
font-size: 13px;
font-weight: 600;
margin-bottom: 6px;
color: var(--text-muted);
}
.field input,
.field select {
width: 100%;
padding: 10px 12px;
font-size: 14px;
font-family: inherit;
border-radius: 9px;
border: 1px solid var(--border);
background: var(--input-bg);
color: var(--text);
outline: none;
transition: border-color 0.15s, box-shadow 0.15s;
}
.field input:focus,
.field select:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(79,70,229,0.15);
}
.field input.invalid {
border-color: var(--danger);
box-shadow: 0 0 0 3px rgba(239,68,68,0.15);
}
.field-error {
font-size: 12px;
color: var(--danger);
margin-top: 5px;
display: none;
}
.field-error.show { display: block; }
.modal-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 22px;
}
@media (max-width: 820px) {
.board { grid-template-columns: 1fr; }
.column { min-height: unset; }
}
@media (max-width: 520px) {
.app { padding: 14px 12px 30px; }
.logo { font-size: 18px; }
.logo-icon { width: 28px; height: 28px; font-size: 15px; }
.search-box { flex: 1 1 100%; order: 3; }
.header-spacer { display: none; }
#addBtn { order: 2; }
.column { min-height: unset; }
.delete-btn { opacity: 1; }
}
</style>
</head>
<body>
<div class="app">
<header>
<div class="logo">
<span class="logo-icon">▦</span>
<span>Kanban</span>
</div>
<div class="header-spacer"></div>
<div class="search-box">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round">
<circle cx="11" cy="11" r="7"></circle>
<line x1="16.5" y1="16.5" x2="21" y2="21"></line>
</svg>
<input id="search" type="text" placeholder="Search cards…" aria-label="Search cards">
</div>
<button class="btn btn-primary" id="addBtn">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
Add card
</button>
<button class="btn btn-ghost theme-toggle" id="themeBtn" aria-label="Toggle theme" title="Toggle theme">🌙</button>
</header>
<div class="board" id="board">
<section class="column col-todo" data-col="todo">
<div class="column-header">
<span class="col-dot"></span>
<span class="column-title">To do</span>
<span class="count" data-count="todo">0</span>
</div>
<div class="card-list" data-list="todo"></div>
</section>
<section class="column col-progress" data-col="progress">
<div class="column-header">
<span class="col-dot"></span>
<span class="column-title">In progress</span>
<span class="count" data-count="progress">0</span>
</div>
<div class="card-list" data-list="progress"></div>
</section>
<section class="column col-done" data-col="done">
<div class="column-header">
<span class="col-dot"></span>
<span class="column-title">Done</span>
<span class="count" data-count="done">0</span>
</div>
<div class="card-list" data-list="done"></div>
</section>
<div class="no-results" id="noResults">No cards match your search.</div>
</div>
</div>
<!-- Modal -->
<div class="modal-overlay" id="modalOverlay">
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="modalTitle">
<h2 id="modalTitle">New card</h2>
<form id="cardForm" novalidate>
<div class="field">
<label for="cardTitle">Title <span style="color:var(--danger)">*</span></label>
<input id="cardTitle" type="text" placeholder="e.g. Design the landing page" autocomplete="off" maxlength="120">
<div class="field-error" id="titleError">Please enter a title.</div>
</div>
<div class="field">
<label for="cardPriority">Priority</label>
<select id="cardPriority">
<option value="low">Low</option>
<option value="medium" selected>Medium</option>
<option value="high">High</option>
</select>
</div>
<div class="field">
<label for="cardColumn">Column</label>
<select id="cardColumn">
<option value="todo" selected>To do</option>
<option value="progress">In progress</option>
<option value="done">Done</option>
</select>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-ghost" id="cancelBtn">Cancel</button>
<button type="submit" class="btn btn-primary">Add card</button>
</div>
</form>
</div>
</div>
<script>
(function () {
'use strict';
// ---------- State ----------
let cards = [
{ id: 1, title: 'Design the new onboarding flow', priority: 'high', column: 'todo' },
{ id: 2, title: 'Write API documentation for v2 endpoints', priority: 'medium', column: 'todo' },
{ id: 3, title: 'Fix responsive layout on the pricing page', priority: 'high', column: 'progress' },
{ id: 4, title: 'Set up CI pipeline with GitHub Actions', priority: 'medium', column: 'progress' },
{ id: 5, title: 'Migrate user avatars to CDN', priority: 'low', column: 'done' },
{ id: 6, title: 'Ship dark mode toggle for the dashboard', priority: 'low', column: 'done' }
];
let nextId = 7;
let searchQuery = '';
let draggedId = null;
let dragOverColumn = null;
const COLUMNS = ['todo', 'progress', 'done'];
// ---------- DOM refs ----------
const board = document.getElementById('board');
const searchInput = document.getElementById('search');
const themeBtn = document.getElementById('themeBtn');
const addBtn = document.getElementById('addBtn');
const modalOverlay = document.getElementById('modalOverlay');
const modal = modalOverlay.querySelector('.modal');
const cardForm = document.getElementById('cardForm');
const cardTitle = document.getElementById('cardTitle');
const cardPriority = document.getElementById('cardPriority');
const cardColumn = document.getElementById('cardColumn');
const titleError = document.getElementById('titleError');
const cancelBtn = document.getElementById('cancelBtn');
const noResults = document.getElementById('noResults');
const lists = {};
const counts = {};
COLUMNS.forEach(function (col) {
lists[col] = document.querySelector('[data-list="' + col + '"]');
counts[col] = document.querySelector('[data-count="' + col + '"]');
});
// ---------- Rendering ----------
function priorityLabel(p) {
return p.charAt(0).toUpperCase() + p.slice(1);
}
function matchesSearch(card) {
if (!searchQuery) return true;
return card.title.toLowerCase().indexOf(searchQuery) !== -1;
}
function createCardElement(card) {
const el = document.createElement('div');
el.className = 'card';
el.draggable = true;
el.dataset.id = String(card.id);
const title = document.createElement('div');
title.className = 'card-title';
title.textContent = card.title;
const footer = document.createElement('div');
footer.className = 'card-footer';
const badge = document.createElement('span');
badge.className = 'badge ' + card.priority;
badge.textContent = priorityLabel(card.priority);
const del = document.createElement('button');
del.className = 'delete-btn';
del.type = 'button';
del.setAttribute('aria-label', 'Delete card: ' + card.title);
del.title = 'Delete card';
del.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>';
del.addEventListener('click', function (e) {
e.stopPropagation();
deleteCard(card.id);
});
footer.appendChild(badge);
footer.appendChild(del);
el.appendChild(title);
el.appendChild(footer);
// Drag events
el.addEventListener('dragstart', function (e) {
draggedId = card.id;
el.classList.add('dragging');
e.dataTransfer.effectAllowed = 'move';
try { e.dataTransfer.setData('text/plain', String(card.id)); } catch (_) {}
});
el.addEventListener('dragend', function () {
el.classList.remove('dragging');
draggedId = null;
clearDropIndicators();
COLUMNS.forEach(function (c) {
document.querySelector('.column[data-col="' + c + '"]').classList.remove('drag-over');
});
});
return el;
}
function render() {
let visibleTotal = 0;
COLUMNS.forEach(function (col) {
const list = lists[col];
list.innerHTML = '';
const colCards = cards.filter(function (c) { return c.column === col; });
const visibleCards = colCards.filter(matchesSearch);
visibleTotal += visibleCards.length;
counts[col].textContent = String(colCards.length);
visibleCards.forEach(function (card) {
list.appendChild(createCardElement(card));
});
if (visibleCards.length === 0) {
const msg = document.createElement('div');
msg.className = 'empty-msg';
msg.textContent = colCards.length > 0 ? 'No matches' : 'No cards yet';
list.appendChild(msg);
}
});
noResults.style.display = visibleTotal === 0 ? 'block' : 'none';
}
// ---------- Card actions ----------
function deleteCard(id) {
cards = cards.filter(function (c) { return c.id !== id; });
render();
}
function addCard(title, priority, column) {
cards.push({
id: nextId++,
title: title,
priority: priority,
column: column
});
render();
}
// ---------- Drag & Drop targets ----------
function clearDropIndicators() {
document.querySelectorAll('.drop-indicator').forEach(function (n) {
n.parentNode.removeChild(n);
});
}
function getInsertionPoint(list, clientY) {
const cardEls = Array.prototype.slice.call(list.querySelectorAll('.card:not(.dragging)'));
for (let i = 0; i < cardEls.length; i++) {
const rect = cardEls[i].getBoundingClientRect();
const mid = rect.top + rect.height / 2;
if (clientY < mid) return cardEls[i];
}
return null;
}
COLUMNS.forEach(function (col) {
const columnEl = document.querySelector('.column[data-col="' + col + '"]');
const list = lists[col];
columnEl.addEventListener('dragover', function (e) {
if (draggedId === null) return;
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
if (dragOverColumn !== col) {
dragOverColumn = col;
COLUMNS.forEach(function (c) {
document.querySelector('.column[data-col="' + c + '"]')
.classList.toggle('drag-over', c === col);
});
}
clearDropIndicators();
const indicator = document.createElement('div');
indicator.className = 'drop-indicator';
const target = getInsertionPoint(list, e.clientY);
if (target) {
list.insertBefore(indicator, target);
} else {
list.appendChild(indicator);
}
});
columnEl.addEventListener('dragleave', function (e) {
if (e.relatedTarget && columnEl.contains(e.relatedTarget)) return;
columnEl.classList.remove('drag-over');
if (dragOverColumn === col) dragOverColumn = null;
clearDropIndicators();
});
columnEl.addEventListener('drop', function (e) {
if (draggedId === null) return;
e.preventDefault();
const card = cards.find(function (c) { return c.id === draggedId; });
if (!card) return;
// Compute target index among the destination column's cards
const indicator = list.querySelector('.drop-indicator');
let insertIndex;
if (indicator) {
const siblings = Array.prototype.slice.call(list.children);
insertIndex = siblings.indexOf(indicator);
// number of real cards before the indicator
let realBefore = 0;
for (let i = 0; i < insertIndex; i++) {
if (siblings[i].classList && siblings[i].classList.contains('card')) realBefore++;
}
insertIndex = realBefore;
} else {
insertIndex = cards.filter(function (c) {
return c.column === col && c.id !== card.id;
}).length;
}
// Reorder cards array: remove, then insert into position within destination column
const movingId = card.id;
card.column = col;
// Rebuild cards order preserving per-column order
const others = cards.filter(function (c) { return c.id !== movingId; });
const destCards = others.filter(function (c) { return c.column === col; });
const rest = others.filter(function (c) { return c.column !== col; });
const idx = Math.max(0, Math.min(insertIndex, destCards.length));
destCards.splice(idx, 0, card);
// Reconstruct: keep a stable global order by column
const byColumn = {};
COLUMNS.forEach(function (c) { byColumn[c] = []; });
rest.forEach(function (c) { byColumn[c.column].push(c); });
byColumn[col] = destCards;
cards = [];
COLUMNS.forEach(function (c) {
byColumn[c].forEach(function (cc) { cards.push(cc); });
});
clearDropIndicators();
COLUMNS.forEach(function (c) {
document.querySelector('.column[data-col="' + c + '"]').classList.remove('drag-over');
});
dragOverColumn = null;
draggedId = null;
render();
});
});
// ---------- Search ----------
searchInput.addEventListener('input', function () {
searchQuery = searchInput.value.trim().toLowerCase();
render();
});
// ---------- Theme ----------
function setTheme(dark) {
document.body.classList.toggle('dark', dark);
themeBtn.textContent = dark ? '☀️' : '🌙';
themeBtn.title = dark ? 'Switch to light theme' : 'Switch to dark theme';
}
themeBtn.addEventListener('click', function () {
setTheme(!document.body.classList.contains('dark'));
});
// ---------- Modal ----------
function openModal() {
cardForm.reset();
cardPriority.value = 'medium';
cardColumn.value = 'todo';
cardTitle.classList.remove('invalid');
titleError.classList.remove('show');
modalOverlay.classList.add('open');
setTimeout(function () { cardTitle.focus(); }, 30);
}
function closeModal() {
modalOverlay.classList.remove('open');
}
addBtn.addEventListener('click', openModal);
cancelBtn.addEventListener('click', closeModal);
modalOverlay.addEventListener('mousedown', function (e) {
if (e.target === modalOverlay) closeModal();
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && modalOverlay.classList.contains('open')) closeModal();
});
cardForm.addEventListener('submit', function (e) {
e.preventDefault();
const title = cardTitle.value.trim();
if (!title) {
cardTitle.classList.add('invalid');
titleError.classList.add('show');
cardTitle.focus();
return;
}
addCard(title, cardPriority.value, cardColumn.value);
closeModal();
});
cardTitle.addEventListener('input', function () {
if (cardTitle.value.trim()) {
cardTitle.classList.remove('invalid');
titleError.classList.remove('show');
}
});
// ---------- Init ----------
render();
setTheme(false);
})();
</script>
</body>
</html>