Models 44

DeepSeek V4.1 Flash 1017

by DeepSeekReasoningClean
Open in new window

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>